UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

1,280 lines (1,275 loc) 34.9 kB
import { QueryEngine } from "./chunk-BYTAOXGW.js"; import { EAVStore, init_eav_store } from "./chunk-G3XIHPSQ.js"; import { filterDurableAttributes, findSchemaForType } from "./chunk-LEGH72HW.js"; import { OP_PREIMAGE_VERSION, hashKernelOp, init_canonical_op } from "./chunk-RUMOVKR4.js"; // src/core/kernel/trellis-kernel.ts init_eav_store(); init_canonical_op(); // src/core/ontology/core-ontology.ts var VERSION = "1.0.0"; function f(name, valueType, opts) { return { name, valueType, ...opts }; } var thing = { "@id": "core:Thing", "@type": "trellis:Schema", version: VERSION, tier: "core", label: "Thing", icon: "lucide:box", fields: [ f("id", "title", { required: true }), f("createdAt", "date"), f("updatedAt", "date"), f("createdBy", "relation", { relation: { targetSchema: "core:Member", cardinality: "one" } }), f("tags", "multi_select") ] }; var record = { "@id": "core:Record", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Record", icon: "lucide:file", fields: [ f("title", "title", { required: true }), f("description", "rich_text"), f("status", "select"), f("tags", "multi_select") ] }; var document = { "@id": "core:Document", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Record", label: "Document", icon: "lucide:file-text", fields: [ f("content", "rich_text"), f("mimeType", "rich_text"), f("fileUrl", "url") ] }; var event = { "@id": "core:Event", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Record", label: "Event", icon: "lucide:calendar", fields: [ f("startDate", "date"), f("endDate", "date"), f("location", "rich_text"), f("allDay", "checkbox") ] }; var collection = { "@id": "core:Collection", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Collection", icon: "lucide:database", fields: [ f("title", "title", { required: true }), f("description", "rich_text"), f("icon", "rich_text"), f("schema", "rich_text"), f("recordType", "relation", { relation: { targetSchema: "core:Record", cardinality: "one" } }) ] }; var tag = { "@id": "core:Tag", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Tag", icon: "lucide:tag", fields: [ f("name", "title", { required: true }), f("slug", "rich_text"), f("color", "rich_text"), f("icon", "rich_text"), f("parentTag", "relation", { relation: { targetSchema: "core:Tag", cardinality: "one" } }) ] }; var workspace = { "@id": "core:Workspace", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Workspace", icon: "lucide:building-2", fields: [ f("name", "title", { required: true }), f("slug", "rich_text"), f("avatar", "files"), f("plan", "select") ] }; var app = { "@id": "core:App", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "App", icon: "lucide:layout-grid", fields: [ f("name", "title", { required: true }), f("slug", "rich_text"), f("icon", "rich_text"), f("color", "rich_text"), f("description", "rich_text"), f("ontologies", "multi_select") ] }; var member = { "@id": "core:Member", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Member", icon: "lucide:user", fields: [ f("name", "title", { required: true }), f("email", "email"), f("avatar", "files"), f("role", "select", { required: true, selectOptions: ["owner", "admin", "member", "guest"], defaultValue: "member" }), f("status", "select", { required: true, selectOptions: ["pending", "active", "suspended"], defaultValue: "pending" }), f("orgId", "relation", { required: true, relation: { targetSchema: "core:Workspace", cardinality: "one" } }), f("userId", "relation", { relation: { targetSchema: "core:Person", cardinality: "one" } }), f("invitedAt", "date"), f("joinedAt", "date") ] }; var notification = { "@id": "core:Notification", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Notification", icon: "lucide:bell", fields: [ f("recipientId", "relation", { required: true, relation: { targetSchema: "core:Person", cardinality: "one" } }), f("orgId", "relation", { relation: { targetSchema: "core:Workspace", cardinality: "one" } }), f("orgName", "rich_text"), f("type", "select", { required: true, selectOptions: [ "invite_accepted", "invite_sent", "member_joined", "member_removed", "role_changed", "mention", "comment", "entity_updated", "system" ] }), f("title", "title", { required: true }), f("message", "rich_text", { required: true }), f("actionUrl", "url"), f("icon", "rich_text"), f("variant", "select", { selectOptions: ["default", "success", "warning", "destructive", "info"] }), f("isRead", "checkbox", { defaultValue: false }), f("actorId", "relation", { relation: { targetSchema: "core:Person", cardinality: "one" } }), f("actorName", "rich_text"), f("metadata", "rich_text"), f("createdAt", "date", { required: true }) ] }; var share = { "@id": "core:Share", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Share", icon: "lucide:share-2", fields: [ f("entityId", "relation", { required: true }), f("entityType", "select", { selectOptions: ["entity", "collection"] }), f("userId", "relation", { required: true, relation: { targetSchema: "core:Person", cardinality: "one" } }), f("orgId", "relation", { relation: { targetSchema: "core:Workspace", cardinality: "one" } }), f("permission", "select", { required: true, selectOptions: ["view", "comment", "edit"], defaultValue: "view" }), f("sharedBy", "relation", { relation: { targetSchema: "core:Person", cardinality: "one" } }), f("createdAt", "date", { required: true }) ] }; var person = { "@id": "core:Person", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Person", icon: "lucide:user", fields: [f("name", "title", { required: true })] }; var workflow = { "@id": "core:Workflow", "@type": "trellis:Schema", version: VERSION, tier: "core", subClassOf: "core:Thing", label: "Workflow", icon: "lucide:git-branch", fields: [ f("name", "title", { required: true }), f("trigger", "rich_text"), f("steps", "multi_select"), f("active", "checkbox") ] }; var workflowStep = { "@id": "core:WorkflowStep", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Workflow Step", icon: "lucide:list-checks", fields: [ f("name", "title", { required: true }), f("description", "rich_text"), f("commands", "json"), f("turbo", "checkbox"), f("layer", "select", { selectOptions: ["pre_flight", "setup", "implement", "review", "closure"] }) ] }; var workflowEdge = { "@id": "core:WorkflowEdge", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Workflow Edge", icon: "lucide:arrow-right", fields: [ f("name", "title", { required: true }), f("condition", "rich_text"), f("status", "select", { selectOptions: ["HANDOFF", "CLARIFY", "REJECT", "BLOCKED", "DECISION"], defaultValue: "HANDOFF" }), f("from", "relation", { relation: { targetSchema: "core:WorkflowStep", cardinality: "one" } }), f("to", "relation", { relation: { targetSchema: "core:WorkflowStep", cardinality: "one" } }) ] }; var workflowGate = { "@id": "core:WorkflowGate", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Workflow Gate", icon: "lucide:shield-check", fields: [ f("name", "title", { required: true }), f("type", "select", { required: true, selectOptions: ["test", "manual", "ac_check", "semantic_diff"] }), f("criteria", "rich_text"), f("onFail", "select", { selectOptions: ["stop", "retry", "route_to"], defaultValue: "stop" }), f("step", "relation", { relation: { targetSchema: "core:WorkflowStep", cardinality: "one" } }), f("retryStep", "relation", { relation: { targetSchema: "core:WorkflowStep", cardinality: "one" } }), f("failRoute", "relation", { relation: { targetSchema: "core:WorkflowEdge", cardinality: "one" } }) ] }; var agent = { "@id": "core:Agent", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Agent", icon: "lucide:bot", fields: [ f("name", "title", { required: true }), f("description", "rich_text"), f("role", "select", { required: true, selectOptions: [ "strategist", "designer", "architect", "executor", "reviewer", "optimizer", "synthesist", "writer", "human" ] }), f("inbox", "rich_text"), f("model", "rich_text"), f("status", "select", { defaultValue: "active", selectOptions: ["active", "inactive", "deprecated"] }), f("capabilities", "multi_select"), f("provider", "rich_text"), f("systemPrompt", "rich_text"), f("temperature", "number"), f("maxTokens", "number"), f("workflow", "relation", { relation: { targetSchema: "core:Workflow", cardinality: "one" } }), f("tools", "relation", { relation: { targetSchema: "core:Tool", cardinality: "many" } }) ] }; var tool = { "@id": "core:Tool", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Tool", icon: "lucide:wrench", fields: [ f("name", "title", { required: true }), f("description", "rich_text"), f("schema", "json"), f("endpoint", "url") ] }; var agentRun = { "@id": "trellis:AgentRun", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "AgentRun", icon: "lucide:play", fields: [ f("startedAt", "date", { required: true }), f("completedAt", "date"), f("status", "select", { required: true, selectOptions: ["running", "plan_pending", "paused", "completed", "failed", "cancelled"], defaultValue: "running" }), f("input", "rich_text"), f("output", "rich_text"), f("totalTokens", "number"), f("promptTokens", "number"), f("completionTokens", "number"), f("maxRetries", "number"), f("timeoutMs", "number"), f("executedBy", "relation", { relation: { targetSchema: "core:Agent", cardinality: "one" } }), f("hasPlan", "relation", { relation: { cardinality: "many" } }), f("usedTool", "relation", { relation: { targetSchema: "core:Tool", cardinality: "many" } }), f("handoffTo", "relation", { relation: { targetSchema: "trellis:AgentRun", cardinality: "many" } }), f("handoffFrom", "relation", { relation: { targetSchema: "trellis:AgentRun", cardinality: "one" } }) ] }; var decisionTrace = { "@id": "trellis:DecisionTrace", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "DecisionTrace", icon: "lucide:git-branch", fields: [ f("toolName", "title", { required: true }), f("timestamp", "date", { required: true }), f("input", "json"), f("output", "rich_text"), f("rationale", "rich_text"), f("alternatives", "json"), f("belongsToRun", "relation", { relation: { targetSchema: "trellis:AgentRun", cardinality: "one" } }), f("madeBy", "relation", { relation: { targetSchema: "core:Agent", cardinality: "one" } }), f("relatedTo", "relation", { relation: { cardinality: "many" } }) ] }; var workerPoolTask = { "@id": "trellis:WorkerPoolTask", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "WorkerPool Task", icon: "lucide:list-queue", fields: [ f("agentId", "title", { required: true }), f("runId", "title", { required: true }), f("input", "rich_text"), f("status", "select", { required: true, selectOptions: ["queued", "running", "paused", "completed", "failed", "cancelled"], defaultValue: "queued" }), f("queuedAt", "date", { required: true }), f("startedAt", "date"), f("completedAt", "date"), f("error", "rich_text") ] }; var dagRun = { "@id": "trellis:DAGRun", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "DAG Run", icon: "lucide:workflow", fields: [ f("workflowId", "title", { required: true }), f("workflowName", "title"), f("status", "select", { required: true, selectOptions: ["running", "completed", "failed", "cancelled"], defaultValue: "running" }), f("steps", "json"), f("startedAt", "date", { required: true }), f("completedAt", "date") ] }; var handoff = { "@id": "core:Handoff", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Handoff", icon: "lucide:arrow-left-right", fields: [ f("name", "title", { required: true }), f("status", "select", { required: true, selectOptions: ["HANDOFF", "CLARIFY", "REJECT", "BLOCKED", "DECISION"] }), f("body", "rich_text"), f("refs", "multi_select"), f("timestamp", "date"), f("from", "relation", { relation: { targetSchema: "core:Agent", cardinality: "one" } }), f("to", "relation", { relation: { targetSchema: "core:Agent", cardinality: "one" } }), f("re", "relation", { relation: { cardinality: "one" } }) ] }; var pipeline = { "@id": "trellis:Pipeline", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Pipeline", icon: "lucide:git-merge", fields: [ f("name", "title", { required: true }), f("description", "rich_text"), f("trigger", "rich_text"), f("active", "checkbox"), f("phases", "relation", { relation: { targetSchema: "trellis:PipelinePhase", cardinality: "many" } }), f("workflow", "relation", { relation: { targetSchema: "core:Workflow", cardinality: "many" } }) ] }; var pipelinePhase = { "@id": "trellis:PipelinePhase", "@type": "trellis:Schema", version: VERSION, tier: "system", subClassOf: "core:Thing", label: "Pipeline Phase", icon: "lucide:step-forward", fields: [ f("name", "title", { required: true }), f("description", "rich_text"), f("order", "number"), f("agentRole", "select", { required: true, selectOptions: [ "strategist", "designer", "architect", "executor", "reviewer", "optimizer", "synthesist", "writer", "human" ] }), f("workflow", "relation", { relation: { targetSchema: "core:Workflow", cardinality: "one" } }) ] }; var CORE_ONTOLOGY = [ thing, record, document, event, collection, tag, workspace, app, member, notification, share, person, workflow, workflowStep, workflowEdge, workflowGate, agent, tool, agentRun, decisionTrace, workerPoolTask, dagRun, handoff, pipeline, pipelinePhase ]; // src/core/kernel/trellis-kernel.ts var TrellisKernel = class { store; backend; middleware; agentId; snapshotThreshold; opsSinceSnapshot = 0; _booted = false; ontologies = /* @__PURE__ */ new Map(); workspaceConfig = null; autoReplay = true; defaultProvenance; constructor(config) { this.store = new EAVStore(); this.backend = config.backend; this.agentId = config.agentId; this.middleware = config.middleware ?? []; this.snapshotThreshold = config.snapshotThreshold ?? 0; this.autoReplay = config.autoReplay ?? true; this.defaultProvenance = config.provenance ?? { actorType: "machine", origin: "sdk" }; for (const schema of CORE_ONTOLOGY) { this.ontologies.set(schema["@id"], schema); } } // ------------------------------------------------------------------------- // Lifecycle // ------------------------------------------------------------------------- /** * Initialize the backend and replay persisted state. * Loads latest snapshot if available, then replays ops after it. */ boot() { this.backend.init(); if (!this.autoReplay) { this._booted = true; return { opsReplayed: 0, fromSnapshot: false }; } let opsReplayed = 0; let fromSnapshot = false; const snapshot = this.backend.loadLatestSnapshot(); if (snapshot) { this.store.restore(snapshot.data); fromSnapshot = true; const recentOps = this.backend.readAfter(snapshot.lastOpHash); for (const op of recentOps) { this._replayOp(op); opsReplayed++; } } else { const allOps = this.backend.readAll(); for (const op of allOps) { this._replayOp(op); opsReplayed++; } } this._booted = true; return { opsReplayed, fromSnapshot }; } /** * Close the backend connection. */ close() { this.backend.close?.(); this._booted = false; } isBooted() { return this._booted; } // ------------------------------------------------------------------------- // Mutations // ------------------------------------------------------------------------- /** * Apply a mutation to the graph. Creates an op, runs it through middleware, * decomposes into EAV primitives, persists, and returns the result. */ async mutate(kind, payload, ctx) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const lastOp = this.backend.getLastOp(); const agentId = ctx?.agentId ?? this.agentId; const provenance = payload.provenance ?? ctx?.provenance ?? this.defaultProvenance; const resolved = { ...payload, provenance }; const hash = await hashKernelOp( { kind, timestamp, agentId, previousHash: lastOp?.hash }, resolved ); const op = { hash, kind, timestamp, agentId, previousHash: lastOp?.hash, v: OP_PREIMAGE_VERSION, facts: [...payload.facts ?? []], links: [...payload.links ?? []], deleteFacts: payload.deleteFacts?.length ? [...payload.deleteFacts] : void 0, deleteLinks: payload.deleteLinks?.length ? [...payload.deleteLinks] : void 0, provenance }; const mwCtx = { agentId, ...ctx }; await this._runMiddleware(op, mwCtx); let factsAdded = 0; let factsDeleted = 0; let linksAdded = 0; let linksDeleted = 0; if (payload.deleteFacts && payload.deleteFacts.length > 0) { this.store.deleteFacts(payload.deleteFacts); factsDeleted = payload.deleteFacts.length; } if (payload.deleteLinks && payload.deleteLinks.length > 0) { this.store.deleteLinks(payload.deleteLinks); linksDeleted = payload.deleteLinks.length; } if (payload.facts && payload.facts.length > 0) { this.store.addFacts(payload.facts); factsAdded = payload.facts.length; } if (payload.links && payload.links.length > 0) { this.store.addLinks(payload.links); linksAdded = payload.links.length; } this.backend.append(op); this.opsSinceSnapshot++; if (this.snapshotThreshold > 0 && this.opsSinceSnapshot >= this.snapshotThreshold) { this.checkpoint(); } return { op, factsDelta: { added: factsAdded, deleted: factsDeleted }, linksDelta: { added: linksAdded, deleted: linksDeleted } }; } /** * Create a snapshot of the current store state. */ checkpoint() { const lastOp = this.backend.getLastOp(); if (!lastOp) return; this.backend.saveSnapshot(lastOp.hash, this.store.snapshot()); this.opsSinceSnapshot = 0; } // ------------------------------------------------------------------------- // Queries // ------------------------------------------------------------------------- /** * Get the underlying EAV store for direct queries. */ getStore() { return this.store; } /** * Get the persistence backend. */ getBackend() { return this.backend; } /** * Get the agent ID. */ getAgentId() { return this.agentId; } /** * Read all persisted ops. */ readAllOps() { return this.backend.readAll(); } /** * Get the last persisted op. */ getLastOp() { return this.backend.getLastOp(); } /** * Create a QueryEngine bound to this kernel's store. */ createQueryEngine() { return new QueryEngine(this.store); } /** * Execute an EQL-S query, routing through middleware handleQuery hooks. * If no middleware intercepts, the query runs directly against the store. */ async query(q) { const engine = new QueryEngine(this.store); const ctx = { agentId: this.agentId, store: this.store }; const chain = this.middleware.filter((m) => m.handleQuery); if (chain.length === 0) { return engine.execute(q); } let result; let idx = 0; const next = (query, context) => { if (idx < chain.length) { const mw = chain[idx++]; return mw.handleQuery(query, context, next); } result = engine.execute(query); return result; }; result = next(q, ctx); return result; } /** * Time-travel: reconstruct the store state at a specific op hash. * Returns a new EAVStore with state replayed up to (and including) that op. */ timeTravel(opHash) { const ops = this.backend.readUntil(opHash); const snapshot = new EAVStore(); for (const op of ops) { if (op.deleteFacts && op.deleteFacts.length > 0) { snapshot.deleteFacts(op.deleteFacts); } if (op.deleteLinks && op.deleteLinks.length > 0) { snapshot.deleteLinks(op.deleteLinks); } if (op.facts && op.facts.length > 0) { snapshot.addFacts(op.facts); } if (op.links && op.links.length > 0) { snapshot.addLinks(op.links); } } return snapshot; } // ------------------------------------------------------------------------- // Entity CRUD (high-level graph operations) // ------------------------------------------------------------------------- /** * Create a new entity with the given type and attributes. * Returns the entity ID. */ async createEntity(entityId, type, attributes = {}, links, ctx) { const schema = findSchemaForType(this.ontologies.values(), type); const filtered = filterDurableAttributes( attributes, schema ); const facts = [{ e: entityId, a: "type", v: type }]; if (filtered.createdAt === void 0) { facts.push({ e: entityId, a: "createdAt", v: (/* @__PURE__ */ new Date()).toISOString() }); } for (const [attr, value] of Object.entries(filtered)) { facts.push({ e: entityId, a: attr, v: value }); } const linkRecords = (links ?? []).map((l) => ({ e1: entityId, a: l.attribute, e2: l.targetEntityId })); return this.mutate( "addFacts", { facts, links: linkRecords.length > 0 ? linkRecords : void 0 }, ctx ); } /** * Get an entity by ID, returning all its facts and links. */ getEntity(entityId) { const facts = this.store.getFactsByEntity(entityId); if (facts.length === 0) return null; const typeFact = facts.find((f2) => f2.a === "type"); return { id: entityId, type: typeFact?.v ?? "unknown", facts, links: this.store.getLinksByEntity(entityId) }; } /** * Update an entity's attributes. Deletes old values and adds new ones. */ async updateEntity(entityId, updates, ctx) { const existing = this.getEntity(entityId); const schema = existing ? findSchemaForType(this.ontologies.values(), existing.type) : void 0; const filtered = filterDurableAttributes( updates, schema ); const existingFacts = this.store.getFactsByEntity(entityId); const deleteFacts = []; const addFacts = []; for (const [attr, newValue] of Object.entries(filtered)) { const existingAttr = existingFacts.filter((f2) => f2.a === attr); deleteFacts.push(...existingAttr); addFacts.push({ e: entityId, a: attr, v: newValue }); } const updatedAtFacts = existingFacts.filter((f2) => f2.a === "updatedAt"); deleteFacts.push(...updatedAtFacts); addFacts.push({ e: entityId, a: "updatedAt", v: (/* @__PURE__ */ new Date()).toISOString() }); return this.mutate( "addFacts", { facts: addFacts, deleteFacts }, ctx ); } /** * Delete an entity and all its facts and links. */ async deleteEntity(entityId, ctx) { const facts = this.store.getFactsByEntity(entityId); const links = this.store.getLinksByEntity(entityId); return this.mutate( "deleteFacts", { deleteFacts: facts, deleteLinks: links }, ctx ); } /** * List entities by type, with optional attribute filters. */ listEntities(type, filters) { let entityIds; if (type) { const typeFacts = this.store.getFactsByValue("type", type); entityIds = new Set(typeFacts.map((f2) => f2.e)); } else { const allTypeFacts = this.store.getFactsByAttribute("type"); entityIds = new Set(allTypeFacts.map((f2) => f2.e)); } if (filters) { for (const [attr, value] of Object.entries(filters)) { const matchingFacts = this.store.getFactsByValue(attr, value); const matchingEntities = new Set(matchingFacts.map((f2) => f2.e)); for (const id of entityIds) { if (!matchingEntities.has(id)) { entityIds.delete(id); } } } } return Array.from(entityIds).map((id) => this.getEntity(id)).filter(Boolean); } /** * Add a link between two entities. */ async addLink(sourceId, attribute, targetId, ctx) { return this.mutate( "addLinks", { links: [{ e1: sourceId, a: attribute, e2: targetId }] }, ctx ); } /** * Remove a link between two entities. */ async removeLink(sourceId, attribute, targetId, ctx) { return this.mutate( "deleteLinks", { deleteLinks: [{ e1: sourceId, a: attribute, e2: targetId }] }, ctx ); } /** * Add a fact to an entity. */ async addFact(entityId, attribute, value, ctx, meta) { return this.mutate( "addFacts", { facts: [{ e: entityId, a: attribute, v: value, ...meta ? { meta } : {} }] }, ctx ); } /** * Remove a fact from an entity. */ async removeFact(entityId, attribute, value, ctx) { return this.mutate( "deleteFacts", { deleteFacts: [{ e: entityId, a: attribute, v: value }] }, ctx ); } // ------------------------------------------------------------------------- // Ontology CRUD // ------------------------------------------------------------------------- /** * Get an ontology schema by ID. */ getOntology(id) { return this.ontologies.get(id); } /** * List all ontologies. */ listOntologies() { return Array.from(this.ontologies.values()); } /** * Create a new ontology schema. */ createOntology(schema) { const tier = schema.tier ?? "user"; if (tier === "core") { throw new Error("Cannot modify core ontologies"); } if (this.ontologies.has(schema["@id"])) { throw new Error(`Ontology ${schema["@id"]} already exists`); } this.ontologies.set(schema["@id"], schema); const fact = { e: schema["@id"], a: "schema", v: JSON.stringify(schema) }; this.store.addFacts([fact]); } /** * Update an existing ontology schema. */ updateOntology(id, updates) { const existing = this.ontologies.get(id); if (!existing) { throw new Error(`Ontology ${id} not found`); } const tier = existing.tier ?? "user"; if (tier === "core") { throw new Error("Cannot modify core ontologies"); } const updated = { ...existing, ...updates }; this.ontologies.set(id, updated); const existingFacts = this.store.getFactsByEntity(id); const schemaFacts = existingFacts.filter((f2) => f2.a === "schema"); const deleteFacts = [...schemaFacts]; const addFacts = [ { e: id, a: "schema", v: JSON.stringify(updated) } ]; this.store.deleteFacts(deleteFacts); this.store.addFacts(addFacts); } /** * Delete an ontology schema. */ deleteOntology(id) { const existing = this.ontologies.get(id); if (!existing) { throw new Error(`Ontology ${id} not found`); } const tier = existing.tier ?? "user"; if (tier === "core") { throw new Error("Cannot delete core ontologies"); } this.ontologies.delete(id); const facts = this.store.getFactsByEntity(id); this.store.deleteFacts(facts); } // ------------------------------------------------------------------------- // Workspace // ------------------------------------------------------------------------- /** * Boot the kernel with a workspace configuration. * Loads ontologies, projections, and seed data. */ bootWorkspace(config) { this.workspaceConfig = config; if (config.workspace.ontologies) { for (const [id, schema] of Object.entries(config.workspace.ontologies)) { if (!this.ontologies.has(id)) { this.ontologies.set(id, schema); } } } if (config.workspace.graph?.nodes) { for (const node of config.workspace.graph.nodes) { const n = node; if (n.id && n.type) { const facts = [ { e: String(n.id), a: "type", v: String(n.type) } ]; for (const [key, value] of Object.entries(n)) { if (key !== "id" && key !== "type") { facts.push({ e: String(n.id), a: key, v: value }); } } this.store.addFacts(facts); } } } if (config.workspace.graph?.edges) { for (const edge of config.workspace.graph.edges) { const e = edge; if (e.source && e.target && e.relation) { this.store.addLinks([ { e1: String(e.source), a: String(e.relation), e2: String(e.target) } ]); } } } this.boot(); } /** * Export the current workspace configuration. */ exportWorkspace() { const ontologies = {}; for (const [id, schema] of this.ontologies) { if (schema.tier !== "core") { ontologies[id] = schema; } } const allFacts = this.store.getAllFacts(); const nodes = []; const entityIds = new Set(allFacts.map((f2) => f2.e)); for (const id of entityIds) { const facts = this.store.getFactsByEntity(id); const typeFact = facts.find((f2) => f2.a === "type"); const node = { id }; if (typeFact) { node.type = typeFact.v; } for (const fact of facts) { if (fact.a !== "type") { node[fact.a] = fact.v; } } nodes.push(node); } const allLinks = this.store.getAllLinks(); const edges = allLinks.map((l) => ({ source: l.e1, relation: l.a, target: l.e2 })); return { workspace: { name: this.workspaceConfig?.workspace.name, description: this.workspaceConfig?.workspace.description, ontologies: Object.keys(ontologies).length > 0 ? ontologies : void 0, graph: { nodes, edges }, projections: this.workspaceConfig?.workspace.projections, routes: this.workspaceConfig?.workspace.routes, app: this.workspaceConfig?.workspace.app } }; } // ------------------------------------------------------------------------- // TQL Compatibility Aliases // ------------------------------------------------------------------------- /** * Create a node (alias for createEntity with schema validation). */ async createNode(id, data, type, ctx) { return this.createEntity(id, type, data, void 0, ctx); } /** * Update a node (alias for updateEntity with schema validation). */ async updateNode(id, data, type, ctx) { return this.updateEntity(id, data, ctx); } /** * Delete a node (alias for deleteEntity). */ async deleteNode(id, ctx) { return this.deleteEntity(id, ctx); } /** * Link two nodes (alias for addLink). */ async link(e1, a, e2, ctx) { return this.addLink(e1, a, e2, ctx); } /** * Unlink two nodes (alias for removeLink). */ async unlink(e1, a, e2, ctx) { return this.removeLink(e1, a, e2, ctx); } // ------------------------------------------------------------------------- // Middleware // ------------------------------------------------------------------------- addMiddleware(mw) { this.middleware.push(mw); } removeMiddleware(name) { this.middleware = this.middleware.filter((m) => m.name !== name); } // ------------------------------------------------------------------------- // Internal // ------------------------------------------------------------------------- async _runMiddleware(op, ctx) { const chain = [...this.middleware]; let idx = 0; const next = async (op2, ctx2) => { const mw = chain[idx++]; if (mw?.handleOp) { await mw.handleOp(op2, ctx2, next); } }; if (chain.length > 0) { await next(op, ctx); } } _replayOp(op) { if (op.deleteFacts && op.deleteFacts.length > 0) { this.store.deleteFacts(op.deleteFacts); } if (op.deleteLinks && op.deleteLinks.length > 0) { this.store.deleteLinks(op.deleteLinks); } if (op.facts && op.facts.length > 0) { this.store.addFacts(op.facts); } if (op.links && op.links.length > 0) { this.store.addLinks(op.links); } } }; export { TrellisKernel };