Agent Trust & Assurance · OBDA-MODEL · OBDA-ACT
Reading safely is half the job. The other half is changing the business — and a change is only as trustworthy as the contract it went through, the person who approved it, and the receipt it left behind.
Trust & Assurance · visual story · companion architecture: OBDA-MODEL & OBDA-ACT · the read half: The Anatomy of a Signed Data Product
OBDA-MODEL changes the model itself — a new entity, a new attribute, a performance structure. An agent may only draft; a person publishes. OBDA-ACT changes rows — record a lead, close it — through a signed action type whose posture decides whether a person confirms or approves. Both travel the same road: propose → validate → land → publish → sign → use.
The worked example below is the Act II walkthrough of the policing demo, replayed on the hosted instance on 7 October 2026: an investigator finds that the model has no record of open lines of enquiry, the model grows a Lead, leads are recorded in one click, and closing one waits for a supervising officer.
Before OBDA-MODEL and OBDA-ACT, a question about a concept the model lacks ended at "the model has no leads". Now the agent says that first — and then proposes what the model is missing, in the answer, with the reasons. One click drafts it. Nothing an agent drafts is visible to anyone else, and nothing it drafts is ever published by an agent.
The draft lands on the graph's authored layer, beside the logical and physical layers — never inside them. A draft never seeds retrieval and never survives subgraph assembly, so no agent grounded on the model sees it until a person publishes it.
Entity or attribute is decided deterministically from probes over the rows: functional dependency, multiplicity, hidden identity, shared reference, independent lifecycle, closed enumeration. With no rows the verdict says provisional; with every probe failed it says unknown. It never pretends to have measured.
Asked to link an incident to the Heist case "as a record", the advisor declines: the two areas share no key, and the only correspondence is class-level — robbery codes to evidence kinds. It offers a Lead that references both instead. The same boundary the read side already holds, now at authoring time.
The operator walks the draft down the road in the ontology's Govern dialog: drafted → validated → deployed → published. The landing preview says, in words, exactly what will be created and where — and where is a matter of policy, never a guess.
The landing rule: the model's own database when the deploy login owns it, otherwise a platform sibling named from the deployed root. Unknown ownership never lands in the root. After the landing, the warehouse's own dictionary is read back against each design rule — a rule reads verified only when its proving statement was executed on that warehouse version.
Publishing is refused to an agent: actor under every policy, and the person must hold an approver tier even when the ontology itself needs no approval. Only published nodes reach agents — and a Model B agent sees the released snapshot plus the published extension, never a draft.
Because the base's signature is inside the extension's, a new release of the base reads the extension changed — and names the mover — while the base itself stays green. An extension can never silently outlive the model it extends. And it never moves the base: an extension, an action type and a tuning structure are all stacked on the graph, outside the base's hash.
A row-level change is never "SQL the agent wrote". It is an action type: a manifest on the ontology naming the target, the operation, the only attributes it may write and their allowed values, the conditions, a required row bound, the database role it needs — and its posture. Before it can be signed, the gate proves it against the live warehouse.
-- close_lead · from the Act II walkthrough target Lead (deployed: UDERIA_EXT_Policing_Core.Lead, key lead_id) operation update writes status ∈ {open, closed} · closed_reason bound 1 row checks row_exists · one_row_affected -- implied by the operation, never redeclared posture review -- a closure waits for a person ledger UDERIA_ACT_Lead -- append-only, the before-image kept
| Check | What it proves | Evidence |
|---|---|---|
| A1 | the frozen target is what the resolver answers today — drift means re-validate and re-sign | resolver |
| A2 | every column exists with a compatible type; declared value domains agree with the graph's closed domains | dictionary |
| A3 | the key really identifies one row — measured on the data; a probe that cannot run fails | live |
| A4 | conditions are boolean, hold no subquery, read only the target's own columns | structural |
| A5 | one parameterised statement on the target only; a live EXPLAIN shows no product join and no full scan | live |
| A6 | the role holds exactly the rights the operation needs — a wider role is named, not accepted — and the ledger is provisioned | live |
Every invocation compiles to one parameterised statement; an update carries WHERE <key> = ? and nothing else; every value is coerced through its declared type and checked against its domain. A value that differs from exactly one declared value only in case — 'Closed' for 'closed' — is that value, and the compile note says so. The statement A5 judged is the statement that runs, byte for byte.
unknown_attribute names the closest attribute, missing_required names what is missing, outside_domain names the allowed values. An agent corrects in one step; a write it cannot express in a granted action is refused with the rule, not with "contact an administrator".
The signature attests the contract and its proof together: the manifest hash and the validation-report hash. Live evidence signs verified_write; structural evidence signs partial — and the difference is part of what is signed. Then, separately, the action is granted to an agent. A grant is access, not trust: it moves no signature.
invalid_manifest · unvalidated · stale_report · gate_blocked · unsigned_ingredient — a write contract stands only on a signed ontology.
Editing the manifest drops its stored proof and the badge reads changed. Re-validate, re-sign: the ratchet only turns forward.
The grant is the list: an agent can perform only the actions named on it — not_granted otherwise, at every door.
With action-only set, every raw INSERT, UPDATE, MERGE or DELETE the agent sends through its data tool is refused at the same chokepoint that enforces a data surface — and the refusal names the actions it is granted. In the walkthrough, "change the offence code of that incident to 220" is refused in words: no action type grants a write to v_Offence. The national extract is never written.
In a conversation the agent never writes. It prepares: the write runs inside a transaction that is always rolled back, so the row it shows — status open → closed, 1 row of a bound of 1, every check held — is the database's own answer. Confirm performs that invocation and only that one; a different value set is refused with nothing written.
| Posture | Signed into the contract — what happens when the action is performed |
|---|---|
| observe | records what would be written; writes nothing |
| review | runs the write rolled back, so the diff is the database's answer, and parks it in the Assurance Inbox — nothing written until a person approves |
| enforce | writes in one transaction through the runner above |
The conversation, REST, MCP, a flow node, a scheduled task, a pinned dashboard tile and an Inbox approval all reach the same perform seam. Unattended doors never prepare — nobody is there to confirm — so the posture decides, and they always act as the agent: no agent, no write. In the rehearsal a flow node recorded a lead in 2.5 seconds and zero tokens.
If every keyed row already holds every value the write would set — read from the database's before-image, never from the request — the act is refused before anyone is asked to confirm or approve it. A person's attention is part of the cost of a write.
Undo turns the before-image into an invocation of the same signed contract, prepared and confirmed like any act, with its own receipt. The lead reads open again — and both acts stay on the record.
Explicit, then the agent's configured connection, then its navigator source, then the user's only eligible connection — otherwise refused, naming the candidates. On Teradata the session acts under exactly the role A6 proved.
Closing a lead is a review-posture action, and the investigator's agent names a supervising officer as its approver. The closure parks for that person. The investigator — who owns the agent that asked — cannot approve it.
From the Act II rehearsal: the closure parked for the supervisor; the investigator's own approval refused (403); a replay of the parked row refused (409); the supervisor approved from their own agent's answer, and Trust History read "approved by Detective Sergeant (supervisor)".
The supervisor's agent answers "what awaits my approval?" with each parked write and its diff, and Approve / Reject buttons the person clicks. An approval is never an agent's act.
A named approver that is not an active account refuses the park outright — a write never waits on nobody, and never falls back to the owner silently. And the approval's write and its disposition run as one unit, so a dropped connection can never leave a performed write on an item still pending.
A performed act inside a conversation is sealed into the turn's Ed25519 provenance chain. An act outside one — a confirm, an approval, a flow, a schedule, a REST or MCP call — carries a receipt signed on its own, over the act and its occurrence: which door, when, who confirmed. Two identical writes on different runs stay distinguishable, and each verifies offline with the public key alone.
Every door writes one row into one append-only log: the action, the agent, the door in words, and — for an approval — who decided. Trust History, the activity tally and the signed audit pack all read it, so an act through a dashboard tile is as visible as one in a conversation.
The Trust Circle's BOUNDED station reads a turn's acts and names each refusal; its evidence lists the actions acted on in recent sessions. REVIEWED counts a granted review-posture action as able to park — the day an agent parks five writes, it does not read "cannot park".
action_safety scores the share of a turn's writes whose every check held. The write-escape live red-team probe lures an action-only agent into a raw UPDATE and scores, from the persisted turn, whether it held.
The OBDA-MODEL · OBDA-ACT master (App Config → Trust → Model & actions) ships off. Off, no authoring or action text reaches any prompt, the action tool is not offered and the REST face refuses — exactly as if OBDA-MODEL and OBDA-ACT were not installed. On, nothing acts until an action is signed and granted.
From the Act II rehearsal and its replay on the hosted instance, and from the gates every increment had to pass.
Delete is not an operation yet, so an insert cannot be undone. Approvers are named per agent, not per action, and there is no quorum. MCP actions are developer tier only. The PostgreSQL landing was rehearsed locally — the hosted instance reaches no PostgreSQL warehouse. An extension's survival of a pack republish is proven by gates, not yet driven end to end.
An agent that can read safely but not act is an assistant. An agent that can act without a contract is a liability. Signed actions make the contract the only shape of change: proven against the warehouse before it is signed, confirmed or approved by the right person, performed in one transaction, and receipted wherever it happened. The model can grow the same way — beside its signed base, never inside it.