How to trace and audit a fact#
This guide shows you how to start from a fact you already hold (its assertion id or AssertionRecord), read the provenance recorded on it, and inspect which value the runtime chose for that entity field cell, including when several active assertions compete.
Prerequisites#
- A
FactGraph(aliasSDKStore) with a registered schema and at least one written field assertion. - The assertion id of the fact, or an
AssertionRecordfor it. Field writes return the assertion id:fg.fields.set(...)andfg.fields.add(...)both return astrasrt_id. If you only have the entity and field, see step 1 for how to recover the id.
The examples use this schema and a single written cell:
from factgraph.sdk import Entity, FactGraph, Field, Identity
class User(Entity):
user_id: str = Identity()
name: str = Field()
tag: list[str] = Field()
fg = FactGraph.create(schema_classes=[User])
ref = fg.entities.ref(User, user_id="u-1")
name_asrt = fg.fields.set(User.name, ref, "Alice", meta={"source": "seed"})Steps#
Get the
AssertionRecordfor the fact. If you held onto the asrt_id, look it up directly:record = fg.assertions.by_id(name_asrt)fg.assertions.by_id(asrt_id)returns anAssertionRecord, orNoneif no claim has that id. If instead you only have the entity reference and theField, recover the active records first and take theirasrt_id:record = fg.assertions.where(field=User.name, e_ref=ref).first()fg.assertions.where(...)filters active records byfield=,e_ref=,value=,value_tag=, and_meta=. See reference/three-layer-api.md for the full filter surface.Read the record’s value and provenance. An
AssertionRecordis a frozen dataclass withasrt_id,value,value_tag,is_active,entity_type,field_name,pred_id,e_ref, andmeta:record.value # "Alice" record.is_active # True until a revocation supersedes it record.pred_id # predicate id for User.name record.e_ref # the entity reference this cell belongs toThe
metaattribute is anAssertionMetadataclass carrying the typed provenance pulled from the ledger’s meta rows:source,trace_id,ingested_at,approved_by,note,derived_rule_id,derived_rule_version,candidate_id,candidate_key,candidate_kind, andraw. Read the typed fields when present, and fall back tometa.raw(adictof every recorded meta key) for keys without a dedicated attribute:record.meta.source # "seed" record.meta.derived_rule_id # set when a rule derived the fact, else None record.meta.raw # {"source": "seed", ...} every recorded meta keyFor a fact written by a rule rather than asserted by hand,
meta.derived_rule_idandmeta.derived_rule_versioncarry the deriving rule’s identity when the writer recorded them. See reference/three-layer-api.md for theAssertionMetafield list and evaluate-a-rule-and-read-the-proof.md for the evidence side of derivation.Inspect the chosen value at the cell with
fg.audit.explain:explanation = fg.audit.explain(record)fg.audit.explain(target)accepts either theAssertionRecordor its asrt_id string. It returns adictdescribing the predicate/entity cell the assertion sits in:pred_id: the predicate id of the cell.e_ref: the entity reference.active_claims: a list of rows for the currently active claims at that cell, each row a dict withasrt_id,args, andmeta.chosen_asrt_id: the asrt_id the policy selected for the cell, orNone.asrt_id: the asrt_id of the target you passed.chosen:Truewhen your target is the chosen assertion,Falseotherwise.
To verify:
explanation["chosen"]isTrueandexplanation["chosen_asrt_id"] == record.asrt_idfor a cell with a single active write.If you need only the competing ids at a cell, or you want to start from the entity and field rather than an assertion, use
fg.audit.conflicts:conflicts = fg.audit.conflicts(record) # or, by entity reference and field name: conflicts = fg.audit.conflicts((ref, "name")) # or by entity reference and Field descriptor: conflicts = fg.audit.conflicts((ref, User.name))fg.audit.conflicts(target)accepts anAssertionRecord, an asrt_id string, or a two-tuple of(entity, field)whereentityis an entity reference string (or anEntitySnapshot) andfieldis aFielddescriptor or a field name string. It returns adictwith:pred_idande_ref: the cell.active_asrt_ids: the list of all active assertion ids competing at the cell.chosen_asrt_id: the selected asrt_id, orNone.
When
active_asrt_idsholds more than one id, the cell has competing active writes andchosen_asrt_idtells you which one wins. To verify your fact is among them:record.asrt_id in conflicts["active_asrt_ids"].To resolve a competing write, retract the losing assertion by id with
fg.assertions.retract(asrt_id); see write-and-read-facts.md.
See also: reference/audit.md for the full fg.audit and fg.meta surface, reference/three-layer-api.md for AssertionRecord and AssertionMeta, and About auditability for why the ledger records provenance this way.