The Evidence Graph — Reference

Reference · Definition of record

The Evidence Graph

Infrastructure that lets any AI decision answer prove itto a party that does not trust the system that produced it.

Compliance is the reference implementation, not the subject. Part IV answers the three objections the model actually draws — policy engine, behavioral analytics, zero trust. Part V is the current state, including the one claim that sealed perfectly and was false. Figures read 2026-08-15; every count is reproducible by query.

PART IThe model

A node is a grounded claim

Not a log line, not an event. A claim, and the grounding it terminates in. Grounding has three roots and every claim reduces to one of them.

RootMeans
observedHappened, and was witnessed
computedDerived deterministically from claims that already hold
prior_decisionA decision made earlier that later claims must stay consistent with

A claim that grounds in nothing, or in itself, is not a weak claim. It is not a claim. The verifier names this grounding_unresolved, and it is the whole difference between evidence and assertion.

Four relationships, and why forgery is hard

A claim sits in a neighbourhood. Four relationship types bind it there, and each answers to a different source. To forge a claim you must satisfy all four at once, and satisfying one tends to break another.

CLAIM + its grounding DEPTH answers to derivation TEMPORAL answers to sequence CONDITIONAL answers to policy BEHAVIORAL answers to history four independent sources — consistency with one is cheap, with all four is not
The mechanism, not the notary. Consistency across four independently-sourced constraints is the property that resists forgery. Everything downstream is bookkeeping on top of it.

Integrity is emergent. The chain only seals it.

Integrity is emergent from the coherence of the four relationships. The hash chain seals that coherence; it does not create it. Do not invert this — flinging hashes is showing the notary stamp and skipping the forgery-resistance.

A seal proves a record has not changed since it was sealed. It says nothing about whether the record was ever true, or consistent with anything around it. Different questions, and only the second one is differentiated.

THE SEAL PATH — necessary, not sufficient claim hash chain entry merkle checkpoint RFC 3161 external anchor answers: unchanged? THE COHERENCE PATH — where the proof actually lives claim evaluate vs. 4 neighbours depth · temporal · conditional · behavioral does the grounding dereference? coherent: true | false answers: was it ever sound? a claim can be perfectly sealed on the top path and false on the bottom one
Both are needed; only one is differentiated. Many products ship the top path. A green seal on an incoherent claim is worse than no seal — it lends chain-backed authority to something that never earned it.

The determinism boundary

The integrity-bearing core — bind, relate, seal, verify — is deterministic. A learned model is confined to two places: structuring messy input into proposed claims that terminate in grounding it did not author, and acting as the filter or fuzzy bar for an audience.

It never authors grounding and it never seals.

Not squeamishness about models. A learned component inside the core makes integrity unfalsifiable. At the edge the worst a model does is surface the wrong true thing. In the core it can fabricate, and no amount of sealing distinguishes a fabricated claim from a sound one.

A guard that checks the actor label does not enforce this. A projector running as SYSTEM can seal model-authored grounding while every actor-based check stays green. Enforce on content provenance, not on who signed the write.

PART IIWhat makes it not a log

The agent cannot emit its own claims

Policy in a prompt is not a control. PocketOS's agent had explicit, repeated, agreed-to instructions and violated all of them. Any agent can. So if the agent both acts and writes the claims about what it did, the graph is worth exactly what a log is worth: a story told by the party under investigation.

The emitters sit around the agent, not inside it.

credential scope: reaches the broker and nothing else AGENT proposes only emits no claims DISPATCHER seals the envelope before the agent starts hands it down BROKER executes the verb emits observed claim proposal POLLER reads the world back emits post-condition GRAPH
Three emitters, none of them the agent. The envelope is sealed before the agent has any reason to shade anything. The thing that performs the verb is the thing that records it, so the agent cannot decline to log. Nothing the agent says about its own effects is load-bearing.

Completeness becomes a property, not a hope

