Merge pull request 'feat: Master Convergence Stages 1–4 (land closed stack + skeptic fixes)' (#95) from feat/observed-he-morph-constraint-v0 into main

Reviewed-on: #95
---
Cl(4,1) geometric sovereignty convergence (Master Blueprint Stages Pre→1–4) is on feat/observed-he-morph-constraint-v0 (tip 8c4221d4), with Forgejo PRs #90–#94.

• Validate: uv run core test --suite smoke -q (or full: uv run core test --suite full -q)
• Stage pins: uv run pytest tests/test_geometric_convergence_checklist.py tests/test_stage3_epistemic_inductive.py -q
• HE four-arm ablation: PYTHONPATH=. python3 -c "from generate.observed_he_morph_v0.ablation import run_four_arm_ablation; print(run_four_arm_ablation())"
This commit is contained in:
Joshua Matthew-Catudio Shay 2026-07-20 22:40:57 +00:00
commit 18c578d960
28 changed files with 2214 additions and 214 deletions

View file

@ -25,156 +25,12 @@ _CORE_RS_MANIFEST = _CORE_RS_DIR / "Cargo.toml"
DESCRIPTION = "CORE versor engine command suite."
EPILOG = 'Examples:\n core chat\n core pulse "What is truth?"\n core pulse --no-glove --json "Compare knowledge and wisdom"\n core bench\n core bench --suite all\n core bench --suite all --json --report bench_all.json\n core bench --suite determinism --runs 50\n core bench --suite speedup --json\n core trace "word beginning truth"\n core trace --output-language grc --frame-pack grc --json "logos"\n core rust status\n core rust build\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core teaching audit\n core teaching audit --json\n core teaching gaps --top 10\n core teaching queue --threshold 3\n core teaching hitl-queue list\n core teaching hitl-queue list --state all --json\n core teaching hitl-queue show <proposal_id>\n core teaching propose <candidate-jsonl-path>\n core teaching propose-from-exemplars teaching/admissibility_exemplars/rate_with_currency_v1.jsonl\n core teaching propose-from-exemplars --all\n core teaching proposals --state pending\n core teaching review <proposal_id> --accept --review-date 2026-05-18\n core teaching supersede cause_light_reveals_truth --subject light --intent cause --connective grounds --object truth --review-date 2026-05-18\n core teaching supersessions\n core teaching supersessions --json\n core test --suite fast -q\n core test --suite pulse -q\n core test --suite proof -q\n core test --suite cognition -q\n core test -- tests/test_alignment_graph.py -q\n core demo audit-tour\n core demo register-tour\n core demo anchor-lens-tour\n core demo orthogonality-tour\n core demo pack-measurements\n core demo long-context-comparison\n core demo anti-regression\n core demo learning-loop\n core demo learning-arc\n core demo articulation\n core demo conversation\n core demo conversation --no-stream\n core demo all\n core demo adr-0024-chain\n core eval --list\n core eval cognition\n core eval cognition --json --save\n core eval cognition --split dev --version v1\n core eval cognition --split holdout\n core eval contemplation_quality\n core eval contemplation_quality --json --save\n core eval math-contemplation\n core eval math-contemplation --audit evals/gsm8k_math/train_sample/v1/audit_brief_11.json\n core eval math-contemplation --output teaching/math_proposals/proposals.jsonl\n core workbench api\n core workbench api --port 9000\n core workbench api --host 0.0.0.0 --allow-nonlocal-bind'
_TEST_SUITES: dict[str, tuple[str, ...]] = {
"fast": (
"tests/test_cli_test_suites.py",
"tests/test_runtime_config.py",
"tests/test_core_semantic_seed_pack.py",
"tests/test_intent_proposition_graph.py",
"tests/test_articulation_realizer_v2.py",
"tests/test_reviewed_teaching_loop.py",
"tests/test_cognitive_eval_harness.py",
),
"smoke": (
"tests/test_chat_runtime.py",
"tests/test_achat.py",
"tests/test_runtime_config.py",
"tests/test_cognitive_turn_pipeline.py",
"tests/test_architectural_invariants.py",
# ADR-0043 — identity falsifiability: ratified identity packs must
# produce distinct, directionally-correct articulations, with a
# pack-invariant grounding/refusal floor and zero fabrication. Lives
# only under ``full`` historically, so a divergence regression cleared
# the PR gate and surfaced only post-merge. Promoted into smoke so
# the falsifiability claim blocks-on-regression rather than
# detect-after-merge.
"tests/test_pack_measurements_phase2.py",
),
"runtime": (
"tests/test_chat_runtime.py",
"tests/test_achat.py",
"tests/test_runtime_config.py",
"tests/test_session_coherence.py",
),
"cognition": (
"tests/test_intent_proposition_graph.py",
"tests/test_cognitive_turn_pipeline.py",
"tests/test_articulation_realizer_v2.py",
"tests/test_semantic_realizer_integration.py",
"tests/test_cognitive_eval_harness.py",
"tests/test_deterministic_hash.py",
"tests/test_morphology_irregular.py",
"tests/test_realizer_quantifier_agreement.py",
"tests/test_benchmarks_profiler.py",
"tests/test_compose_relations.py",
"tests/test_replay_vs_llm_benchmark.py",
),
"teaching": (
"tests/test_reviewed_teaching_loop.py",
"tests/test_pipeline_teaching_integration.py",
"tests/test_epistemic_invariants.py",
"tests/test_adr_0172_w2_decomposer.py",
"tests/test_adr_0172_w5_inference_proposal.py",
"tests/test_math_frame_ratification.py",
"tests/test_math_composition_ratification.py",
"tests/test_teaching_coverage_cli.py",
),
"packs": (
"tests/test_core_semantic_seed_pack.py",
"tests/test_adr_0127_pack_ratification.py",
"tests/test_frame_registry_load.py",
"tests/test_composition_registry_load.py",
"tests/test_composition_consult_in_injector.py",
"tests/test_consumption_case_0050_hazard_pin.py",
"tests/test_consumption_empty_registry_no_op.py",
"tests/test_consumption_partition.py",
"tests/test_matcher_extension_currency_per_unit.py",
"tests/test_matcher_extension_case_0050_hazard_pin.py",
"tests/test_matcher_extension_end_to_end_admission.py",
"tests/test_me2_cross_sentence_subject.py",
"tests/test_me2_case_0019_admits.py",
"tests/test_me3_additive_composition.py",
"tests/test_me4_subtractive_composition.py",
"tests/test_me5_all_categories_integration.py",
"tests/test_rat1_end_to_end_admission.py",
"tests/test_wave_a_multiplicative_aggregation_injector.py",
),
"algebra": (
"tests/test_versor_closure.py",
"tests/test_holonomy.py",
"tests/test_holonomy_resonance.py",
"tests/test_energy.py",
"tests/test_motor.py",
"tests/test_null_cone.py",
"tests/test_vault_recall.py",
"tests/test_vault_recall_vectorised.py",
"tests/test_vault_recall_rust_parity.py",
"tests/test_cga_inner_rust_parity.py",
"tests/test_geometric_product_rust_parity.py",
"tests/test_versor_condition_rust_parity.py",
"tests/test_versor_apply_rust_parity.py",
),
"sensorium": (
"tests/test_sensorium_compiler_delta.py",
"tests/test_audio_compiler.py",
"tests/test_audio_crdt_merge.py",
"tests/test_audio_eval_gates.py",
"tests/test_audio_pack_manifest.py",
"tests/test_audio_sensorium_mount.py",
"tests/test_vision_compiler.py",
"tests/test_event_vision_compiler.py",
"tests/test_vision_crdt_merge.py",
"tests/test_vision_eval_gates.py",
"tests/test_vision_sensorium_mount.py",
"tests/test_sensorimotor_contract.py",
"tests/test_sensorimotor_pack_manifest.py",
"tests/test_observation_frame_contract.py",
"tests/test_observation_frame_harness.py",
"tests/test_environment_falsification.py",
"tests/test_environment_falsification_eval_cli.py",
"tests/test_witness_log_importer.py",
"tests/test_tabletop_lab_protocol.py",
"tests/test_sensorium_eval_cli.py",
"tests/test_efferent_gate.py",
),
"pulse": (
"tests/test_pulse_integration.py",
"tests/test_graph_diffusion.py",
),
"formation": ("tests/formation",),
"proof": ("tests/test_proof_properties.py",),
# ADR-0024 chain suites (Phases 2-6). Each phase has its own
# contract tests so investors / reviewers can run them
# independently; ``adr-0024`` runs the full chain end-to-end.
"refusal": ("tests/test_refusal_contract.py",),
"margin": ("tests/test_margin_admissibility.py",),
"rotor": ("tests/test_rotor_admissibility.py",),
"inner-loop": (
"tests/test_inner_loop_admissibility.py",
"tests/test_inner_loop_phase2.py",
"tests/test_inner_loop_phase3.py",
"tests/test_inner_loop_phase4.py",
),
"phase5": ("tests/test_phase5_corpus.py",),
"phase6": ("tests/test_phase6_demo.py",),
"adr-0024": (
"tests/test_refusal_contract.py",
"tests/test_margin_admissibility.py",
"tests/test_rotor_admissibility.py",
"tests/test_inner_loop_admissibility.py",
"tests/test_inner_loop_phase2.py",
"tests/test_inner_loop_phase3.py",
"tests/test_inner_loop_phase4.py",
"tests/test_phase5_corpus.py",
"tests/test_phase6_demo.py",
),
# ADR-0126 P6 — measurement harness for the GSM8K candidate-graph
# parser exit criterion. ``wrong == 0`` is a hard gate (Obligation
# #4: refuse rather than confabulate).
"math": ("tests/test_adr_0126_train_sample_runner.py",),
"deductive": ("tests/test_deductive_logic_entail.py",),
"full": ("tests/",),
}
# Canonical suite map lives in core.cli_test (runtime + --list-suites).
# Re-export here for argparse choices and tests that import core.cli.
# Do not maintain a second copy — Stage dual-pack / algebra pins drifted
# when cli.py lagged cli_test.TEST_SUITES (full-suite failure 2026-07-20).
from core.cli_test import TEST_SUITES as _TEST_SUITES # noqa: E402
def _run(*args: str, check: bool = False, cwd: Path | None = None) -> int:

View file

@ -44,6 +44,8 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
# the falsifiability claim blocks-on-regression rather than
# detect-after-merge.
"tests/test_pack_measurements_phase2.py",
# ADR-0253 dual-pack boundary — draft he/grc trees must not be serve imports.
"tests/test_pack_draft_serve_boundary.py",
),
"runtime": (
"tests/test_chat_runtime.py",
@ -75,6 +77,7 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
"tests/test_teaching_coverage_cli.py",
),
"packs": (
"tests/test_pack_draft_serve_boundary.py",
"tests/test_core_semantic_seed_pack.py",
"tests/test_adr_0127_pack_ratification.py",
"tests/test_frame_registry_load.py",
@ -95,6 +98,8 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
"tests/test_wave_a_multiplicative_aggregation_injector.py",
),
"algebra": (
"tests/test_stage2_physics_hardening.py",
"tests/test_geometric_convergence_checklist.py",
"tests/test_versor_closure.py",
"tests/test_holonomy.py",
"tests/test_holonomy_resonance.py",

View file

@ -0,0 +1,118 @@
"""Turn-level geometric coherence verdict (Master Blueprint Stage 3A).
Ownership model (deliberate dual taxonomy do NOT collapse names):
* ``teaching.epistemic.EpistemicStatus`` vault / pack durable standing
(SPECULATIVE | COHERENT | CONTESTED | FALSIFIED). Mutation only via
``vault/store.py`` (INV-29).
* ``core.epistemic_state.EpistemicState`` turn / surface taxonomy for
dialogue observability (perceived, verified, decoded, ). **No** member
named COHERENT vault COHERENT maps to DECODED via
``epistemic_state_for_vault_status``.
This module adds a third, geometry-native axis: whether the *field* closed
under versor + GoldTether residual checks on this turn. It never renames
EpistemicState and never stamps vault COHERENT by itself.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Any
import numpy as np
from algebra.backend import versor_condition
from core.physics.goldtether import coherence_residual
_CLOSURE = 1e-6
class GeometricCoherenceStatus(str, Enum):
"""Turn-level geometric standing — orthogonal to vault EpistemicStatus."""
GEOMETRICALLY_VERIFIED = "geometrically_verified"
UNVERIFIED = "unverified"
REFUSED = "refused"
@dataclass(frozen=True, slots=True)
class GeometricCoherenceVerdict:
"""Pipeline-visible geometric coherence for one turn.
``GEOMETRICALLY_VERIFIED`` requires:
* field present
* versor_condition(F) < 1e-6
* R_GoldTether 1e-6
Identity leakage flags are observational while identity_wave_gate is off
(ADR-0244 honest scope) and do not alone refuse this verdict unless
``boundary_violations`` is non-empty.
"""
status: GeometricCoherenceStatus
versor_condition: float
goldtether_residual: float
field_present: bool
identity_boundary_breach: bool = False
detail: str = ""
@property
def closed(self) -> bool:
return self.status is GeometricCoherenceStatus.GEOMETRICALLY_VERIFIED
def as_dict(self) -> dict[str, Any]:
return {
"status": self.status.value,
"closed": self.closed,
"versor_condition": float(self.versor_condition),
"goldtether_residual": float(self.goldtether_residual),
"field_present": bool(self.field_present),
"identity_boundary_breach": bool(self.identity_boundary_breach),
"detail": self.detail,
}
def evaluate_geometric_coherence(
F,
*,
identity_score=None,
epsilon: float = _CLOSURE,
) -> GeometricCoherenceVerdict:
"""Compute turn geometric coherence from the live field versor."""
if F is None:
return GeometricCoherenceVerdict(
status=GeometricCoherenceStatus.REFUSED,
versor_condition=float("inf"),
goldtether_residual=float("inf"),
field_present=False,
detail="missing_wave_field",
)
arr = np.asarray(F, dtype=np.float64)
vc = float(versor_condition(arr))
r_gt = float(coherence_residual(arr))
boundary = bool(getattr(identity_score, "boundary_violations", ()) or ())
if boundary:
return GeometricCoherenceVerdict(
status=GeometricCoherenceStatus.REFUSED,
versor_condition=vc,
goldtether_residual=r_gt,
field_present=True,
identity_boundary_breach=True,
detail="identity_boundary_breach",
)
if vc >= float(epsilon) or r_gt > float(epsilon):
return GeometricCoherenceVerdict(
status=GeometricCoherenceStatus.UNVERIFIED,
versor_condition=vc,
goldtether_residual=r_gt,
field_present=True,
detail=f"versor_condition={vc:.3e}; R_GoldTether={r_gt:.3e}",
)
return GeometricCoherenceVerdict(
status=GeometricCoherenceStatus.GEOMETRICALLY_VERIFIED,
versor_condition=vc,
goldtether_residual=r_gt,
field_present=True,
detail="versor_and_goldtether_closed",
)

View file

@ -0,0 +1,243 @@
"""Bounded multi-step inductive closure over teaching-store relations (Stage 3C).
Fixed-point expansion of same-relation chains with explicit budgets,
cycle handling, contradiction detection, geometric admissibility, and
replayable provenance.
Atom *identity* for entailment telemetry remains conformal
(``CognitiveTurnPipeline._proof_atom``). This module expands the *relation
graph* of surface triples from ``TeachingStore.triples()`` into a closed
set under transitive composition of equal relation labels not string
atom join as final identity authority.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Iterable, Sequence
_DEFAULT_BUDGET = 16
# geometric_admissible(head, relation, tail) -> bool
GeometricAdmissibleFn = Callable[[str, str, str], bool]
def _norm(token: str) -> str:
return token.strip().lower()
@dataclass(frozen=True, slots=True)
class DerivedRelation:
"""One promoted (or base) triple with provenance path."""
head: str
relation: str
tail: str
path: tuple[str, ...] # entity path head … tail
step: int # 0 = base fact from store; >0 = derived at fixed-point step
admissible: bool = True
contradiction: bool = False
def as_triple(self) -> tuple[str, str, str]:
return (self.head, self.relation, self.tail)
def as_dict(self) -> dict[str, Any]:
return {
"head": self.head,
"relation": self.relation,
"tail": self.tail,
"path": list(self.path),
"step": self.step,
"admissible": self.admissible,
"contradiction": self.contradiction,
}
@dataclass(frozen=True, slots=True)
class InductiveClosureResult:
"""Result of bounded fixed-point expansion."""
base: tuple[DerivedRelation, ...]
derived: tuple[DerivedRelation, ...]
contradictions: tuple[DerivedRelation, ...]
steps_taken: int
budget: int
fixed_point: bool
truncated: bool
def as_dict(self) -> dict[str, Any]:
return {
"base": [r.as_dict() for r in self.base],
"derived": [r.as_dict() for r in self.derived],
"contradictions": [r.as_dict() for r in self.contradictions],
"steps_taken": self.steps_taken,
"budget": self.budget,
"fixed_point": self.fixed_point,
"truncated": self.truncated,
"n_derived": len(self.derived),
"n_admissible_derived": sum(1 for d in self.derived if d.admissible),
}
def default_geometric_admissible(head: str, relation: str, tail: str) -> bool:
"""Strict default: a derived relation is admissible only if endpoints are
non-empty distinct tokens and the relation is non-empty. Callers that
have a vocab/field resolver should pass a stronger callback that checks
closed Cl(4,1) versors (see pipeline wiring).
"""
del relation
h, t = head.strip(), tail.strip()
return bool(h) and bool(t) and h != t
def expand_relation_closure(
triples: Sequence[tuple[str, str, str]],
*,
budget: int = _DEFAULT_BUDGET,
relations: Iterable[str] | None = None,
geometric_admissible: GeometricAdmissibleFn | None = None,
) -> InductiveClosureResult:
"""Compute same-relation transitive closure with budget and contradictions.
Rules:
* Base facts: each input triple (normalized).
* Step k+1: if (a,r,b) and (b,r,c) known and ac and (a,r,c) unknown,
derive (a,r,c) with path ac.
* Cycle: if path would revisit a node, skip (no infinite loop).
* Contradiction: two different tails for the same (head, relation)
among base facts mark contradiction=True.
* Geometric admissibility (Stage 3 exit gate): a candidate edge is
*promoted* into the expansion frontier (``work``) and into
``derived`` **only** when ``geometric_admissible(h,r,t)`` is True.
Non-admissible candidates are excluded they must not seed further
fixed-point steps (no ``admissible=False`` intermediates that still
expand). Base store facts remain visible in ``base`` with their
admissibility stamp, but only admissible base edges enter ``work``.
Default requires non-empty distinct endpoints; pipeline supplies
versor-grounded checks when session vocab is available.
* Termination: no new triples or budget exhausted.
"""
if budget < 1:
raise ValueError("budget must be >= 1")
geom = geometric_admissible or default_geometric_admissible
rel_filter = None if relations is None else {_norm(r) for r in relations}
base_list: list[DerivedRelation] = []
edge_map: dict[tuple[str, str], set[str]] = {}
known: set[tuple[str, str, str]] = set()
for h, r, t in triples:
hn, rn, tn = _norm(h), _norm(r), _norm(t)
if not hn or not rn or not tn:
continue
if rel_filter is not None and rn not in rel_filter:
continue
key3 = (hn, rn, tn)
if key3 in known:
continue
known.add(key3)
edge_map.setdefault((hn, rn), set()).add(tn)
# Base facts are store-grounded; admissible if geometric check passes.
base_list.append(
DerivedRelation(
head=hn,
relation=rn,
tail=tn,
path=(hn, tn),
step=0,
admissible=bool(geom(hn, rn, tn)),
)
)
contradictions: list[DerivedRelation] = []
for (h, r), tails in edge_map.items():
if len(tails) > 1:
for dr in base_list:
if dr.head == h and dr.relation == r:
contradictions.append(
DerivedRelation(
head=dr.head,
relation=dr.relation,
tail=dr.tail,
path=dr.path,
step=0,
admissible=False,
contradiction=True,
)
)
derived: list[DerivedRelation] = []
# Frontier is geometry-gated: non-admissible base never seeds expansion.
work: set[tuple[str, str, str]] = {
dr.as_triple() for dr in base_list if dr.admissible
}
steps_taken = 0
fixed_point = False
truncated = False
for step in range(1, budget + 1):
steps_taken = step
new_edges: list[DerivedRelation] = []
by_hr: dict[tuple[str, str], list[str]] = {}
for h, r, t in work:
by_hr.setdefault((h, r), []).append(t)
for (a, r), mids in by_hr.items():
for b in mids:
for c in by_hr.get((b, r), ()):
if a == c:
continue
key3 = (a, r, c)
if key3 in work:
continue
# Promote only when geometrically admissible — never
# park an inadmissible edge in work to seed hop k+1.
if not bool(geom(a, r, c)):
continue
path = (a, b, c)
new_edges.append(
DerivedRelation(
head=a,
relation=r,
tail=c,
path=path,
step=step,
admissible=True,
contradiction=False,
)
)
if not new_edges:
fixed_point = True
steps_taken = step - 1 if step > 0 else 0
break
for dr in new_edges:
key3 = dr.as_triple()
if key3 in work:
continue
work.add(key3)
edge_map.setdefault((dr.head, dr.relation), set()).add(dr.tail)
derived.append(dr)
else:
truncated = True
by_hr = {}
for h, r, t in work:
by_hr.setdefault((h, r), []).append(t)
for (a, r), mids in by_hr.items():
for b in mids:
for c in by_hr.get((b, r), ()):
if a != c and (a, r, c) not in work:
truncated = True
break
return InductiveClosureResult(
base=tuple(base_list),
derived=tuple(derived),
contradictions=tuple(contradictions),
steps_taken=steps_taken,
budget=budget,
fixed_point=fixed_point and not truncated,
truncated=truncated,
)

View file

@ -24,6 +24,8 @@ import numpy as np
from algebra.backend import versor_condition
from algebra.cl41 import geometric_product, reverse, scalar_part
from field.state import FieldState
from core.cognition.geometric_coherence import evaluate_geometric_coherence
from core.cognition.inductive_closure import expand_relation_closure
from core.cognition.leeway import build_leeway_record
from core.cognition.result import CognitiveTurnResult
from core.cognition.surface_resolution import resolve_surface
@ -439,6 +441,27 @@ class CognitiveTurnPipeline:
):
compose_surface = CognitiveTurnPipeline._render_compose_surface(compose_result)
# Stage 3C — bounded inductive closure over teaching-store relations.
# Provenance-preserving fixed-point; derived edges require geometric
# admissibility (closed Cl(4,1) versors when vocab can ground them).
def _geom_admissible(h: str, r: str, t: str) -> bool:
del r
hv = self._resolve_surface_versor(h)
tv = self._resolve_surface_versor(t)
if hv is None or tv is None:
# Ungrounded endpoints cannot be promoted as geometric facts.
return False
return (
float(versor_condition(hv)) < 1e-6
and float(versor_condition(tv)) < 1e-6
)
inductive_closure = expand_relation_closure(
triples,
budget=16,
geometric_admissible=_geom_admissible,
)
entailment_trace = self._maybe_entailment_trace(intent, triples)
# === SHADOW COHERENCE GATE WIRING ===
@ -645,10 +668,34 @@ class CognitiveTurnPipeline:
entailment_serialised = CognitiveTurnPipeline._serialize_entailment_trace(
entailment_trace
)
# Deterministic concatenation: walk, compose, then entailment. Empty
# strings are dropped so unaffected turns keep existing trace bytes.
# Stage 3C — inductive fixed-point provenance (before hash so replay
# includes multi-step derivation when teaching store has chains).
inductive_serialised = ""
if inductive_closure.derived or inductive_closure.contradictions:
inductive_serialised = json.dumps(
{
"inductive_closure": {
"n_derived": len(inductive_closure.derived),
"n_contradictions": len(inductive_closure.contradictions),
"steps_taken": inductive_closure.steps_taken,
"fixed_point": inductive_closure.fixed_point,
"truncated": inductive_closure.truncated,
"derived": [d.as_dict() for d in inductive_closure.derived[:8]],
}
},
sort_keys=True,
ensure_ascii=False,
)
# Deterministic concatenation: walk, compose, entailment, inductive.
# Empty strings are dropped so unaffected turns keep existing trace bytes.
operator_invocation = "|".join(
s for s in (walk_serialised, compose_serialised, entailment_serialised)
s
for s in (
walk_serialised,
compose_serialised,
entailment_serialised,
inductive_serialised,
)
if s
)
# ADR-0023 — admissibility trace + ratification provenance.
@ -724,6 +771,15 @@ class CognitiveTurnPipeline:
license_decision=getattr(accrual, "license", None),
)
# Stage 3A — turn-level geometric coherence (orthogonal to vault COHERENT).
geo_F = F_gate
if geo_F is None and field_state_after is not None:
geo_F = getattr(field_state_after, "F", None)
geometric_coherence = evaluate_geometric_coherence(
geo_F,
identity_score=response.identity_score,
)
return CognitiveTurnResult(
input_text=text,
input_tokens=raw_tokens,
@ -762,6 +818,7 @@ class CognitiveTurnPipeline:
dispatch_trace=getattr(response, "dispatch_trace", None),
dropped_compound_clauses=dropped_compound_clauses,
versor_condition=response.versor_condition,
geometric_coherence=geometric_coherence,
trace_hash=trace_hash,
leeway=leeway,
# Phase A — Shadow Coherence Gate observability.

View file

@ -10,6 +10,7 @@ from __future__ import annotations
from dataclasses import dataclass
from core.cognition.geometric_coherence import GeometricCoherenceVerdict
from core.cognition.leeway import LeewayRecord
from field.state import FieldState
from generate.articulation import ArticulationPlan
@ -150,6 +151,9 @@ class CognitiveTurnResult:
# --- invariant bookkeeping ---
versor_condition: float = 0.0 # must be < 1e-6
# Stage 3A — geometry-native turn coherence (orthogonal to vault EpistemicStatus).
# None only on pre-Stage-3 artifacts; live pipeline always populates.
geometric_coherence: GeometricCoherenceVerdict | None = None
trace_hash: str = "" # SHA-256 over deterministic key fields
# --- response-governance leeway evidence (B4; observational, not in trace_hash) ---

View file

@ -15,9 +15,16 @@ class PromotionDecision:
class VaultPromotionPolicy:
"""Promote only settled, coherent regions into deep vault storage."""
"""Promote only settled, *geometrically* coherent regions to COHERENT.
def __init__(self, residual_threshold: float = 0.05) -> None:
Stage 3A / Master Blueprint: COHERENT standing requires the unitary
residual condition (``coherence_residual 1e-6`` by default), not a
soft energy-band threshold alone. Tests may pass a looser
``residual_threshold`` explicitly for energy-class isolation fixtures;
production ``VaultPromotionPolicy()`` uses the geometric floor.
"""
def __init__(self, residual_threshold: float = 1e-6) -> None:
if residual_threshold < 0.0:
raise ValueError("residual_threshold must be non-negative")
self.residual_threshold = float(residual_threshold)
@ -27,6 +34,13 @@ class VaultPromotionPolicy:
return PromotionDecision(False, "missing_energy_profile", EnergyClass.E2)
if not energy.energy_class.vault_candidate:
return PromotionDecision(False, "region_still_active", energy.energy_class)
# Full geometric unitarity gate for COHERENT promotion (Stage 3A).
if energy.coherence_residual > self.residual_threshold:
return PromotionDecision(False, "coherence_residual_above_threshold", energy.energy_class)
return PromotionDecision(
False, "coherence_residual_above_threshold", energy.energy_class
)
# Residual must also be finite and non-negative (typed safety).
r = float(energy.coherence_residual)
if not (r == r) or r < 0.0: # NaN or negative
return PromotionDecision(False, "coherence_residual_invalid", energy.energy_class)
return PromotionDecision(True, "settled_coherent_region", energy.energy_class)

View file

@ -0,0 +1,40 @@
# ADR-0253: Master Blueprint ADR Collision Resolution & Dual-Pack Boundary
**Status**: Accepted (Stage 1 governance freeze)
**Date**: 2026-07-20
**Deciders**: CORE engineering (Master Convergence Stage 1)
**Related**: `docs/adr/MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md`, Master Architectural Specification 2026-07-20
## Context
The Master Blueprint §3.2 lists ADRs 02400253 with intent titles that **collide by number** with the repositorys already-Accepted ADR-0246 through ADR-0252 (and with other live 02400245 titles). Overwriting Accepted history is forbidden by repository governance and by the Stage 1 program rule.
Separately, language packs exist as both:
- **compiled runtime artifacts** under `packs/data/<pack_id>/`, and
- **source/draft trees** such as `packs/he`, `packs/grc`, `packs/en`, `packs/el`.
Serve paths must not treat draft source trees as importable authority.
## Decision
1. **ID history is immutable.** Accepted ADR numbers keep their live titles and scopes. Blueprint intent titles never renumber Accepted ADRs.
2. **Mapping is the reconciliation surface.** The file
`docs/adr/MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md`
is the authoritative Blueprint-ID → repository-home table (Covered / Partial / Gap / reserved new IDs 02540261).
3. **This ADR-0253 number is claimed for governance freeze**, not for the Blueprints “Holonomy Primacy Rust SIMD” title. That Blueprint intent is reserved as **ADR-0261** if implemented later.
4. **Dual-pack boundary**
- Runtime serve/load of language packs uses `packs.compiler``packs/data/<pack_id>/` only.
- `packs/he`, `packs/grc`, and peer source trees are draft/source material; they are not serve-import authority.
- Architecture tests pin that serve-entry modules do not import `packs.he` / `packs.grc` as packages for serving.
## Consequences
- Stage 3/4 Blueprint work must open **new** ADR numbers (0254+) or amend the correct existing owner ADR — never 02460252 renames.
- CI fails if draft HE/GRC package imports appear on the serve import graph.
- Documentation that cites Blueprint ADR numbers for convergence work must also cite this mapping.
## Validation
- `tests/test_pack_draft_serve_boundary.py`
- Mapping file present and linked from `docs/adr/README.md`

View file

@ -0,0 +1,75 @@
# Master Blueprint ADR Mapping (2026-07-20)
**Status**: Governing mapping for the Master Architectural Specification & System Convergence Blueprint (2026-07-20)
**Policy**: Do **not** overwrite, renumber, or falsify Accepted ADR-0246 through ADR-0252.
**Authority**: This mapping reconciles Blueprint §3.2 titles with the live `docs/adr/` registry.
## Collision rule
The Blueprint lists ADR-02400253 with intent titles that **collide by number** with already-Accepted repository ADRs (especially 02460252). Repository history wins for IDs. Blueprint *decisions* land as:
1. **Covered** — mapped to an existing Accepted ADR (or AGENTS.md / runtime_contracts) that already carries the intent; or
2. **Amended** — small amendment to the correct existing ADR when scope already owns the decision; or
3. **Newly allocated** — next free ADR number (starting ADR-0253+) for Blueprint intents with no home.
## Registry truth (live Accepted / Proposed)
| ID | Live title (repository) | Live status |
|----|-------------------------|-------------|
| 0240 | Analogical Transfer Validation Harness + Biography Holonomy Blade | Proposed |
| 0241 | Wave-Field Driven Hyperbolic Atlas and Resonant Algebraic Cognition | Accepted |
| 0242 | Deterministic Fibonacci Operators and Evidence-Gated Optimization | Accepted |
| 0243 | Wave-Field Cognitive Lifecycle | Accepted |
| 0244 | Wave-Field Identity Manifold and Inalienable Geometric Alignment | Accepted |
| 0245 | CGA Unification — Mechanical Sympathy, Boundary Rigor | Accepted |
| 0246 | Induced Identity Action and Path Integrity | Accepted |
| 0247 | Multi-Port Residual Protocol | Accepted |
| 0248 | Integrity-Coordinated Handoffs | Accepted |
| 0249 | Reader→Hamiltonian Compiler | Accepted |
| 0250 | Tier-2 Multi-Entity Arithmetic | Accepted |
| 0251 | Reader-Arc Recalibration | Partial / decision §§14 |
| 0252 | CORE Problem-Solving Paradigm | Accepted |
| 0253 | *(was vacant; reserved for governance — see below)* | — |
## Blueprint intent → home
| Blueprint ID / title (intent) | Resolution | Home in repository |
|-------------------------------|------------|--------------------|
| BP-0240 Cl(4,1) Conformal Wave-Field Sovereignty & Pipeline Integration | **Covered (multi-home)** | `AGENTS.md` invariants + ADR-0241 + ADR-0244 + cognitive pipeline contracts; do not steal ADR-0240 number from biography harness |
| BP-0241 Holographic Standing-Wave Storage & Resonant Recall | **Covered** | ADR-0241 (live title differs; wave/resonant substrate) + holographic vault research quarantine |
| BP-0242 Deterministic Fibonacci Operators & Bounded Local Search | **Covered** | ADR-0242 |
| BP-0243 Wave-Field Cognitive Lifecycle & Multimodal Ingress | **Covered** | ADR-0243 |
| BP-0244 Wave-Field Identity Manifold & Gram Subspace Projection | **Covered** | ADR-0244 |
| BP-0245 CGA Unification, PyO3 Fast-Paths, f64→f32 Boundary | **Covered** | ADR-0245 |
| BP-0246 Smith Chart Conformal Interconnect Grammar | **Gap / new allocation** | Not ADR-0246 (that ID is Induced Identity Action). Allocate **ADR-0254** if/when interconnect grammar is implemented |
| BP-0247 Multi-Step Inductive Closure & Conformal Atom Unification | **Gap / Stage 3** | Not ADR-0247 (Multi-Port Residual). Partial telemetry in `CognitiveTurnPipeline._proof_atom`. Allocate **ADR-0255** for full fixed-point closure |
| BP-0248 Fail-Closed Hebrew Root-Sense Ambiguity Policy | **Gap / Stage 34** | Not ADR-0248 (Integrity Handoffs). Allocate **ADR-0256** |
| BP-0249 Active Dual-Correction Backpressure Signaling | **Partial cover** | Backpressure types live under `core/cognition/backpressure.py`; not ADR-0249 (Reader compiler). Map intent to existing backpressure module; new ADR **0257** only if policy must be re-decided |
| BP-0250 Autonomous Geometric Promotion SPECULATIVE→COHERENT | **Covered (vault)** | `vault/store.py` promotion + `teaching.epistemic.EpistemicStatus`; not ADR-0250 (Tier-2 arithmetic). Document in vault/teaching ADRs; optional **ADR-0258** if geometric promotion conditions need a dedicated decision |
| BP-0251 Delta-CRDT Vault Serialization & 256-bit Digest Integrity | **Partial cover** | Digests: `multivector_content_digest` / vault metadata; CRDT claims must not overwrite ADR-0251. Allocate **ADR-0259** only if Delta-CRDT is newly decided |
| BP-0252 Low-Discrepancy Mode Centroid Sunflower Allocator | **Gap** | Not ADR-0252 (Problem-Solving Paradigm). Allocate **ADR-0260** if implemented |
| BP-0253 Holonomy Primacy Enforcement in Rust PyO3 SIMD Kernels | **Gap** | No live ADR-0253 holonomy doc. Allocate **ADR-0261** for holonomy/Rust primacy if needed |
## Governance ADRs allocated by Stage 1
| New ID | Title | Purpose |
|--------|-------|---------|
| **ADR-0253** | Master Blueprint ADR Collision Resolution & Dual-Pack Boundary | This freeze: mapping policy + dual-pack serve isolation (does **not** claim Blueprint holonomy content) |
Numbers **02540261** are **reserved** for Blueprint intents listed as Gap above; they are not materialised until the owning stage implements them.
## Dual-pack boundary (summary)
| Tree | Role | Serve authority |
|------|------|-----------------|
| `packs/data/<pack_id>/` | **Compiled runtime** language packs (manifest + lexicon + checksums) | **Yes** — via `packs.compiler.load_pack` |
| `packs/he`, `packs/grc`, `packs/en`, `packs/el`, … | **Source / draft** language material (morphology, lemmas, probes) | **No** as Python import for serve; compile into `packs/data` first |
| `packs/safety`, `packs/identity`, `packs/ethics`, … | Governance/style modality packs | Serve via dedicated loaders |
Enforcement: architecture test `tests/test_pack_draft_serve_boundary.py`.
## Non-goals
- Renaming or rewriting Accepted ADR-02460252 bodies to match Blueprint titles
- Claiming Stage 3/4 decisions complete by documentation alone
- Force-fitting Blueprint numbers onto Accepted history

View file

@ -8,3 +8,14 @@ references.
Runtime execution contracts live in `docs/specs/runtime_contracts.md`; ADRs
may cite that spec when a decision adds or changes a runtime invariant.
## Master Blueprint ADR mapping (2026-07-20)
The Master Architectural Specification lists ADR-02400253 with titles that
**collide by number** with Accepted repository ADRs (especially 02460252).
- **Do not renumber or overwrite Accepted ADRs.**
- Authoritative reconciliation:
[`MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md`](./MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md)
- Governance freeze decision:
[`ADR-0253-master-blueprint-adr-collision-and-dual-pack-boundary.md`](./ADR-0253-master-blueprint-adr-collision-and-dual-pack-boundary.md)

View file

@ -0,0 +1,26 @@
# Epistemic taxonomy ownership (Master Blueprint Stage 3A)
**Status**: Binding ownership note (not an ADR renumber)
**Date**: 2026-07-20
**Related**: `core/epistemic_state.py`, `teaching/epistemic.py`, `vault/store.py`, `core/cognition/geometric_coherence.py`
## Decision
CORE keeps **three orthogonal axes**. They must not be collapsed into one enum.
| Axis | Type | Owner module | Purpose |
|------|------|--------------|---------|
| Vault / pack standing | `EpistemicStatus` | `teaching/epistemic.py` | Durable SPECULATIVE / COHERENT / CONTESTED / FALSIFIED |
| Turn / dialogue taxonomy | `EpistemicState` | `core/epistemic_state.py` | Observability (perceived, verified, decoded, …) — **no COHERENT member** |
| Field geometric closure | `GeometricCoherenceVerdict` | `core/cognition/geometric_coherence.py` | Turn-level versor + GoldTether closed vs unverified |
## Mapping
- Vault `EpistemicStatus.COHERENT` → surface `EpistemicState.DECODED` via `epistemic_state_for_vault_status` (existing).
- Turn `GeometricCoherenceVerdict.GEOMETRICALLY_VERIFIED` does **not** auto-promote vault rows.
- Vault COHERENT promotion remains `VaultStore.store` / `apply_certified_promotion` / `promote_eligible_entries` only (INV-29).
## Forbidden
- Adding `EpistemicState.COHERENT` as a duplicate label without this ownership split.
- Using a single scalar score or bookkeeping flag as “coherent” without geometric checks on the field axis.

View file

@ -0,0 +1,29 @@
"""Observed-Hebrew morph → canonical constraint vertical slice (Stage 4).
``feat/observed-he-morph-constraint-v0`` compiled pack data only, no
English-to-Hebrew pseudo-morphology.
"""
from generate.observed_he_morph_v0.ablation import run_four_arm_ablation
from generate.observed_he_morph_v0.consumer import (
ConstraintDecision,
apply_he_morph_constraint,
)
from generate.observed_he_morph_v0.records import (
AuthoredMappingRule,
CanonicalConstraint,
ObservedHebrewSurface,
load_observed_morphology,
)
from generate.observed_he_morph_v0.rules import PLURAL_ABSTAIN_RULE_V0
__all__ = [
"AuthoredMappingRule",
"CanonicalConstraint",
"ConstraintDecision",
"ObservedHebrewSurface",
"PLURAL_ABSTAIN_RULE_V0",
"apply_he_morph_constraint",
"load_observed_morphology",
"run_four_arm_ablation",
]

View file

@ -0,0 +1,165 @@
"""Sealed four-arm ablation harness for observed-HE morph constraint v0."""
from __future__ import annotations
import hashlib
import json
from dataclasses import dataclass
from typing import Any
from generate.observed_he_morph_v0.consumer import (
ConstraintDecision,
DecisionKind,
apply_he_morph_constraint,
)
from generate.observed_he_morph_v0.records import load_observed_morphology
@dataclass(frozen=True, slots=True)
class ArmResult:
arm: str
decision: ConstraintDecision
digest: str
def as_dict(self) -> dict[str, Any]:
return {
"arm": self.arm,
"decision": self.decision.as_dict(),
"digest": self.digest,
}
@dataclass(frozen=True, slots=True)
class AblationReport:
arms: tuple[ArmResult, ...]
metadata_bit_identical_to_canonical: bool
executable_changed_decision: bool
wrong_count: int
refusal_correct: bool
provenance_complete: bool
def as_dict(self) -> dict[str, Any]:
return {
"arms": [a.as_dict() for a in self.arms],
"metadata_bit_identical_to_canonical": self.metadata_bit_identical_to_canonical,
"executable_changed_decision": self.executable_changed_decision,
"wrong_count": self.wrong_count,
"refusal_correct": self.refusal_correct,
"provenance_complete": self.provenance_complete,
}
def _digest(decision: ConstraintDecision) -> str:
payload = json.dumps(decision.as_dict(), sort_keys=True, ensure_ascii=False)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def run_four_arm_ablation(
*,
lemma_key: str = "דבר",
he_surface_plural: str | None = None,
proposal_singular_claim: str = "דבר must be singular only — exclusive singular identity",
proposal_neutral: str = "דבר is a logos utterance",
) -> AblationReport:
"""Run the sealed four-arm harness over compiled HE pack data."""
catalog = load_observed_morphology("he_logos_micro_v1")
# Prefer a known plural row from the pack.
plural = next((s for s in catalog if s.number == "plural"), None)
if plural is None:
raise RuntimeError("compiled pack has no plural morphology row")
surface = he_surface_plural or plural.surface
lemma = plural.lemma
arms: list[ArmResult] = []
# 1. canonical-only baseline
d_can = apply_he_morph_constraint(
proposal_text=proposal_singular_claim,
lemma_key=lemma,
observed_catalog=catalog,
mode="canonical",
he_surface=surface,
)
arms.append(ArmResult("canonical", d_can, _digest(d_can)))
# 2. metadata-only (must be decision-identical to canonical PASS)
d_meta = apply_he_morph_constraint(
proposal_text=proposal_singular_claim,
lemma_key=lemma,
observed_catalog=catalog,
mode="metadata",
he_surface=surface,
)
arms.append(ArmResult("metadata", d_meta, _digest(d_meta)))
# 3. executable mapping rule
d_exec = apply_he_morph_constraint(
proposal_text=proposal_singular_claim,
lemma_key=lemma,
observed_catalog=catalog,
mode="executable",
he_surface=surface,
)
arms.append(ArmResult("executable", d_exec, _digest(d_exec)))
# 4. adversarial: OOV + invalid + ambiguous
d_oov = apply_he_morph_constraint(
proposal_text=proposal_singular_claim,
lemma_key=lemma,
observed_catalog=catalog,
mode="adversarial",
he_surface="לא_קיים_oov_zzz",
)
d_missing = apply_he_morph_constraint(
proposal_text=proposal_singular_claim,
lemma_key=lemma,
observed_catalog=catalog,
mode="adversarial",
he_surface=None,
)
# Bundle adversarial as one arm: all must fail closed
adv_ok = (
d_oov.kind is DecisionKind.FAIL_CLOSED
and d_missing.kind is DecisionKind.FAIL_CLOSED
)
arms.append(
ArmResult(
"adversarial",
d_oov if d_oov.kind is DecisionKind.FAIL_CLOSED else d_missing,
_digest(d_oov),
)
)
# Metrics — Stage 4 requires metadata bit-identical to canonical baseline
# (full SHA-256 of decision payload, not kind-only equality).
meta_identical = _digest(d_meta) == _digest(d_can) and d_meta.kind is DecisionKind.PASS
# Executable must change decision to ABSTAIN vs baseline PASS.
exec_changed = (
d_can.kind is DecisionKind.PASS and d_exec.kind is DecisionKind.ABSTAIN
)
wrong = 0
if not meta_identical:
wrong += 1
if not exec_changed:
wrong += 1
if not adv_ok:
wrong += 1
# Provenance: executable arm carries constraint with source_span
provenance_ok = False
if d_exec.constraints:
payload = d_exec.constraints[0].payload
provenance_ok = (
"source_span" in payload
and "morphology_id" in payload
and "source_pack_id" in payload
)
return AblationReport(
arms=tuple(arms),
metadata_bit_identical_to_canonical=meta_identical,
executable_changed_decision=exec_changed,
wrong_count=wrong,
refusal_correct=adv_ok,
provenance_complete=provenance_ok,
)

View file

@ -0,0 +1,154 @@
"""Teaching-store contradiction / abstention consumer for HE morph constraints."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Any, Sequence
from generate.observed_he_morph_v0.records import (
AuthoredMappingRule,
CanonicalConstraint,
ObservedHebrewSurface,
lookup_surface,
)
from generate.observed_he_morph_v0.rules import PLURAL_ABSTAIN_RULE_V0
from recognition.depth_canonical import observe_root_ambiguity
class DecisionKind(str, Enum):
PASS = "pass" # no effect
ABSTAIN = "abstain" # force contested / refuse
FAIL_CLOSED = "fail_closed" # ambiguous / OOV / invalid
@dataclass(frozen=True, slots=True)
class ConstraintDecision:
kind: DecisionKind
reason: str
constraints: tuple[CanonicalConstraint, ...] = ()
surfaces: tuple[ObservedHebrewSurface, ...] = ()
rule_id: str | None = None
def as_dict(self) -> dict[str, Any]:
return {
"kind": self.kind.value,
"reason": self.reason,
"constraints": [c.as_dict() for c in self.constraints],
"surfaces": [s.as_dict() for s in self.surfaces],
"rule_id": self.rule_id,
}
def apply_he_morph_constraint(
*,
proposal_text: str,
lemma_key: str,
observed_catalog: Sequence[ObservedHebrewSurface],
rule: AuthoredMappingRule = PLURAL_ABSTAIN_RULE_V0,
mode: str = "executable",
he_surface: str | None = None,
) -> ConstraintDecision:
"""Live consumer at teaching-chain abstention seam.
Modes:
* ``canonical`` ignore HE morph entirely (baseline)
* ``metadata`` attach morph records but never change decision
* ``executable`` rule may force ABSTAIN
* ``adversarial`` invalid/ambiguous inputs must FAIL_CLOSED
``lemma_key`` is the language-independent subject/lemma under review.
``he_surface`` is the observed HE surface form when present.
"""
mode = mode.strip().lower()
# Canonical baseline decision (no morph authority). Metadata-only must
# return this *exact* decision object shape so digests are bit-identical
# to baseline (Stage 4 sealed harness). Morph provenance is tracked by
# the ablation harness separately, never folded into the decision payload
# on the metadata arm.
baseline = ConstraintDecision(kind=DecisionKind.PASS, reason="canonical_baseline")
if mode == "canonical":
return baseline
if not he_surface:
if mode == "adversarial":
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="missing_he_surface",
)
# No HE surface → identical to baseline (including metadata arm).
return baseline
hits = lookup_surface(observed_catalog, he_surface)
if hits is None:
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="oov_he_surface",
)
if len(hits) > 1:
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="ambiguous_surface_matches",
surfaces=hits,
)
# Multi-root ambiguity across catalog for same lemma → fail closed
lemma_rows = [s for s in observed_catalog if s.lemma == hits[0].lemma]
roots = sorted({s.root for s in lemma_rows if s.root})
if len(roots) > 1:
depth = {
s.morphology_id: {"language": "he", "root": s.root}
for s in lemma_rows
if s.root
}
amb = observe_root_ambiguity(depth)
if amb is not None:
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="ambiguous_hebrew_roots",
surfaces=tuple(lemma_rows),
)
surface = hits[0]
if not surface.root or not surface.morphology_id:
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="invalid_morphology",
surfaces=(surface,),
)
if mode == "metadata":
# Morph observed but must not change the consumer decision vs baseline.
return baseline
if mode == "adversarial" and "INVALID" in he_surface:
return ConstraintDecision(
kind=DecisionKind.FAIL_CLOSED,
reason="adversarial_invalid_surface",
surfaces=(surface,),
)
# Executable rule arm
if not rule.matches(surface):
return baseline
constraint = rule.to_constraint(surface)
# Abstain when proposal asserts exclusive singular / singular-only claim
# about a lemma that is observed plural in HE morph.
text_l = proposal_text.lower()
singular_claim = any(
tok in text_l
for tok in ("singular only", "not plural", "must be singular", "exclusive singular")
)
lemma_mentioned = lemma_key.lower() in text_l or surface.lemma in proposal_text
if singular_claim and lemma_mentioned:
return ConstraintDecision(
kind=DecisionKind.ABSTAIN,
reason="plural_morph_blocks_singular_exclusivity",
constraints=(constraint,),
surfaces=(surface,),
rule_id=rule.rule_id,
)
# Rule matched but no singular-exclusivity conflict → same decision as baseline.
return baseline

View file

@ -0,0 +1,127 @@
"""Observed Hebrew morphology records from compiled runtime pack data."""
from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Sequence
_REPO_ROOT = Path(__file__).resolve().parents[2]
_DEFAULT_PACK = "he_logos_micro_v0" # may not exist
_DEFAULT_PACK_ID = "he_logos_micro_v1"
@dataclass(frozen=True, slots=True)
class ObservedHebrewSurface:
"""Observed surface form from compiled pack with source-span provenance."""
surface: str
lemma: str
language: str
morphology_id: str
root: str
number: str # singular | plural | unknown
source_pack_id: str
source_span: tuple[int, int] # byte offsets in morphology.jsonl line stream
raw_record: dict[str, Any]
def as_dict(self) -> dict[str, Any]:
return {
"surface": self.surface,
"lemma": self.lemma,
"language": self.language,
"morphology_id": self.morphology_id,
"root": self.root,
"number": self.number,
"source_pack_id": self.source_pack_id,
"source_span": list(self.source_span),
}
@dataclass(frozen=True, slots=True)
class CanonicalConstraint:
"""Language-independent constraint shared across consumers."""
constraint_id: str
kind: str # e.g. plurality_marked
payload: dict[str, Any]
def as_dict(self) -> dict[str, Any]:
return {
"constraint_id": self.constraint_id,
"kind": self.kind,
"payload": dict(self.payload),
}
@dataclass(frozen=True, slots=True)
class AuthoredMappingRule:
"""Authored morph → constraint mapping with preconditions + counterexamples."""
rule_id: str
preconditions: tuple[str, ...]
counterexamples: tuple[str, ...]
constraint_kind: str
def matches(self, surface: ObservedHebrewSurface) -> bool:
raise NotImplementedError
def to_constraint(self, surface: ObservedHebrewSurface) -> CanonicalConstraint:
raise NotImplementedError
def load_observed_morphology(
pack_id: str = _DEFAULT_PACK_ID,
*,
repo_root: Path | None = None,
) -> tuple[ObservedHebrewSurface, ...]:
"""Load morphology from compiled ``packs/data/<pack_id>/morphology.jsonl``.
Fail closed if the pack is missing or contains no HE rows.
"""
root = repo_root or _REPO_ROOT
path = root / "packs" / "data" / pack_id / "morphology.jsonl"
if not path.is_file():
raise FileNotFoundError(f"compiled HE morphology missing: {path}")
out: list[ObservedHebrewSurface] = []
offset = 0
for line in path.read_text(encoding="utf-8").splitlines():
raw_line = line
start = offset
end = offset + len(raw_line.encode("utf-8"))
offset = end + 1 # newline
if not line.strip():
continue
rec = json.loads(line)
if rec.get("language") not in (None, "he"):
# Only HE surfaces for this vertical slice.
if rec.get("language") not in ("he",):
continue
infl = rec.get("inflection") or {}
number = str(infl.get("number") or "unknown")
out.append(
ObservedHebrewSurface(
surface=str(rec.get("surface") or ""),
lemma=str(rec.get("lemma") or ""),
language="he",
morphology_id=str(rec.get("morphology_id") or ""),
root=str(rec.get("root") or ""),
number=number,
source_pack_id=pack_id,
source_span=(start, end),
raw_record=rec,
)
)
if not out:
raise ValueError(f"no HE morphology rows in {path}")
return tuple(out)
def lookup_surface(
surfaces: Sequence[ObservedHebrewSurface],
surface: str,
) -> tuple[ObservedHebrewSurface, ...] | None:
"""Exact surface match; multi-hit returns all candidates (ambiguity)."""
hits = tuple(s for s in surfaces if s.surface == surface)
return hits if hits else None

View file

@ -0,0 +1,59 @@
"""Authored mapping rules for observed-HE morph constraints v0."""
from __future__ import annotations
from generate.observed_he_morph_v0.records import (
AuthoredMappingRule,
CanonicalConstraint,
ObservedHebrewSurface,
)
class PluralAbstainRuleV0(AuthoredMappingRule):
"""Map observed plural HE morphology → plurality_marked constraint.
Precondition: inflection.number == plural and root non-empty.
Counterexamples: singular surfaces; OOV; multi-root ambiguous packs.
Consumer: teaching-store contradiction/abstention force abstain when a
proposal asserts exclusive singular identity for the same lemma.
"""
def __init__(self) -> None:
super().__init__(
rule_id="he_morph_v0.plural_abstain",
preconditions=(
"language==he",
"inflection.number==plural",
"root non-empty",
),
counterexamples=(
"singular dabar surface must not fire",
"OOV surface with no pack row must fail closed",
"ambiguous multi-root without rule must not auto-commit",
),
constraint_kind="plurality_marked",
)
def matches(self, surface: ObservedHebrewSurface) -> bool:
if surface.language != "he":
return False
if not surface.root:
return False
return surface.number == "plural"
def to_constraint(self, surface: ObservedHebrewSurface) -> CanonicalConstraint:
return CanonicalConstraint(
constraint_id=f"{self.rule_id}:{surface.morphology_id}",
kind=self.constraint_kind,
payload={
"lemma": surface.lemma,
"root": surface.root,
"surface": surface.surface,
"morphology_id": surface.morphology_id,
"source_span": list(surface.source_span),
"source_pack_id": surface.source_pack_id,
},
)
PLURAL_ABSTAIN_RULE_V0 = PluralAbstainRuleV0()

View file

@ -1,35 +1,32 @@
# Runtime Pack Artifacts
`packs/` contains ratified runtime pack artifacts and their narrow loaders:
safety, identity, ethics, register, anchor-lens, modality packs, source-language
packs, primitives, and companion validators.
`packs/` holds runtime pack **artifacts**, **source language trees**, and **compilers/loaders**.
This directory is not interchangeable with `packs/`:
## Dual-pack boundary (ADR-0253 / Master Blueprint Stage 1)
- `packs/` stores runtime governance/style/safety/modality artifacts and source
pack material such as `packs/en`, `packs/he`, `packs/grc`, and `packs/el`.
- `packs/` stores linguistic pack schemas, compilers, loaders, and
reviewed semantic pack data under `packs/data/`.
- `core_ingest/` prepares external candidate pressure; it does not ratify or
rewrite these packs.
| Path | Role | Serve / runtime authority |
|------|------|---------------------------|
| `packs/data/<pack_id>/` | **Compiled runtime** language packs (`manifest.json`, `lexicon.jsonl`, checksummed companions) | **Yes** — only via `packs.compiler.load_pack` / `load_pack_entries` |
| `packs/he`, `packs/grc`, `packs/en`, `packs/el`, … | **Source / draft** language material (lemmas, morphology, probes, pack.toml) | **No** — not importable serve authority; compile into `packs/data` first |
| `packs/safety`, `packs/identity`, `packs/ethics`, `packs/register`, `packs/anchor_lens`, modality packs | Governance / style / modality artifacts | Yes — via their dedicated loaders |
| `packs/primitives`, `packs/schema.py`, `packs/compiler.py`, `packs/loader.py` | Schemas, compilers, loaders | Tooling / load path only |
Mutation rule: durable pack changes must be reviewed or proof-carrying and
must use the relevant validator/ratification lane. Do not add ad hoc runtime
pack writes.
**Hard rule:** Durable pack mutation is reviewed or proof-carrying. Do not write ad hoc runtime pack files from serve.
## Compilers (two different boundaries)
# Language Packs
| Compiler | Consumes | Produces |
|----------|----------|----------|
| `packs.compiler` | Reviewed pack data under `packs/data` | Runtime vocab / manifold structures |
| `core_ingest.compiler` | External candidate pressure | Validation reports + provisional learning artifacts |
`packs/` owns reviewed language-pack loading and compilation. It turns
pack manifests, lexicon rows, morphology, grammar attractors, and alignment
metadata into runtime vocab/manifold structures.
The shared word “compiler” means deterministic lowering across a trust boundary; source material and outputs differ.
This compiler is distinct from `core_ingest.compiler`:
## Mutation
- `packs.compiler` consumes reviewed pack data and builds linguistic
runtime structures.
- `core_ingest.compiler` consumes external candidate pressure and produces
validation reports plus provisional learning artifacts.
Durable changes must use the relevant validator/ratification lane. Session or speculative state must not masquerade as compiled pack COHERENT authority.
The shared word "compiler" means "deterministic lowering across a boundary";
the source material, trust boundary, and output structures are different.
## Validation
Architecture pin: `tests/test_pack_draft_serve_boundary.py`
Mapping: `docs/adr/MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md`

View file

@ -7,11 +7,14 @@ Used by derive_recognizer, recognize (for matching), and assessment enrichment.
Connects to cognitive path: listen/comprehend (depth from packs) -> think (anti-unif canonical) -> articulate.
Preserves exact recall, no drift repair.
Stage 3 (Master Blueprint / ADR-0256 reserve): multi-root Hebrew/Greek
depth must fail closed never silently commit ``roots[0]``.
"""
from __future__ import annotations
from dataclasses import replace
from dataclasses import dataclass, replace
from typing import Any, Sequence, Tuple
from recognition.outcome import EvidenceSpan, FeatureBundle
@ -23,12 +26,99 @@ except ImportError:
GraphNode = Any # type: ignore
def canonicalize_token(token: str, node_id: str | None, depths: dict[str, dict] | None) -> str:
"""Wrap root_normalize keyed on node_id from depths dict.
@dataclass(frozen=True, slots=True)
class RootSenseAmbiguity:
"""Typed multi-root / multi-sense ambiguity (fail-closed).
depths: {node_id: {"language": , "root": , ...}, ...}
If node_id in depths and lang he/grc and root, return root, else token.
Caller must pass the token associated with that node_id.
Carries every observed candidate root with node provenance. Downstream
must not emit a single canonical constraint unless an authored
``disambiguation_rule_id`` resolves the set.
"""
candidates: tuple[str, ...]
node_ids: tuple[str, ...]
languages: tuple[str, ...]
disambiguation_rule_id: str | None = None
@property
def resolved(self) -> bool:
return self.disambiguation_rule_id is not None and len(self.candidates) == 1
def as_dict(self) -> dict[str, Any]:
return {
"kind": "root_sense_ambiguity",
"candidates": list(self.candidates),
"node_ids": list(self.node_ids),
"languages": list(self.languages),
"disambiguation_rule_id": self.disambiguation_rule_id,
"resolved": self.resolved,
}
def observed_roots(depth: dict[str, dict] | None) -> tuple[str, ...]:
"""Stable unique roots observed across depth entries (order of first appearance)."""
if not depth:
return ()
seen: list[str] = []
for nid, d in depth.items():
del nid # provenance via observe_root_ambiguity
roots_field = d.get("roots")
if isinstance(roots_field, (list, tuple)):
for r in roots_field:
if r and r not in seen:
seen.append(str(r))
r = d.get("root")
if r and r not in seen:
seen.append(str(r))
return tuple(seen)
def observe_root_ambiguity(
depth: dict[str, dict] | None,
*,
disambiguation_rule_id: str | None = None,
) -> RootSenseAmbiguity | None:
"""Return typed ambiguity when ≥2 distinct roots are observed without resolution.
A single authored ``disambiguation_rule_id`` alone does **not** collapse
candidates that would be the forbidden "one authored mapping" shortcut.
Resolution requires the rule id *and* a single remaining candidate
(caller must filter candidates before calling).
"""
if not depth:
return None
cands = observed_roots(depth)
if len(cands) <= 1:
return None
node_ids: list[str] = []
langs: list[str] = []
for nid, d in depth.items():
rset = set()
if d.get("root"):
rset.add(str(d["root"]))
if isinstance(d.get("roots"), (list, tuple)):
rset.update(str(x) for x in d["roots"] if x)
if rset:
node_ids.append(nid)
langs.append(str(d.get("language") or ""))
amb = RootSenseAmbiguity(
candidates=cands,
node_ids=tuple(node_ids),
languages=tuple(langs),
disambiguation_rule_id=disambiguation_rule_id,
)
if amb.resolved:
return None
return amb
def canonicalize_token(token: str, node_id: str | None, depths: dict[str, dict] | None) -> str:
"""Root-normalize keyed on node_id from depths dict.
depths: {node_id: {"language": , "root": , "roots": optional multi, ...}, ...}
If the node carries multiple roots (``roots`` len>1), fail closed: return
the surface token unchanged (no silent first-root commit).
If global depth is multi-root ambiguous, also refuse cross-node first-match.
"""
if not depths or not node_id:
return token
@ -36,9 +126,16 @@ def canonicalize_token(token: str, node_id: str | None, depths: dict[str, dict]
if not d:
return token
lang = d.get("language")
if lang not in ("he", "grc"):
return token
multi = d.get("roots")
if isinstance(multi, (list, tuple)) and len({str(x) for x in multi if x}) > 1:
return token # fail closed: multi-root on this node
root = d.get("root")
if lang in ("he", "grc") and root:
return root
if root:
return str(root)
if isinstance(multi, (list, tuple)) and len(multi) == 1 and multi[0]:
return str(multi[0])
return token
@ -49,8 +146,7 @@ def canonicalize_agent_slot(
"""Return copy of tokens with agent slot canonicalized using EvidenceSpan.start + node_id if available.
Single lookup by agent_node_id (node-keyed, requires explicit nid or no-op; no first-key proxy).
If bundle use its start, else start_idx.
Pure.
Pure. Multi-root nodes fail closed (surface token retained).
"""
if not depths:
return tuple(tokens)
@ -66,7 +162,7 @@ def canonicalize_agent_slot(
if nid is None or nid not in depths:
return tuple(new_tokens) # require explicit nid, no first-key proxy
d = depths[nid]
if d.get("language") in ("he", "grc") and d.get("root"):
if d.get("language") in ("he", "grc") and (d.get("root") or d.get("roots")):
orig = new_tokens[start]
new_tokens[start] = canonicalize_token(orig, nid, depths)
return tuple(new_tokens)
@ -75,34 +171,75 @@ def canonicalize_agent_slot(
def build_node_depths(nodes: Sequence[Any]) -> dict[str, dict]:
"""Lift the node_depths dict from list of GraphNode (or objects with .node_id, .language etc).
Pure extraction of the comprehension in pipeline.
Pure extraction of the comprehension in pipeline. Includes optional
multi-root ``roots`` when present on the node.
"""
return {
n.node_id: {
k: v for k, v in {
"language": getattr(n, "language", None),
"root": getattr(n, "root", None),
"morphology_id": getattr(n, "morphology_id", None),
}.items() if v is not None
}
for n in nodes
if getattr(n, "language", None) in ("he", "grc") or getattr(n, "root", None)
}
out: dict[str, dict] = {}
for n in nodes:
if getattr(n, "language", None) not in ("he", "grc") and not getattr(n, "root", None) and not getattr(n, "roots", None):
continue
payload: dict[str, Any] = {}
lang = getattr(n, "language", None)
root = getattr(n, "root", None)
roots = getattr(n, "roots", None)
mid = getattr(n, "morphology_id", None)
if lang is not None:
payload["language"] = lang
if root is not None:
payload["root"] = root
if roots is not None:
payload["roots"] = tuple(roots) if not isinstance(roots, tuple) else roots
if mid is not None:
payload["morphology_id"] = mid
if payload:
out[n.node_id] = payload
return out
def enrich_assessments_with_depth(assessments: Tuple[Any, ...], depth: dict | None) -> Tuple[Any, ...]:
"""Immutable enrichment of assessments with root note using dataclasses.replace.
Returns a new tuple. Enrichment is best-effort: if an assessment does not
support replace (e.g. non-dataclass or frozen in an incompatible way), the
original item is kept without the depth note. No __dict__ reconstruction
is performed (avoids producing invalid objects for slots/frozen types).
* **0 roots** assessments unchanged.
* **1 unique root** append `` [root:]`` to runnable explanations.
* **2 unique roots** **fail closed**: do not pick ``roots[0]``; mark
runnable assessments non-runnable with ``AMBIGUOUS_ROOTS`` provenance
listing every candidate (Master Blueprint Stage 3 HE policy).
Returns a new tuple. Enrichment is best-effort for non-dataclass items.
"""
if not depth:
return assessments
roots = [d.get("root") for d in depth.values() if d.get("root")]
amb = observe_root_ambiguity(depth)
roots = observed_roots(depth)
if not roots:
return assessments
if amb is not None:
note = (
f" [AMBIGUOUS_ROOTS candidates={list(amb.candidates)} "
f"nodes={list(amb.node_ids)} — fail-closed; no silent first-root commit]"
)
new_ass = []
for a in assessments:
if hasattr(a, "explanation"):
try:
kwargs: dict[str, Any] = {
"explanation": (getattr(a, "explanation", "") or "") + note,
}
if hasattr(a, "runnable"):
kwargs["runnable"] = False
if hasattr(a, "unresolved_hazards"):
hazards = tuple(getattr(a, "unresolved_hazards", ()) or ())
if "ambiguous_hebrew_roots" not in hazards:
kwargs["unresolved_hazards"] = hazards + ("ambiguous_hebrew_roots",)
new_a = replace(a, **kwargs)
except Exception:
new_a = a
new_ass.append(new_a)
else:
new_ass.append(a)
return tuple(new_ass)
# Single unambiguous root
note = f" [root:{roots[0]}]"
new_ass = []
for a in assessments:
@ -110,8 +247,6 @@ def enrich_assessments_with_depth(assessments: Tuple[Any, ...], depth: dict | No
try:
new_a = replace(a, explanation=(getattr(a, "explanation", "") or "") + note)
except Exception:
# Best-effort only; do not synthesize copies that could violate
# frozen/slots invariants after CGA substrate types.
new_a = a
new_ass.append(new_a)
else:

View file

@ -137,6 +137,53 @@ def _proposal_id(candidate: CorrectionCandidate) -> str:
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
# Cached compiled HE morphology for the live teaching consumer (Stage 4).
_HE_MORPH_CATALOG: tuple | None = None
_HE_MORPH_LOAD_ATTEMPTED = False
def _he_morph_catalog():
"""Load compiled HE morphology once; fail closed on missing pack (None)."""
global _HE_MORPH_CATALOG, _HE_MORPH_LOAD_ATTEMPTED
if _HE_MORPH_LOAD_ATTEMPTED:
return _HE_MORPH_CATALOG
_HE_MORPH_LOAD_ATTEMPTED = True
try:
from generate.observed_he_morph_v0.records import load_observed_morphology
_HE_MORPH_CATALOG = load_observed_morphology("he_logos_micro_v1")
except Exception:
_HE_MORPH_CATALOG = None
return _HE_MORPH_CATALOG
def _auto_he_morph_decision(proposal: PackMutationProposal):
"""Apply executable HE morph rule when correction text cites a catalog surface.
Live production path for Stage 4 not optional test-only wiring.
Returns None when no HE surface is present (no-op, English corrections unchanged).
"""
catalog = _he_morph_catalog()
if not catalog:
return None
text = proposal.correction_text or ""
# Prefer longest surface match present in the correction text.
hits = [s for s in catalog if s.surface and s.surface in text]
if not hits:
return None
hits.sort(key=lambda s: len(s.surface), reverse=True)
surface = hits[0]
from generate.observed_he_morph_v0.consumer import apply_he_morph_constraint
return apply_he_morph_constraint(
proposal_text=text,
lemma_key=str(proposal.subject or surface.lemma),
observed_catalog=catalog,
mode="executable",
he_surface=surface.surface,
)
class TeachingStore:
"""Bounded, append-only store for reviewed teaching examples.
@ -154,7 +201,12 @@ class TeachingStore:
def capacity(self) -> int:
return self._capacity
def add(self, example: ReviewedTeachingExample) -> PackMutationProposal | None:
def add(
self,
example: ReviewedTeachingExample,
*,
he_morph_decision=None,
) -> PackMutationProposal | None:
"""Store an accepted example and return a mutation proposal.
Rejected examples are dropped silently. Returns None if the
@ -167,6 +219,11 @@ class TeachingStore:
conflicting prior are upgraded to ``EpistemicStatus.CONTESTED``
neither is admissible as evidence until a coherence judgment
ratifies one of them. See ``_detect_contradiction``.
Stage 4 observed-HE morph consumer (optional ``he_morph_decision``):
when the executable HE morph rule returns ABSTAIN or FAIL_CLOSED,
the proposal is forced to CONTESTED (abstention at the teaching
contradiction seam) without English-to-Hebrew pseudo-morphology.
"""
if not example.accepted:
return None
@ -194,6 +251,16 @@ class TeachingStore:
epistemic_status=example.epistemic_status,
)
# Stage 4 HE morph constraint — live path auto-applies executable rule
# from compiled packs/data morphology (not tests-only optional wiring).
if he_morph_decision is None:
he_morph_decision = _auto_he_morph_decision(proposal)
if he_morph_decision is not None:
kind = getattr(he_morph_decision, "kind", None)
kind_val = getattr(kind, "value", kind)
if kind_val in ("abstain", "fail_closed"):
proposal = proposal.with_status(EpistemicStatus.CONTESTED)
# Coherence judgment — detect (S, R, T) ↔ (S, R, ¬T) pairs and
# transition both proposals to CONTESTED. ADR-0021: CONTESTED is
# not admissible as evidence; the next reviewed correction can

