core/chat
Shay 9b1b63b253 feat(anchor_lens): ADR-0073b — L1.2 class + loader + unanchored sentinel
L1.2 of the anchor-lens inside-out rollout — pack class, loader,
ratified sentinel pack, and runtime threading.  Mirrors the
ADR-0068 register-class pattern exactly.  No composer consumes the
lens yet — that's L1.3.

AnchorLens frozen dataclass (packs/anchor_lens/loader.py)
  - lens_id / version / description / display_name
  - primary_substrate ∈ {grc, he, en, none}
  - semantic_domain_preferences: tuple[str, ...] (ordered, ≤64 atoms
    of ≤64 chars each, no duplicates)
  - cognitive_mode_label: str (≤64 chars)
  - mastery_report_sha256
  - is_unanchored() / is_null_lens() predicates
  - unanchored() classmethod + module-level UNANCHORED singleton

Loader contract (mirror of packs/register/loader.py)
  - safe_pack_id path-traversal rejection
  - Schema validation + envelope bounds checks
  - Companion mastery report self-seal + report_sha256 verification
  - CORE_ALLOW_UNRATIFIED_ANCHOR_LENS=1 dev bypass
  - require_ratified default True
  - No truth-path imports (pinned by seam test)

default_unanchored_v1 ratified pack
  - Null lens: primary_substrate="none", empty preferences,
    empty cognitive_mode_label
  - Self-sealed at b3235072fdbb2219...
  - Ratification method: byte_identity_null_lift
  - scripts/ratify_anchor_lens_packs.py L1.2 gate accepts only
    null lenses; L1.3 will widen.  Idempotent.

RuntimeConfig threading
  - new field: anchor_lens_id: str | None = None
  - new constant: DEFAULT_ANCHOR_LENS = "default_unanchored_v1"
  - ChatRuntime.__init__ loads the lens (None → AnchorLens.
    unanchored(); otherwise load_anchor_lens(id)) and stores as
    self.anchor_lens + self.anchor_lens_id.  Invalid ids fail-fast
    at init via AnchorLensError, not at first turn.
  - No composer reads the attribute yet.

Tests pinned (37 total)
  - tests/test_anchor_lens_pack_loader.py (24) — load happy path,
    sentinel structural identity, invalid id rejection (traversal,
    empty, slashes, missing), ratification bypass paths, companion
    SHA mismatch, bounds (substrate / preferences / atoms / label /
    duplicates / capacity), field-missing, lens_id mismatch with
    filename, unsupported schema_version.
  - tests/test_anchor_lens_null_lift.py (4) — load-bearing L1.2
    invariant `anchor_lens_byte_identity_null_lift`: full public
    cognition lane byte-identical for surface, trace_hash, and
    aggregate metrics between anchor_lens_id=None and
    "default_unanchored_v1".
  - tests/test_anchor_lens_pack_seam.py (9) — AST refuses any
    `packs.anchor_lens` import from truth-path modules (cognition /
    trace / pipeline / intent / propagation / vault / algebra) AND
    refuses any truth-path import from the loader itself.

Lane evidence
  - All 37 anchor-lens tests pass.
  - python -m core.cli eval cognition → public 100/100/91.7/100
    byte-identical (lens loaded but no composer reads it).
  - core demo register-tour --json → all_claims_supported: True
    (R5 seam still holds; L1.2 doesn't perturb register).
  - Full lane: 2669 passed / 4 skipped / 1 pre-existing failure
    (+37 over L1.1's 2632; the one failure remains
    test_all_preamble_explains_combined_run, unrelated).

Trust boundaries (per CLAUDE.md / ADR-0051)
  - safe_pack_id path-traversal rejection at loader entry.
  - No dynamic imports.
  - Loader is read-only; mutation only via ratify script.
  - Seam test refuses any new anchor-lens import upstream of the
    realizer.  L1.3 will widen the allow-list to include composer
    files at the same time it adds composer behaviour — exactly the
    way the register seam was widened at R2.

What L1.2 deliberately does NOT do
  - No composer consumes the lens (that's L1.3).
  - No TurnEvent / ChatResponse telemetry fields (L1.4).
  - No `core chat --anchor-lens` CLI flag (L1.4).
  - No anchor-lens-tour demo (L1.4).
2026-05-19 19:46:34 -07:00
..
__init__.py Add live chat runtime 2026-05-13 20:40:56 -07:00
__main__.py Add live chat runtime 2026-05-13 20:40:56 -07:00
anaphora.py feat(adr-0066): session-thread context + opt-in anaphora prefix (Phase 3.1 + 3.2) 2026-05-18 17:01:34 -07:00
cross_pack_grounding.py feat(register): R1–R4 register pack subsystem — deterministic surface variation 2026-05-19 16:52:36 -07:00
example_surface.py feat(register): R1–R4 register pack subsystem — deterministic surface variation 2026-05-19 16:52:36 -07:00
narrative_surface.py feat(register): R1–R4 register pack subsystem — deterministic surface variation 2026-05-19 16:52:36 -07:00
oov_surface.py feat(adr-0066): NARRATIVE + EXAMPLE intents with multi-clause composers (Phase 3.3 + 3.4) 2026-05-18 17:01:55 -07:00
pack_grounding.py feat(register): R1–R4 register pack subsystem — deterministic surface variation 2026-05-19 16:52:36 -07:00
pack_resolver.py feat(pack-resolver): gloss resolver with lexicon-residency + dual-checksum hardening 2026-05-19 07:24:36 -07:00
pack_surface_candidate.py feat(pack-grounding): selector-ready gloss wiring via PackSurfaceCandidate 2026-05-19 07:26:46 -07:00
partial_surface.py feat(adr-0065): OOV cliff → five-tier honesty gradient (Phase 2.1 + 2.2) 2026-05-18 16:41:45 -07:00
refusal.py feat(adr-0037,adr-0038): per-predicate ethics refusal + hedge injection 2026-05-17 21:23:28 -07:00
register_variation.py feat(register): R5 — operator-visible register telemetry + tour demo 2026-05-19 19:03:07 -07:00
runtime.py feat(anchor_lens): ADR-0073b — L1.2 class + loader + unanchored sentinel 2026-05-19 19:46:34 -07:00
teaching_grounding.py feat(register): R1–R4 register pack subsystem — deterministic surface variation 2026-05-19 16:52:36 -07:00
telemetry.py feat(register): R5 — operator-visible register telemetry + tour demo 2026-05-19 19:03:07 -07:00
thread_context.py feat(adr-0066): session-thread context + opt-in anaphora prefix (Phase 3.1 + 3.2) 2026-05-18 17:01:34 -07:00
verdicts.py feat(adr-0039): audit completeness — TurnVerdicts bundle, stub TurnEvent, hedge_injected 2026-05-17 21:32:46 -07:00