fix(3lang-depth): remove is_fully_grounded gate from depth enrichment so result always gets node_depths/gau for 3-lang; fix fixture to use combined packs + explicit skip docs; add runtime docstrings for contract; update draft md evidence
Some checks failed
lane-shas / verify pinned lane SHAs (pull_request) Failing after 2m29s
smoke / smoke (-m "not quarantine") (pull_request) Failing after 4h41m33s

- Fixes skeptic gaps on empty fields in some smokes and fixture mismatch.
- Targeted tests green post-fix.
- Fresh captures have required non-empty data + documented skips.
This commit is contained in:
Shay 2026-07-08 08:02:01 -07:00
parent 5d9e2d0748
commit 0188af46c0
4 changed files with 57 additions and 46 deletions

View file

@ -888,9 +888,16 @@ class ChatRuntime:
self._pending_recognizer_examples.clear()
candidates_to_save = self._pending_candidates
if self.config.auto_contemplate and candidates_to_save:
# 3-lang depth propagation contract (AC5 / review):
# _last_node_depths is written by CognitiveTurnPipeline after PropGraph construction
# (from resolver + build_node_depths on enriched GraphNodes). It is forwarded here
# as depth= into teaching.contemplation.contemplate so that framing findings and
# proposed_chain carry depth_roots for he/grc. Same contract used for runtime
# contemplate and candidate paths. See also core/cognition/result.py (node_depths /
# graph_anti_unify on result) + pipeline.py.
from teaching.contemplation import contemplate
vault_probe = _vault_probe_for_context(self._context) if self._context else None
depth = getattr(self, '_last_node_depths', None) # from pipeline PropGraph depth (3-lang root propagation contract: see pipeline.py + CognitiveTurnResult docstring)
depth = getattr(self, '_last_node_depths', None)
candidates_to_save = [
contemplate(c, vault_probe=vault_probe, depth=depth)
for c in candidates_to_save
@ -972,6 +979,7 @@ class ChatRuntime:
vault_probe = (
_vault_probe_for_context(self._context) if self._context else None
)
# 3-lang depth propagation contract (see checkpoint_engine_state)
depth = getattr(self, '_last_node_depths', None)
contemplated = [
contemplate(candidate, vault_probe=vault_probe, depth=depth)
@ -1542,6 +1550,7 @@ class ChatRuntime:
if self.config.vault_probe_discoveries
else None
)
# 3-lang depth propagation contract (see checkpoint_engine_state)
depth = getattr(self, '_last_node_depths', None)
candidates = tuple(
contemplate(c, vault_probe=vault_probe, depth=depth) for c in candidates

View file

@ -292,13 +292,13 @@ class CognitiveTurnPipeline:
effective_graph = graph
recalled_words = response.recalled_words or ()
# Depth enrichment is now DEFAULT (AC2) for 3-lang mastery on spine.
# Always attempt per-subject resolution + GraphNode depth + node_depths
# for OOV context. The grounded authority flag now only controls the
# recalled_words filling + re-realize step (kept for backward surface compat).
if effective_graph and not effective_graph.is_fully_grounded():
# Generalize to per-subject resolution for multi-node/compound graphs.
# Collect unique subjects, resolve with depth packs for language/root/gloss.
# This feeds both recalled_words (for ground_graph) and per-node enrichment.
# ALWAYS attempt per-subject resolution + GraphNode lang/root enrichment
# (independent of is_fully_grounded) so node_depths / graph_anti_unify
# and result fields are populated for both OOV/pending and grounded 3-lang cases.
# The grounded authority flag only controls the recalled_words fill + re-realize.
# Collect unique subjects, resolve with depth packs for language/root/gloss.
# This feeds both recalled_words (for ground_graph) and per-node enrichment.
if effective_graph:
subjects = []
seen = set()
for n in effective_graph.nodes:
@ -326,26 +326,28 @@ class CognitiveTurnPipeline:
subject_to_res[s] = res
# Collect glosses for pending nodes in order (feeds ground_graph sequentially)
recalled_glosses = []
for n in effective_graph.nodes:
obj = n.obj
if obj in (None, "", "<pending>", "<prior>") or (isinstance(obj, str) and "..." in obj):
s = n.subject.strip().lower()
res = subject_to_res.get(s)
if res and getattr(res, 'gloss', None):
recalled_glosses.append(res.gloss)
elif resolve_lemma(n.subject):
# legacy fallback per-subject
g = resolve_gloss(n.subject)
if g:
_, _, gloss_text = g
if gloss_text:
recalled_glosses.append(gloss_text)
if recalled_glosses:
recalled_words = tuple(recalled_glosses)
# (only when not fully grounded, to preserve prior behavior for gloss fill)
if not effective_graph.is_fully_grounded():
recalled_glosses = []
for n in effective_graph.nodes:
obj = n.obj
if obj in (None, "", "<pending>", "<prior>") or (isinstance(obj, str) and "..." in obj):
s = n.subject.strip().lower()
res = subject_to_res.get(s)
if res and getattr(res, 'gloss', None):
recalled_glosses.append(res.gloss)
elif resolve_lemma(n.subject):
# legacy fallback per-subject
g = resolve_gloss(n.subject)
if g:
_, _, gloss_text = g
if gloss_text:
recalled_glosses.append(gloss_text)
if recalled_glosses:
recalled_words = tuple(recalled_glosses)
# Enrich every node with its subject's resolution (subject→node map)
# Immutable; only rebuild if any depth present.
# Immutable; only rebuild if any depth present. ALWAYS for 3-lang depth support.
if subject_to_res:
new_nodes = []
changed = False

View file

@ -43,14 +43,14 @@ Full feature touched ~15-20 files; see `git diff --name-only c1e723f1..HEAD`.
- This is expected/allowed per plan and compact: "only expected demo drifts".
## 3-lang evidence (Hebrew roots, depths, gau)
- "define אמת" produces node_depths e.g. {'p0': {'language': 'he', 'root': 'א-מ-נ', ...}}
- graph_anti_unify: {'matched_roots': [('p0', 'א-מ-נ')], 'topology': ('p0',)}
- "define אמת" produces on CognitiveTurnResult: node_depths={'p0': {'language': 'he', 'root': 'א-מ-נ', ...}}, graph_anti_unify={'matched_roots': [('p0', 'א-מ-נ')], ...} (also in oov_geometric_context)
- anti-unifier canonicalizes to root for he/grc agent slots (nid-keyed)
- contemplate depth= framing findings contain root
- teaching contemplation attaches depth_roots immutably
- Direct tests: test_anti_unifier_root_aware..., test_pipeline_..., test_graph_anti..., test_depth_canonical..., test_contemplate..., test_teaching...
- Captured in scratch + prior logs: root in proposition, ctx, gau, result top-level fields.
- Direct tests (all under @pytest.mark.requires_depth_packs): the five 3-lang + result fields test
- Captured fresh in scratch: spine_evidence_contemplate.log and _teaching.log contain non-empty node_depths, matched_roots, roots from real pl.run + teach_contemplate; result_fields.log shows the fields on shipped CognitiveTurnResult.
- All via real shipped entry points (ChatRuntime + CognitiveTurnPipeline + contemplate fns), not synth.
- Marker fixture produces explicit documented skips naming the combined DEFAULT+DEPTH list when absent.
## Test plan
- New/updated: marker @pytest.mark.requires_depth_packs + autouse fixture that does explicit skip (documenting DEPTH_PACK_IDS) if not resolvable. No silent no-depth branches for marked tests.

View file

@ -20,32 +20,32 @@ def pytest_configure(config: pytest.Config) -> None:
@pytest.fixture(autouse=True)
def _enforce_depth_packs_if_marked(request: pytest.FixtureRequest) -> None:
"""If test is marked requires_depth_packs, verify at least one DEPTH_PACK can resolve a Hebrew term.
Produces explicit skip (with pack names) rather than silent no-depth execution path.
"""If test is marked requires_depth_packs, verify the combined DEFAULT+DEPTH packs can resolve a Hebrew term.
Produces explicit skip (with documented pack list) rather than silent no-depth execution path.
Matches how tests invoke: DEFAULT_RESOLVABLE_PACK_IDS + DEPTH_PACK_IDS.
"""
if request.node.get_closest_marker("requires_depth_packs"):
try:
from chat.pack_resolver import DEPTH_PACK_IDS, resolve_entry
from chat.pack_resolver import DEFAULT_RESOLVABLE_PACK_IDS, DEPTH_PACK_IDS, resolve_entry
except Exception as e: # pragma: no cover - import guard
pytest.skip(f"requires_depth_packs: pack_resolver unavailable ({e}); packs={DEPTH_PACK_IDS if 'DEPTH_PACK_IDS' in dir() else 'unknown'}")
pytest.skip(f"requires_depth_packs: pack_resolver unavailable ({e}); depth_packs={DEPTH_PACK_IDS if 'DEPTH_PACK_IDS' in dir() else 'unknown'}")
combined = tuple(DEFAULT_RESOLVABLE_PACK_IDS) + tuple(DEPTH_PACK_IDS or ())
if not DEPTH_PACK_IDS:
pytest.skip("requires_depth_packs: DEPTH_PACK_IDS is empty")
pytest.skip(f"requires_depth_packs: DEPTH_PACK_IDS empty (combined={combined})")
# Use a known Hebrew term that the he depth packs should ground for root
# Use a known Hebrew term that the he depth packs should ground for root (same as test code)
term = "אמת"
available = False
for pid in DEPTH_PACK_IDS:
try:
if resolve_entry(term, pack_ids=(pid,)) is not None:
available = True
break
except Exception:
continue
try:
if resolve_entry(term, pack_ids=combined) is not None:
available = True
except Exception:
pass
if not available:
pytest.skip(
f"requires_depth_packs: none of DEPTH_PACK_IDS={DEPTH_PACK_IDS} "
f"resolvable for term='{term}' (he/grc depth packs not mounted or not providing root data)"
f"requires_depth_packs: combined packs {combined} not resolvable for term='{term}' "
"(he/grc depth packs not mounted or not providing root data in DEFAULT+DEPTH)"
)
# else: proceed to run the test fully with real depth data
# else: proceed to run the test fully with real depth data from combined packs