# ADR-0073 — Anchor lens: substrate-driven substantive variation

**Status:** Accepted (umbrella ratified; sub-ADRs L1.1–L1.4 each ratify in isolation)
**Date:** 2026-05-19
**Ratified:** 2026-05-19
**Author:** Shay
**Phase:** Plan Phase L1 (anchor lens, inside-out)
**Builds on:** ADR-0048 (pack-grounded surface), ADR-0050 (COMPARISON),
ADR-0052 (teaching-grounded surface), ADR-0063 (cross-pack resolver),
ADR-0064 (cross-pack teaching), ADR-0068..0072 (register subsystem)
**Composes against:** the register axis (ADR-0068..0072) as an
*orthogonal* axis — register varies surface text, anchor lens varies
the *proposition itself*.

---

## Context

The register subsystem (R1–R5) closed the **presentation axis**: same
truth, different wording.  That is cosmetic by design — every
register holds `grounding_source` and `trace_hash` byte-identical
across {neutral, terse, convivial} for the same input (`core demo
register-tour` asserts this turn-by-turn).

The original deterministic-AI critique CORE answers (`identity informs
doing`, `truth is coherent`) asks for something stronger: the system
saying **genuinely different things from different conceptual
substrates**, not the same thing in different clothes.  That axis is
**substantive variation**.

The substrate to deliver it already sits half-built on disk.

* `packs/data/grc_logos_micro_v1` (11 entries, alignment),
  `grc_logos_cognition_v1` (20 entries, **no alignment**),
  `he_logos_micro_v1` (9 entries, alignment),
  `he_core_cognition_v1` (20 entries, **no alignment**) —
  all four mounted by default in `RuntimeConfig.input_packs`.
* Cross-language binding is **shared `semantic_domains` atoms**, not
  transliteration tables: the same load-bearing tag (e.g.
  `logos.aletheia.verity`) appears across grc/he/en, and the
  alignment file binds entry-id pairs with evidence ids.  This is the
  deterministic pivot an anchor-lens composer would traverse.
* `allow_cross_language_recall=True` already; cross-language
  *generation* is deliberately gated off.

Three honest gaps separate the substrate from anchor-lens-driven
surfaces:

1. **Distinction-bearing breadth absent.**  Greek's interesting move
   is splitting one English concept into multiple lemmas
   (γνῶσις / ἐπιστήμη / σύνεσις; ἀγάπη / φιλία / ἔρως / στοργή;
   αἰών / χρόνος), but the cognition packs carry one lemma per
   concept.  Hebrew distinction-bearing families (חסד, שלום, צדק)
   absent.  Without these families, anchor lens cannot render
   "knowing-as-experience grounds knowing-as-system."
2. **No teaching corpus for non-English.**  `teaching/cognition_chains/`,
   `teaching/relations_chains/`, `teaching/cross_pack_chains/` are
   English-only.  Anchor lens needs reviewed chains traversing the
   substrate's distinctions, not bare lexicon.
3. **No realizer infrastructure for cross-lingual surface
   composition.**  Today's composers assume English lemmas.  The
   anchor-lens composer must render Greek/Hebrew distinctions
   deterministically and ideally in English compound phrasing
   (e.g. "knowing-as-experience") rather than emitting raw
   non-English glyphs at a layperson.

The register subsystem solved a similar inside-out arc in five
phases (R1 class → R2 wiring → R3 first knob → R4 seeded variation
→ R5 operator surface).  Anchor lens warrants the same discipline:
the architecture is sound but the content prerequisites are real,
and entangling code work with bulk pack ingestion is the failure
mode this ADR exists to prevent.

---

## Decision

L1 ships an **anchor lens** as a fourth pack-layer sibling
(identity / safety / ethics / **register** / **anchor lens**),
delivered inside-out across **four phases** mirroring the R1–R5
cadence.  Each phase lands its own ADR; this ADR is the umbrella
that names the architecture and binds the phases together.

