ADR-0072 ratified + implemented. Closes the register subsystem
inside-out arc (R1 ADR-0068 → R5 ADR-0072): the presentation axis is
now operator-visible, CI-falsifiable, and audit-traceable.
Telemetry extension
- TurnEvent + ChatResponse gain register_id + register_variant_id
(12-char SHA-256 prefix of selected (opening, closing) pair;
empty string for UNREGISTERED / no-decoration registers).
- serialize_turn_event surfaces both fields in every audit JSONL
line. Pre-R5 callers stay byte-identical (defaults are "").
Decoration result widened
- chat/register_variation.py: decorate_surface now returns
DecorationResult(surface, opening, closing, variant_id).
- decorate_surface_str alias preserves the pre-R5 string-only API
for off-runtime callers.
- chat/runtime.py updated at both call sites (stub + main).
Operator surface
- core chat --register REGISTER_ID threads into
RuntimeConfig.register_pack_id via _runtime_config_from_args.
- Invalid id ⇒ RegisterPackError caught at cmd_chat and surfaced
as a clean _die(...) before the REPL launches.
Narrative demo
- evals/register_tour/run_tour.py walks 4 prompts × 3 ratified
registers ({default_neutral_v1, terse_v1, convivial_v1}) and
asserts three load-bearing seam claims:
* all_grounding_sources_identical
* all_trace_hashes_identical (ADR-0069 invariant C, falsifiable)
* surfaces_vary_at_least_once (ADR-0071 seeded variation lift)
- core demo register-tour exit code = 0 iff every claim holds.
Tests
- tests/test_register_telemetry.py (6) — TurnEvent default,
serializer keys, runtime emits register_id/variant_id for
convivial/terse/unregistered, ChatResponse mirrors event fields.
- tests/test_register_cli.py (7) — _runtime_config_from_args
threading, invalid-id fail-fast, parser wires --register.
- tests/test_register_tour_demo.py (7) — three seam claims pinned
individually + all_claims_supported + per-cell register_id +
variant_id discipline (empty for neutral/terse, non-empty for
convivial).
- tests/test_register_variation.py extended (4 new) — DecorationResult
shape, decorate_surface_str alias, variant_id stability,
bijection between non-trivial marker pairs and variant_ids.
Lane evidence
- Full lane: 2632 passed / 4 skipped / 1 pre-existing failure
(tests/test_cli_demo.py::test_all_preamble_explains_combined_run,
unrelated to R5).
- Cognition eval byte-identical: public 100 / 100 / 91.7 / 100.
Trust boundaries (per CLAUDE.md)
- --register flag does not bypass ratification; loader validates the
pack id through _find_pack and the ratify gate at load time.
- variant_id is content-addressed; no raw markers leak into audit.
- Telemetry stays redact-safe — register_id and variant_id are
identifiers, not content, so include_content=False emits them
unconditionally.
- No new mutation surface; pack files on disk are not modified.
234 lines
8.2 KiB
Python
234 lines
8.2 KiB
Python
"""Seeded surface variation — selector determinism + decoration shape
|
||
(ADR-0071, Plan Phase R4).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from chat.register_variation import (
|
||
DecorationResult,
|
||
_select_bucket_entry,
|
||
decorate_surface,
|
||
decorate_surface_str,
|
||
)
|
||
from packs.register.loader import UNREGISTERED, load_register_pack
|
||
|
||
|
||
def test_selector_returns_empty_string_for_empty_bucket():
|
||
result = _select_bucket_entry(
|
||
(),
|
||
seed_text="anything",
|
||
register_id="test_v1",
|
||
turn_idx=0,
|
||
bucket_name="openings",
|
||
)
|
||
assert result == ""
|
||
|
||
|
||
def test_selector_is_deterministic():
|
||
bucket = ("a", "b", "c", "d", "e")
|
||
args = dict(
|
||
seed_text="some surface", register_id="test_v1",
|
||
turn_idx=7, bucket_name="openings",
|
||
)
|
||
first = _select_bucket_entry(bucket, **args)
|
||
for _ in range(50):
|
||
assert _select_bucket_entry(bucket, **args) == first
|
||
|
||
|
||
def test_selector_varies_with_turn_idx():
|
||
bucket = ("a", "b", "c", "d", "e")
|
||
seen = set()
|
||
for turn_idx in range(20):
|
||
seen.add(_select_bucket_entry(
|
||
bucket,
|
||
seed_text="constant",
|
||
register_id="test_v1",
|
||
turn_idx=turn_idx,
|
||
bucket_name="openings",
|
||
))
|
||
# 20 turns over a 5-entry bucket should hit at least 3 entries.
|
||
assert len(seen) >= 3, (
|
||
f"seed is not varying with turn_idx — only saw {seen}"
|
||
)
|
||
|
||
|
||
def test_selector_varies_with_register_id():
|
||
bucket = ("a", "b", "c", "d", "e")
|
||
a = _select_bucket_entry(
|
||
bucket, seed_text="same", register_id="reg_A",
|
||
turn_idx=0, bucket_name="openings",
|
||
)
|
||
b = _select_bucket_entry(
|
||
bucket, seed_text="same", register_id="reg_B",
|
||
turn_idx=0, bucket_name="openings",
|
||
)
|
||
# Not guaranteed for a single bucket size 5 — sweep a few
|
||
# turn_idx values to ensure register_id moves the seed at all.
|
||
diffs = 0
|
||
for t in range(20):
|
||
a_t = _select_bucket_entry(
|
||
bucket, seed_text="same", register_id="reg_A",
|
||
turn_idx=t, bucket_name="openings",
|
||
)
|
||
b_t = _select_bucket_entry(
|
||
bucket, seed_text="same", register_id="reg_B",
|
||
turn_idx=t, bucket_name="openings",
|
||
)
|
||
if a_t != b_t:
|
||
diffs += 1
|
||
assert diffs > 0, (
|
||
f"register_id has no effect on selector across 20 turn_idx "
|
||
f"values (first picks: a={a!r}, b={b!r})"
|
||
)
|
||
|
||
|
||
def test_selector_distributes_across_bucket():
|
||
"""Across many distinct seed_text inputs, every entry should be
|
||
selected at least once."""
|
||
bucket = ("a", "b", "c")
|
||
seen: set[str] = set()
|
||
for i in range(300):
|
||
seen.add(_select_bucket_entry(
|
||
bucket,
|
||
seed_text=f"surface_{i}",
|
||
register_id="test",
|
||
turn_idx=0,
|
||
bucket_name="openings",
|
||
))
|
||
assert seen == {"a", "b", "c"}
|
||
|
||
|
||
def test_selector_uses_bucket_name():
|
||
"""Different bucket_name keys produce different seeds."""
|
||
bucket = ("a", "b", "c", "d", "e", "f", "g", "h")
|
||
args = dict(seed_text="x", register_id="r", turn_idx=0)
|
||
o = _select_bucket_entry(bucket, bucket_name="openings", **args)
|
||
c = _select_bucket_entry(bucket, bucket_name="closings", **args)
|
||
diffs = 0
|
||
for t in range(20):
|
||
o_t = _select_bucket_entry(
|
||
bucket, seed_text="x", register_id="r",
|
||
turn_idx=t, bucket_name="openings",
|
||
)
|
||
c_t = _select_bucket_entry(
|
||
bucket, seed_text="x", register_id="r",
|
||
turn_idx=t, bucket_name="closings",
|
||
)
|
||
if o_t != c_t:
|
||
diffs += 1
|
||
# Not strict on (o, c) at turn 0 alone — sweep guards.
|
||
_ = (o, c)
|
||
assert diffs > 0
|
||
|
||
|
||
def test_decorate_surface_empty_buckets_noop():
|
||
"""UNREGISTERED / neutral / terse all have empty buckets."""
|
||
surface = "Light is illumination. Pack-grounded (en_core_cognition_v1)."
|
||
out = decorate_surface(surface, UNREGISTERED, turn_idx=0)
|
||
assert isinstance(out, DecorationResult)
|
||
assert out.surface == surface
|
||
assert out.variant_id == ""
|
||
|
||
neutral = load_register_pack("default_neutral_v1")
|
||
assert decorate_surface(surface, neutral, turn_idx=0).surface == surface
|
||
assert decorate_surface(surface, neutral, turn_idx=0).variant_id == ""
|
||
|
||
terse = load_register_pack("terse_v1")
|
||
assert decorate_surface(surface, terse, turn_idx=0).surface == surface
|
||
assert decorate_surface(surface, terse, turn_idx=0).variant_id == ""
|
||
|
||
|
||
def test_decorate_surface_empty_input_noop():
|
||
"""Empty input ⇒ empty output regardless of register."""
|
||
convivial = load_register_pack("convivial_v1")
|
||
out = decorate_surface("", convivial, turn_idx=0)
|
||
assert out.surface == ""
|
||
assert out.variant_id == ""
|
||
|
||
|
||
def test_decorate_surface_convivial_attaches_markers():
|
||
"""Convivial register has populated openings and closings — at
|
||
least one of them should attach to most surfaces."""
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
out = decorate_surface(surface, convivial, turn_idx=0)
|
||
# The opening (3 entries, no empty) is always non-empty.
|
||
# Therefore the surface must change.
|
||
assert out.surface != surface
|
||
assert surface in out.surface
|
||
# ADR-0072 (R5) — non-empty markers produce a non-empty variant_id.
|
||
assert out.variant_id != ""
|
||
assert len(out.variant_id) == 12
|
||
|
||
|
||
def test_decorate_surface_is_deterministic():
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
a = decorate_surface(surface, convivial, turn_idx=0)
|
||
b = decorate_surface(surface, convivial, turn_idx=0)
|
||
assert a == b
|
||
|
||
|
||
def test_decorate_surface_turn_idx_varies_output():
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
seen: set[str] = set()
|
||
for t in range(15):
|
||
seen.add(decorate_surface(surface, convivial, turn_idx=t).surface)
|
||
# 15 turns × 3 openings × 3 closings = 9 possible outputs;
|
||
# should see at least 4 distinct.
|
||
assert len(seen) >= 4, f"only {len(seen)} distinct decorations across 15 turns"
|
||
|
||
|
||
# ADR-0072 (R5) — DecorationResult + variant_id + str alias.
|
||
|
||
|
||
def test_decorate_surface_str_alias_returns_string():
|
||
"""decorate_surface_str preserves the pre-R5 string-only API."""
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
out = decorate_surface_str(surface, convivial, turn_idx=0)
|
||
assert isinstance(out, str)
|
||
full = decorate_surface(surface, convivial, turn_idx=0)
|
||
assert out == full.surface
|
||
|
||
|
||
def test_variant_id_stable_across_invocations():
|
||
"""Same (opening, closing) pair ⇒ identical 12-char variant_id."""
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
a = decorate_surface(surface, convivial, turn_idx=0)
|
||
b = decorate_surface(surface, convivial, turn_idx=0)
|
||
assert a.variant_id == b.variant_id
|
||
# Stable hex prefix of 12 chars.
|
||
assert len(a.variant_id) == 12
|
||
int(a.variant_id, 16) # raises if not hex
|
||
|
||
|
||
def test_variant_id_distinguishes_distinct_marker_pairs():
|
||
"""Different marker selections ⇒ different variant_ids."""
|
||
convivial = load_register_pack("convivial_v1")
|
||
surface = "Light is illumination."
|
||
seen_pairs: set[tuple[str, str]] = set()
|
||
seen_variant_ids: set[str] = set()
|
||
for t in range(40):
|
||
result = decorate_surface(surface, convivial, turn_idx=t)
|
||
seen_pairs.add((result.opening, result.closing))
|
||
if result.variant_id:
|
||
seen_variant_ids.add(result.variant_id)
|
||
# Bijection between non-trivial pairs and variant_ids:
|
||
# the number of distinct variant_ids equals the number of
|
||
# distinct non-empty pairs (collisions on 48 bits ≈ 2^-48 ⇒ none).
|
||
non_trivial_pairs = {p for p in seen_pairs if p != ("", "")}
|
||
assert len(seen_variant_ids) == len(non_trivial_pairs)
|
||
|
||
|
||
def test_variant_id_empty_for_no_decoration():
|
||
"""UNREGISTERED / neutral / terse always emit ``variant_id=""``."""
|
||
surface = "Light is illumination."
|
||
for pack_id in ("default_neutral_v1", "terse_v1"):
|
||
pack = load_register_pack(pack_id)
|
||
for t in range(5):
|
||
assert decorate_surface(surface, pack, turn_idx=t).variant_id == ""
|
||
for t in range(5):
|
||
assert decorate_surface(surface, UNREGISTERED, turn_idx=t).variant_id == ""
|