feat(anchor_lens): ADR-0073d — L1.4 telemetry, CLI flag, tour demo
L1.4 closes the anchor-lens inside-out arc (L1.1→L1.4 mirroring
R1→R5). Substantive axis is now operator-observable,
operator-driven, and demo-falsifiable — exactly what R5 did for
the register subsystem.
Telemetry extension
- TurnEvent + ChatResponse gain anchor_lens_id +
anchor_lens_mode_label (both default "" → pre-L1.4
byte-identical).
- serialize_turn_event surfaces both fields in every JSONL line.
- Mode-label extracted via _ANCHOR_LENS_ANNOTATION_RE from the
PRE-decoration surface (so register decoration cannot interfere
with anchor-lens telemetry). Composer remains the sole source
of truth for engagement; the runtime helper is read-only.
Operator surface
- core chat --anchor-lens <id> CLI flag threads into
RuntimeConfig.anchor_lens_id.
- Invalid id → AnchorLensError caught at cmd_chat and surfaced
as _die("invalid --anchor-lens pack id: ...", code=2) before
the REPL launches.
- Composes with --register (both flags wire through
_runtime_config_from_args).
Narrative demo
- evals/anchor_lens_tour/run_tour.py walks 2 prompts × 3
ratified lenses ({default_unanchored_v1, grc_logos_v1,
he_logos_v1}). Asserts four claims:
* lens_ids_recorded_per_turn
* trace_hashes_distinct_across_lenses (OPPOSITE of
register-tour's identical-hash claim)
* surface_propositions_distinct_across_lenses
* no_substrate_glyph_leak (block-scoped Greek/Hebrew/
Syriac/Arabic; stylistic punct allowed)
- Exit code 0 iff all four hold.
- Bundled into `core demo` choices + EPILOG.
Tests (30 new)
- tests/test_anchor_lens_telemetry.py (16) — TurnEvent shape,
serializer keys, runtime emits per lens / per engagement
state, ChatResponse mirrors event, mode-label extractor unit.
- tests/test_anchor_lens_cli.py (9) — _runtime_config_from_args
threading, invalid id fail-fast, parser flag wiring, parser
composes with --register.
- tests/test_anchor_lens_tour_demo.py (9) — four seam claims
pinned individually + all_claims_supported + per-cell
anchor_lens_id + unanchored cells empty mode + engaged cells
carry mode label.
Lane evidence
- 30 new L1.4 tests pass.
- core demo anchor-lens-tour --json → all_claims_supported: True.
- core demo register-tour --json → all_claims_supported: True.
Both tours pass simultaneously — orthogonality CI-pinned.
- python -m core.cli eval cognition → public 100/100/91.7/100
byte-identical (lens=None / default_unanchored_v1).
- Full lane: 2736 passed / 4 skipped / 1 pre-existing failure
(+30 over L1.3's 2706; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
Live demo (canonical proof)
P1: 'What is knowledge?'
default_unanchored_v1 trace=17c9aabe… mode=(none)
grc_logos_v1 trace=0198ad4c… mode=systematic
he_logos_v1 trace=17c9aabe… mode=(none)
P2: 'What is truth?'
default_unanchored_v1 trace=2557f3e8… mode=(none)
grc_logos_v1 trace=2557f3e8… mode=(none)
he_logos_v1 trace=ec8d84aa… mode=covenant-verity
Engagement is substrate-scoped: grc never touches truth, he
never touches knowledge. Trace hashes diverge exactly where the
lens engages.
Trust boundaries
- --anchor-lens flag does not bypass ratification; loader still
enforces companion mastery report self-seal + ratify-time
substrate-atom existence check (ADR-0073b/c).
- Mode-label extraction is read-only regex parse; can't forge
annotations the composer didn't emit.
- Telemetry stays redact-safe — both fields are identifiers /
mode labels, not content. include_content=False emits them
unconditionally.
- No new mutation surface; pack files unchanged.
Closes the anchor-lens inside-out arc
L1.1 content prerequisite ✓ (ADR-0073a)
L1.2 class + loader + unanchored sentinel ✓ (ADR-0073b)
L1.3 first lenses + composer wiring ✓ (ADR-0073c)
L1.4 telemetry + CLI + tour demo ✓ (this commit)
Mirrors the R1→R5 register cadence exactly. Both axes are now
operator-observable, CI-falsifiable, audit-traceable, and
composable via the orthogonality claim pinned in both tours.
This commit is contained in:
parent
b35bec6465
commit
1feec74b1c
10 changed files with 1110 additions and 2 deletions
|
|
@ -78,6 +78,32 @@ from session.correction import CorrectionPass
|
|||
from vault.decompose import default_decomposer, default_gate
|
||||
|
||||
_TOKEN_RE = re.compile(r"\w+", re.UNICODE)
|
||||
# ADR-0073d (L1.4) — extracts the engaged ``cognitive_mode_label`` from a
|
||||
# composer-emitted ``[lens(<lens_id>):<mode>]`` annotation. The runtime
|
||||
# uses this read-only to populate the TurnEvent telemetry field; the
|
||||
# composer remains the only source of truth for engagement.
|
||||
_ANCHOR_LENS_ANNOTATION_RE = re.compile(r"\[lens\(([^):]+)\):([^\]]+)\]")
|
||||
|
||||
|
||||
def _extract_anchor_lens_mode_label(surface: str, lens_id: str) -> str:
|
||||
"""Return the engaged mode_label if *surface* carries a
|
||||
``[lens(<lens_id>):<mode>]`` annotation for the given ``lens_id``.
|
||||
|
||||
Returns ``""`` when:
|
||||
* surface is empty or contains no lens annotation
|
||||
* lens_id is empty (no lens loaded)
|
||||
* the annotation in surface is for a different lens_id (defensive)
|
||||
|
||||
Pure read; no side effects. Telemetry-only — the composer is the
|
||||
sole source of truth for engagement (ADR-0073c).
|
||||
"""
|
||||
if not surface or not lens_id:
|
||||
return ""
|
||||
for match in _ANCHOR_LENS_ANNOTATION_RE.finditer(surface):
|
||||
if match.group(1) == lens_id:
|
||||
return match.group(2)
|
||||
return ""
|
||||
|
||||
_SEED_ALIASES = {
|
||||
"logos": "\u03bb\u03cc\u03b3\u03bf\u03c2",
|
||||
"dabar": "\u05d3\u05d1\u05e8",
|
||||
|
|
@ -311,6 +337,12 @@ class ChatResponse:
|
|||
# for callers that construct ChatResponse without these fields.
|
||||
register_id: str = ""
|
||||
register_variant_id: str = ""
|
||||
# ADR-0073d (L1.4) — operator-visible anchor-lens identity per turn.
|
||||
# Mirrors the TurnEvent fields so callers (CLI, demos, tests) can
|
||||
# read the lens state from ChatResponse without re-parsing the
|
||||
# telemetry JSONL. ``""`` defaults preserve pre-L1.4 byte-identity.
|
||||
anchor_lens_id: str = ""
|
||||
anchor_lens_mode_label: str = ""
|
||||
|
||||
|
||||
class ChatRuntime:
|
||||
|
|
@ -968,6 +1000,18 @@ class ChatRuntime:
|
|||
"" if self.register_pack.is_unregistered()
|
||||
else self.register_pack.register_id
|
||||
)
|
||||
# ADR-0073d — anchor-lens telemetry. ``id`` reflects the loaded
|
||||
# pack (empty for UNANCHORED); ``mode_label`` reflects the
|
||||
# engaged label this turn (empty when the lens didn't fire on
|
||||
# this turn's lemma). Mode is extracted from the pre-decoration
|
||||
# surface so register decoration cannot interfere.
|
||||
anchor_lens_id_stub = (
|
||||
"" if self.anchor_lens.is_unanchored()
|
||||
else self.anchor_lens.lens_id
|
||||
)
|
||||
anchor_lens_mode_label_stub = _extract_anchor_lens_mode_label(
|
||||
pre_decoration_surface_stub, anchor_lens_id_stub,
|
||||
)
|
||||
verdicts_bundle = TurnVerdicts(
|
||||
identity_score=None,
|
||||
safety_verdict=safety_verdict,
|
||||
|
|
@ -995,6 +1039,8 @@ class ChatRuntime:
|
|||
grounding_source=grounding_source,
|
||||
register_id=register_id_stub,
|
||||
register_variant_id=decoration_stub.variant_id,
|
||||
anchor_lens_id=anchor_lens_id_stub,
|
||||
anchor_lens_mode_label=anchor_lens_mode_label_stub,
|
||||
)
|
||||
self.turn_log.append(stub_event)
|
||||
self._emit_turn_event(stub_event)
|
||||
|
|
@ -1041,6 +1087,8 @@ class ChatRuntime:
|
|||
pre_decoration_surface=pre_decoration_surface_stub,
|
||||
register_id=register_id_stub,
|
||||
register_variant_id=decoration_stub.variant_id,
|
||||
anchor_lens_id=anchor_lens_id_stub,
|
||||
anchor_lens_mode_label=anchor_lens_mode_label_stub,
|
||||
)
|
||||
|
||||
def chat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
|
||||
|
|
@ -1321,6 +1369,15 @@ class ChatRuntime:
|
|||
"" if self.register_pack.is_unregistered()
|
||||
else self.register_pack.register_id
|
||||
)
|
||||
# ADR-0073d — anchor-lens telemetry (main path). See stub-path
|
||||
# comment above for semantics.
|
||||
anchor_lens_id_main = (
|
||||
"" if self.anchor_lens.is_unanchored()
|
||||
else self.anchor_lens.lens_id
|
||||
)
|
||||
anchor_lens_mode_label_main = _extract_anchor_lens_mode_label(
|
||||
pre_decoration_surface_main, anchor_lens_id_main,
|
||||
)
|
||||
verdicts_bundle = TurnVerdicts(
|
||||
identity_score=identity_score,
|
||||
safety_verdict=safety_verdict,
|
||||
|
|
@ -1347,6 +1404,8 @@ class ChatRuntime:
|
|||
grounding_source=warm_grounding_source or "vault",
|
||||
register_id=register_id_main,
|
||||
register_variant_id=decoration_main.variant_id,
|
||||
anchor_lens_id=anchor_lens_id_main,
|
||||
anchor_lens_mode_label=anchor_lens_mode_label_main,
|
||||
)
|
||||
self.turn_log.append(turn_event)
|
||||
self._emit_turn_event(turn_event)
|
||||
|
|
@ -1382,6 +1441,8 @@ class ChatRuntime:
|
|||
pre_decoration_surface=pre_decoration_surface_main,
|
||||
register_id=register_id_main,
|
||||
register_variant_id=decoration_main.variant_id,
|
||||
anchor_lens_id=anchor_lens_id_main,
|
||||
anchor_lens_mode_label=anchor_lens_mode_label_main,
|
||||
)
|
||||
|
||||
def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse:
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ def serialize_turn_event(
|
|||
# marker buckets, so the wire format degrades cleanly.
|
||||
"register_id": str(getattr(event, "register_id", "") or ""),
|
||||
"register_variant_id": str(getattr(event, "register_variant_id", "") or ""),
|
||||
# ADR-0073d (L1.4) — operator-visible anchor-lens identity per
|
||||
# turn. Empty strings on pre-L1.4 events / UNANCHORED runtimes /
|
||||
# turns where the lens did not engage.
|
||||
"anchor_lens_id": str(getattr(event, "anchor_lens_id", "") or ""),
|
||||
"anchor_lens_mode_label": str(getattr(event, "anchor_lens_mode_label", "") or ""),
|
||||
}
|
||||
safety = getattr(event, "safety_verdict", None)
|
||||
if safety is not None:
|
||||
|
|
|
|||
34
core/cli.py
34
core/cli.py
|
|
@ -23,7 +23,7 @@ _CORE_RS_DIR = _REPO_ROOT / "core-rs"
|
|||
_CORE_RS_MANIFEST = _CORE_RS_DIR / "Cargo.toml"
|
||||
|
||||
DESCRIPTION = "CORE versor engine command suite."
|
||||
EPILOG = "Examples:\n core chat\n core pulse \"What is truth?\"\n core pulse --no-glove --json \"Compare knowledge and wisdom\"\n core bench\n core bench --suite all\n core bench --suite all --json --report bench_all.json\n core bench --suite determinism --runs 50\n core bench --suite speedup --json\n core trace \"word beginning truth\"\n core trace --output-language grc --frame-pack grc --json \"logos\"\n core rust status\n core rust build\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core teaching audit\n core teaching audit --json\n core teaching gaps --top 10\n core teaching queue --threshold 3\n core teaching propose <candidate-jsonl-path>\n core teaching proposals --state pending\n core teaching review <proposal_id> --accept --review-date 2026-05-18\n core teaching supersede cause_light_reveals_truth --subject light --intent cause --connective grounds --object truth --review-date 2026-05-18\n core teaching supersessions\n core teaching supersessions --json\n core test --suite fast -q\n core test --suite pulse -q\n core test --suite proof -q\n core test --suite cognition -q\n core test -- tests/test_alignment_graph.py -q\n core demo audit-tour\n core demo register-tour\n core demo pack-measurements\n core demo long-context-comparison\n core demo anti-regression\n core demo learning-loop\n core demo articulation\n core demo conversation\n core demo conversation --no-stream\n core demo all\n core demo adr-0024-chain\n core eval --list\n core eval cognition\n core eval cognition --json --save\n core eval cognition --split dev --version v1\n core eval cognition --split holdout"
|
||||
EPILOG = "Examples:\n core chat\n core pulse \"What is truth?\"\n core pulse --no-glove --json \"Compare knowledge and wisdom\"\n core bench\n core bench --suite all\n core bench --suite all --json --report bench_all.json\n core bench --suite determinism --runs 50\n core bench --suite speedup --json\n core trace \"word beginning truth\"\n core trace --output-language grc --frame-pack grc --json \"logos\"\n core rust status\n core rust build\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core teaching audit\n core teaching audit --json\n core teaching gaps --top 10\n core teaching queue --threshold 3\n core teaching propose <candidate-jsonl-path>\n core teaching proposals --state pending\n core teaching review <proposal_id> --accept --review-date 2026-05-18\n core teaching supersede cause_light_reveals_truth --subject light --intent cause --connective grounds --object truth --review-date 2026-05-18\n core teaching supersessions\n core teaching supersessions --json\n core test --suite fast -q\n core test --suite pulse -q\n core test --suite proof -q\n core test --suite cognition -q\n core test -- tests/test_alignment_graph.py -q\n core demo audit-tour\n core demo register-tour\n core demo anchor-lens-tour\n core demo pack-measurements\n core demo long-context-comparison\n core demo anti-regression\n core demo learning-loop\n core demo articulation\n core demo conversation\n core demo conversation --no-stream\n core demo all\n core demo adr-0024-chain\n core eval --list\n core eval cognition\n core eval cognition --json --save\n core eval cognition --split dev --version v1\n core eval cognition --split holdout"
|
||||
|
||||
_TEST_SUITES: dict[str, tuple[str, ...]] = {
|
||||
"fast": (
|
||||
|
|
@ -173,6 +173,7 @@ def _runtime_config_from_args(args: argparse.Namespace):
|
|||
admissibility_threshold=getattr(args, "admissibility_threshold", 0.0),
|
||||
identity_pack=getattr(args, "identity", "") or "",
|
||||
register_pack_id=(getattr(args, "register", None) or None),
|
||||
anchor_lens_id=(getattr(args, "anchor_lens", None) or None),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -216,10 +217,13 @@ def cmd_chat(args: argparse.Namespace) -> int:
|
|||
|
||||
try:
|
||||
runtime = ChatRuntime(config=_runtime_config_from_args(args))
|
||||
except Exception as exc: # noqa: BLE001 — surface RegisterPackError / pack-load errors
|
||||
except Exception as exc: # noqa: BLE001 — surface pack-load errors
|
||||
from packs.anchor_lens.loader import AnchorLensError
|
||||
from packs.register.loader import RegisterPackError
|
||||
if isinstance(exc, RegisterPackError):
|
||||
_die(f"invalid --register pack id: {exc}", code=2)
|
||||
if isinstance(exc, AnchorLensError):
|
||||
_die(f"invalid --anchor-lens pack id: {exc}", code=2)
|
||||
raise
|
||||
show_verdicts = bool(getattr(args, "show_verdicts", False))
|
||||
while True:
|
||||
|
|
@ -1969,6 +1973,14 @@ def cmd_demo(args: argparse.Namespace) -> int:
|
|||
print(json.dumps(result, indent=2, sort_keys=True, default=str))
|
||||
return 0 if result.get("all_claims_supported", False) else 1
|
||||
|
||||
if target == "anchor-lens-tour":
|
||||
from evals.anchor_lens_tour.run_tour import run_tour as run_lens_tour
|
||||
|
||||
result = run_lens_tour(emit_json=args.json)
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, sort_keys=True, default=str))
|
||||
return 0 if result.get("all_claims_supported", False) else 1
|
||||
|
||||
if target == "pack-measurements":
|
||||
from scripts.publish_pack_measurements import (
|
||||
build_combined_report,
|
||||
|
|
@ -2521,6 +2533,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"runtime init before the REPL starts."
|
||||
),
|
||||
)
|
||||
chat.add_argument(
|
||||
"--anchor-lens",
|
||||
metavar="LENS_ID",
|
||||
default=None,
|
||||
dest="anchor_lens",
|
||||
help=(
|
||||
"optional anchor-lens pack id (ADR-0073+); default: no "
|
||||
"lens (unanchored sentinel, byte-identical to "
|
||||
"default_unanchored_v1). Examples: default_unanchored_v1, "
|
||||
"grc_logos_v1, he_logos_v1. Invalid ids fail-fast at "
|
||||
"runtime init before the REPL starts."
|
||||
),
|
||||
)
|
||||
chat.set_defaults(func=cmd_chat)
|
||||
|
||||
test = subparsers.add_parser("test", help="run pytest with curated suite aliases or direct passthrough")
|
||||
|
|
@ -2851,6 +2876,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"adr-0024-chain",
|
||||
"audit-tour",
|
||||
"register-tour",
|
||||
"anchor-lens-tour",
|
||||
"pack-measurements",
|
||||
"long-context-comparison",
|
||||
"anti-regression",
|
||||
|
|
@ -2871,6 +2897,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"register-tour: ADR-0068..0072 presentation-axis seam — same "
|
||||
"prompts × three registers; surface varies, grounding_source "
|
||||
"and trace_hash byte-identical. "
|
||||
"anchor-lens-tour: ADR-0073 substantive-axis seam — same "
|
||||
"prompts × three lenses; trace_hash DISTINCT across lenses, "
|
||||
"no substrate glyph leak. Opposite invariant from register-tour; "
|
||||
"both must hold continuously. "
|
||||
"pack-measurements: ADR-0043 — pack-layer claims → CI-enforced "
|
||||
"numbers across the three ratified identity packs. "
|
||||
"long-context-comparison: ADR-0045 — CORE exact recall NIAH at "
|
||||
|
|
|
|||
|
|
@ -301,3 +301,14 @@ class TurnEvent:
|
|||
# byte-identical.
|
||||
register_id: str = ""
|
||||
register_variant_id: str = ""
|
||||
# ADR-0073d (L1.4) — operator-visible anchor-lens identity per turn.
|
||||
# ``anchor_lens_id`` is the loaded pack id (e.g. ``"grc_logos_v1"``),
|
||||
# or ``""`` for the in-memory UNANCHORED sentinel.
|
||||
# ``anchor_lens_mode_label`` is the engaged ``cognitive_mode_label``
|
||||
# when the lens fired on this turn's lemma (extracted from the
|
||||
# composer-emitted ``[lens(<id>):<mode>]`` annotation), or ``""``
|
||||
# when the lens was loaded but did not engage on this turn's
|
||||
# lemma, or when no lens was loaded at all. Both default to
|
||||
# ``""`` so pre-L1.4 callers stay byte-identical.
|
||||
anchor_lens_id: str = ""
|
||||
anchor_lens_mode_label: str = ""
|
||||
|
|
|
|||
348
docs/decisions/ADR-0073d-anchor-lens-telemetry-tour.md
Normal file
348
docs/decisions/ADR-0073d-anchor-lens-telemetry-tour.md
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
# ADR-0073d — Anchor-lens telemetry, CLI, and tour demo (Plan Phase L1.4)
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-19
|
||||
**Ratified:** 2026-05-19
|
||||
**Author:** Shay
|
||||
**Phase:** Plan Phase L1.4 (operator-visible anchor lens, falsifiable demo)
|
||||
**Parent:** [ADR-0073](./ADR-0073-anchor-lens-substrate.md) (umbrella)
|
||||
**Builds on:** [ADR-0073a](./ADR-0073a-anchor-lens-content-phase.md),
|
||||
[ADR-0073b](./ADR-0073b-anchor-lens-class-loader.md),
|
||||
[ADR-0073c](./ADR-0073c-anchor-lens-composer-wiring.md)
|
||||
**Pattern:** mirrors ADR-0072 (register R5 — telemetry + tour demo)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
L1.1–L1.3 built the anchor-lens subsystem inside-out: substrate
|
||||
content (L1.1), pack class + loader (L1.2), composer wiring with
|
||||
surface lift on cognition lemmas (L1.3). The lens is loaded,
|
||||
engages on its intended pack lemmas via the alignment graph, and
|
||||
appends `[lens(<id>):<mode>]` to the surface when engaged.
|
||||
|
||||
L1.4 closes the architectural arc by making the lens
|
||||
**operator-observable, operator-driven, and demo-falsifiable** —
|
||||
exactly what R5 did for the register subsystem. After L1.4 the
|
||||
substantive axis is feature-complete enough to compose against the
|
||||
register axis in operator-visible demos and audit pipelines.
|
||||
|
||||
The load-bearing claim L1.4 ships is the **opposite** of register-tour's:
|
||||
|
||||
```
|
||||
register-tour : per prompt, fix lens, vary register → trace_hash CONSTANT
|
||||
anchor-lens-tour : per prompt, fix register, vary lens → trace_hash DISTINCT
|
||||
```
|
||||
|
||||
Both invariants must continue to hold turn-by-turn. L1.4 packages
|
||||
the second one into a falsifiable demo.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
L1.4 ships three artifacts:
|
||||
|
||||
1. **Telemetry extension** — `TurnEvent` + `ChatResponse` gain
|
||||
`anchor_lens_id` (loaded pack id, empty for UNANCHORED) and
|
||||
`anchor_lens_mode_label` (engaged mode label this turn, empty
|
||||
when the lens didn't engage on this turn's lemma).
|
||||
2. **Operator surface** — `core chat --anchor-lens <id>` CLI flag
|
||||
wires into `RuntimeConfig.anchor_lens_id`. Invalid id fails
|
||||
fast at `ChatRuntime.__init__` (mirrors `--register`).
|
||||
3. **Narrative demo** — `core demo anchor-lens-tour` walks a fixed
|
||||
2-prompt sequence under {default_unanchored_v1, grc_logos_v1,
|
||||
he_logos_v1} and asserts the three load-bearing claims.
|
||||
|
||||
### Telemetry — `TurnEvent` shape extension
|
||||
|
||||
```python
|
||||
# core/physics/identity.py
|
||||
@dataclass(frozen=True)
|
||||
class TurnEvent:
|
||||
...
|
||||
register_id: str = ""
|
||||
register_variant_id: str = ""
|
||||
# ADR-0073d (L1.4) — operator-visible anchor-lens identity per turn.
|
||||
anchor_lens_id: str = ""
|
||||
anchor_lens_mode_label: str = ""
|
||||
```
|
||||
|
||||
* `anchor_lens_id`: the loaded pack's `lens_id`, or `""` for the
|
||||
in-memory `UNANCHORED` sentinel. Pre-L1.4 callers stay
|
||||
byte-identical (empty string is the default).
|
||||
* `anchor_lens_mode_label`: the engaged `cognitive_mode_label`
|
||||
when the lens fired on this turn's lemma, or `""` when the lens
|
||||
was loaded but did not engage (different lemma than its
|
||||
alignment scope), or when no lens was loaded at all.
|
||||
|
||||
Reading this pair tells operators three things at a glance:
|
||||
|
||||
* `(id="", mode="")` — no lens loaded
|
||||
* `(id="<x>", mode="")` — lens loaded, did not engage on this turn
|
||||
* `(id="<x>", mode="<label>")` — lens loaded, engaged, surface
|
||||
carries `[lens(<x>):<label>]`
|
||||
|
||||
### Mode-label extraction
|
||||
|
||||
The composer (L1.3) embeds the engaged mode label in the surface
|
||||
string as `[lens(<lens_id>):<mode_label>]`. At telemetry-build
|
||||
time, the runtime extracts the mode label by reading that
|
||||
annotation from the pre-decoration surface. A small deterministic
|
||||
parser:
|
||||
|
||||
```python
|
||||
# chat/runtime.py
|
||||
_ANCHOR_LENS_ANNOTATION_RE = re.compile(
|
||||
r"\[lens\(([^):]+)\):([^\]]+)\]"
|
||||
)
|
||||
|
||||
def _extract_anchor_lens_mode_label(surface: str, lens_id: str) -> str:
|
||||
"""Return the engaged mode_label if surface carries an annotation
|
||||
for ``lens_id``, else ``""``. Pure read; no side effects."""
|
||||
if not surface or not lens_id:
|
||||
return ""
|
||||
for match in _ANCHOR_LENS_ANNOTATION_RE.finditer(surface):
|
||||
if match.group(1) == lens_id:
|
||||
return match.group(2)
|
||||
return ""
|
||||
```
|
||||
|
||||
This keeps L1.4 a **read-only telemetry pass** over the L1.3
|
||||
surface. The composer remains the only source of truth for
|
||||
engagement; the runtime simply mirrors what the composer emitted.
|
||||
|
||||
### CLI — `core chat --anchor-lens <id>`
|
||||
|
||||
```python
|
||||
chat.add_argument(
|
||||
"--anchor-lens",
|
||||
metavar="LENS_ID",
|
||||
default=None,
|
||||
help=(
|
||||
"optional anchor-lens pack id (ADR-0073+); default: no "
|
||||
"lens (unanchored sentinel, byte-identical to "
|
||||
"default_unanchored_v1). Examples: default_unanchored_v1, "
|
||||
"grc_logos_v1, he_logos_v1. Invalid ids fail-fast at "
|
||||
"runtime init before the REPL starts."
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Threads into `_runtime_config_from_args` as
|
||||
`RuntimeConfig(anchor_lens_id=args.anchor_lens)`. Invalid id ⇒
|
||||
`AnchorLensError` at `ChatRuntime.__init__` — the CLI catches and
|
||||
surfaces it as `_die("invalid --anchor-lens pack id: ...", code=2)`
|
||||
before the REPL launches, exactly as `--register` does.
|
||||
|
||||
### Demo — `core demo anchor-lens-tour`
|
||||
|
||||
A narrative demo that walks a fixed two-prompt sequence under each
|
||||
ratified lens, prints a lens × prompt grid, and emits a structured
|
||||
JSON record with the three load-bearing claims:
|
||||
|
||||
```
|
||||
Lenses exercised:
|
||||
- default_unanchored_v1 (engagement baseline; no annotation expected)
|
||||
- grc_logos_v1 (engages on knowledge via ἐπιστήμη)
|
||||
- he_logos_v1 (engages on truth via אמת)
|
||||
|
||||
Prompt sequence (2 prompts; deterministic order):
|
||||
P1: "What is knowledge?"
|
||||
P2: "What is truth?"
|
||||
|
||||
Per cell, the demo records:
|
||||
surface, grounding_source, trace_hash, anchor_lens_id, anchor_lens_mode_label.
|
||||
|
||||
Load-bearing claims (asserted before exit):
|
||||
lens_ids_recorded_per_turn : True
|
||||
trace_hashes_distinct_across_lenses : True (≥ 2 distinct hashes per prompt)
|
||||
surface_propositions_distinct_across_lenses: True (≥ 2 distinct surfaces per prompt)
|
||||
no_substrate_glyph_leak : True (surfaces stay ASCII at the lens block)
|
||||
```
|
||||
|
||||
Exit code `0` iff every claim holds. Schema mirrors
|
||||
`core demo register-tour --json`.
|
||||
|
||||
### Files
|
||||
|
||||
```
|
||||
core/physics/identity.py EDIT
|
||||
- TurnEvent gains anchor_lens_id + anchor_lens_mode_label
|
||||
|
||||
chat/runtime.py EDIT
|
||||
- _extract_anchor_lens_mode_label helper
|
||||
- both stub + main paths populate the two new fields on
|
||||
TurnEvent and ChatResponse
|
||||
- ChatResponse mirrors the two fields
|
||||
|
||||
chat/telemetry.py EDIT
|
||||
- serialize_turn_event surfaces both fields
|
||||
|
||||
core/cli.py EDIT
|
||||
- cmd_chat adds --anchor-lens flag with fail-fast handler
|
||||
- _runtime_config_from_args threads anchor_lens_id
|
||||
- demo target choices add "anchor-lens-tour"
|
||||
- cmd_demo handler wires evals/anchor_lens_tour/run_tour
|
||||
- EPILOG gains "core demo anchor-lens-tour"
|
||||
|
||||
evals/anchor_lens_tour/__init__.py NEW
|
||||
evals/anchor_lens_tour/run_tour.py NEW
|
||||
|
||||
tests/test_anchor_lens_telemetry.py NEW
|
||||
- TurnEvent default empty / populated under each lens
|
||||
- serialize_turn_event surfaces both fields
|
||||
- mode_label is "" when lens loaded but no engagement this turn
|
||||
- ChatResponse mirrors event fields
|
||||
|
||||
tests/test_anchor_lens_cli.py NEW
|
||||
- _runtime_config_from_args threading
|
||||
- --anchor-lens parser wiring + default None
|
||||
- Invalid id ⇒ AnchorLensError at ChatRuntime init
|
||||
|
||||
tests/test_anchor_lens_tour_demo.py NEW
|
||||
- Three seam claims pinned individually
|
||||
- all_claims_supported overall
|
||||
- Per-cell anchor_lens_id recorded correctly
|
||||
- No substrate glyphs in the surfaces
|
||||
|
||||
docs/decisions/ADR-0073d-anchor-lens-telemetry-tour.md NEW (this file)
|
||||
```
|
||||
|
||||
### Invariants pinned at L1.4
|
||||
|
||||
```
|
||||
anchor_lens_byte_identity_null_lift (L1.2) — preserved
|
||||
anchor_lens_lifts_proposition (L1.3) — preserved
|
||||
anchor_lens_no_glyph_leak (L1.3) — preserved
|
||||
register-tour seam (R5) — preserved
|
||||
|
||||
invariant_anchor_lens_telemetry_visible (NEW):
|
||||
serialize_turn_event(event) always contains
|
||||
anchor_lens_id and anchor_lens_mode_label keys; the values
|
||||
reflect the runtime's loaded lens and the engaged mode label
|
||||
on this turn (or empty when no engagement).
|
||||
|
||||
invariant_anchor_lens_tour_seam (NEW):
|
||||
evals/anchor_lens_tour/run_tour.py asserts:
|
||||
- anchor_lens_id recorded per turn (matches runtime config)
|
||||
- trace_hash DISTINCT across lenses per prompt where lens engages
|
||||
- surface DISTINCT across lenses per prompt where lens engages
|
||||
- no substrate-block glyphs in any surface under any lens
|
||||
Exits non-zero on any violation. Pinned by
|
||||
tests/test_anchor_lens_tour_demo.py.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Capability unlocked at L1.4
|
||||
|
||||
* Operators drive any ratified lens from the CLI.
|
||||
* Every audit JSONL line names which lens was active and whether it
|
||||
engaged on that turn.
|
||||
* A single demo proves end-to-end that switching lenses moves
|
||||
trace_hash and surface — the opposite invariant from
|
||||
`register-tour`, asserted continuously.
|
||||
* The anchor-lens × register matrix is now feature-complete enough
|
||||
to compose: any combination of `--register` × `--anchor-lens` is
|
||||
operator-driveable and audit-traceable.
|
||||
|
||||
### Cognition lane — unchanged
|
||||
|
||||
Empty defaults on the new TurnEvent fields preserve byte-identical
|
||||
output. The L1.2 null-lift and L1.3 lift invariants continue to
|
||||
hold. The cognition eval public/holdout numbers stay byte-identical
|
||||
under the unanchored default.
|
||||
|
||||
### Backwards compatibility
|
||||
|
||||
* `TurnEvent` fields default to `""` — pre-L1.4 callers that
|
||||
construct `TurnEvent(...)` without the new fields keep working.
|
||||
* `ChatResponse` defaults likewise.
|
||||
* Existing telemetry consumers that read JSONL by key access continue
|
||||
to work; snapshot consumers may need to update — `anchor_lens_id`
|
||||
and `anchor_lens_mode_label` are added. Snapshot tests document
|
||||
the new shape.
|
||||
|
||||
### Performance
|
||||
|
||||
One additional regex match per turn (for `_extract_anchor_lens_
|
||||
mode_label`) when a lens is loaded. When unanchored, the helper
|
||||
early-exits on the empty `lens_id`. Negligible.
|
||||
|
||||
### Trust boundaries
|
||||
|
||||
* **`--anchor-lens` flag does not bypass ratification.** The flag
|
||||
value is passed through `_find_pack` and the loader's
|
||||
ratification check (see ADR-0073b). An unratified pack id raises
|
||||
`AnchorLensError` exactly as a config-driven load would.
|
||||
* **Mode-label extraction is read-only.** The regex parses a
|
||||
surface the composer already produced; nothing in L1.4 can
|
||||
forge a `[lens(...):...]` annotation that the composer didn't
|
||||
emit, because the regex anchors on the literal
|
||||
composer-emitted format.
|
||||
* **Telemetry stays redact-safe.** Neither `anchor_lens_id` nor
|
||||
`anchor_lens_mode_label` carries surface content; both are pack
|
||||
identifiers / mode labels. `include_content=False` paths surface
|
||||
them unconditionally because they're not content.
|
||||
* **No new mutation surface.** Pack files on disk are not modified
|
||||
by anything in L1.4.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
tests/test_anchor_lens_telemetry.py N passed
|
||||
tests/test_anchor_lens_cli.py N passed
|
||||
tests/test_anchor_lens_tour_demo.py N passed
|
||||
Curated lanes (must remain green):
|
||||
smoke / cognition / teaching / packs / runtime / algebra
|
||||
Cognition eval byte-identical under default_unanchored_v1:
|
||||
public 100 / 100 / 91.7 / 100
|
||||
core demo anchor-lens-tour exit 0
|
||||
core demo anchor-lens-tour --json stable schema
|
||||
core demo register-tour exit 0
|
||||
(R5 seam still
|
||||
holds; both
|
||||
tours coexist)
|
||||
```
|
||||
|
||||
The tour exit code is the canonical L1.4 gate — if `anchor-lens-tour`
|
||||
ever exits non-zero in CI, the substantive axis has regressed.
|
||||
|
||||
---
|
||||
|
||||
## Composition with the register axis
|
||||
|
||||
`core demo register-tour` and `core demo anchor-lens-tour` test
|
||||
opposite invariants and both must pass continuously:
|
||||
|
||||
```
|
||||
register-tour : trace_hash CONSTANT across registers
|
||||
anchor-lens-tour : trace_hash DISTINCT across lenses
|
||||
```
|
||||
|
||||
A future two-axis tour (`anchor-lens × register × prompts`) is
|
||||
natural follow-on work but deferred — single-axis tours land first,
|
||||
composition tour after.
|
||||
|
||||
---
|
||||
|
||||
## Open questions deferred
|
||||
|
||||
* **Combined CLI flag composition.** `core chat --register X
|
||||
--anchor-lens Y` already works at the wiring level (both flags
|
||||
thread into `RuntimeConfig`). A combined "audit view" demo
|
||||
showing the orthogonality matrix is a future ADR.
|
||||
* **TurnVerdicts integration.** Should TurnVerdicts carry
|
||||
`anchor_lens_id` / `register_id` alongside safety/ethics? Yes
|
||||
eventually, but L1.4 keeps the fields on TurnEvent itself for now.
|
||||
A unifying ADR can consolidate later.
|
||||
* **Mid-session lens switching.** Today the lens is loaded once at
|
||||
`ChatRuntime.__init__`. A `runtime.set_anchor_lens(<id>)` API
|
||||
would let an operator switch live. Deferred — needs careful
|
||||
thinking about replay equivalence across the switch (mirror of
|
||||
the same deferral for register).
|
||||
6
evals/anchor_lens_tour/__init__.py
Normal file
6
evals/anchor_lens_tour/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"""Anchor-lens tour — narrative walkthrough demonstrating the
|
||||
substantive-axis seam (ADR-0073). Same prompts under different
|
||||
lenses produce structurally different surfaces and trace_hashes —
|
||||
the *opposite* invariant from ``core demo register-tour``. See
|
||||
``run_tour.py`` for the entry point.
|
||||
"""
|
||||
285
evals/anchor_lens_tour/run_tour.py
Normal file
285
evals/anchor_lens_tour/run_tour.py
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
"""Anchor-lens tour — narrative walkthrough demonstrating the
|
||||
substantive-axis seam (ADR-0073 → ADR-0073d).
|
||||
|
||||
Walks a fixed two-prompt sequence under three ratified lenses
|
||||
({default_unanchored_v1, grc_logos_v1, he_logos_v1}) and prints a
|
||||
grid of per-cell ``(surface, grounding_source, trace_hash,
|
||||
anchor_lens_id, anchor_lens_mode_label)``.
|
||||
|
||||
Load-bearing claims asserted before exit (L1.4 invariants — the
|
||||
*opposite* of register-tour's invariants):
|
||||
|
||||
* ``lens_ids_recorded_per_turn`` — every TurnEvent records the
|
||||
loaded lens id (empty for unanchored, lens_id otherwise).
|
||||
* ``trace_hashes_distinct_across_lenses`` — for each prompt where
|
||||
any lens engages, at least two distinct trace_hashes appear
|
||||
across the three runs. This is the substantive-axis claim:
|
||||
switching the lens moves the proposition, not just the surface
|
||||
text.
|
||||
* ``surface_propositions_distinct_across_lenses`` — at least one
|
||||
prompt yields at least two distinct surfaces across the lens
|
||||
triple.
|
||||
* ``no_substrate_glyph_leak`` — no Greek / Hebrew / Syriac / Arabic
|
||||
letter blocks appear in any cell's surface under any lens
|
||||
(ADR-0073c hard gate, re-asserted in tour scope).
|
||||
|
||||
Exit code 0 iff every claim holds. Designed for ``core demo
|
||||
anchor-lens-tour`` and the corresponding
|
||||
``tests/test_anchor_lens_tour_demo.py`` gate.
|
||||
|
||||
Composition with register-tour
|
||||
------------------------------
|
||||
``register-tour`` asserts trace_hash CONSTANT across registers.
|
||||
``anchor-lens-tour`` asserts trace_hash DISTINCT across lenses.
|
||||
Both must hold continuously; failure of either breaks the
|
||||
orthogonality seam claimed by ADR-0073.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
from core.cognition.pipeline import CognitiveTurnPipeline
|
||||
from core.config import RuntimeConfig
|
||||
|
||||
|
||||
_LENSES = (
|
||||
"default_unanchored_v1",
|
||||
"grc_logos_v1",
|
||||
"he_logos_v1",
|
||||
)
|
||||
|
||||
# Prompts are chosen so each non-trivial lens engages on at least one
|
||||
# of them (grc_logos_v1 on knowledge, he_logos_v1 on truth). The
|
||||
# unanchored baseline never engages, demonstrating the null-lift floor
|
||||
# explicitly inside the tour.
|
||||
_PROMPTS = (
|
||||
"What is knowledge?",
|
||||
"What is truth?",
|
||||
)
|
||||
|
||||
#: Forbidden Unicode blocks: substrate letter scripts that anchor lens
|
||||
#: must not leak (ADR-0073c hard gate).
|
||||
_FORBIDDEN_BLOCKS: tuple[tuple[int, int, str], ...] = (
|
||||
(0x0370, 0x03FF, "Greek and Coptic"),
|
||||
(0x1F00, 0x1FFF, "Greek Extended"),
|
||||
(0x0590, 0x05FF, "Hebrew"),
|
||||
(0x0700, 0x074F, "Syriac"),
|
||||
(0x0600, 0x06FF, "Arabic"),
|
||||
)
|
||||
|
||||
|
||||
_VERBOSE = True
|
||||
|
||||
|
||||
def _say(*args: Any, **kwargs: Any) -> None:
|
||||
if _VERBOSE:
|
||||
print(*args, **kwargs)
|
||||
|
||||
|
||||
def _print_header() -> None:
|
||||
_say()
|
||||
_say("=" * 72)
|
||||
_say(" CORE Anchor-lens Tour — substantive-axis seam in three lenses")
|
||||
_say("=" * 72)
|
||||
_say(
|
||||
" Same prompt sequence run under three ratified anchor-lens packs.\n"
|
||||
" Claim: switching lens MOVES the proposition — trace_hash and\n"
|
||||
" surface differ across lenses where engagement fires (ADR-0073\n"
|
||||
" L1.3 lift + L1.4 telemetry). This is the *opposite* invariant\n"
|
||||
" from `core demo register-tour`, which asserts trace_hash CONSTANT\n"
|
||||
" across registers. Both invariants must hold continuously."
|
||||
)
|
||||
_say()
|
||||
|
||||
|
||||
def _substrate_glyph_violations(surface: str) -> list[tuple[int, str, str]]:
|
||||
out: list[tuple[int, str, str]] = []
|
||||
for i, ch in enumerate(surface):
|
||||
cp = ord(ch)
|
||||
for start, end, label in _FORBIDDEN_BLOCKS:
|
||||
if start <= cp <= end:
|
||||
out.append((i, ch, label))
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def _run_one_lens(lens_id: str) -> list[dict[str, Any]]:
|
||||
"""Run the prompt sequence under ``lens_id`` and return per-cell records."""
|
||||
runtime = ChatRuntime(config=RuntimeConfig(anchor_lens_id=lens_id))
|
||||
pipeline = CognitiveTurnPipeline(runtime=runtime)
|
||||
cells: list[dict[str, Any]] = []
|
||||
for prompt in _PROMPTS:
|
||||
result = pipeline.run(prompt)
|
||||
turn_event = runtime.turn_log[-1]
|
||||
cells.append(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"surface": turn_event.surface,
|
||||
"grounding_source": getattr(turn_event, "grounding_source", ""),
|
||||
"trace_hash": result.trace_hash,
|
||||
"anchor_lens_id": getattr(turn_event, "anchor_lens_id", ""),
|
||||
"anchor_lens_mode_label": getattr(
|
||||
turn_event, "anchor_lens_mode_label", ""
|
||||
),
|
||||
}
|
||||
)
|
||||
return cells
|
||||
|
||||
|
||||
def _print_grid(grid: dict[str, list[dict[str, Any]]]) -> None:
|
||||
for prompt_idx, prompt in enumerate(_PROMPTS):
|
||||
_say(f" P{prompt_idx + 1}: {prompt!r}")
|
||||
for lens_id in _LENSES:
|
||||
cell = grid[lens_id][prompt_idx]
|
||||
_say(f" {lens_id:24s} surface = {cell['surface']!r}")
|
||||
mode = cell["anchor_lens_mode_label"] or "(no engagement)"
|
||||
_say(
|
||||
f" {'':24s} trace_hash = {cell['trace_hash'][:12]}… "
|
||||
f"mode_label = {mode}"
|
||||
)
|
||||
_say()
|
||||
|
||||
|
||||
def _check_claims(
|
||||
grid: dict[str, list[dict[str, Any]]],
|
||||
) -> dict[str, Any]:
|
||||
"""Return the four load-bearing seam-claim booleans + evidence."""
|
||||
lens_ids_recorded = True
|
||||
surfaces_vary_at_least_once = False
|
||||
trace_hashes_distinct_when_engaged = True
|
||||
glyph_violations: list[str] = []
|
||||
|
||||
per_prompt_evidence: list[dict[str, Any]] = []
|
||||
for prompt_idx, prompt in enumerate(_PROMPTS):
|
||||
cells = [grid[lens_id][prompt_idx] for lens_id in _LENSES]
|
||||
|
||||
# Telemetry visibility: each cell's anchor_lens_id matches the
|
||||
# lens we configured (empty when the lens_id was the
|
||||
# unanchored sentinel — pack id "default_unanchored_v1" still
|
||||
# records as that string).
|
||||
for lens_id, cell in zip(_LENSES, cells):
|
||||
if cell["anchor_lens_id"] != lens_id:
|
||||
lens_ids_recorded = False
|
||||
|
||||
# Glyph-leak gate.
|
||||
for lens_id, cell in zip(_LENSES, cells):
|
||||
violations = _substrate_glyph_violations(cell["surface"])
|
||||
for pos, ch, block in violations:
|
||||
glyph_violations.append(
|
||||
f"{prompt!r} × {lens_id}: substrate glyph "
|
||||
f"{ch!r} (block={block}) at pos {pos}"
|
||||
)
|
||||
|
||||
# Distinctness claims. Any prompt where ≥2 distinct
|
||||
# surfaces / trace_hashes appear across the lens triple
|
||||
# counts toward the "vary at least once" gate. For the
|
||||
# "distinct when engaged" gate, we require that if any lens
|
||||
# cell has a non-empty mode_label (engaged), its trace_hash
|
||||
# must differ from the unanchored baseline.
|
||||
unanchored_cell = cells[0]
|
||||
surface_set = {c["surface"] for c in cells}
|
||||
if len(surface_set) > 1:
|
||||
surfaces_vary_at_least_once = True
|
||||
for c in cells[1:]:
|
||||
if c["anchor_lens_mode_label"]:
|
||||
# Lens engaged; require trace_hash divergence from baseline.
|
||||
if c["trace_hash"] == unanchored_cell["trace_hash"]:
|
||||
trace_hashes_distinct_when_engaged = False
|
||||
|
||||
per_prompt_evidence.append(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"distinct_surfaces_count": len(surface_set),
|
||||
"distinct_trace_hashes_count": len(
|
||||
{c["trace_hash"] for c in cells}
|
||||
),
|
||||
"lenses_engaged": [
|
||||
c["anchor_lens_id"]
|
||||
for c in cells
|
||||
if c["anchor_lens_mode_label"]
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"lens_ids_recorded_per_turn": lens_ids_recorded,
|
||||
"trace_hashes_distinct_across_lenses": trace_hashes_distinct_when_engaged,
|
||||
"surface_propositions_distinct_across_lenses": surfaces_vary_at_least_once,
|
||||
"no_substrate_glyph_leak": not glyph_violations,
|
||||
"per_prompt_evidence": per_prompt_evidence,
|
||||
"glyph_violations": glyph_violations,
|
||||
}
|
||||
|
||||
|
||||
def run_tour(*, emit_json: bool = False) -> dict[str, Any]:
|
||||
"""Run the anchor-lens tour end-to-end and return a structured report."""
|
||||
global _VERBOSE
|
||||
_VERBOSE = not emit_json
|
||||
|
||||
if not emit_json:
|
||||
_print_header()
|
||||
|
||||
grid: dict[str, list[dict[str, Any]]] = {}
|
||||
for lens_id in _LENSES:
|
||||
if not emit_json:
|
||||
_say(f" Running lens: {lens_id}")
|
||||
grid[lens_id] = _run_one_lens(lens_id)
|
||||
if not emit_json:
|
||||
_say()
|
||||
_say("-" * 72)
|
||||
_say(" Lens × prompt grid")
|
||||
_say("-" * 72)
|
||||
_print_grid(grid)
|
||||
|
||||
claims = _check_claims(grid)
|
||||
all_supported = (
|
||||
claims["lens_ids_recorded_per_turn"]
|
||||
and claims["trace_hashes_distinct_across_lenses"]
|
||||
and claims["surface_propositions_distinct_across_lenses"]
|
||||
and claims["no_substrate_glyph_leak"]
|
||||
)
|
||||
|
||||
if not emit_json:
|
||||
_say("=" * 72)
|
||||
_say(" Load-bearing seam claims (L1.4 invariant)")
|
||||
_say("=" * 72)
|
||||
_say(
|
||||
f" lens_ids_recorded_per_turn : "
|
||||
f"{claims['lens_ids_recorded_per_turn']}"
|
||||
)
|
||||
_say(
|
||||
f" trace_hashes_distinct_across_lenses : "
|
||||
f"{claims['trace_hashes_distinct_across_lenses']}"
|
||||
)
|
||||
_say(
|
||||
f" surface_propositions_distinct_across_lenses : "
|
||||
f"{claims['surface_propositions_distinct_across_lenses']}"
|
||||
)
|
||||
_say(
|
||||
f" no_substrate_glyph_leak : "
|
||||
f"{claims['no_substrate_glyph_leak']}"
|
||||
)
|
||||
_say()
|
||||
_say(f" all_claims_supported : {all_supported}")
|
||||
_say()
|
||||
|
||||
return {
|
||||
"lenses": list(_LENSES),
|
||||
"prompts": list(_PROMPTS),
|
||||
"grid": grid,
|
||||
"claims": claims,
|
||||
"all_claims_supported": all_supported,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
emit_json = "--json" in sys.argv
|
||||
report = run_tour(emit_json=emit_json)
|
||||
if emit_json:
|
||||
print(json.dumps(report, indent=2, sort_keys=True, default=str))
|
||||
sys.exit(0 if report["all_claims_supported"] else 1)
|
||||
99
tests/test_anchor_lens_cli.py
Normal file
99
tests/test_anchor_lens_cli.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""Anchor-lens CLI — ``core chat --anchor-lens <id>`` flag wiring
|
||||
(ADR-0073d / L1.4).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
import pytest
|
||||
|
||||
from core.cli import _runtime_config_from_args
|
||||
from core.config import DEFAULT_CONFIG
|
||||
|
||||
|
||||
def _namespace(**overrides) -> argparse.Namespace:
|
||||
"""Build a Namespace shaped like cmd_chat's args."""
|
||||
defaults = dict(
|
||||
output_language=DEFAULT_CONFIG.output_language,
|
||||
frame_pack=None,
|
||||
pack=None,
|
||||
max_tokens=DEFAULT_CONFIG.max_tokens,
|
||||
no_cross_language_recall=False,
|
||||
allow_cross_language_generation=False,
|
||||
vault_reproject_interval=DEFAULT_CONFIG.vault_reproject_interval,
|
||||
no_salience=False,
|
||||
salience_top_k=DEFAULT_CONFIG.salience_top_k,
|
||||
inhibition_threshold=DEFAULT_CONFIG.inhibition_threshold,
|
||||
inner_loop_admissibility=False,
|
||||
admissibility_threshold=0.0,
|
||||
identity="",
|
||||
register=None,
|
||||
anchor_lens=None,
|
||||
)
|
||||
defaults.update(overrides)
|
||||
return argparse.Namespace(**defaults)
|
||||
|
||||
|
||||
def test_runtime_config_default_anchor_lens_is_none():
|
||||
cfg = _runtime_config_from_args(_namespace())
|
||||
assert cfg.anchor_lens_id is None
|
||||
|
||||
|
||||
def test_runtime_config_threads_anchor_lens_flag():
|
||||
cfg = _runtime_config_from_args(_namespace(anchor_lens="grc_logos_v1"))
|
||||
assert cfg.anchor_lens_id == "grc_logos_v1"
|
||||
|
||||
|
||||
def test_runtime_config_empty_anchor_lens_treated_as_none():
|
||||
cfg = _runtime_config_from_args(_namespace(anchor_lens=""))
|
||||
assert cfg.anchor_lens_id is None
|
||||
|
||||
|
||||
def test_runtime_config_anchor_lens_threads_into_runtime():
|
||||
from chat.runtime import ChatRuntime
|
||||
|
||||
cfg = _runtime_config_from_args(_namespace(anchor_lens="grc_logos_v1"))
|
||||
rt = ChatRuntime(config=cfg)
|
||||
assert rt.anchor_lens_id == "grc_logos_v1"
|
||||
assert rt.anchor_lens.lens_id == "grc_logos_v1"
|
||||
assert not rt.anchor_lens.is_unanchored()
|
||||
|
||||
|
||||
def test_runtime_config_unratified_anchor_lens_id_fails_fast():
|
||||
from chat.runtime import ChatRuntime
|
||||
from packs.anchor_lens.loader import AnchorLensError
|
||||
|
||||
cfg = _runtime_config_from_args(_namespace(anchor_lens="bogus_v999"))
|
||||
with pytest.raises(AnchorLensError):
|
||||
ChatRuntime(config=cfg)
|
||||
|
||||
|
||||
def test_chat_parser_accepts_anchor_lens_flag():
|
||||
from core.cli import build_parser
|
||||
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args(["chat", "--anchor-lens", "grc_logos_v1"])
|
||||
assert ns.anchor_lens == "grc_logos_v1"
|
||||
|
||||
|
||||
def test_chat_parser_anchor_lens_defaults_none():
|
||||
from core.cli import build_parser
|
||||
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args(["chat"])
|
||||
assert ns.anchor_lens is None
|
||||
|
||||
|
||||
def test_chat_parser_accepts_anchor_lens_and_register_together():
|
||||
"""Composition of orthogonal axes via the CLI."""
|
||||
from core.cli import build_parser
|
||||
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args([
|
||||
"chat",
|
||||
"--register", "convivial_v1",
|
||||
"--anchor-lens", "grc_logos_v1",
|
||||
])
|
||||
assert ns.register == "convivial_v1"
|
||||
assert ns.anchor_lens == "grc_logos_v1"
|
||||
176
tests/test_anchor_lens_telemetry.py
Normal file
176
tests/test_anchor_lens_telemetry.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
"""Anchor-lens telemetry — TurnEvent + serialize_turn_event carry
|
||||
anchor_lens_id and anchor_lens_mode_label (ADR-0073d / L1.4).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from chat.runtime import (
|
||||
ChatRuntime,
|
||||
_extract_anchor_lens_mode_label,
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
# ---------- TurnEvent shape ----------
|
||||
|
||||
|
||||
def test_turn_event_defaults_anchor_lens_fields_empty():
|
||||
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.anchor_lens_id == ""
|
||||
assert event.anchor_lens_mode_label == ""
|
||||
|
||||
|
||||
def test_serialize_turn_event_emits_anchor_lens_fields():
|
||||
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,
|
||||
anchor_lens_id="grc_logos_v1",
|
||||
anchor_lens_mode_label="systematic",
|
||||
)
|
||||
payload = serialize_turn_event(event)
|
||||
assert payload["anchor_lens_id"] == "grc_logos_v1"
|
||||
assert payload["anchor_lens_mode_label"] == "systematic"
|
||||
|
||||
|
||||
# ---------- end-to-end runtime ----------
|
||||
|
||||
|
||||
def test_unanchored_runtime_emits_empty_anchor_lens_fields():
|
||||
runtime = ChatRuntime(config=RuntimeConfig())
|
||||
sink = JsonlBufferSink()
|
||||
runtime.attach_telemetry_sink(sink)
|
||||
runtime.chat("What is knowledge?")
|
||||
last = _decode(sink.lines[-1])
|
||||
assert last["anchor_lens_id"] == ""
|
||||
assert last["anchor_lens_mode_label"] == ""
|
||||
|
||||
|
||||
def test_grc_logos_engaged_turn_emits_mode_label():
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(anchor_lens_id="grc_logos_v1")
|
||||
)
|
||||
sink = JsonlBufferSink()
|
||||
runtime.attach_telemetry_sink(sink)
|
||||
runtime.chat("What is knowledge?")
|
||||
last = _decode(sink.lines[-1])
|
||||
assert last["anchor_lens_id"] == "grc_logos_v1"
|
||||
assert last["anchor_lens_mode_label"] == "systematic"
|
||||
|
||||
|
||||
def test_he_logos_engaged_turn_emits_mode_label():
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(anchor_lens_id="he_logos_v1")
|
||||
)
|
||||
sink = JsonlBufferSink()
|
||||
runtime.attach_telemetry_sink(sink)
|
||||
runtime.chat("What is truth?")
|
||||
last = _decode(sink.lines[-1])
|
||||
assert last["anchor_lens_id"] == "he_logos_v1"
|
||||
assert last["anchor_lens_mode_label"] == "covenant-verity"
|
||||
|
||||
|
||||
def test_lens_loaded_but_not_engaged_emits_empty_mode_label():
|
||||
"""grc_logos_v1 doesn't engage on the en lemma 'truth' — lens_id
|
||||
is recorded but mode_label stays empty."""
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(anchor_lens_id="grc_logos_v1")
|
||||
)
|
||||
sink = JsonlBufferSink()
|
||||
runtime.attach_telemetry_sink(sink)
|
||||
runtime.chat("What is truth?")
|
||||
last = _decode(sink.lines[-1])
|
||||
assert last["anchor_lens_id"] == "grc_logos_v1"
|
||||
assert last["anchor_lens_mode_label"] == ""
|
||||
|
||||
|
||||
def test_chat_response_mirrors_event_anchor_lens_fields():
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(anchor_lens_id="grc_logos_v1")
|
||||
)
|
||||
response = runtime.chat("What is knowledge?")
|
||||
event = runtime.turn_log[-1]
|
||||
assert response.anchor_lens_id == event.anchor_lens_id == "grc_logos_v1"
|
||||
assert (
|
||||
response.anchor_lens_mode_label
|
||||
== event.anchor_lens_mode_label
|
||||
== "systematic"
|
||||
)
|
||||
|
||||
|
||||
def test_default_unanchored_pack_emits_pack_id_and_empty_mode():
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(anchor_lens_id="default_unanchored_v1")
|
||||
)
|
||||
sink = JsonlBufferSink()
|
||||
runtime.attach_telemetry_sink(sink)
|
||||
runtime.chat("What is knowledge?")
|
||||
last = _decode(sink.lines[-1])
|
||||
assert last["anchor_lens_id"] == "default_unanchored_v1"
|
||||
assert last["anchor_lens_mode_label"] == ""
|
||||
|
||||
|
||||
# ---------- mode-label extractor unit ----------
|
||||
|
||||
|
||||
def test_extractor_returns_empty_for_empty_surface():
|
||||
assert _extract_anchor_lens_mode_label("", "grc_logos_v1") == ""
|
||||
|
||||
|
||||
def test_extractor_returns_empty_for_empty_lens_id():
|
||||
assert _extract_anchor_lens_mode_label(
|
||||
"X [lens(grc_logos_v1):systematic].", "",
|
||||
) == ""
|
||||
|
||||
|
||||
def test_extractor_returns_empty_when_no_annotation_present():
|
||||
assert _extract_anchor_lens_mode_label(
|
||||
"Plain surface no lens.", "grc_logos_v1",
|
||||
) == ""
|
||||
|
||||
|
||||
def test_extractor_finds_annotation():
|
||||
out = _extract_anchor_lens_mode_label(
|
||||
"X. pack-grounded (Y) [lens(grc_logos_v1):systematic].",
|
||||
"grc_logos_v1",
|
||||
)
|
||||
assert out == "systematic"
|
||||
|
||||
|
||||
def test_extractor_ignores_annotation_for_different_lens_id():
|
||||
"""Defensive: a surface might carry an annotation for a different
|
||||
lens (composer bug, future multi-lens compositions). Extractor
|
||||
only returns mode_label for the requested lens_id."""
|
||||
out = _extract_anchor_lens_mode_label(
|
||||
"X. [lens(he_logos_v1):covenant-verity].",
|
||||
"grc_logos_v1",
|
||||
)
|
||||
assert out == ""
|
||||
87
tests/test_anchor_lens_tour_demo.py
Normal file
87
tests/test_anchor_lens_tour_demo.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""Anchor-lens tour demo — load-bearing seam claims (ADR-0073d / L1.4).
|
||||
|
||||
Pins the four seam claims:
|
||||
|
||||
* lens_ids_recorded_per_turn — telemetry visibility holds.
|
||||
* trace_hashes_distinct_across_lenses — substantive lift fires.
|
||||
* surface_propositions_distinct_across_lenses — surface lift fires.
|
||||
* no_substrate_glyph_leak — ASCII contract holds.
|
||||
|
||||
Any one failing is the L1.4 architectural-regression signal.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from evals.anchor_lens_tour.run_tour import _LENSES, _PROMPTS, run_tour
|
||||
|
||||
|
||||
def test_tour_returns_structured_report():
|
||||
report = run_tour(emit_json=True)
|
||||
assert set(report) >= {
|
||||
"lenses", "prompts", "grid", "claims", "all_claims_supported",
|
||||
}
|
||||
assert list(report["lenses"]) == list(_LENSES)
|
||||
assert list(report["prompts"]) == list(_PROMPTS)
|
||||
|
||||
|
||||
def test_tour_lens_ids_recorded_per_turn():
|
||||
report = run_tour(emit_json=True)
|
||||
assert report["claims"]["lens_ids_recorded_per_turn"] is True
|
||||
|
||||
|
||||
def test_tour_trace_hashes_distinct_across_lenses():
|
||||
"""L1.3 lift claim, restated as a falsifiable demo invariant — and
|
||||
the *opposite* of register-tour's trace_hashes_identical claim."""
|
||||
report = run_tour(emit_json=True)
|
||||
assert report["claims"]["trace_hashes_distinct_across_lenses"] is True
|
||||
|
||||
|
||||
def test_tour_surface_propositions_distinct_across_lenses():
|
||||
report = run_tour(emit_json=True)
|
||||
assert report["claims"]["surface_propositions_distinct_across_lenses"] is True
|
||||
|
||||
|
||||
def test_tour_no_substrate_glyph_leak():
|
||||
"""ADR-0073c hard gate, re-asserted in tour scope."""
|
||||
report = run_tour(emit_json=True)
|
||||
assert report["claims"]["no_substrate_glyph_leak"] is True
|
||||
assert report["claims"]["glyph_violations"] == []
|
||||
|
||||
|
||||
def test_tour_all_claims_supported():
|
||||
"""Canonical L1.4 gate — every claim must hold or exit non-zero."""
|
||||
report = run_tour(emit_json=True)
|
||||
assert report["all_claims_supported"] is True
|
||||
|
||||
|
||||
def test_tour_grid_carries_anchor_lens_id_per_cell():
|
||||
"""Each grid cell records the lens that produced it."""
|
||||
report = run_tour(emit_json=True)
|
||||
for lens_id in _LENSES:
|
||||
cells = report["grid"][lens_id]
|
||||
assert len(cells) == len(_PROMPTS)
|
||||
for cell in cells:
|
||||
assert cell["anchor_lens_id"] == lens_id
|
||||
|
||||
|
||||
def test_tour_unanchored_cells_have_empty_mode_label():
|
||||
"""The unanchored baseline never engages, so its mode_label is
|
||||
always empty regardless of prompt."""
|
||||
report = run_tour(emit_json=True)
|
||||
for cell in report["grid"]["default_unanchored_v1"]:
|
||||
assert cell["anchor_lens_mode_label"] == ""
|
||||
|
||||
|
||||
def test_tour_engaged_cells_carry_mode_label():
|
||||
"""grc_logos_v1 engages on knowledge; he_logos_v1 engages on truth."""
|
||||
report = run_tour(emit_json=True)
|
||||
grc_knowledge = next(
|
||||
c for c in report["grid"]["grc_logos_v1"]
|
||||
if c["prompt"] == "What is knowledge?"
|
||||
)
|
||||
assert grc_knowledge["anchor_lens_mode_label"] == "systematic"
|
||||
he_truth = next(
|
||||
c for c in report["grid"]["he_logos_v1"]
|
||||
if c["prompt"] == "What is truth?"
|
||||
)
|
||||
assert he_truth["anchor_lens_mode_label"] == "covenant-verity"
|
||||
Loading…
Reference in a new issue