> Per `feedback-no-timelines`: phases are sequenced by prerequisites,
> not clock-time.  No phase starts until its predecessor's
> invariants are pinned in CI.

### Architectural commitments (all phases)

* **Anchor lens is the substantive axis.**  Register holds
  `grounding_source` and `trace_hash` byte-identical across choices;
  anchor lens **deliberately moves both** — different substrate ⇒
  different proposition ⇒ different trace_hash.  The R5 register-tour
  seam claim does not extend to anchor lens.  A new tour
  (`core demo anchor-lens-tour`) asserts the *opposite* claim:
  `trace_hashes_distinct_across_lenses` and
  `surface_propositions_distinct_across_lenses`.
* **Anchor lens does not emit non-English glyphs.**  The user-facing
  surface is composed in English compound phrasing
  ("knowing-as-experience", "knowing-as-system") derived from the
  non-English lemma's `semantic_domains` atoms.  Greek/Hebrew glyphs
  appear only in audit / trace / provenance fields, never in
  `ChatResponse.surface`.  This protects the layperson surface
  contract (`core demo conversation`) and matches existing
  `allow_cross_language_generation=False` policy.
* **The pivot is `semantic_domains`, not lemma strings.**  Anchor
  lens never resolves on `"γνῶσις"` literally; it resolves on
  `logos.epignosis.experiential_knowledge` (or whatever atoms the
  ratified pack content commits to).  This keeps the seam
  language-neutral so future substrates (Sanskrit, Aramaic) compose
  without touching anchor-lens code.
* **No new mutation surface.**  Anchor lens packs are ratified just
  like register / identity / safety / ethics packs: JSON +
  companion `.mastery_report.json` self-seal.  Pack mutation stays
  proposal-only.
* **Replay equivalence holds within a lens.**  Same input × same
  anchor lens × same packs ⇒ byte-identical surface and trace_hash.
  Replay equivalence does NOT hold across lenses — the whole point
  of anchor lens is that it changes the proposition.

### Phase L1.1 — Content phase (pack enrichment, no code change)

**Prerequisite for every later phase.**  Enrich the substrate so
distinction-bearing lemmas exist on disk:

* **grc cognition pack additions** (target: full distinction families
  for the load-bearing English collapses):
  * Knowledge family: γνῶσις (present), **ἐπιστήμη** (systematic
    knowledge), **σύνεσις** (insight), **σοφία** (wisdom, present).
  * Love family: **ἀγάπη**, **φιλία**, **ἔρως**, **στοργή**.
  * Time family: **αἰών** (age, era), **χρόνος** (clock time),
    **καιρός** (opportune moment).
* **he cognition pack additions**: **חסד** (covenant-love), **שלום**
  (peace-wholeness), **צדק** (righteousness-rightness).
* **`alignment.jsonl` on the cognition-tier packs** (currently only
  the micro packs carry it).  Binds new lemmas to their English /
  cross-lang counterparts where they exist, and records
  `relation: cross_lang.no_english_collapse` for families English
  doesn't split.
* **Distinguishing `semantic_domains` atoms** per family member —
  e.g. `logos.epignosis.experiential_knowledge`,
  `logos.epignosis.systematic_knowledge`,
  `logos.epignosis.insight` — so the anchor-lens composer has
  ratified atoms to render against.

No realizer / runtime / composer code in L1.1.  The phase
deliverable is enriched lexicon + alignment + ratified self-seal.
This is the highest-leverage step because it unblocks every later
phase without touching code at all.

### Phase L1.2 — Pack class + loader (architectural class, no consumer)

Once L1.1 ratifies the substrate, introduce the anchor-lens pack
class.  Mirrors the register pack class (ADR-0068):

