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.
113 lines
3.6 KiB
Python
113 lines
3.6 KiB
Python
"""Register telemetry — TurnEvent + serialize_turn_event carry
|
|
register_id and register_variant_id (ADR-0072 / Plan Phase R5).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from chat.runtime import ChatRuntime
|
|
from chat.telemetry import JsonlBufferSink, serialize_turn_event
|
|
from core.config import RuntimeConfig
|
|
from core.physics.identity import TurnEvent
|
|
|
|
|
|
def _decode(line: str) -> dict:
|
|
return json.loads(line)
|
|
|
|
|
|
def test_turn_event_defaults_register_fields_empty():
|
|
"""Pre-R5 callers constructing TurnEvent without the new fields
|
|
see empty strings — preserves byte-identity."""
|
|
event = TurnEvent(
|
|
turn=0,
|
|
input_tokens=(),
|
|
surface="",
|
|
walk_surface="",
|
|
articulation_surface="",
|
|
dialogue_role="assert",
|
|
identity_score=None,
|
|
cycle_cost_total=0.0,
|
|
vault_hits=0,
|
|
versor_condition=0.0,
|
|
flagged=False,
|
|
)
|
|
assert event.register_id == ""
|
|
assert event.register_variant_id == ""
|
|
|
|
|
|
def test_serialize_turn_event_emits_register_fields():
|
|
"""The JSONL audit line carries register_id / register_variant_id."""
|
|
event = TurnEvent(
|
|
turn=0,
|
|
input_tokens=(),
|
|
surface="",
|
|
walk_surface="",
|
|
articulation_surface="",
|
|
dialogue_role="assert",
|
|
identity_score=None,
|
|
cycle_cost_total=0.0,
|
|
vault_hits=0,
|
|
versor_condition=0.0,
|
|
flagged=False,
|
|
register_id="convivial_v1",
|
|
register_variant_id="abcdef012345",
|
|
)
|
|
payload = serialize_turn_event(event)
|
|
assert payload["register_id"] == "convivial_v1"
|
|
assert payload["register_variant_id"] == "abcdef012345"
|
|
|
|
|
|
def test_runtime_chat_emits_register_id_when_set():
|
|
"""Convivial-loaded runtime ⇒ every TurnEvent carries register_id."""
|
|
runtime = ChatRuntime(
|
|
config=RuntimeConfig(register_pack_id="convivial_v1")
|
|
)
|
|
sink = JsonlBufferSink()
|
|
runtime.attach_telemetry_sink(sink)
|
|
runtime.chat("What is light?")
|
|
assert sink.lines, "expected at least one telemetry line"
|
|
last = _decode(sink.lines[-1])
|
|
assert last["register_id"] == "convivial_v1"
|
|
# convivial_v1 always emits a non-empty opening on non-empty
|
|
# surfaces, so variant_id is a 12-char hex.
|
|
variant_id = last["register_variant_id"]
|
|
assert variant_id != ""
|
|
assert len(variant_id) == 12
|
|
int(variant_id, 16)
|
|
|
|
|
|
def test_runtime_chat_empty_register_id_for_unregistered():
|
|
"""Default runtime (no --register) ⇒ empty register_id / variant_id."""
|
|
runtime = ChatRuntime(config=RuntimeConfig())
|
|
sink = JsonlBufferSink()
|
|
runtime.attach_telemetry_sink(sink)
|
|
runtime.chat("What is light?")
|
|
last = _decode(sink.lines[-1])
|
|
assert last["register_id"] == ""
|
|
assert last["register_variant_id"] == ""
|
|
|
|
|
|
def test_runtime_chat_empty_variant_id_for_terse():
|
|
"""terse_v1 has empty marker buckets ⇒ variant_id stays empty even
|
|
though register_id is set."""
|
|
runtime = ChatRuntime(
|
|
config=RuntimeConfig(register_pack_id="terse_v1")
|
|
)
|
|
sink = JsonlBufferSink()
|
|
runtime.attach_telemetry_sink(sink)
|
|
runtime.chat("What is light?")
|
|
last = _decode(sink.lines[-1])
|
|
assert last["register_id"] == "terse_v1"
|
|
assert last["register_variant_id"] == ""
|
|
|
|
|
|
def test_chat_response_mirrors_register_fields():
|
|
"""ChatResponse exposes the same register fields as TurnEvent."""
|
|
runtime = ChatRuntime(
|
|
config=RuntimeConfig(register_pack_id="convivial_v1")
|
|
)
|
|
response = runtime.chat("What is light?")
|
|
event = runtime.turn_log[-1]
|
|
assert response.register_id == event.register_id == "convivial_v1"
|
|
assert response.register_variant_id == event.register_variant_id
|