Epistemic Audit & Compliance (CONCEPT:AU-KG.enrichment.compliance-posture-rollup)¶
Persona: internal audit, SOC2/compliance officer, or anyone building regulated decision support on top of the knowledge graph — "what did we believe, on what evidence, as of what date, and can I prove the record hasn't been tampered with."
This guide is a walkthrough for a specific narrow question a plain
graph_query Cypher result can't answer on its own: not just what is in the
graph, but why do we believe it, who could see it, and is the trail
provably intact. Everything below wraps existing primitives — no new
compliance or redaction logic was added to write this guide; the tools it
documents (graph_audit, graph_compliance, graph_epistemic) are thin
aggregation/naming layers over the engine's own audit chain and belief
machinery (agent_utilities/mcp/tools/audit_tools.py,
agent_utilities/mcp/tools/compliance_tools.py,
agent_utilities/mcp/tools/epistemic_tools.py).
What you get¶
| Question | Tool / action | Backing primitive |
|---|---|---|
| "Has the audit log been tampered with?" | graph_audit(action="verify") |
Rust SHA-256 hash-chained mutation log (epistemic-graph/src/audit.rs, redb AUDIT table) |
| "What happened to this entity, in order?" | graph_audit(action="for_target", target_id=...) |
KG's own :ToolCall provenance, reverse-indexed |
| "What's our overall compliance posture right now?" | graph_compliance(action="posture") |
audit-chain verify() + node-count/status rollup of the governance labels the CISO Assistant extractor + TRM engine already ingest |
| "Export this subgraph for an auditor, with redaction applied" | graph_compliance(action="export") |
bulk explain_belief(node_id, disclosure_level) over an id list or Cypher selection |
| "Why do we believe this claim?" | graph_epistemic(action="why", node_id=...) |
explain_belief justification tree (Asserted / DerivedSupport / DerivedContradiction / BayesianUpdate) |
| "Do we still believe it, since when, and what would flip it?" | graph_epistemic(action="status", node_id=...) |
epistemic_status acceptance capstone (opt-in epistemic-tms engine feature) |
| "What changed between two audit periods?" | graph_epistemic(action="what_changed", tx_from=..., tx_to=...) |
whole-graph bitemporal diff (opt-in epistemic-tms) |
| "As of last quarter-end, what did the graph say?" | graph_query(cypher=..., as_of="2026-03-31T00:00:00Z") |
bitemporal as_of cutoff on the read path |
All of these are also plain MCP tools you can call from any client that
speaks MCP, and each has a REST twin (POST /audit, POST /compliance,
POST /epistemic on the graph-os gateway — the generic REST-twin factory in
kg_server._build_server mounts one for every entry in ACTION_TOOL_ROUTES
without a bespoke handler).
1. Verify the audit ledger hasn't been tampered with¶
Every durable mutation to the graph already chains into a per-graph SHA-256
hash chain (epistemic-graph/src/audit.rs): each entry binds the previous
entry's hash, so altering, reordering, deleting, or inserting any entry
breaks the chain at that exact position. graph_audit(action="verify")
walks the chain and reports the first break, if any.
// Call
graph_audit(action="verify")
// Response shape (AuditReport, eg-types/src/protocol.rs):
{
"surface": "audit",
"action": "verify",
"available": true,
"graph": "__commons__",
"ok": true,
"entries": 4218,
"first_broken_seq": null,
"detail": "chain verified: 4218 entries, no breaks"
}
If the chain has been altered, ok is false and first_broken_seq names
the exact sequence number where verification first failed — the entry at
that seq (and everything after it) is suspect.
Degrade contract. AuditVerify requires the engine to be built with the
security cargo feature (part of the default full build) and a durable
redb persist dir configured (GRAPH_SERVICE_PERSIST_DIR /
--persist-dir). An in-memory-only engine, or a slim build without
security, returns a clean error instead of raising:
{
"surface": "audit",
"action": "verify",
"available": false,
"error": "audit ledger not exposed by this engine build/config (...). Requires the epistemic-graph `security` cargo feature (part of the default `full` build) AND a durable redb persist dir configured — otherwise this is a corresponding epistemic-graph-side gap, not an agent-utilities one."
}
If you're building toward an audit-defensible deployment, confirm this
returns available: true before relying on the chain for anything — the
Enterprise and Single-node prod
recipes both configure a durable persist dir; the zero-infra
Tiny recipe may not, depending on your .env.
2. Reconstruct "what happened to entity X"¶
graph_audit(action="for_target", target_id="...") is the entity-anchored
half of the same primitive: every :ToolCall that acted on a given entity
id, in call order, reverse-indexed off the KG's own tool-call provenance
(Orchestrator.get_tool_calls_for_target), plus a best-effort chain-verify
snapshot alongside it — so you get both "what touched this record" and "is
the ledger recording it still intact" in one call.
3. One-view compliance posture¶
graph_compliance(action="posture") is a rollup, not new logic: it joins the
same verify() report from step 1 with a node-count / status-breakdown of
the governance labels already ingested into the graph by the CISO Assistant
GRC extractor and the TRM portfolio-intelligence engine —
Control, Policy, Risk, ComplianceRequirement, ComplianceGate,
Regulation, ComplianceAssessment, Assessment, Incident,
RemediationProposal, Finding, SecurityException.
graph_compliance(action="posture")
// ->
{
"surface": "compliance",
"action": "posture",
"audit_ledger": { "ok": true, "entries": 4218, "first_broken_seq": null, ... },
"node_counts": {
"Control": 214, "Policy": 38, "Risk": 91, "ComplianceRequirement": 156,
"ComplianceGate": 12, "Regulation": 9, "ComplianceAssessment": 47,
"Assessment": 47, "Incident": 6, "RemediationProposal": 3,
"Finding": 22, "SecurityException": 1
},
"status_breakdown": {
"Control": {"implemented": 190, "planned": 24},
"Incident": {"open": 2, "resolved": 4}
}
}
A read failure on any one label degrades that label's count to 0 rather
than failing the whole rollup — a partial governance mirror still gives you
a usable posture view.
4. Redaction-compliant bulk export, as-of a date¶
Per-node redaction already existed at the engine (Method::ExplainBelief's
disclosure_level — Full / Skeleton / ExistenceOnly — masks, never
silently drops, an evidence node the caller's row-level security can't see).
What was missing was a bulk "export this subgraph, redacted, for an
auditor" primitive — that's graph_compliance(action="export"): it takes
either an explicit id list or a read-only Cypher query selecting an id
column, and calls the engine's own explain_belief(node_id, disclosure_level)
per id — the exact same per-node redaction primitive graph_epistemic's
why action uses, just batched.
// Select by Cypher (any read-only query returning an `id` column) and
// export at Skeleton disclosure, as of a fixed instant, capped at 500 rows:
graph_compliance(
action="export",
cypher="MATCH (c:ComplianceAssessment) WHERE c.framework = 'SOC2' RETURN c.id AS id",
disclosure_level="Skeleton",
as_of="2026-06-30T23:59:59Z",
limit=500
)
// ->
{
"surface": "compliance",
"action": "export",
"disclosure_level": "Skeleton",
"as_of": "2026-06-30T23:59:59Z",
"requested": 47,
"exported": 47,
"truncated": false,
"entries": [
{"node_id": "compliance:soc2:cc6.1", "belief": { "root": { "claim": "compliance:soc2:cc6.1", "rule": "DerivedSupport", "confidence": 0.94, "premises": [ ... ] } } },
...
]
}
disclosure_level controls what an out-of-scope evidence node in the
justification tree looks like to the recipient of this export:
Full(default) — every node in the proof tree, as the caller's own RLS view sees it.Skeleton— structure (claim ids, rule kind, confidence) preserved, but the content of nodes the recipient's policy can't see is masked.ExistenceOnly— the recipient learns a supporting/contradicting node exists at all, nothing about its content.
This is the mechanism you want when handing a subgraph to an external auditor who should see that a control claim is supported, without seeing the (possibly sensitive) evidence text itself.
5. Explainable decision logs — why do we believe this¶
For a single claim rather than a bulk export, graph_epistemic is the
purpose-named wrapper (SKILL: kg-epistemic-answer) over the same
justification machinery:
// The justification tree — what supports/contradicts this claim, and how
graph_epistemic(action="why", node_id="claim:mine:abc123")
// -> { "engine_method": "explain_belief",
// "result": { "root": {
// "claim": "claim:mine:abc123", "rule": "DerivedSupport",
// "confidence": 0.82,
// "premises": [
// {"claim": "evidence:doc:44", "rule": "Asserted", "confidence": 1.0, "premises": []}
// ]
// } } }
rule is one of Asserted / DerivedSupport / DerivedContradiction /
BayesianUpdate — the actual proof-tree vocabulary the engine uses
(eg-epistemic::model::JustRule), not a paraphrase.
The acceptance capstone goes one step further — "do we still believe
this, since when, on what evidence, and what would flip it" — via
epistemic_status (opt-in engine feature epistemic-tms, not in the
default full build; check for a clean {"error": ...} degrade before
assuming it ran):
graph_epistemic(action="status", node_id="claim:mine:abc123")
// -> { "engine_method": "epistemic_status",
// "result": {
// "claim": "claim:mine:abc123",
// "believed": true,
// "confidence": 0.82,
// "uncertainty": 0.05,
// "proof": { "root": { "rule": "DerivedSupport", ... } },
// "why_not": null,
// "evidence": ["evidence:doc:44"],
// "valid_time": [1751328000, null],
// "tx_time": [1751328042, null],
// "what_would_invalidate": { ... }
// } }
And for "what changed between two audit periods" rather than one claim's
history, what_changed (same opt-in feature) gives a whole-graph bitemporal
diff between two transaction-time bounds:
6. As-of reads for point-in-time audit snapshots¶
Every read that goes through graph_query/graph_ask accepts an as_of
ISO-8601 instant — a bitemporal cutoff (valid_from <= as_of < valid_to) —
so you can reconstruct exactly what the graph asserted on a given date,
independent of what's been written since:
graph_query(
cypher="MATCH (c:Control {framework:'SOC2'}) RETURN c.id AS id, c.status AS status",
as_of="2026-03-31T00:00:00Z"
)
Add include_epistemic=true to get each row back as a full EpistemicRow
(confidence, bitemporal window, evidence refs, policy labels) in the same
call, instead of a bare row plus a second explain_belief round-trip.
Putting it together: a SOC2 evidence-collection pass¶
- Confirm the ledger is trustworthy first.
graph_audit(action="verify")→ok: true. If this returnsavailable: false, your engine build/config doesn't carry a durable audit trail yet — treat that as a finding, not a tool bug. - Pull the posture rollup.
graph_compliance(action="posture")for the control-coverage/status snapshot to attach to the audit narrative. - Export the in-scope control evidence, redacted, as of period-end.
graph_compliance(action="export", cypher=..., disclosure_level="Skeleton", as_of=<period-end>). - Deep-dive any control an auditor flags.
graph_epistemic(action="why", node_id=...)for the justification tree;action="status"for the acceptance capstone if your engine build hasepistemic-tms. - Answer "what changed since last audit."
graph_epistemic(action="what_changed", tx_from=<last_audit_tx>, tx_to=<now>).
Honest limitations¶
epistemic_status/what_changedneed the opt-in engine featureepistemic-tms— not folded into the defaultfullbuild. A clean{"error": ...}means the connected engine doesn't have it, not that the claim is unbelieved.disclosure_levelredaction (Full/Skeleton/ExistenceOnly) needs the opt-inepistemic-redactionfeature for the masking behavior; without it,explain_beliefstill runs but does not mask.graph_audit(action="verify")needs thesecuritycargo feature (defaultfullbuild carries it) and a durable redb persist dir — an in-memory-only engine has no chain to verify.- These are read-only diagnostics over what was already written. They
explain and export existing belief/audit structure; they do not create
compliance controls or remediate findings themselves — see
graph_ops_causal'scontrol_evidenceaction and the CISO Assistant writeback sink for the write side of GRC.
See also¶
- Skill:
kg-epistemic-answer— the full four-layer epistemic-answer surface this guide's §5 draws from, includingexplain_provenance_by_ids(currency-upgrading any id list) andexplain_policy(visible vs policy-denied ids for a plan). - Configuration Reference & Flag Audit —
KG_BRAIN_ENFORCE(fail-closed node ACLs),KG_AUTH_REQUIRED(identity on the KG surface) — the enforcement layer this guide's redaction sits on top of. - Supported Deployment Configurations — rung (b)/(c) for how to get a durable, identity-gated deployment this guide's audit chain can actually attest to.