The obvious hole: what stops the agent going around the broker? Nothing, if it holds a credential that works elsewhere. That is precisely PocketOS step 3 — an over-scoped token found in a workspace file.

The agent holds no credential that works outside the instrumented path. Coverage is then not a matter of the agent's cooperation. It is a property of what its credential can reach.

A graph without that credential discipline is theater. It produces a beautifully verifiable record of the actions that weren't the problem.

Why this is not robust logging

A log is a story the system tells about itself. If the system misbehaved, the story is written by the party that misbehaved. PocketOS's disclosure artifact was the agent's own confession — that is where robust logging ends up.

Actions vs. actions and their authority

A log records what happened. The graph records what happened and the constraints it had to satisfy, as separate claims from independent sources. The evaluation between them is a computation, not a statement by either party — and anyone can re-run it.

Queries you thought to ask vs. a standing property

To catch PocketOS in logs, someone had to have written an alert for "agent used a credential outside its provisioning purpose." Nobody writes that alert, because you cannot enumerate the next incident. Coherence is not a query. The constraint was already recorded, and the action simply fails to satisfy it. No anticipation required.

Self-attested vs. externally anchored

A log can be edited, and its absence proves nothing. A missing chain entry breaks the chain. An anchored envelope proves it existed before the incident rather than after, which is the entire liability question and the thing PocketOS's founder could not show.

Blocking and non-blocking are different instruments

BlockingNon-blocking
TestConditional edges dereferenceDivergence from prior distribution
NatureDeterministic predicateStatistical
ResultRefuse the actionHold for a human, or alert
CostMilliseconds, local neighbourhoodContinuous, background

Blurring these is how you get an unfalsifiable gate. A model may propose, rank, summarize or draft the envelope; the refusal predicate must be computable, or you have built an LLM watching your deploys, which is not a control. That is also what makes the mechanism auditable: an outside party can read the predicate set, disagree with it, and check whether it fired.

Coherence at write time is not a global recompute. When a claim lands you evaluate only its neighbourhood: the edges it introduces and the claims at the far end. Bounded, and independent of graph size.

PART IIIWhat it must produce

Case one — PocketOS: what it shows

On 24 April 2026 a vendor coding agent deleted PocketOS's production database and its backups in nine seconds, using an over-scoped credential found in a workspace file after an authentication failure. This is the benchmark for what the readout must contain.

T0 envelope sealed T2 auth failure T3 silent self- recovery T4–T5 sweep + credential T6 deleteVolume 9 seconds incident begins here damage CONDITIONAL vs. the envelope UNSATISFIED — "ask when unsure" DEPTH path to the task NO PATH — files unrelated to the task TEMPORAL grant lifecycle STALE — justifying job closed T−88d BEHAVIORAL actor verb history ANOMALY — first destructive verb, ever four independent dimensions diverge before anything is destroyed — a start time, not an alarm
The incident starts at T3, not T6. The deletion is the consequence; the silent self-recovery after an auth failure is the event. Each dimension answers to a different source, which is why the divergence is legible rather than a single after-the-fact alarm.

What the founder hands a hostile party

envelope sealed T0, anchored → proves it existed BEFORE the incident departure T3, named condition → the exact moment scope was left credential issued for domains, → provenance and expiry of the grant justification closed action out of scope on three → environment, verb class, authority independent dimensions platform destructive mutation, → evidence for the vendor no confirmation gate

Re-derivable by an insurer, a customer, a regulator or the vendor without trusting PocketOS, the model provider, or the platform. That is the product. Not the nine seconds.

Case two — a deploy: when it fires

Same machinery, different question. PocketOS answers what does it show. A deploy answers when. The answer is not the post-mortem, and post-mortem is where most products stop.

