R5 (ADR-0072) shipped the register *machinery*; ADR-0074's orthogonality tour proved the axis was decoratively orthogonal to anchor-lens but inspection of the cognition-eval surfaces revealed two structural gaps: * On pack-grounded DEFINITION/RECALL/COMPARISON composers, the only realizer override any register consumed was `disclosure_domain_count` — which only fires on the no-gloss disclosure path. Under terse_v1, every gloss-DEFINITION cell was byte-identical to default_neutral_v1. * The register-tour's `surfaces_vary_at_least_once` gate could be satisfied by convivial's decorative wrapper alone, masking that regression in CI. R6 closes both: Layering separation (the load-bearing fix): * New TurnEvent/ChatResponse field `register_canonical_surface` carries the composer output BEFORE any register transformation. The pipeline hashes this field for `trace_hash`, preserving R5's invariant that per-prompt trace_hash is CONSTANT across registers even while substantive transforms produce visibly different surfaces. Substantive transforms (`chat/register_substantive.py`): * terse_v1 gains 3 bool knobs: `drop_provenance_tag`, `compress_gloss`, `drop_articles` — all pure regex transforms on the canonical surface. * convivial_v1 gains `append_semantic_domain_clause` — appends a single bounded "Related: <atom>." clause using the lemma's pack atoms. * default_neutral_v1 leaves overrides empty; substantive transform is byte-identical no-op (preserves `byte_identity_null_lift`). * C1 (ADR-0075) safety preserved: drop_articles refuses to drop articles following `not` (avoids R3 violations); no knob combination trips R2/R3. Strengthened tour gate (`evals/register_tour/run_tour.py`): * Replaces `surfaces_vary_at_least_once` with two falsifiable claims: - `terse_substantively_differs_from_neutral_on_pack_grounded_definition` - `convivial_substantively_differs_from_neutral_on_pack_grounded_definition` Both restrict to DEFINITION+pack-grounded cells and require difference beyond whitespace/punctuation. * New claim `register_canonical_surfaces_identical` directly proves the layering separation. * Preserves R5's `all_grounding_sources_identical` + `all_trace_hashes_identical`. Pack ratification: * Loader widened to accept `bool` for closed-set R6 keys (drop_provenance_tag / compress_gloss / drop_articles / append_semantic_domain_clause). * `_KNOWN_OVERRIDE_KEYS` ratify gate extended with same. * terse_v1 + convivial_v1 reratified with new knobs; companion mastery reports re-sealed. default_neutral_v1 unchanged. Invariants pinned: * `invariant_register_canonical_surface_constant_across_registers` (new) * `invariant_terse_substantively_distinct_from_neutral` (new) * `invariant_convivial_substantively_distinct_from_neutral` (new) * `invariant_realizer_no_illegal_articulation` (C1, preserved) * `invariant_realizer_guard_byte_identity_on_currently_passing_cases` (C1, preserved) Verification: * `core eval cognition`: 100.0% / 91.7% / 100.0% / 100.0% — byte- identical under default_neutral_v1. * `core demo register-tour`: all 5 claims green, exit 0. * `core demo anchor-lens-tour`: green (no anchor-lens code touched). * `core demo orthogonality-tour`: green (5/5 claims). * Full lane: 2858 passed, 1 pre-existing failure (test_all_preamble_explains_combined_run, carried forward unchanged from main). 56 new R6 tests across three files.
437 lines
14 KiB
Python
437 lines
14 KiB
Python
"""ADR-0077 (R6) — per-knob unit + end-to-end coverage of
|
|
``chat/register_substantive.py``.
|
|
|
|
Two test families:
|
|
|
|
* Pure-unit tests against synthetic canonical surfaces — assert each
|
|
knob is the deterministic string transform documented in the ADR,
|
|
with no dependency on the runtime.
|
|
* End-to-end tests run a real ChatRuntime under each ratified
|
|
register and assert the user-facing surface matches the post-
|
|
substantive expectation.
|
|
|
|
C1 (ADR-0075) preservation: every R6 knob combination must produce a
|
|
surface that the realizer guard accepts. This module pins that
|
|
contract by running the guard against every produced surface.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Mapping
|
|
|
|
import pytest
|
|
|
|
from chat.register_substantive import apply_substantive_register
|
|
from chat.runtime import ChatRuntime
|
|
from core.cognition.pipeline import CognitiveTurnPipeline
|
|
from core.config import RuntimeConfig
|
|
from generate.realizer_guard import check_surface
|
|
from packs.register.loader import RegisterPack, load_register_pack
|
|
|
|
|
|
# ---------- Fake register for synthetic unit coverage ----------
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _FakeMarkers:
|
|
openings: tuple[str, ...] = ()
|
|
transitions: tuple[str, ...] = ()
|
|
closings: tuple[str, ...] = ()
|
|
|
|
def is_empty(self) -> bool:
|
|
return True
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _FakeRegister:
|
|
"""Minimal duck-type that ``apply_substantive_register`` accepts.
|
|
|
|
Only ``realizer_overrides`` is read in the substantive path.
|
|
"""
|
|
|
|
realizer_overrides: Mapping[str, object] = field(default_factory=dict)
|
|
discourse_markers: _FakeMarkers = field(default_factory=_FakeMarkers)
|
|
|
|
|
|
def _r(overrides: Mapping[str, object]) -> _FakeRegister:
|
|
return _FakeRegister(realizer_overrides=dict(overrides))
|
|
|
|
|
|
_DEF_CANONICAL = (
|
|
"Light is a source of revelation that makes things knowable. "
|
|
"pack-grounded (en_core_cognition_v1)."
|
|
)
|
|
_DEF_CANONICAL_LENS = (
|
|
"Light is a source of revelation that makes things knowable. "
|
|
"pack-grounded (en_core_cognition_v1) [lens(grc_logos_v1):systematic]."
|
|
)
|
|
_CMP_CANONICAL = (
|
|
"light (cognition.illumination; logos.core) contrasts with "
|
|
"truth (cognition.truth; logos.core) — pack-grounded "
|
|
"(en_core_cognition_v1). No session evidence yet."
|
|
)
|
|
_DOT_CANONICAL = (
|
|
"light — pack-grounded (en_core_cognition_v1): "
|
|
"cognition.illumination; logos.core; cognition.knowledge. "
|
|
"No session evidence yet."
|
|
)
|
|
|
|
|
|
# ---------- No-op contract ----------
|
|
|
|
|
|
def test_empty_overrides_is_byte_identical():
|
|
"""The default register (empty overrides) must be a no-op."""
|
|
out = apply_substantive_register(_DEF_CANONICAL, _r({}))
|
|
assert out == _DEF_CANONICAL
|
|
|
|
|
|
def test_all_knobs_false_is_byte_identical():
|
|
"""Explicit False values must also be a no-op — the ``.get(key)``
|
|
lookup returns ``False`` which is falsy."""
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL,
|
|
_r({
|
|
"drop_provenance_tag": False,
|
|
"compress_gloss": False,
|
|
"drop_articles": False,
|
|
"append_semantic_domain_clause": False,
|
|
}),
|
|
)
|
|
assert out == _DEF_CANONICAL
|
|
|
|
|
|
def test_empty_canonical_is_byte_identical():
|
|
out = apply_substantive_register("", _r({"drop_provenance_tag": True}))
|
|
assert out == ""
|
|
|
|
|
|
# ---------- drop_provenance_tag ----------
|
|
|
|
|
|
def test_drop_provenance_strips_trailing_gloss_clause():
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL, _r({"drop_provenance_tag": True}),
|
|
)
|
|
assert "pack-grounded" not in out
|
|
assert out.endswith("knowable.")
|
|
assert "..," not in out and ".." not in out # no double-period bug
|
|
|
|
|
|
def test_drop_provenance_preserves_lens_annotation():
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL_LENS, _r({"drop_provenance_tag": True}),
|
|
)
|
|
assert "pack-grounded" not in out
|
|
assert "[lens(grc_logos_v1):systematic]" in out
|
|
|
|
|
|
def test_drop_provenance_strips_infix_em_dash_form():
|
|
out = apply_substantive_register(
|
|
_CMP_CANONICAL, _r({"drop_provenance_tag": True}),
|
|
)
|
|
assert "pack-grounded" not in out
|
|
# Em-dash before the provenance is consumed by the infix regex.
|
|
assert " — pack-grounded" not in out
|
|
assert "contrasts with truth" in out
|
|
assert "No session evidence yet" in out
|
|
|
|
|
|
def test_drop_provenance_strips_dotted_disclosure_form():
|
|
out = apply_substantive_register(
|
|
_DOT_CANONICAL, _r({"drop_provenance_tag": True}),
|
|
)
|
|
assert "pack-grounded" not in out
|
|
assert out.startswith("light:") # em-dash + provenance gone; colon kept
|
|
|
|
|
|
# ---------- compress_gloss ----------
|
|
|
|
|
|
def test_compress_gloss_replaces_is_a():
|
|
out = apply_substantive_register(
|
|
"Light is a source of revelation.", _r({"compress_gloss": True}),
|
|
)
|
|
assert out == "Light: source of revelation."
|
|
|
|
|
|
def test_compress_gloss_replaces_is_an():
|
|
out = apply_substantive_register(
|
|
"Answer is an example of speech.", _r({"compress_gloss": True}),
|
|
)
|
|
assert out == "Answer: example of speech."
|
|
|
|
|
|
def test_compress_gloss_replaces_is_the():
|
|
out = apply_substantive_register(
|
|
"Beginning is the origin point.", _r({"compress_gloss": True}),
|
|
)
|
|
assert out == "Beginning: origin point."
|
|
|
|
|
|
def test_compress_gloss_replaces_bare_is():
|
|
out = apply_substantive_register(
|
|
"Cause is that which produces effect.",
|
|
_r({"compress_gloss": True}),
|
|
)
|
|
assert out == "Cause: that which produces effect."
|
|
|
|
|
|
def test_compress_gloss_only_first_occurrence():
|
|
"""Multiple ``is`` tokens must not all collapse — only the gloss
|
|
opener. Mangling a subordinate clause's ``is`` would be a bug."""
|
|
out = apply_substantive_register(
|
|
"X is a Y. The reason is clear.", _r({"compress_gloss": True}),
|
|
)
|
|
assert out == "X: Y. The reason is clear."
|
|
|
|
|
|
def test_compress_gloss_noop_on_comparison():
|
|
"""COMPARISON form has no ``is`` — must be byte-identical."""
|
|
out = apply_substantive_register(
|
|
_CMP_CANONICAL, _r({"compress_gloss": True}),
|
|
)
|
|
assert out == _CMP_CANONICAL
|
|
|
|
|
|
# ---------- drop_articles ----------
|
|
|
|
|
|
def test_drop_articles_strips_mid_sentence_a():
|
|
out = apply_substantive_register(
|
|
"Light reveals a truth in this case.", _r({"drop_articles": True}),
|
|
)
|
|
assert out == "Light reveals truth in this case."
|
|
|
|
|
|
def test_drop_articles_strips_mid_sentence_the():
|
|
out = apply_substantive_register(
|
|
"Light reveals the truth.", _r({"drop_articles": True}),
|
|
)
|
|
assert out == "Light reveals truth."
|
|
|
|
|
|
def test_drop_articles_preserves_article_after_not():
|
|
"""C1 R3 safety: must not collapse ``is not a claim`` to ``is not
|
|
claim`` because R3 fires when a be-negation is followed by a VERB.
|
|
``claim`` is a NOUN so R3 wouldn't fire anyway, but the lookbehind
|
|
guards us against a more dangerous case in the future."""
|
|
out = apply_substantive_register(
|
|
"Light is not a claim.", _r({"drop_articles": True}),
|
|
)
|
|
assert out == "Light is not a claim."
|
|
|
|
|
|
def test_drop_articles_collapses_double_space_artifact():
|
|
"""Adjacent removals must not leave a double space in the output."""
|
|
out = apply_substantive_register(
|
|
"Walks the path of a sage.", _r({"drop_articles": True}),
|
|
)
|
|
assert " " not in out
|
|
assert out == "Walks path of sage."
|
|
|
|
|
|
# ---------- append_semantic_domain_clause ----------
|
|
|
|
|
|
def test_append_picks_lex_first_unused_atom():
|
|
out = apply_substantive_register(
|
|
"Light is the source.",
|
|
_r({"append_semantic_domain_clause": True}),
|
|
semantic_domains=("logos.core", "cognition.illumination"),
|
|
)
|
|
# Sorted atoms: ("cognition.illumination", "logos.core").
|
|
# Both are absent from canonical, so the first lex-sorted wins.
|
|
assert out.endswith(" Related: cognition.illumination.")
|
|
|
|
|
|
def test_append_skips_atom_already_in_canonical():
|
|
out = apply_substantive_register(
|
|
"Light is a source in cognition.illumination.",
|
|
_r({"append_semantic_domain_clause": True}),
|
|
semantic_domains=("cognition.illumination", "logos.core"),
|
|
)
|
|
assert out.endswith(" Related: logos.core.")
|
|
|
|
|
|
def test_append_noop_when_all_atoms_already_used():
|
|
canonical = "Light: covers logos.core cognition.illumination."
|
|
out = apply_substantive_register(
|
|
canonical,
|
|
_r({"append_semantic_domain_clause": True}),
|
|
semantic_domains=("cognition.illumination", "logos.core"),
|
|
)
|
|
assert out == canonical
|
|
|
|
|
|
def test_append_noop_when_no_atoms_supplied():
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL,
|
|
_r({"append_semantic_domain_clause": True}),
|
|
semantic_domains=(),
|
|
)
|
|
assert out == _DEF_CANONICAL
|
|
|
|
|
|
# ---------- Knob composition (terse_v1 combined) ----------
|
|
|
|
|
|
def test_terse_full_combo_def_form():
|
|
"""compress_gloss + drop_provenance_tag + drop_articles together
|
|
on the canonical DEFINITION form."""
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL,
|
|
_r({
|
|
"compress_gloss": True,
|
|
"drop_provenance_tag": True,
|
|
"drop_articles": True,
|
|
}),
|
|
)
|
|
# compress: "Light is a source ... knowable." -> "Light: source ... knowable."
|
|
# drop_articles: removes leftover "a/the" articles (none in this gloss).
|
|
# drop_provenance_tag: trailing pack-grounded clause removed.
|
|
assert out == "Light: source of revelation that makes things knowable."
|
|
|
|
|
|
def test_terse_full_combo_with_lens():
|
|
"""Lens annotation must survive the terse combo."""
|
|
out = apply_substantive_register(
|
|
_DEF_CANONICAL_LENS,
|
|
_r({
|
|
"compress_gloss": True,
|
|
"drop_provenance_tag": True,
|
|
"drop_articles": True,
|
|
}),
|
|
)
|
|
assert "[lens(grc_logos_v1):systematic]" in out
|
|
assert "pack-grounded" not in out
|
|
|
|
|
|
# ---------- Guard safety (C1 preservation) ----------
|
|
|
|
|
|
def _trivial_pos_lookup(_token: str) -> None:
|
|
"""Pack-less POS lookup — returns None for every token. The C1
|
|
guard then fails-open on every unknown token, which is what we
|
|
want for synthetic unit assertions.
|
|
"""
|
|
return None
|
|
|
|
|
|
@pytest.mark.parametrize("knobs", [
|
|
{"compress_gloss": True},
|
|
{"drop_provenance_tag": True},
|
|
{"drop_articles": True},
|
|
{"append_semantic_domain_clause": True},
|
|
{
|
|
"compress_gloss": True,
|
|
"drop_provenance_tag": True,
|
|
"drop_articles": True,
|
|
},
|
|
])
|
|
def test_every_knob_combination_passes_guard(knobs):
|
|
"""invariant_realizer_no_illegal_articulation preservation —
|
|
no R6 knob combination produces a surface that trips R2/R3."""
|
|
domains = ("cognition.illumination", "logos.core")
|
|
for canonical in (_DEF_CANONICAL, _DEF_CANONICAL_LENS, _CMP_CANONICAL, _DOT_CANONICAL):
|
|
out = apply_substantive_register(
|
|
canonical, _r(knobs), semantic_domains=domains,
|
|
)
|
|
v = check_surface(out, pos_lookup=_trivial_pos_lookup)
|
|
assert v.status == "ok", (
|
|
f"knobs={knobs} produced guard-rejected surface "
|
|
f"{out!r} from canonical {canonical!r}: {v}"
|
|
)
|
|
|
|
|
|
# ---------- End-to-end against real packs + runtime ----------
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def neutral_def_surface() -> str:
|
|
rt = ChatRuntime(config=RuntimeConfig(register_pack_id="default_neutral_v1"))
|
|
pipe = CognitiveTurnPipeline(runtime=rt)
|
|
pipe.run("What is light?")
|
|
return rt.turn_log[-1].surface
|
|
|
|
|
|
def test_e2e_terse_drops_provenance_tag():
|
|
rt = ChatRuntime(config=RuntimeConfig(register_pack_id="terse_v1"))
|
|
pipe = CognitiveTurnPipeline(runtime=rt)
|
|
pipe.run("What is light?")
|
|
te = rt.turn_log[-1]
|
|
assert "pack-grounded" not in te.surface
|
|
assert te.register_canonical_surface != ""
|
|
assert "pack-grounded" in te.register_canonical_surface
|
|
|
|
|
|
def test_e2e_terse_compresses_gloss():
|
|
rt = ChatRuntime(config=RuntimeConfig(register_pack_id="terse_v1"))
|
|
pipe = CognitiveTurnPipeline(runtime=rt)
|
|
pipe.run("What is light?")
|
|
te = rt.turn_log[-1]
|
|
assert "Light: " in te.surface
|
|
# Gloss compression strips " is a "; raw " is a " must NOT survive
|
|
# in the user-facing surface under terse.
|
|
assert " is a " not in te.surface
|
|
|
|
|
|
def test_e2e_terse_drops_articles():
|
|
rt = ChatRuntime(config=RuntimeConfig(register_pack_id="terse_v1"))
|
|
pipe = CognitiveTurnPipeline(runtime=rt)
|
|
pipe.run("What is light?")
|
|
te = rt.turn_log[-1]
|
|
# Should not contain standalone mid-sentence articles in the
|
|
# compressed surface. ``" a "`` and ``" the "`` are mid-sentence
|
|
# markers we expect to be absent in the compressed gloss.
|
|
assert " a " not in te.surface
|
|
assert " the " not in te.surface
|
|
|
|
|
|
def test_e2e_convivial_appends_related_clause(neutral_def_surface: str):
|
|
rt = ChatRuntime(config=RuntimeConfig(register_pack_id="convivial_v1"))
|
|
pipe = CognitiveTurnPipeline(runtime=rt)
|
|
pipe.run("What is light?")
|
|
te = rt.turn_log[-1]
|
|
assert "Related: " in te.surface
|
|
# The Related clause must be derived from a pack atom — atoms in
|
|
# the cognition pack are dotted ``domain.subdomain`` form (e.g.
|
|
# ``cognition.illumination``, ``logos.core``). Match a dotted
|
|
# atom inside the Related clause specifically.
|
|
import re
|
|
m = re.search(r"Related: ([\w]+\.[\w.]+)\.", te.surface)
|
|
assert m is not None, f"no dotted atom in Related clause: {te.surface!r}"
|
|
assert "." in m.group(1)
|
|
|
|
|
|
def test_e2e_neutral_unchanged_by_r6(neutral_def_surface: str):
|
|
"""default_neutral_v1 has no R6 knobs → surface must be identical
|
|
to the pre-R6 byte-for-byte expected gloss."""
|
|
expected = (
|
|
"Light is a source of revelation that makes things knowable. "
|
|
"pack-grounded (en_core_cognition_v1)."
|
|
)
|
|
assert neutral_def_surface == expected
|
|
|
|
|
|
# ---------- Pack-loaded knob schema ----------
|
|
|
|
|
|
@pytest.mark.parametrize("register_id,expected_keys", [
|
|
("default_neutral_v1", set()),
|
|
(
|
|
"terse_v1",
|
|
{"compress_gloss", "disclosure_domain_count", "drop_articles",
|
|
"drop_provenance_tag"},
|
|
),
|
|
("convivial_v1", {"append_semantic_domain_clause"}),
|
|
])
|
|
def test_register_pack_carries_expected_r6_keys(
|
|
register_id: str, expected_keys: set[str],
|
|
):
|
|
"""Pin the on-disk pack contents so future re-ratification can't
|
|
silently drop the R6 knobs."""
|
|
pack = load_register_pack(register_id)
|
|
actual_keys = set(pack.realizer_overrides.keys())
|
|
assert actual_keys == expected_keys
|