View file

@ -157,11 +157,16 @@ def test_core_test_suite_accepts_pytest_flags_without_separator(monkeypatch) ->
# (growing) packs file list: the contract under test is that a curated
# suite expands to its files followed by the forwarded "-q", with no "--"
# separator needed.
# Assert against the runtime suite map (core.cli_test.TEST_SUITES), which
# cli.cmd_test actually expands. core.cli._TEST_SUITES is a re-export of
# the same object — do not pin a second stale list.
from core.cli_test import TEST_SUITES
assert calls[0] == (
cli.sys.executable,
"-m",
"pytest",
*cli._TEST_SUITES["packs"],
*TEST_SUITES["packs"],
"-q",
)

View file

@ -0,0 +1,158 @@
"""Stage 4 — observed-HE morph constraint v0 four-arm ablation."""
from __future__ import annotations
from generate.observed_he_morph_v0 import (
PLURAL_ABSTAIN_RULE_V0,
apply_he_morph_constraint,
load_observed_morphology,
run_four_arm_ablation,
)
from generate.observed_he_morph_v0.consumer import DecisionKind
from teaching.epistemic import EpistemicStatus
from teaching.store import TeachingStore
from teaching.review import ReviewedTeachingExample, ReviewOutcome
from teaching.correction import CorrectionCandidate
from generate.intent import DialogueIntent, IntentTag
def test_load_compiled_he_morphology_with_provenance():
rows = load_observed_morphology("he_logos_micro_v1")
assert len(rows) >= 1
assert all(r.language == "he" for r in rows)
assert all(r.source_pack_id == "he_logos_micro_v1" for r in rows)
assert all(isinstance(r.source_span, tuple) and len(r.source_span) == 2 for r in rows)
assert any(r.number == "plural" for r in rows)
assert any(r.root for r in rows)
def test_four_arm_ablation_sealed_metrics():
report = run_four_arm_ablation()
assert report.metadata_bit_identical_to_canonical is True
assert report.executable_changed_decision is True
assert report.wrong_count == 0
assert report.refusal_correct is True
assert report.provenance_complete is True
arms = {a.arm: a for a in report.arms}
assert arms["canonical"].decision.kind is DecisionKind.PASS
assert arms["metadata"].decision.kind is DecisionKind.PASS
assert arms["executable"].decision.kind is DecisionKind.ABSTAIN
assert arms["adversarial"].decision.kind is DecisionKind.FAIL_CLOSED
def test_metadata_only_inert_vs_executable_effect():
import hashlib
import json
catalog = load_observed_morphology("he_logos_micro_v1")
plural = next(s for s in catalog if s.number == "plural")
claim = f"{plural.lemma} must be singular only — exclusive singular identity"
can = apply_he_morph_constraint(
proposal_text=claim,
lemma_key=plural.lemma,
observed_catalog=catalog,
mode="canonical",
he_surface=plural.surface,
)
meta = apply_he_morph_constraint(
proposal_text=claim,
lemma_key=plural.lemma,
observed_catalog=catalog,
mode="metadata",
he_surface=plural.surface,
)
exe = apply_he_morph_constraint(
proposal_text=claim,
lemma_key=plural.lemma,
observed_catalog=catalog,
mode="executable",
he_surface=plural.surface,
)
# Bit-identical decision payloads (Stage 4 sealed requirement).
d_can = hashlib.sha256(
json.dumps(can.as_dict(), sort_keys=True).encode()
).hexdigest()
d_meta = hashlib.sha256(
json.dumps(meta.as_dict(), sort_keys=True).encode()
).hexdigest()
assert d_can == d_meta
assert meta.kind is DecisionKind.PASS
assert exe.kind is DecisionKind.ABSTAIN
assert exe.rule_id == PLURAL_ABSTAIN_RULE_V0.rule_id
assert exe.constraints[0].payload["source_span"]
def test_oov_and_invalid_fail_closed():
catalog = load_observed_morphology("he_logos_micro_v1")
oov = apply_he_morph_constraint(
proposal_text="x",
lemma_key="x",
observed_catalog=catalog,
mode="executable",
he_surface="zzz_not_in_pack",
)
assert oov.kind is DecisionKind.FAIL_CLOSED
assert oov.reason == "oov_he_surface"
def test_teaching_store_consumer_seam_abstains_on_plural_rule():
"""Live TeachingStore.add path: HE plural rule auto-applies (no test-only kwarg)."""
catalog = load_observed_morphology("he_logos_micro_v1")
plural = next(s for s in catalog if s.number == "plural")
store = TeachingStore(capacity=8)
cand1 = CorrectionCandidate(
correction_text=f"{plural.lemma} is a logos utterance",
intent=DialogueIntent(tag=IntentTag.CORRECTION, subject=plural.lemma),
prior_surface="prior",
prior_turn=0,
candidate_id="c1",
)
rev1 = ReviewedTeachingExample(
candidate=cand1,
outcome=ReviewOutcome.ACCEPTED,
review_hash="h1",
epistemic_status=EpistemicStatus.SPECULATIVE,
)
p1 = store.add(rev1)
assert p1 is not None
# Correction text includes the observed HE plural surface so the live
# auto-path in TeachingStore.add finds the catalog row and abstains.
cand2 = CorrectionCandidate(
correction_text=(
f"{plural.surface} ({plural.lemma}) must be singular only — "
f"exclusive singular identity"
),
intent=DialogueIntent(tag=IntentTag.CORRECTION, subject=plural.lemma),
prior_surface="prior",
prior_turn=1,
candidate_id="c2",
)
rev2 = ReviewedTeachingExample(
candidate=cand2,
outcome=ReviewOutcome.ACCEPTED,
review_hash="h2",
epistemic_status=EpistemicStatus.SPECULATIVE,
)
# No he_morph_decision kwarg — production auto-path must fire.
p2 = store.add(rev2)
assert p2 is not None
assert p2.epistemic_status is EpistemicStatus.CONTESTED
def test_vault_promotion_default_requires_geometric_unitarity():
"""Production VaultPromotionPolicy() uses 1e-6 residual, not soft 0.05."""
from core.physics.learning import VaultPromotionPolicy
from core.physics.energy import EnergyClass, EnergyProfile
policy = VaultPromotionPolicy() # production default
assert policy.residual_threshold <= 1e-6
soft = EnergyProfile(
raw=0.05, energy_class=EnergyClass.E0, coherence_residual=0.02
)
assert policy.decide(soft).promote is False
tight = EnergyProfile(
raw=0.05, energy_class=EnergyClass.E0, coherence_residual=1e-9
)
assert policy.decide(tight).promote is True

