Replaces the cognition-shape-uniform threshold dispatch in
core/capability/expert_demo.py with an explicit LANE_SHAPE_REGISTRY
mapping 8 ratified lane ids to 5 shapes:
cognition -> cognition_shape
elementary_math_ood -> accuracy_shape
foundational_physics_ood -> accuracy_shape
symbolic_logic -> symbolic_logic_shape
hebrew_fluency -> accuracy_shape
koine_greek_fluency -> accuracy_shape
inference_closure -> inference_shape
fabrication_control -> refusal_shape
Each shape has a documented threshold checker. Unknown lane ids
fail-closed with a named reason. ADR-0106 \xc2\xa71.1/\xc2\xa71.3/\xc2\xa71.4/\xc2\xa71.5
unchanged; only \xc2\xa71.2 (threshold rules) dispatches by shape.
tests/test_lane_shape_thresholds.py pins all four ADR-0109 invariants
plus dead-shape and threshold-value gates (13 new tests).
tests/test_expert_demo_contract.py fixtures updated to provide
shape-appropriate metrics (no semantic change to those tests; same
12 cases still pin the ADR-0106 contract).
ADR-0109 status: Proposed -> Accepted. README sequencing updated
(ADR-0110 now only blocked by inference_closure, not by metric-shape
amendment).
Ledger: all five domains remain reasoning-capable, expert_demo=false.
Amends ADR-0106 \xc2\xa71.2 to dispatch threshold rules by lane shape rather
than imposing cognition-pack-shape metrics uniformly. ADR-0107
surfaced that every non-cognition lane was failing the gate by
absence-of-key, not by substance.
Status: Proposed. Ships five shapes covering every lane currently
attached to a ratified pack: cognition_shape, accuracy_shape,
inference_shape, refusal_shape, symbolic_logic_shape. Four invariants
pinned. Unknown lanes fail closed; new shapes require ADR amendment.
\xc2\xa71.1 (reasoning-capable prereq), \xc2\xa71.3 (signature scoping), \xc2\xa71.4
(domain-aware), \xc2\xa71.5 (replay byte-equality) all preserved. ADR-0106
status remains Accepted.
The ADR-0106 contract correctly refused promotion. ADR-0107 records the
deferral and reserves two follow-up ADRs:
- ADR-0109 (lane-shape-aware threshold amendment): ADR-0106 \xc2\xa71.2
prescribes cognition-pack-shape metrics uniformly, but math /
physics / systems / hebrew-greek lanes carry native shapes
(accuracy, passed_rate, all_pass_rate). Prerequisite for any future
expert-demo promotion.
- ADR-0110 (math re-attempt): conditional on ADR-0109 landing and
inference_closure substantively passing (currently all_pass_rate=0.4
on public).
tests/test_adr_0107_deferral.py pins adr_0107_no_silent_promotion: math
stays at reasoning-capable, has no expert_demo_claims entry, and the
ledger row carries a named refusal reason.
No change to core/capability/expert_demo.py or reporting.py -- the
contract is honored, not amended. README sequencing updated to reflect
ADR-0107 acceptance and the new ADR-0109/0110 prerequisites.
Closes ADR-0106 acceptance evidence:
- ExpertDemoClaim dataclass + additive expert_demo_claims block on
ReviewerRegistry (schema_version stays at 1; backward-compatible).
- New core/capability/expert_demo.py with derive_evidence_digest,
evaluate_expert_demo, collect_domain_lanes, materialise_lane_results.
- core/capability/reporting.py: replaces the cognition-lane-only
predicate (previous lines 418-433) with a domain-aware,
reviewer-signed gate; ledger rows now also carry
expert_demo_reason for operator legibility. Reviewer registry is
fail-closed: an unloadable registry yields zero claims, so a broken
registry never silently grants expert_demo=true.
- tests/test_expert_demo_contract.py covers all three ADR-0106
invariants: requires_signature, domain_aware, replay_byte_equality;
plus threshold + production-ledger-untouched gates. 12 new tests.
- tests/test_reviewer_registry.py extended with TestExpertDemoClaimsSchema
covering omitted block, valid parse, unknown signer rejection,
malformed digest rejection, duplicate domain rejection. 5 new tests.
- README index row + table preface updated to note expert_demo is
contract-gated. Frontier list trimmed (ADR-0106 has landed).
- ADR-0106 Status flipped Proposed -> Accepted.
No domain row's expert_demo field flips by this PR -- only the contract
changes. Promotion of any ratified domain requires a follow-up ADR
(ADR-0107 reserved for mathematics_logic) plus a signed claim.
- ADR-0108 Status: Proposed -> Accepted
- README index row updated to Accepted
- 'Current frontier' rewritten with the ranked Proposed-ADR list mandated
by ADR-0108 \xc2\xa7Decision; removes the now-false 'No ADR currently sits in
a "Proposed but unimplemented" state' sentence
- Open candidate directions (no-ADR-yet) section retained for the
multi-reviewer governance frontier item from ADR-0105
Makes the post-ADR-0105 sequencing of ADR-0080 / 0084 / 0087 / 0106
explicit, durable, and revisable. Status: Proposed. No content of the
four sequenced ADRs is modified — sequencing is meta, not content.
Defines a domain-aware, reviewer-signed expert_demo promotion gate to
replace the current cognition-lane-only predicate in
core/capability/reporting.py:418. Status: Proposed. This ADR does not
promote any domain — it defines the contract that a follow-up ADR (likely
mathematics_logic as ADR-0107) will consume as the first worked
promotion.
Sibling reconciliation PR to #104. The four ADRs explicitly called out as
the 'current implementation frontier' in PR #104 are already implemented
to the same evidence bar as the eight ADRs that PR accepted:
- ADR-0094: teaching/source.py + proposal schema widening + migration
script; tests/test_proposal_source.py green
- ADR-0095: teaching/from_miner.py + miner_loop_closure lane;
SHA-pinned in scripts/verify_lane_shas.py; tests/test_miner_proposals.py
green
- ADR-0098: core/demos/contract.py + adapter surface + demo_composition
lane; SHA-pinned; tests/test_demo_composition.py green
- ADR-0099: core/demos/showcase.py + public_demo lane;
SHA-pinned; tests/test_public_showcase.py green
Three of four lanes are SHA-pinned in CI (a stricter bar than several
already-accepted ADRs). Local pytest run: 85/85 passed across the four
tests/test_*.py files in 17s.
Also refreshes docs/decisions/README.md:
- flips the four table rows to Accepted (2026-05-22)
- rewrites the 'Current frontier' section now that no ADR-0091..0102
entry is unimplemented
- enumerates candidate next directions (curriculum proposals,
language-specific holdout splits, expert-demo ratification)
Docs-only change; no runtime code touched.
Eight load-bearing ADRs closing the loop from contemplation Phase 5 through
a public showcase demo. Each one is small and evidence-bearing; together
they sequence the next arc without duplicating existing substrate.
- 0092 Reviewer Registry v1 — populates docs/reviewers.yaml schema;
unblocks all reasoning-capable claims under ADR-0091.
- 0093 Domain Pack Contract v1 Implementation — wires ADR-0091's five
follow-up items (parser, dry-run validator, chain registry, eval lane
refs, reviewer resolution) so manifest fields actually gate status.
- 0094 Proposal Source Provenance — sealed ProposalSource type widening
proposal schemas ahead of 0095.
- 0095 Miner-Sourced Teaching Proposals — closes the contemplation
loop: articulation_quality / contradiction_detection / frontier_compare
miners emit PackMutationProposal candidates routed through the single
reviewed teaching path; identity-pack defense at construction, not
review; replay-equivalence pre-gate.
- 0096 Fabrication-Control Eval Lane — first negative-control measure;
three case classes (phantom endpoint, cross-pack non-bridge, sibling
collapse) with frozen thresholds (fabrication_rate ≤ 0.01).
- 0097 Mathematics-Logic Reasoning-Capable Ratification — first
domain claim under ADR-0091; chain corpus + eval lanes already
exist, this is the formal contract ratification.
- 0098 Demo Composition Contract — DemoCommand protocol so demos can
be composed without reimplementation; deterministic JSON, no global
state mutation, declared output paths only.
- 0099 Public Showcase Demo — composes four scenes (determinism /
honest unknown / reviewed learning / multi-hop+trace) under 30s;
pure composition enforced by grep gate; JSON byte-equality CI-pinned.
Landing order: 0092 → 0094 → 0095 → 0093 → 0096 → 0097 → 0098 → 0099.
Deliberately not included: curriculum compiler, formation course
runner, calculator operators, response-mode taxonomy expansion,
learning-scale 10k harness. Each is deferred with a documented reason.
Pre-work for a writing-curriculum extension to CORE. Two companion
documents, both Proposed status (no code shipped).
docs/decisions/ADR-0087-rhetorical-style-axis.md
Pins rhetorical style as a third selection axis — sibling to anchor
lens (ADR-0073), orthogonal to register (ADR-0070). Substantive
axis: trace_hash DISTINCT across styles (style changes which moves
the composer requires and which frames the realizer emits, which
changes the propositional plan, which changes the trace).
Four anti-patterns explicitly named and rejected:
- style as motor (re-couples realizer to geometry; same shape as
the ADR-0085 fusion-operator rejection)
- style as register variant (conflates substantive with stylistic)
- style as identity axis (bloats identity doctrine)
- style auto-detected from user input (operator-chosen only)
Pack shape mirrors packs/anchor_lens/. default_unstyled_v1 is the
null-lift pack identical to no-style behavior. Three CI invariants
proposed: rhetorical_style_null_lift, schema validation, three-axis
orthogonality.
Substrate-only ADR — no consumer code, no genre packs. Consumer
integration is a follow-up ADR (composer + realizer extensions
that read permitted_frames + required_moves_per_claim +
forbidden_moves).
docs/curriculum/writing-chain-harvester-spec.md
Layer 0 of the writing curriculum. A deterministic tool that
extracts candidate (subject, predicate, object) triples from
reviewed expert prose and surfaces them as proposals to the
existing teaching/review pipeline.
Five stages (segment → classify → extract → propose → audit) —
pure-Python rule-based, no LLM generation, no auto-acceptance.
Trust boundary: reviewer accept/reject via the existing
core teaching propose/review path. No bypass permitted.
The harvester is a proposal PRODUCER, not a proposal CONSUMER.
Plugs into the existing pipeline without inventing a new review
mechanism. Each proposal carries source_id + source_line + the
exact source_clause it came from for reviewer verification.
First-implementation acceptance criteria deliberately tight:
Stage 0+1 with dry-run only. Stages 2-5 are follow-up PRs.
Substrate-first sequencing pattern (ADR-0084 → 0085) reused
throughout. Both documents acknowledge open questions deferred to
implementation phase rather than pre-deciding.
Why now: a writing curriculum is being scoped. Without this ADR,
every downstream PR faces the same "should style be a motor?"
question and the temptation to reach for the geometry will recur
every time the realizer produces a stilted surface. Pinning the
axis up front prevents that recurrence.
The original "Why does light exist?" complaint that motivated ADR-0084
was specifically about CAUSE-intent surfaces. ADR-0084 (substrate) +
PR #65 (content) already moved DEFINITION/RECALL to gloss-grounded
surfaces ("Light is visible medium that reveal truth."). But CAUSE
still dispatched through the chain-walk path:
Before: light — teaching-grounded (cognition_chains_v1):
cognition.illumination; logos.core.
light reveals truth (cognition.truth).
No session evidence yet.
After: Light exists as visible medium that reveal truth.
pack-grounded (en_core_cognition_v1).
The chain-walk is structurally correct but the wrong SHAPE for a why-
question — it's a graph traversal, not an explanation. ADR-0085 fixes
the shape using the same gloss material that DEFINITION/RECALL already
consume, with no new content authoring.
Additive composer
chat/pack_grounding.py:gloss_aware_cause_surface()
- Resolves gloss via lexicon-residency-checked resolve_gloss().
- Frames POS-aware:
NOUN -> "{Lemma} exists as {gloss}."
VERB -> "To {lemma} is to {gloss}."
ADJ -> "To be {lemma} is to {gloss}."
* -> falls back to _frame_gloss (predicate-identity).
- Threads anchor lens via the existing helper (ADR-0073c parity).
- Returns None when no gloss exists — runtime falls through to the
existing chain-walk path. Additive: no CAUSE case loses its surface.
Runtime dispatch
chat/runtime.py — IntentTag.CAUSE tries gloss path FIRST under the
flag; falls through to teaching_grounded_surface* on None.
Unconditional fallback — never silent.
Opt-in flag
core/config.py — RuntimeConfig.gloss_aware_cause: bool = False
Default off preserves pre-ADR-0085 chain-walk surfaces byte-
identically (null-drop invariant, CI-pinned).
Prompt-diversity classifier update
evals/prompt_diversity/runner.py — _CAUSE_MARKERS widened with the
explanation-frame markers ("exists as", "is to", "to be", "is for",
"purpose of") plus bare-form predicates ("reveal" alongside
"reveals"). Neither composer path is penalised on shape_fit just on
inflection grounds.
v1/public lift (flag OFF vs ON, 26 cases)
intent_accuracy : 65.4% -> 65.4% ( — )
versor_closure_rate : 100.0% -> 100.0% ( — )
response_shape_fit : 57.7% -> 57.7% ( — , both frames recognized)
audit_in_surface_rate : 42.3% -> 42.3% ( — , envelope ADR's job)
gloss_quote_rate : 11.5% -> 23.1% (+11.5pp, structural lift)
Tests (15)
- 5 pure composer (NOUN/VERB frame, unknown/empty None, no chain-
walk artifacts in surface)
- 5 runtime dispatch (flag-off chain-walk, flag-on gloss, parametrized
across glossed subjects, VERIFICATION unchanged under flag, no-
gloss fallback engages)
- 5 cognition lane invariance (aggregate metrics byte-identical
under both flag states; surfaces deliberately shift on the 2 CAUSE
cases with glossed subjects — the structural-change-vs-metric-
invariance both-sides invariant)
Lanes
smoke 67/0, cognition 120/0/1 skipped, packs 6/0, teaching 17/0,
runtime 19/0. core eval cognition byte-identical 100/91.7/100/100
under both flag states.
Scope limits (per ADR §Scope limits)
- CAUSE only; VERIFICATION still chain-walks (different shape).
- English pilot only; Greek/Hebrew packs not opted into definitional
layer yet (ADR-0084 scope limit).
- Single-lemma subjects; compound/anaphoric fall through.
- Opt-in until cognition holdout confirms the lift transfers off-
fixture. Future PR flips default on.
Out of scope
- Surface-vs-envelope cleanup ("pack-grounded (...)" still leaks).
- Predicate licensing (ADR-0086).
- Content style pass (bare lemma forms in glosses — separate brief).
* docs(adr-0084): propose definitional layer + prompt-diversity suite
Three companion artifacts proposing the next substantive design step
after ADR-0083:
1. ADR-0084 (Proposed) — Definitional Layer for Lexicon Packs
Optional `definition` block on pack entries: gloss,
definitional_atoms, predicates_invited, definition_version,
provenance. Pack-level opt-in. Closure rule: every word in a
gloss must resolve to a same-pack lemma, another mounted pack's
lemma, or a primitive in a new `packs/primitives/` pack.
NO composer change in this ADR (sequenced for ADR-0085) —
ratify substrate before any consumer depends on it.
2. evals/prompt_diversity/ (Proposed) — companion eval lane
~50 cases across question-shape × sophistication × domain,
measuring three new metrics: response_shape_fit,
audit_in_surface_rate (quantifies the trust-boundary leak into
user surfaces), gloss_quote_rate (zero today; rises with future
gloss-aware composer). No v1 pass thresholds — the lane
establishes a baseline distribution so future work has
something to move. 26 seed cases authored covering all 21
categories.
3. docs/handoff/ADR-0084-pack-content-brief.md — paste-ready brief
for a cheaper/faster dev agent to produce the pack content in
parallel. Self-contained, 5 sequenced phases (primitives pack
→ extend 9 existing glosses → add to relations/anchors → write
closure verifier → run safety lanes), explicit don't-touch list
(no composer / runtime / algebra / Greek+Hebrew packs / schema
parser), no-LLM-glosses discipline, per-phase acceptance.
Discovery while drafting: 9 packs already carry glosses.jsonl
under language_packs/data/ with a flat schema (78 entries in
en_core_cognition_v1 alone). The brief reflects that — most
work is extending existing entries, not authoring from scratch.
Strategic context: ADR-0083 raised the *depth* ceiling on chain
composition; ADR-0084 raises the *fidelity* ceiling. The φ
separation probe (memory: phi-separation-falsified) established
that semantic capability lives in chain composition, not in φ
geometry, so deepening the composer's substrate is the natural
next step. ADR-0084 → 0085 (gloss-aware composer) → 0086
(predicate licensing at ratification) is the planned sequence.
* feat(adr-0084): substrate — schema parser, primitives loader, closure verifier
Substrate-only code-side for ADR-0084 (Definitional Layer for Lexicon Packs).
No composer touches the new fields yet; consumer integration is ADR-0085.
Schema (additive, default preserves byte-identity)
- LanguagePackManifest.definitional_layer: bool = False
- compiler loader propagates the flag from manifest.json
language_packs/definitions.py (new)
- GlossEntry dataclass: lemma, gloss, pos, definitional_atoms,
predicates_invited, definition_version, provenance_ids
- parse_gloss_entry(payload, *, strict) — strict mode enforces ADR-0084
§Schema validation row-by-row: required keys, typed lists, no
unknown keys, positive definition_version; lax mode preserves the
legacy two-field shape for back-compat
- load_pack_glosses(pack_id, *, strict) with cache + clear hook
- verify_definitional_closure(pack_id, *, mounted_pack_lemmas,
primitive_lemmas, strict) returning tuple[ClosureViolation, ...];
case-insensitive resolution; cycles permitted per ADR
packs/primitives/loader.py (new)
- Sister loader to packs/safety/ and packs/identity/
- PrimitivesPack frozen dataclass with .lemmas frozenset
- Gates: checksum match, kind=='primitives', definitional_layer:true,
never_auto_mutable:true, pack_id matches dir, primitive_count
cross-check, duplicate-lemma rejection, path-traversal rejection,
strict per-entry schema with allow-list
- DEFAULT_PRIMITIVES_PACK = 'en_semantic_primitives_v1'
tests/test_adr_0084_definitional_substrate.py
- 38 tests covering strict parser (each required key rejection, unknown
key rejection, empty predicates_invited allowed, empty
definitional_atoms rejected, invalid definition_version), lax
parser back-compat, load_pack_glosses (missing/strict raise/lax
skip/malformed JSON), closure verifier (same-pack/primitive/mounted/
unresolved/case-insensitive), primitives loader (every gate), and
a back-compat check that every shipped pack still ratifies with
definitional_layer=False
Lanes: smoke 67/0, cognition 120/0/1, teaching 17/0, runtime 19/0,
packs 6/0. Cognition eval byte-identical 100/91.7/100/100.
When the content PR lands (primitives.jsonl + extended glosses.jsonl
under ADR-0084-pack-content-brief.md), the gate catches any closure-rule
violation without further code change.
* feat(evals): prompt_diversity lane runner — measurement instrument for ADR-0084+
Implements the runner against the existing contract.md + 26-case v1
public split. Lane auto-discovered by evals.framework via the standard
contract + runner convention.
Runner (evals/prompt_diversity/runner.py)
- run_lane(cases, *, config, workers) -> LaneReport
- 5 metrics: intent_accuracy, versor_closure_rate (carried over from
cognition), plus the three new lane-specific metrics —
response_shape_fit, audit_in_surface_rate, gloss_quote_rate
- breakdown dict groups by (question_shape, sophistication, domain)
per contract §How to read the output
- mirrors evals.cognition.runner's parallel worker pattern
Per-shape classifier (deliberately substring/regex-simple at v1)
- predicate_identity, explanation, sequence, two_subject_contrast,
narrative, honest_disclosure
- Unknown shape => neutral pass (don't penalise new categories)
Audit-leak detector
- trust-boundary preamble markers (teaching-grounded (, pack-grounded
(, No session evidence yet.)
- dotted semantic-domain tag regex (cognition.illumination, etc.)
Gloss-quote detector
- resolves expected_terms via chat.pack_resolver.resolve_gloss
- 4-token contiguous-window match against surface (high-confidence
"gloss actually quoted", not "shared one common word")
Tests (tests/test_prompt_diversity_runner.py — 23)
- shape classifier parametrized over the six expected_shape values
- audit-leak detector parametrized over preamble + tag + clean cases
- end-to-end on v1 public:
* versor_closure_rate == 1.0 (only v1 pass threshold per contract)
* every metric in [0, 1]
* breakdown groups present with the four per-cell metrics
* diversity gate: >=5 question shapes, >=3 domains
(defends against future regressions that collapse the suite
back to a cognition-shaped fixture)
v1/public baseline (26 cases)
intent_accuracy : 65.4% (contract predicted 70-85%)
versor_closure_rate : 100.0% (only v1 pass threshold) PASS
response_shape_fit : 53.8% (contract predicted low)
audit_in_surface_rate: 42.3% (contract predicted ~100%)
gloss_quote_rate : 7.7% (contract predicted 0%)
Three baseline surprises worth noting in the report (NOT failures —
the v1 lane is explicitly there to establish the distribution):
- audit_in_surface_rate at 42% (not 100%) means the chain-walk leak
fires on ~11/26; the other 15 are honest-disclosure cases that
emit no audit envelope. Sharpens the future surface-vs-envelope
ADR's actual target: grounded surfaces specifically.
- response_shape_fit at 54% (not "low") — classifier likely has
false positives on the ", which " cause-marker. Worth tightening
once we have an ADR-0085 baseline to compare against.
- intent_accuracy at 65% (below predicted 70-85%) — classifier dips
harder on adversarial/cross-pack than expected. Real gap.
All five smoke/cognition/teaching/runtime/packs lanes still green;
core eval cognition byte-identical 100/91.7/100/100.
* feat(packs): ADR-0084 pack content (primitives + extend glosses + closure verifier) (#65)
* feat(packs): ADR-0084 pack content
* feat(packs): repair ADR-0084 definitional content
* test(adr-0084): adjust substrate manifest tests for post-#65 content reality
PR #65 flipped definitional_layer:true on 13 English packs (9 core +
4 relations + collapse-anchors). The substrate's previous test
test_existing_packs_unchanged asserted that en_core_cognition_v1 +
en_core_relations_v1 still had definitional_layer:False — which was
the right pre-content invariant but is wrong post-content.
Replace it with two complementary tests that hold against real content:
- test_non_opted_packs_default_false:
pins that packs that DIDN'T flip the flag (en_minimal_v1,
he_core_cognition_v1, grc_logos_cognition_v1) still surface
definitional_layer=False through the loader. Defends against
a future change accidentally flipping the flag on a non-opted
pack.
- test_opted_packs_carry_flag:
pins that packs that DID flip the flag (en_core_cognition_v1,
en_core_relations_v1) surface definitional_layer=True through
the loader. Proves the substrate's manifest-field propagation
works against real ratified content, not just fixture packs.
Net: +1 test, same intent (substrate ratifies the manifest field
correctly), now with real-content coverage on both sides of the gate.
All 62 ADR-0084 substrate + prompt-diversity tests pass.
Strict superset of ADR-0062's depth-1 composer. `max_depth` is the
number of follow-up hops appended beyond the initial chain:
max_depth=0 → byte-identical to single-chain surface
max_depth=1 → byte-identical to ADR-0062 composed
max_depth=2 → byte-identical to ADR-0062 when no second hop
survives, strict superset when one does
The composer surfaces content the realizer was silently dropping
from chains already ratified in `cognition_chains_v1`. Example
live lift on `"Why does light exist?"`:
composed: "light reveals truth, which grounds knowledge."
transitive(2): "...which grounds knowledge, which requires evidence."
Cycle-safe at every depth via a single visited-set; single-corpus
traversal in v1 (cross-corpus transitive deferred to a follow-up
ADR alongside ADR-0064's cross-pack model).
Both flags default False — every existing surface is preserved
byte-identically. When both `composed_surface` and
`transitive_surface` are True, transitive wins.
Implementation:
- `core/config.py`: `transitive_surface: bool = False`,
`transitive_max_depth: int = 2`.
- `chat/teaching_grounding.py`: `_resolve_followup` shared helper
refactored out of the depth-1 composer (no behavioural change),
plus new `teaching_grounded_surface_transitive(subject,
intent_tag, *, max_depth)`.
- `chat/runtime.py`: dispatch order — transitive > composed > single.
Verification:
- tests/test_transitive_surface.py: 16 new tests covering pure-fn
contract, visited-set cycle guard at every depth, runtime
integration, and the cognition-lane null-drop invariant at
`max_depth=2` (public + holdout splits).
- tests/test_composed_surface.py: 11/11 pass after the helper
refactor (ADR-0062 behaviour preserved).
- `core test --suite smoke`: 67 pass.
- `core test --suite cognition`: 120 pass, 1 skipped.
- `core test --suite teaching`: 17 pass.
- `core eval cognition`: 100 / 91.7 / 100 / 100 (byte-identical).
Wires observational telemetry on the composer-vs-graph atom-set
relationship. Phase 1 is strictly observational: no enforcement,
no surface mutation, no grounding-source change, no trace-hash impact.
New telemetry fields on TurnEvent + ChatResponse:
composer_graph_atom_status ∈ {equivalent, divergent,
graph_unconstrained,
composer_no_atoms,
not_applicable, ""}
composer_atom_set_hash SHA-256 over sorted unique atoms
graph_atom_set_hash SHA-256 over sorted unique atoms
composer_graph_atom_overlap_count int
Composer atoms come from existing pack candidate metadata
(pack_semantic_domains channel through _maybe_pack_grounded_surface).
Graph atoms come from build_graph_from_input + resolve_lemma on
node.subject/predicate/obj — no prose parsing. When a grounded
composer path lacks explicit atom provenance, status is
'composer_no_atoms'.
New pure helper:
chat/atom_equivalence.py — normalize_atoms, hash_atoms,
atoms_for_graph_nodes, compare_atom_sets
Tests (tests/test_composer_graph_atom_equivalence.py):
- Pack DEFINITION path produces observable equivalence
- Divergent atom sets produce distinct hashes
- Register invariance: atom hashes + status identical across
{neutral, terse, convivial}; trace_hash also constant (R5 axis)
- Anchor lens engaged case still ASCII-only on surface
- No prose-parsing helper symbols introduced in runtime.py
(extract_candidate_surface_lemmas, surface_lemma,
parse_surface_atoms) — enforces Phase 1 boundary
Performance note: build_graph_from_input now runs on every warm
English turn (previously only when forward_graph_constraint=True).
Phase 1 accepts this cost to make the telemetry universally
available; Phase 2+ can introduce a feature flag if needed.
Validation:
- Cognition eval byte-identical: 100/100/91.7/100
- Full lane: 2864 passed, 3 skipped, 0 failed (+5 over baseline)
- Targeted lane: 72 passed in tests/test_{graph_constraint,
pack_grounding,register_tour_demo,anchor_lens_tour_demo,
orthogonality_tour_demo,realizer_guard_holdout,
composer_graph_atom_equivalence}.py
R5 (ADR-0072) shipped the register *machinery*; ADR-0074's orthogonality
tour proved the axis was decoratively orthogonal to anchor-lens but
inspection of the cognition-eval surfaces revealed two structural gaps:
* On pack-grounded DEFINITION/RECALL/COMPARISON composers, the only
realizer override any register consumed was `disclosure_domain_count`
— which only fires on the no-gloss disclosure path. Under terse_v1,
every gloss-DEFINITION cell was byte-identical to default_neutral_v1.
* The register-tour's `surfaces_vary_at_least_once` gate could be
satisfied by convivial's decorative wrapper alone, masking that
regression in CI.
R6 closes both:
Layering separation (the load-bearing fix):
* New TurnEvent/ChatResponse field `register_canonical_surface` carries
the composer output BEFORE any register transformation. The pipeline
hashes this field for `trace_hash`, preserving R5's invariant that
per-prompt trace_hash is CONSTANT across registers even while
substantive transforms produce visibly different surfaces.
Substantive transforms (`chat/register_substantive.py`):
* terse_v1 gains 3 bool knobs: `drop_provenance_tag`, `compress_gloss`,
`drop_articles` — all pure regex transforms on the canonical surface.
* convivial_v1 gains `append_semantic_domain_clause` — appends a single
bounded "Related: <atom>." clause using the lemma's pack atoms.
* default_neutral_v1 leaves overrides empty; substantive transform is
byte-identical no-op (preserves `byte_identity_null_lift`).
* C1 (ADR-0075) safety preserved: drop_articles refuses to drop
articles following `not` (avoids R3 violations); no knob combination
trips R2/R3.
Strengthened tour gate (`evals/register_tour/run_tour.py`):
* Replaces `surfaces_vary_at_least_once` with two falsifiable claims:
- `terse_substantively_differs_from_neutral_on_pack_grounded_definition`
- `convivial_substantively_differs_from_neutral_on_pack_grounded_definition`
Both restrict to DEFINITION+pack-grounded cells and require
difference beyond whitespace/punctuation.
* New claim `register_canonical_surfaces_identical` directly proves
the layering separation.
* Preserves R5's `all_grounding_sources_identical` +
`all_trace_hashes_identical`.
Pack ratification:
* Loader widened to accept `bool` for closed-set R6 keys
(drop_provenance_tag / compress_gloss / drop_articles /
append_semantic_domain_clause).
* `_KNOWN_OVERRIDE_KEYS` ratify gate extended with same.
* terse_v1 + convivial_v1 reratified with new knobs; companion
mastery reports re-sealed. default_neutral_v1 unchanged.
Invariants pinned:
* `invariant_register_canonical_surface_constant_across_registers` (new)
* `invariant_terse_substantively_distinct_from_neutral` (new)
* `invariant_convivial_substantively_distinct_from_neutral` (new)
* `invariant_realizer_no_illegal_articulation` (C1, preserved)
* `invariant_realizer_guard_byte_identity_on_currently_passing_cases`
(C1, preserved)
Verification:
* `core eval cognition`: 100.0% / 91.7% / 100.0% / 100.0% — byte-
identical under default_neutral_v1.
* `core demo register-tour`: all 5 claims green, exit 0.
* `core demo anchor-lens-tour`: green (no anchor-lens code touched).
* `core demo orthogonality-tour`: green (5/5 claims).
* Full lane: 2858 passed, 1 pre-existing failure
(test_all_preamble_explains_combined_run, carried forward
unchanged from main). 56 new R6 tests across three files.
C1 coherence floor: a deterministic verifier that runs on every
candidate surface produced by the truth path, before assignment to
ChatResponse.surface. Rejects illegal articulations and routes them
to a bounded disclosure string — admission control with a
deterministic fallback, not normalization.
Active rules (R1 deferred during ratification — see ADR):
R2_aux_neg_requires_verb — "<aux> not <wrong-POS>" rejected
R3_be_neg_requires_predicate — "<be> not <verb>" rejected
Fail-open on unknown POS, fail-closed on explicit wrong POS.
Cognition eval byte-identical (100/91.7/100/100).
Original bug class — "Light reveals truth, right?" → "Right does not
thought." — now routes to "I do not have a reviewed articulation for
that yet." with grounding_source=none, walk_surface preserving the
rejected candidate, and telemetry carrying R2_aux_neg_requires_verb.
Files:
generate/realizer_guard.py NEW — pure verifier
chat/runtime.py hook on stub + main paths
chat/telemetry.py serialize guard fields
core/physics/identity.py TurnEvent +2 fields
evals/realizer_guard/run_holdout.py NEW — 6-prompt cluster
tests/test_realizer_guard_*.py NEW — 46 tests (unit/seam/holdout)
docs/decisions/ADR-0075-*.md NEW — ratified
Invariants pinned:
invariant_realizer_no_illegal_articulation
invariant_realizer_guard_byte_identity_on_currently_passing_cases
Lanes (excluding 1 pre-existing TestDemoPreambles failure unrelated
to C1, already present at 4426f38):
smoke 67/67 cognition 120/120(+1s) teaching 17/17
packs 6/6 runtime 19/19 algebra 132/132 full 2792/2793
A single demo that walks the full 3 × 3 × 2 matrix (register × lens
× prompts, 18 cells) and pins five claims simultaneously, packaging
both single-axis invariants into one composition gate.
The single-axis tours assert opposite invariants:
register-tour : per (lens, prompt), trace_hash CONSTANT across
registers (R5 / ADR-0072).
anchor-lens-tour : per (register, prompt), engaged lens diverges
in trace_hash from the unanchored baseline
(L1.4 / ADR-0073d).
Orthogonality-tour packages both claims simultaneously across the
full matrix, plus three surface-level claims that pin the markers
operators actually see.
Composed claims (all five must hold)
A) inner_register_invariant_within_lens
For each (lens, prompt) cell, the three register runs share an
identical trace_hash. (R5 register-tour, applied 6 times:
3 lenses × 2 prompts.)
B) outer_lens_distinctness_within_register
For each (register, prompt) cell where any non-unanchored lens
engages, that engaged lens's trace_hash differs from the
unanchored baseline at the same (register, prompt).
(L1.4 anchor-lens-tour, applied 6 times: 3 registers × 2 prompts.)
C) surface_carries_register_marker_under_convivial
Every convivial cell with a non-empty surface has a non-empty
register_variant_id.
D) surface_carries_lens_annotation_when_engaged
Every engaged cell carries [lens(<id>):<mode>] in surface AND
a non-empty anchor_lens_mode_label.
E) no_substrate_glyph_leak_across_grid
No cell's surface contains Greek/Hebrew/Syriac/Arabic glyphs.
(ADR-0073c gate re-asserted across the full matrix.)
CLI wiring
core demo orthogonality-tour human-readable grid + claims
core demo orthogonality-tour --json structured report
Exit code 0 iff all five claims hold.
Files
evals/orthogonality_tour/__init__.py NEW
evals/orthogonality_tour/run_tour.py NEW
core/cli.py EDIT
- cmd_demo handler wires orthogonality-tour
- demo choices + EPILOG examples updated
tests/test_orthogonality_tour_demo.py NEW (9 tests)
docs/decisions/ADR-0074-orthogonality-tour.md NEW
Sanity check baked into tests
test_engaged_cells_appear_for_both_non_trivial_lenses pins that
grc_logos_v1 engages on knowledge in all 3 registers (3 cells)
and he_logos_v1 engages on truth in all 3 registers (3 cells).
Prevents the lift claims being vacuously satisfied by a future
engagement regression.
Lane evidence
- 9 new orthogonality-tour tests pass.
- core demo register-tour → all_claims_supported: True
- core demo anchor-lens-tour → all_claims_supported: True
- core demo orthogonality-tour → all_claims_supported: True
- python -m core.cli eval cognition → byte-identical 100/100/91.7/100.
- Full lane: 2745 passed / 4 skipped / 1 pre-existing failure
(+9 over L1.4's 2736; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
No runtime / composer / loader / pack / schema changes. Pure demo
consumer of existing telemetry contracts.
L1.3 of the anchor-lens inside-out rollout — first substantive
surface lift on the substantive axis. Two ratified non-trivial
lenses engage on cognition-pack lemmas via the alignment graph,
appending [lens(<id>):<mode>] annotations to the existing
pack-grounded surface.
Two ratified lenses
grc_logos_v1 (Greek substrate)
primary_substrate : "grc"
semantic_domain_preferences: ["logos.episteme.systematic_knowledge"]
cognitive_mode_label : "systematic"
Engages on en "knowledge" via grc-core-cog-021 (ἐπιστήμη) →
en-core-cog-007 alignment edge.
he_logos_v1 (Hebrew substrate)
primary_substrate : "he"
semantic_domain_preferences: ["logos.aletheia.verity"]
cognitive_mode_label : "covenant-verity"
Engages on en "truth" via he-core-cog-002 (אמת) →
en-core-cog-002 alignment edge.
Both ratified under method anchor_lens_lifts_proposition.
Engagement rule (single)
1. Resolve en_lemma → entry_id (cognition pack).
2. For each substrate pack matching lens.primary_substrate, load
alignment.jsonl; find edges where target_id == entry_id.
3. For each such substrate lemma, if any atom in its
semantic_domains ∈ lens.semantic_domain_preferences → engage.
4. No match → None (no annotation; byte-identical surface).
The pivot is shared semantic_domain atoms surfaced via the
alignment graph — exactly the language-neutral commitment from
ADR-0073. Engagement never touches non-English surface text;
entry_ids and atom strings only.
Surface lift
no-lens : "Knowledge is X. pack-grounded (en_core_cognition_v1)."
lens-on : "Knowledge is X. pack-grounded (en_core_cognition_v1) [lens(grc_logos_v1):systematic]."
Annotation between existing provenance and trailing period.
Both metadata fields are ASCII-bounded ≤64 chars at the loader
level, so the annotation can never carry non-ASCII.
Scope deliberately narrow
L1.3 wiring restricted to pack_grounded_surface /
build_pack_surface_candidate (DEFINITION/RECALL only). Other
composers (COMPARISON / CORRECTION / PROCEDURE / NARRATIVE /
EXAMPLE / CAUSE / VERIFICATION) accept the anchor_lens kwarg via
forward-compat default UNANCHORED but do not yet consume it.
L1.3b or later broadens to those intent shapes.
Ratify gate widening
Non-null lenses must:
- have primary_substrate ∈ {grc, he, en}
- have a non-empty cognitive_mode_label
- every preferred atom must exist in at least one lemma of the
named substrate (trust boundary: operators cannot ship a lens
pointing at atoms not on disk).
Method: anchor_lens_lifts_proposition. Null lenses still ratify
under byte_identity_null_lift (L1.2 method).
Seam allow-list widening
Truth-path modules (cognition / trace / pipeline / intent /
propagation / vault / algebra) still refused. Composer-side
imports from chat/pack_grounding.py now permitted — the same way
ADR-0069's R2 widened the register seam.
New invariants pinned (3)
tests/test_anchor_lens_engagement_unit.py (14 tests) — resolver
returns mode label only on intended substrate × en lemma pair;
case-insensitive; engagement None under null lens; synthetic
lens with unmatched atom returns None; annotation is pure ASCII.
tests/test_anchor_lens_lifts_proposition.py (17 tests) — grc
engages on knowledge only, he engages on truth only,
cross-lens isolation, three-way distinctness, replay determinism
per (lens × prompt), register-tour seam holds within each lens
scope (orthogonality CI-pinned, parametrized over 4 lens
choices).
tests/test_anchor_lens_no_glyph_leak.py (5 tests) — hard
block-scoped gate: Greek (U+0370..03FF, U+1F00..1FFF), Hebrew
(U+0590..05FF), Syriac, Arabic. Stylistic punctuation
(em-dash etc.) explicitly allowed; em-dash predates L1.3 by a
wide margin and is not a substrate-leak risk. Tested per-lens
across every cognition case + direct lens-metadata ASCII check.
Lane evidence
74 anchor-lens tests pass (37 from L1.2 + 37 new).
python -m core.cli eval cognition → public 100/100/91.7/100
byte-identical (lens=None / default_unanchored_v1).
core demo register-tour --json → all_claims_supported: True
(R5 seam still holds; L1.3 doesn't perturb presentation axis).
Full lane: 2706 passed / 4 skipped / 1 pre-existing failure
(+37 over L1.2's 2669; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
Files
packs/anchor_lens/grc_logos_v1.json NEW
packs/anchor_lens/grc_logos_v1.mastery_report.json NEW
packs/anchor_lens/he_logos_v1.json NEW
packs/anchor_lens/he_logos_v1.mastery_report.json NEW
scripts/ratify_anchor_lens_packs.py EDIT
LENS_IDS adds grc_logos_v1 / he_logos_v1; gate widened.
chat/pack_grounding.py EDIT
_resolve_anchor_lens_mode, _maybe_append_anchor_lens_annotation,
_substrate_lexicon_by_entry_id, _en_lemma_to_entry_id.
build_pack_surface_candidate + pack_grounded_surface gain
anchor_lens kwarg (default UNANCHORED).
chat/runtime.py EDIT
Thread self.anchor_lens into pack_grounded_surface() call.
tests/test_anchor_lens_pack_seam.py EDIT
Doc-comment updated for L1.3 allow-list.
tests/test_anchor_lens_* NEW (3 files)
docs/decisions/ADR-0073c-anchor-lens-composer-wiring.md NEW
Umbrella ADR-0073 ratified (Accepted); L1.1 content phase
(ADR-0073a) landed. Pure pack enrichment — no runtime code, no
composer change, no test of behaviour. Substrate prerequisite for
the L1.2–L1.4 phases.
Greek additions (grc_logos_cognition_v1, 20 → 29 entries)
Knowledge family (English collapses to `knowledge`):
- ἐπιστήμη logos.episteme.systematic_knowledge
- σύνεσις logos.synesis.insight
(γνῶσις at grc-core-cog-007 unchanged — treated as the
experiential variant by the L1.3 lens config)
Love family (English collapses to `love`):
- ἀγάπη logos.agape.covenant_love
- φιλία logos.philia.companion_love
- ἔρως logos.eros.passionate_love
- στοργή logos.storge.familial_love
Time family (English collapses to `time`):
- αἰών logos.aion.age_era
- χρόνος logos.chronos.clock_time
- καιρός logos.kairos.opportune_moment
Hebrew additions (he_core_cognition_v1, 20 → 23 entries)
- חסד logos.chesed.covenant_loyalty
- שלום logos.shalom.wholeness_peace
- צδק logos.tzedek.right_order
Alignment.jsonl on both cognition-tier packs (previously only the
micro packs carried alignment)
- grc_logos_cognition_v1/alignment.jsonl — 20 edges: three-way core
dyads (word / truth / light / life / beginning / wisdom),
knowledge-family → en collapse, ἀγάπη↔חסד covenant-love pairing
(weight 0.86, Septuagintal), `cross_lang.no_english_collapse`
annotations for love + time families pointing at
`en-collapse-<family>` sentinel ids (weight 0.0).
- he_core_cognition_v1/alignment.jsonl — 7 edges: core dyads to en,
חסד↔ἀγάπη covenant pairing, no-english-collapse annotations for
חסד / שלום / צδק.
Manifest checksums refreshed per CLAUDE.md doctrine
- grc_logos_cognition_v1: b45bcf581cee… → 0f9436675707…
- he_core_cognition_v1: dee1e8c6ad9a… → 22145d008185…
Design decisions
- Existing 20 + 20 lemma atoms untouched — downstream tests /
composers / teaching chains keep referencing the same atoms.
Only new lemmas carry the distinguishing atoms.
- `cross_lang.no_english_collapse` edges are metadata not data
(sentinel target ids, weight 0.0). Their purpose is letting the
alignment graph answer "does English split this family?" without
forcing an artificial English lemma.
- Every new entry carries `adr-0073a:hand_authored:2026-05-19` in
its `provenance_ids` so future audits can find the L1.1 cohort
deterministically.
Verification
- python -m language_packs verify grc_logos_cognition_v1 → OK
- python -m language_packs verify he_core_cognition_v1 → OK
- python -m language_packs compile <both> → 29 / 23
manifold points; spot-check confirms καιρός / צδק resolve.
- python -m core.cli eval cognition → public
100 / 100 / 91.7 / 100 byte-identical (new lemmas sit on disk but
no composer references them yet).
- python -m core.cli test --suite cognition → 120/1 pass
- python -m core.cli test --suite smoke → 67/0 pass
- python -m core.cli test --suite full → 2632 passed
/ 4 skipped / 1 pre-existing failure (test_all_preamble_explains_
combined_run rename drift, unrelated).
- core demo register-tour → exit 0
(R5 seam still holds; L1.1 doesn't touch register pathway).
What L1.1 deliberately does NOT do
- No AnchorLens class (that's L1.2 / ADR-0073b).
- No composer wiring (L1.3 / ADR-0073c).
- No --anchor-lens CLI flag or demo (L1.4 / ADR-0073d).
- No teaching corpus in non-English (post-L1).
Umbrella ADR for the substantive-variation axis that composes
orthogonally against register (ADR-0068..0072). Drafted only;
status Proposed. No code, no pack, no test landed.
Architecture summary
- Anchor lens is the substantive axis: register varies surface text
while keeping grounding_source / trace_hash byte-identical;
anchor lens deliberately moves both because the proposition
itself changes when the substrate changes.
- Pivot is shared `semantic_domains` atoms (already on disk across
grc / he / en cognition packs), not transliteration tables — the
seam stays language-neutral so future substrates compose without
touching anchor-lens code.
- English compound phrasing only at the surface ("knowing-as-
experience", "knowing-as-system"); Greek / Hebrew glyphs live in
audit / provenance fields only. L1.3 invariant
`anchor_lens_no_glyph_leak` is a hard gate.
Four-phase rollout (mirrors R1–R5 cadence)
L1.1 content phase — distinction-bearing lemma additions
(ἐπιστήμη / σύνεσις / ἀγάπη-φιλία-ἔρως-στοργή / αἰών-χρόνος-
καιρός; חסד / שלום / צδק) + alignment.jsonl on the cognition-
tier packs. No code. Prerequisite for every later phase.
L1.2 AnchorLens pack class + loader + `default_unanchored_v1`
sentinel. Null-lift CI invariant pinned.
L1.3 First non-trivial lenses (`grc_logos_v1`, `he_logos_v1`)
wired into chat/pack_grounding.py composers. Proposition-
lift invariant + glyph-leak gate pinned.
L1.4 Telemetry (TurnEvent + ChatResponse gain anchor_lens_id),
`core chat --anchor-lens` flag, `core demo anchor-lens-tour`
asserting trace_hashes_distinct_across_lenses (opposite of
register-tour's claim — both must hold).
Three honest gaps blocking L1.2+
- Distinction-bearing lemmas absent from cognition packs.
- No reviewed teaching corpus for non-English (cognition_chains,
relations_chains, cross_pack_chains all en-only).
- No realizer infrastructure for cross-lingual surface composition.
L1.1 (pure content) closes all three for the cognition tier.
Orthogonality claim — load-bearing
register-tour : per prompt, fix lens, vary register → trace_hash CONSTANT
anchor-lens-tour : per prompt, fix register, vary lens → trace_hash DISTINCT
Both must continue to hold; failure of either breaks the seam.
Two new intent shapes + composers turn the runtime's corpus
density into operator-visible articulation. Both consult the
cross-corpus aggregator from ADR-0064; no new ratification needed.
P3.3 — chat/narrative_surface.py + IntentTag.NARRATIVE.
Classifier patterns (registered BEFORE generic DEFINITION):
^tell\s+me\s+about\s+
^describe\s+
^what\s+(?:can|do)\s+you\s+(?:say|know)\s+about\s+
narrative_grounded_surface(subject, max_clauses=4) walks every
reviewed chain rooted on subject across all registered teaching
corpora. Dedupes by (connective, object) — cause + verification
carrying the same predicate emit one clause, not two. Sorts by
(intent, connective, object) for replay stability.
Surface format:
"{X} — narrative-grounded ({corpus_ids}): {dX1}; {dX2}.
{X} {conn1} {O1} ({dO1}); {X} {conn2} {O2} ({dO2}).
No session evidence yet."
Cross-corpus subjects (e.g. mother in relations_v2) emit
narrative-grounded (relations_chains_v2) tag; cognition subjects
emit cognition_chains_v1 tag. Multi-corpus subjects (when
applicable) emit composite "corpus_a + corpus_b" tag.
P3.4 — chat/example_surface.py + IntentTag.EXAMPLE.
Classifier patterns:
^(?:give|show)\s+(?:me\s+)?an?\s+(?:example|instance)\s+of\s+
^example\s+of\s+
example_grounded_surface(object_lemma, max_examples=3) walks chains
where the lemma is the OBJECT — inverts the typical subject-keyed
access pattern. Dedupes by subject; sorts by (intent, subject,
connective).
Surface format:
"{X} — example-grounded ({corpus_ids}): {dX1}.
Example: {subj1} {conn1} {X}; {subj2} {conn2} {X}.
No session evidence yet."
Cross-cutting:
- Both intents added to _OOV_INTENT_TAGS — fall through to OOV
invitation when subject is unknown (Phase 2 gradient discipline).
- Both tagged grounding_source="teaching" (same provenance tier
as the existing teaching_grounded_surface).
- No prose generation, no new mutation surface.
Live verification:
> Tell me about truth.
[teaching] truth — narrative-grounded (cognition_chains_v1):
cognition.truth; logos.core. truth grounds knowledge
(cognition.knowledge); truth requires evidence (cognition.evidence).
> Give me an example of knowledge.
[teaching] knowledge — example-grounded (cognition_chains_v1):
cognition.knowledge. Example: truth grounds knowledge;
understanding requires knowledge; evidence grounds knowledge.
> Tell me about mother.
[teaching] mother — narrative-grounded (relations_chains_v2):
kinship.parent.female. mother precedes daughter (kinship.child.female).
> Describe photosynthesis.
[oov] I haven't learned 'photosynthesis' yet (intent: narrative). ...
ADR-0066 (this commit completes the ADR). 30 new tests passed.
Full lane: 2067 passed, 2 skipped, 0 failed in 2:32.
Mirrors the chain-gap pipeline (Phase 1.1+1.2) for vocabulary gaps:
the OOV invitation surface (P2.1) now emits structured signals that
operators can aggregate, rank, and auto-promote into reviewed
PackMutationProposal candidates — closing the OOV loop the same way
Phase 1 closed the chain loop.
Three new modules + two new CLI surfaces:
teaching/oov_sink.py.
OOVCandidate dataclass mirroring teaching.discovery.DiscoveryCandidate.
OOVBufferSink (in-memory) + OOVMonthlyFileSink (append-only JSONL
under <root>/<YYYY>/<YYYY-MM>.jsonl — same layout as discovery sink
so the aggregator reuses the file-walk machinery).
hash_oov_candidate_id(token, intent, trace_hash) — deterministic
32-char hex id matching DiscoveryCandidate's replay invariant.
format_oov_candidate_jsonl — sorted-keys compact JSONL line.
teaching/oov_gaps.py.
aggregate_oov_gaps(root, since, sample_limit) groups emitted
candidates by token, tracks intent-shape union (a token asked under
multiple intents is a stronger curriculum signal), splits
boundary_clean from boundary_tainted counts, supports --since
YYYY-MM filtering via the sink's file naming convention.
Pure reader; never mutates the sink. Deterministic ordering:
(count desc, token asc).
teaching/oov_promotion.py.
promote_oov_gaps(gaps, threshold, include_tainted, suggested_packs)
lifts threshold-crossing tokens to OOVPromotion records.
- boundary_clean_count gates promotion by default (tainted-only
tokens may indicate the prompt hit a safety axis rather than a
vocab gap).
- --include-tainted flag for operator override.
- threshold < 1 raises.
- queue_id deterministic: ``oov:<token>@<threshold>`` — diffable
across runs.
- suggested_packs lists mounted packs but does NOT recommend one
— domain inference is out of scope (would require a stochastic
classifier). Operator picks the destination.
Runtime wiring:
ChatRuntime.attach_oov_sink(sink) mirrors attach_discovery_sink.
Runtime emits one OOVCandidate JSONL line per turn whose
grounding_source == "oov", no-op when no sink is attached.
Intent classifier is now invoked when EITHER sink is attached
(was: only discovery sink) — both downstream paths need it.
CLI:
core teaching oov-gaps [--top N] [--since YYYY-MM] [--root PATH]
[--sample-limit N] [--json]
core teaching oov-queue [--threshold N] [--include-tainted]
[--root PATH] [--since YYYY-MM] [--json]
ADR-0065 documents the full design (five-tier honesty gradient,
P2.1-P2.4 architecture). README.md updated with the ADR-0065
index entry.
Verification:
tests/test_oov_pipeline.py 24 passed
Operator workflow round-trip verified live:
> rt.attach_oov_sink(sink); rt.chat("What is photosynthesis?")
→ sink receives:
{"boundary_clean":true,"candidate_id":"f51bf8...",
"intent":"definition","token":"photosynthesis","trigger":"unresolved_subject",
"source_turn_trace":"","review_state":"unreviewed"}
> core teaching oov-gaps --root /tmp/oov_demo
→ ranked table by count, intent-set per token
> core teaching oov-queue --root /tmp/oov_demo --threshold 2
→ promoted tokens + suggested mounted packs
Full lane: 2005 passed, 2 skipped, 0 failed in 2:34 (xdist).
ADR-0064 is the corpus-layer sibling of ADR-0063. The teaching-grounded
surface composer was hardcoded to cognition_chains_v1, so kinship CAUSE/
VERIFICATION prompts fell through to the universal disclosure even though
en_core_relations_v1 was mounted on the live runtime (ADR-0063).
Architectural change in chat/teaching_grounding.py:
- New TeachingCorpusSpec dataclass (corpus_id, path, pack_id).
- TEACHING_CORPORA tuple registers every active corpus. Each
corpus is 1:1-bound to one lexicon pack — cross-domain triples
deferred per docs/teaching_order.md §5.
- _load_corpus(spec) loads one corpus with pack-residency scoped
to its declared pack.
- _all_chains_index() aggregates across all registered corpora
(first-match-wins; cognition first preserves byte-identity).
- _pack_for_corpus(corpus_id) → bound pack lexicon.
- clear_teaching_caches() atomic cache invalidation.
- TeachingChain gains corpus_id field → surface tag follows resolving corpus.
Wiring updates:
- teaching_grounded_surface + teaching_grounded_surface_composed
consult _all_chains_index; surface tag follows chain.corpus_id.
- teaching/discovery.py gate uses chat.pack_resolver.is_resolvable
(any mounted pack) + _all_chains_index (any registered corpus).
- teaching/replay.py _swap_corpus_path rewrites the registry path
+ clears all teaching caches during the gate's transient phase.
Active corpus bytes unchanged (replay invariant preserved).
- evals/learning_loop/run_demo.py scene-5 swap mirrors the new
pattern so the demo still grounds against transient corpora.
Back-compat preserved: _corpus_index, _CORPUS_PATH, TEACHING_CORPUS_ID
remain cognition-corpus-specific for audit/replay consumers.
Phase 1.4 — relations_chains_v1 seeded with 7 reviewed kinship chains:
cause_parent_precedes_child
cause_child_follows_parent
cause_ancestor_precedes_descendant
cause_descendant_follows_ancestor
cause_family_grounds_parent
verification_child_requires_parent
verification_descendant_requires_ancestor
5 of 8 relations lemmas covered. All connectives already humanised.
Strict pack-internal to en_core_relations_v1 (no cross-domain in v1).
Seed pattern matches cognition_chains_v1's original pre-ADR-0055 seed.
Live verification:
> Why does parent exist?
parent — teaching-grounded (relations_chains_v1):
kinship.ascendant.direct; kinship.parent.
parent precedes child (kinship.descendant.direct).
grounding_source = teaching
Cognition eval byte-identical to pre-ADR baseline:
public: intent 100% / surface 100% / term 91.7% / closure 100%
holdout: intent 100% / surface 100% / term 83.3% / closure 100%
Lanes green: smoke 67 / cognition 121 / teaching 17 / packs 6 /
runtime 19 / algebra 132 / full 1933 passed.
ADR-0063 closes the ADR-0048/0050/0053/0061 hardcoded-cognition-pack
asymmetry. New chat/pack_resolver.py provides resolve_lemma(lemma,
pack_ids) → (resolving_pack_id, semantic_domains) across an ordered
tuple of mounted lexicon packs (first-match-wins, lru_cache per-pack).
Surface composers in chat/pack_grounding.py now consult the resolver
instead of a hardcoded en_core_cognition_v1. en_core_relations_v1
joins RuntimeConfig.input_packs defaults; kinship lemmas now ground
on the live path:
> What is a parent?
parent — pack-grounded (en_core_relations_v1):
kinship.ascendant.direct; kinship.parent; biology.progenitor.
No session evidence yet.
Cross-pack comparison (knowledge × parent) renders composite tag
(en_core_cognition_v1 × en_core_relations_v1). Cognition lane
remains byte-identical: cognition is resolved first and the surface
format for cognition lemmas is unchanged.
Cognition eval (byte-identical to pre-ADR baseline):
public → intent 100% / surface 100% / term 91.7% / closure 100%
holdout → intent 100% / surface 100% / term 83.3% / closure 100%
Curated lanes green: smoke 67 / cognition 121 / teaching 17 /
packs 6 / runtime 19 / algebra 132.
New tests: test_pack_resolver.py (28) + test_cross_pack_grounding.py
(17). test_en_core_relations_v1_pack.py: default-input-packs guard
inverted. test_pack_grounding.py: two stale ADR-0048 tests rewritten
(premises invalidated by ADR-0052/0061; now use fully-out-of-pack
prompts).
chat/teaching_grounding.py UNCHANGED — cognition_chains_v1 corpus
stays cognition-only. Cross-pack teaching corpora are the natural
ADR-0064.
Pre-ADR-0062, the teaching-grounded composer emitted exactly one
reviewed chain per surface — "light reveals truth" — even when the
corpus already contained an immediate follow-up "truth grounds
knowledge". With 21 active chains after curriculum saturation v2,
many grounded prompts had a corpus-ratified follow-up the composer
silently dropped.
ADR-0062 adds the composed composer + an opt-in config flag:
flag OFF (default):
light — teaching-grounded (cognition_chains_v1): cognition.illumination;
logos.core. light reveals truth (cognition.truth). No session evidence yet.
flag ON:
light — teaching-grounded (cognition_chains_v1): cognition.illumination;
logos.core. light reveals truth (cognition.truth), which grounds
knowledge (cognition.knowledge). No session evidence yet.
Follow-up resolution:
- prefer cause; fall back to verification (deterministic preference)
- cycle guard: 1-step cycles (A→B, B→A) blocked
- pack-residency guard: follow-up's object must be pack-resident
- bounded depth: v1 follows exactly one hop
- degrades to single-chain BYTE-IDENTICALLY when no follow-up
survives the guards (drop-in replacement)
Trust-boundary invariants preserved:
- Every visible non-template token is lemma / pack-domain /
humanize_predicate connective / template constant. Only added
template constant: ", which "
- Deterministic: same chains → same surface bytes
- Default-False flag pattern mirrors ADR-0047/0058
- `versor_condition < 1e-6` invariant untouched (surface composition only)
Cognition lane null-drop invariant CI-pinned:
Composed mode emits a strictly LONGER surface (extra follow-up
clause); every expected_term passing flag-OFF must still pass flag-ON.
Asserted in test_cognition_lane_metrics_unchanged_with_composed_flag
for both public and holdout splits. If a future change drops tokens,
the test fails as a deliberate regression.
public flag OFF: intent 100% / surface 100% / term 91.7% / versor 100%
public flag ON : intent 100% / surface 100% / term 91.7% / versor 100% (identical)
holdout flag OFF: intent 100% / surface 100% / term 83.3% / versor 100%
holdout flag ON : intent 100% / surface 100% / term 83.3% / versor 100% (identical)
Live-prompt lift visible on ~12 of 21 active chains; the rest hit
cycle or pack-residency guards. Saturation v2's clusters were
authored partly with composition in mind (thought→meaning→
understanding, inference→evidence→knowledge, etc.).
- core/config.py — `RuntimeConfig.composed_surface: bool = False`
- chat/teaching_grounding.py — `teaching_grounded_surface_composed`
sibling to `teaching_grounded_surface`
- chat/runtime.py — dispatch branch in `_maybe_pack_grounded_surface`
selects composed vs single-chain based on config flag
- tests/test_composed_surface.py — 11 tests pin: function-level
(None on no chain / degrades when no follow-up / two-clause when
follow-up exists / includes intermediate + final domains /
deterministic / cycle guard / trust label preserved); runtime
integration (default single-chain / flag-on composed / frozen
config); cognition-lane null-drop invariant.
Lanes (regression): smoke 67 / cognition 121 / teaching 17 /
composed-surface 11 — all green.