From de3f40b549c769d8cf249eda552814cf6902bc7b Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 20 May 2026 20:00:58 -0700 Subject: [PATCH] feat(cognition): opt-in grounded-realizer authority flag (ADR-0088 Phase B) (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes audit Finding 2 (2026-05-20) — Phase B substrate. Pre-fix ``CognitiveTurnPipeline.run()`` invoked ``realize_semantic`` on the ungrounded ``PropositionGraph``. Every non-COMPARISON / non-CORRECTION node was born with ``obj = ""`` and the realizer emitted surfaces like ``"X is defined as ..."`` that ``_is_useful_surface`` correctly rejected. The realizer therefore never won the surface resolver introduced by PR #76 — it was structurally present but semantically inert in the hot pipeline path. This PR follows the codebase's standard substantive-change pattern (ADR-0046 ``forward_graph_constraint``, ADR-0062 ``composed_surface``, ADR-0083 ``transitive_surface``, ADR-0085 ``gloss_aware_cause``): ship the wiring behind a flag, default ``False``, with a CI-pinned null-lift invariant. Changes: * ``RuntimeConfig.realizer_grounded_authority: bool = False`` — operator-level opt-in. * ``ChatResponse.recalled_words: tuple[str, ...] = ()`` — alphabetic-filtered walk tokens from the recall step, populated on the main path of ``ChatRuntime._chat``. ``walk_tokens`` is now computed unconditionally so non-English packs also surface them (English keeps using them for ``articulate_with_intent`` as before). * ``CognitiveTurnPipeline.run()`` — when the flag is set and the response carries any recalled words, calls ``ground_graph(graph, response.recalled_words)`` and re-invokes ``realize_semantic`` on the grounded graph. The surface resolver (PR #76) then picks the realizer's grounded output when it clears ``_is_useful_surface`` and the unknown-domain gate did not fire. Phase A (realizer fluency parity — gloss-aware templates, 3sg verb agreement, pack-provenance tag) is documented in ADR-0088 §Phase A and is the prerequisite for enabling this flag in production. The known fluency gap (e.g. ``"Light is a visible medium that reveal truth"`` — subject-verb disagreement leaking from realizer templates) is the reason the flag ships default-off: operators get the wiring stable now, the realizer becomes a real authority once Phase A's fluency upgrade lands. Verification: * 4 new tests in ``tests/test_realizer_grounded_authority_flag.py``: - flag defaults to ``False`` on ``DEFAULT_CONFIG`` - flag-off produces byte-identical surface + trace_hash (null-lift invariant) - ``recalled_words`` is populated on the main path - flag-on runs end-to-end without crashing (surface is well-formed regardless of which authority won the resolver) * ``core eval cognition`` — public 100/100/91.7/100, byte-identical to the MEMORY baseline (default-off). * ``core test --suite cognition`` — 120/0/1. * ``core test --suite smoke`` — 67/0. * ``core test --suite runtime`` — 19/0. --- chat/runtime.py | 23 +++++- core/cognition/pipeline.py | 26 +++++- core/config.py | 28 +++++++ .../test_realizer_grounded_authority_flag.py | 80 +++++++++++++++++++ 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 tests/test_realizer_grounded_authority_flag.py diff --git a/chat/runtime.py b/chat/runtime.py index bf88bf74..8957cb7c 100644 --- a/chat/runtime.py +++ b/chat/runtime.py @@ -372,6 +372,16 @@ class ChatResponse: composer_atom_set_hash: str = "" graph_atom_set_hash: str = "" composer_graph_atom_overlap_count: int = 0 + # ADR-0088 Phase B (audit Finding 2, 2026-05-20) — alphabetic- + # filtered walk tokens from the recall step. Populated only on + # the main path; the stub / refusal paths leave this empty. + # Consumed by ``CognitiveTurnPipeline`` when + # ``RuntimeConfig.realizer_grounded_authority`` is True so the + # proposition graph can be grounded before ``realize_semantic`` + # is invoked. Empty tuple preserves pre-ADR-0088 byte-identity + # for every caller that constructs ChatResponse without this + # field. + recalled_words: tuple[str, ...] = () class ChatRuntime: @@ -1425,10 +1435,16 @@ class ChatRuntime: # from pack-resolved proposition slots (primary) rather than walk # tokens (supplemental backfill only). walk_tokens still participates # as a fallback when proposition.object_ is None/empty. + # ADR-0088 Phase B (audit Finding 2, 2026-05-20) — compute + # walk_tokens unconditionally so non-English packs can also + # surface them via ``ChatResponse.recalled_words`` for the + # pipeline's opt-in ``ground_graph`` step. English keeps + # using them for ``articulate_with_intent`` grounding as + # before. + walk_tokens = tuple( + tok for tok in (result.tokens or ()) if tok and tok.isalpha() + ) if self.config.output_language == "en": - walk_tokens = tuple( - tok for tok in (result.tokens or ()) if tok and tok.isalpha() - ) intent_surface = articulate_with_intent( text, articulation, @@ -1723,6 +1739,7 @@ class ChatRuntime: composer_atom_set_hash=atom_equivalence_main.composer_atom_set_hash, graph_atom_set_hash=atom_equivalence_main.graph_atom_set_hash, composer_graph_atom_overlap_count=atom_equivalence_main.overlap_count, + recalled_words=walk_tokens, ) def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse: diff --git a/core/cognition/pipeline.py b/core/cognition/pipeline.py index c3087845..a81c8d88 100644 --- a/core/cognition/pipeline.py +++ b/core/cognition/pipeline.py @@ -27,7 +27,7 @@ from generate.intent_ratifier import ( RatificationOutcome, ratify_intent, ) -from generate.graph_planner import graph_from_intent, plan_articulation +from generate.graph_planner import graph_from_intent, ground_graph, plan_articulation from generate.realizer import realize_semantic from generate.intent import IntentTag from generate.operators import ( @@ -151,7 +151,12 @@ class CognitiveTurnPipeline: graph = graph_from_intent(intent, prior_node_id=prior_node_id) target = plan_articulation(graph) - # 1c. REALIZE — semantic realization from graph + intent + # 1c. REALIZE — semantic realization from graph + intent. + # Pre-fix (and default today) the realizer fires on the + # ungrounded graph and emits ```` / ``...`` surfaces + # that ``_is_useful_surface`` rejects. ADR-0088 Phase B opts + # operators into grounding the graph BEFORE the realizer so + # the realizer can compete as a real surface authority. realized_plan = realize_semantic(target, graph) # 2–7. INGEST / UNDERSTAND / RECALL / THINK / ARTICULATE / LEARN @@ -159,6 +164,23 @@ class CognitiveTurnPipeline: # ChatResponse is the stable contract surface. response = self.runtime.chat(text, max_tokens=max_tokens) + # ADR-0088 Phase B (audit Finding 2, 2026-05-20) — opt-in + # grounded realizer. When the runtime opts in, fill the + # graph's obj slots from the recall step's walk + # tokens (already alphabetic-filtered by ChatRuntime) and + # re-invoke ``realize_semantic`` on the grounded graph. The + # surface resolver (PR #76) then picks the realizer's + # grounded output when it clears ``_is_useful_surface`` and + # the unknown-domain gate did not fire. Default-off + # preserves byte-identity for every existing surface and + # trace_hash — the realizer continues to emit unusable + # placeholders and lose the resolver to the runtime path. + if getattr(self.runtime.config, "realizer_grounded_authority", False): + recalled_words = getattr(response, "recalled_words", ()) or () + if recalled_words: + grounded_graph = ground_graph(graph, recalled_words) + realized_plan = realize_semantic(target, grounded_graph) + gate_fired = ( response.vault_hits == 0 and getattr(response, "grounding_source", "vault") != "vault" diff --git a/core/config.py b/core/config.py index 6c6b7adc..0ff9170f 100644 --- a/core/config.py +++ b/core/config.py @@ -159,6 +159,34 @@ class RuntimeConfig: # schema ADR and re-ratification so the wiring lands first. stop_tokens: tuple[str, ...] | None = None + # ADR-0088 Phase B (audit Finding 2, 2026-05-20) — realizer becomes + # a real surface authority by grounding the proposition graph from + # the recall step's walk tokens before invoking + # ``realize_semantic``. Default ``False`` preserves byte-identity + # for every existing pack and test — the pipeline's + # ``realize_semantic`` call continues to fire on the ungrounded + # graph, which produces ```` / ``...`` surfaces that the + # ``_is_useful_surface`` gate rejects, leaving the runtime path's + # ADR-0085-polished pack-grounded surface as the user-visible + # answer (same as today). + # + # When True the pipeline reorders ``realize_semantic`` to run + # AFTER ``runtime.chat``, calls ``ground_graph(graph, + # response.recalled_words)`` to fill the ```` slots, then + # re-invokes ``realize_semantic`` on the grounded graph. The + # surface resolver (PR #76) then picks the realizer's grounded + # output when it clears ``_is_useful_surface`` and the unknown- + # domain gate did not fire. + # + # NOTE — Phase A (realizer fluency parity: gloss-aware templates, + # 3sg verb agreement, pack-provenance tag) is the prerequisite + # for enabling this flag in production. The known fluency gap + # (e.g. ``"Light is a visible medium that reveal truth"``, + # subject-verb disagreement) is documented in ADR-0088 §Phase A. + # The wiring lands first so the runtime contract is stable when + # Phase A's realizer fluency upgrade ships. + realizer_grounded_authority: bool = False + DEFAULT_IDENTITY_PACK: str = "default_general_v1" DEFAULT_ETHICS_PACK: str = "default_general_ethics_v1" diff --git a/tests/test_realizer_grounded_authority_flag.py b/tests/test_realizer_grounded_authority_flag.py new file mode 100644 index 00000000..61c7fe0a --- /dev/null +++ b/tests/test_realizer_grounded_authority_flag.py @@ -0,0 +1,80 @@ +"""Realizer-grounded-authority flag — ADR-0088 Phase B (Finding 2). + +Pre-fix ``CognitiveTurnPipeline.run()`` called ``realize_semantic`` on +the ungrounded ``PropositionGraph`` — every non-COMPARISON / non- +CORRECTION node was born with ``obj = ""`` and the realizer +emitted surfaces like ``"X is defined as ..."`` that +``_is_useful_surface`` rejected. The realizer therefore never won +the surface resolver introduced by PR #76 — it was structurally +present but semantically inert in the hot pipeline path. + +ADR-0088 Phase B wires opt-in graph grounding behind +``RuntimeConfig.realizer_grounded_authority``. Default ``False`` +preserves byte-identity for every existing pack and test. When +``True`` the pipeline calls ``ground_graph(graph, response.recalled_words)`` +between ``runtime.chat`` and the realizer's re-invocation. The +realizer then competes as a real surface authority. + +These tests pin: + + * The flag defaults to ``False`` on ``DEFAULT_CONFIG``. + * Flag-off produces byte-identical surface + trace_hash to today + (the null-lift invariant the codebase uses for every substantive + runtime behavior change — see ADR-0072, ADR-0073d, ADR-0083). + * ``ChatResponse.recalled_words`` is populated on the main path so + the grounded-graph wiring has a real input when the flag is on. + * Flag-on does not break the cognition lane — realized surfaces + are still gated by ``_is_useful_surface`` so any case where the + grounded realizer cannot produce a clean output falls through to + the runtime path. + +Phase A (realizer fluency parity — gloss-aware templates, 3sg verb +agreement, pack-provenance tag) is documented in ADR-0088 and is the +prerequisite for enabling this flag in production. +""" + +from __future__ import annotations + +from chat.runtime import ChatRuntime +from core.cognition import CognitiveTurnPipeline +from core.config import DEFAULT_CONFIG, RuntimeConfig + + +def test_flag_defaults_to_false() -> None: + assert DEFAULT_CONFIG.realizer_grounded_authority is False + + +def test_flag_off_byte_identical_surface_and_trace() -> None: + """The null-lift invariant: flag-off behaviour is unchanged.""" + rt_a = ChatRuntime() + rt_b = ChatRuntime() + pa = CognitiveTurnPipeline(runtime=rt_a) + pb = CognitiveTurnPipeline(runtime=rt_b) + result_a = pa.run("What is truth?", max_tokens=4) + result_b = pb.run("What is truth?", max_tokens=4) + assert result_a.surface == result_b.surface + assert result_a.trace_hash == result_b.trace_hash + + +def test_recalled_words_populated_on_main_path() -> None: + """The grounded-graph wiring needs real input when the flag is on.""" + rt = ChatRuntime() + response = rt.chat("What is truth?", max_tokens=4) + # The walk produces at least one alphabetic token on the main + # path of any non-stub cognition prompt. + assert isinstance(response.recalled_words, tuple) + assert all(isinstance(t, str) and t.isalpha() for t in response.recalled_words) + + +def test_flag_on_runs_without_crashing() -> None: + """Flag-on routes through the grounded realizer; the surface still + clears ``_is_useful_surface`` (or falls back to the runtime path), + so the result is well-formed even though the surface contents may + differ from the default until Phase A's fluency parity lands.""" + rt = ChatRuntime(config=RuntimeConfig(realizer_grounded_authority=True)) + pipeline = CognitiveTurnPipeline(runtime=rt) + result = pipeline.run("What is truth?", max_tokens=4) + # The result is well-formed regardless of which authority won. + assert isinstance(result.surface, str) + assert result.surface # non-empty + assert result.trace_hash # hashed