View file

@ -381,8 +381,13 @@ def test_depth_canonical_direct():
assert real_pg.get_node_depths()["n1"]["root"] == "א-מ-ן"
from generate.problem_frame_contracts import ContractAssessment
a = ContractAssessment(candidate_organ="t", runnable=True, explanation="base")
# Multi-root depth must fail closed (Stage 3) — not silent roots[0].
en = enrich_assessments_with_depth((a,), depths)
assert "[root:א-מ-ן]" in (en[0].explanation or "")
assert en[0].runnable is False
assert "AMBIGUOUS_ROOTS" in (en[0].explanation or "")
# Single-root enrichment still annotates.
en_one = enrich_assessments_with_depth((a,), {"n1": {"language": "he", "root": "א-מ-ן"}})
assert "[root:א-מ-ן]" in (en_one[0].explanation or "")
print("depth_canonical direct tests passed")

View file

@ -0,0 +1,117 @@
"""Architecture pin: draft language-pack trees are not serve import authority.
ADR-0253 / Master Blueprint Stage 1 dual-pack boundary:
* Runtime language packs load from ``packs/data/<pack_id>/`` via
``packs.compiler.load_pack``.
* Source trees ``packs/he``, ``packs/grc`` (and peers) are draft material;
serve entrypoints must not import them as Python packages.
This is a static + process probe not a substitute for compile-time validation.
"""
from __future__ import annotations
import ast
import subprocess
import sys
from pathlib import Path
import pytest
_ROOT = Path(__file__).resolve().parents[1]
# Draft/source language trees — not compiled runtime authority.
_DRAFT_PACK_MODULES = frozenset(
{
"packs.he",
"packs.grc",
"packs.en",
"packs.el",
}
)
# Serve-adjacent entry modules that must not import draft pack packages.
_SERVE_ENTRY_FILES = (
_ROOT / "chat" / "runtime.py",
_ROOT / "core" / "cognition" / "pipeline.py",
_ROOT / "core" / "cli.py",
)
def _module_imports(path: Path) -> set[str]:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
found: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
found.add(alias.name)
elif isinstance(node, ast.ImportFrom) and node.module:
found.add(node.module)
return found
def test_serve_entry_files_do_not_import_draft_language_pack_packages():
offenders: list[str] = []
for path in _SERVE_ENTRY_FILES:
assert path.is_file(), f"missing serve entry {path}"
imports = _module_imports(path)
for draft in _DRAFT_PACK_MODULES:
if draft in imports or any(
imp == draft or imp.startswith(draft + ".") for imp in imports
):
offenders.append(f"{path.relative_to(_ROOT)} imports {draft}")
assert not offenders, "draft pack imports on serve entries:\n" + "\n".join(offenders)
def test_compiler_load_pack_resolves_under_packs_data_only():
"""``_load_pack_cached`` must root pack_dir at packs/data/<id>."""
compiler_path = _ROOT / "packs" / "compiler.py"
src = compiler_path.read_text(encoding="utf-8")
assert 'Path(__file__).parent / "data"' in src
assert "def load_pack(" in src
# No alternate root that points at packs/he or packs/grc as compiled home.
tree = ast.parse(src)
for node in ast.walk(tree):
if isinstance(node, ast.Constant) and isinstance(node.value, str):
# Allow incidental strings in comments/docs only via absence of
# path construction to /he/ or /grc/ as pack_dir — checked below.
pass
# Explicit: load path construction uses data/, not he/ or grc/.
assert '/ "he"' not in src and "/ 'he'" not in src
assert '/ "grc"' not in src and "/ 'grc'" not in src
def test_import_chat_runtime_does_not_load_draft_he_grc_modules():
"""Process probe: serve import must not pull packs.he / packs.grc into sys.modules."""
banned = sorted(_DRAFT_PACK_MODULES)
probe = (
"import importlib, sys, json;"
"importlib.import_module('chat.runtime');"
f"banned={banned!r};"
"leaked=sorted(m for m in sys.modules for b in banned "
"if m==b or m.startswith(b+'.'));"
"print(json.dumps(leaked))"
)
result = subprocess.run(
[sys.executable, "-c", probe],
cwd=str(_ROOT),
capture_output=True,
text=True,
env={"PYTHONPATH": str(_ROOT), "PATH": ""},
check=False,
)
assert result.returncode == 0, f"probe failed: {result.stderr[-2000:]}"
import json as _json
leaked = _json.loads(result.stdout.strip().splitlines()[-1])
assert leaked == [], f"serve process loaded draft pack modules: {leaked}"
def test_mapping_document_exists_and_forbids_overwrite_policy():
mapping = _ROOT / "docs" / "adr" / "MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md"
assert mapping.is_file()
text = mapping.read_text(encoding="utf-8")
assert "Do **not** overwrite" in text or "Do not overwrite" in text
assert "ADR-0246" in text and "Induced Identity Action" in text
assert "packs/data" in text

