- Add LexicalResolution dataclass + resolve_entry() in chat/pack_resolver.py
that returns language, root, morphology_id, gloss, semantic_domains from
he/grc/en packs (lru-cached, first-match, full depth support).
- Extend GraphNode (generate/graph_planner.py) with optional language/root/
morphology_id fields (defaults preserve all call sites). Update as_dict()
to include them conditionally. ground_graph() now propagates depth.
- Generalize enrichment in core/cognition/pipeline.py:
- Per-subject resolution map using depth packs.
- Enrich all matching nodes before ground (subject→node map).
- Pass depth alongside recalled_words to ground_graph().
- Consume depth on articulation side:
- realize_semantic() and render_semantic() now accept/use language+root
for etymological/Logos framing on Hebrew/Greek nodes (e.g. "אמת (Hebrew
root: א-מ-ן) is defined as..."). English unchanged.
- Enrich oov_geometric_context with node_depths for future geometric
anti-unification using roots.
- Extend recognition/connector.py to forward depth from EpistemicNode
paths into GraphNode.
- Add full Hebrew turn test under realizer_grounded_authority flag.
- Update related tests (semantic realizer, OOV context, surface resolution).
- Cleaned legacy type() hack immediately on discovery (hard-stop rule).
All targeted tests green (52+ in slices), broad relevant suite 581 passed.
Invariants preserved: versor only at owned boundaries, exact recall,
immutable updates, no new legacy parsers. 3 pillars upheld.
Work continues tomorrow from this checkpoint.
215 lines
9.1 KiB
Python
215 lines
9.1 KiB
Python
"""
|
|
Deterministic trace hashing for cognitive turns.
|
|
|
|
The hash captures every meaningful output of a pipeline run so that:
|
|
- identical inputs on identical field state → identical hash
|
|
- any output change → different hash
|
|
|
|
Only stable, semantically meaningful fields are included. Floating-point
|
|
values are rounded to 9 decimal places before hashing so that numeric
|
|
noise from different hardware does not break determinism within a run.
|
|
|
|
Phase B (Trace Equivalence Hazard fix per refined plan / engineer's assessment §2):
|
|
The PropositionGraph (the "think" structure) is now folded into the hash
|
|
via its canonical discrete topological serialization (to_json / as_dict).
|
|
This is a pure structural Merkle-DAG of nodes + directed edges with discrete
|
|
labels (subjects, predicates, objects, intents, relations). No raw f64
|
|
geometry, no versor arrays, no platform-dependent float associativity or FMA.
|
|
|
|
Continuous versor_condition remains a runtime guard (rounded only for the
|
|
hash payload) and is still asserted < 1e-6 exclusively at construction
|
|
boundaries (algebra/versor.py, VersorBinding, etc.). The graph inclusion
|
|
makes replay equivalence sensitive to the actual substrate reasoning path
|
|
while remaining 100% cross-platform (Python, Rust FFI, MLX on Apple Silicon,
|
|
x86) and byte-stable for equivalent turns.
|
|
|
|
New field is included *only* when non-empty, preserving byte-identical
|
|
payloads (and thus trace_hashes) for any pre-inclusion turns or turns
|
|
without a graph.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from core.cognition.result import CognitiveTurnResult
|
|
|
|
|
|
def _round_float(v: float, ndigits: int = 9) -> float:
|
|
return round(float(v), ndigits)
|
|
|
|
|
|
def compute_trace_hash(
|
|
input_text: str,
|
|
filtered_tokens: tuple[str, ...],
|
|
surface: str,
|
|
walk_surface: str,
|
|
articulation_surface: str,
|
|
dialogue_role: str,
|
|
versor_condition: float,
|
|
vault_hits: int,
|
|
intent_tag: str = "unknown",
|
|
teaching_review_hash: str = "",
|
|
teaching_proposal_id: str = "",
|
|
teaching_epistemic_status: str = "",
|
|
operator_invocation: str = "",
|
|
admissibility_trace_hash: str = "",
|
|
ratification_outcome: str = "",
|
|
region_was_unconstrained: bool = True,
|
|
refusal_reason: str = "",
|
|
proposition_graph: str = "",
|
|
) -> str:
|
|
"""Return a deterministic SHA-256 hex digest over the turn's key outputs.
|
|
|
|
Parameters match the subset of CognitiveTurnResult that is both
|
|
semantically meaningful and stable across hardware.
|
|
|
|
``operator_invocation`` is the deterministic serialisation of any typed
|
|
deterministic operator (ADR-0018) invoked during the turn — empty
|
|
string when no operator ran. Folding it explicitly makes operator
|
|
invocation a load-bearing part of replay equality, not just an
|
|
indirect consequence of surface-change.
|
|
|
|
``teaching_epistemic_status`` is the serialised EpistemicStatus of the
|
|
pack mutation proposal load-bearing in this turn — empty string when
|
|
no proposal was emitted. Folded per ADR-0021 §Consequences so replay
|
|
detects when a downstream surface was produced under a different
|
|
epistemic frame than at the time of recall.
|
|
|
|
``proposition_graph`` (Phase B) is the *discrete topological*
|
|
canonical serialization of the PropositionGraph (typically
|
|
graph.to_json() or equivalent as_dict JSON). This captures the
|
|
network structure (nodes + directed edges) and discrete labels
|
|
that drove the substrate articulation. It is the Merkle-DAG
|
|
representation of the "think" step.
|
|
|
|
It contains only strings, enums, and structural tuples — zero raw
|
|
floating-point CGA state. This guarantees identical hashes on
|
|
Apple Silicon (MLX), x86, Rust FFI paths, etc. The continuous
|
|
versor_condition (rounded) remains only as an ephemeral runtime
|
|
guard in the payload.
|
|
"""
|
|
payload = {
|
|
"input_text": input_text,
|
|
"filtered_tokens": list(filtered_tokens),
|
|
"surface": surface,
|
|
"walk_surface": walk_surface,
|
|
"articulation_surface": articulation_surface,
|
|
"dialogue_role": str(dialogue_role),
|
|
"versor_condition": _round_float(versor_condition),
|
|
"vault_hits": int(vault_hits),
|
|
"intent_tag": intent_tag,
|
|
"teaching_review_hash": teaching_review_hash,
|
|
"teaching_proposal_id": teaching_proposal_id,
|
|
"teaching_epistemic_status": teaching_epistemic_status,
|
|
"operator_invocation": operator_invocation,
|
|
}
|
|
# ADR-0023 additions are folded in only when they carry non-default
|
|
# values, so a turn unaffected by forward semantic control keeps the
|
|
# exact same payload bytes as before ADR-0023. Once a turn does
|
|
# carry admissibility evidence, those keys become load-bearing in
|
|
# replay equality.
|
|
if admissibility_trace_hash:
|
|
payload["admissibility_trace_hash"] = admissibility_trace_hash
|
|
if ratification_outcome:
|
|
payload["ratification_outcome"] = ratification_outcome
|
|
if not region_was_unconstrained:
|
|
payload["region_was_unconstrained"] = False
|
|
# ADR-0024 Phase 2 — fold refusal_reason only when non-empty so a
|
|
# turn that did not refuse keeps byte-identical payload bytes (and
|
|
# therefore byte-identical trace_hash) relative to pre-Phase-2.
|
|
# Once a turn does materialise a refusal, the reason becomes
|
|
# load-bearing in replay equality.
|
|
if refusal_reason:
|
|
payload["refusal_reason"] = refusal_reason
|
|
# Phase B — discrete PropositionGraph topology (Shadow Coherence Gate
|
|
# unification). Included only when present so pre-Phase-B turns and
|
|
# turns without a graph keep byte-identical payloads/hashes.
|
|
# The value is the full canonical JSON of nodes+edges (structural DAG).
|
|
# This makes the cognitive spine's reasoning load-bearing for replay
|
|
# while obeying Mechanical Sympathy (no FP) and Semantic Rigor (exact
|
|
# discrete structure, no approximation).
|
|
if proposition_graph:
|
|
payload["proposition_graph"] = proposition_graph
|
|
serialized = json.dumps(payload, sort_keys=True, ensure_ascii=False)
|
|
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def hash_admissibility_trace(trace: tuple) -> str:
|
|
"""SHA-256 over the canonical serialization of an admissibility trace.
|
|
|
|
Returns the empty string for an empty trace so callers can
|
|
short-circuit the ADR-0023 payload addition (preserving pre-ADR-0023
|
|
trace_hash bytes for turns that did not run admissibility).
|
|
"""
|
|
if not trace:
|
|
return ""
|
|
serialized = json.dumps(
|
|
[step.canonical() for step in trace],
|
|
sort_keys=True,
|
|
ensure_ascii=False,
|
|
)
|
|
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def trace_hash_from_result(result: "CognitiveTurnResult") -> str:
|
|
"""Convenience wrapper — compute the hash directly from a result object.
|
|
|
|
Phase B: extracts the discrete topological form of proposition_graph
|
|
(if present) using its to_json() canonical serialization. This ensures
|
|
that any caller using the helper gets the same payload as the direct
|
|
compute_trace_hash path in the pipeline.
|
|
"""
|
|
intent_tag = result.intent.tag.value if result.intent is not None else "unknown"
|
|
review_hash = (
|
|
result.reviewed_teaching_example.review_hash
|
|
if result.reviewed_teaching_example is not None
|
|
else ""
|
|
)
|
|
proposal_id = (
|
|
result.pack_mutation_proposal.proposal_id
|
|
if result.pack_mutation_proposal is not None
|
|
else ""
|
|
)
|
|
epistemic_status = (
|
|
result.pack_mutation_proposal.epistemic_status.value
|
|
if result.pack_mutation_proposal is not None
|
|
else ""
|
|
)
|
|
# Discrete graph topo for Phase B (quantized topological hashing).
|
|
# Uses the stable to_json() of the stored PropositionGraph (nodes +
|
|
# edges in their deterministic order, string labels only). Safe across
|
|
# all backends; no raw geometry.
|
|
graph_topo = ""
|
|
pg = getattr(result, "proposition_graph", None)
|
|
if pg is not None:
|
|
if hasattr(pg, "to_json"):
|
|
graph_topo = pg.to_json()
|
|
elif hasattr(pg, "as_dict"):
|
|
import json as _json
|
|
graph_topo = _json.dumps(
|
|
pg.as_dict(), sort_keys=True, ensure_ascii=False
|
|
)
|
|
return compute_trace_hash(
|
|
input_text=result.input_text,
|
|
filtered_tokens=result.filtered_tokens,
|
|
surface=result.surface,
|
|
walk_surface=result.walk_surface,
|
|
articulation_surface=result.articulation_surface,
|
|
dialogue_role=str(result.dialogue_role),
|
|
versor_condition=result.versor_condition,
|
|
vault_hits=result.vault_hits,
|
|
intent_tag=intent_tag,
|
|
teaching_review_hash=review_hash,
|
|
teaching_proposal_id=proposal_id,
|
|
teaching_epistemic_status=epistemic_status,
|
|
operator_invocation=result.operator_invocation,
|
|
admissibility_trace_hash=getattr(result, "admissibility_trace_hash", ""),
|
|
ratification_outcome=getattr(result, "ratification_outcome", ""),
|
|
region_was_unconstrained=getattr(result, "region_was_unconstrained", True),
|
|
refusal_reason=getattr(result, "refusal_reason", ""),
|
|
proposition_graph=graph_topo,
|
|
)
|