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:
commit
18c578d960
28 changed files with 2214 additions and 214 deletions
156
core/cli.py
156
core/cli.py
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
118
core/cognition/geometric_coherence.py
Normal file
118
core/cognition/geometric_coherence.py
Normal 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",
|
||||
)
|
||||
243
core/cognition/inductive_closure.py
Normal file
243
core/cognition/inductive_closure.py
Normal 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 a≠c and (a,r,c) unknown,
|
||||
derive (a,r,c) with path a…c.
|
||||
* 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,
|
||||
)
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) ---
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 0240–0253 with intent titles that **collide by number** with the repository’s already-Accepted ADR-0246 through ADR-0252 (and with other live 0240–0245 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 0254–0261).
|
||||
3. **This ADR-0253 number is claimed for governance freeze**, not for the Blueprint’s “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 0246–0252 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`
|
||||
75
docs/adr/MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md
Normal file
75
docs/adr/MASTER-BLUEPRINT-2026-07-20-ADR-MAPPING.md
Normal 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-0240–0253 with intent titles that **collide by number** with already-Accepted repository ADRs (especially 0246–0252). 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 §§1–4 |
|
||||
| 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 3–4** | 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 **0254–0261** 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-0246–0252 bodies to match Blueprint titles
|
||||
- Claiming Stage 3/4 decisions complete by documentation alone
|
||||
- Force-fitting Blueprint numbers onto Accepted history
|
||||
|
|
@ -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-0240–0253 with titles that
|
||||
**collide by number** with Accepted repository ADRs (especially 0246–0252).
|
||||
|
||||
- **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)
|
||||
|
|
|
|||
26
docs/adr/epistemic-taxonomy-ownership-stage3.md
Normal file
26
docs/adr/epistemic-taxonomy-ownership-stage3.md
Normal 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.
|
||||
29
generate/observed_he_morph_v0/__init__.py
Normal file
29
generate/observed_he_morph_v0/__init__.py
Normal 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",
|
||||
]
|
||||
165
generate/observed_he_morph_v0/ablation.py
Normal file
165
generate/observed_he_morph_v0/ablation.py
Normal 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,
|
||||
)
|
||||
154
generate/observed_he_morph_v0/consumer.py
Normal file
154
generate/observed_he_morph_v0/consumer.py
Normal 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
|
||||
127
generate/observed_he_morph_v0/records.py
Normal file
127
generate/observed_he_morph_v0/records.py
Normal 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
|
||||
59
generate/observed_he_morph_v0/rules.py
Normal file
59
generate/observed_he_morph_v0/rules.py
Normal 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()
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
||||
|
|
|
|||
158
tests/test_observed_he_morph_constraint_v0.py
Normal file
158
tests/test_observed_he_morph_constraint_v0.py
Normal 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
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
|||
117
tests/test_pack_draft_serve_boundary.py
Normal file
117
tests/test_pack_draft_serve_boundary.py
Normal 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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
214
tests/test_stage2_physics_hardening.py
Normal file
214
tests/test_stage2_physics_hardening.py
Normal 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()
|
||||
166
tests/test_stage3_epistemic_inductive.py
Normal file
166
tests/test_stage3_epistemic_inductive.py
Normal 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: a→b→c→d. Refuse a→c and b→d so the only multi-hop bridge to a→d
|
||||
would be the non-admissible a→c path. Closure must not invent a→d.
|
||||
"""
|
||||
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)
|
||||
58
tests/test_stage3_oov_egress_authority.py
Normal file
58
tests/test_stage3_oov_egress_authority.py
Normal 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
|
||||
87
tests/test_stage3_root_ambiguity.py
Normal file
87
tests/test_stage3_root_ambiguity.py
Normal 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
|
||||
Loading…
Reference in a new issue