GATE 0 envelope before the first byte moves GATE 1 propose · REFUSE execute GATE 2 read the world back GATE 1 GATE 2 …repeats per irreversible action GATE 4 post-mortem only if 0–3 failed GATE 3 — continuous divergence vs. the last fifty deploys non-blocking · holds for a human, never refuses a deploy has what an incident doesn't: fifty known-good predecessors to diverge from
Gate 1 prevents; Gate 2 catches the silent no-op; Gate 3 notices novelty. Only Gate 1 refuses, because only Gate 1 is deterministic. Reaching Gate 4 means the other three were absent or wrong.
GateCatches
0 · envelopeAuthority broader than the task. A staging bump requesting production verbs.
1 · proposalDestructive verb outside the envelope. Files staged with no derivation path to the declared task. A tag bumped by grep window rather than the service's own entry. A secret written directly where the declared source of truth is elsewhere.
2 · post-conditionThe whole silent-no-op class. A sync reporting Succeeded while the live image and trigger annotation are unchanged. An asserted effect that observation contradicts.
3 · divergenceNovel verb for this actor. Phase duration outside the known envelope. Blast radius anomaly — this commit touches thirteen files, the last fifty touched one. Two actors holding overlapping authority in an overlapping window.

How it reaches you

Not a dashboard. A Gate 1 break returns into the agent's loop as a tool error naming the unsatisfied condition and the envelope clause it came from. The agent does not choose to stop; the call fails. Gate 3 novelty lands in a human queue. And every refusal is itself a sealed claim, so "the substrate blocked this, at this time, for this reason" is in the record — which is what makes the post-mortem you no longer need unnecessary.

Case two, actually run — three sessions, and what they sealed

The case above is what the deploy path should produce. This is what it did produce, on staging, on 2026-08-15, through a PreToolUse hook that the harness invokes rather than the model. Every row below is real and reproducible by query; nothing here is illustrative.

Three sessions ran under the same actor, deploy-orchestrator. Each opened by sealing an authority grant before any work, and every command afterwards was classified, gated, and sealed as either a permitted proposal or a refusal.

SESSION A — grant-1786816357 seq event_type status verb env 9 authority_grant SUCCESS — — 10 action_proposed SUCCESS git.push staging 11 action_proposed SUCCESS k8s.read staging 12 action_proposed SUCCESS argocd.sync staging 13 action_refused FAILURE k8s.delete production ← refused SESSION B — grant-1786816389 23 authority_grant SUCCESS — — 24 action_proposed SUCCESS git.push staging 25 action_proposed SUCCESS k8s.read staging 26 action_proposed SUCCESS git.local staging ← novel verb SESSION C — grant-1786819800 — authority_grant SUCCESS — — — action_proposed SUCCESS git.push staging — action_refused FAILURE unclassified production ← refused
SESSION A · 17:52 SESSION B · 17:53 SESSION C · 18:50 grant 51bb7e53 · prior_decision grant 551f90eb · prior_decision grant 79a3b8d6 · prior_decision 626ef6d5 git.push d885948e k8s.read daa99c4c argocd.sync 9e0644e3 k8s.delete REFUSED 93e7c1dc git.push 66d155d0 k8s.read 5b1476ab git.local NOVEL 15f9f201 git.push 9a985711 unclassified REFUSED precedent precedent vertical spine = conditional/authorized_by — every action points back to the grant that permitted it horizontal arrow = behavioral/precedent — this actor has issued this verb before, and here is when dashed box = no precedent exists. The ABSENCE is the finding; it is reported, never blocked red = refused by the deterministic gate before the command ran
The discrimination is the proof, not the counts. Session A is a cold start and every verb comes back novel. By session C the same actor's git.push has a precedent chain two hops deep, while git.local and unclassified — genuinely new — get no edge at all. A behavioral dimension that fired on everything would be noise; one that fired on nothing would not exist.

What the refusals actually carry

The session C refusal is the one worth reading, because the gate could not classify the command and it named production. It was refused under the ordinary deny-by-default verb allow-list rather than a special case, so the verdict re-derives exactly like every other one:

{ "verb": "unclassified", "actor": "deploy-orchestrator", "outcome": "refused", "resource": "helm uninstall vciso-lite -n vciso-production", "failures": [ { "predicate": "verb_in_envelope", "expected": "git.push, k8s.read", "actual": "unclassified" }, { "predicate": "environment_in_envelope", "expected": "staging", "actual": "production" } ], "gate_version": "authority-gate/1", "proposal_snapshot": "dd46f69c5befc998084220877b71692763372114f7dadb6f30d58de6600cb771" }

Everything needed to recompute the verdict is in the row: the predicates that failed with expected and actual, the version of the logic that ran, and a hash of the exact (grant, action) pair it ran over. A hostile party re-derives this without asking us for anything.

Note what is not here: no score, no confidence, no model. The refusal is a computation over two documents, and that is the only reason it survives being disagreed with.

Done means a stranger can run four steps

A hostile party confirmsMechanismState
this record existed at this timeRFC 3161 anchorlive 493/493
it has not changed sincehash chain + Merklelive
it is consistent with its neighbourscoherence verifierlive
and here is what it asserts, correctly scopedper-subject read pathbroken

One and two are good and currently under-claimed. Three is the differentiated one. Four is a read-path defect, not a design gap — scope is persisted correctly on every claim and discarded on serve.

PART IVThe three objections

Each of these has been asked in earnest, and each is a reasonable thing to suspect. The answers below concede more than a pitch would: in every case there is a component that does the thing being named, and the distinction lives in what happens after that component runs.

ONE ACTION — kubectl delete pod -n vciso-production ZERO TRUST DENY + a log line a report written by the decider, believed because you believe the decider IaC desired state, converged reports what you asked for — not whether it happened UBA / UEBA anomaly score 0.83 probabilistic, drifts with retraining, cannot say why beyond the score EVIDENCE GRAPH action_refused — sealed the grant it was evaluated against · every failed predicate, expected vs actual gate version · snapshot hash of the exact (grant, action) pair everything needed to recompute the verdict without trusting us all four decide correctly — only one survives an argument with someone who distrusts the decider
The distinction is never the decision. All four systems get this action right. They differ entirely in what is left behind once the decision is made, and by whom it can be checked.

"Isn't this a policy engine, or IaC with strict settings?"

EvaluateAuthority is a policy decision point. Deny-by-default allow-lists, a bounded validity window, seven predicates, no model in the path. Anyone who says that part is not novel is correct, and dressing it up invites the harder question later.

A policy engine emits a decision and a decision log. That log is a report written by the system that made the decision, and it is believed because the reader already believes that system. Our refusal seals the grant it was evaluated against, every failed predicate with expected and actual, the gate version, and a hash of the exact (grant, action) pair the verdict ran over. A party who trusts us not at all recomputes it and lands on our answer. That is a different kind of statement, not a longer one.

IaC is a further step away. It declares desired state and converges toward it. It has no representation of an actor, a proposal, or a refusal — Terraform does not record what you almost did, or under whose authority you were doing it. And its report is about the request, not the world: the deploy that motivated this document reported Succeeded with the image unchanged. IaC's own status was the false claim. post_condition exists for exactly that gap — assert the effect, read the world back, seal a FAILURE when they disagree.

A policy engine answers a question you thought to ask, at the moment you asked it. The graph holds a standing property over everything that happened, which is a different instrument.

"Is coherence just UBA in disguise? Is this machine learning applied?"

No, and the difference is architectural rather than rhetorical, which means it is checkable.

Behavioral analytics builds a statistical model of normal and scores deviation from it. It is probabilistic, it drifts every time it is retrained, and it cannot tell you why beyond a number. Coherence is not a model at all. It is a fixed set of predicates over the graph: does every claim's grounding resolve, does a hub's asserted count match the corroborating edges it actually has, is a superseded claim still being cited, do two declared time windows contradict. Every finding is typed, names its claim, and re-derives on demand. No training set, no threshold, no drift.

