From 0188af46c0fd8ac92ad3f8aa792cd48cab3b21fd Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 8 Jul 2026 08:02:01 -0700 Subject: [PATCH] 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 - 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. --- chat/runtime.py | 11 +++++++- core/cognition/pipeline.py | 52 +++++++++++++++++++------------------ docs/3lang-depth-pr-plan.md | 8 +++--- tests/conftest.py | 32 +++++++++++------------ 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/chat/runtime.py b/chat/runtime.py index 2f427200..088764e7 100644 --- a/chat/runtime.py +++ b/chat/runtime.py @@ -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 diff --git a/core/cognition/pipeline.py b/core/cognition/pipeline.py index 7920c478..731cea2b 100644 --- a/core/cognition/pipeline.py +++ b/core/cognition/pipeline.py @@ -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, "", "", "") 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, "", "", "") 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 diff --git a/docs/3lang-depth-pr-plan.md b/docs/3lang-depth-pr-plan.md index a0fe4352..244d4743 100644 --- a/docs/3lang-depth-pr-plan.md +++ b/docs/3lang-depth-pr-plan.md @@ -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. diff --git a/tests/conftest.py b/tests/conftest.py index a8ee8d20..b0ba76b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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