core/core/reasoning/evidence.py
Shay 568face63e feat(reasoning): field-wedge foundation — f64 embedding, lineage firewall, INV-27
Phase 0 of the field-reasoner wedge — net hardening regardless of the
experiment's outcome.

- algebra/cga.py: embed_point gains a dtype kwarg (f32 default byte-unchanged;
  cl41.geometric_product already preserves f64) + read_scalar_e1 projective
  dehomogenization read-back (weight-invariant; correct for dilations, where a
  raw distance-from-origin is wrong) + EMBED_EXACT_MAX pinned magnitude ceiling.
  f32 silently collapsed integer coordinates past ~1e4.
- core/reasoning/evidence.py: verify_tier2_agreement now keys independence on a
  reader_lineage pathway token (refuses SAME_READER_LINEAGE), replacing the
  label-only len(set(signatures))<2 check a single reader could satisfy by
  relabeling. reader_lineage is excluded from canonical serialization, so the
  entailment trace_hash is unchanged.
- tests INV-27: transitive reader-disjointness over TIER2_READER_PATHWAYS makes
  the lineage check load-bearing (distinct lineage => proven import-disjoint
  pathway). The two seeded readers share zero transitive first-party modules.

Green: smoke 87, algebra 82, cognition 121, 53 architectural invariants,
reasoning/deductive/r1 50; 16 new f64-exactness tests; zero regressions.
2026-06-04 19:22:16 -07:00

250 lines
9.4 KiB
Python

