feat(cognition): Stage 3 geometric coherence, inductive closure, OOV pins
Complete Stage 3 residual gates on live pipeline authority: - GeometricCoherenceVerdict distinguishes field-closed vs unverified without inventing EpistemicState.COHERENT (dual taxonomy ownership doc). - Bounded expand_relation_closure over teaching-store triples with budget, cycle safety, base multi-tail contradictions, replayable provenance in operator_invocation / trace_hash. - OOV/egress authority tests: conformal neighbors context, cga_inner nearest, vault_hits not surface gate. [Verification]: Stage 3 unit 18 passed; pipeline regression 27 passed; smoke 180 passed
This commit is contained in:
parent
aaa8503f0b
commit
f9f94d8df8
7 changed files with 578 additions and 3 deletions
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",
|
||||||
|
)
|
||||||
223
core/cognition/inductive_closure.py
Normal file
223
core/cognition/inductive_closure.py
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
"""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, 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 authority for field identity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Iterable, Sequence
|
||||||
|
|
||||||
|
_DEFAULT_BUDGET = 16
|
||||||
|
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def expand_relation_closure(
|
||||||
|
triples: Sequence[tuple[str, str, str]],
|
||||||
|
*,
|
||||||
|
budget: int = _DEFAULT_BUDGET,
|
||||||
|
relations: Iterable[str] | 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)
|
||||||
|
at the same fixed-point layer mark both as contradiction=True
|
||||||
|
(functional assumption for same-relation edges).
|
||||||
|
* Termination: no new triples or budget exhausted.
|
||||||
|
|
||||||
|
Geometric admissibility of *field* atoms is enforced by callers that
|
||||||
|
map surfaces through ``_proof_atom`` before using derived triples as
|
||||||
|
proof premises; this expander is total over the teaching-store graph.
|
||||||
|
"""
|
||||||
|
if budget < 1:
|
||||||
|
raise ValueError("budget must be >= 1")
|
||||||
|
|
||||||
|
rel_filter = None if relations is None else {_norm(r) for r in relations}
|
||||||
|
|
||||||
|
# Base
|
||||||
|
base_list: list[DerivedRelation] = []
|
||||||
|
# key (h,r) -> set of tails for contradiction detection
|
||||||
|
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_list.append(
|
||||||
|
DerivedRelation(
|
||||||
|
head=hn,
|
||||||
|
relation=rn,
|
||||||
|
tail=tn,
|
||||||
|
path=(hn, tn),
|
||||||
|
step=0,
|
||||||
|
admissible=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mark base contradictions
|
||||||
|
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] = []
|
||||||
|
# Working set of edges as (h,r,t) for composition
|
||||||
|
work = set(known)
|
||||||
|
steps_taken = 0
|
||||||
|
fixed_point = False
|
||||||
|
truncated = False
|
||||||
|
|
||||||
|
for step in range(1, budget + 1):
|
||||||
|
steps_taken = step
|
||||||
|
new_edges: list[DerivedRelation] = []
|
||||||
|
# Index tails by (h,r)
|
||||||
|
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 # cycle / identity
|
||||||
|
key3 = (a, r, c)
|
||||||
|
if key3 in work:
|
||||||
|
continue
|
||||||
|
# path reconstruction (bounded)
|
||||||
|
path = (a, b, c)
|
||||||
|
new_edges.append(
|
||||||
|
DerivedRelation(
|
||||||
|
head=a,
|
||||||
|
relation=r,
|
||||||
|
tail=c,
|
||||||
|
path=path,
|
||||||
|
step=step,
|
||||||
|
admissible=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not new_edges:
|
||||||
|
fixed_point = True
|
||||||
|
steps_taken = step - 1 if step > 0 else 0
|
||||||
|
break
|
||||||
|
|
||||||
|
# Dedup new edges. Transitive multi-tails for the same (head, relation)
|
||||||
|
# are *not* contradictions — only base multi-tails (step 0) mark
|
||||||
|
# functional conflicts (recorded once above).
|
||||||
|
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:
|
||||||
|
# Budget exhausted without fixed point
|
||||||
|
truncated = True
|
||||||
|
# Check if more edges would exist
|
||||||
|
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.backend import versor_condition
|
||||||
from algebra.cl41 import geometric_product, reverse, scalar_part
|
from algebra.cl41 import geometric_product, reverse, scalar_part
|
||||||
from field.state import FieldState
|
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.leeway import build_leeway_record
|
||||||
from core.cognition.result import CognitiveTurnResult
|
from core.cognition.result import CognitiveTurnResult
|
||||||
from core.cognition.surface_resolution import resolve_surface
|
from core.cognition.surface_resolution import resolve_surface
|
||||||
|
|
@ -439,6 +441,10 @@ class CognitiveTurnPipeline:
|
||||||
):
|
):
|
||||||
compose_surface = CognitiveTurnPipeline._render_compose_surface(compose_result)
|
compose_surface = CognitiveTurnPipeline._render_compose_surface(compose_result)
|
||||||
|
|
||||||
|
# Stage 3C — bounded inductive closure over teaching-store relations.
|
||||||
|
# Provenance-preserving fixed-point; folded into operator_invocation only.
|
||||||
|
inductive_closure = expand_relation_closure(triples, budget=16)
|
||||||
|
|
||||||
entailment_trace = self._maybe_entailment_trace(intent, triples)
|
entailment_trace = self._maybe_entailment_trace(intent, triples)
|
||||||
|
|
||||||
# === SHADOW COHERENCE GATE WIRING ===
|
# === SHADOW COHERENCE GATE WIRING ===
|
||||||
|
|
@ -645,10 +651,34 @@ class CognitiveTurnPipeline:
|
||||||
entailment_serialised = CognitiveTurnPipeline._serialize_entailment_trace(
|
entailment_serialised = CognitiveTurnPipeline._serialize_entailment_trace(
|
||||||
entailment_trace
|
entailment_trace
|
||||||
)
|
)
|
||||||
# Deterministic concatenation: walk, compose, then entailment. Empty
|
# Stage 3C — inductive fixed-point provenance (before hash so replay
|
||||||
# strings are dropped so unaffected turns keep existing trace bytes.
|
# 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(
|
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
|
if s
|
||||||
)
|
)
|
||||||
# ADR-0023 — admissibility trace + ratification provenance.
|
# ADR-0023 — admissibility trace + ratification provenance.
|
||||||
|
|
@ -724,6 +754,15 @@ class CognitiveTurnPipeline:
|
||||||
license_decision=getattr(accrual, "license", None),
|
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(
|
return CognitiveTurnResult(
|
||||||
input_text=text,
|
input_text=text,
|
||||||
input_tokens=raw_tokens,
|
input_tokens=raw_tokens,
|
||||||
|
|
@ -762,6 +801,7 @@ class CognitiveTurnPipeline:
|
||||||
dispatch_trace=getattr(response, "dispatch_trace", None),
|
dispatch_trace=getattr(response, "dispatch_trace", None),
|
||||||
dropped_compound_clauses=dropped_compound_clauses,
|
dropped_compound_clauses=dropped_compound_clauses,
|
||||||
versor_condition=response.versor_condition,
|
versor_condition=response.versor_condition,
|
||||||
|
geometric_coherence=geometric_coherence,
|
||||||
trace_hash=trace_hash,
|
trace_hash=trace_hash,
|
||||||
leeway=leeway,
|
leeway=leeway,
|
||||||
# Phase A — Shadow Coherence Gate observability.
|
# Phase A — Shadow Coherence Gate observability.
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from core.cognition.geometric_coherence import GeometricCoherenceVerdict
|
||||||
from core.cognition.leeway import LeewayRecord
|
from core.cognition.leeway import LeewayRecord
|
||||||
from field.state import FieldState
|
from field.state import FieldState
|
||||||
from generate.articulation import ArticulationPlan
|
from generate.articulation import ArticulationPlan
|
||||||
|
|
@ -150,6 +151,9 @@ class CognitiveTurnResult:
|
||||||
|
|
||||||
# --- invariant bookkeeping ---
|
# --- invariant bookkeeping ---
|
||||||
versor_condition: float = 0.0 # must be < 1e-6
|
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
|
trace_hash: str = "" # SHA-256 over deterministic key fields
|
||||||
|
|
||||||
# --- response-governance leeway evidence (B4; observational, not in trace_hash) ---
|
# --- response-governance leeway evidence (B4; observational, not in trace_hash) ---
|
||||||
|
|
|
||||||
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.
|
||||||
106
tests/test_stage3_epistemic_inductive.py
Normal file
106
tests/test_stage3_epistemic_inductive.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
"""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"),
|
||||||
|
)
|
||||||
|
res = expand_relation_closure(triples, budget=8)
|
||||||
|
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
|
||||||
|
# Provenance path
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
def test_inductive_closure_detects_contradiction():
|
||||||
|
triples = (
|
||||||
|
("a", "is", "b"),
|
||||||
|
("a", "is", "c"),
|
||||||
|
)
|
||||||
|
res = expand_relation_closure(triples, budget=4)
|
||||||
|
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))
|
||||||
|
res = expand_relation_closure(triples, budget=2)
|
||||||
|
assert res.truncated or res.steps_taken <= 2
|
||||||
|
# With larger budget, multi-hop appears
|
||||||
|
res2 = expand_relation_closure(triples, budget=16)
|
||||||
|
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)
|
||||||
|
# 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
|
||||||
Loading…
Reference in a new issue