scripts/hooks/pre-push — the automation of the AGENTS.md local-first protocol
— opens by describing its own step 1 as "the smoke suite — exact CI-gate
parity". It runs 23 files; the CI gate runs 13. That is the second in-repo
assertion of a parity that does not hold, the first being core/cli_test.py's
audio block, and it is the strongest evidence available that the drift was
never intended rather than a deliberate narrowing.
Phase 6 of the holistic assessment: what follows from it, and what it needs
ruled before anything else can be sequenced.
`50-execution-plan.md` — five waves (scrub/rule → delete → simplify/enforce →
accelerate evidence loops → automate last) plus five frontier tracks, covering
every G-1…G-20 and H-1…H-12 entry, with four hard dependency gates and nine
risks stated before they are incurred.
`50-rulings.md` — R-1…R-14. One decision per section: the evidence verified at
ed06dd64, the options enumerated, a recommendation with its reasoning, and the
exact diff that follows from each choice, so ruling costs one word. Nothing in
it recommends reversing a ratified decision; R-12 amends two ADRs' records
without touching either decision.
§0 of the plan carries SEVEN corrections to the assessment, all found by
reading code, workflows and ADR supersession banners — none by re-reading
documents. Four change planned work:
- N-3 (new) the local smoke suite (23 files) and smoke.yml (13) have diverged
by 10 files. Severity is LOWER than this plan's first draft stated: AGENTS.md
makes the in-worktree run the merge gate and CI secondary observability, and
the local gate is the superset — so nothing is unguarded. What is real is one
in-code comment claiming an equality that does not hold, and the push that
skipped the local gate, for which CI is the only automatic check.
- N-4 the L10 soak HAS been run — 5000 beats, reboot at 2500, all four gates
pass (contract.md, landed aed273b1). G-5's "never produced an artifact" is
wrong. The debt is the ceremony: no committed report, no pinned
deterministic_digest (the contract's own unmet closing instruction), no
cadence. Prose with no pinned digest is testimony, not evidence.
- N-5 G-10's blocker does not exist. ADR-0264 §4.1 carries a self-supersession
banner: R5 removed the 16-premise cap on 2026-07-26, and four bands would
earn SERVE the moment a ledger is sealed. PR-13 is WITHDRAWN and the whole
throughput frontier reduces to one ruling (R-8).
- N-7 the flag count is 28 of 32 RuntimeConfig booleans, not seventeen.
Plus N-1 (the orphan scan artifact: TEST_SUITES["full"] is a directory),
N-2 (the L10 pins run twice a day, post-merge and nightly), and N-6 (H-8 has a
fourth instance inside the code — two flag docstrings assert a production
profile the production profile does not have, which moves R-3 decisively
toward "incomplete flag set").
Docs only. No runtime behavior, no flag, no test changed.
40-assessment.md — the verdict in one paragraph (skeleton real, discipline
exceptional, the two deepest commitments unproven in opposite directions);
the cognitive cycle stage-by-stage with the honest sentence for each; what
is excellent, stated as findings; the macro picture (built-and-dark,
capability outruns proof, the record decays faster than the code); THE
FIVE FRONTIERS (the reading, the verdict, the chooser, the proof of life,
the throughput — everything else on the registers is hygiene, enforcement,
or ceremony); the recommended attack order in five waves sequenced by the
mastery algorithm (scrub/rule -> delete -> simplify/enforce -> accelerate
evidence loops -> automate last, with the warning that an always-on
process consolidating an empty set is "garbage at high speed" and CORE
came within one flag of it); the charter's four questions answered
directly; and the maintenance contract that keeps this directory from
becoming the next dead instrument.
README.md — the directory index and reading order.
The assessment is complete: 21 documents, four phases, three
self-corrections, every claim SHA-stamped at 8927c563.
30-gap-register.md — CORE's first LIVE gap register since docs/gaps.md
closed its 26th entry (supersession proposed for ruling). 20 entries in
four tiers, each with evidence, deciding authority, and leverage rank:
- Tier A (frontier-blocking): G-1 the unrun ADR-0252 §5 experiment
(leverage 1 — authorized, scaffolded, verdict-less; the paradigm governs
ALL future comprehension); G-2 the #138 fabrications (pre-labeled
measured-and-pinned, held for ADR + ratification); G-3 reader 19 vs
writer 1739; G-4 CR-2 no-chooser (the AGI-grade conceptual absence);
G-5 L10 proof debt (no artifact, no suite, no cadence); G-6 the
half-gated lived learning loop.
- Tier B (enforcement debt): G-7 orphaned-pin meta-check (the highest-
leverage single mechanical change); G-8 flag-default register; G-9 three
doctrine laws without verified failing pins; G-10 curriculum scoping +
absent ledger; G-11 identity authorization bar unstated.
- Tier C (one-line rulings): CR-3 efferent, CR-4 temporal stance, CR-1
attention ADR, the daemon's owning ADR.
- Tier D (latent/carried): aspect-arm defect class (generate/templates.py
:79, unreachable today), sensorium entry criterion, curriculum-formation
bypass (unverified carry), Wilson evidence debt, refusal materialisation.
- Plus what is NOT in the register and why (deferred-with-ruling is the
scripture model; benchmarks excluded by the completeness criterion).
31-hindrance-audit.md — 11 entries ranked by leverage, each with fitness
verdict, evidence, proposed better home, deciding authority; decides
nothing. Headliners: H-1 Wilson/replay counting basis (wrong-solution in
the counting, not the gating — 21/25 bands short; the re-count may demote
licenses and that is the mechanism working); H-2 decoration as testimony
(DriveGradientMap, InhibitionMask — deletion per mastery step 2); H-3 the
typed refusal discarded at the public boundary; H-4 extend the resolver's
declared-precedence pattern upstream; H-8 three record/code contradictions
(each one paragraph to fix); H-9 dead instruments standing as if live.
Plus five examined-and-CLEARED candidates (the 18 organs' continued
service is governance working; pure-Python-by-default is measured-correct;
flag-gated conservatism is not hindrance — unregistered darkness is).
Eight component cards in 20-component-cards/: the four zero-subsystem zones
(comprehend-organ, determine-phase, realize-phase, sensorium-falsification)
plus always-on-process, derivation-organs, surface-selection,
attention-allocation. Plus 05-phase3-findings.md and correction banners on
the M6 and M4 layer cards.
CORRECTIONS (C-1..C-5):
- THREE more map live-serving labels demoted to live-internal:
comprehend-organ (a MATH SETUP ROUTER, not chat comprehension — imported
by neither serving entrypoint), determine-phase and realize-phase (both
behind accrue_realized_knowledge=False / idle flags; NO default-config
serving turn touches any of the three).
- Phase 2's M6 card overstated ephemerality: Shape B+ persistence exists
(persist_session_state, daemon-forced, "restored bit-exactly").
- Phase 2's M4 card understated the resolver: surface_resolution.py IS a
declared-precedence resolver for the pipeline seam; only the upstream
composer arms remain ordered branches. Third Door refines to "extend the
existing pattern upstream."
- 34-vs-18 RESOLVED: 18 resolve_promotable_* organs existed AT the ADR-0252
ratification commit; "34" counted modules (~32-33). No consolidation has
occurred; the governing ADR's headline number has no stated basis.
- The 2026-07-25 verification doc's claim that accrual "is enabled by the
production L10 process" is contradicted: CONTINUOUS_LIFE_CONFIG_FLAGS is
exactly {persist_session_state, consolidate_determinations,
strict_identity_continuity}.
NEW FINDINGS (F-6..F-10):
- F-6 THE CONTINUOUS LIFE MAY CONSOLIDATE AN EMPTY SET: the daemon forces
the consolidator ON but not the accruer, and realize_comprehension — the
only turn-path writer of realized facts — sits behind the accrual flag.
The two halves of the lived learning loop are gated by different flags
and only one is forced. Undocumented either way.
- F-7 ADR-0008's Allocation Physics LANDED, mutated, ungoverned: curvature
kernel -> salience adapter -> attention threshold plan -> candidate
intersection, with budget feedback making attention SELF-NARROWING
across a walk. InhibitionMask is decoration. top_k=16/threshold=0.3 have
no recorded derivation. CR-1 closes with a one-page ADR.
- F-8 two load-bearing naming traps (comprehend-organ; realize vs realizer).
- F-9 REALIZE is sound; its ceiling is the reader that feeds it (the #138
fabrication locus — measured/pinned/held, recorded not re-discovered).
- F-10 _accrue_in_turn swallows exceptions into a no-op with no telemetry.
Phase 4 seed list consolidated: 8 hindrance candidates, 10 gap candidates,
each with its deciding authority. Method note recorded: three phases of
corrections, and nothing in the chain was caught by re-reading documents.
Fills the taxonomy with evidence. Every liveness label re-verified at
8927c563 rather than inherited from the 2026-06-09 system map. Three
inherited claims did not survive, and every one of them came from a
document while every falsification came from reading code.
CORRECTIONS:
- Phase 0 Finding 0-C was WRONG. The L11 always-on process IS BUILT:
chat/always_on.py::run_continuous, chat/always_on_daemon.py::run_daemon
(single-instance lock, SIGINT/SIGTERM stop, load-time identity guard),
CLI `core always-on`. Landed 2026-06-14 — five days AFTER the system-map
snapshot that declared no forever entrypoint exists. The real finding is
sharper: built, with a complete falsifiable soak harness, never run to a
recorded artifact, and enforced by NO SUITE AT ALL.
- CR-1 attention/allocation is not a missing layer but a live undocumented
one: generate/stream.py:255-263 runs SalienceOperator -> AttentionOperator
and gates every generation step; use_salience defaults True. It owns the
~73%-of-turn hot path and has no ADR, no card, no layer in any ratified
articulation. (generate/salience.py COMPOSES core.physics.salience — not
a duplicate; that false lead is recorded, not propagated.)
- CR-2's components exist: DriveGradientMap and ExertionMeter are both
constructed in chat/runtime.py. DriveGradientMap is NEVER READ — textbook
decoration by the sabotage test. ExertionMeter runs but feeds telemetry
only; fatigue gates no decision. The claim survives and sharpens: CORE has
drive objects and no chooser.
STAGE-COVERAGE AUDIT: 7 of 9 covered; non-text ingest uncovered (59
sensorium modules reach no serving path); the cycle's runner claimed-only.
CROSS-CUTTING: F-1 built-and-off is the dominant pattern (17 flags default
False, only deduction_serving ratified ON) with no register of the set or
what would flip it. F-2 enforcement lags capability — orphaned pins are
undetectable because suite tuples are hand-curated. F-3 formation's
six-boundary trust standard is not applied at M2, the surface facing
untrusted user text. F-4 ADR/code contradictions are load-bearing (the
daemon has no ADR while ADR-0146 rejected the daemon shape; ADR-0252's
"34 surface organs" does not reproduce — 18 found). F-5 what is excellent,
stated plainly: the typed learning boundary, selection-not-rewrite, the
non-hardening invariant, fail-closed lane shapes.
No layer is wrong-solution. Two candidates deferred to Phase 4 with
evidence: Wilson/replay independence in licensing, DriveGradientMap.
Fixes the decomposition Phases 2-3 fill and Phase 4 audits against, per
Pillar I: criteria before judgment. Five decisions recorded explicitly:
- D1 The five unreconciled articulations answer five DIFFERENT questions
(what for / what happens today / by what mechanism / judged how / built
where) and become axes of one taxonomy rather than competitors. Functional
axis = the AGENTS.md north star; structural axis = 7 macro layers (M0
Substrate, M1 Knowledge & Memory, M2 Afferent, M3 Comprehension &
Reasoning, M4 Expression & Serving, M5 Learning & Growth, M6 Continuity &
Process) + 2 cross-cuts (MG Governance, MV Verification), grouping all 33
system-map zones with straddles recorded honestly. Mind-physics blueprint
dispositioned element-by-element (identity/compositional/renderer =
superseded by stronger implementations; Allocation Physics and
DriveGradientMap = orphans -> Candidate Register).
- D2 System-map schema EXTENDED, not replaced: 7-value liveness kept;
four missing dimensions added as orthogonal fields — evidence (with
would-fail-if-absent sabotage bit), capacity (designed/measured/ceilings),
design-vs-build split, fitness (6-value: fit/strained/misplaced/
wrong-solution/superseded-in-place/undetermined). Liveness ⊥ fitness:
the 34 organs are live-serving + superseded-in-place, a state no merged
enum could express.
- D3 Completeness criteria fixed: coverage = evidence-bearing ownership of
a functional stage; deferred-with-ruling ≠ missing; pins must run.
- D4 Candidate Register: CR-1 attention/allocation (the ~73%-of-turn-time
hot path has no owner), CR-2 agenda/drive (mechanisms without a chooser;
L10 risks landing as a heartbeat with nothing to want), CR-3 efferent
action (needs an explicit deferred-vs-out-of-telos ruling), CR-4 temporal
self-location (determinism vs lived time, undesigned). Considered-and-not-
registered recorded (sociality, affect, embodiment, self-governance-as-
layer) so the boundary is visibly examined.
- D5 Assessment output is COMMITTED (reviewable, versioned, visible);
system map stays local as a regeneratable navigation index. Staleness
handled by verified_at SHA stamps on every card.
Phase 2 work order: nine layer cards, priority M6 > M3 > M4 > M5; re-verify
every map liveness label; run the stage-coverage audit with evidence; the
four zero-subsystem zones scoped honestly and queued first for Phase 3.
Opens the holistic macro→micro assessment of CORE's cognitive-cycle design
and implementation fulfillment. Read-only; no runtime behavior changes.
Method is governed by docs/conceptualizing_engineering_mastery.md (committed
here because the assessment cites it as its method): Semantic Rigor,
Mechanical Sympathy, Third Door; delete before optimizing; automate last.
Phase 0 findings:
- 0-A CORE carries FIVE unreconciled macro articulations of its own cognitive
cycle (AGENTS.md north star; AGENTS.md live path; the 2026-05-12 draft
three-physics-layer blueprint; ratified ADR-0252's five paradigm stages; the
system map's 10-band L0-L11 layering). The blueprint's allocation/attention
layer (Salience/Attention/Inhibition) exists in no other articulation.
- 0-B .system-map/ is the best existing macro→micro artifact and is 48 days
stale, gitignored/local-only, and carries four zero-subsystem zones — three
of which sit on the serving path.
- 0-C ADR-0252 §5 — the experiment gating the governing paradigm's build — has
never returned a verdict (two unmerged rnd/ worktrees). The 34 surface organs
the ADR diagnoses as the error keep serving. L10 blocks all 7 OPEN W-* items;
field + T1 vault are discarded on exit, so "one continuous life" is today
many short lives sharing a checkpoint.
- 0-D No live gap register: docs/gaps.md is 26/26 closed, the liveness ratchet
is L10-blocked and stale, docs/analysis/ is a chronological archive.
- 0-E 333 ADRs, flat numbering, status not reliably machine-extractable;
INDEX-by-domain is 21 files behind; the Blueprint reserved 0254-0261 for
intents never materialised while those numbers were minted for other work.
- 0-F Measured: CGA is ~73% of turn time via cga_inner/geometric_product
(~34k calls/turn), NOT versor_condition (0.22%). Rust backend off by default,
parity question open and blocked. MLX in no runtime path.
- 0-G The 2026-07-25 verification precedent falsified ~1/3 of an external
blueprint's items by reading code — the bar for every downstream phase.
Phase 1 receives the system map's 17-field proto schema and 7-value liveness
vocabulary, plus the four dimensions it lacks (evidence pointer, capacity,
design-vs-built, fitness judgment) and five decisions to make explicitly.
PR #138's fabrication findings are recorded as measured-and-pinned, held for
ADR + ratification — never re-discovered, never fixed here.
Phase 5 item 1 asked for the overlap to be sized "by measuring the reader's
construction set against the writer's rather than by growing corpora blindly".
New lane `evals/construction_inventory` does that: it sweeps the writer's whole
parameter space (every RhetoricalMove x IntentTag x predicate x quantifier x
tense x aspect, both public entry points, 32292 cells), quotients it by the
reader's OWN function-word skeleton, and comprehends each construction under
three vocabularies.
writer constructions 1739
reader constructions 19 (mint-site AST-guarded)
overlap, faithful 6
accepts but MIS-READS 22
refuses 1711
vocabulary-dependent 0
Faithfulness, not acceptance, is the criterion — and that reverses two claims
in the plan's §6 RESULT, using a metric that was already on the page:
* g_read_rate is 1/293 but **g_args_rate is 0.0**. The one surface that "reads",
`all molecules are defined as compounds`, is comprehended as
`subset(molecule, defined_as_compound)` — the reader chunked the writer's verb
phrase into a class name. It accepted; it did not comprehend.
* "one construction wide" was wrong both ways: zero on that corpus, six over the
writer's actual output space. A corpus cannot report an inventory's size.
Three findings re-order Phase 5 item 1:
1. The reader FABRICATES on 22 constructions — neither reads nor refuses.
`every dog is a mammal` -> member(every_dog, mammal);
`furthermore, all dogs are mammals` -> asserted(furthermore).
Both are ordinary user English, not writer artefacts. Root cause: _RESERVED
lacks the function words the writer emits, and _parse_propositional accepts
any single token as a fact.
2. It reaches SERVED output. deduction_surface recites
`Given: furthermore; p implies q; p.` — a premise the user never stated — and
chat/runtime.py realizes declarative turns into the held self, so a
fabricated atom is vault-writable. Widening the inventory first would widen
the fabrication surface with it.
3. ADR-0265's defect class survives inside ADR-0265's designated owner of clause
grammar: the four aspect arms of _inflect_predicate bind `negated` to a
wildcard and never read it, so `dog has been defined as mammal` serves both
the assertion and the denial (10530/16146 points). Unreachable today — no
producer sets aspect — so a loaded gun, not a casualty. It survived because
ADR-0265's invariant is structural (is `negated` threaded?) and cannot see an
arm that receives the flag and ignores it. A behavioural sweep can.
The two fixes are written already, as the mutations that turn the defect pins
red. They are NOT applied here: they change what CORE comprehends from user
input, which is a serving change on the truth path — authorization-gated, ADR
first, on the ADR-0261 §5.1 refuse-don't-drop precedent.
Guards, so the tables cannot rot: reader constructions are pinned to an AST
count of the reader's 10 mint sites; the declared tense/aspect axes are pinned
to _inflect_predicate's match arms; the committed corpus is pinned to the
fillers in use. 14 pins, 13 mutations observed RED.
[Verification]: in-worktree, CPython 3.12.13, uv sync --locked.
deductive 517 (was 503, +14 — count moved, registration confirmed)
smoke 641 (unchanged; the file is registered in `deductive`)
lane SHAs 11/11 match, no pin edited
pyright 0 errors on all new files
mutations 13/13 red, including both fabrication fixes
Serving-path authorization for the negation fix in this PR. The ADR is
Accepted rather than Proposed because the defect it removes was LIVE behind a
shipped flag (realizer_grounded_authority), not latent: CORE served the
affirmative of propositions users denied.
CORE served the affirmative of propositions the user denied. Measured live on
main @ 536d6e55 with realizer_grounded_authority=True:
"evidence does not support truth" -> 'Evidence is verified: what supports truth.'
"evidence supports truth" -> 'Evidence is verified: what supports truth.'
Byte-identical.
Phase 4 (#136) pinned "render_semantic has no negated parameter" as a defect.
Sizing the exposure before acting on it -- the step §7 requires -- showed the
pin was correct and INCOMPLETE. The defect is two drops in series, and the
first is in the graph:
intent.negated parsed from the user's words, always has been
GraphNode NO FIELD FOR IT <-- first drop
ground_graph rebuilds nodes field-by-field; nothing to carry
depth enrichment rebuilds nodes field-by-field; nothing to carry
plan_articulation cannot carry what the node does not hold
realize_semantic never read step.negated
render_semantic no parameter for it <-- second drop
Five separate constructors, each silently defaulting to the affirmative.
Why every gate was green
------------------------
On the default config the ungrounded realizer emits "...", _is_useful_surface
rejects it, and the runtime echo wins the resolver -- and the echo contains the
user's own "does not". The truth path was correct BY ACCIDENT. ADR-0088 Phase B
grounds the graph first, which is exactly when the realizer's surface becomes
useful enough to win. So the defect sat behind a shipped flag, invisible to
every property-of-one-surface test, because nothing compared a denial to its
assertion.
Delivered
---------
- GraphNode.negated, threaded intent -> graph -> ground -> enrich -> step ->
surface. Serialized ONLY when True, so every pre-existing as_dict and every
trace_hash folded from one stays byte-identical -- which is why no lane pin
moves.
- Clause grammar delegated to its one owner. Four of the eight intent
"templates" were never frames; they were plain clauses. They now call
render_step. Writing a second negation implementation in semantic_templates
was the tempting fix and is rejected: Phase 2A spent a unit giving every
linguistic fact one owner, and that design rebuilds the disease one level up.
- Frames that keep a finite verb (VERIFICATION, PROCEDURE, COMPARISON) get an
explicit negated form. RECALL is a speech act with no proposition to deny, so
it falls back to the clause path rather than drop the denial (ADR-0261 §5.1).
Measured
--------
delegation on affirmatives 192/192 byte-identical
serving realizer, all corpora 85/347 -> 109/347 (+24 = the denials)
feature-bearing bucket 49/214 -> 73/214
CONTROL (nothing droppable) 33/33 -> 33/33 unchanged
multi-node (clause joining) 3/100 -> 3/100 unchanged, out of scope
lane pins 11/11 byte-identical, none edited
The control staying 33/33 and multi-node staying 3/100 is the evidence that
this moved the denials and nothing else.
The exhaustive control earned its keep immediately
--------------------------------------------------
It found FIVE more intents serving a denial as its own assertion --
TRANSITIVE_QUERY, FRAME_TRANSFER, NARRATIVE, EXAMPLE, DEDUCTION -- because an
intent with no frame fell back to the UNKNOWN *template* (which cannot say
"not") rather than the UNKNOWN *clause* (which can). The default is now the
capable path, so the next intent added inherits correctness. Found by the
control, not by inspection.
Mutation -- every link reverted individually
--------------------------------------------
baseline 19 pass
graph_from_intent drops intent.negated 8 FAIL
plan_articulation drops node.negated 6 FAIL
ground_graph drops it on rebuild 2 FAIL
realize_semantic stops passing step.negated 6 FAIL
unframed intents fall back to the template 1 FAIL
pipeline depth-enrichment drops it 1 FAIL
The last row was GREEN on the first run -- my fix there was unguarded. That is
what added the structural invariant: every GraphNode(...) built on the serving
path must NAME `negated` or be recorded in an allowlist with a reason. The
defect was five constructors; a per-site test must be written per site, and a
site added without one is invisible. Only recognition/connector.py is exempt
(an EpistemicNode has no polarity to carry).
Four Phase 4 pins are revised, not relaxed -- #136's M2 mutation ("render_semantic
GAINS a negated parameter -> FAIL") has now happened for real, and forced the
deliberate revision it was built to force.
Registered in `smoke` in the same PR that creates it, per the #136 finding that
an unregistered pin runs nowhere.
Still unexpressed, deliberately: quantifier, tense, aspect. NO PRODUCER sets
them anywhere on the serving path, so threading them would be machinery with no
caller. render_step already handles all three the moment a producer exists.
[Verification]: in-worktree on CPython 3.12.13 with `uv sync --locked` --
smoke 641 (was 621), deductive 503, lane pins 11/11 with no pin edited.
Phase 4 of the grammar-unification arc, resolved by option (b) of the plan.
The problem: 149 green fluency cases scored `realize_target`, and
`core/cognition/pipeline.py` never calls it -- it calls `realize_semantic`.
`english_fluency_ood` reported 117/117 + 39/39 for a function that does not
speak, for the whole life of the lane.
The plan framed this as "which realizer is better". Reading the source says it
is not a quality question. `render_semantic`'s signature is
`(intent, subject, predicate, obj, secondary, language, root)` -- no `negated`,
no `quantifier`, no `tense`, no `aspect` -- and `realize_semantic` never reads
them off the step. So the serving writer cannot express content the
ArticulationStep is carrying:
negated=False -> 'Knowledge is defined as opinion.'
negated=True -> 'Knowledge is defined as opinion.'
It serves the AFFIRMATIVE of a negated proposition. That is the ADR-0261 §5.1
family, not a fluency defect. Pinned here as a defect; NOT fixed, because
fixing it changes served output and belongs to Shay.
Measured -- identical contract, all seven scored corpora
--------------------------------------------------------
bucket n realize_target realize_semantic
feature-bearing, single node 214 207 49
no features, multi-node 100 100 3
no features, single node (CONTROL) 33 33 33
total 347 340 85
The control is what makes the rest mean anything. Every corpus hardcodes
IntentTag.UNKNOWN, so "the serving writer scores badly" could have been an
artifact of never giving it a real intent. On the 33 cases carrying nothing it
cannot express, the two are IDENTICAL -- so the gap on the other 314 is the
dropped features and the clause joining, not the intent.
Delivered
---------
- `grammatical_coverage/runner.py` reports `serving_accuracy` beside
`accuracy`; `english_fluency_ood` delegates to that run_lane and gains it
for free. The realizer is a parameter now instead of a hardcoded import.
- `tests/test_phase4_realizer_resolution.py` -- the control, the decomposition,
the negation defect pin, and the §6 evidence.
- The claim is restated at both places it was made: the lane docstring and
`grammar_roundtrip/contract.md`.
Zero served bytes change. No serving authorization needed.
§6 -- the plan's pre-commitment was wrong, and is corrected rather than
quietly edited
------------------------------------------------------------------------
§6 forks on read_rate: risen => Phase 5 diversity; near-zero => §1.8 graph-model
mismatch => ADR. It pre-committed to the second. Neither is what the
measurement says.
g_read_rate went to 1/293. The unblocked case was blocked by a one-line WRITER
defect (predicate-nominal object agreement, #135), not by §1.8. The other 292:
no_template_match 289 reader has no SUBJ-VERB-OBJ template at all
unknown_morphology 2 prepositional objects (reserved_word_in_np)
unsupported_negation 1 reader has no negated-categorical template
Every one is the reader declining a CONSTRUCTION, not a projection disagreeing
about a graph it parsed. Where a construction is in both inventories the round
trip closes exactly. So the barrier is the OVERLAP of the two construction
inventories, currently one construction wide -- Phase 5's item 1, tractable,
not an ADR-scale model decision.
§1.6 read "uniform no_template_match" as evidence FOR the type mismatch. It is
not: no_template_match is a coverage fact, and the corpus was 289/293 bare
transitives, a construction the reader never claimed to read. The measurement
was mostly reporting the corpus's composition.
And a finding in my own stack
------------------------------
`tests/test_realizer_quantifier_agreement.py` lived ONLY in the `cognition`
suite, which is not on the AGENTS.md pre-push gate. So every pin Phases 3 and 4
added to it -- including the invariant covering all twelve inflection branches
-- ran in NO gate. That is why smoke stayed at 621 across two PRs that added 13
tests between them, and it is the same silent-red shape the smoke list already
calls out for test_adr_index.py.
Registered into `deductive`, which now runs 504 instead of 406.
Mutation
--------
baseline 12 pass
serving metric computed with realize_target 4 FAIL
render_semantic GAINS a `negated` parameter 2 FAIL
realize_semantic delegated to realize_target 6 FAIL
Row 2 matters most: if someone FIXES the negation defect, the pin forces a
deliberate revision instead of passing silently.
Still open, still Shay's: option (a), promoting realize_target to the serving
path. It now has a price tag -- 340/347 over 85/347 and the ability to say
"not", against a move in every surface hash and whatever the Shadow Coherence
Gate ruling in core/cognition/surface_resolution.py was protecting.
[Verification]: in-worktree on CPython 3.12.13 with `uv sync --locked` --
smoke 621, deductive 504 (was 406; +12 new Phase 4 pins, +86 previously
ungated), lane pins 11/11 unchanged, no pin edited.
Phase 3 fixed two of the twelve branches of `_inflect_predicate` by hand and
pinned them with a hand-written oracle. The other ten kept handing whole
predicate phrases to single-verb functions, so the same root cause was still
live on **57 of 96** (branch x multi-word predicate) pairs, 9 of 12 branches:
"belongs to" --perfective--> "has belongs toed"
"belongs to" --imperfective--> "is belongs toing"
"belongs to" --past--> "belongs toed"
"is defined as" --future--> "will is defined a"
"contrasts with" --negated--> "does not contrasts with"
Every branch now routes through `morphology.inflect_phrase_head`, which applies
a single-verb inflection to the finite verb and carries tokens 2..n through
byte-identically. Two closed tables were needed because `_base_form` is a
suffix stripper and the head of every copular predicate is a form of BE:
`base_form("is")` returned "i" and `present_participle("is")` returned "iing".
Also fixes predicate-nominal object agreement. `render_step` pluralized the
subject and never the object, so it wrote "all dogs are a mammal". The object
agrees only for a predicate nominal -- a closed set of two -- because a
prepositional object carries its own number and a productive rule would write
"all claims are grounded in evidences".
Measured
--------
tail-mangling pairs 57/96 -> 0/96
g_read_rate (round-trip) 0.0 -> 0.003413 first non-zero in the arc
grammatical_coverage v1 49/49 (2 cases corrected, see below)
english_fluency_ood 117/117 + 39/39 + 13/13 unchanged
discourse_paragraph 12/12 + 6/6 + 5/5 + 1/1 unchanged
zero_code_domain_acquisition 18/18 + 30/30 + 21/21 unchanged
`gram_C14_p01` and `gram_C14_p10` expected "...defined as compound", which is
not English. Corrected, and the old string moved into `reject_surfaces` so the
case now actively rejects what it used to accept. The correction is not my
judgment: the reader independently READS "all molecules are defined as
compounds" and REFUSES the singular form, and that is precisely what took
g_read_rate off zero.
Why the invariant instead of a bigger oracle
--------------------------------------------
A per-branch oracle has to be extended by hand for each new branch, and a
branch added without one is invisible -- which is how eight survived Phase 3.
The pin is structural instead: English marks tense, number and aspect on the
finite verb, so inflection must leave tokens 2..n byte-identical. It needs no
oracle and covers branches nobody has written yet.
It is necessary, not sufficient: "does not contrasts with" preserves its tail
perfectly and is still wrong. `test_do_support_puts_the_head_in_the_bare_
infinitive` is the sufficiency half.
Mutation
--------
baseline 84 pass
inflect_phrase_head applied to the whole phrase 39 FAIL
_IRREGULAR_BASE emptied 2 FAIL
_IRREGULAR_PARTICIPLE auxiliaries removed 2 FAIL
PREDICATIVE_NOMINAL emptied 3 FAIL
PREDICATIVE_NOMINAL widened to every copular predicate 2 FAIL
The last row is the one that matters: the plausible-but-wrong fix -- "pluralize
the object under a plural subject" -- is caught by the mass-noun control.
Not fixed here, deliberately: "has not the following steps" is archaic rather
than wrong, and modernizing it to do-support is a separate judgment call.
[Verification]: smoke 621, deductive 406, lane pins 11/11 unchanged
(grammatical_coverage is not a pinned lane; no pin was edited).
_inflect_predicate applied base_form — a SINGLE-VERB function — to whole
humanized predicate phrases, stripping the final word's last character
class, and its plural branch never consulted `copular`. Nine of the 26 seed
predicates came out wrong and every multi-word one did:
is defined as -> "is defined a" want "are defined as"
has the following ... -> "...step" want "have the following steps"
belongs to -> "belongs to" want "belong to"
causes -> "caus" want "cause"
Root causes, both single-point as §1.4 predicted:
1. morphology.agree_plural_phrase inflects the HEAD (the finite verb is the
first token of every humanized predicate) and carries the rest through.
base_form stays single-verb and is PINNED as still wrong on a phrase, so
nobody "fixes" the symptom in the wrong place.
2. The plural branch now agrees the phrase. The plural NEGATED branch had the
same defect ("do not is defined a") and is fixed with it: a plural copula
takes a bare "not", everything else takes do-support.
Two further defects found while writing the eval cases, both fixed here:
base_form("causes") -> "caus". The -es sibilant rule fired on a stem
ending in a single "s"; it must require a doubled "ss" ("passes"->"pass"),
because a single "s" is nearly always a stem ending in "e" that took a
plain -s ("causes"->"cause").
pluralize("proof") -> "prooves". f/fe -> ves is NOT productive in English
(proof->proofs, chief->chiefs, roof->roofs); it is a closed set. Now
derived as lexicon.VES_PLURAL_SINGULARS from the ves-rows of
IRREGULAR_SINGULARS, so the rule cannot claim a word the table does not
know. Phase 2B had put pluralize on the SERVING path, so this one was live
— and the surface-hashing pin from #133 confirms no served surface moved,
which is that guard's first real use.
New construction C14 quantified_copular, 13 cases: the combination that was
broken was the one never tested. Measured before: 152 corpus cases carry a
quantifier and ZERO combine it with a copular predicate.
MY FIRST DRAFT OF THOSE CASES COULD NOT FAIL. must_contain/word_order listed
quantifier, subject and object but NOT THE VERB, so "all molecules is
defined a compound" passed and reverting the fix left 47/47 green. Rewritten
with the agreed verb in both constraints plus reject_surfaces carrying the
ACTUAL pre-fix output, computed by running the reverted code rather than
guessed. Mutation now:
baseline 13/13 C14
revert phrase-head agreement 4/13
revert -es stem rule 12/13
revert closed ves set 11/13
The 4 survivors of the first mutation are the mass-noun controls, which is
correct — "all evidence is grounded in truth" must NOT pluralize.
Same lesson as the lane pins in #133: a pin that cannot fail guards nothing,
and the only way to know is to break the thing on purpose.
grammar_roundtrip's graph corpus is harvested from the committed case files,
so it grew 280 -> 293. Updated exactly rather than loosened to an inequality:
a corpus that grows or shrinks should require a deliberate edit.
[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
agreement 26/26 (from 17/26); english_fluency_ood 117/117 + 39/39 + dev
13/13 unchanged; grammatical_coverage v1 49/49; smoke 621 unchanged;
deductive 405; scripts/verify_lane_shas.py 11/11, no pin edited.
The pinned artifact carried verdict counts only. The runner's docstring
justified that: prose is "presentation, not decision", so the pinned bytes
"stay stable against wording-only changes; wording is covered by
tests/test_deduction_surface.py."
Measured, that justification was false. With render._display_noun sabotaged
so every clause reads "all SABOTAGE_dogs are SABOTAGE_animals":
11 lane SHA pins -> 11/11 byte-identical, blind
test_deduction_serve_lane + _license -> 20 passed, blind
tests/test_deduction_surface.py -> 41 passed, blind
(the named wording guard)
evals/grammar_roundtrip -> RED, the only witness
So CORE's user-visible output was unguarded by its own hash pins, which is
how the ratified v1b band served "all dog are mammal" for the entire arc
with wrong=0 intact.
build_report now emits surface_sha256 + per-case surfaces from the real
deduction_grounded_surface — the same call chat serving makes, so what is
hashed is what a user reads. Note build_combined_report re-projects five
named fields per split, so a field added to build_report alone never reaches
the pinned bytes; both had to change. The payload is not the report.
Surfaces are recorded, not just digested, so a moved pin shows the exact
sentence that changed in review instead of an opaque hash to go re-derive.
2,766 -> 37,280 bytes.
deduction_serve_v1 under sabotage: BEFORE byte-identical (blind)
AFTER 52370b73 vs c855d55c (RED)
Re-pinned surgically, one line, old hash recorded beside it, never --update.
Verdicts untouched: 166/166 correct, wrong=0. The hash moved because the
payload grew.
test_surface_hash_moves_when_the_renderer_is_sabotaged makes it permanent:
it corrupts the renderer, requires a digest to move, AND asserts the
aggregate counts are unchanged — proving the digest tracks PROSE rather than
smuggling in a decision change. A pin that cannot fail guards nothing.
Accepted cost: wording-only changes now move this pin. That is the intent —
a wording change IS a user-visible change and should require a deliberate
re-pin. The other 10 lanes are untouched; deduction_serve was fixed because
it is the one demonstrably serving prose to users.
[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
smoke 621 unchanged; deductive 405 (403 + 2 new);
scripts/verify_lane_shas.py 11/11 with the new pin, and 10/11 (RED on
deduction_serve_v1) under the sabotage it previously could not see.
The ratified, flag-ON v1b categorical band served "all dog are mammal" for
every noun: the A/E/I/O templates supply all/no/some + are, which demand a
plural, and the slots were filled with singular canonical entity ids. Four
of the 47 ratified corpus cases were affected, and wrong=0 never noticed
because every verdict was correct — only the prose was wrong.
Two fixes that compose into a closed round trip:
1. generate/morphology.py becomes the single owner of the number RULES
(Phase 2A gave the TABLES one owner). Both directions live there:
pluralize + singularize, over lexicon.IRREGULAR_PLURALS /
IRREGULAR_SINGULARS, with mass-noun and compound-head handling.
templates.pluralize and reader._singularize were two independent copies
of the regular suffix rules that disagreed about which irregulars they
knew; both now delegate.
2. render._display_noun re-inflects at render time and swaps _ for a space,
so compound ids read "guard dogs" rather than "guards dog" or
"guard_dog".
Result: reader gives wolves -> wolf, renderer gives wolf -> wolves. The
round trip closes.
0 malformed of 47 (from 4)
reader-vs-reader singularization 20/20 (from 8/20), 0 silently wrong
s_surface_match_rate 0.0 -> 0.625, now EQUAL to s_renderable_rate, so the
only remaining losses are surfaces the renderer cannot express at all
NEAR-MISS — widening a reader broke soundness, caught by the lane:
Routing the reader through the full 29-entry table first produced wrong=1 on
band v6-EX. ds-ex-0012 ("No fish are mammals. Therefore some fish are
mammals.") answered invalid where gold is refuted.
_singularize returning None makes the reader REFUSE, and the serving
composer tries the categorical band FIRST, falling through to more capable
bands. "fish" previously returned None (it matches no suffix rule), so v6-EX
got the case and decided it correctly. Resolving "fish" made v1b accept a
sentence it cannot decide.
Fixed by a principle, not a patch: a number-INVARIANT form is ambiguous in
number — "fish are mammals" is plural, "a fish is a mammal" is singular, and
the token cannot tell you which — so a reader that must not guess number
declines it. lexicon.INVARIANT_NUMBER is derived from the singularizer's own
key == value rows, so the rule cannot drift from the table.
=> Coverage and correctness are different axes. Fixing a corrupted VALUE is
safe; widening ACCEPTANCE changes which band answers, and in a
first-match composer that turns a right answer into a wrong one. Same
family as ADR-0261 5.1 refuse-don't-drop.
Also fixed, found by testing the inverse law: the two number tables were not
mutual inverses. The pluralizer lacked cactus->cacti, fungus->fungi,
die->dice and the invariants aircraft/means/offspring, so CORE could read
"cacti" but wrote "cactuses", and would have written "aircrafts", "meanses",
"offsprings". No round trip can close across a non-invertible table.
THE LANE SHA PINS ARE BLIND TO SERVED ENGLISH:
2B changed 4 served surfaces and the pins came back 11/11 byte-identical.
The deduction_serve_v1 hashed report holds only n/counts/by_gold/
correct_by_gold/all_cases_correct/mismatch_examples — no prose at all.
Confirmed by sabotage: with _display_noun returning "SABOTAGE_" + ..., so
every clause reads "all SABOTAGE_dogs are SABOTAGE_animals",
11 lane SHA pins -> 11/11 byte-identical, blind
test_deduction_serve_lane + _license -> 20 passed, blind
Phase 1 grammar_roundtrip -> 3 tests RED, caught it
This corrects #129: byte-identity of the pins is the arbiter for values and
verdicts, NOT for surface text, so the 2A/2B split is not a partition of
"changes users can see." Phase 2A's 11/11 conclusion still stands on its own
independent evidence (24/24 table-equality checks vs the pre-migration
literals), but the justification was thinner than stated.
Before Phase 1 no test in the tree would have noticed CORE's served prose
turning into word salad. That is exactly how "all dog are mammal" survived
on a ratified flag-ON band with wrong=0 intact.
Deliberately not fixed: mass-noun verb agreement ("all evidence ARE truth"
should be "is"). The copula is fixed text inside the templates, so agreeing
it changes the template shape rather than a slot, and no mass noun reaches a
categorical clause in any serve corpus. Recorded in render.py.
No lane pin edited — none moved.
[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
smoke 621 unchanged; deductive 403 passed (383 + 20 new/rewritten);
scripts/verify_lane_shas.py 11/11 (see blindness note above — gate on
grammar_roundtrip for surface work, not on these pins).
Collapses the duplicated closed English tables into generate/lexicon.py.
Measured on main @ 0948f7cd: the same facts were encoded up to five times
across the reading and writing paths, each copy written independently as a
successive deduction band landed.
The 2A/2B split is decided empirically per #129 — make the change, run the
11 pinned lanes, let byte-identity rule. Result: 11/11 byte-identical, so
this entire unit is 2A and nothing spilled into the authorization-gated 2B.
Collapsed to one object:
- connectives 3 identical copies (member, verb, cond_member)
- predicate display 2 identical 26-entry copies (semantic_templates,
templates)
- be-form inventory 5 copies, not the 2 §1.3 counted. The structural
test found realizer_guard._BE_AUX and
chat/runtime._BE_FORMS, which a COPULA-shaped grep
could never see.
Derived rather than duplicated, so they can no longer drift:
- STRUCTURAL = CONNECTIVES | {therefore}
- QUANTIFIER_TOKENS = QUANTIFIER_LEAD | QUANTIFIER_NON_LEAD
- NEGATION_BEARING_WITH_NOT = NEGATION_BEARING | {not}
- READER_IRREGULAR_SINGULARS = IRREGULAR_SINGULARS restricted to 8 keys
§1.3 re-measured while doing this, and it overstated the disease (plan
§1.9). Almost nothing contradicts:
- the "3 divergent plural tables" are 1 pluralizer + 2 singularizers,
i.e. inverse directions; comparing them as copies is a category error
- reader's 8 singulars are a STRICT SUBSET of member's 29 with the
difference in reader's favour empty, so its values are all correct and
only its coverage is short
- the "3 divergent quantifier sets" are 3 distinct facts; every/each lead
a clause but take singular nouns, so their absence from
PLURAL_QUANTIFIERS is correct
- english's negation set is a subset of member's, and the difference is
principled: v2-EN normalizes "<copula> not", v3-MEM refuses it
Exactly one genuine contradiction exists in the whole inventory:
discourse_planner maps is_defined_as -> "is" where the others map it to
"is defined as". Preserved as a register choice, pinned by test.
Found while reading, pinned as current behaviour for 2B to flip:
reader._singularize does NOT refuse uncovered plurals despite a comment
claiming it does — it falls through to a bare -s strip, so news -> new and
species -> specy, exactly the corruptions member.py's table comment says
its table exists to prevent.
Two 2A exit criteria were unmeasurable as written and are replaced:
- "Jaccard -> 1.00" cannot work. measure_grammar_seam.py scans source
literals, so unifying a fact removes it from those files and Jaccard
FELL, 0.083 -> 0.023 — the success direction reported by a metric
shaped to read like failure. Replaced with an object-identity count:
3 distinct objects behind 8 names, from 8-behind-8. Value comparison
cannot tell a shared object from two equal copies.
- "one table per fact" presumed §1.3's inventory was right. The report now
separates must-be-one-object (all UNIFIED) from related-but-distinct
(6 relations, all HOLD).
ADR-0258 §5 already decided morphology's destination is a ratified pack,
triggered by a grc/he member band. No such band exists, so the promotion is
correctly deferred and lexicon.py is a staging area, not a rival home — no
new ADR. Destination measured for whoever picks it up: packs/en/morphology
.jsonl has 9 records and zero irregular plurals, features.number has no
consumer, and _pack_morph_roots_for reads a top-level "root" key that 0 of
31 records across all four packs define, so it returns {} every call.
[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
smoke 621 unchanged; deductive 383 (364 + 19 new);
scripts/verify_lane_shas.py 11/11 byte-identical.
The measurement foundation for docs/plans/grammar-unification-2026-07-26.md.
WHY: evals/deterministic_fluency reports 1.00 on all six predicates and
still passes "banana does the.", "wet ground rains the is." and
"is is is is." — it checks terminal punctuation, presence of a verb-shaped
token, and two anti-shape regexes. Heuristic predicates will always have
that failure mode, because grammaticality cannot be measured without a
grammar. So this lane measures agreement between the two halves of CORE
that already encode grammar, and requires the measurement to FAIL on salad.
Two directions, reported separately because they fail for different
reasons and have different remedies:
G-round-trip graph -> realize_target -> surface -> comprehend -> graph
S-round-trip surface -> comprehend -> graph -> categorical renderer -> surface
v1 baseline on main @ 9696443a:
graph_cases 280 surface_cases 8
g_write_rate 1.000 s_read_rate 1.000
g_read_rate 0.000 s_renderable_rate 0.625
g_exact_rate 0.000 s_surface_match_rate 0.000
negative_cases 16
reject_rate 1.000
g_read_rate and s_surface_match_rate are pins on measured DEFECTS, not
goals; they may be revised upward only. s_surface_match_rate = 0 is the
§1.7 categorical render defect caught by construction — the lane found it
without being told to look.
g_args_rate and g_predicates_rate are deliberately separate: high argument
agreement with low predicate agreement would mean the grammars align and
only the vocabulary is split, a materially different remedy from both
being low. That distinction decides the arc's direction (plan §6).
Design notes:
- The committed cases.jsonl is the SINGLE source for authored surfaces —
no in-module duplicate, since a second copy of a corpus is the defect
this arc exists to remove. Negative shuffles are DERIVED at run time so
they cannot drift from the positives.
- The shuffles are lexically identical to positives (same vocabulary, same
length, order destroyed) so the lane cannot pass by vocabulary-checking.
- Fixed rotation, not a PRNG, so reject_rate is byte-reproducible.
- The lane keeps a local copy of the reader's quantifier map ON PURPOSE so
it never becomes a consumer of what it measures;
test_quantifier_map_matches_reader fails loudly if the reader changes.
- _render_categorical deliberately reaches a private serving function: a
lane measuring a private copy would measure what users never see.
Every guarantee is paired with a mutation test. The load-bearing one is
test_reject_rate_goes_red_when_the_reader_accepts_everything: an
accept-everything reader must drive reject_rate to 0.0. Without it,
reject_rate == 1.0 would be unfalsifiable — precisely the defect that
makes the existing fluency lane decoration.
Also documents plainly what round-trip does NOT prove: it measures mutual
intelligibility, not English quality. english_fluency_ood accepts "river
flows valley" and round-trip would be happy with it. No metric here may be
cited as evidence of prose quality.
scripts/measure_grammar_seam.py reproduces every number in the plan's §1
so a reader can check them instead of trusting them.
[Verification]: in-worktree on CPython 3.12.13, uv sync --locked —
smoke 621 (unchanged), deductive 364 (349 + 15 new). Lane SHA pins
verified separately. No serving code touched; new files plus one suite
registration line.
Third correction, same session, again from measurement rather than
argument.
I specified the Phase 2A/2B split as "a table is off-serving only when
every caller is proven eval-only," then computed the actual serving import
closure: 227 first-party modules from the six real serving entries, and
ALL 13 table-owning modules are in it — 13 of 13, including
generate/templates.py via pipeline.py -> realizer.py -> templates.py. So
the static test I had just written returns "everything is serving" and
discriminates nothing.
Import-reachable is not call-reachable. Importing a module executes its
table literals (pure data) but not its functions. The chain
realize_target -> render_step -> _inflect_predicate -> base_form is closed
and small enough to verify exhaustively, and realize_target has zero
non-eval non-test callers, so §1.2 and §1.4 stand as written.
But Python call-reachability is not statically decidable in general.
So the arbiter for this arc is EMPIRICAL: make the change, run the 11
pinned lanes, let byte-identity decide. That is stronger than a static
claim because it tests behaviour instead of predicting it, and it is what
the lane pins already encode as doctrine.
Consequence: 2A and 2B are now defined as a RESULT. A change is 2A if the
pins come back byte-identical and 2B if they move. Work the changes in
order, run the pins, let the split fall out — do not argue a change into
2A.
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)
CORE cannot read its own writing. Measured on main @ 9696443a:
- round-trip is 0/280 (0.0%) — all 280 writer-produced surfaces refuse
with no_template_match; all six readers refuse all writer output,
including CORE's own live served surfaces (24 refusals, 0 comprehensions)
- 35 word-tables on the reading path vs 9 on the writing path,
Jaccard 0.083; three divergent _IRREGULAR_PLURALS tables, two of them
both on the reading side
- 9 of 26 predicates produce wrong plural agreement
("all wolves is defined a mammal"); two single-point root causes
- the fluency instrument is decorative: "banana does the." passes all
five content predicates, and 0 of 280 eval cases exercise the
quantifier x copular combination that breaks
- the good realizer (render_step, 232 lines) is eval-only; 149 green
fluency cases score a function that never serves
Central design decision: the instrument IS the fix. Grammaticality cannot
be measured without a grammar, so round-trip forces one artifact to both
parse and generate. Positive AND negative corpora are both load-bearing —
round-trip proves mutual intelligibility, not English quality.
Phases 1-3 cannot change serving; Phase 4 is authorization-gated. Phase 5
is deliberately unestimated until Phase 1 reports. Section 6 makes the
next direction a measured fork rather than an argument, and pre-commits to
the reading that the graph-model mismatch (MeaningGraph vs
PropositionGraph are not type-compatible) is the likelier barrier.
Refs: ADR-0264, ADR-0071/0072 (seeded variation reused for diversity)
Ratified by Joshua Shay 2026-07-26. All nine rules are implemented and merged to
main @ 803703fc: R5-R7 (#120), R9 (#117), R1-R4/R8 (#125). The status line names
the enforcement pins so the record points at what holds it up.
Also discharges a staleness the ratification would otherwise have blessed. §4.1's
heading — "No curriculum band can earn a SERVE license under the current
architecture" — is NO LONGER TRUE of the running system, and it was falsified by
this ADR's own R5: the finding rested on "the premise cap holds a family to <=16
chains", and query-scoped compilation removed that cap. Post-R5, four bands would
earn SERVE the moment a ledger is sealed. Leaving that heading unqualified in an
Accepted ADR is precisely the ADR-0256 shape — a governance record asserting
something false about live code.
The §4.1 CONCLUSION survives, for a different and stronger reason, and the note
says so: reliability is commitment precision and a correct UNKNOWN is a
commitment, so a band clears theta_SERVE on non-commitments alone
(conservative_floor(660,660)=0.990046). Licensable evidence is 99.0-99.98%
non-entailed; max entailed volume in any band is 9. So §4.1's "≥97.6%
non-entailed" arithmetic was directionally right and its CAUSE was wrong — it is
not the premise cap that makes a curriculum license dishonest, it is the absence
of a mix rule. The binding constraint is now the outcome-mix ruling (§5, still
open), and nothing is licensed while it is unmade: the ledger is absent and
`core proposal-queue reseal` refuses to grant a license without
--allow-new-licenses.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync --locked`:
smoke 621 (unchanged), and 335 passed across test_adr_status_governance.py /
test_adr_index.py / test_volume_honesty.py. Status parses as "Accepted …" under
the governance regex and the banned `ratify-on-merge` predicate is absent. No
flag moved; no code changed.
Both units append to `deductive` at the same anchor, immediately after
`tests/test_curriculum_practice.py`. Resolution keeps BOTH entries; nothing
else in either hunk overlapped, and `core/cli_proposal_queue.py` auto-merged
(polarity adds `--polarity` to the ratify parser, this branch adds the reseal
subparser and ratify's "next:" line).
Resolved here rather than at merge time so the resolution is visible on the PR
that caused it.
Every correction here is to something I wrote earlier in this arc. Original text
is preserved in place; nothing was rewritten to look right in hindsight.
ADR-0264 §4.2 — CORRECTED block. I sized the eleven band ceilings from per-TERM
exclusivity; `resolve_domain`'s predicate is per-PAIR, which is looser, so every
ceiling was understated and one verdict was wrong: systems_software_causal is
720, not 630, i.e. reachable rather than impossible. Seven of eleven bands
cannot reach 657, not eight. Downstream conclusions survive — physics·modal (480)
is still impossible and philosophy_theology·modal is still the target.
ADR-0264 R5 + DIVISION-OF-WORK ×2 + compile_premises docstring — the
"verdict-identical" claim is exact only where the full family was READABLE. Over
MAX_PREMISE_SENTENCES, full-family compilation refuses outright, so narrowing is
verdict-IMPROVING there. Read literally the unqualified phrasing implies a
17-chain family keeps declining, which is backwards — the opposite of the
unblock.
E2 rename staleness, 4 sites: ADR-0256 (Accepted) named
`gold.py::assert_corpus_sound`, which #119 deleted; the plan's E2 item and
DIVISION-OF-WORK's §2 row, §4 heading and "renaming after that is strictly more
work" reasoning were all still written as pending.
DIVISION-OF-WORK §0b — new status table: which PR each unit is, what stacks on
what, and the four things the units found that the plan did not predict.
Adds the arc-close brief from ARC-CLOSE-TEMPLATE.md. Its §3 (falsified
assumptions) is the section that cannot be recovered from the diff: nine
entries, five of them corrections to my own claims. Records that the
articulation trigger is not merely unmet but STRUCTURALLY unmeetable — the
previous arc left it unrecorded, which is what kept it re-triggerable — and
names four pieces of stale reasoning still in the tree, with files, since that
is the class of staleness nobody greps for.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 569, deductive 291 — both baseline, since the only non-doc
change is one docstring.
`polarity` was read by NOTHING. `CurriculumChain.sentence` was unconditionally
affirmative, so a row authored to REFUTE an atom compiled to a premise
ASSERTING it and the question came back `entailed`. The independent oracle
ignored the field too — so gold agreed and wrong=0 stayed green. That is the
shape of defect no gate in this repo can catch, which is why ADR-0264 settled
the epistemology before any code moved.
R1 — row-level `polarity`, absent => affirmative. Not an `intent` value and not
a new `operator_family`: bands key on the connective-derived family, so
`modal_negative` would open a fresh band at n=0 instead of adding refuted
volume to the band it belongs to. An UNRECOGNIZED token is dropped at the
admission boundary rather than read as affirmative — that is the one direction
of error the rule exists to prevent. `as_row` OMITS the field when affirmative,
so committed corpora stay byte-identical and no lane hash moves.
R2 — a negative row compiles under the sentential-negation prefix the argument
reader already parses, so `(¬p, therefore p)` is a tautological refutation.
R3 — `atom_sentence` is polarity-independent, which IS the rule: a negative row
must mint the SAME propositional atom. Demonstrated, not just asserted —
`test_r3_a_different_connective_would_silently_fail_to_refute` shows a negated
paraphrase returning UNKNOWN, i.e. a taught refutation that quietly fails to
refute with nothing going red.
R4 — one atom cannot hold both polarities; rejected at ratification, naming
both polarities and the consequence. Left to serve time it would surface as
INCONSISTENT_PREMISES: the band silently going dark across the whole family
instead of the authoring mistake being named. Duplicate-edge and contradiction
stay distinguishable failures.
R8 — the oracle learns polarity INDEPENDENTLY (its own constants, its own row
reader; asserted at source level that it imports nothing from
teaching.curriculum_premises) and EXCLUDES negatives from the reachability
adjacency, because a denial supplies no step from subject to object.
NO negative row is committed — authoring curriculum is Phase F and Shay's. So
the shipped gold mix is still entailed 72 / unknown 4968, zero refuted, and
Phase C's `test_gold_mix_has_no_refuted_class` stays green with its message
retargeted from "unimplemented" to "no content yet".
Also adds `--polarity` to `core proposal-queue ratify`, so the mechanism is
reachable from the operator surface rather than only from Python.
The end-to-end tests repoint BOTH loaders at a temp corpus instead of writing
the committed one. An earlier draft appended to physics_chains_v1.jsonl and
restored it, which is safe under smoke/deductive (serial) but NOT under `full`,
which injects `-n auto`: a parallel worker reading mid-mutation would see an
uncommitted row and the failure would look like a corpus problem rather than
test isolation. Each loader is still redirected separately at its own module
constant and still parses with its own code, so R8's independence is untouched.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 593 (571 + 22), deductive 332 (310 + 22). curriculum_serve
lane unchanged at n=32 correct=32 wrong=0 anti_recall=5; all 11 lane SHA pins
match; committed corpora untouched (git status clean under
teaching/domain_chains/). Mutation-checked all three load-bearing rules: R2
sentence ignoring polarity -> 5 red; R8 negatives back in the adjacency -> the
exclusion test red; R4 check removed -> both R4 tests red. The R8 test has an
affirmative control proving the 2-hop path is real when the row is positive,
so `depth == 0` cannot pass vacuously.
Plan §6A, report-only per DIVISION-OF-WORK §4. Both halves ran. The cargo half
is real evidence; the LANE half is vacuous, and saying so is most of the point.
cargo test (PYO3_PYTHON=/opt/homebrew/bin/python3.12, --offline): 43 passed,
0 failed across 7 suites including test_crdt_hash_parity.rs. The plan's read
was right — cargo 1.97.1 and the registry cache are present locally and the
cloud session was blocked only by sandbox network policy.
Lane parity: 11/11 pinned SHAs match under both backends. That number means
nothing. Sabotaging EVERY core_rs callable to raise and re-running under
CORE_BACKEND=rust still gives `lanes: 11/11 match pinned SHAs`, exit 0. Three
sufficient reasons:
- The hot path bypasses the dispatch. algebra.cga.cga_inner is NOT
algebra.backend.cga_inner; 69 call sites import the hot functions from
algebra.cga/algebra.cl41 directly vs 24 through algebra.backend. The ~34k
cga_inner->geometric_product calls/turn that plan §6C calls ~73% of turn
time never reach the dispatch.
- The dtype gates reject the runtime workload. versor_condition IS imported
from algebra.backend by pipeline.py:24, and still falls through: the Rust
arm requires f32 and the wave-field workload is f64.
- Every dispatch arm ends in `except (..., Exception): pass`, so a broken,
garbage-returning, or absent kernel falls back to Python SILENTLY. That is
what makes the first two unfalsifiable by lane hashes.
Instrumented under CORE_BACKEND=rust: zero Rust calls in the whole
deductive_logic lane (pure ROBDD, no CGA), and exactly ONE
(versor_apply_with_closure_f64) in a full pipeline turn. CORE_BACKEND=rust is
today a one-call switch.
So §6B ("if parity holds, consider Rust default") rests on a measurement that
cannot fail, and §6C is the real blocker and is upstream of the backend. NOT
acted on — §6.4 says a parity/determinism finding is escalated, not fixed.
Recorded three follow-ups in dependency order: make the dispatch fail loudly,
add a parity test that fails when the Rust path is not TAKEN, only then route
the hot path.
The one thing fixed here is a plain defect blocking the measurement.
`core rust build` — the activation path print_rust_status() itself recommends —
raised `TypeError: _run() got an unexpected keyword argument 'env'` on every
invocation. core/cli.py::_run is injected into cli_rust.cmd_rust_build, which
passes env=rust_build_env() to carry PYO3_USE_ABI3_FORWARD_COMPATIBILITY, and
_run lacked the keyword the CommandRunner Protocol declares. One keyword, plus
tests/test_cli_runner_contract.py deriving the expected keywords FROM the
Protocol so the two cannot drift apart again. Verified end-to-end: `core rust
build` now builds and installs.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 575 (569 + 6), deductive 291 (unchanged). New tests fail
under mutation (revert the env keyword -> 3 red). Registered in `fast` AND
`smoke`. Installing core_rs does not activate it (_ALLOW_RUST requires
CORE_BACKEND), verified: extension present + no env var -> "rust backend:
inactive", so no default behaviour moves.
Audit-ledger R7, implementing docs/handoff/curriculum-license-loop-2026-07/
E1-R7-ASSERTION-SPEC.md. `ChatRuntime.chat` seals a TurnEvent before the
surface authority is known; `CognitiveTurnPipeline` back-stamps the resolved
surface and canonical trace_hash onto `turn_log[-1]` after `chat` returns. So
the in-memory log ended up correct and the DURABLE stream carried the
pre-override record — the thing that persists was the wrong one.
Mechanism, per the spec's M1-M3:
- M1 — stage an INDEX into `turn_log`, never the TurnEvent. The flush re-reads
the log, so every back-stamp is picked up by construction and the staged
record cannot go stale. Capturing the event object would be the original
defect with extra steps.
- M2 — `attach_telemetry_sink` does NOT flush a staged turn event, the
opposite of what it correctly does for `_pending_reboot_payload`. The reboot
payload is final when buffered; a staged turn event is mid-flight. Flushing
on attach would silently restore the defect.
- M3 — deferral is opt-in. The pipeline wraps the runtime rather than the
reverse, so the runtime cannot detect whether a pipeline will run. A runtime
used directly (eval and demo paths) emits inline, byte-identically and at
the same moment as before.
The `finally` at the serve boundary is load-bearing (I5): a pipeline that
raises after `chat` returns must still flush, or an error path silently
swallows a telemetry record — worse than the staleness being fixed.
TWO CORRECTIONS TO MY OWN SPEC, reported rather than quietly absorbed:
1. I2 bullet 3 and I3 assert on an emitted `trace_hash`. There is no such
field — `chat/telemetry.py` never serializes `TurnEvent.trace_hash`, so the
sink never carried a trace_hash, stale or otherwise. R7 repairs the SURFACE
only. Pinned by `test_trace_hash_is_not_in_the_wire_format_at_all` so the
claim cannot be re-asserted; the replay-not-recomputation ruling is kept on
the object that does carry the hash.
2. The surface divergence is unreachable under the DEFAULT config:
`finalize_turn_surface` is a no-op on all 36 turns probed, because the
runtime's own surface already wins the resolver. It fires under
`realizer_grounded_authority` — 24 divergences over 84 turns. A test on the
default config would have passed while proving nothing, exactly as the spec
warned, so the fixture pins that flag and asserts the pre-fix divergence
exists before asserting it is gone.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 591 (569 + 22), deductive 291 (unchanged). All seven existing
sink test files pass UNCHANGED (80 tests). warmed_session_consistency
telemetry_consistency_rate = 1.0. Mutation-checked all three mechanisms:
staging the event instead of the index -> 3 I2 tests red; flushing on attach ->
the M2 test red; dropping the `finally` -> the I5 test red. Registered in
`smoke` and `cognition`.
`RatificationReceipt.pending_stages` has named `ledger_reseal` since the
ratification ceremony was written and nothing performed it, so a sealed ledger
went stale the moment curriculum changed. This is that stage, plus the CLI
verb a human runs, and it is deliberately NOT reachable from `ratify_chain`
(doctrine forbids ratification automation, and a per-row reseal would churn
the ledger's content_sha256).
The load-bearing property is not "the ledger can be rewritten" — it is that
rewriting it cannot grant a serving license by accident. Reliability is
commitment precision, so a curriculum band clears theta_SERVE on correct
NON-COMMITMENTS alone (Phase C, PR #121). A plain regenerate verb would flip
four bands from disclosed to authoritative as a side effect of housekeeping,
on evidence that is ~99% non-entailed, which ADR-0262 §5.1 rules
unacceptable. So:
- `plan_reseal` computes the license DELTA before writing anything, reading
the current ledger through `load_capability_ledger` — the production entry
point — so a tampered ledger refuses here instead of being overwritten and
losing the evidence, and the stage inherits the capability's DECLARED
absence policy rather than choosing its own (bridge rule 5).
- `apply_reseal` REFUSES by default when any band would become newly
licensed, naming the bands and their `entailed` counts. `--allow-new-
licenses` is the ratification. Revocations are never gated: losing a
license is the gate becoming more conservative and needs no authorization.
- `deduction_serve` is deliberately absent from `_RESEALABLE`. Its ledger is
SHA-sealed, ratified, and gating a live flag, and 21 of its 25 bands do not
clear theta_SERVE on distinct evidence. A verb that regenerated it would
erase a measured exposure as housekeeping.
- `ratify`'s output now names the command that performs its pending stage.
Naming a stage without naming its command is how this one stayed unperformed.
Exit codes: 0 dry-run/applied, 1 refused, 2 unknown capability.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 571 (unchanged), deductive 327 (310 + 17).
`chat/data/curriculum_serve_ledger.json` still absent after the full suite —
no test writes it. Caught and fixed one real gate failure:
`test_no_production_adapter_passes_missing_ok` correctly rejected an explicit
`missing_ok=True`; the fix routes through the manifest instead.
Builds the producer `chat/curriculum_serve_license.py` already named as the
only writer of the ledger it reads — `evals.curriculum_serve.practice.runner
.seal_ledger` — which did not exist. Until now, authoring curriculum could
not earn a license at any volume.
A band's case space is ENUMERATED, not authored: every exam question that
routes to that subject (resolve_domain) whose relation is in that family, one
committed case per distinct query atom. So `committed == distinct` is not a
property the generator maintains, it is the same fact twice — the structural
form of ADR-0264 R9, and why this producer cannot develop the deduction
producer's exposure (CASES_PER_BAND=720 filled from spaces as small as 28).
CASES_PER_BAND=660 follows `evals/determination_estimation` per
DIVISION-OF-WORK §4: a ceiling on committed volume, not a quota.
11 bands, wrong=0, inflation 1.0 everywhere.
THE LEDGER IS DELIBERATELY NOT COMMITTED. The plan's Phase-1 exit criterion
asked for "a real (still-unearned) ledger"; that state is unreachable.
Reliability is commitment precision and a correct UNKNOWN is a commitment, so
conservative_floor(660,660)=0.990046 clears theta_SERVE on non-commitments
alone. Sealing today licenses FOUR bands on evidence that is 99.0%-99.98%
correct non-commitment — which ADR-0262 §5.1 rules unacceptable and which
ClassTally structurally cannot see (no verdict axis). Writing the artifact is
therefore a ratification and belongs to Phase D's explicit human-run verb.
The open outcome-mix ruling is now blocking, not academic.
Two corrections to my own earlier records, both measured:
- ADR-0264 §4.2 sized bands from per-TERM exclusivity; the router's predicate
is per-PAIR, which is looser. Every ceiling was understated, and
systems_software_causal is 720 not 630 — flipping it from "cannot reach
657" to "can". 4 of 11 bands can reach the floor, not 3. Now pinned as
measured values in BAND_ATOM_SPACE.
- `then` and `therefore` are taught philosophy_theology lemmas AND control
words of the argument reader (`therefore` is the conclusion marker). 164
routable atoms refuse compiled_premises_unreadable — coverage misses, never
wrongs — in the band Phase F retargets to. The vocabulary boundary is not
screened against the reader's grammar; a pack teaching and/or/not/if would
open a much larger hole the same way. Recorded, not fixed (§6.5), pinned as
a two-lemma bound.
Registers curriculum_serve in AUDIT_SOURCES, retiring the declared `None`
that was Phase C's forcing function, and adds the producer to `deductive`.
[Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync
--locked`: smoke 571 (569 + 1 test + 1 parametrization), deductive 310
(291 + 19). Mutation-checked: injecting the deduction padding pattern into
band_cases turns 5 tests red including the R9 audit and the seal guard.
curriculum_serve lane unchanged at n=32 correct=32 wrong=0 anti_recall=5;
all 11 lane SHA pins match; no ledger written, so no serving change.