# Ontology

One typed object model across the desk, SAP and Salesforce: object types from system metadata, objects, scored links, bound actions, lineage and agent tools.

The ontology joins the systems an agent works in into one object model. A ticket can be linked to the SAP equipment it names, and that equipment to the customer's Salesforce account. Agents then ask "what is this, what is it related to, and what can be done to it" instead of reading each system's raw API.

Open **Connect** › **Ontology** (`/ontology`). It has tabs for **Types**, **Objects**, **Links** and **Lineage**.

## Object types

A type is named `<system>:<Type>`, for example `liya:Ticket`, `sap-s4hana-cloud:A_Equipment` or `salesforce:Account`. Each type has typed properties, keys, relations to other types, a source system and a record of where it was built from.

Property kinds: `string`, `text`, `integer`, `number`, `boolean`, `date`, `datetime`, `id`, `reference`, `picklist`, `email` and `list`.

Choose **Build types** (`POST /api/ontology/build`) and pick a source:

| `kind` | Builds from |
| --- | --- |
| `salesforce` | The org models read on the [Salesforce](/docs/connect/salesforce) setup page. Every reference field becomes a relation |
| `sap` | The SAP products your SAP servers connect to, narrowed to the APIs each server enables (or one `product`'s whole catalogue) |
| `edmx` | A pasted OData `$metadata` document, for the `system` you name |

`liya:Ticket` and `liya:Source` are built in.

- **Custom types.** Choose **Add type** for a system that ships no metadata. A build never overwrites a custom type.
- **Overlays.** On a generated type you can change only the label, description and relations. The change is kept as an overlay that a rebuild preserves.
- **Relations.** Open a type to add one, for example on `A_Equipment` add `account` to `salesforce:Account` where `Customer` equals `AccountNumber`.

> [!NOTE]
> SAP metadata does not give a navigation's target type, so SAP navigations are kept as relations with no target until you name one. Cross-system relations such as equipment to account are declared by hand; they are not suggested.

## Objects

An object is keyed by its origin: system, type and id. It holds bounded properties and the identifying values the resolver matches on. Tickets and sources are not copied: they are read from where they live.

Objects come from reads of bound tools (with lineage on), from the API, and from the resolver's inputs. There is no scheduled crawl that copies SAP or Salesforce records in, so a ticket only links to records something has already read.

## Links

A link is a typed relation between two objects, with a confidence and the evidence for it. **Resolve links** on the **Links** tab (`POST /api/ontology/resolve`) resolves one ticket, or the newest tickets as a job. Pipeline syncs also resolve the tickets they open.

| Evidence | Confidence |
| --- | --- |
| A type's declared relation: a property equals the target's key or named match property | 0.95 |
| An identifier in the ticket text with its type named beside it | 0.9 |
| The requester's email address on a record | 0.85 |
| An identity the identity map says the requester holds | 0.8 |
| An identifier alone | 0.75 |
| Another identifying property, such as a serial number | 0.6 |

When the resolver finds a link again, the higher confidence and its evidence are kept. A link you add by hand has confidence 1 and stays manual.

## Actions

An action binds an MCP tool to a type and says how each argument is filled from an object: an SAP write, a Salesforce Flow, or any server's tool. Open a type to see suggested actions and choose **Bind** or **Unbind**. Suggestions come from each connection: an SAP type's get, update, delete and bound actions, and a Salesforce type's `get_record` and `update_record`.

Whether an action reads or writes comes from the server's own classification, never from the person binding it. The ontology never calls a tool. The agent calls it through its own server, so writes still go through approvals and SAP confirmations.

## Lineage

`TICKETIQ_ONTOLOGY_LINEAGE` (default `true`) records who read or wrote which object, when, and whether through a sync or a tool call:

- A call of a bound tool is lineage for the object its key arguments name, with outcome `ok`, `error` or `pending` (with the approval id). When a read returns JSON, each record in it is observed as an object.
- Each source sync records that the source was read. A pipeline sync records each ticket it opened and resolves its links.
- A [stream consumer](/docs/connect/stream-consumers) records what it read once per quality window.

Lineage is kept `TICKETIQ_ONTOLOGY_LINEAGE_DAYS` (default `90`), never less than the retention floor. A row a legal hold covers is kept; under the audit legal hold, or when holds cannot be read, nothing is pruned.

Edits to tickets by people or by ticket tools, and knowledge article writes, are not recorded as lineage.

## Agent tools

With `TICKETIQ_ONTOLOGY_AGENT_TOOLS` (default `true`), managed agents are offered three tools:

| Tool | Does |
| --- | --- |
| `ontology_describe` | Describes a type: properties, relations, actions |
| `ontology_get` | Reads one object, with its links and its actions with arguments filled |
| `ontology_related` | Follows links from an object |

- Cedar decides each call on `Tool::"ticketiq.ontology/<op>"` (`describe`, `get` or `related`), and checks again at call time. On a run as the invoker, the person's `ontology.read` and Cedar apply too.
- Reads are scoped to the agent's tenant. Each call is audited as `agent.ontology.call` with the tool and type, never the result.
- An action listed to the agent says whether the agent has that server attached.
- A ticket comes back as its id and links only. Its title, description and requester are read with the ticket tools, under their own tenant fence, Cedar and data policies.

The tools are offered to every managed agent; there is no per-agent opt-in. Narrow them with a Cedar forbid:

```text
forbid(principal == TicketIQ::Agent::"helper", action == TicketIQ::Action::"call_tool",
       resource == TicketIQ::Tool::"ticketiq.ontology/get");
```

The governed `/mcp` server does not expose these tools, so external MCP clients cannot reach them.

## Routes and permissions

`ontology.read` is implied by `sources.read`; `ontology.write` by `sources.write`. A custom role can hold either alone.

| Task | Endpoint | Permission |
| --- | --- | --- |
| What is on, and how much the model holds | `GET /api/ontology/status` | `ontology.read` |
| List types, read one with bound and suggested actions | `GET /api/ontology/types`, `/types/{name}` | `ontology.read` |
| Add, edit or delete a type | `POST /api/ontology/types`, `PUT` or `DELETE /types/{name}` | `ontology.write` |
| Build types | `POST /api/ontology/build` | `ontology.write` |
| List objects, read one with links, lineage and filled actions | `GET /api/ontology/objects`, `/objects/{type}/{id}` | `ontology.read` |
| Record or forget an object | `POST /api/ontology/objects`, `DELETE /objects/{type}/{id}` | `ontology.write` |
| List, add or delete links | `GET`, `POST /api/ontology/links`, `DELETE /links/{id}` | `ontology.read` / `ontology.write` |
| Related objects | `GET /api/ontology/related?type=&id=&depth=` | `ontology.read` |
| Resolve links | `POST /api/ontology/resolve` | `ontology.write` |
| Bind or unbind an action | `POST /api/ontology/types/{name}/actions`, `DELETE .../actions/{server}/{tool}` | `ontology.write` |
| Lineage | `GET /api/ontology/lineage` | `ontology.read` |

Reading a ticket's fields at `/api/ontology/objects/liya:Ticket/{id}` also needs `tickets.read`. Every write is made in the caller's own tenant and audited as `ontology.*`. On an instance with tenants, an account with no tenant is refused a write.

> [!WARNING]
> Objects observed from a bound read are stored in the caller's tenant, shaped by the data policies of whoever made that call. Anyone in the tenant with `ontology.read` can then read them. There is no per-object access control yet.

## Deletion and erasure

- **Forgetting an object** removes it and its links. Its lineage stays: what happened to it still happened.
- **A source deletion** removes the deleted records' objects and their links, and keeps their lineage. See [Deletion propagation](/docs/connect/deletion-propagation).
- **A deleted ticket** loses its object row, its links and its lineage, in every tenant.
- **A GDPR erasure** finds objects, links and lineage that name the person. Their own objects, and any whose id names them, are deleted with their links and lineage: these are copies, and the source system keeps the original. An object that only mentions them is kept with every mention replaced. A link naming them is deleted, so it cannot tie a pseudonymised ticket back to them. A lineage row is redacted, or deleted with its object. See [GDPR](/docs/govern/gdpr).
- **A deleted tenant** loses its types and bound actions, and its objects, links and lineage unless a hold on the tenant or the audit legal hold keeps them.