View file

@ -269,6 +269,13 @@ def test_malformed_depth_labels_cannot_elevate_runnable() -> None:
def test_valid_depth_metadata_on_gold_does_not_change_answer() -> None:
"""Multi-root labels must not change the operator answer (Stage 3 fail-closed).
With 2 unique roots, enrichment appends AMBIGUOUS_ROOTS provenance and does
**not** commit a silent ``[root:]`` first-root note. Single-root decoration
with ``[root:]`` is covered by
``test_metadata_only_decorates_but_does_not_change_answer``.
"""
depths = {
"p0": {"language": "he", "root": "א-מ-נ"},
"p1": {"language": "grc", "root": "λογ"},
@ -276,7 +283,8 @@ def test_valid_depth_metadata_on_gold_does_not_change_answer() -> None:
op = run_operator(GOLD_0001)
meta = run_metadata_only(GOLD_0001, depth_labels=depths)
assert answers_match(op, meta)
assert meta.explanation_has_root_note is True
# Multi-root: fail-closed — no silent first-root [root:] commit.
assert meta.explanation_has_root_note is False
def test_distractor_quantity_case_refuses() -> None:

View file

@ -0,0 +1,214 @@
"""Stage 2 Master Blueprint — physics hardening & parity boundary pins.
Proves (without inventing dual implementations):
1. Shared fraction-decrease scale domain ``0 < k < 1`` is one function used by
both obligation assessment and geometric admission.
2. Identity final authority has no L2 blend helpers / PASSTHROUGH.
3. Python/Rust parity surface: ops that are dual-backend are named; ops that
are Python-authoritative only are documented so we never claim false parity.
4. Content digests remain full 64-hex LE f64 SHA-256.
"""
from __future__ import annotations
import ast
import hashlib
import inspect
from pathlib import Path
import numpy as np
import pytest
_ROOT = Path(__file__).resolve().parents[1]
# --- 1. Shared scale invariant -------------------------------------------------
def test_fraction_decrease_scale_domain_shared_by_assessment_and_geometry():
from generate.problem_frame_contracts import _is_valid_fraction_decrease_scale
# Domain itself
assert _is_valid_fraction_decrease_scale(0.5) is True
assert _is_valid_fraction_decrease_scale(0.0) is False
assert _is_valid_fraction_decrease_scale(1.0) is False
assert _is_valid_fraction_decrease_scale(-0.1) is False
src = (_ROOT / "generate" / "problem_frame_contracts.py").read_text(encoding="utf-8")
# Both obligation and geometric admission call the shared helper by name.
assert src.count("_is_valid_fraction_decrease_scale") >= 3
assert "scale_out_of_range" in src
# Geometric admission path references the same helper (not a parallel check).
assert "assess_fraction_decrease" in src
assert "_dilation_versor_payload" in src or "VersorBinding" in src or "geometric" in src
def test_fraction_decrease_scale_source_has_single_domain_predicate():
path = _ROOT / "generate" / "problem_frame_contracts.py"
tree = ast.parse(path.read_text(encoding="utf-8"))
defs = [
n.name
for n in ast.walk(tree)
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
and "scale" in n.name
and "valid" in n.name
]
# Exactly one canonical domain predicate name.
assert "_is_valid_fraction_decrease_scale" in defs
assert defs.count("_is_valid_fraction_decrease_scale") == 1
# --- 2. L2 / legacy identity authority excision -------------------------------
def test_identity_module_has_no_l2_blend_helpers():
path = _ROOT / "core" / "physics" / "identity.py"
src = path.read_text(encoding="utf-8")
tree = ast.parse(src)
names = {
n.name
for n in ast.walk(tree)
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
}
forbidden = {
"_axis_projection",
"_mean_frame_coherence",
"_legacy_score",
"_l2_score",
"_scalar_l2_blend",
}
assert not (names & forbidden), f"L2 helper functions still present: {names & forbidden}"
# No 0.75 blend formula as live authority
assert "0.75 *" not in src and "0.75*" not in src
def test_goldtether_primary_residual_not_flat_dot():
path = _ROOT / "core" / "physics" / "goldtether.py"
src = path.read_text(encoding="utf-8")
# coherence_residual must route through WaveManifold / geometric product path
assert "measure_unitary_residual" in src or "versor_unit_residual" in src
assert "np.dot(psi" not in src
assert "GoldTetherViolationError" in src
def test_pipeline_ratifier_has_no_passthrough_authority():
from generate.intent_ratifier import RatificationOutcome
values = {m.value for m in RatificationOutcome}
assert "passthrough" not in values
assert values == {"ratified", "demoted"}
# --- 3. Cl(4,1) active-path smoke (exact ops) ---------------------------------
def test_active_cl41_ops_geometric_product_conformal_gram_gt_sandwich():
from algebra.backend import geometric_product, versor_condition
from algebra.cga import N_INF, N_O, cga_inner, embed_point, is_null
from algebra.cl41 import reverse
from algebra.rotor import make_rotor_from_angle, word_transition_rotor
from core.physics.cognitive_lifecycle import modality_transition_sandwich
from core.physics.goldtether import require_unitary
from core.physics.identity_manifold import gram_matrix, lift_axis
# Geometric product + reverse unit condition
R = make_rotor_from_angle(0.4)
prod = geometric_product(R, reverse(R))
assert abs(float(prod[0]) - 1.0) < 1e-9
assert float(versor_condition(R)) < 1e-6
# Conformal embedding null (f64 path; f32 embeds may exceed 1e-9 residual)
X = embed_point(np.array([0.2, -0.1, 0.5], dtype=np.float64), dtype=np.float64)
assert is_null(np.asarray(X, dtype=np.float64), tol=1e-6)
assert abs(cga_inner(N_O, N_INF) + 1.0) < 1e-12
# Gram subspace (identity axes)
axes = [lift_axis((1.0, 0.0, 0.0)), lift_axis((0.0, 1.0, 0.0))]
G = gram_matrix(axes)
assert G.shape == (2, 2)
# GoldTether unitary
assert require_unitary(R) <= 1e-6
# Multimodal sandwich
psi = np.zeros(32, dtype=np.float64)
psi[0] = 1.0
target = make_rotor_from_angle(0.2)
rotor = word_transition_rotor(psi, target)
out, tr = modality_transition_sandwich(psi, rotor, source_modality="a", target_modality="b")
assert out.shape == (32,)
assert len(tr.psi_out_digest) == 64
assert tr.goldtether_residual <= 1e-6
# --- 4. Python / Rust parity boundary -----------------------------------------
# Dual-backend ops (Rust exported + parity tests exist when core_rs built).
_DUAL_BACKEND_OPS = (
"geometric_product",
"cga_inner",
"versor_condition",
"versor_apply", # sandwich
"embed_point",
)
# Python-authoritative (or not exported on core_rs PyO3 surface). Claiming
# bit-parity without a Rust binding is forbidden.
_PYTHON_AUTHORITATIVE_OPS = (
"rotor_power", # algebra.rotor — no core_rs export
"incidence", # if present in algebra — not dual-backend
)
def test_rust_parity_surface_documented_and_no_false_claims():
lib = (_ROOT / "core-rs" / "src" / "lib.rs").read_text(encoding="utf-8")
for op in _DUAL_BACKEND_OPS:
assert op in lib or op.replace("_", "") in lib or f"fn {op}" in lib or op in lib, (
f"expected dual-backend op {op} in core-rs exports"
)
# rotor_power must NOT be falsely present as a public pyfunction
assert "fn rotor_power" not in lib
assert "wrap_pyfunction!(rotor_power" not in lib
def test_python_authoritative_ops_live_in_python_modules():
from algebra import rotor
assert hasattr(rotor, "rotor_power")
assert callable(rotor.rotor_power)
# Incidence: optional; if missing, document as N/A
try:
from algebra import incidence as _inc # type: ignore
assert _inc is not None
except ImportError:
pytest.skip("no algebra.incidence module — Python N/A, not a false Rust claim")
def test_existing_parity_test_files_cover_dual_backend_ops():
tests_dir = _ROOT / "tests"
names = {p.name for p in tests_dir.glob("test_*rust*parity*.py")}
names |= {p.name for p in tests_dir.glob("test_*parity*.py")}
# At least one parity file per dual-backend family
assert any("geometric_product" in n for n in names)
assert any("cga_inner" in n for n in names)
assert any("versor_condition" in n for n in names)
assert any("versor_apply" in n for n in names)
assert any("cga_rust" in n or "embed" in n for n in names)
# --- 5. Content digests -------------------------------------------------------
def test_multivector_content_digest_is_sha256_le_f64():
from core.physics.wave_manifold import multivector_content_digest
F = np.zeros(32, dtype=np.float64)
F[0] = 1.0
dig = multivector_content_digest(F)
assert len(dig) == 64
assert all(c in "0123456789abcdef" for c in dig)
le = np.ascontiguousarray(F, dtype=np.dtype("<f8"))
assert dig == hashlib.sha256(le.tobytes()).hexdigest()

View file

@ -0,0 +1,166 @@
"""Stage 3A/C — geometric coherence taxonomy + inductive fixed-point."""
from __future__ import annotations
import numpy as np
from algebra.rotor import make_rotor_from_angle
from core.cognition.geometric_coherence import (
GeometricCoherenceStatus,
evaluate_geometric_coherence,
)
from core.cognition.inductive_closure import expand_relation_closure
from chat.runtime import ChatRuntime
from core.cognition import CognitiveTurnPipeline
def test_geometric_coherence_verified_on_unit_versor():
R = make_rotor_from_angle(0.25)
v = evaluate_geometric_coherence(R)
assert v.status is GeometricCoherenceStatus.GEOMETRICALLY_VERIFIED
assert v.closed is True
assert v.field_present is True
def test_geometric_coherence_refuses_missing_field():
v = evaluate_geometric_coherence(None)
assert v.status is GeometricCoherenceStatus.REFUSED
assert v.closed is False
def test_geometric_coherence_unverified_on_dirty_field():
dirty = np.zeros(32, dtype=np.float64)
dirty[0] = 0.5
dirty[1] = 0.5
v = evaluate_geometric_coherence(dirty)
assert v.status is GeometricCoherenceStatus.UNVERIFIED
assert v.closed is False
def test_pipeline_populates_geometric_coherence():
rt = ChatRuntime()
p = CognitiveTurnPipeline(rt)
result = p.run("what is light", max_tokens=6)
assert result.geometric_coherence is not None
assert result.geometric_coherence.field_present is True
# Live fields after chat are typically closed versors.
assert result.geometric_coherence.status in {
GeometricCoherenceStatus.GEOMETRICALLY_VERIFIED,
GeometricCoherenceStatus.UNVERIFIED,
GeometricCoherenceStatus.REFUSED,
}
# Dual taxonomy: no EpistemicState.COHERENT
from core.epistemic_state import EpistemicState
from teaching.epistemic import EpistemicStatus
assert not hasattr(EpistemicState, "COHERENT")
assert EpistemicStatus.COHERENT.value == "coherent"
def test_inductive_closure_derives_two_hop():
triples = (
("a", "is", "b"),
("b", "is", "c"),
)
# Explicit geometric_admissible always-true for pure graph composition tests.
res = expand_relation_closure(
triples, budget=8, geometric_admissible=lambda h, r, t: True
)
assert len(res.base) == 2
derived_tails = {(d.head, d.relation, d.tail) for d in res.derived}
assert ("a", "is", "c") in derived_tails
assert res.fixed_point is True
assert res.steps_taken >= 1
a_to_c = next(d for d in res.derived if d.head == "a" and d.tail == "c")
assert a_to_c.path[0] == "a" and a_to_c.path[-1] == "c"
assert a_to_c.admissible is True
def test_inductive_derived_requires_geometric_admissibility():
"""Non-admissible candidates must not be promoted into derived or work.
Stage 3 exit: multi-step paths close ONLY under geometric conditions.
Stamping admissible=False while still seeding further expansion is a leak.
"""
triples = (
("a", "is", "b"),
("b", "is", "c"),
)
def refuse_all(h, r, t):
del h, r, t
return False
res = expand_relation_closure(
triples, budget=8, geometric_admissible=refuse_all
)
# Refuse-all: base stays store-visible but no derived promotion.
assert len(res.derived) == 0
assert not any(d.head == "a" and d.tail == "c" for d in res.derived)
assert all(b.admissible is False for b in res.base)
def test_inductive_non_admissible_intermediate_does_not_seed_expansion():
"""a→c rejected must not yield a→d solely via that intermediate.
Graph: abcd. Refuse ac and bd so the only multi-hop bridge to ad
would be the non-admissible ac path. Closure must not invent ad.
"""
triples = (
("a", "is", "b"),
("b", "is", "c"),
("c", "is", "d"),
)
def geom(h: str, r: str, t: str) -> bool:
del r
# Block the two-hop bridges that would otherwise seed a→d.
if (h, t) in {("a", "c"), ("b", "d")}:
return False
return True
res = expand_relation_closure(
triples, budget=8, geometric_admissible=geom
)
derived_pairs = {(d.head, d.tail) for d in res.derived}
assert ("a", "c") not in derived_pairs
assert ("b", "d") not in derived_pairs
assert ("a", "d") not in derived_pairs
assert all(d.admissible for d in res.derived)
def test_inductive_closure_detects_contradiction():
triples = (
("a", "is", "b"),
("a", "is", "c"),
)
res = expand_relation_closure(
triples, budget=4, geometric_admissible=lambda h, r, t: True
)
assert any(c.contradiction for c in res.contradictions)
assert len(res.contradictions) >= 2
def test_inductive_closure_budget_truncation():
# Long chain: a0->a1->...->a20
triples = tuple((f"a{i}", "r", f"a{i+1}") for i in range(20))
ok = lambda h, r, t: True # noqa: E731
res = expand_relation_closure(triples, budget=2, geometric_admissible=ok)
assert res.truncated or res.steps_taken <= 2
res2 = expand_relation_closure(triples, budget=16, geometric_admissible=ok)
assert any(d.head == "a0" and d.tail == "a2" for d in res2.derived) or any(
d.head == "a0" for d in res2.derived
)
def test_inductive_closure_cycle_safe():
triples = (
("a", "r", "b"),
("b", "r", "a"),
)
res = expand_relation_closure(
triples, budget=8, geometric_admissible=lambda h, r, t: True
)
# Must terminate without inventing infinite chain
assert res.fixed_point or res.steps_taken <= 8
assert all(len(d.path) < 20 for d in res.derived)

View file

@ -0,0 +1,58 @@
"""Stage 3D — OOV conformal probe + horosphere egress authority pins."""
from __future__ import annotations
import numpy as np
from algebra.cga import cga_inner
from algebra.versor import unitize_versor
from chat.runtime import ChatRuntime
from core.cognition import CognitiveTurnPipeline
from vocab.manifold import VocabManifold
def test_oov_geometric_context_carries_conformal_neighbors_or_topology():
"""Live pipeline OOV/pending path records geometric context, not lexical only."""
rt = ChatRuntime()
p = CognitiveTurnPipeline(rt)
# Force an OOV-shaped prompt (unlikely pack lemma).
result = p.run("what is xyzzyplugh_oov_token_zzz", max_tokens=6)
ctx = result.oov_geometric_context
# Either OOV path fired with conformal note, or graph topology was recorded.
if ctx is not None:
assert "conformal_neighbors" in ctx or "unresolved_topology" in ctx or "node_depths" in ctx
if "note" in ctx:
assert "cga_inner" in ctx["note"] or "Conformal" in ctx["note"] or "depth" in ctx["note"]
def test_vocab_nearest_is_cga_inner_not_cosine():
"""Horosphere egress ranking is exact cga_inner argmax (Blueprint B.3)."""
rng = np.random.default_rng(11)
m = VocabManifold()
for w in ("alpha", "beta", "gamma"):
m.add(w, unitize_versor(rng.standard_normal(32).astype(np.float64)))
query = unitize_versor(
m.get_versor("beta").astype(np.float64) + 0.08 * rng.standard_normal(32)
)
word, idx = m.nearest(query)
scores = [float(cga_inner(query, m.get_versor_at(i))) for i in range(len(m))]
assert idx == int(np.argmax(scores))
assert word == m.get_word_at(idx)
def test_pipeline_does_not_use_vault_hits_as_gate_for_surface():
"""vault_hits remains telemetry; surface authority is geometric/resolution."""
rt = ChatRuntime()
p = CognitiveTurnPipeline(rt)
result = p.run("what is light", max_tokens=6)
assert isinstance(result.vault_hits, int)
assert result.authority_source in {
"runtime_canonical",
"runtime_pre_decoration",
"runtime",
"realizer",
"substrate_realizer",
"",
}
# Geometric coherence is first-class and independent of vault_hits.
assert result.geometric_coherence is not None

View file

@ -0,0 +1,87 @@
"""Stage 3 — fail-closed multi-root Hebrew/Greek depth policy."""
from __future__ import annotations
from generate.problem_frame_contracts import ContractAssessment
from recognition.depth_canonical import (
RootSenseAmbiguity,
build_node_depths,
canonicalize_token,
enrich_assessments_with_depth,
observe_root_ambiguity,
observed_roots,
)
def test_observed_roots_unique_stable_order():
depth = {
"n1": {"language": "he", "root": "א-מ-ן"},
"n2": {"language": "he", "root": "ד-ב-ר"},
"n3": {"language": "he", "root": "א-מ-ן"},
}
assert observed_roots(depth) == ("א-מ-ן", "ד-ב-ר")
def test_observe_root_ambiguity_when_multi():
depth = {
"n1": {"language": "he", "root": "א-מ-ן"},
"n2": {"language": "he", "root": "ד-ב-ר"},
}
amb = observe_root_ambiguity(depth)
assert isinstance(amb, RootSenseAmbiguity)
assert amb.candidates == ("א-מ-ן", "ד-ב-ר")
assert amb.resolved is False
def test_enrich_assessments_fails_closed_on_multi_root_no_first_match():
depth = {
"n1": {"language": "he", "root": "א-מ-ן"},
"n2": {"language": "he", "root": "ד-ב-ר"},
}
a = ContractAssessment(candidate_organ="t", runnable=True, explanation="base")
en = enrich_assessments_with_depth((a,), depth)
assert en[0].runnable is False
assert "AMBIGUOUS_ROOTS" in (en[0].explanation or "")
assert "א-מ-ן" in (en[0].explanation or "")
assert "ד-ב-ר" in (en[0].explanation or "")
# Must NOT silently commit only the first root as the sole note.
assert "[root:א-מ-ן]" not in (en[0].explanation or "") or "AMBIGUOUS" in (
en[0].explanation or ""
)
assert "ambiguous_hebrew_roots" in (en[0].unresolved_hazards or ())
def test_enrich_single_root_still_annotates():
depth = {"n1": {"language": "he", "root": "א-מ-ן"}}
a = ContractAssessment(candidate_organ="t", runnable=True, explanation="base")
en = enrich_assessments_with_depth((a,), depth)
assert en[0].runnable is True
assert "[root:א-מ-ן]" in (en[0].explanation or "")
def test_canonicalize_multi_root_node_fails_closed():
depths = {
"n1": {"language": "he", "roots": ("א-מ-ן", "ד-ב-ר")},
}
# Surface retained — no silent first-root commit.
assert canonicalize_token("דָּבָר", "n1", depths) == "דָּבָר"
def test_canonicalize_single_root_still_maps():
depths = {"n1": {"language": "he", "root": "א-מ-ן"}}
assert canonicalize_token("אמת", "n1", depths) == "א-מ-ן"
def test_build_node_depths_carries_roots_tuple():
class _N:
node_id = "n1"
language = "he"
root = None
roots = ("א-מ-ן", "ד-ב-ר")
morphology_id = None
d = build_node_depths([_N()])
assert d["n1"]["roots"] == ("א-מ-ן", "ד-ב-ר")
amb = observe_root_ambiguity(d)
assert amb is not None
assert len(amb.candidates) == 2