core/docs/plans/grammar-unification-2026-07-26.md
Shay 7acd6b9722 docs(plans): a ratified band serves malformed English; correct Phase 2 risk
Two additions found while designing Phase 1's projection.

1. NEW §1.7 — band v1b serves malformed surfaces today, user-visible:

     in : All dogs are mammals. All mammals are animals.
          Therefore all dogs are animals.
     out: Given: all dog are mammal; all mammal are animal.
          That's valid — all dog are animal follows.

   render.py::_CATEGORICAL_PHRASE uses plural syntax ("all {s} are {p}")
   but interpolates the reader's SINGULARIZED entity ids, with no
   re-pluralization step — because reader and renderer sit on opposite
   sides of the §1.3 seam. Affects every plural noun, regular or
   irregular; 4 of 47 ratified corpus cases that serve a categorical
   clause (ds-v1-0023/0024/0026/0028). wrong=0 is NOT threatened — every
   verdict is correct; this is purely surface.

   Compounding cause: the reader's plural table has 8 entries where the
   v3-MEM band's has 29, so wolves->wolve, knives->knive, leaves->leave,
   lives->live, halves->halve, loaves->loave, thieves->thieve,
   elves->elve. CORE's two readers disagree on 12 of 20 plurals. The
   wrong singulars do not break soundness (uniform deterministic
   bijection) but they are why cause 2 needs a SHARED table.

2. CORRECTED §4 — the earlier draft claimed "Phases 1-3 cannot change
   serving behaviour." That was wrong, and it is the same shape as the
   mistakes this arc exists to fix: I classified a table as off-serving
   without asking which callers reach it. reader.py::_IRREGULAR_PLURALS
   IS serving-reachable (comprehend -> to_syllogism -> v1b ->
   deduction_grounded_surface).

   New rule: a table is off-serving only when EVERY caller is proven
   eval-only. Phase 2 splits on that test into 2A (off-serving,
   byte-identical) and 2B (serving tables + the render fix,
   authorization-gated). Phase 2A now ships a caller-provenance test so
   this check lives in the tree, not in a reviewer's head.

Refs: ADR-0256 (band v1b), ADR-0258 (v3-MEM plural table)
2026-07-26 15:37:26 -07:00

28 KiB
Raw Blame History

Grammar Unification — Plan of Record

