Ontology (Objects, Links & Actions)
The ontology is Ontul's typed semantic model of your business — a layer of
object types, link types, and action types that sit above the
physical tables, catalogs, and graphs the engine reads and writes. Where a
semantic view curates analytics (metrics + dimensions)
and a retriever curates retrieval (vector / graph / full-text),
the ontology curates the entities and operations an application or agent works
with: a Customer, an Order, the places relationship between them, and the
governed approve_invoice write.
The design goal is a credible, open ontology + Action layer: reads win structurally (open Iceberg as the source of truth, single-engine graph RAG via NeorunBase, a semantic layer, IAM, and an MCP surface), and the ontology adds the typed object/link model plus governed write-back Actions on top.
Agent (MCP) / App / Admin UI
│
▼
┌──────────────────────── Ontology ────────────────────────┐
│ ObjectType ──places──► ObjectType (type graph) │
│ (Customer) (Order) │
│ │ read: query / traverse │ write: invoke action │
└──────┼─────────────────────────────┼─────────────────────┘
▼ ▼
read runtime write runtime
├─ ObjectSet query → engine ├─ DML → SoT (Iceberg / JDBC)
└─ Link traversal └─ OPERATION → REST operational system
├─ JOIN → engine SQL (ERP / CRM / payment / internal service …)
└─ GRAPH → NeorunBase GRAPH_NEIGHBORS
Two principles run through the whole layer:
- Iceberg is the system of record; NeorunBase is derived, rebuildable serving. Reads may come from the derived serving layer; writes go to the source of record (Iceberg's ACID / write-audit-publish) or to an external operational system over REST — an ERP, CRM, payment gateway, ticketing or internal service — never to the derived layer, which is then rebuilt from the SoT. Writing critical action-writes straight into a lakehouse table, or via raw JDBC into an operational database, is an anti-pattern; the real write goes to the system of record's own API. (ERP is just one example of such a system, not the framing.)
- Two graphs. The ontology type graph (object/link definitions) is small
metadata in the cluster store. The instance graph (actual edges between
millions of rows) lives in NeorunBase; a
GRAPHlink binds a link type to it for traversal.
All three primitives are persisted in the cluster metadata store (key prefixes
objecttype:, linktype:, actiontype:) and replicate to follower masters with
the rest of the cluster metadata, exactly like semantic views and retrievers.
Object types
An object type maps a business entity onto a physical read source, naming its properties in business terms and declaring where writes to it should land.
| Field | Purpose |
|---|---|
catalog, schema, name |
The object type's identity; its FQN is catalog.schema.Name. |
readSource |
The physical table (a catalog.schema.table registered in Ontul) instances are read from. |
primaryKey[] |
One or more property names (not physical columns) that identify an instance. |
properties[] |
Each property: name (logical), type (string/long/double/boolean/…), column (the physical column it maps to), plus optional synonyms (NL/agent matching) and pii. |
writeTarget |
Where a write action lands: DML (a table — catalog.schema.table) or OPERATION (a connector operation). |
allowedRoles[], tags[], status |
Governance + lifecycle metadata. Read effectiveStatus (below) rather than status for trust. |
The property → column mapping is the crux: callers, agents, and SDKs reference
logical property names, and Ontul resolves them to physical columns when it
generates SQL. A primaryKey entry must name a declared property — registering a
primary key that references a physical column name (e.g. o_orderkey instead of
the property orderkey) is rejected.
{
"catalog": "lake", "schema": "sales", "name": "Order",
"readSource": "lake.sales.orders",
"primaryKey": ["orderkey"],
"properties": [
{ "name": "orderkey", "type": "long", "column": "o_orderkey" },
{ "name": "status", "type": "string", "column": "o_orderstatus", "synonyms": ["상태"] },
{ "name": "custkey", "type": "long", "column": "o_custkey" }
],
"writeTarget": { "mode": "DML", "catalog": "lake", "schema": "sales", "table": "orders" }
}
Link types
A link type is a typed relationship between two object types — the edges of the ontology's type graph. It declares a binding that says how the relationship resolves at query time.
| Field | Purpose |
|---|---|
fromObjectType, toObjectType |
The endpoint object-type FQNs. |
cardinality |
ONE_TO_ONE / ONE_TO_MANY / MANY_TO_ONE / MANY_TO_MANY. |
binding.mode |
JOIN (relational key equality) or GRAPH (a native NeorunBase edge). |
binding.fromKey, binding.toKey |
(JOIN) the property names on each side whose values must match. |
binding.graphCatalog, binding.edgeTable, binding.edgeLabel, binding.direction |
(GRAPH) the NeorunBase catalog, the edge/relations table, the edge label, and traversal direction. |
- A JOIN binding resolves through the query engine:
Customer places OrderwhereCustomer.custkey = Order.custkey. - A GRAPH binding is pushed down to NeorunBase's graph engine — the same instance graph a retriever reaches — so traversal is the graph engine, not an application-side loop.
{
"catalog": "lake", "schema": "sales", "name": "places",
"fromObjectType": "lake.sales.Customer",
"toObjectType": "lake.sales.Order",
"cardinality": "ONE_TO_MANY",
"binding": { "mode": "JOIN", "fromKey": "custkey", "toKey": "custkey" }
}
Reading the ontology
The read runtime is the resolver that turns an ontology intent — expressed against object/link definitions — into injection-safe SQL the existing engines execute, and returns typed rows. It is the read counterpart to Actions (write).
ObjectSet query
POST /api/v1/object-types/{fqn}/query returns instances of one object type,
filtered and projected by property name. The body accepts filters
(property → value equality predicates, ANDed), select (property names to
project; omit for all), orderBy, desc, and limit.
curl -X POST "$ADMIN/api/v1/object-types/lake.sales.Order/query" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{ "filters": { "status": "OPEN" }, "select": ["orderkey","status"], "limit": 50 }'
Ontul resolves each property to its column, builds
SELECT "o_orderkey" AS "orderkey", … FROM lake.sales.orders WHERE "o_orderstatus" = 'OPEN' LIMIT 50,
executes it through the query engine, and returns { columns, rows, rowCount }
where the columns are the logical property names. Referencing an undeclared
property is rejected with 400 before any SQL runs.
Link traversal
POST /api/v1/link-types/{fqn}/traverse returns the related to-object instances
reachable from one source object. The body takes sourceKey (the source
object's primary-key value), select, limit, and — for GRAPH links — an
optional maxDepth.
- JOIN links resolve relationally. When the link's
fromKeyis the from-object's primary key (the common case), traversal collapses to a single filter on the to-object (toKey = sourceKey) — no join needed; otherwise a relational join resolves the key. - GRAPH links are pushed down to NeorunBase's
GRAPH_NEIGHBORS(edge_table, seed, max_depth, edge_filter)table-valued function; the returned neighbour ids are joined back to the to-object's table so you get typed instances, not bare ids.
# Walk the graph: relations of entity 1, along 'is_a' edges, up to depth 2.
curl -X POST "$ADMIN/api/v1/link-types/nb.public.is_a/traverse" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{ "sourceKey": "1", "maxDepth": 2, "select": ["id","name"], "limit": 50 }'
Actions (governed write-back)
An action type is a typed, parameterized, governed write operation over
an object type — how an application or agent changes data, in contrast to the
read runtime above. An agent invokes a named action (approve_invoice) instead
of composing raw SQL; the platform validates parameters, authorizes the caller,
enforces idempotency, executes against the write system-of-record, and audits the
run.
| Field | Purpose |
|---|---|
objectType |
The object type this action operates on (its write context). |
mode |
DML (execute rendered SQL against the SoT) or OPERATION (call a connector operation). |
parameters[] |
Each: name, type (STRING/LONG/DOUBLE/BOOLEAN/IDENT), required, description. |
sqlTemplate |
(DML) parameterized SQL with ${param} placeholders. Admin-authored and trusted. |
operationCatalog, operation |
(OPERATION) the operation-surface catalog + operation id to invoke. |
allowedRoles[], requiresApproval |
Governance — role gating + an optional human-in-the-loop gate. |
tags[], status, owner |
Lifecycle metadata. |
Invoking an action
An action can be invoked two ways — REST and SQL — and both run the same code
(ActionInvoker). That is deliberate: an action writes to a system of record, so a second
entry point must not become a weaker path around the governance. The only difference recorded
is via=REST / via=SQL in the audit entry.
# REST
curl -X POST "$ADMIN/api/v1/action-types/lake.sales.approve_invoice/invoke" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{ "args": { "id": 42, "reason": "reviewed" }, "idempotencyKey": "req-42" }'
-- SQL (JDBC / Arrow Flight SQL / MCP) — see "Calling an action from SQL" below
CALL lake.sales.approve_invoice(id => 42, reason => 'reviewed', idempotency_key => 'req-42');
Either way the invoke is fail-closed and governed. In order:
- Authorization — the caller must be an administrator or hold an explicit
ontology:InvokeActiongrant on the action's FQN. Denied by default. AnOPERATION-mode action additionally requiresontology:InvokeOperationonoperation:<operationCatalog>:<operation>— writing into an external system is a different power from reading the warehouse (data:table:…), so it is granted separately. That second check is report-only untilontul.iam.operation.permission.enforced=true; while it is off, a missing grant is audited asaction:invoke:grant-missingnaming the exact resource, so the grants can be added before enforcement is switched on. - Idempotency — an optional
idempotencyKeyis checked against a ledger (actionrun:); a retried key returns the prior result without re-applying the write. - Parameter validation — every required parameter must be present, else
400. - Approval gate — if
requiresApprovalis set, the invoke is staged (returns202 pending_approval) rather than applied. - Execution:
- DML — the template is rendered into injection-safe SQL and executed through the query engine against the source of record; the result is audited and the idempotency outcome recorded.
- OPERATION — the action's
operationCatalogis resolved to a connector exposing an operation surface (see below) and the named operation is invoked; the result is audited and recorded identically.
Calling an action from SQL
Ontul's catalog concept comes from Trino, where a catalog is the namespace of everything a connector exposes — procedures included, not only tables. An action is reachable as a procedure:
CALL erp.default.post_invoice(order_id => '1001', amount => 250.00);
-- retry-safe: the same key returns the prior outcome instead of posting twice
CALL erp.default.post_invoice(order_id => '1001', idempotency_key => 'req-42');
- Named arguments only. Positional arguments are rejected: an action writes to a system of record, and a mis-ordered positional argument is exactly the mistake that is expensive to discover afterwards.
idempotency_keyis reserved, not an action parameter. It carries the same de-duplication token the REST body passes and shares the same ledger, so retrying a statement — or switching from REST to SQL — never re-applies the write.- Returns one row:
action | status | detail, wheredetailis the JSON document the REST endpoint returns.statusisOK,REPLAYED(idempotent retry), orPENDING_APPROVAL— the last one means the call was staged for a human, not applied. - Approval gates, validation, audit and lineage behave exactly as on the REST path.
Discover what can be called:
SELECT routine_catalog, routine_schema, routine_name, mode, target, parameters,
requires_approval, status
FROM information_schema.routines;
| Column | Meaning |
|---|---|
ROUTINE_CATALOG / _SCHEMA / _NAME |
the action's FQN, i.e. what CALL addresses |
ROUTINE_TYPE |
always PROCEDURE |
MODE |
DML (writes the warehouse) or OPERATION (writes an external system) |
TARGET |
for OPERATION: <operationCatalog>:<operation> |
PARAMETERS |
declared parameters with type and nullability |
REQUIRES_APPROVAL |
YES → a CALL returns PENDING_APPROVAL, it does not apply |
STATUS |
lifecycle status of the action definition |
The same rows back JDBC DatabaseMetaData.getProcedures(), so a BI tool or an agent finds
actions without a side channel. This is also why an operation catalog shows no tables: its
surface is procedures, and that surface is listed here.
Injection safety
The same explicit model as retrievers applies: the template is admin-authored
and trusted; the arguments are caller-supplied and untrusted. Each parameter's
declared type governs how its value is rendered — STRING becomes a
single-quote-escaped literal ('…''…'), LONG/DOUBLE/BOOLEAN are validated
and inlined bare, and IDENT is restricted to [A-Za-z0-9_.]. A caller cannot
smuggle SQL through an argument, and an argument referencing an undeclared
parameter is dropped.
Action workflows (Saga / DAG)
A single write is one action. A real operation is often several — reserve stock, charge payment, create a shipment — that must run as one governed unit and, if a later step fails, undo what already ran. An action workflow is that unit: a DAG of action types executed server-side as a Saga.
Because the writes target external systems of record over REST (which cannot join a distributed transaction and cannot be rolled back), "all-or-nothing" here means run to completion, or compensate what ran — a best-effort logical inverse call per completed step, not a database rollback. Orchestration, compensation, idempotency, and audit are the platform's responsibility, so a caller — an agent, an app, the admin UI — invokes one workflow and gets the whole guarantee.
DAG, not just a chain
Each step names an already-registered action and its place in the graph:
| Step field | Purpose |
|---|---|
id |
Step id (referenced by other steps' requires). |
action |
FQN of a registered action type to run. |
args |
param → workflow-input key (or =literal) — builds the action's args from the workflow's input. |
requires |
Ids of steps that must complete first. Empty = a root. |
compensate |
(optional) FQN of a registered action that undoes this step on rollback. |
compensateArgs |
(optional) arg mapping for the compensate action; defaults to this step's own args. |
Steps run in topological order: a step runs once its requires are done, so
independent branches (a "diamond") both run and a join step waits for both. A
workflow where no step declares requires degenerates to declared (linear)
order — so a simple sequence is just the trivial DAG. Cycles and references to
unknown steps are rejected at registration (400).
Authoring in YAML
Workflows are authored as YAML (the admin UI is a full-width YAML editor; the
same document round-trips via the REST API). A step's compensate is optional
— omit it and that step is simply skipped on rollback.
catalog: erp
schema: ops
name: fulfill_order
title: Fulfill order
steps:
- id: reserve
action: erp.ops.reserve_stock
args: { orderId: orderId }
compensate: erp.ops.release_stock # registered action; undoes reserve
- id: charge
action: erp.ops.charge_payment
args: { orderId: orderId, amount: amount }
requires: [reserve]
compensate: erp.ops.refund_payment
compensateArgs: { orderId: orderId } # optional; else the step's own args
- id: ship
action: erp.ops.create_shipment
args: { orderId: orderId }
requires: [charge] # no compensate → nothing to undo
Register it with POST /api/v1/action-workflows/yaml (body = the YAML) and export
the editable YAML back with GET /api/v1/action-workflows/{fqn}/yaml (server-
managed/computed fields — fqn, createdAt, owner, … — are stripped so you see
only what you can edit). A JSON form of the same document works via
POST /api/v1/action-workflows.
Invoke → run, or roll back
POST /api/v1/action-workflows/{fqn}/invoke runs the Saga:
- Authorization — gated on
ontology:InvokeWorkflowfor the workflow FQN (admin-bypass, fail-closed). An optionalrequiresApprovalstages it instead. - Idempotency — an optional
idempotencyKeyreturns the prior run's result. - Forward pass — steps execute in topological order; each result records
ok, and where it ran (driver). - Failure → compensation — the first hard failure (a step that isn't
continueOnError) stops the forward pass and runs each completed step'scompensateaction in reverse order. Final status iscompleted,completed_with_errors, orrolled_back.
curl -X POST "$ADMIN/api/v1/action-workflows/erp.ops.fulfill_order/invoke" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{ "input": { "orderId": 42, "amount": 199.90 }, "idempotencyKey": "order-42" }'
# → { "status": "rolled_back", "steps": [...], "compensations": [ {"via":"erp.ops.refund_payment",...}, {"via":"erp.ops.release_stock",...} ] }
Governance — every action stays governed
Compensation is a workflow concern, so it lives on the step (compensate), not
as a property of the action type. And crucially, a compensate must be a
registered action — never inline ad-hoc REST — because both the forward step
and the compensation run through the same governed path: each is IAM-checked on
its own ontology:InvokeAction (fail-closed, admin-bypass), parameter-
validated, and audited. RBAC is thereby delegated to each registered action: a
compensation runs only if the caller may invoke that action. There is no way to
smuggle an ungoverned call into a rollback.
Execution — master coordinates, workers act
The master is the logical driver: it owns the DAG scheduling, compensation
order, idempotency ledger, IAM, and audit. The actual work — the (possibly
slow) outbound REST call of an OPERATION action — is delegated to a worker,
exactly the way batch/streaming jobs are dispatched (TASK_ASSIGN). The master
resolves the concrete request (base URL + auth from the operation catalog /
Connection ID) and hands the worker a self-contained call to
perform; it falls back to running the call in-process only when no worker is ready.
So a long-running action doesn't tie up the master, and action execution scales
across the worker fleet.
Relevant timeouts are configurable:
# ontul.properties
ontul.action.exec.timeout.ms=600000 # master awaiting a worker's action result
ontul.action.http.timeout.seconds=60 # the action's outbound HTTP request
The operation surface (REST connector)
Not every write is SQL DML. To let an Action write back to an external operational
system of record over REST, Ontul provides a generic rest-operation connector
— the substrate for OPERATION-mode actions. A catalog registered with this
connector exposes a set of named, parameterized HTTP operations. Any operational
system with an API fits: ERP, CRM, payment, ticketing, an internal service. An ERP
integration, for instance, is simply this connector plus an ERP profile
(default headers / conventions) and the ERP's operation set — the connector is
generic, and a "profile" adapts it to a given system.
Config keys for a rest-operation catalog:
| Key | Purpose |
|---|---|
baseUrl |
The external base URL (e.g. https://erp.example.com/api). |
authType |
NONE / BASIC / BEARER / HEADER; credentials via user/password, token, or authHeaderName/authHeaderValue (resolvable through a Connection ID). |
profile |
generic / sap-odata / … — supplies default request headers (content type / accept conventions). |
operations |
A JSON array of operations, each { id, method, path, body, headers?, successCodes? } with ${param} placeholders. |
Rendering is injection-safe: path values are URL-encoded and body values are JSON-escaped (the template author supplies the surrounding JSON structure and quoting, the same contract as the SQL template). The connector is a write target, not a readable source, so it exposes no tables.
{
"connector": "rest-operation",
"baseUrl": "https://erp.example.com/api",
"profile": "generic", "authType": "BEARER", "token": "…",
"operations": "[{\"id\":\"post_invoice\",\"method\":\"POST\",\"path\":\"/invoices\",\"body\":\"{\\\"id\\\": ${id}, \\\"amount\\\": ${amount}}\",\"successCodes\":[200,201]}]"
}
Agent access (MCP)
Every primitive is exposed on the Ontul MCP server so an agent discovers and uses the ontology through one governed surface — the read tools and the write tool are symmetric:
| Tool | Purpose |
|---|---|
ontul_list_object_types / ontul_describe_object_type |
Discover object types + their property contracts. |
ontul_query_object_type |
Read instances (ObjectSet query) by property filters. |
ontul_list_link_types / ontul_describe_link_type |
Discover relationships. |
ontul_traverse_link |
Walk a relationship from a source object to its related objects. |
ontul_list_action_types / ontul_describe_action_type |
Discover write actions + their parameter contracts. |
ontul_invoke_action_type |
Invoke a governed write-back (DML or OPERATION). |
ontul_list_action_workflows / ontul_describe_action_workflow |
Discover multi-action workflows + their step DAG. |
ontul_invoke_action_workflow |
Run a workflow as one governed Saga (with reverse compensation on failure). |
Because governance (IAM, idempotency, audit) is enforced by the master beneath these tools, an agent gets the ontology's read/write power without a path around its controls.
Admin UI
The admin UI manages the whole ontology under dedicated pages — Object Types, Link Types, Action Types (with a parameter editor and an invoke tester), and Action Workflows (a full-width YAML editor with a diamond-DAG starter template, plus a DAG visualization and an invoke panel that shows the run outcome and any compensations). The Catalogs page gains a REST catalog type (base URL, profile, auth, and a JSON operations editor) for the operation surface, and a Drivers page manages uploaded JDBC driver JARs (one JAR per driver class; uploading a newer version replaces the old one, streamed to every worker). Both REST and NeorunBase catalogs can reference a registered Connection ID instead of inlining credentials.
Row caps
An ObjectSet query or link traversal is bounded by each request's own limit and
by a hard cluster-wide ceiling — the effective cap is the smaller of the two:
# ontul.properties — hard cluster-wide cap on rows returned by an ontology read
# (ObjectSet query or link traversal). Applied on top of each request's own limit.
ontul.ontology.read.max.rows.ceiling=10000
Both the relational (JOIN / ObjectSet) path and the GRAPH pushdown honour this ceiling.
Certification (trust an agent can act on)
Every ontology definition carries a status of DRAFT / CERTIFIED / DEPRECATED. On its own
a status is only a label, so Ontul adds two things that make it mean something, and folds the
result into one read-only field: effectiveStatus.
effectiveStatus |
Meaning |
|---|---|
CERTIFIED |
Signed off, unchanged since, and everything it is built on is certified too. |
STALE |
Was certified — then the definition changed underneath the signature. |
DRAFT |
Never certified (or capped there by a dependency). |
DEPRECATED |
Retired. |
Certifying is its own endpoint, never a field you send:
curl -XPOST $ADMIN/api/v1/object-types/ontology.sales.Customer/certify -H "$AUTH" -d '{}'
# → { …, "status": "CERTIFIED", "certifiedBy": "alice", "effectiveStatus": "CERTIFIED" }
# Undo, or retire:
curl -XPOST $ADMIN/api/v1/object-types/ontology.sales.Customer/decertify -H "$AUTH" \
-d '{"status":"DEPRECATED"}'
The certifier is the caller — status, certifiedBy, certifiedAt and the fingerprint are
server-owned and ignored on register/update, so nobody can register a definition that declares
itself certified, or name someone else as the approver.
Certification breaks when the definition changes
Certifying stamps a fingerprint of the parts that decide what the definition resolves to — the
read source, the property→column bindings, the primary key, the join keys. Change any of them and
effectiveStatus drops to STALE on the next read:
{ "fqn": "ontology.sales.Customer", "status": "CERTIFIED",
"effectiveStatus": "STALE",
"certificationNote": "the definition changed after it was certified" }
Wording is deliberately excluded: editing a description, a title, a synonym or a tag never revokes a sign-off. Re-certify to sign off on the new shape.
Trust cannot exceed what it is built on
effectiveStatus is capped by dependencies:
- an object type cannot be more trusted than the semantic view it reads from;
- a link type cannot be more trusted than either of the object types it connects;
- an action type cannot be more trusted than the object type it mutates.
So a CERTIFIED object type over a DRAFT view reports DRAFT, with the reason in
certificationNote. An agent asking "is everything behind this answer certified?" reads one field
per object instead of walking the graph itself.
Where it shows up
- Reads carry it.
GET /api/v1/object-types,GET .../{fqn}, the ObjectSetPOST .../queryresponse and the linkPOST .../traverseresponse all includeeffectiveStatus(+certificationNote), so a client rendering rows knows what they are worth without a second round-trip. - MCP returns the same fields through
list_object_types/describe_object_type. - Admin UI shows the badge on the Object Types page, with a Certify button and the reason as a tooltip on a STALE badge.
Who may certify
Certification is an ownership decision, not an editing one, so it has its own IAM action —
ontology:Certify — separate from the administrator rights needed to edit the ontology. That
lets a domain owner bless their own subtree without being made an admin:
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "CertifySalesOntology",
"Effect": "Allow",
"Action": "ontology:Certify",
"Resource": "ontology.sales.*"
}
]
}
Shipped in the Admin UI's policy editor as Ontology Certifier (Domain Owner) — see IAM Policy Templates.
Administrators may always certify. Every certify / decertify is written to the
Audit Log as ontology:certify / ontology:decertify, and a refusal as
ontology:certify:denied.
REST API summary
All routes are under the master's admin HTTP port and reuse the same IAM token
as the rest of the admin surface. Reads are IAM-filtered on the backing read
source; registration/deletion is administrator-gated; action invoke is gated on
ontology:InvokeAction; certification on ontology:Certify.
| Method + path | Purpose |
|---|---|
GET/POST /api/v1/object-types |
List / register object types. |
GET/DELETE /api/v1/object-types/{fqn} |
Fetch / delete one. |
POST /api/v1/object-types/{fqn}/query |
ObjectSet query (filters / select / limit). |
GET/POST /api/v1/link-types |
List / register link types. |
GET/DELETE /api/v1/link-types/{fqn} |
Fetch / delete one. |
POST /api/v1/link-types/{fqn}/traverse |
Traverse from a source object (JOIN or GRAPH). |
GET/POST /api/v1/action-types |
List / register action types. |
GET/DELETE /api/v1/action-types/{fqn} |
Fetch / delete one. |
POST /api/v1/action-types/{fqn}/invoke |
Governed write-back (DML or OPERATION). |
GET/POST /api/v1/action-workflows |
List / register workflows (JSON). |
POST /api/v1/action-workflows/yaml |
Register a workflow from YAML. |
GET /api/v1/action-workflows/{fqn} · /yaml |
Fetch one (JSON) · export editable YAML. |
DELETE /api/v1/action-workflows/{fqn} |
Delete one. |
POST /api/v1/action-workflows/{fqn}/invoke |
Run the Saga (gated on ontology:InvokeWorkflow). |
POST /api/v1/{object-types,link-types,action-types,action-workflows,retrievers}/{fqn}/certify |
Sign off (gated on ontology:Certify). |
POST /api/v1/…/{fqn}/decertify |
Return to DRAFT, or {"status":"DEPRECATED"} to retire. |
Related
- Semantic Layer — metrics + dimensions the engine rewrites.
- Retrievers — vector / graph / full-text retrieval pushdown.
- Connector Architecture — how catalogs and
connectors (including
rest-operation) are registered. - IAM — the actions and grants the ontology's governance builds on.