"""Domain-neutral reasoning evidence and Tier-2 agreement checks.
This module is deliberately pure data plus deterministic serialization. It is
the shared evidence shape for proof, reconstruction, contemplation, and sealed
learning arenas; it does not authorize serving behavior by itself.
"""
from __future__ import annotations
import hashlib
import json
from collections import Counter
from collections.abc import Mapping
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Any, Final
TIER2_VERIFIED: Final[str] = "tier2_verified"
INSUFFICIENT_EVIDENCE: Final[str] = "insufficient_evidence"
DUPLICATE_STRUCTURAL_SIGNATURE: Final[str] = "duplicate_structural_signature"
COMMITMENT_DISAGREEMENT: Final[str] = "commitment_disagreement"
MISSING_COMMITMENT: Final[str] = "missing_commitment"
SAME_READER_LINEAGE: Final[str] = "same_reader_lineage"
TIER2_REASONS: Final[frozenset[str]] = frozenset({
TIER2_VERIFIED,
INSUFFICIENT_EVIDENCE,
DUPLICATE_STRUCTURAL_SIGNATURE,
COMMITMENT_DISAGREEMENT,
MISSING_COMMITMENT,
SAME_READER_LINEAGE,
})
def _freeze_json_value(value: Any) -> Any:
"""Recursively freeze JSON-like payloads for immutable evidence storage."""
if isinstance(value, Mapping):
frozen = {str(k): _freeze_json_value(v) for k, v in value.items()}
return MappingProxyType(frozen)
if isinstance(value, list | tuple):
return tuple(_freeze_json_value(v) for v in value)
if value is None or isinstance(value, str | int | float | bool):
return value
raise TypeError(f"unsupported evidence payload value: {type(value).__name__}")
def _json_value(value: Any) -> Any:
"""Return a JSON-serializable copy of a frozen payload value."""
if isinstance(value, Mapping):
return {str(k): _json_value(v) for k, v in value.items()}
if isinstance(value, list | tuple):
return [_json_value(v) for v in value]
if value is None or isinstance(value, str | int | float | bool):
return value
raise TypeError(f"unsupported frozen evidence value: {type(value).__name__}")
def _canonical_json(payload: Mapping[str, Any]) -> str:
return json.dumps(
_json_value(payload),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
@dataclass(frozen=True, slots=True)
class OperatorEvidence:
"""Replayable evidence for one deterministic operator invocation."""
domain: str
operator: str
outcome: str
reason: str
input_keys: tuple[str, ...]
check_keys: tuple[str, ...]
commitment_key: str
structural_signature: str
# reader_lineage — the mechanistic identity of the decoding PATHWAY that
# produced this evidence (e.g. "proof_chain.entail"). The Tier-2 gate keys
# independence on this, not on the cosmetic structural_signature string, so a
# single reader cannot self-verify by relabeling. Deliberately EXCLUDED from
# ``as_dict``/``canonical_json`` (and therefore the evidence/trace hash): it is
# gate-routing provenance, not replayable evidence content, and including it
# would perturb the entailment trace_hash with no replay benefit. The
# static guarantee that distinct lineages are import-disjoint (cannot share a
# decoding pathway) is enforced by the architectural reader-disjointness
# invariant, not by this string alone.
reader_lineage: str = ""
payload: Mapping[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
for field_name in (
"domain",
"operator",
"outcome",
"reason",
"structural_signature",
"reader_lineage",
):
value = getattr(self, field_name)
if not isinstance(value, str) or not value.strip():
raise ValueError(f"OperatorEvidence.{field_name} is required")
object.__setattr__(self, field_name, value.strip())
if not isinstance(self.commitment_key, str):
raise ValueError("OperatorEvidence.commitment_key must be a string")
object.__setattr__(self, "input_keys", tuple(str(k) for k in self.input_keys))
object.__setattr__(self, "check_keys", tuple(str(k) for k in self.check_keys))
object.__setattr__(self, "payload", _freeze_json_value(dict(self.payload)))
def as_dict(self) -> dict[str, Any]:
return {
"domain": self.domain,
"operator": self.operator,
"outcome": self.outcome,
"reason": self.reason,
"input_keys": list(self.input_keys),
"check_keys": list(self.check_keys),
"commitment_key": self.commitment_key,
"structural_signature": self.structural_signature,
"payload": _json_value(self.payload),
}
def canonical_json(self) -> str:
return _canonical_json(self.as_dict())
@property
def evidence_hash(self) -> str:
return hashlib.sha256(self.canonical_json().encode("utf-8")).hexdigest()
@dataclass(frozen=True, slots=True)
class EvidenceBundle:
"""Ordered collection of operator evidence with stable serialization."""
evidences: tuple[OperatorEvidence, ...]
def __post_init__(self) -> None:
object.__setattr__(self, "evidences", tuple(self.evidences))
if not all(isinstance(ev, OperatorEvidence) for ev in self.evidences):
raise ValueError("EvidenceBundle.evidences must contain OperatorEvidence")
def as_dict(self) -> dict[str, Any]:
return {"evidences": [ev.as_dict() for ev in self.evidences]}
def canonical_json(self) -> str:
return _canonical_json(self.as_dict())
@property
def evidence_hash(self) -> str:
return hashlib.sha256(self.canonical_json().encode("utf-8")).hexdigest()
@dataclass(frozen=True, slots=True)
class Tier2Verdict:
"""Result of a domain-neutral convergent self-verification check."""
verified: bool
reason: str
commitment_key: str = ""
evidence_hash: str = ""
structural_signatures: tuple[str, ...] = ()
reader_lineages: tuple[str, ...] = ()
def __post_init__(self) -> None:
if self.reason not in TIER2_REASONS:
raise ValueError(f"unknown Tier2Verdict.reason: {self.reason!r}")
object.__setattr__(
self,
"structural_signatures",
tuple(str(s) for s in self.structural_signatures),
)
object.__setattr__(
self,
"reader_lineages",
tuple(str(s) for s in self.reader_lineages),
)
def as_dict(self) -> dict[str, Any]:
return {
"verified": self.verified,
"reason": self.reason,
"commitment_key": self.commitment_key,
"evidence_hash": self.evidence_hash,
"structural_signatures": list(self.structural_signatures),
"reader_lineages": list(self.reader_lineages),
}
def verify_tier2_agreement(
evidences: tuple[OperatorEvidence, ...] | list[OperatorEvidence],
) -> Tier2Verdict:
"""Require two **independently-read** structures converging on one commitment.
Independence is keyed on ``reader_lineage`` — the decoding PATHWAY — not on the
cosmetic ``structural_signature``. The same reader cannot self-verify by emitting
two differently-labeled signatures for one case: that is refused with
``SAME_READER_LINEAGE``. The static guarantee that distinct lineages are
import-disjoint (so distinct lineage ⟹ no shared decoding pathway) is enforced
by the architectural reader-disjointness invariant; this gate enforces the
runtime half (distinct producing pathways + distinct structure + one commitment).
"""
bundle = EvidenceBundle(tuple(evidences))
if len(bundle.evidences) < 2:
return Tier2Verdict(False, INSUFFICIENT_EVIDENCE)
if any(not ev.commitment_key for ev in bundle.evidences):
return Tier2Verdict(False, MISSING_COMMITMENT, evidence_hash=bundle.evidence_hash)
lineages = tuple(ev.reader_lineage for ev in bundle.evidences)
signatures = tuple(ev.structural_signature for ev in bundle.evidences)
# Mechanistic independence firewall: at least two DISTINCT decoding pathways.
if len(set(lineages)) < 2:
return Tier2Verdict(
False,
SAME_READER_LINEAGE,
evidence_hash=bundle.evidence_hash,
structural_signatures=tuple(sorted(set(signatures))),
reader_lineages=tuple(sorted(set(lineages))),
)
if len(set(signatures)) < 2:
return Tier2Verdict(
False,
DUPLICATE_STRUCTURAL_SIGNATURE,
evidence_hash=bundle.evidence_hash,
structural_signatures=tuple(sorted(set(signatures))),
reader_lineages=tuple(sorted(set(lineages))),
)
commitments = Counter(ev.commitment_key for ev in bundle.evidences)
shared = [key for key, count in commitments.items() if count >= 2]
if len(shared) != 1 or len(commitments) != 1:
return Tier2Verdict(
False,
COMMITMENT_DISAGREEMENT,
evidence_hash=bundle.evidence_hash,
structural_signatures=tuple(sorted(set(signatures))),
reader_lineages=tuple(sorted(set(lineages))),
)
return Tier2Verdict(
True,
TIER2_VERIFIED,
commitment_key=shared[0],
evidence_hash=bundle.evidence_hash,
structural_signatures=tuple(sorted(set(signatures))),
reader_lineages=tuple(sorted(set(lineages))),
)