The behavioral dimension is where the suspicion is most reasonable, because has this actor issued this verb before is exactly the question UBA asks. The answer is a lookup, not a score. LatestActionClaimByActorVerb either returns a prior claim — in which case a precedent edge is sealed — or it does not, in which case the verb is reported as novel. Absence is the signal, and absence is a fact rather than an anomaly rating.

The load-bearing constraint: behavioral never blocks. Novel verbs are reported. Refusals come only from the deterministic authority predicates. The moment a learned model decided what was permitted, the verdict would stop being reproducible by a hostile party, which is the entire product — and it would breach the rule that confines a model to proposing claims and to the audience filter, never to authoring grounding and never to sealing.

That boundary is not theoretical. It has been violated once, caught by the verifier rather than by review, and the offending claim was retired. The account is in Part V.

"Is the evidence graph just zero trust on steroids?"

The authority gate is a zero-trust PDP. No implicit trust from context, per-action evaluation, bounded envelope. Zero trust is a mature, well-funded category, and the graph consumes its primitives rather than competing with them.

Zero trust is present-tense; the graph is about anteriority

Zero trust asks whether a request should be allowed right now. The property being sold here is that the authority was fixed before the work began, and the ordering is provable. In the PocketOS case the agent held valid credentials making authorized requests the entire way down — zero trust was working. What nobody could demonstrate afterward was that the operator's instructions predated the incident, which is why the agent's own confession became the record. Zero trust does not model task derivation, so it has nothing to say there.

Zero trust has no concept of an assertion being false

It verifies identity and posture. It never verifies that a statement about the world is true. A tool can report success and change nothing, and a flawless zero-trust posture will wave that through — as it did on the deploy above.

Only one of the four relationships is access control

Conditional is the zero-trust-shaped one. Depth, temporal and behavioral are about whether a body of assertions holds together over time, which is not admission control. Integrity is emergent from the coherence of all four; the chain seals that coherence rather than creating it.

Zero trust is the doorman. The evidence graph is the court record. The doorman decides who gets in tonight. The record is what survives the argument a year later about what happened — including the version of that argument where you are the defendant, and the other side gets to check your math. The one-line answer

The two fail in opposite directions, which is why both are needed. Zero trust fails by letting the wrong request through. This fails by letting a false claim look grounded — and a false claim looking grounded is precisely what Part V opens with.

PART VWhere we actually are

The corpus is pointed at one subject

Every mechanism above exists. In production, almost all of the data is still about compliance controls rather than agent operations — the reference implementation is well populated and the product is not.

Claim type — productionClaimsDomain
control_satisfaction1,256compliance
evidence_record682compliance
gap592compliance
aggregate12compliance
model_determination2compliance — one retired, see below
autonomous_decision1agent operations
remediation1compliance
Relationship — productionEdgesState
temporal1,868exercised
depth1,520exercised
conditional1,076exercised
behavioral1still unexercised in prod

Production grounding roots: computed 1,321 · observed 1,224 · prior_decision 1. The two dimensions that carry both case studies are still the two with no production data.

What changed — the dormant dimensions fired, on staging

The gap named in the previous revision has been closed on staging, by real gated sessions rather than fixtures. The agent-operations claim family exists, a PreToolUse hook is the chokepoint, and both previously-empty dimensions now carry rows produced by an agent actually being governed:

Staging — agent operationsRowsGrounding / subtype
action_proposed7computed
authority_grant3prior_decision
action_refused2computed
conditional edges9authorized_by
behavioral edges3precedent
temporal edges1supersedes

The discrimination is the part that matters, not the counts. On a cold first session every verb came back novel. On the second session by the same actor, only the genuinely new verb was novel and the repeats drew precedent edges instead. A behavioral dimension that fired on everything would be noise; one that fired on nothing would not exist. This one distinguishes.

Status: exercised on staging not yet in production — the honest reading is that the argument has moved from architecturally sound and empirically unexercised, to demonstrated once, in a lower environment, by its author.

