W-006 (operator decision: delete): - Remove dormant packs/en/el/grc/he/readback_rules.py (4 files, 0 live production callers). generate/realizer.py superseded the per-language readback path; per [[feedback-cleanup-as-you-find]], superseded code is removed rather than preserved. - Remove _gate_readback from packs/common/validator.py and drop it from the validate_pack_dir gate sequence. Add language to the report dict so the param remains non-vacuous. W-010 (operator decision: intentional token-level): - Amend ADR-0143 with "Vocabulary isolation is intentional" section. Token-level anti-unification derives its own structural vocabulary; importing VocabManifold adds no information at that level. Confirmed intentional by operator review 2026-05-25. W-014 (operator decision: evals-only): - Add deployment-scope note to core/cognition/provenance.py docstring: evals-only infrastructure, no live runtime caller. Confirmed evals-only by operator review 2026-05-25.
109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
"""Provenance — back-pointers from a cognitive turn to its grounding sources.
|
|
|
|
Every articulated claim must trace to at least one of:
|
|
|
|
- **pack** — the intent classifier matched a pack-defined intent rule, so the
|
|
proposition graph is grounded in axiomatic vocabulary.
|
|
- **vault** — exact CGA recall returned one or more stored versors that
|
|
influenced the field state during the turn.
|
|
- **teaching** — a reviewed teaching example (and its mutation proposal)
|
|
captured a correction that shaped this turn.
|
|
|
|
A turn with no provenance is a free-floating articulation and is a structural
|
|
failure.
|
|
|
|
The Provenance object is derived from a ``CognitiveTurnResult``; it does not
|
|
mutate the result and never invents sources.
|
|
|
|
**Deployment scope (W-014):** this module is *evals-only infrastructure*. It
|
|
is consumed by ``evals/provenance/runner.py`` for offline audit analysis and
|
|
by tests. There is no live runtime caller — provenance introspection is an
|
|
offline operation, not a per-turn runtime emission. Promotion to runtime use
|
|
(e.g., attaching a ``Provenance`` object to ``ChatResponse``) requires a new
|
|
ADR if that capability is ever desired. Confirmed by operator review
|
|
2026-05-25 (W-014 closure).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import TYPE_CHECKING
|
|
|
|
from generate.intent import IntentTag
|
|
|
|
if TYPE_CHECKING:
|
|
from core.cognition.result import CognitiveTurnResult
|
|
|
|
# The three valid source kinds. Tuple (not set) so iteration order is stable.
|
|
SOURCE_KINDS: tuple[str, ...] = ("pack", "vault", "teaching")
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProvenanceSource:
|
|
"""A single back-pointer to a grounding source.
|
|
|
|
- kind: one of "pack", "vault", "teaching"
|
|
- ref: stable string identifier (intent tag value, vault hit index,
|
|
teaching proposal id). Stable across replay.
|
|
"""
|
|
|
|
kind: str
|
|
ref: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Provenance:
|
|
"""The full set of source back-pointers for one cognitive turn."""
|
|
|
|
turn_trace_hash: str
|
|
sources: tuple[ProvenanceSource, ...]
|
|
|
|
@property
|
|
def is_empty(self) -> bool:
|
|
return not self.sources
|
|
|
|
def kinds(self) -> tuple[str, ...]:
|
|
"""Return the sorted, deduplicated set of source kinds present."""
|
|
return tuple(sorted({s.kind for s in self.sources}))
|
|
|
|
def has_kind(self, kind: str) -> bool:
|
|
return any(s.kind == kind for s in self.sources)
|
|
|
|
def refs(self, kind: str) -> tuple[str, ...]:
|
|
"""Return all refs for a given kind, in insertion order."""
|
|
return tuple(s.ref for s in self.sources if s.kind == kind)
|
|
|
|
|
|
def compute_provenance(result: "CognitiveTurnResult") -> Provenance:
|
|
"""Derive a Provenance record from a CognitiveTurnResult.
|
|
|
|
Pack source: intent classifier mapped the input to a known IntentTag
|
|
(anything other than UNKNOWN means a pack rule matched).
|
|
Vault source: any vault_hits indicate exact recall fired during the turn.
|
|
vault_hits is an int count; refs are synthetic indices
|
|
("vault_hit_0", "vault_hit_1", ...) — stable because the
|
|
pipeline is deterministic.
|
|
Teaching source: a reviewed teaching example produced a mutation proposal,
|
|
whose proposal_id is the stable back-pointer.
|
|
"""
|
|
sources: list[ProvenanceSource] = []
|
|
|
|
if result.intent is not None and result.intent.tag is not IntentTag.UNKNOWN:
|
|
sources.append(ProvenanceSource(kind="pack", ref=result.intent.tag.value))
|
|
|
|
if result.vault_hits > 0:
|
|
for i in range(int(result.vault_hits)):
|
|
sources.append(ProvenanceSource(kind="vault", ref=f"vault_hit_{i}"))
|
|
|
|
if result.pack_mutation_proposal is not None:
|
|
sources.append(
|
|
ProvenanceSource(
|
|
kind="teaching",
|
|
ref=result.pack_mutation_proposal.proposal_id,
|
|
)
|
|
)
|
|
|
|
return Provenance(
|
|
turn_trace_hash=result.trace_hash,
|
|
sources=tuple(sources),
|
|
)
|