mixle.substrate.trust module

verify_lineage() – check that an item’s provenance chain is intact (trust / provenance, N1).

Every substrate item can point at the items it genuinely derives from – derived_from is the typed provenance/ancestry edge list (parent documents, the model that produced an artifact, the trace it came from), kept separate from links, the generic, untyped KG-relation surface (kg_rag and multihop’s “related to” edges) that is NOT ancestry (MXR-080-0261; see SubstrateItem). A citation or a merge is only trustworthy if that ancestry chain actually resolves: a link to an item that no longer exists is a dangling provenance edge, and any claim resting on it is unverifiable.

verify_lineage() walks an item’s ancestry through derived_from alone and returns a three-state LineageState verdict, never a bare boolean (MXR-080-0260/0261):

  • INTACT – the full chain was traversed: every ancestor authorized, resolved, cycle-free, complete.

  • BROKEN – a confirmed defect within the verified region: a dangling edge, or a cycle (an ancestor that is, directly or transitively, its own ancestor).

  • UNVERIFIED – the chain could not be fully checked: max_depth was reached with further, unexplored edges beyond it (MXR-080-0260 – an unvisited tail is never silently certified), or an ancestor lies outside the caller’s authorized scope and could be neither confirmed present nor absent (MXR-080-0261).

Scope enforcement (MXR-080-0261) follows the same convention belief uses for the identical problem (see assimilate/_resolve/_launders): scope doubles as the authorization principal, and every ancestor at every hop is resolved through an AccessPolicy – never a raw, scope-blind lookup. An ancestor id that does not exist at all and one that exists but is private to a scope the caller cannot read are deliberately indistinguishable in the report (both land in unverified): telling them apart would let a caller use their own scoped audits as an oracle for “does something exist in a scope I can’t read,” the same side channel MXR-080-0237 closed for semantic search and MXR-080-0244 closed for belief evidence. A scoped audit can no longer be validated – silently promoted to INTACT – using content it has no authorized view of.

audit_substrate() runs it over the whole store – a knowledge-integrity sweep, the same “trust is re-derivable, not asserted” discipline the factuality receipts apply to answers, applied here to the knowledge itself.

class LineageState(*values)[source]

Bases: StrEnum

The three-state verdict of a lineage verification (MXR-080-0260/0261) – a closed vocabulary, the same discipline MergeStrategy applies to merge strategies. Never a bare boolean: a bare intact cannot distinguish “checked everything, it’s fine” from “stopped looking partway and don’t actually know” – exactly the overclaim these findings fix.

INTACT: the full chain was traversed – every ancestor authorized and resolved, no cycle, no

depth-cap truncation, no dangling edge. The only state that certifies anything.

BROKEN: a confirmed structural defect within the verified region – a dangling edge (an ancestor

id that resolves to nothing, where the caller had legitimate visibility to know that for a fact) or a cycle (an ancestor that is, directly or transitively, its own ancestor).

UNVERIFIED: the chain could not be fully checked – max_depth was reached with further,

unexplored edges beyond it, or an ancestor lies outside the caller’s authorized scope and could be neither confirmed present nor absent. Never silently promoted to INTACT.

class LineageReport(item_id, state, n_links, dangling=<factory>, cycles=<factory>, unverified=<factory>, truncated=<factory>, depth=0, visited=0)[source]

Bases: object

Whether an item’s provenance chain resolves end to end – and exactly why, if it doesn’t.

state is the headline three-state verdict (see LineageState); every other field names precisely which ancestor ids drove it, so a caller can always see WHY, not just THAT.

Parameters:
property intact: bool

True iff state is LineageState.INTACT.

Never itself the source of truth – state is (MXR-080-0260/0261): a bare boolean cannot distinguish a confirmed break from an open question, which is exactly what these findings fix. Kept only so a caller that wants a single coarse check still gets a CORRECT one: both BROKEN and UNVERIFIED now read False here, where the pre-fix code could read True for a truncated or cross-scope chain it had never actually verified.