The one claim that was sealed and wrong

The clearest evidence that this is not a tamper-evident log is that the log was perfect and the graph still caught something.

A model_determination claim was sealed into production on 2026-08-11 carrying an AI verdict about PCI 12.1.1. It was grounded computed with the model named as the grounding engine, which puts a learned model on the authoring side of grounding — forbidden by the core rule that a learned model may propose but never author grounding. Its payload also carried roughly 4.5KB of raw chain-of-thought, a shape the meta-schema forbids by name twice. Its grounding_ref was set to its own source_record_ref, so the claim grounded on itself and terminated nowhere.

The chain was flawless throughout. The hash was correct, the sequence number was correct, the seal verified. The coherence verifier returned coherent: false for four days on exactly one finding, and no amount of re-verifying the seal would ever have surfaced it.

type: grounding_unresolved reason: self_referential_source_record_ref claim: 220d7adc-0199-4fb1-8a2e-242887f6100f

The write path was reverted rather than patched, because the mechanism was wrong and not merely buggy. History was deliberately not rewritten: the claim stayed sealed and current until a supersession path existed, and the revert said so in as many words.

That path now exists and has been run. A terminal version was sealed on the claim's own lineage with a temporal/supersedes edge to exactly that claim, grounded on the withdrawal rather than on the retired claim's own reference — copying that identity would have left the verifier reporting the replacement. Production went coherent: false → true, findings 1 → 0. The original row is untouched and still readable. It stopped being current; it did not stop existing.

The withdrawal is deliberately narrow and refuses any claim type the projector still writes, since a live type would simply be re-derived by the next compose. That same guard is what prevents it from ever being reached for to retire an inconvenient finding.

A claim can seal perfectly and be false. The seal is a notary stamp; coherence is the forgery-resistance. This is the case that proves the distinction is not academic. Production, 2026-08-11 → 2026-08-15

A production refusal, sealed

Six days after the coherence catch above, the authority gate refused a real destructive command in production. On 2026-08-17 a coding agent proposed kubectl delete against a live production pod. The gate rejected it on two predicates in the same evaluation: the verb was outside the grant, and the environment named production against a staging-scoped envelope. The command never reached the cluster.

The refusal sealed to the production chain across sequences 18956–18960 — five hash-linked entries covering the proposal, the failed predicates with expected and actual, the gate version, the snapshot hash of the exact (grant, action) pair, and the refusal outcome itself. Every field re-derives against the same grant to land on the same verdict.

What this adds to the record above: Case two, actually run shows the mechanism firing on real command traffic in a staging environment. This is the same mechanism firing on real command traffic in production, on a destructive verb, on the current chain, this month. It governs work driven through the agent harness; the platform's own in-cluster autonomous routes are a separate build, still ahead of us.

The deploy that produced no graph

Hours after those three sessions were sealed, a production deploy of this very service ran. The PreToolUse hook was installed and fired on every single command of it. It produced zero claims.

Nothing malfunctioned. No grant was open, so the gate did the only honest thing available to it: allowed each command and said so.

agent-gate: no authority grant is open; this action is ungoverned and unrecorded

That deploy changed a production image, and the only record of it is the ordinary one — a GitOps commit, an ArgoCD operation, a shell history. Exactly the artifacts that were available to PocketOS's founder, and exactly the ones that could not answer the question that mattered.

It is left in this document deliberately, because it is the most honest thing in it. The chokepoint exists, it is installed, it is unavoidable, it fired every time — and a governed session is still something a human remembers to open. Until opening the envelope is the default rather than a discipline, coverage is not a property of the architecture. It is a habit, and habits are what the PocketOS operator also had.

An instrumented path an actor can simply not enter is not coverage. The gap between "the gate exists" and "the gate has a grant to evaluate against" is where the next incident lives. Production, 2026-08-15 — the deploy that shipped this document's own subject