This guide walks through the complete Open Ontology DSL using an HR domain example. You'll learn how to define schemas, relationships, entities, rules, actions, workflows, and integrations.
We'll model a simple HR system with:
Object types define the schema for your entities. Use Ontology.ObjectTypes() to declare them:
| Type | Builder | Description |
|---|---|---|
| string | Attr.string() | Text values |
| number | Attr.number() | Numeric values (including timestamps) |
| boolean | Attr.boolean() | True/false |
| enum | Attr.enum([...]) | Constrained set of values |
| ref | Attr.ref() | Reference to another entity |
| json | Attr.json() | Arbitrary JSON data |
Relationships define how object types connect. Use Ontology.Relationships():
| Builder | Meaning |
|---|---|
r.one("Type") | Single reference (many-to-one) |
r.many("Type") | Multiple references (many-to-many) |
Create entity instances with Ontology.Objects():
Note: Use o.ref("entity-id") to create references to other entities.
Link types define typed relationships that can have properties. Unlike simple relationships, links are first-class entities:
| Value | Meaning |
|---|---|
one-to-one | Each source has at most one target |
one-to-many | Each source can have multiple targets |
many-to-one | Multiple sources point to one target |
many-to-many | No restrictions |
Create link instances with Ontology.Links():
Rules define compliance requirements using Datalog queries. When a rule's query returns results, violations are created:
| Field | Description |
|---|---|
name | Unique identifier |
displayName | Human-readable name |
severity | error, warning, or info |
entityType | Type of entity being validated |
query | Datalog query that finds violations |
message | Template with {{?var}} placeholders |
Actions define operations that users can perform. They support wizard (multi-step) and instant modes:
| Type | Description |
|---|---|
text | Single-line text input |
email | Email with validation |
number | Numeric input |
date | Date picker |
select | Dropdown from options |
boolean | Checkbox |
entity-ref | Reference to another entity |
textarea | Multi-line text |
Workflows define multi-step processes as DAGs (directed acyclic graphs):
| Type | Description |
|---|---|
automation | Execute code/API calls |
human-task | Create a task for a person |
query | Run a Datalog query |
decision | Conditional branching |
ai | AI-powered operations |
Connect external systems using Sources, Mappings, and Match Rules:
Map external fields to your ontology:
Resolve incoming records to existing entities:
Synced entities automatically include provenance attributes:
Query your ontology using Datalog:
Assemble all components with Ontology.build():
The compiled ontology contains:
import { Ontology, Attr } from "@open-ontology/core/dsl";const objectTypes = Ontology.ObjectTypes({ Department: { description: "Organizational unit", attributes: { name: Attr.string().required().description("Department name"), code: Attr.string().required().unique().description("Short code (e.g., ENG)"), budget: Attr.number().description("Annual budget"), }, }, Employee: { description: "Company employee", attributes: { name: Attr.string().required(), email: Attr.string().required().format("email"), title: Attr.string().required(), startDate: Attr.number().required().description("Unix timestamp"), status: Attr.enum(["onboarding", "active", "offboarding"]).required(), }, }, Contract: { description: "Employment contract", attributes: { signedDate: Attr.number().description("Unix timestamp when signed"), type: Attr.enum(["full-time", "part-time", "contractor"]).required(), documentUrl: Attr.string().format("url"), }, }, LeaveRequest: { description: "Time-off request", attributes: { startDate: Attr.number().required(), endDate: Attr.number().required(), type: Attr.enum(["vacation", "sick", "personal"]).required(), status: Attr.enum(["pending", "approved", "rejected"]).required(), reason: Attr.string(), }, },});Attr.string() .required() // Must be present .unique() // Must be unique across entities .format("email") // Validate format (email, url, uuid, phone, date) .description("...") // Documentation .validation({ min, max }) // Custom validationconst relationships = Ontology.Relationships(objectTypes, (r) => ({ Employee: { department: r.one("Department").required().description("Primary department"), manager: r.one("Employee").description("Direct manager"), }, Contract: { employee: r.one("Employee").required().description("Contract holder"), }, LeaveRequest: { employee: r.one("Employee").required().description("Requestor"), approvedBy: r.one("Employee").description("Manager who approved/rejected"), },}));const objects = Ontology.Objects(objectTypes, relationships, (o) => ({ // Departments "dept:engineering": o.Department({ name: "Engineering", code: "ENG", budget: 2000000, }), "dept:hr": o.Department({ name: "Human Resources", code: "HR", budget: 500000, }), // Employees "emp:alice": o.Employee({ name: "Alice Chen", email: "[email protected]", title: "VP of Engineering", startDate: Date.parse("2020-01-15"), status: "active", department: o.ref("dept:engineering"), }), "emp:bob": o.Employee({ name: "Bob Smith", email: "[email protected]", title: "Senior Engineer", startDate: Date.parse("2021-06-01"), status: "active", department: o.ref("dept:engineering"), manager: o.ref("emp:alice"), }), "emp:carol": o.Employee({ name: "Carol Davis", email: "[email protected]", title: "HR Manager", startDate: Date.parse("2019-03-20"), status: "active", department: o.ref("dept:hr"), }), // Contracts "contract:bob": o.Contract({ employee: o.ref("emp:bob"), signedDate: Date.parse("2021-05-25"), type: "full-time", }),}));const linkTypes = Ontology.LinkTypes(objectTypes, { "reports-to": { description: "Reporting relationship between employees", sourceType: "Employee", targetType: "Employee", cardinality: "many-to-one", properties: { since: Attr.number().required().description("When reporting started"), level: Attr.enum(["direct", "skip-level"]), }, }, "works-in": { description: "Employee assignment to department", sourceType: "Employee", targetType: "Department", cardinality: "many-to-one", properties: { role: Attr.string(), allocation: Attr.number().description("Percentage (0-100)"), }, }, "mentors": { description: "Mentorship relationship", sourceType: "Employee", targetType: "Employee", cardinality: "many-to-many", properties: { focus: Attr.string().description("Mentorship focus area"), startedAt: Attr.number(), }, },});const links = Ontology.Links(objects, linkTypes, (l) => [ // Bob reports to Alice l("reports-to", "emp:bob", "emp:alice", { since: Date.parse("2021-06-01"), level: "direct", }), // Department assignments with allocation l("works-in", "emp:alice", "dept:engineering", { role: "Leadership", allocation: 100, }), l("works-in", "emp:bob", "dept:engineering", { role: "Individual Contributor", allocation: 100, }), // Alice mentors Bob on architecture l("mentors", "emp:alice", "emp:bob", { focus: "System Architecture", startedAt: Date.parse("2022-01-01"), }),]);const rules = [ { name: "contract-required", displayName: "Contract Required for Active Employees", severity: "error", entityType: "Employee", query: { find: ["?emp", "?name"], where: [ ["?emp", ":_meta/type", "Employee"], ["?emp", ":employee:status", "active"], ["?emp", ":employee:name", "?name"], ["not", ["?contract", ":contract:employee", "?emp"]] ], }, message: "{{?name}} is missing a signed contract", }, { name: "manager-required", displayName: "Manager Required", severity: "warning", entityType: "Employee", query: { find: ["?emp", "?name"], where: [ ["?emp", ":_meta/type", "Employee"], ["?emp", ":employee:status", "active"], ["?emp", ":employee:name", "?name"], ["not", ["?emp", ":employee:manager", "?_"]], // Exclude VPs and C-level ["not", ["?emp", ":employee:title", "VP of Engineering"]], ["not", ["?emp", ":employee:title", "CEO"]], ], }, message: "{{?name}} has no assigned manager", }, { name: "leave-request-date-validation", displayName: "Leave End Date After Start", severity: "error", entityType: "LeaveRequest", query: { find: ["?request"], where: [ ["?request", ":_meta/type", "LeaveRequest"], ["?request", ":leave-request:start-date", "?start"], ["?request", ":leave-request:end-date", "?end"], [">", "?start", "?end"], ], }, message: "Leave request has end date before start date", },];{ name: "employee-onboarding", displayName: "Onboard New Employee", entityType: "Employee", mode: "wizard", steps: [ { id: "personal-info", title: "Personal Information", fields: [ { name: "name", type: "text", required: true, label: "Full Name" }, { name: "email", type: "email", required: true, label: "Work Email" }, { name: "phone", type: "text", label: "Phone Number" }, ], }, { id: "job-details", title: "Job Details", fields: [ { name: "title", type: "text", required: true, label: "Job Title" }, { name: "department", type: "select", required: true, options: ["Engineering", "HR", "Sales", "Marketing"], }, { name: "startDate", type: "date", required: true, label: "Start Date" }, { name: "manager", type: "entity-ref", entityType: "Employee", label: "Reporting Manager", }, ], }, { id: "equipment", title: "Equipment", fields: [ { name: "laptop", type: "select", options: ["MacBook Pro 14\"", "MacBook Pro 16\"", "ThinkPad X1"], }, { name: "additionalEquipment", type: "text", label: "Additional Needs" }, ], }, ], onComplete: [ { type: "assert", entityId: "$new", attribute: ":employee:status", value: "onboarding" }, { type: "assert", entityId: "$new", attribute: ":employee:onboarded-at", value: "$now" }, ],}{ name: "approve-leave", displayName: "Approve Leave Request", entityType: "LeaveRequest", mode: "instant", precondition: { find: ["?request"], where: [ ["?request", ":_meta/type", "LeaveRequest"], ["?request", ":leave-request:status", "pending"], ["?request", ":leave-request:employee", "?emp"], ["?emp", ":employee:manager", "?currentUser"], ], }, onComplete: [ { type: "update", entityId: "$entity", attribute: ":leave-request:status", value: "approved" }, { type: "assert", entityId: "$entity", attribute: ":leave-request:approved-by", value: "$currentUser" }, { type: "assert", entityId: "$entity", attribute: ":leave-request:approved-at", value: "$now" }, ],}{ name: "employee-onboarding-workflow", displayName: "Employee Onboarding Process", trigger: { type: "entity-created", entityType: "Employee" }, steps: [ // Step 1: Send welcome email (runs immediately) { id: "send-welcome-email", type: "automation", dependsOn: [], config: { handler: "email:send", input: { to: "{{ entity.email }}", template: "welcome", variables: { name: "{{ entity.name }}", startDate: "{{ entity.startDate }}", }, }, }, }, // Step 2: Find and assign onboarding buddy { id: "assign-buddy", type: "query", dependsOn: ["send-welcome-email"], config: { query: { find: ["?buddy"], where: [ ["?buddy", ":_meta/type", "Employee"], ["?buddy", ":employee:department", "{{ entity.department }}"], ["?buddy", ":employee:is-buddy", true], ["?buddy", ":employee:status", "active"], ], limit: 1, }, }, }, // Step 3: Human task - collect documents { id: "collect-documents", type: "human-task", dependsOn: ["assign-buddy"], config: { assignedTo: "{{ outputs.assign-buddy.buddy }}", title: "Collect Onboarding Documents for {{ entity.name }}", description: "Please collect and verify the following documents.", form: { fields: [ { name: "id-verified", type: "boolean", label: "ID Verified" }, { name: "contract-signed", type: "boolean", label: "Contract Signed" }, { name: "tax-forms", type: "boolean", label: "Tax Forms Submitted" }, { name: "notes", type: "textarea", label: "Notes" }, ], }, }, }, // Step 4: Check if all documents collected { id: "check-documents", type: "decision", dependsOn: ["collect-documents"], config: { condition: "{{ outputs.collect-documents.id-verified && outputs.collect-documents.contract-signed }}", branches: { true: ["provision-systems"], false: ["escalate-to-hr"], }, }, }, // Step 5a: Provision systems (if documents complete) { id: "provision-systems", type: "automation", dependsOn: ["check-documents"], config: { handler: "systems:provision", input: { userId: "{{ entity.id }}", email: "{{ entity.email }}", systems: ["email", "slack", "github", "jira"], }, }, }, // Step 5b: Escalate to HR (if documents incomplete) { id: "escalate-to-hr", type: "human-task", dependsOn: ["check-documents"], config: { assignedTo: { query: { find: ["?hr"], where: [["?hr", ":role", "HR Manager"]] } }, title: "Incomplete Onboarding Documents for {{ entity.name }}", priority: "high", }, }, // Step 6: Update employee status { id: "complete-onboarding", type: "automation", dependsOn: ["provision-systems"], config: { handler: "entity:update", input: { entityId: "{{ entity.id }}", updates: [ { attribute: ":employee:status", value: "active" }, { attribute: ":employee:onboarding-completed-at", value: "{{ $now }}" }, ], }, }, }, ],}// Trigger on entity creation{ type: "entity-created", entityType: "Employee" }// Trigger on attribute change{ type: "attribute-changed", entityType: "LeaveRequest", attribute: ":status" }// Manual trigger{ type: "manual" }// Scheduled{ type: "schedule", cron: "0 9 * * 1" } // Every Monday at 9am// Register Salesforce as a data sourceyield* IntegrationService.registerSource({ name: "salesforce-prod", adapter: "salesforce", displayName: "Salesforce (Production)", config: { instanceUrl: "https://acme.salesforce.com", apiVersion: "59.0", }, credentials: { type: "oauth2", accessToken: "...", refreshToken: "...", expiresAt: Date.now() + 3600000, },});yield* IntegrationService.createMapping({ source: "salesforce-prod", externalType: "Contact", internalType: "Employee", fields: { // External ID for provenance tracking "Id": { attribute: ":_provenance/external-id", isKey: true, }, // Direct mappings "FirstName": { attribute: ":employee:first-name" }, "LastName": { attribute: ":employee:last-name" }, "Email": { attribute: ":employee:email" }, // Type transformation "Salary__c": { attribute: ":employee:salary", transform: "number", }, // Lookup (resolve external ID to internal entity) "AccountId": { attribute: ":employee:company", transform: "lookup", lookupType: "Account", }, // DateTime transformation "HireDate": { attribute: ":employee:start-date", transform: "datetime", }, },});yield* IntegrationService.createMatchRule({ source: "salesforce-prod", externalType: "Contact", internalType: "Employee", priority: 1, // Lower = higher priority query: { find: ["?employee"], where: [ ["?employee", ":_meta/type", "Employee"], ["?employee", ":employee:email", "?email"], // ?email bound from incoming record ], },});const result = yield* SyncService.sync({ source: "salesforce-prod", dryRun: false,});console.log(`Created: ${result.created}`);console.log(`Updated: ${result.updated}`);console.log(`Matched: ${result.matched}`);console.log(`Errors: ${result.errors.length}`);{ entityId: "emp:alice", ":employee:name": "Alice Chen", ":employee:email": "[email protected]", // Provenance metadata ":_provenance/source": "salesforce-prod", ":_provenance/external-id": "003xx000004TmEQ", ":_provenance/external-type": "Contact", ":_provenance/last-synced-at": 1705312200000,}// Find all active employees in Engineering{ find: ["?name", "?email"], where: [ ["?emp", ":_meta/type", "Employee"], ["?emp", ":employee:status", "active"], ["?emp", ":employee:name", "?name"], ["?emp", ":employee:email", "?email"], ["?emp", ":employee:department", "?dept"], ["?dept", ":department:name", "Engineering"], ],}// Count employees per department{ find: ["?deptName", { count: "?emp" }], where: [ ["?emp", ":_meta/type", "Employee"], ["?emp", ":employee:department", "?dept"], ["?dept", ":department:name", "?deptName"], ], groupBy: ["?deptName"],}// Employees without a manager (excluding VPs){ find: ["?name"], where: [ ["?emp", ":_meta/type", "Employee"], ["?emp", ":employee:name", "?name"], ["?emp", ":employee:status", "active"], ["not", ["?emp", ":employee:manager", "?_"]], ["not", ["?emp", ":employee:title", "VP of Engineering"]], ],}// Find pending leave requests with employee and manager info{ find: ["?empName", "?mgrName", "?startDate", "?type"], where: [ ["?request", ":_meta/type", "LeaveRequest"], ["?request", ":leave-request:status", "pending"], ["?request", ":leave-request:start-date", "?startDate"], ["?request", ":leave-request:type", "?type"], ["?request", ":leave-request:employee", "?emp"], ["?emp", ":employee:name", "?empName"], ["?emp", ":employee:manager", "?mgr"], ["?mgr", ":employee:name", "?mgrName"], ],}// What was the employee's status last month?const lastMonth = Date.now() - 30 * 24 * 60 * 60 * 1000;const result = yield* store.queryAsOf( { find: ["?status"], where: [ ["emp:bob", ":employee:status", "?status"], ], }, lastMonth);const ontology = Ontology.build({ name: "hr", version: "1.0.0", description: "Human Resources domain ontology", objectTypes, relationships, objects, linkTypes, links,});// Deploy to the triple storeyield* OntologyService.deploy(ontology);