Type:

Read-only convenience view

as_dict()[source]

Return a JSON-serializable lineage report.

Return type:

dict[str, Any]

verify_lineage(substrate, item_id, *, max_depth=20, scope=None, policy=None)[source]

Walk item_id’s ancestry via derived_from ONLY, returning a three-state verdict (cycle-safe).

Traverses exclusively derived_from (MXR-080-0261) – never the generic, untyped links KG edges (see SubstrateItem) – so a merely-related entity can no longer be certified as a derivation ancestor just by existing.

scope, when given, doubles as the authorization principal (the same convention belief uses for the identical problem): every ancestor, at every hop, is resolved through policy (a fresh, home-scope-plus-PUBLIC-only AccessPolicy when omitted). An ancestor outside that authorized view can neither ground an INTACT verdict nor be silently skipped as though absent-therefore-fine – it is named in LineageReport.unverified. An ancestor id that does not exist at all and one that exists but is private to a scope scope cannot read are DELIBERATELY indistinguishable here: both land in unverified, never dangling – telling them apart would let a caller use their own scoped audits as an existence oracle for another scope’s content, the same side channel MXR-080-0237 closed for semantic search and MXR-080-0244 closed for belief evidence. scope=None (the default) is the one deliberate exception, matching Substrate.all()/Substrate.search()’s own convention: an unrestricted caller has no boundary to hide behind, so a missing ancestor is unambiguously reportable as LineageState.BROKEN (dangling) – the legacy behavior.

max_depth bounds pathological chains, but reaching it is no longer silent (MXR-080-0260): a node at the cap that still has its OWN unexplored derived_from entries is named in LineageReport.truncated and the verdict is LineageState.UNVERIFIED, never INTACT – an unvisited tail is never certified. A node at the cap with no further edges (a genuine leaf) is not truncated.

A back-edge into an ancestor already on the CURRENT root-to-node path is a genuine cycle (an item cannot legitimately derive, even transitively, from itself) and is named in LineageReport.cycles, forcing BROKEN. Reaching an already-fully-resolved ancestor via a DIFFERENT branch (a shared ancestor / diamond) is ordinary DAG structure, not a cycle, and is not re-walked.

Precedence when a walk has more than one kind of finding: BROKEN (a confirmed defect) outranks UNVERIFIED (an open question) outranks INTACT – the legacy “one break anywhere fails the whole chain” discipline, extended to the two new states. A missing or unauthorized ROOT item is handled the same way: BROKEN when unrestricted (scope=None), UNVERIFIED when scoped.

Parameters:
  • substrate (Substrate)

  • item_id (str)

  • max_depth (int)

  • scope (str | None)

  • policy (AccessPolicy | None)

Return type:

LineageReport

audit_substrate(substrate, *, scope=None, policy=None)[source]

A knowledge-integrity sweep: how many items are intact/broken/unverified, every one named.

MXR-080-0261: scope/policy are threaded into every verify_lineage() call, so a scoped audit (scope not None) can only ever certify a chain using ancestors that SAME scope is authorized to read. Previously this filtered its own item list by scope but then handed each id to an unscoped verify_lineage, which walked every scope regardless – a scoped audit could be, and was, validated by (or leak the internal integrity of) items it had no business seeing at all.

Returns {n_items, n_intact, n_broken, n_unverified, broken: [...], unverified: [...]}. broken names every item with a confirmed structural defect (dangling edge and/or cycle) within its authorized visibility; unverified names every item whose chain could not be fully checked (depth-cap truncation and/or an ancestor outside scope’s authorized visibility) – kept separate from broken because “could not verify” is never the same claim as “confirmed defective.”

Parameters:
  • substrate (Substrate)

  • scope (str | None)

  • policy (AccessPolicy | None)

Return type:

dict[str, Any]