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:
parent
133a1a3e1c
commit
de3f40b549
4 changed files with 152 additions and 5 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
# 2–7. INGEST / UNDERSTAND / RECALL / THINK / ARTICULATE / LEARN
|
# 2–7. 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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
80
tests/test_realizer_grounded_authority_flag.py
Normal file
80
tests/test_realizer_grounded_authority_flag.py
Normal 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
|
||||||
Loading…
Reference in a new issue