```python
@dataclass(frozen=True, slots=True)
class AnchorLens:
    lens_id: str                         # e.g. "grc_logos_v1"
    primary_substrate: str               # "grc" | "he" | "en"
    semantic_domain_preferences: tuple[str, ...]
                                         # ordered atoms; left-most wins
    cognitive_mode_label: str            # rendered in English compound
                                         # phrasing — "experiential" / "systematic"
                                         # / "covenant-bound" / etc.

@classmethod
def unanchored(cls) -> "AnchorLens": ...
    # In-memory sentinel.  Lens-aware composers MUST treat it as
    # structurally identical to "no lens applied" — byte-identical
    # to pre-L1.2 behaviour.
```

L1.2 ships the class, the loader, and one ratified pack
(`default_unanchored_v1` — structurally equivalent to the unanchored
sentinel).  No composer consumes the lens yet.  CI pins:

* `anchor_lens_byte_identity_null_lift` — full lane unchanged whether
  `RuntimeConfig.anchor_lens_id` is `None` or `default_unanchored_v1`.

### Phase L1.3 — First non-trivial lens + composer wiring

L1.3 ships the first lens that actually moves the proposition:

* `grc_logos_v1` — primary substrate grc; domain preferences pivot
  knowledge-family queries onto `logos.epignosis.experiential_knowledge`
  rather than the en-default `cognition.knowledge`.
* `he_logos_v1` — primary substrate he; pivots claim-family queries
  onto `logos.aletheia.covenant_verity` rather than
  `cognition.truth`.

`chat/pack_grounding.py` gains an `AnchorLens` parameter (mirroring
the R3 register parameter pattern).  Composers consult the lens's
`semantic_domain_preferences` to pick which lemma to render *before*
constructing the surface string.  Rendering stays English compound:
e.g. `"Knowledge-as-experience is grounded in personal acquaintance,
not abstract system."`

CI pins:
* `anchor_lens_lifts_proposition` — same prompt × {unanchored,
  grc_logos_v1, he_logos_v1} produces three different propositions
  (surface differs structurally, not just lexically; trace_hash
  differs deliberately).
* `anchor_lens_no_glyph_leak` — non-ASCII characters are absent from
  `ChatResponse.surface` regardless of lens (audit / provenance
  fields are unrestricted).

### Phase L1.4 — Telemetry + operator surface + tour demo

Mirrors R5.  TurnEvent + ChatResponse gain `anchor_lens_id`.
Telemetry serializer surfaces it.  `core chat --anchor-lens <id>`
flag.  `core demo anchor-lens-tour` walks the same prompts under
{unanchored, grc_logos_v1, he_logos_v1} and asserts:

* `lens_ids_recorded_per_turn` — TurnEvent populates `anchor_lens_id`
  on every turn.
* `trace_hashes_distinct_across_lenses` — the *opposite* of
  `register-tour`'s claim, and load-bearing here.
* `surface_propositions_distinct_across_lenses` — at least one prompt
  yields a structurally-different proposition across lenses (not just
  marker variation).

Exit code 0 iff every claim holds.

---

## Consequences

### Capability unlocked at L1.4

CORE produces structurally different propositions from different
conceptual substrates, deterministically.  A Greek-anchored answer to
"what is knowledge?" does not rephrase the English answer; it answers
from γνῶσις-vs-ἐπιστήμη as compound English phrasing and produces a
proposition that English-default would have collapsed.  Combined with
register (ADR-0068..0072), the matrix is
`{substantive lens} × {presentation register}` — orthogonal axes.

### Trust boundaries

* **Lens packs ratified like every other pack class.**  Mastery
  reports self-seal; loader rejects unratified ids in production
  mode (per ADR-0027 pattern).
* **Glyph escape is not a silent regression.**  L1.3 invariant
  `anchor_lens_no_glyph_leak` is a hard gate: a non-ASCII character
  in `ChatResponse.surface` fails the lane.
* **No new mutation surface.**  Anchor lens consumption stays
  proposal-only at the pack-mutation layer.

### Cognition lane

L1.1 (content only) and L1.2 (null-lift unanchored sentinel) are
byte-identical.  L1.3 *deliberately* moves cognition-eval numbers
when a non-default lens is loaded; the public-split byte-identical
contract holds for the unanchored default.

