core/tests/test_register_variation.py
Shay 7f0bad3e20 feat(register): R5 — operator-visible register telemetry + tour demo
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.
2026-05-19 19:03:07 -07:00

234 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 == ""