core/core/cognition/provenance.py
Shay d52bff56e7
chore(phase2): close W-006/W-010/W-014 — cleanup + doc amendments (#264)
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.
2026-05-25 06:05:17 -07:00

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),
)