feat(cognition): opt-in grounded-realizer authority flag (ADR-0088 Phase B) (#88)

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 = "<pending>"`` 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.
This commit is contained in:
Shay 2026-05-20 20:00:58 -07:00 committed by GitHub
parent 133a1a3e1c
commit de3f40b549
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 152 additions and 5 deletions

View file

@ -372,6 +372,16 @@ class ChatResponse:
composer_atom_set_hash: str = "" composer_atom_set_hash: str = ""
graph_atom_set_hash: str = "" graph_atom_set_hash: str = ""
composer_graph_atom_overlap_count: int = 0 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: class ChatRuntime:
@ -1425,10 +1435,16 @@ class ChatRuntime:
# from pack-resolved proposition slots (primary) rather than walk # from pack-resolved proposition slots (primary) rather than walk
# tokens (supplemental backfill only). walk_tokens still participates # tokens (supplemental backfill only). walk_tokens still participates
# as a fallback when proposition.object_ is None/empty. # 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": 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( intent_surface = articulate_with_intent(
text, text,
articulation, articulation,
@ -1723,6 +1739,7 @@ class ChatRuntime:
composer_atom_set_hash=atom_equivalence_main.composer_atom_set_hash, composer_atom_set_hash=atom_equivalence_main.composer_atom_set_hash,
graph_atom_set_hash=atom_equivalence_main.graph_atom_set_hash, graph_atom_set_hash=atom_equivalence_main.graph_atom_set_hash,
composer_graph_atom_overlap_count=atom_equivalence_main.overlap_count, 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: def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse:

View file

@ -27,7 +27,7 @@ from generate.intent_ratifier import (
RatificationOutcome, RatificationOutcome,
ratify_intent, 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.realizer import realize_semantic
from generate.intent import IntentTag from generate.intent import IntentTag
from generate.operators import ( from generate.operators import (
@ -151,7 +151,12 @@ class CognitiveTurnPipeline:
graph = graph_from_intent(intent, prior_node_id=prior_node_id) graph = graph_from_intent(intent, prior_node_id=prior_node_id)
target = plan_articulation(graph) 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 ``<pending>`` / ``...`` 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) realized_plan = realize_semantic(target, graph)
# 27. INGEST / UNDERSTAND / RECALL / THINK / ARTICULATE / LEARN # 27. INGEST / UNDERSTAND / RECALL / THINK / ARTICULATE / LEARN
@ -159,6 +164,23 @@ class CognitiveTurnPipeline:
# ChatResponse is the stable contract surface. # ChatResponse is the stable contract surface.
response = self.runtime.chat(text, max_tokens=max_tokens) 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 <pending> 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 = ( gate_fired = (
response.vault_hits == 0 response.vault_hits == 0
and getattr(response, "grounding_source", "vault") != "vault" and getattr(response, "grounding_source", "vault") != "vault"

View file

@ -159,6 +159,34 @@ class RuntimeConfig:
# schema ADR and re-ratification so the wiring lands first. # schema ADR and re-ratification so the wiring lands first.
stop_tokens: tuple[str, ...] | None = None 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 ``<pending>`` / ``...`` 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 ``<pending>`` 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_IDENTITY_PACK: str = "default_general_v1"
DEFAULT_ETHICS_PACK: str = "default_general_ethics_v1" DEFAULT_ETHICS_PACK: str = "default_general_ethics_v1"

View file

@ -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 = "<pending>"`` 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