### Backwards compatibility

* `RuntimeConfig.anchor_lens_id: str | None = None` — defaults
  preserve pre-L1 behaviour.
* TurnEvent / ChatResponse fields default to `""`.
* Existing register / identity / safety / ethics interactions
  unchanged — anchor lens composes *upstream* of those layers
  (the composer chooses which proposition to construct; register
  then varies the surface text of that proposition).

### Performance

Negligible.  Lens lookup is one tuple-iteration over
`semantic_domain_preferences`; no SHA-256 hash per turn (variant_id
discipline is register-specific; anchor lens identity is the lens_id
itself).

---

## Verification (L1.4 acceptance gate)

```
tests/test_anchor_lens_pack_loader.py            N passed
tests/test_anchor_lens_null_lift.py              N passed
tests/test_anchor_lens_runtime_threading.py      N passed
tests/test_anchor_lens_proposition_lift.py       N passed
tests/test_anchor_lens_no_glyph_leak.py          N passed
tests/test_anchor_lens_telemetry.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 unanchored:
  public 100 / 100 / 91.7 / 100
  holdout 100 / 100 / 83.3 / 100

core demo register-tour                          exit 0 (still holds)
core demo anchor-lens-tour                       exit 0
```

The `register-tour` claim **must continue to hold** under any anchor
lens — within a fixed lens, register choice still preserves
grounding_source and trace_hash.  The two axes are genuinely
orthogonal, not just architecturally claimed.

---

## Open questions deferred

* **Composing anchor lens × register simultaneously in the tour.**
  Two-axis demo (lens × register × prompts) is a natural follow-on
  but inflates the grid 9×; L1.4 ships single-axis lens tour first,
  composition tour later.
* **Lens × identity interaction.**  Does a `precision_first_v1`
  identity bias propositions differently under `grc_logos_v1` than
  under `he_logos_v1`?  Probably yes (axis-hedge phrasing composes
  through), but documenting it needs L1.3 evidence first.
* **Cross-language teaching corpus.**  L1.3 lemma-tier lensing is a
  prerequisite for grc/he teaching chains; the chains themselves are
  a separate phase (L2+, ADR not yet drafted).
* **More substrates beyond grc/he.**  Sanskrit (jñāna/vidyā/prajñā)
  and Aramaic distinctions sit naturally on the
  language-neutral-pivot architecture but are deferred to post-L1
  ADRs after the two-substrate case has shipped.

---

## Sequencing summary

```
L1.1  content phase — pack enrichment + alignment           ADR-0073a (this scope)
L1.2  AnchorLens class + loader + unanchored sentinel       ADR-0073b
L1.3  first non-trivial lenses + composer wiring            ADR-0073c
L1.4  telemetry + operator CLI + anchor-lens-tour demo      ADR-0073d
```

Each sub-ADR ratifies its phase in isolation, in the R1–R5 style.
This umbrella ADR-0073 names the architecture; the sub-ADRs commit
to specific lemma lists, atom names, and pack ids.  Substrate
enrichment (L1.1) is the prerequisite that unblocks everything;
nothing in L1.2–L1.4 starts until L1.1 ratifies.

---

## Why this composes against register, not replaces it

Register and anchor lens are mathematically orthogonal because:

* Register operates **after** the proposition is fixed — it only
  varies the surface string the realizer emitted (post-composer
  decoration, ADR-0071 R4).
* Anchor lens operates **before** the proposition is constructed —
  it picks which lemma's `semantic_domains` the composer should
  render, which changes the proposition itself.

This means `register-tour` and `anchor-lens-tour` test *opposite*
invariants and both must pass:

```
register-tour   asserts:  per prompt, fixing lens, varying register   ⇒ trace_hash CONSTANT
anchor-lens-tour asserts: per prompt, fixing register, varying lens   ⇒ trace_hash DISTINCT
```

If either claim ever fails the seam is broken — the orthogonality is
the load-bearing architectural commitment.