Date: 2026-07-26 · Status: ACTIVE · Base: main @ 9696443a (post ADR-0264 ratification, PR #127)

One sentence: CORE cannot read its own writing — measured 0/280 — and this arc makes reading and writing share one grammar so that every construction taught works in both directions, with round-trip as the falsifiable gate.


0. Why this arc, and why now

The curriculum-license-loop arc closed on the finding that the outcome-mix ruling is the binding constraint for curriculum serving. That finding stands. It is not, however, the binding constraint on delivered capability, and the measurements in §1 say what is.

Six ADR-scoped deduction bands (v1, v1b, v2-EN, v3-MEM, v4-CM, v5-VP, v6-EX) are ratified, flag-ON, and wrong=0. They are reachable only through looks_like_deductive_argument, which is sentence-initial "therefore":

"If it rains then the ground is wet. It rains. Therefore the ground is wet."
  → Given: if it rains then the ground is wet; it rains.
    Your premises entail: the ground is wet.                    DECIDED

"It rains. So the ground is wet, right?"
  → Pack-resident tokens — pack-grounded (en_core_cognition_v1):
    ground (relation.ground; predicate.support)…                TOKEN DUMP

"All men are mortal and Socrates is a man, so he must be mortal."
  → Pack-resident tokens — pack-grounded (en_core_quantitative_v1):
    all (quantitative.universal.total…)                         TOKEN DUMP

Each band widened what CORE can decide. None widened what a person can ask. Deepening the room behind the keyhole is not progress in delivered capability, and the keyhole is a reading problem.

This arc is scoped to the reading/writing seam. It does not author content, does not move a flag, and does not touch the decision layer.


1. Measured baseline

Every number below was measured on main @ 9696443a by reading source and running probes, not inferred from docs. Reproduction scripts land in Phase 1.

1.1 The writer that serves is eight format strings

generate/semantic_templates.py::render_semantic — the function core/cognition/pipeline.py actually calls — is a dict of 8 format strings keyed on IntentTag, plus a predicate-display dict. No morphology, no agreement, no tense, no negation, no pluralization, no clause joining.

Best-case live output:

Q: Explain knowledge. A: Knowledge is what a person knows from truth and evidence. Furthermore, it belongs to cognition.knowledge. In turn, it requires evidence.

One fact per sentence, uniform subject-predicate-object, connectives from a fixed bucket, and an internal dotted path (cognition.knowledge) leaking into prose.

1.2 The good realizer exists and never serves

generate/templates.py::render_step (232 lines) has real linguistics — quantifier agreement, mass vs count nouns, irregular morphology, tense/aspect, and via realize_target it joins clauses with and / or / that / which.

It is called from exactly three places, all evals/*/runner.py. core/cognition/pipeline.py calls realize_semantic, never realize_target. This was deliberate — core/cognition/surface_resolution.py records that the realize_semantic path "was granted supremacy by the Shadow Coherence Gate" — but the consequence is that english_fluency_ood 117/117 + 39/39 scores a function that never speaks.

1.3 Linguistic knowledge is duplicated 35-vs-9 ways, Jaccard 0.083

word-tables distinct words
reading path (9 files) 35 202
writing path (6 files) 9 359
shared 43 of 518 union (Jaccard 0.083)

Tables that should agree and do not:

linguistic fact copies status
irregular plurals 3 (member.py 29, reader.py 8, templates.py 25) all three diverge
quantifier tokens 5 across both paths 3-way divergence
connective tokens 4 (english, member, verb, cond_member) 3 identical, english adds therefore
sentential-negation prefix 2 (english, member) identical
negation-bearing tokens 2 diverge by 'not'
predicate display 2 (semantic_templates, templates) identical, 26 entries
copula forms 2 same content, different type (frozenset vs tuple)

Demonstrated consequences of the plural divergence:

  • CORE reads wolves / knives / leaves / thieves / halves / loaves / elves / cacti / fungi / dice / aircraft / offspring and cannot write them — pluralize("cactus") → "cactuses", pluralize("die") → "dies".
  • CORE writes criterion→criteria, phenomenon→phenomena, analysis→analyses, hypothesis→hypotheses, axis→axes, basis→bases, thesis→theses and the reader's table knows none of those plurals.

Note this duplication is not only read-vs-write: two of the three plural tables are both on the reading side, and four connective tables are all on the reading side. The six deduction bands were built as successive independent tiers, each re-encoding English from scratch.

1.4 A demonstrated grammar defect: 9 of 26 predicates mis-agree

_inflect_predicate(display, plural_subject=True) against a hand-written English oracle:

plural-subject agreement over _PREDICATE_DISPLAY: 17/26 correct, 9 WRONG

  belongs_to             'belongs to'              -> 'belongs to'              want 'belong to'
  causes                 'causes'                  -> 'caus'                    want 'cause'
  contrasts_with         'contrasts with'          -> 'contrasts with'          want 'contrast with'
  has_steps              'has the following steps' -> 'has the following step'   want 'have the following steps'
  is_caused_by           'is caused by'            -> 'is caused by'            want 'are caused by'
  is_defined_as          'is defined as'           -> 'is defined a'            want 'are defined as'
  is_distinguished_from  'is distinguished from'   -> 'is distinguished from'   want 'are distinguished from'
  is_grounded_in         'is grounded in'          -> 'is grounded in'          want 'are grounded in'
  is_verified_as         'is verified as'          -> 'is verified a'           want 'are verified as'

Live example: render_step(ASSERT, "wolf", "is_defined_as", "mammal", quantifier="all")"all wolves is defined a mammal".

Two root causes, both single-point:

  1. base_form() is written for single verbs and applied to whole humanized predicate phrases. It strips the last character-class of the final word: "is defined as" → "is defined a", "has the following steps" → "has the following step", "causes" → "caus".
  2. The copular flag is computed in _inflect_predicate and consulted only on the negated-singular branch. The plural branch (case (_, _, False, True)) ignores it, so is/has/belongs never becomes are/have/belong.

Singular is clean: 0 of 26 differ. This defect reaches zero served turns today because render_step is eval-only (§1.2). It matters because the fluency suite scores this function, and because Phase 4 would wire it into serving.

1.5 The fluency instrument is decorative

evals/deterministic_fluency reports 1.00 on all six predicates. Control test (the absence test — a green measurement that would look identical with the mechanism removed):

candidate surface passes all 5 content predicates?
"Truth is what corresponds to reality, and knowledge is belief grounded in it." yes
"colorless green ideas sleep furiously." yes
"wet ground rains the is." yes
"is is is is." yes
"banana does the." yes

It checks terminal punctuation, presence of a verb-ish token, and two anti-shape regexes. It cannot distinguish English from word salad.

And the suite cannot see §1.4 either. Across all 280 cases in english_fluency_ood + grammatical_coverage:

  • 40 use a quantifier — only all and some
  • 0 use any copular predicate
  • 0 exercise the quantifier × copular combination that breaks

english_fluency_ood's own accept_surfaces include "river flows valley" and "wind shapes dune" — token-ordered, not English. The 100% is real against the lane's contract and says nothing about fluency.

1.6 The headline: round-trip is 0/280, and all six readers refuse

Feed each of the 280 writer-produced surfaces to meaning_graph.comprehend:

surfaces written by CORE : 280
reader COMPREHENDED      : 0/280 = 0.0%
reader REFUSED           : 280/280 = 100.0%
refusal reasons          : {'no_template_match': 280}

Control (proves the harness works): the reader comprehends 31/166 = 18.7% of the deduction_serve corpus, and comprehend("Socrates is a man.") returns 2 entities + member(socrates, man).

Six readers × four writer outputs, including CORE's own live served surfaces — 24 refusals, 0 comprehensions:

writer output comprehend english member cond_member verb exist
Molecule binds enzyme. (writer's own accept_surface) no_template_match no_conclusion out_of_band out_of_band no_conclusion no_conclusion
all wolves is defined a mammal no_template_match empty empty empty empty empty
Knowledge is what a person knows from truth and evidence. (live) no_template_match no_conclusion mixed_structure out_of_band mixed_structure mixed_structure
Wisdom is a good use of knowledge. Furthermore, it belongs to cognition.wisdom. (live) reserved_word_in_np membership_shape out_of_band out_of_band out_of_band out_of_band

1.7 A ratified band serves malformed English today

This was found while designing Phase 1's projection, and it is the most consequential item in §1 because it is user-visible on a ratified, flag-ON band.

generate/proof_chain/render.py:

_CATEGORICAL_PHRASE = {
    "A": "all {s} are {p}",     # plural syntax…
    "E": "no {s} are {p}",
    "I": "some {s} are {p}",
    "O": "some {s} are not {p}",
}

…but {s} / {p} are filled with the singularized entity ids the MeaningGraph reader produces (_chunk_class_singularize). There is no re-pluralization step, because the reader and the renderer are on opposite sides of the seam described in §1.3. Result, live through deduction_grounded_surface:

in : All dogs are mammals. All mammals are animals. Therefore all dogs are animals.
out: Given: all dog are mammal; all mammal are animal. That's valid — all dog are animal follows.

in : No dogs are cats. All cats are animals. Therefore no dogs are animals.
out: Given: no dog are cat; all cat are animal. That doesn't follow — …

in : All wolves are mammals. All mammals are animals. Therefore all wolves are animals.
out: Given: all wolve are mammal; … — all wolve are animal follows.

Scope, measured two ways:

  • Synthetically: every plural noun, regular or irregular. all dog are mammal, all cat are mammal, all student are mammal, all child are mammal, all man are mammal.
  • In the ratified corpus: 4 of 47 cases that serve a categorical clause — ds-v1-0023, ds-v1-0024, ds-v1-0026, ds-v1-0028, all band v1. The corpus under-represents the defect only because band routing sends most arguments to renderers that echo the user's original text.

wrong=0 is not threatened. Every verdict above is correct; this is purely a surface defect. Two distinct causes compound in it:

  1. No re-pluralization at render time — affects all nouns. This is the dominant cause.
  2. The reader's plural table is 8 entries where the v3-MEM band's is 29, so wolves → wolve, knives → knive, leaves → leave, lives → live, halves → halve, loaves → loave, thieves → thieve, elves → elve8 silently wrong singulars, and 4 more (cacti, dice, fungi, oxen) that refuse outright. CORE's two readers disagree on 12 of 20 plurals.

The wrong singulars do not break soundness, because _singularize is a deterministic function applied uniformly to premises and conclusion, so the lowering stays a consistent bijection. They are label defects — and they are the reason cause (2) must be fixed with a shared table rather than by patching one side.

1.8 The models are not type-compatible

A literal read(write(g)) == g is not expressible today:

  • MeaningGraph = Entity(entity_id, name, span, kind) + Relation(predicate, arguments: tuple[str, ...], span, negated) — n-ary predicate/argument structure with source spans.
  • PropositionGraph = GraphNode(node_id, subject, predicate, obj, source_intent, language, root, morphology_id) + GraphEdge(source, target, relation) — fixed SPO triples with discourse edges.

Round-trip therefore needs a defined projection, not identity. Choosing that projection is Phase 1's first design decision, and §6 records why this matters for direction.


2. Goal, non-goals, and the thesis check

Goal. One grammar, used in both directions, so that a construction taught once works for reading and for writing, and so that surface diversity becomes verifiable rather than risky.

Non-goals — stated so success is not silently redefined:

  • Not "prettier prose." Fluidity is a function of construction-inventory size, which is content work. This arc builds the mechanism that makes growing the inventory safe and measurable.
  • Not an LLM, sampler, or paraphraser. No stochastic generation enters the truth path. See the thesis check below.
  • Not curriculum content. Scripture/theology content is separately deferred (word-for-word verification + a faith-domain evidentiary regime are both undesigned).
  • Not a graph-model merge. §1.8 says that may be needed; §6 makes it a measured decision for a later arc behind an ADR, not a silent expansion of this one.

Thesis check — "decoding, not generating." Every ADR must pass this. A bidirectional grammar is a decoder run in reverse: writing selects among surface forms that the decoder provably maps back to the same graph. Nothing is sampled, nothing is invented, and every emitted variant carries a proof of meaning-preservation. Diversity comes from enumerating verified forms, not from generating candidates and hoping. chat/register_variation.py already implements exactly the deterministic seeded selector this needs (uniform, replay-stable, variant_id in the audit stream).


3. The central design decision: the instrument is the fix

An honest fluency measurement cannot be built out of heuristic predicates — §1.5 is what that attempt already produced, and any new heuristic set will be gameable the same way. You cannot measure grammaticality without a grammar.

Therefore the instrument and the remedy are one artifact:

  • A grammar that parses is a measurement (word salad fails to parse).
  • The same grammar used to generate is the remedy.
  • Round-trip forces one artifact to do both, and is falsifiable without a judge, an embedding, or a gold aesthetic.

Necessary, not sufficient — stated up front. Round-trip measures mutual intelligibility, not English quality. "river flows valley" may round-trip perfectly while remaining un-English, because both sides can share an impoverished construction. So the lane needs two corpora and both are load-bearing:

  • a positive corpus (round-trip must succeed), and
  • a negative corpus of word salad (round-trip must fail, 100%).

If the lane ever reports high scores on both, the lane is broken. That bidirectional requirement is what keeps this instrument from becoming the next §1.5.


4. Phases

Each phase is independently valuable, independently landable, and has a falsifiable exit test.

Serving-risk classification — corrected 2026-07-26 after §1.7. An earlier draft of this plan asserted "Phases 13 cannot change serving behaviour." That was wrong, and the error is worth recording because it is the same shape as the mistakes this arc exists to fix: I classified a table as off-serving without asking which callers reach it. generate/meaning_graph/reader.py::_IRREGULAR_PLURALS is on the serving path — comprehendto_syllogism → band v1b → deduction_grounded_surface. Unifying it changes entity ids, which moves trace hashes and served surfaces.

The corrected rule for this arc: a table is off-serving only when every caller is proven to be eval-only. Phase 2 is therefore split by that test, not by topic.

phase changes served output? gate
1 — instrument no (new files only) local smoke/deductive
2A — off-serving tables no (callers proven eval-only) byte-identical lane SHAs
2B — serving tables + render fix yes explicit authorization
3 — agreement defect no (render_step is eval-only) 26/26 oracle
4 — which realizer serves yes explicit authorization
5 — round-trip + diversity yes explicit authorization

Phase 1 — Build the instrument, record the baseline

Touches: new evals/grammar_roundtrip/, new generate/grammar/ (smallest shared construction inventory), reproduction scripts for every §1 number. Serving risk: none (new files + eval lane only).

Deliver:

  1. evals/grammar_roundtrip/ lane reporting, over a positive corpus: write_rate, read_rate, graph_match_rate under the chosen projection.
  2. Over a negative corpus (hand-written word salad + programmatically shuffled tokens from positive cases): reject_rate.
  3. The chosen MeaningGraphPropositionGraph projection, written down with what it deliberately discards.
  4. Reproduction scripts for §1.3§1.7 so every number in this plan is re-runnable.

Exit criteria (all required):

criterion measure
baseline recorded read_rate on the 280-case corpus, expected ≈ 0.0%
word salad rejected reject_rate = 1.00 on the negative corpus
lane is non-vacuous mutating the reader to accept-everything drives reject_rate → 0 (must go red)
no behaviour change smoke 621, deductive 349, 11/11 lane SHA pins unchanged

Failure / stop condition: if the negative corpus cannot be rejected without also rejecting the positive corpus, the grammar formulation is wrong. Stop and reassess — do not tune a threshold to split them.

Phase 2A — One source of linguistic truth, off-serving only

Touches: the word-tables in §1.3 whose every caller is proven eval-only, plus new single-owner modules. Serving risk: none, and proven by byte-identity.

Deliver:

  1. One module owning each linguistic fact: plurals, quantifiers, connectives, copulas, negation-bearing tokens, predicate display.
  2. The identical duplicate groups collapsed — 3 connective tables, 2 _SENTENTIAL_NOT, 2 predicate-display tables — with byte-identical lane hashes as the proof of zero behaviour change.
  3. Each divergence among off-serving tables resolved by an explicit recorded decision (not by picking the larger table), with a test pinning the answer.
  4. A structural test asserting no module defines a second copy — same discipline as tests/test_volume_honesty.py, which must fail if a duplicate is reintroduced.
  5. A caller-provenance test: for every table the new module owns, assert whether it is serving-reachable. This is the check whose absence produced the error corrected above, so it belongs in the tree, not in a reviewer's head.

Exit criteria:

criterion measure
duplication removed one table per fact; structural test green and mutation-red
identical collapses are inert 11/11 lane SHA pins byte-identical
divergences decided, not averaged each has a recorded decision + pinning test
agreement Jaccard → 1.00 for facts that should agree (from 0.083 overall)
serving reachability is known caller-provenance test covers every owned table

Failure / stop condition: if collapsing an "identical" pair moves a lane hash, the tables were not actually identical — stop, treat it as a divergence requiring a decision, and move it to Phase 2B.

Phase 2B — Serving-path tables and the categorical render defect — authorization gate

Touches: generate/meaning_graph/reader.py::_IRREGULAR_PLURALS, generate/proof_chain/render.py::_CATEGORICAL_PHRASE, the band readers' tables. Changes served surfaces and trace hashes. Depends on 2A's shared pluralizer.

Deliver:

  1. Re-pluralization at categorical render time, fixing all dog are mammalall dogs are mammals (§1.7 cause 1 — affects all nouns).
  2. The reader's 8-entry plural table replaced by 2A's shared table, fixing the 8 silently-wrong singulars and the 12-of-20 reader-vs-reader disagreement (§1.7 cause 2).
  3. Updated lane SHA pins — surgical single-line edits only, never --update — with the old and new hash recorded per lane.

Exit criteria:

criterion measure
surfaces well-formed 0 malformed categorical surfaces; ds-v1-0023/0024/0026/0028 render plurals
readers agree reader-vs-reader singularization agreement 20/20 (from 8/20)
soundness preserved wrong=0 holds; deduction lane verdicts unchanged case-for-case
hash movement is intended every moved pin explained; no pin changed that shouldn't move

Why gated: this changes what users see. Fixing a defect is still a serving change, and serving changes are Shay's call.

Phase 3 — Fix the 9-of-26 agreement defect

Touches: generate/morphology.py::base_form, generate/templates.py::_inflect_predicate, new eval cases. Depends on Phase 2A (unified tables) and Phase 1 (instrument). Serving risk: none (render_step is eval-only).

Deliver:

  1. base_form no longer applied to multi-word phrases — phrase-head handling.
  2. The copular flag consulted on the plural branch.
  3. Eval cases covering quantifier × copular, which is 0 of 280 today.

Exit criteria:

criterion measure
agreement correct 26/26 against the oracle (from 17/26)
the gap is closed for good new cases exercise quantifier × copular; reverting either fix goes red
no regression english_fluency_ood 117/117 + 39/39 holds; smoke/deductive unchanged

Phase 4 — Resolve realize_semantic vs realize_targetauthorization gate

Touches: core/cognition/pipeline.py or the eval lanes. First phase that can change served output. Requires explicit authorization before merge.

The current state is untenable in one specific way: 149 green fluency cases score a function that never serves. Exactly one of these must become true:

  • (a) realize_target earns the serving path, gated on the Phase 1 round-trip instrument and a wrong=0 hold; or
  • (b) the lanes scoring it are retired/re-pointed, and the claim "realizer fluency is mechanistic" is restated as a claim about eval-only code.

Exit criterion: no eval lane scores a non-serving function. Whichever branch is taken is recorded with its reasoning.

Why this is gated: (a) changes what users see and would move surface hashes. It is a serving change and belongs to Shay, not to this plan.

Phase 5 — Raise round-trip, then earn diversity

Depends on: Phases 13 plus 2B, and on the Phase 1 baseline being non-zero-able. Scope: deliberately unestimated — see §5.

Deliver:

  1. Construction inventory grown until read_rate clears a floor, with the floor ratcheted (never lowered to accommodate a regression).
  2. N > 1 verified surface forms per graph, each proven to round-trip to the same graph.
  3. Deterministic seeded selection among verified variants, reusing chat/register_variation.py's existing selector, with variant_id in the audit stream.

Exit criteria:

criterion measure
round-trip improved read_rate clears a ratcheted floor, well above the ≈0% baseline
diversity is real mean verified variants per graph > 1, measured
diversity is safe 100% of emitted variants round-trip to the source graph — zero exceptions
truth path untouched wrong=0 holds; variant selection provably cannot alter a verdict

5. What is deliberately not estimated

Phase 5's size cannot be estimated before Phase 1 produces real numbers, and pretending otherwise is how the last arc acquired an unreachable exit criterion. Phases 13 are bounded and specified. Phase 5 is scoped only after Phase 3 reports.

I also do not claim Phases 14 produce human-like prose. They produce: a measurement that cannot be faked, a single source of linguistic truth, a fixed agreement defect, and an honest answer about which realizer serves. That is the foundation the diversity work needs — and it is worth landing even if Phase 5 is later judged infeasible.


6. The decision rule for afterwards

The point of the instrument is that the next direction is chosen by a number, not by argument. After Phase 3, read_rate is re-measured on the unified grammar. That number forks the road:

  • read_rate rises materially from unification alone ⇒ the two grammars were closer than they looked, the surface layer was the barrier, and Phase 5 (diversity) is the right next arc.
  • read_rate stays near zero after unification ⇒ the barrier is §1.8: MeaningGraph and PropositionGraph are genuinely different models, and no amount of table-sharing bridges them. The next arc is then a graph-model decision, which is large, touches the decision layer, and must go to an ADR before any code. It is explicitly not an extension of this plan.

Either way we will know which, and why, from a measured quantity — which is the thing the previous arc could not do until after it had built the machinery.

One reading of the evidence is worth pre-committing to now: §1.6's uniform no_template_match across all 280, combined with §1.8's type mismatch, makes the second fork more likely than the first. If that is where we land, Phases 13 are still exactly the right work — they are what makes the graph-model question answerable instead of speculative — but the arc should be expected to end at the ADR rather than at fluid prose.


7. Verification protocol

Per AGENTS.md, unchanged from prior arcs:

  • Every phase, in-worktree on canonical CPython 3.12.13 with uv sync --locked, before push: uv run core test --suite smoke -q then --suite deductive -q. Baselines: smoke 621 / deductive 349.
  • Every new gate must fail under mutation. A pin that cannot fail is worthless — this arc exists because three such pins were found (§1.5, the Rust parity lane, and the fluency suite's blind spot).
  • Phase 2A additionally requires byte-identical 11/11 lane SHA pins. Never scripts/verify_lane_shas.py --update; surgical single-line edits only.
  • One PR per phase, branch + worktree each, Forgejo compare URL, [Verification]: line. No merge automation — PRs sit until authorized.
  • Phase 4 additionally requires explicit serving-change authorization.

8. Standing traps carried into this arc

  • then / therefore are taught philosophy_theology lemmas AND the argument reader's control words. The vocabulary boundary is never screened against the reader's grammar. A unified grammar makes this screenable for the first time — and makes forgetting to screen it a single-point failure. Add the screen in Phase 2A.
  • Corpus-mutating tests must never write committed files: the full suite injects -n auto, so writes race.
  • Never "fix" a hash drift or a ledger inventory to make a test pass.
  • PYO3_PYTHON=/opt/homebrew/bin/python3.12 is required for core-rs builds.