core/tests/test_register_variation.py
Shay 6207b5fd0e feat(register): R1–R4 register pack subsystem — deterministic surface variation
Introduces the presentation axis as a fourth pack class (sibling to identity /
safety / ethics), orthogonal to the truth path. Same input + same packs +
same register ⇒ bit-for-bit reproducible surface; varying any of the three ⇒
genuinely different output. No stochastic sampling.

ADR-0068 (R1): RegisterPack frozen dataclass, loader, ratify script, seam test.
  - default_neutral_v1 ratified as null register.

ADR-0069 (R2): realizer register parameter threaded through 9 composer entry
  points; RuntimeConfig.register_pack_id; three byte-identity invariants
  (A: None ≡ pre-R2 unregistered; B: None ≡ default_neutral_v1; C: trace_hash
  invariant under register). Amended to default-with-lint after 167-call-site
  scout: composers default to UNREGISTERED, AST lint enforces explicit
  register= at runtime call sites.

ADR-0070 (R3): terse_v1 register, first non-neutral pack. realizer_overrides
  schema with known-keys allow-list (disclosure_domain_count ∈ {1,2,3}).
  build_pack_surface_candidate reads override with fail-soft clamp. New
  invariant register_invariant_grounding asserts grounding_source +
  trace_hash byte-identical across {None, neutral, terse}.

ADR-0071 (R4): seeded surface variation via convivial_v1.
  chat/register_variation.py applies SHA-256-seeded marker selection from
  bounded discourse-marker buckets. ChatResponse.pre_decoration_surface routes
  truth-path surface to core/cognition/pipeline.py so trace_hash stays
  invariant under register (the load-bearing architectural fix — initially
  invariant C failed under convivial because decoration was leaking into
  trace_hash via response.surface). Empty-string marker entries now
  legitimate ("no marker this turn" is a valid seed pick). realizer_overrides
  schema widened with per_intent nested block (validated against IntentTag
  whitelist; wired but not exercised by convivial). Two new invariants:
  seeded_variation_replay_equivalence (fresh runtimes → byte-identical) and
  seeded_variation_turn_distinct (same prompt across turns → ≥2 distinct
  surfaces).

ADR-0072 (R5, draft): telemetry + operator surface — TurnEvent gains
  register_id and register_variant_id, core chat --register flag, core demo
  register-tour. Status: Proposed; not yet implemented.

Three ratified register packs ship: default_neutral_v1 (null), terse_v1
(disclosure_domain_count=1), convivial_v1 (3 openings × 3 closings).

Verification:
  - 84 register tests pass + 1 documented skip
  - Curated lanes green: smoke 67, cognition 120+1s, teaching 17, packs 6,
    runtime 19, algebra 132
  - Cognition eval byte-identical to pre-register baseline:
    public 100/100/91.7/100, holdout 100/100/83.3/100
  - Full lane: 2608 passed, 4 skipped, 1 failed (pre-existing
    test_cli_demo.py "Combined Demo" → "Run Every Demo" rename, unrelated)

Truth-path isolation: chat/register_variation.py is realizer-side; the seam
test (tests/test_register_pack_seam.py) refuses imports of packs.register
from intent classification, propagation, vault recall, trace hashing, and
algebra.
2026-05-19 16:52:36 -07:00

166 lines
5.5 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 _select_bucket_entry, decorate_surface
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 out == surface
neutral = load_register_pack("default_neutral_v1")
assert decorate_surface(surface, neutral, turn_idx=0) == surface
terse = load_register_pack("terse_v1")
assert decorate_surface(surface, terse, turn_idx=0) == surface
def test_decorate_surface_empty_input_noop():
"""Empty input ⇒ empty output regardless of register."""
convivial = load_register_pack("convivial_v1")
assert decorate_surface("", convivial, turn_idx=0) == ""
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
assert surface in out
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))
# 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"