Rank 5 on the docket, and the first item this arc whose expected outcome is
LOSING capability.
conservative_floor is a one-sided Wilson lower bound, and Wilson assumes
independent trials. CORE's pipeline is deterministic, so replaying an identical
case is not a second trial — it is the same trial with a guaranteed outcome.
The deduction sealer folded the raw practice corpus and counted every replay,
so bands recorded 720/720 committed whether that was 720 decisions or 28
decisions seen 26 times each. conservative_floor(720,720)=0.990868 cleared
theta_SERVE=0.99; conservative_floor(28,28)=0.808413 is not close.
RESULT: 25 licensed bands -> 4. Twenty-one demoted.
Survivors: en_conditional_chain, en_disjunctive, en_verb_fact,
en_verb_universal — the only bands whose corpus holds enough independent cases.
What the demotion did and did not do, stated exactly. NO ANSWER CHANGED. No
answer became wrong. `wrong` stayed 0 across all 25 bands. The engine is
exactly as correct as it was. What changed is the CLAIM attached to the answer:
21 bands now serve the same sound conclusion prefixed with "(reasoned, but I
haven't yet earned a verified track record on arguments of this shape)". The
reasoning did not get worse; the boast did.
The producer is hardened, which is the half that stops this recurring.
seal_ledger now refuses outright — assert_sealed_evidence_distinct compares the
ledger about to be written against the corpus's distinct-case count and raises
BEFORE anything is written. The curriculum sealer has carried this guarantee
since ADR-0264 R9; the deduction sealer had no equivalent, which is exactly how
the exposure arose. Catching it at seal time rather than in an audit matters:
an audit finds a padded ledger after it is committed, trusted, and gating a
live flag, and unwinding that then needs a ruling — it needed one.
A HOLLOW GUARD OF MY OWN, caught by sabotage rather than trusted. The first
version compared all_gold_problems() with distinct_gold_problems() — two
functions that agree with each other by construction, and neither of which is
what build_ledger folds. Reverting build_ledger to the raw corpus walked
straight past it AND re-sealed the inflated artifact. Same defect class the
ledger already suffered: a guard checking something adjacent to the thing it
protects. It now takes the BUILT LEDGER as input, because the invariant is
about the artifact. Re-running the identical attack: refused, wrote nothing.
Pinned as test_sealer_refuses_a_ledger_that_counts_replays.
A SECOND MISS, caught by the gate rather than by me. My blast-radius scan
grepped for deduction_serve_license / deduction_serve_ledger / 720 and found
nine files. It missed tests/test_construction_inventory.py, which asserts
deduction SURFACE STRINGS and never mentions the ledger — so a pattern search
over the wrong noun could not see it. The full gate found it. Recorded because
"I scanned for the blast radius" is worth exactly as much as the scan's key,
and mine was the wrong key.
That test is the #138 fabrication defect pin, and it is updated WITHOUT being
weakened. Both surfaces now carry the disclosure prefix, and the fabrication
survives untouched — which is a sharper finding than before: a disclosure
prefix looks like it might cover the hazard, and it does not. The user is still
told they said "furthermore". Exact-match assertions kept on the full strings
so neither the fabrication nor the licence state can drift unnoticed, plus an
isolating assertion that the only difference between clean and leaked recitals
is the fabricated premise.
Four records corrected in the same PR, each of which would otherwise have gone
on asserting capability that no longer exists:
- CAPABILITY_LEDGERS' manifest note ("25 bands at 720/720 wrong=0")
- the published CLAIMS.md claim — caught immediately by G-22's own pin, which
is that fix earning its keep three days after landing
- test_volume_honesty.py's module docstring, which said the exposure was
"NOT silently repaired ... changing it is Shay's ratification, not a
test's". R-13 IS that ratification, so the line went stale by being acted on
- the exposure inventory's framing: it now pins the repair, not the gap
test_volume_honesty's sealed-vs-producer assertion is strictly stronger than it
was. It compared sealed committed counts to the producer's committed counts and
both were the inflated 720 — the assertion held perfectly while the number it
agreed on was the wrong number. Two records agreeing is not evidence that
either is right. It now pins committed == distinct, plus a non-vacuity check.
Nine tests encoded the unearned expectation and were updated to the honest one.
Closes G-19 and H-1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
Rank 4 on the docket. The L10 always-on lane has passed a 5000-beat soak since
2026-07-19. That result lived as PROSE IN A CONTRACT FILE — no machine-readable
artifact, no run SHA, no rerun path, and a deterministic_digest that report.py
computed and nothing pinned. The contract's own closing line asked for the pin
("Pin it once the lane is trusted so a regression flips it") and it was never
added. A recorded prose result with no re-derivable artifact is testimony, not
evidence, and that file is exactly where the distinction was meant to be
enforced.
R-2 first, before the run — as the plan required, so the soak measured a
decided question rather than deciding one by accident.
R-2 was ruled B (beats govern cognition; wall-clock permitted in telemetry
only). Measured: this lane carries NO WALL-CLOCK AT ALL — runner.py,
predicates.py and report.py contain no time, datetime, or timestamp of any
kind. That is stronger than B requires, and it is KEPT rather than "completed",
for three reasons now recorded in the contract:
1. B's permissive half is a permission, not an obligation. Adding a field so
the code matches a ruling's letter, then adding a pin to guard the field,
is a mechanism whose only purpose is to be guarded.
2. "When was this last run?" is already the artifact's COMMIT DATE —
immutable, verifiable, stated once. A generated_at inside the report would
be a second copy of one fact, drifting the moment a report is regenerated
and not committed. That is G-23's defect class, registered this same week.
3. It keeps the digest honestly deterministic. A timestamp would have to be
excluded from it — a value in the artifact that the artifact's own
integrity check ignores.
The contract also records what WOULD change this: a predicate whose claim is
about duration rather than sequence (H5's resource-cost leg is the live
candidate). B is already ruled, so that needs no new decision — only the
predicate that requires it.
The re-run: 5000 beats, reboot at 2500, under the POST-R-3 profile. All four
predicates pass. Committed as
evals/l10_always_on/results/report-5000-6c67e88d.json, digest f814fd97….
The most useful result was not the pass. Every DETERMINISTIC fact reproduced
exactly — vault bounded at 6, convergence at beat 1 with a 4999-beat tail,
clean reboot with derived learning intact — while the machine-variant float
moved from the prose's 1.389e-07 to a worst of 6.177e-08. That is the first
real evidence that excluding versor_condition from the digest was correct
rather than convenient: a digest covering it would have flipped on a machine
change and taught every reader to ignore it. The prose figure is superseded,
not contradicted — it was never a reproducible quantity.
tests/test_l10_soak_evidence.py, on the gate, guarding two DIFFERENT failure
modes that are easy to conflate:
REGRESSION — the digest is pinned, plus the horizon (a 24-beat report pinned
as though it were the 5000-beat one would pass everything else), the four
predicates by name (all_gates_pass alone is satisfied by an empty predicate
list), and a vacuity guard on the attested set.
STALENESS — the mode nothing guarded, and the one that had ALREADY FIRED.
R-9 ruled the cadence change-triggered, not clock-triggered: local-first
doctrine makes "nightly" a ruling rather than a cron line, and a nightly that
does not run is green for the wrong reason (H-9). So instead of "when did we
last run it?", the pin asks the question that matters — DOES THIS EVIDENCE
STILL DESCRIBE THE CODE THAT SHIPS? — by holding the SHA-256 of every source
the soak attests: chat/always_on.py, chat/always_on_daemon.py, and the three
harness modules.
That is not hypothetical. It had already fired silently for six weeks: the
2026-07-19 soak ran with accrue_realized_knowledge absent from
CONTINUOUS_LIFE_CONFIG_FLAGS, so from R-3 (PR-5, this same day) onward the
recorded result attested a process that no longer existed — and nothing in the
repository could notice. HAD THIS PIN EXISTED, PR-5 WOULD HAVE FAILED IT. That
is precisely the intended behaviour, and it is verified by sabotage: touching
always_on_daemon.py turns it red.
The failure message says RE-RUN THE SOAK — DO NOT EDIT THE HASHES, because
updating a hash to match changed source without re-running converts the pin
into a record asserting evidence that does not exist. That is the failure this
whole arc is about, committed inside its own remedy.
Three sabotages observed red: a touched daemon (the staleness mode), a
regressed verdict in the artifact, and a shorter horizon pinned as if it were
5000 beats.
H1-H4 promoted onto the gate, and the cheap option was deliberately refused.
Measured 20.95s for the four real-soak "holds" and 0.46s for the eight mutation
"bites" — 98% of the cost is the holds. Shipping only the bites, or parking the
file in a curated-but-unreachable suite, would have satisfied R-9's letter and
bought nothing; a suite nobody runs is the non-guarantee this arc spent PR-4
closing. Those four holds are the ONLY tests on any gate that run
chat/always_on's run_continuous end-to-end, and that loop just shipped a
six-week silent divergence. 21s per push to actually run the process that broke
is the most defensible time on this gate. Removed from full_only_baseline.txt
in the same edit; the baseline shrank 746 -> 745.
Closes G-5. Proof-of-life has moved from prose to committed, pinned,
change-guarded evidence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
Rank 3 on the docket. RuntimeConfig carries 32 boolean fields and nothing in
the repository stated that set, distinguished a capability flag from a posture
flag from a deployment knob, or recorded what evidence would flip any of them.
Two independent counts of the same set differed by eleven — the assessment said
"seventeen capability flags," N-7 measured 28 default-off plus 4 default-on —
and neither was wrong about what it looked at. They counted different things
because nothing declared what the set was.
R-3 (ruled A — incomplete flag set). accrue_realized_knowledge joins
CONTINUOUS_LIFE_CONFIG_FLAGS. Step D consolidates the realized facts Step B
writes, and the daemon forced the consumer without the producer: a continuous
life consolidating an empty set, which the mastery framework calls "garbage at
high speed." It shipped that way for six weeks.
The decisive evidence was in the code, not the documents. BOTH flags' comment
blocks in core/config.py already described the corrected profile —
accrue_realized_knowledge says "the production L10 process enables it alongside
persist_session_state", consolidate_determinations says "...alongside
accrue_realized_knowledge + persist_session_state". Three records (two
docstrings and the 07-25 verification doc) agreed with each other and disagreed
with four lines of code. Dormancy was a coherent ruling and would have cost
correcting three records that were RIGHT about the design; incompleteness cost
one line.
So the planned "N-6 docstring correction" deliverable DISSOLVED rather than
shipping: adding the flag made both docstrings true. Recorded explicitly,
because a PR that quietly ships less than it promised is the same divergence
class this arc exists to close.
R-4 (ruled A) — docs/specs/flag_register.md. All 32 booleans with class,
governing ADR or an honest "none", recorded rationale, and what evidence would
flip it. The classification is the load-bearing half and is stated as a
taxonomy rather than a label:
CAPABILITY — what CORE can do; engineering may flip it on named evidence
POSTURE — what CORE serves or refuses AS TRUE; the wrong=0 boundary,
ruling-only
DEPLOYMENT — cost, lifecycle, process shape; per-deployment
estimation_enabled and composed_surface are both "= False" and are not the same
kind of thing. Nothing said so before.
Profiles are declared as the UNIT of decision (R-4's mechanism): one-shot/eval
and continuous-life. The absence of a SERVING profile is stated deliberately —
the four POSTURE serving flags each ride their own license, and grouping them
before the ledger is sealed would license by association, the outcome R-8
exists to prevent.
tests/test_flag_register.py, on the gate. BIDIRECTIONAL, because either
direction alone would have passed through both failures it exists to prevent:
a flag added to config.py and not registered fails; a register row outliving
its flag fails. The counts the register's prose states (32 / 4 ON) are pinned,
since a stale count is how this register came to be needed. Four sabotages
observed red — new unregistered flag, deleted row, orphaned row, and a flag
silently flipped ON, the last caught by two independent pins.
THREE FINDINGS THE ITEM DID NOT ANTICIPATE, all from measuring rather than
reading.
1. Accumulated permissiveness, not just hesitancy. Of the four default-ON
flags, ONE has a governing ADR (deduction_serving_enabled / ADR-0256) and
TWO have no recorded reason of any kind — allow_cross_language_recall and
use_salience carry no comment block, no ADR, no criterion. In an
architecture built on earned licenses, two permanently-on capability flags
with no recorded decision is G-8's question inverted. Registered and
deliberately NOT fixed: writing a rationale after the fact would invent a
decision nobody made, which is worse than recording the absence.
2. A hollow gate inside the governance pin itself.
test_default_on_flag_is_not_governed_by_a_proposed_adr is parameterized over
(default-ON flag x cited ADR), so the three uncited ON flags contribute ZERO
cases — it covered exactly one flag. Its non-vacuity guard asserted only
that SOME flag cites an ADR, which default-off flags satisfy in abundance.
One reformatted comment away from zero coverage with everything green: the
failure state indistinguishable from the success state, inside the module
written to prevent exactly that. Guard tightened to require a non-empty
parametrization, and observed red by stripping ADR-0256's citation.
3. The daemon's own tests ran on no gate. R-3 changes what the always-on
process does every beat, and tests/test_l10_always_on_daemon.py was in no
curated suite — so this behaviour change would have shipped guarded solely
by tests no gate invokes, which is the hollow-gate pattern landing on the
exact file covering the change. Promoted to smoke WITH the change it guards.
Measured +9.4s, +4.3% of smoke — paid deliberately, not estimated. Removed
from full_only_baseline.txt in the same edit (both-directions ratchet; the
baseline shrank 747 -> 746).
Red before green throughout: the assertion cfg.accrue_realized_knowledge is
True was added to the daemon test and OBSERVED FAILING before the flag existed.
H-8(c) closed by AMENDING the correction note rather than deleting it, and the
amendment is the more useful artifact. PR-1's note said the 07-25 document's
claim was false; R-3 then made that claim TRUE, so the correction note itself
went stale — in the direction nobody guards, a correction made wrong by its own
subject being fixed. Both notes now carry the commit window they apply to. A
dated correction survives its subject changing; an undated "this is false" does
not.
Also delivered, from the 2026-07-28 external-assessment triage: §5, the
declared-table index. Eight single-source-of-truth tables and the pin that makes
each true. A reader's aid, explicitly NOT a central contracts.toml — that would
add a fifth copy needing agreement with four generators and put four different
authorities in one merge surface.
Handed forward to PR-11: the 5000-beat soak recorded in
evals/l10_always_on/contract.md (2026-07-19) predates this change and therefore
describes a configuration that NO LONGER SHIPS. PR-11's re-run is the first soak
of the corrected profile, and Step B feeding Step D is exactly what a long
horizon stresses.
Closes G-6, G-8, H-6, H-8(c), H-8(d) — and H-8 in full. Wave 4's F-6 half-gate
is lifted; the Track C half is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
core/capability/domains.py's DOMAIN_PACKS declares domain -> pack membership
for the capability ledger. Each packs/data/<pack>/manifest.json independently
declares domain_id. Two statements of one fact, and only one direction was
ever checked: domain_contract_predicates P3 validates domain_id -> a known
ledger domain. Nothing validated DOMAIN_PACKS -> the manifest agrees.
P3 passes VACUOUSLY on a manifest with no domain_id at all — the same shape as
its own test_pack_without_contract_reports_absent. So a pack that DOMAIN_PACKS
places in a domain, but whose manifest never says so, is invisible to every
check in the repository.
Measured: 7 of 9 bound, 0 contradictory, 2 absent — en_core_cognition_v1 and
en_core_meta_v1, both placed in philosophy_theology, which is one of the four
bands queued to earn a SERVE license behind R-8. The domain whose license is
next to be earned is the one whose binding exists in only one place.
Why nothing caught it: tests/test_domain_contract_predicates.py exercises
P1-P9 entirely against synthetic tmp_path manifests, never against the real
nine, and is itself in no curated suite. A predicate proven correct on
fabricated input and never run on the real input is this arc's defect class
stated precisely — its success state and "it never ran here" are
indistinguishable.
tests/test_domain_pack_binding.py, registered in smoke (on the gate):
- every pack DOMAIN_PACKS names has a manifest on disk
- a manifest that CONTRADICTS the table always fails
- a manifest with NO domain_id fails unless the pack is in UNBOUND_BASELINE
- the reverse direction: a pack may not self-assign a ledger domain the
table does not grant (clean across all 30 packs today, pinned because a
new manifest copying domain_id from a sibling is the obvious mistake and
would silently widen a licensed domain)
- both directions on the baseline: a pack that gains domain_id must leave it
- the parse is guarded against vacuity (N-1 has produced three false
conclusions in this repo)
- the count is pinned so the gap moving is a reviewed decision
Four sabotages, each observed red, each caught by its intended pin.
PINNED, NOT FIXED, deliberately. Whether en_core_cognition_v1 is a
philosophy_theology domain pack or merely grouped under that domain for ledger
accounting decides what the license actually covers. That is
ratification-adjacent content, not engineering, and it is routed to R-8 /
PR-14 where that band's license is decided. Sealing that ledger without
answering it licenses a domain whose membership is asserted in one place and
denied by silence in the other.
How it was found, recorded because the method is the point. An external
assessment proposed collapsing the pack/domain machinery, stating its method
up front: "I have enough from the directory and CLAIMS.md to reason
seriously." That is the method AGENTS.md protocol rule 1 forbids, and five of
its six proposals are falsified by opening the file — recorded in full in
31-hindrance-audit.md, in short:
- conftest.py "almost certainly" holds suite-membership logic: it holds two
autouse fixtures and two data registries, and its collection hook stamps
markers and never skips. Zero membership logic.
- cli_test.py "almost certainly a nested conditional or a big dict": a flat
dict, 17 keys, zero conditionals in the module body.
- field/algebra/morphology as a peer triad needing a single serve(): 72, 15
and 3 importers respectively, morphology is 97 lines, and no def serve(
exists anywhere in core/, chat/ or generate/.
- the grade space is richer than the serve layer consumes: all 15
EpistemicState members have non-test consumers.
- constraints: blocks in 314 ADRs: that is 314 new places that must be kept
true, inside ratified immutable records, in the repository whose dominant
defect class is records drifting from reality. The existing design puts
the machine-readable half in the checker that reads the ADR, which is
better on purpose.
- a root contracts.toml as "the highest-leverage single change": the
principle is already ADR-0263 Rule 5 and is implemented in the very file
the assessment cites. Centralizing four tables with four authorities adds
a fifth copy that must agree with four generators.
The seam it pointed at was real even though every specific was wrong. Bad
method, useful direction; both halves recorded, and the triage rule earns a
second demonstration — every rejected proposal failed on a file its author did
not open.
AGENTS.md — the standing philosophy revised to eleven, per Shay's review.
- "be quick to be humble" -> "investigate yourself first". Speed is a
consequence of reflex, not a criterion; writing it in makes velocity sound
like the point. Examples replaced with three that are verifiable in-repo:
the H-13 refcount claim, and the two rulings that were about to be
recorded stronger than they were made.
- the defect-class principle gains its prescriptive half as its own
sentence, and gains this commit's instance: a predicate that passes
vacuously on the input it was written for.
- NEW #2: don't build a ratchet to guard a ratchet. Placed adjacent to
"less is more" and named as its corollary so it does not read as
duplication. The arc paid for it — a membership ratchet needed a
reachability ratchet to mean anything, and what actually shrank the gap
was deleting four suites nobody called.
- NEW #10: a closed issue is not evidence the problem is gone. G-7 was
CLOSED and REOPENED-PARTLY inside one arc; H-13 was closed with a fix that
did not work.
Two review points were already satisfied in the file and are recorded as such
rather than re-applied: the defect-class principle already stood alone on its
first line, and already sat at position 3, not 8. The review was reading a
chat summary, not AGENTS.md — which is, exactly, reviewing the record instead
of the reality.
PR-5 additionally owes one page: an index of which declared tables exist and
what pin makes each true. That is the defensible residue of the contracts.toml
proposal, and explicitly not a fifth copy of four tables.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
Wave 1 — delete — run before more Wave 2 enforcement, correcting the method
inversion this arc had been committing: the plan sequences scrub -> DELETE ->
enforce, and Wave 1 was ruling-blocked while Wave 2 was not, so enforcement
landed first on structures deletion might have removed.
PR-3 (R-7 ruled A)
docs/gaps.md and docs/audit/substrate-liveness-ratchet.md carry historical
banners that name H-9's own mechanism as the reason they exist: an instrument
that looks authoritative converts "I should check" into "I already checked".
The ratchet's seven OPEN items — W-003, W-005, W-007, W-008, W-009, W-017,
W-018, every one L10-chained — migrated into G-5 WITH their dependency
chains, because that reasoning is the ratchet's lasting contribution and its
status column is not.
One deliverable could not be executed as written and is recorded rather than
claimed: L12 has NO in-repo generator to drop it from. The system map is
local and gitignored (D5 — a regeneratable index carrying no authority), so
the phantom existed only there and in one taxonomy row. That row now records
the ruling instead of the flag.
PR-3b — new, and it corrected its own author
Added when PR-4's two ratchets made the question unavoidable: what exactly
are they policing? Measured: 21 curated suites, 9 holding exactly ONE file,
12 holding <=4, and 2 reachable from any gate.
I proposed cutting ~11. The evidence supported FOUR. cognition, teaching,
packs and algebra are named in AGENTS.md's own pre-merge-gate instruction;
fast, pulse and proof in the CLI's help text; phase5, phase6, adr-0024, math
and formation in READMEs and ADRs. Deleting those breaks documented commands
— worse than the sprawl. Taking the 4 the evidence supports, rather than the
11 I had already said out loud, is the whole point.
Deleted: refusal, margin, rotor, inner-loop. Zero --suite references anywhere
in code, docs, ADRs, workflows or CLI help. Per-phase ADR-0024 aliases
offered so reviewers could run a phase independently; nothing ever did. All
seven of their files remain covered by adr-0024 — verified BEFORE the cut —
so nothing was orphaned and no coverage moved.
21 -> 17 suites. Pin 3's gate-unreachable gap 19 -> 15 BY DELETION, which
costs no gate time at all, versus promotion which costs it every run. An
alias nobody calls is not a curation decision; it is one more name that has
to be kept true, and two ratchets were policing these.
Registers: H-9 CLOSED, PR-3 and PR-3b marked LANDED, G-5 carries the seven
migrated items, the taxonomy's L12 row records the ruling.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
PR-4's membership ratchet closed "no new orphans". Within the hour this arc's
first full-tree run found tests/test_ratification_ceremony.py RED, in the
curated suite `teaching`, invoked by NO gate tier. Membership was satisfied and
bought nothing — which is exactly the objection PR-4 itself raised against
G-7's original formulation: a curated suite nobody runs is the same
non-guarantee as `full` wearing a different name. The fix had the same blind
spot as the thing it fixed.
Measured: 21 curated suites, 2 gate-reachable (smoke, deductive). The gap is
NINETEEN, not the handful first estimated.
Shipped as baseline + ratchet, sized to the measurement rather than the rule:
19 `post-merge-only` justifications written in one sitting is the ceremony
failure already rejected for the 749-file membership baseline. The pin freezes
the gap, fails on growth, enforces both directions (a promoted suite must leave
the baseline), pins the count, and — critically — VERIFIES ITS DECLARED GATE
SET AGAINST THE SHELL (pre-push / local-ci.sh), so it cannot go vacuous the way
a scan that silently matches nothing does (N-1's class, three false conclusions
in this repo so far).
Two sabotages observed red: a new curated suite with no gate caller, and a
GATE_SUITES value drifted from the scripts it claims to describe. The second
correctly cascaded into two failures, which is the anti-vacuity guard working.
What pin 3 deliberately does NOT do: decide which of the 19 belong on the gate.
That costs gate time and is a decision, not an engineering task. Recorded as the
open half of G-7. `teaching` is the concrete first candidate — it just shipped a
red ratification pin that no gate ran.
Process note: a gate run was invalidated by editing the tree mid-run for the
third time today. Stopped with TaskStop rather than pkill this time, which
reaped cleanly instead of leaving the orphaned wait-loop that ran for 2h16m
earlier. All edits now land before any run starts.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
Ruled: fix, don't document. The argument that settled it — a red test on a
serving-path contract left with an explanatory note IS the H-8 failure mode, a
document acknowledging a gap instead of closing it, which becomes the next
assessor's finding.
Two distinct failures, and the second is the serious one.
1. THE CEREMONY PINS WERE STALE AGAINST CORRECT CODE (H-8e's class).
ChainRecord.polarity defaults to AFFIRMATIVE and is DELIBERATELY OMITTED
from affirmative rows (ADR-0264 R1; teaching/ratification.py:119). Both pins
built ChainRecord(**{k: row[k] for k in __slots__}), so they KeyError'd on
every correctly-serialized row. Now built from the row's own keys, letting
absent fields take their dataclass defaults — which RESTORES the guarantee
rather than weakening it. Consequence of the staleness: ratification-corpus
byte compatibility and unreviewed-status refusal were UNVERIFIED since
ADR-0264 R1 landed.
2. CLAIMS.md PUBLISHED A SUPERSEDED EVIDENCE DIGEST FOR A LICENSED CAPABILITY.
f9e9cc0c (2026-07-26, "the deduction lane hashes the prose it serves")
strengthened the lane, rewrote evals/deduction_serve/report.json, and moved
the authoritative pin in scripts/verify_lane_shas.py:60 to c855d55c —
recording the superseded 0b461a5a on line 56. It did NOT regenerate
CLAIMS.md. So for two days the published claim for deduction_serve_v1
pointed at evidence that no longer existed. The claim TEXT never changed;
only the pointer was wrong — which is exactly the failure a digest exists to
prevent. Regenerated; the diff is one line, the digest.
The drift had reached a THIRD artifact: this assessment's own
M3-comprehension-reasoning.md card cited the same stale 0b461a5a as its
would-fail-if-absent evidence. Corrected.
THE MECHANISM IS G-7's, AGAIN. One commit updated the lane, its verifier and
its lane test, and missed one downstream artifact — and the pin that guards
that artifact (tests/test_claims_md_is_current.py) EXISTED AND RAN ON NO GATE.
Nothing surfaced it until this arc ran the full tree once. Both it and
test_ratification_ceremony.py are now registered in TEST_SUITES["smoke"].
The membership ratchet caught the author a second time: promoting
test_claims_md_is_current.py onto the gate without removing its baseline line
failed test_baseline_has_no_stale_entries immediately. Baseline 748 -> 747.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
G-9's charge: "law-enforced-by-review is weaker than law-enforced-by-test." Its
stated method — verification pass first, then mechanical PRs — is what made this
worth doing, because the three prohibitions turned out to be in three different
states and only one of them was the state the register assumed.
(a) EXACT RECALL — the real gap, and worse than recorded. "Would a cosine
ranker actually fail a test?" answers NO across nearly the whole surface. The
only import ban in the repository covers exactly one file
(core/physics/wave_manifold.py, via test_third_door_cohesion.py — itself in no
curated suite). Meanwhile generate/realize/recall.py, which IS the recall path,
states the prohibition in its module docstring — "an exact, deterministic
equality scan (no cosine / HNSW / ANN)" — and nothing enforced it. Law asserted
where it executes, enforced nowhere: H-8's failure mode applied to a
prohibition rather than a record.
Now scanned across vault/, generate/realize/, generate/meaning_graph/, field/,
recognition/ for banned ANN libraries AND for hand-rolled cosine* definitions.
The second half matters: an import ban only stops the convenient version, and
ten lines of numpy is the realistic way a well-meaning contributor violates
this. A third pin ties the docstring claim to the scan, so the sentence and its
enforcement cannot drift apart.
(b) GOVERNANCE BYPASS — now exact. INV-07 proves governance works WHEN CALLED;
nothing proved it could not be skipped. Mechanism: no function in
chat/runtime.py may construct a TurnVerdicts without invoking
safety_check.check, and every such call must pass self.safety_pack — because a
bypass need not skip the call, it can substitute the pack. Measured at close:
two verdict-constructing functions, both governed.
(c) SAFETY-PACK NON-SWAPPABILITY — already well pinned; the gap was REACH, not
coverage. tests/test_safety_pack.py pins unratified-pack-refused-in-production,
missing-companion-report-refused, seal-failure-refused, path-traversal-rejected,
missing-pack-fails-closed — and ran in NO curated suite, so a fail-closed safety
contract was verified only after merge. Promoted onto the gate; the new pin
guards that placement rather than duplicating the substance.
FOUR SABOTAGES, EACH OBSERVED RED, each caught by its own pin: a banned library
imported on a recall path, a hand-rolled cosine ranker, a serving path
assembling verdicts without governing, the safety pack demoted off the gate.
Both new files registered in TEST_SUITES["smoke"] on creation (#136). Source
scans only — no runtime behavior, no flag, no serving path touched.
Registers: G-9 CLOSED with the per-item findings; PR-6 marked LANDED.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
N-9 is the finding, and it PREVENTS work rather than re-scoping it.
WHAT THE PLAN GOT WRONG. N-3 concluded the 23-vs-13 smoke delta was unintended
drift, on two in-repo comments asserting parity, and called that "the strongest
evidence available that the drift was never intended". H-12 repeated it; R-14
built three options on it recommending "raise CI, +46s measured"; PR-4
specified a bidirectional parity pin as deliverable one.
A third artifact answers all of it, and none of them read it.
tests/test_cli_test_suites.py::test_cli_smoke_suite_covers_ci_smoke_gate has
pinned smoke.yml SUBSET-OF TEST_SUITES["smoke"] since before this assessment —
which is why every measurement finds CI-only == 0; a pin, not luck — beneath a
comment headed "DELIBERATELY ONE-DIRECTIONAL — do not 'complete' this by also
asserting local_paths <= ci_paths". Its history is 50fa287d (2026-07-25):
"revert(tests): drop the local-to-CI smoke parity assertion — CI is not a
gate." An earlier agent read the same comment, drew the same inference, made
the assertion symmetric, and reverted it hours later: "That was wrong, and
AGENTS.md says so in a line I had already read."
AGENTS.md:280 — GitHub is "a mirror only; its Actions are billing-locked and
produce dead signals — never chase them." §277 — the workflows are "secondary
observability only." So the local suite is the SOURCE and is deliberately
broader; the delta is the design.
- PR-4 pin 1: WITHDRAWN. It exists, in the direction that protects something.
- R-14: premise corrected in the packet. Option A spends 46s on a workflow
that does not gate; option B would delete real protection for a fiction;
option C's live half is a two-line comment fix, done here.
- N-3's exposure claim was too GENEROUS to CI: for a push that skipped the
local gate there is no automatic check at all. Larger than stated, and not
closable by editing smoke.yml.
WHAT LANDED. Measured: 749 of 877 test files are in no curated suite. The
plan's mechanism ("assign every orphan, or 749 exclusion reasons") is hollow at
that scale — glob topic-suites ("adr": tests/test_adr_*.py absorbs 172) satisfy
the assertion while changing nothing about what executes, which is precisely the
Third-Door objection the plan levels at G-7's own first formulation. All four
demonstrated incidents (#113, #136, negation-survives-articulation,
speculative-subject-lifecycle) were caused by a NEWLY LANDED orphan; none by a
legacy one.
So: a ratchet. tests/test_suite_membership.py + tests/full_only_baseline.txt.
New orphans fail. The baseline is enforced in BOTH directions — a promoted or
deleted file must leave it, so the list can only shrink — and its count is
pinned so bulk movement is a reviewed decision, in the discipline
test_volume_honesty.py uses.
Also: the gate-guarding pin now runs on the gate. test_cli_test_suites.py lived
in `fast`; the pre-push gate runs smoke + deductive. The pin guarding the gate
did not run on the gate.
Three sabotages, each OBSERVED RED: a new unregistered test file, a stale
baseline entry, the parity pin demoted out of smoke.
Note on the delta: now 12 (local 27, CI 15) because this session added three
pins locally. Under the corrected reading that is the design, not widening
drift — the invariant is CI-only == 0, and that is pinned. The registers now
say so; "ten files" was a measurement, never a target.
Process note: `git checkout --` during sabotage cleanup destroyed uncommitted
edits to core/cli_test.py, which were redone. Backups, not checkout, on files
with unstaged work.
Registers: G-7 CLOSED, H-12 amended, R-14 dissolved in the packet, PR-4
re-specified, N-9 added to plan §0.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
Wave 2's PR-9, whose authority is "mechanical PR" and which is gated on no
ruling. Widened by the 2026-07-28 triage from the accrual guard alone to all
three broad `except` blocks on the turn spine.
Each guard is DELIBERATE and stays: accrual is additive and must never crash a
turn; a pack/catalog failure must not take down logos authority; a probe
failure must not touch the served surface. Narrowing them to ImportError/
OSError — as an external assessment proposed — would convert a malformed
decision object into a turn-spine crash, trading silent failure for
served-surface failure. What was wrong is not the tolerance, it is that each
wrote a value INDISTINGUISHABLE FROM ITS HEALTHY CASE:
- a raising read->realize->determine chain left the same None accrual a quiet
turn leaves;
- a logos decision that raised looked exactly like a turn where logos was
never consulted (both: logos_decision_kind == "");
- a crashed geometric probe recorded the same empty neighborhood as a probe
that ran and found nothing — in the block whose entire job is producing the
data that prices the anti-unification roadmap.
Silence, in the one layer whose constitution is "failures are typed, never
silent" (INV-34).
chat/runtime.py _accrual_swallows / _last_accrual_error, read via
accrual_swallow_telemetry(). _last_turn_accrual is
still None on failure, so _maybe_surface_determination
returns byte-identically — ADDITIVE BY CONSTRUCTION,
and pinned as such.
cognition/result.py logos_error on CognitiveTurnResult.
cognition/pipeline.py logos_error recorded; probe_error,
neighbor_scan_errors, graph_anti_unify_error on the
OOV telemetry.
tests/test_accrual_swallow_telemetry.py — 7 pins. The two behavioral ones were
OBSERVED RED against a build carrying the fields with the recording stripped,
so they discriminate rather than merely pass. Two test bugs of mine were fixed
along the way (wrong logos module path; CognitiveTurnResult has required
fields) — the tests were wrong, not the code.
Registered in BOTH TEST_SUITES["smoke"] and smoke.yml on creation, per #136.
Local/CI delta verified unchanged at exactly ten (local 25, CI 15, CI-only 0),
so this settles nothing R-14 has to rule.
Cleanup as found: three dead imports removed from chat/runtime.py
(_COGNITION_PACK_ID, _TEACHING_CORPUS_ID, engine_identity_for_config), each
confirmed to have no re-export dependence first — workbench/readers.py imports
engine_identity_for_config straight from core.engine_identity. ruff 4 -> 1 on
these paths; the survivor is a pre-existing structural E402.
Registers: H-11 CLOSED; PR-9 marked LANDED in the plan.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
71542e61 fixed only half of H-13 and asserted a premise that is false. This
replaces it, and records why, because the failure mode is the one this arc
exists to catch.
WHAT WAS WRONG. The refcount rested on "seeding and eviction walk the same
source list, so the counts balance." They do not. The call site's arms are
MUTUALLY EXCLUSIVE (pipeline.py §10b): a proposal is seeded when SPECULATIVE or
released when COHERENT, never both. So a release never balances a claim the
same proposal made, and a COHERENT proposal ALWAYS releases claims it never
held. Refcounting therefore repaired only the sub-case where both proposals had
been taught speculatively. The ordinary case — an unreviewed `wisdom`, then a
separate correction about `practical wisdom` that passes review on its own turn
— still stripped the marker. Measured against the landed refcount:
`sibling marked? False`.
The first attempt's tests passed because their helpers split tokens themselves,
mirroring a call pattern production no longer used — tests written to the fix
instead of to the defect. That is the same "asserted past the evidence" failure
diagnosed in two external assessments earlier today, committed here one commit
later, and catchable by reading fifteen lines at the call site.
WHAT IS CORRECT. _speculative_subjects maps token -> SET OF CLAIMING SUBJECTS.
A token is evicted exactly when the subject that actually claimed it is
reviewed; discarding an absent claimant is a no-op, so a neighbour's marker
survives. Token derivation moved out of the call site into
_speculative_index_tokens, because ownership is what the call site cannot see —
doing the split out there made every token its own claimant, which was the bug.
Tests rewritten to call the cache exactly as §10b does. The lead pin
(test_coherent_proposal_does_not_unmark_an_unrelated_subject) was confirmed to
FAIL against the refcount design before this landed, so it discriminates rather
than merely passing. 13 pins green; the 8 pre-existing lifecycle pins unchanged.
SUITE MEMBERSHIP, resolved rather than deferred. The file pinning this behavior
sat in NO curated suite, reachable only through `full` — which is why the
regression was invisible (G-7's mechanism, on the pin that would have caught
it). Registered now in BOTH TEST_SUITES["smoke"] and smoke.yml, per the #136
precedent that an unregistered pin runs nowhere. Verified the local/CI delta is
unchanged at exactly ten files (local 24, CI 14, CI-only 0), so this is
compatible with all three R-14 options and settles none of them; PR-4 still
owns the standing ten.
Cleanup as found: three unambiguously dead imports removed from pipeline.py
(hashlib, RatificationOutcome, RatifiedIntent). ruff 4 -> 1; the survivor is a
pre-existing structural E402, left alone.
Ratification note unchanged: this changes served surfaces — markers appear on
turns where they previously vanished. Direction is toward disclosure and it
restores ADR-0021 §Articulation's stated intent rather than extending it.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
The speculative-marker cache seeds a subject AND each >=4-char token split from
it, so two independent proposals can claim the same token ("wisdom" from both
`wisdom` and `practical wisdom`). `_forget_speculative_subject` popped the token
outright on COHERENT promotion — regardless of which proposal had seeded it — so
promoting one proposal stripped the marker from another that was still
unreviewed. Unreviewed material was then served WITHOUT the "(speculative, not
yet reviewed)" prefix: the expensive failure direction, on the surface whose
whole job is telling reviewed knowledge from unreviewed.
_speculative_subjects becomes OrderedDict[str, int] — the value is a reference
count. Seeding increments and refreshes LRU position; promotion decrements and
evicts at zero. Seeding and eviction walk the same source list so counts
balance; where they disagree the count stays positive and the subject keeps its
marker, which is the honest direction to fail. An unmatched release floors at
removal and never goes negative, so it cannot borrow against a later proposal's
claim. The LRU cap survives unchanged as a size valve.
Four new pins in tests/test_speculative_subject_lifecycle.py, ALL FOUR OBSERVED
RED BEFORE GREEN — sibling token evicted, marker lost at the served surface,
repeated teaching cleared by one promotion, and count underflow. The eight
pre-existing lifecycle pins pass unchanged.
Also H-8e — expand_relation_closure's docstring claimed "Cycle: if path would
revisit a node, skip", describing a check the code does not perform. The code
was right: termination is structural (monotone growth over a finite triple set),
and a path-revisit skip would REFUSE SOUND transitive derivations, since a
witness path that revisits a node still proves a true fact. The docstring now
says that, and records that `path` is the witnessing step rather than full
ancestry — extending it would move operator_invocation and therefore trace_hash,
so it is deliberately not done here.
Registers updated: H-13 marked FIXED with the ratification note (this changes
served surfaces toward disclosure, restoring ADR-0021 §Articulation's stated
intent rather than extending it); H-8e marked CORRECTED.
Found while fixing it, recorded not acted on: tests/test_speculative_subject_
lifecycle.py — the file pinning this exact behavior — is in NO curated suite,
reachable only through `full`. G-7's mechanism, caught on the pin that would
have caught the defect. Assignment left to PR-4; adding it to TEST_SUITES
["smoke"] alone would widen N-3's ten-file delta and prejudge R-14.
Pre-existing ruff findings in pipeline.py (3 unused imports, 1 E402) confirmed
present before this change and left alone — module-top, not adjacent to the edit.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
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
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.
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.
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.
`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.
Phase B of the curriculum-license-loop arc. Stacked on the ADR-0264 branch.
The invariant: conservative_floor is a one-sided Wilson lower bound and Wilson
assumes INDEPENDENT trials. CORE's pipeline is deterministic, so replaying an
identical case is not a second trial -- it is the same trial with a guaranteed
outcome. A ledger's `committed` count is an upper bound on its evidence; the
defensible figure is its distinct-decision count.
Phase B was scoped as a precaution for a curriculum producer that does not
exist yet. Running the instrument against the producers that DO exist found it
already violated:
21 of the 25 ratified deduction_serve bands do not clear theta_SERVE=0.99 on
distinct evidence. Three inflate 28 distinct cases into 720 committed
(honest floor 0.8084 vs claimed 0.9909). deduction_serving_enabled was
ratified ON 2026-07-24 and is live.
The estimation producer (ADR-0175) is clean -- 660 committed, 660 distinct,
zero repeats, evidently chosen just above the 657 a perfect record needs. So
this is a regression from a standard already established in the repo, not an
architectural gap. `claimed` is identical for all 25 deduction bands because
every band commits 720 with a perfect record: the gate sees 25
indistinguishable passes while honest floors span 0.8084..0.9909.
NOT REPAIRED. The ledger is SHA-sealed, ratified, and gating a live flag, so
re-sealing it is Shay's ratification, not a test's. wrong=0 still holds -- no
band answered anything incorrectly. What is not established is reliability at
the volume claimed. Measured, pinned in both directions, written down.
Added:
- core/reliability_gate/evidence.py -- pure measurement, imported by no serving
path. volume_for_theta() DERIVES 657 from conservative_floor rather than
restating it, so a WILSON_Z change cannot leave a stale literal behind.
- tests/test_volume_honesty.py -- 13 tests. AUDIT_SOURCES is checked against
CAPABILITY_LEDGERS, so a new licensed capability cannot ship unaudited; the
curriculum entry is a declared None that fails the moment its ledger exists.
That is Phase C's forcing function against repeating the pattern.
- docs/research/distinct-evidence-audit-2026-07-25.md -- full audit + the open
outcome-mix ruling for Shay.
- ADR-0264 R9 amended with the measured exposure.
Decision key is case TEXT deliberately: a tighter key (normalized atom) would
collapse spelling variants and report MORE inflation, so text-identity
under-reports and every number is a floor on the real gap.
Outcome mix is deliberately NOT pinned. ClassTally has no verdict axis, the
deduction producer already balances 240/240/240 by construction, and imposing
a per-verdict-class floor would retroactively fail all 25 bands on a criterion
no ADR has ratified (smallest per-band class count is 120). Recorded for a
ruling with numbers instead.
[Verification]: uv sync --locked on canonical CPython 3.12.13; in-worktree
smoke 569 passed (556 baseline + 13), deductive 285 passed. Registered in the
`smoke` curated suite -- not left to `full`, which gates nothing. Mutation-
checked: doubling CASES_PER_BAND -> 2 failed; reporting committed as distinct
-> 4 failed; gating on `claimed` instead of `honest` -> 3 failed; tree restores
to 13 passed.
Nine ADRs (0254, 0256-0263) were merged into main and left stamped Proposed.
Two carried an explicit ratify-on-merge predicate their own merge had already
discharged, and ADR-0256 governs deduction_serving_enabled, which was ratified
True on 2026-07-24 and is serving live traffic. So the governance record
asserted "not yet decided" about a decision already in force.
That is the asymmetry the assessment arc (#113) found one file over in
workbench/api.py: the honest path degrades, the stale record lies. An unwritten
ADR is a visible gap; a Proposed one that is actually in force is a false
statement.
Each stamp records the ACTUAL ratifying act -- "Accepted, ratified by Joshua
Shay via <merge> (<sha>, <date>)" -- derived from the commit that added the file
and verified an ancestor of main, not assumed. Merge authority is Shay's alone
(AGENTS.md: no merge automation), so the merge IS the ratifying act.
No ADR content changed. No flag changed. ADR-0262's stamp says so explicitly:
accepting it does NOT enable curriculum_serving_enabled, which stays False
pending ratified volume -- eleven bands re-measured today, still 24x-73x short.
tests/test_adr_status_governance.py pins two independent invariants in the
smoke (pre-push) suite:
1. A default-ON flag is not governed by a Proposed ADR. The flag -> ADR mapping
is DERIVED by walking core/config.py for `<name>: bool = True` and reading
ADR refs from the preceding comment block -- not a hand-written table, which
would be the same second-copy-of-a-closed-set defect ADR-0256's arc fixed.
2. A ratify-on-merge predicate cannot coexist with Proposed. Self-discharging:
the file being on main IS the merge having happened.
Plus a vacuity guard, because a derivation that parses zero flags would make
every other assertion pass on an empty set.
Registered two orphans found in passing: test_adr_index.py (5) and
test_ratification_ceremony.py (14) landed in #113 in NO curated suite, so 19
tests -- including the one mechanism that can move curriculum volume -- ran
only under `full`, which gates nothing. Fifth instance of this shape.
Deliberately NOT fixed, recorded in the research doc: the 312-file corpus has
27 unparseable status lines and draft/ratified/active variants. A closed-vocab
assertion would fail on ~35 pre-existing files and get muted, and a muted gate
reads as coverage.
[Verification]: smoke 555 passed in 137.73s (236 baseline + 314 + 5, +1.2s);
governance pin 314 passed in 1.18s standalone and MUTATION-CHECKED -- reverting
ADR-0256 to Proposed fails both invariants independently (2 failed/312 passed);
orphans 19 passed; ruff clean. Canonical Python 3.12.13, uv sync --locked.
*** VERIFICATION PENDING — see the bottom of this message. ***
99 of the 107 full-suite failures are one test, red on clean main since
2026-07-23, and it is guarding the wrong field.
WHAT IS NOT WRONG. test_register_matrix_trace_hash_invariant — the load-bearing
ADR-0072 truth-path-isolation claim — passes on all 99 registers on pristine
main. trace_hash does not vary across the register axis. The invariant holds;
the seals and wrong=0 are untouched.
WHAT IS. test_register_matrix_canonical_surface_byte_identical asserts on
CognitiveTurnResult.surface, on its docstring's claim that this is "the
truth-path field compute_trace_hash consumes". That stopped being true when
Phase 0 flipped resolve_surface to response-first precedence and introduced
SurfaceResolution.hash_surface as the register-invariant capture. Since then
pipeline.py folds hash_surface into compute_trace_hash, while result.py
documents surface as "final voiced surface (what the user sees)".
So the test demanded that registers NOT differ on the served surface — the
opposite of what the register axis exists for, and a direct contradiction of
test_register_substantive_consumption.py, which pins that they DO differ and is
green in smoke. Two tests on main asserting opposite things about the same
bytes; the red one is in neither gate, so it stayed red.
ROOT CAUSE, and why this is a code fix rather than a test edit: hash_surface was
pipeline-local. The invariant was unobservable from a turn result, which is why
the test reached for the nearest visible field and drifted. Exposing it makes
the contract testable at the seam that owns it:
- CognitiveTurnResult.hash_surface — new field, populated from the value the
pipeline already computed
- evals/cognition CaseResult carries it through
- the test asserts byte-identity on hash_surface, with the history recorded so
it cannot drift back
No stored baseline to regenerate — the fixture computes it live.
[Verification]: NOT YET RUN. The register matrix is ~16 min and was still
executing when the working tree had to be committed; its output buffers through
tail, so no partial signal was available. Committed to preserve the work with an
honest label rather than to claim it passes. DO NOT MERGE until
`pytest tests/test_cognition_eval_register_matrix.py` is green — expected 0
failed where main has 99, with test_register_matrix_trace_hash_invariant still
passing. Result to follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FduW6Krm3PPQv3P5iwBYtx
Squashes the arc's work into one commit; the workflow-file edit it originally
carried is excluded (see the end of this message).
## Lane 1 — Workbench recorded a proved answer as ungrounded
With deduction_serving_enabled ratified ON (ADR-0256), workbench/api.py's live
chat route builds a bare ChatRuntime(), so the deduction composer decides
Workbench turns and stamps grounding_source="deduction" — but
_coerce_grounding_source carried a hand-copied whitelist of the six pre-arc
labels and silently rewrote anything else to "none". The runtime comment
reasoned this was inert because "REPL turns do not flow through Workbench's
CognitivePipelineRecord path". True, and irrelevant: the traffic flows the
other way. Stale since 2026-07-24.
Scope is one field. workbench/api.py:818 prefers TurnEvent.epistemic_state,
which read epistemic_state_needed — honest. So the UNregistered path degraded
honestly while the hand-copied whitelist asserted a falsehood; a second copy of
a closed enum was worse than no copy. Hence registration AND derivation:
GROUNDING_SOURCES exposes the Literal's members, and the coercion reads it.
workbench-ui badges/tokens/snapshot follow; enumCoverage.test.ts forces atomicity.
## Lane 2 — the ratification ceremony
The discovery loop was instrumented but not closed. teaching/ratification.py
turns a reviewed decision into a chain record, a corpus commit, and a receipt.
Its design turns on one observation: _ratified_rows DROPS unadmissible rows
silently — correct when serving, a trap when ratifying, because the file grows,
the commit lands, and the band count does not move. So the ceremony refuses to
call an append a ratification until it has re-read the curriculum through the
real loader and seen the chain arrive; a non-admitted append is rolled back.
Validation is a pre-flight courtesy, admission is the proof.
Arena queue entry and ledger reseal are deliberately NOT performed (bridge rule
1); the receipt names them. Front door: `core proposal-queue ratify`, a sibling
of `review` rather than a flag on it.
## Lane 3 — structural closures
- ADR-0263 gains rule 5: absence policy is DECLARED in CAPABILITY_LEDGERS, not
passed at the call site. An AST-matched test fails if a serving path passes
missing_ok again.
- Deductive suite added WHOLE to the pre-push gate: 285 tests in 29s against
smoke's 216 in 62s, so no coverage trade was needed.
- Smoke/CI parity assertion made bidirectional. It was one-directional, and had
drifted.
- test_prior_surface_deduction_binding.py pins correction binding on the
deduction path. The review's diagnosis did NOT reproduce — hash_surface moves
in lockstep — so it pins what is there. Mutation-checked.
- Domain-keyed ADR index over 312 flat-numbered files, explicitly partial.
- Arc-close brief template, plus this arc's own brief filled in against it.
## Lanes 4 and 5 — two premises falsified by measurement, one of them mine
Math 4.2: baseline reproduced (correct=5 wrong=0 refused=495); all four named
cases traced to one seam with each gap isolated by one-variable probes. Then the
number that changes the recommendation: the gap blocking case 0000 affects 1
case in 500, the 'than' gap blocking 0001 affects 2. ADR-0251's prohibition on
per-case growth now rests on a count. No reader change made.
CGA: versor_condition is 0.22% of a turn, not the "~10x proof latency" I claimed
— that multiplied an isolated microbenchmark by a call count and compared it to
a single verdict's latency. The real cost is geometric_product at 33,986
calls/turn (~73%) via cga_inner in search paths. The obvious closed form is NOT
bit-exact (954/4000 in f32); backend.vault_recall's serial fold IS (3000/3000,
worst-rel 0) and is the correct target. cargo test could not run —
static.crates.io is denied by the sandbox network policy — so the Rust parity
question stays open and the typestate lane is carried forward, not shipped
uncompiled.
## Not landed: three lines owed to .github/workflows/smoke.yml
The CI smoke gate is narrower than the local one —
test_pack_draft_serve_boundary.py (ADR-0253 INV-33) has been local-only, unseen
because the parity pin checked one direction. The edit was authored and rejected
at push for lacking the `workflow` OAuth scope, so it is recorded as a named,
dated PENDING_IN_CI exception rather than dropped: the assertion still fires on
any new divergence, and a second guard fires once the three land.
[Verification]: pre-push gates all green — smoke 236 passed, warmed_session 10
passed, deductive 285 passed. Ratification 14, ADR index 5, CLI suites 10.
Grounding/epistemic sweep 741 passed 1 skipped. workbench-ui 598 passed across
73 files, tsc -b clean. capability index 11 passed, digest unchanged. Math
holdout correct=5 wrong=0 refused=495. Committed chain corpora byte-unchanged
after the tests that write to them.
Environment caveat: the repo pins requires-python ==3.12.13, which uv cannot
fetch for linux-x86_64, so `uv sync --locked` fails. All Python runs used a
scratch venv on 3.12.11 with declared deps — not the locked universe, not the
full ~12k suite. The pin was left untouched. Re-run on a 3.12.13 host before
treating this as merge evidence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FduW6Krm3PPQv3P5iwBYtx
- Promote tests/test_register_substantive_consumption.py into the smoke
suite (local core/cli_test.py + mirrored .github/workflows/smoke.yml):
the falsifiable register-axis (ADR-0069/0071/0077) contract was red on
main outside every gate for the 2026-07-22..24 window, masked by an
unrelated flake label. 36 tests, ~4.6s.
- Found in passing: test_core_test_deductive_suite_expands_to_entailment_lane
was already red on clean main (an exact-tuple pin from when the
`deductive` suite had one file; it has since grown to 12). Fixed to a
contains-style assertion so it can't go stale the same way again.
- Two new capability-index adapters: deduction_serve_existential_result
(Band v6-EX alone — the one band among six that changes the decision
procedure itself) and curriculum_serve_result. Breadth 11 -> 13; baseline
re-frozen (a deliberate re-freeze per its own docstring).
- Promotion sweep: confirmed via the existing wrong=0 lane gate (run
repeatedly this session across 166/166 deduction-serve cases) that no
split beyond the already-applied ds-mem-0020 has a stale declined-gold
case a current band now decides differently.
`core proposal-queue {status,list,show,review}`: a read + review-state-only
CLI over the two proposal sinks that had a Python API but no operator
surface — teaching/proposals/comprehension_failures/ (N5 contemplation)
and teaching/proposals/derived_close_facts/ (idle_tick PR-2 bridge).
"review" appends to a sidecar log (teaching/proposals/review_log.jsonl)
recording that a human looked at an artifact; it never mutates the
artifact, never ratifies, never mounts, never flips a flag. The existing
ratification corridor (teaching/proposals.py, `core teaching
proposals`/`review`, a different sink) is untouched.
Found and fixed a real bug while building the second sink's reader:
generate/determine/derived_close_proposals.py's DEFAULT_SINK used
parents[3] instead of parents[2], resolving one directory ABOVE the repo
instead of inside it. Silent because the feature is default-off and no
test asserted the path — confirmed live via a stray artifact sitting
outside the repository since 2026-06-16. Pinned by
test_derived_close_default_sink_resolves_inside_the_repo.
Implements the measurable test from COMPREHENSION-READER-AUDIT.md's Q5,
generalized to the deduction and curriculum composers: a deterministic
refusal histogram split mechanism/coverage/engine_refused, plus an
admissions-per-batch counter for measuring a future lexicon or curriculum
expansion's effect as a before/after delta instead of asserting it.
Found and fixed a real classification bug while building it: an argument
successfully read by a band but declined by the ROBDD engine for
inconsistent premises was being misattributed to whichever reader band
happened to run last in the fallback cascade. That case is neither a
shape gap (mechanism) nor a vocabulary gap (coverage), so it gets its own
"engine_refused" bucket rather than being folded into either.
Baseline measurement: deduction refusals are 100% mechanism-class today
(the bands read a closed connective grammar, not open vocabulary) —
curriculum refusals are a real mechanism/coverage mix, confirming the
volume ceiling S6 quantifies independently from the ratified-chain side.
Ratified by Shay 2026-07-24. Default flipped False -> True.
Evidence at ratification:
- 25 shape-bands, each holding SERVE at 720/720 wrong=0 on the SHA-sealed
ratified ledger (theta_SERVE=0.99, n >= 657 committed per band);
- deduction-serve lane 166/166 wrong=0 across six hand-authored splits;
- commit gate narrow by construction — a sentence-initial "therefore" IS an
argument, so the composer cannot claim turns that are not one;
- an unearned shape is still served DISCLOSED, never asserted.
Blast radius verified, not assumed: core test --suite deductive 268 passed
with the flag ON by default; a live default-config ChatRuntime decides real
English and existential arguments through grounding_source="deduction" while
ordinary turns still fall through to pack grounding; lane pins 10/11 match —
unchanged by the flip (the single miss is the pre-existing public_demo drift
documented for Tier S).
Rollback is flipping this back: flag-off remains byte-identical to pre-arc
dispatch and leaves no residue, pinned by
test_flag_off_preserves_pack_token_gloss_byte_identity.
curriculum_serving_enabled stays OFF — every curriculum band is unearned, and
the Sonnet brief now records the explicit bar for revisiting it.
Phase 3.3 of the generalization arc. Estimation (ADR-0175), deduction
serving (ADR-0256) and curriculum serving (ADR-0262) had each written the
same seal -> ratify -> SHA-verify -> serve-gate machinery. core/ratified_ledger.py
now owns it and states the four rules once: only sealed practice writes;
tamper-evidence is structural (a load that cannot reproduce content_sha256
REFUSES); ceilings are not negotiable at the call site; absent evidence is
never a license.
Each capability keeps a thin adapter that names its artifact and preserves
its public API. One real difference is now declared rather than implied:
missing_ok distinguishes a ledger a capability SHIPS with (absence = broken
deployment, refuse) from one whose practice volume is still being built
(absence = nothing earned yet, serve disclosed) — curriculum serving is the
second kind today.
Safety property is byte-identity, asserted not assumed: re-sealing the
committed 25-band deduction ledger through the bridge reproduces it
byte-for-byte, so no artifact and no lane pin moves.
Effect: a new subject arena now needs a gold corpus and a band key, not a
re-implementation of ratification — which is what §3 meant by sequencing
the bridge ahead of the second subject.
[Verification]: tests/test_ratified_ledger_bridge.py 8 passed; core test
--suite deductive 252 passed; estimation/license test set 355 passed;
committed deduction ledger byte-identical after reseal.
Phase 2 of the generalization arc, implementing the plan's §4
curriculum-entailment gold contract. "Does force cause acceleration?" is
answered from the ratified physics chain corpus and nothing else; "Does
gravity cause acceleration?" is declined because gravity is in no pack
CORE has been taught; "Does force cause motion?" is unsettled even though
the curriculum contains both links, because nothing ratified says
causation composes.
Path (flag-gated, default off): closed question grammar -> subject routing
by ratified vocabulary -> family-scoped premise compilation from reviewed,
pack-resident chains -> the SAME argument bands (ADR-0260/0261) -> the
ROBDD engine. Zero subject-specific decision code: physics differs from
philosophy only in which rows load.
Epistemology enforced mechanically, not by convention:
- gold is a function of (curriculum, question); cases pin chain ids and
the runner FAILS a case whose pinned chain is absent or unratified;
- untaught => UNKNOWN, never "no" (open-world; silence is not denial);
- an independent oracle (own loader, ratification predicate, family table,
agreement rules, verdict rule) re-derives every gold — it disagreed once,
on "entropy reveals energy" vs "entropy causes energy", and the ORACLE
was the side that was wrong;
- anti-recall probes are a lane GUARD: a split without >=3 true-but-untaught
probes refuses to run.
Findings recorded rather than worked around (ADR-0262 §5):
- every curriculum band is UNEARNED and every answer is hedged. A band needs
n>=657 with a real outcome mix; physics teaches 7 causal + 9 modal
relations, so at most 16 questions in the subject can ever be ENTAILED.
A balanced band needs ~219 taught relations per subject×family — a ~25x
gap that only ratified curriculum content closes. Phase 2's blocker is
curriculum volume, not machinery.
- REFUTED is unreachable from present corpora (every chain is positive).
- there is NO biology domain-chain corpus; the biology OOD lane measures
fluency, not truth. The four subjects with ratified chains and mounted
packs are physics, mathematics_logic, systems_software,
philosophy_theology — the composer serves all four.
[Verification]: curriculum lane 32/32 wrong=0 with 5 anti-recall probes and
all three contract guards passing; tests/test_curriculum_serve.py 20 passed;
core test --suite deductive 252 passed; lane pinned as curriculum_serve_v1.
Completes the all/no/some square. `some` is read for the first time:
"All mammals are vertebrates. Some whales are mammals. Therefore some
whales are vertebrates." is decided entailed; "All unicorns are horned.
Therefore some unicorns are horned." is decided unsettled, with the
no-existential-import reading disclosed in the surface.
Mechanism (generate/proof_chain/exist.py): v5-VP's per-individual
lowering over a domain widened by one Skolem witness per existential
premise and — the load-bearing part — one ARBITRARY element per
existential conclusion, at which every universal still instantiates and
about which no premise asserts anything. Refuting an existential means
deriving a universal, and the arbitrary element is what makes that
derivation genuine instead of an assumption of domain closure: without
it "Rex is a wolf. Rex is not tame. Therefore some wolves are tame."
reads as REFUTED from a domain of one wolf. With it, UNKNOWN.
Four `en_exist_*` bands earned SERVE at 720x wrong=0 on the first arena
seal (25-band ledger); the 32-case hand-authored v2_exist split passed
32/32 first run; full lane 166/166 wrong=0 across six splits.
Also fixes a wrong-answer path this work uncovered in Band v1b
(ADR-0261 §5.1): `to_syllogism` FILTERED premises it could not express
out of the projection and answered from the remainder, so "Aristotle is
a philosopher. All philosophers are scholars. Therefore some scholars
are philosophers." lost its only witness and was served "that doesn't
follow". It now refuses — matching its propositional sibling
`to_deductive_logic`, which always has — and the argument falls through
to the bands that can hold a singular fact. The categorical band
re-earned SERVE 720/720 after the change; both examples are pinned as
regression cases.
Promotion: `ds-mem-0020` declined -> unknown (ADR-0258's existential
scope-out, now read; an anonymous witness never transfers to a named
individual, so UNKNOWN is its honest verdict, not entailed).
[Verification]: core test --suite deductive 232 passed; reader tests
33/33; register+surface tests 52 passed; arena 25 bands x 720 wrong=0
(all SERVE); lane 166/166 wrong=0; lane SHAs 9/10 match (public_demo
drift is pre-existing on the base commit and unrelated — evidence in
the Tier-S brief).
Generalization arc Phase 1.1 (Tier F, docs/plans/generalization-arc-2026-07-24.md):
"All philosophers teach. Socrates is a philosopher. Therefore Socrates
teaches." now reads and decides — the ADR-0258 §6.3 verb-predicate
scope-out, and the reading gate for Phase 2's subject serving.
generate/proof_chain/verb.py extends v3-MEM's per-individual lowering with
a second atom family in one shared space: membership atoms (v3-MEM's own
parsers reused verbatim) + verb atoms (individual, verb-lemma-group,
object-term). Verb universals instantiate as mem(i,C) -> [~]verb(i), so a
copula-minted membership fact discharges a verb rule. The ONE new semantic
identification is closed 3sg agreement (verb-specific irregular table +
the three regular suffix rules — deliberately NOT the noun table, which
would misroute "lives"/"leaves"). Scope-tight segmentation: single-token
name/class/verb/object shapes only; everything longer refuses typed
(tense_out_of_band joins the closed reason vocabulary). Rendered by
render_entailment_verb (member surface, UNKNOWN scoping extended to the
verb reading). Rides deduction_serving_enabled (default off); pure
widening — every previously-served argument byte-identical, no existing
lane case changed outcome.
Earned: 4 en_verb_* bands x 720 arena wrong=0 (first seal), ledger resealed
at 21 bands; corpus soundness asserted against the independent truth-table
oracle over each template's intended lowering (15,120 cases, INV-25).
Lane: v2_verb 28 hand-authored real cases 28/28 first run; full lane
134/134 wrong=0 across five splits. Surgical single-line pin update +
CLAIMS regen.
Stacked on feat/generalization-phase0 (merge that first — shared
verify_lane_shas.py / CLAIMS.md / generate_claims.py lane-ADR mapping).
[Verification]: verb reader tests 34/34; deduction battery 86/86
(surface +5, e2e +1, lane +1) in-worktree; arena seal + oracle
cross-check green; smoke + warmed_session via pre-push gate.
Plan of record for the generalization arc lands here
(docs/plans/generalization-arc-2026-07-24.md) with the risk-tiered
handoff pack (docs/handoff/generalization-2026-07/); Phase 0.1 executed.
Root-caused the three stale lane pins (docs/research/lane-drift-
investigation-2026-07-24.md):
- miner/curriculum: 5c69b741 (ADR-0244 §2.7) widened content digests
16→64 hex; intentional, pins were simply never updated. Re-pinned
surgically; SHAs triple-run deterministic.
- demo_composition: a REAL serving regression. #76's canonical-first
base precedence in resolve_surface, activated when #96 routed pipeline
turns through the resolver, served the pre-R6 canonical — the register
axis (ADR-0077 R6 substantive + ADR-0071 R4 decoration) was silently
stripped from every pipeline-served turn. T13's back-stamp then made
telemetry mirror the stripped bytes; register-tour claims went red and
the e2e tests pinning the axis (outside smoke) were failing on main.
Fix: response-first base precedence (served bytes = post-guard, post-R6,
post-R4) + new SurfaceResolution.hash_surface carrying the truth-path
canonical-precedence bytes through substrate overrides, folds, hedge,
logos-morph, and the speculative marker. compute_trace_hash folds
hash_surface, so the register axis varies the served surface ONLY and
trace_hash stays register-invariant (ADR-0069 inv C). _prior_surface
(correction binding) stays on the truth path. Dead FailureClass import
and _assessment_residual removed.
Also: generate_claims.py had raised on every run since the
deduction_serve_v1 pin landed without a _LANE_ADR entry — mapping added
(ADR-0256), CLAIMS.md regenerated, --check green.
[Verification]: register tour 6/6 claims (substantive-differs restored,
all_trace_hashes_identical held); 88 tests green in-worktree
(test_surface_resolution incl. 2 new fallback tests,
test_register_substantive_consumption incl. previously-red e2e tests,
test_grounded_open_hedge_arm, test_cognitive_turn_pipeline,
test_warmed_session_lane 10/10 T13 consistency); miner/curriculum lane
SHAs byte-identical across three runs; deduction_serve + deductive_logic
lane reports byte-identical to their unchanged pins; smoke suite via
pre-push gate.
Composes v2-EN's connective grammar (if/then, or, and, either) with
v3-MEM's singular-membership sentence reading over one shared
per-individual atom space, so "If Socrates is a man then Socrates is
mortal. Socrates is a man. Therefore Socrates is mortal." decides — the
exact gap ADR-0258 §6.1 reserved. Genuinely fuses the two mechanisms: a
bare universal's instantiated atom can unify (via the same closed
morphology relation) with a connective leaf's atom, deciding arguments
neither band alone could.
- generate/proof_chain/cond_member.py: new reader. A sentence is checked
for a connective token BEFORE the universal-lead check (so a stray
connective can never leak into an opaque name/class run); reuses v3-MEM's
own _parse_singular/_parse_universal verbatim (already negation-aware,
so no separate negation layer is needed, unlike v2-EN's opaque minter).
Four new shape-bands (fused/disjunctive/chain/conditional), each earning
its own SERVE license.
- chat/deduction_surface.py: new fallback tier tried strictly after v3-MEM
— pure widening, every previously-served argument stays byte-identical.
- evals/deduction_serve/practice/gold.py: 4 new synthetic template groups
(20 templates), reusing the v3-MEM case generator; intended formulas
cross-checked against the independent oracle (INV-25).
- evals/deduction_serve/v2_condmem/: 26 hand-authored real-English cases,
content-disjoint from the synthetic corpus.
- ds-mem-0024 promoted declined -> unknown (not entailed — the antecedent
is never asserted in that text; this is the first promotion in the
corpus that doesn't land on entailed).
- Ledger re-sealed to 17 bands; deduction_serve_v1 lane SHA re-pinned
(surgical single-line edit, not a blanket --update).
Verification: core test --suite deductive 160 passed (31 new reader
tests); practice arena 17 bands x 720 wrong=0, all SERVE-licensed; lane
splits v1 28/28, v2_en 26/26, v2_member 26/26, v2_condmem 26/26; smoke 180
passed; warmed_session 10 passed. Flag deduction_serving_enabled stays
default-off.
CORE now decides natural-English deductive arguments end-to-end:
'If it rains then the ground is wet. It rains. Therefore the ground
is wet.' -> ENTAILED, rendered over the user's own clauses. Both of
ADR-0256's documented boundary cases (multiword conditional, nested-
negation contraposition) are now decided; their lane gold updated
declined->entailed.
- generate/proof_chain/english.py (new): closed function-word grammar
over OPAQUE clause-atoms (minted ids); if/then, [either] or, and,
three negation forms (leading not / sentential / copular incl.
contractions); typed refusals; honesty caps. Soundness: positive
verdicts are substitution-closed; UNKNOWN is scoped + guarded
(quantifier-led, is-a membership, unnormalizable negation refuse
out of the opaque band).
- render_entailment_english: verdicts quoted back verbatim; UNKNOWN
phrasing explicitly scoped to the opaque reading.
- chat/deduction_surface.py: v2-EN fallback tier AFTER v1/v1b —
monotone widening, byte-identical on previously-served arguments.
- Four en_* shape-bands EARN SERVE via the ADR-0199 arena (720/band,
wrong=0, reliability 0.99087 >= 0.99); 9-band ledger re-sealed.
- evals/deduction_serve/v2_en: 26 hand-authored real-English cases
(content disjoint from the synthetic lexicon), wrong=0; report
re-pinned.
- realizer guard (ADR-0075): deduction surfaces exempt — quoted
templates are not slot-composed articulations (pack 'open'=VERB
was rejecting an honest quoted 'the door is not open'); pinned
cold+warm in e2e.
[Verification]: deductive 84 passed; smoke 180 passed; cognition 122
passed 1 skipped; arena 9x720 wrong=0 all SERVE; lanes v1 28/28,
v2_en 26/26.
Pins the arc's original goal against the ACTUAL serving spine (ChatRuntime,
the driver core chat constructs), not just the composer: a user asks a basic
logic question -> CORE decides -> an articulated, deterministic, telemetered
response comes back. Covers propositional + categorical, flag-off byte-identity
across both bands, honest reader-refusal on out-of-regime input (INV-34), and
the peripheral-systems wiring.
Peripheral systems verified (nothing to build, all automatic): deduction
turns are well-formed served turns (turn_log grows, _context.turn advances)
so ADR-0255 discovery-yield counts them; they carry grounding_source=
'deduction' on ChatResponse + TurnEvent and an observable DispatchAttempt;
they emit no discovery candidates (decided answer, not a would-have-grounded
gap) so they lift the yield denominator without inflating the numerator.
Scope-out doc (docs/research/deduction-serve-arc-completion-and-scope-outs-
2026-07-23.md) records the whole arc + everything deliberately deferred with
rationale: multi-word English reader relaxation (risky, shared reader, its
own pass), multi-step proof recap (Phase-6 core_logos), accrue_realized_
knowledge default, ADR-0246 s3.7 (blocked on research evidence not wiring),
Tier-2 (waits for field reader), Hamiltonian compiler (eval-side), and the
GroundingSource enum registration.
New: tests/test_deduction_serve_e2e.py (5 tests through the real REPL path).
[Verification]: core test --suite deductive 50 passed; smoke 180 passed;
cognition 122 passed/1 skipped.
core chat now decides Aristotelian syllogisms end-to-end -- the marquee
'basic logical work' widening. Closes Phase 0's fork: the ONLY categorical
decider was evals/syllogism/oracle.py (the sealed independence oracle serving
must not import; INV-25). This ships a production decider.
generate/proof_chain/categorical.py lowers a categorical argument to the
propositional regime (one Boolean atom per term-membership profile) and rides
the already-verified ROBDD engine -- no new decision procedure, soundness
inherits from the flagship. Sound AND complete for the modern/Boolean reading
(Darapti + existential-import-only forms correctly INVALID), with no reliance
on a lucky domain size. Independent of the eval oracle by mechanism (ROBDD-
over-profiles vs brute-force finite-model enumeration); proven by case-for-case
agreement with it across the whole syllogism gold lane.
New: generate/proof_chain/categorical.py, render_syllogism (deterministic
valid/invalid/inconsistent templates), CATEGORICAL shape-band, arena
categorical band (4 valid forms + 2 invalid, by-construction gold cross-checked
vs the independent syllogism oracle), tests/test_categorical_decider.py (8
tests incl. the independence-by-agreement proof).
Changed: chat/deduction_surface.py (categorical branch, license-gated via a
shared _license_gate helper; propositional path byte-identical), arena
DeductionSolver dual-path in lock-step with the composer, re-sealed ledger (5
bands, categorical earns SERVE at 0.99087 wrong=0), Phase-2 lane decide()
mirrors the dual path with the 3 categorical cases reclassified to their true
valid/invalid verdicts + 1 invalid case (n 27->28), report+SHA regenerated.
Honest wrinkles: irregular plurals ('fish') decline with unknown_morphology
(a reader limit, correctly not a wrong); surfaces read in singularized terms
('all whale are animal') -- the exact ids the engine reasoned over, no cosmetic
re-pluralization that could drift from the decision.
[Verification]: smoke 180 passed; cognition 122 passed/1 skipped; core test
--suite deductive 45 passed; test_categorical_decider 8 passed; practice
runner 5 bands all SERVE wrong=0; deduction_serve lane SHA regenerates to the
committed pin.
Deduction serving stops being a bare flag and becomes an EARNED capability
governed by the ADR-0175 reliability gate over the ADR-0199 learning arena
-- the arena's second concrete instance (GSM8K math is the first) and the
reliability substrate's first non-estimation serving consumer, a concrete
dent in that zone's standing 'designed, not wired' critique.
Non-circular thesis: the ROBDD engine is sound+complete (never wrong on the
problem it's handed), so the license does NOT certify it. It certifies the
full pipeline (reader -> projector -> engine) per argument shape -- the
FALLIBLE part is the template reader, which can misparse an argument and
hand the engine the wrong problem. The arena catches that as a 'wrong' by
comparing the pipeline's committed outcome to by-construction gold.
New: generate/proof_chain/shape.py (4 exhaustive structural shape-bands, the
capability axis, shared by arena + serving), evals/deduction_serve/practice/
(the deduction arena instance: deterministic synthetic corpus, by-construction
gold independent of the reader per ADR-0199 L-2, cross-checked against the
INDEPENDENT truth-table oracle before sealing), chat/data/deduction_serve_
ledger.json (committed SHA-sealed ledger, 4 bands x 720 correct/0 wrong),
chat/deduction_serve_license.py (tamper-evident serving reader, mirrors
estimation_license.py), tests/test_deduction_serve_license.py (13 tests),
docs/adr/ADR-0256 (+ resolves the ADR-0206 numbering collision in doc form).
Changed: chat/deduction_surface.py (composer consults the license: earned
band -> authoritative Phase-1 surface; unearned/stripped ledger -> same sound
answer served DISCLOSED/hedged -- authority now rests on committed evidence,
not a boolean), core/config.py (flag docstring: enables an EARNED path),
core/cli_test.py (deductive suite runs the license test).
All four structural bands earn SERVE at reliability 0.99087 (>= theta_SERVE
0.99) with wrong=0, so Phase-1 behavior is preserved byte-for-byte; the
gate's teeth are proven by injecting an empty ledger (the same sound answer
degrades to a disclosed hedge). Did NOT reuse the ADR-0206 govern_response
bridge (its STRICT/APPROXIMATE 'widen-past-strict' semantics are the opposite
shape from deduction's always-sound answer); did NOT rewrite report.json's
stale adr field (SHA-pinned bytes, documented in ADR-0256 s2a instead).
[Verification]: smoke 180 passed; cognition 122 passed/1 skipped;
core test --suite deductive 38 passed; architectural_invariants 75 passed;
practice runner 4 bands all SERVE wrong=0.
New evals/deduction_serve/ lane scores the PRODUCTION serving decider
(the exact comprehend -> to_deductive_logic -> evaluate_entailment_with_trace
pipeline chat/deduction_surface.py runs) end-to-end from raw text --
distinct from evals/deductive_logic (bare engine vs formula strings) and
evals/comprehension/propositional_runner.py (reader fidelity vs the
independent oracle, not the production engine). This is the only lane
proving the capability core chat actually serves.
27 hand-authored cases (gold computed by independent logical reasoning,
not copied from a first engine run), 4 classes: entailed/refuted/unknown/
declined. 27/27 correct, wrong=0. Wired into core test --suite deductive
(tests/test_deduction_serve_lane.py) and SHA-pinned in
scripts/verify_lane_shas.py (deduction_serve_v1).
Honesty check during authoring: a case intended as 'entailed'
(contraposition, 'Therefore if not q then not p') actually declined --
tracing why found a genuine reader-grammar boundary (negation cannot
nest inside an if/then clause; generate/meaning_graph/reader.py's _chunk
rejects it). Reclassified to declined/out_of_band_nested_negation
(documented in contract.md) rather than forcing an artificial pass, and
added a replacement entailed case (three-hop chain) to keep coverage.
Out-of-scope finding (documented, not fixed here): running
scripts/verify_lane_shas.py --update to compute this lane's pin also
re-executed every OTHER registered lane and surfaced two pre-existing,
unrelated problems on main -- miner_loop_closure/curriculum_loop_closure/
demo_composition regenerate non-deterministic content IDs (their
committed pins don't reproduce even on a clean checkout), and public_demo
errors outright (matches the known env-timeout flake in project memory).
Reverted all four lanes' results/*.json and PINNED_SHAS entries to their
original committed values -- this PR's only PINNED_SHAS change is the new
deduction_serve_v1 entry.
[Verification]: smoke 180 passed; cognition 122 passed/1 skipped;
core test --suite deductive 25 passed; evals.deduction_serve.runner
27/27 wrong=0; pinned SHA independently re-verified against the
committed report.json bytes.
Wires the verified ROBDD entailment engine (generate/proof_chain,
716/716 wrong=0) into core chat serving. A propositional argument
('P1. P2. ... Therefore C.') is now comprehended, projected, and
decided end-to-end -- the first real working basic-logic workflow:
user question -> CORE decides -> articulated deterministic answer.
New: chat/deduction_surface.py (DEDUCTION composer, sibling to the
existing oov/narrative/example composer pattern), generate/proof_chain/
render.py (deterministic EntailmentTrace -> surface templates, no LLM),
tests/test_deduction_surface.py (15 tests incl. full propositional gold
corpus decided end-to-end across a multi-turn session, wrong=0).
Changed: core/config.py (deduction_serving_enabled flag, default off),
generate/intent.py (IntentTag.DEDUCTION, observability-only -- the core
classify_intent rule table is untouched so flag-off stays byte-identical),
chat/runtime.py (one flag-gated block in the existing pack/teaching/
partial/oov dispatcher).
Design notes (full detail in docs/research/deduction-serve-arc-phase1-
turn-spine-2026-07-23.md): the deduction check runs BEFORE generic intent
classification (never touches classify_intent's rule table) and BEFORE
the empty-vault gate (a live multi-turn probe caught turn-12 silently
falling through once the vault warmed -- pack/teaching/oov are cold-start-
only by design, deduction serving isn't). grounding_source='deduction' is
NOT registered in the closed, Workbench-coupled GroundingSource Literal
(cross-stack TS contract, inert for this arc's core-chat-only consumer,
documented as a deferred follow-up). Band v1 stays propositional-only --
categorical/syllogism arguments commit but honestly decline as
out-of-band; evals.syllogism.oracle is never imported (INV-25 intact).
[Verification]: smoke 180 passed; cognition 122 passed/1 skipped;
test_deduction_surface.py 15 passed; test_dispatch_trace.py +
test_oov_surface.py 26 passed; test_intent*.py 145 passed.
2026-07-23 directive: instrument candidates-proposed-per-served-turn on
clean, post-reset traffic. Adds an additive-optional turn_count_baseline
manifest field (engine_state) stamping the turn_count at the last clean
reset of the discovery-candidate ledger, a pure compute_discovery_yield
function (teaching/discovery_yield.py), and a read-only `core teaching
discovery-yield` CLI surface. Fails closed (returns None / exits 1) when
no baseline has been stamped rather than fabricating a denominator.
scripts/ops/stamp_discovery_yield_baseline_20260723.py stamps the T11
reset epoch into the live store, mirroring reset_candidate_corpus_t11's
atomic generation-dir pattern. Dry-run verified against a throwaway copy
of the live store's gen-21703; not yet executed against the real store
(see PR body).
Weekly-audit 2026-07-22, T13 decision (2) ruling (Shay). The Shadow Coherence
Gate hard-refused open-geometry surfaces even when the surface was pack-grounded
and the openness was a geometric-coherence residual the pack never claimed to
certify — a false refusal (the warmed "What is doubt?" case:
"To doubt means to think maybe not. pack-grounded (en_core_meta_v1)." was
replaced by "I cannot certify an answer: ... contract is open (goldtether_residual).").
Fix the reading, not the question: route open-geometry-but-pack-grounded
surfaces to a hedge arm (authoritative=False), discriminated purely by grounding
provenance (structural), never by question type. The query-type classifier
bypass was rejected as a fail-open cue-table (ADR-0252 / INV-34).
- core/cognition/surface_resolution.py: the decision lives INSIDE the gate.
Predicate: pack_grounded (grounding_provenance in {pack, teaching}) AND every
open token in {versor_condition, goldtether_residual}. The ¬hazard clause is
enforced structurally by that residual allowlist — any unrecognized open token
(where a genuine safety/harm/imperative hazard lands), non-pack grounding, or a
None assessment falls through to the unchanged fail-closed refusal. New
authority tag grounded_open_hedge; hedged=True; walk/compose folds suppressed.
- core/cognition/pipeline.py: thread grounding provenance into resolve_surface
(read once at the gate seam, reused for the OOV telemetry).
- docs/adr/ADR-0254: the serving-physics decision + fail-closed argument.
- tests/test_grounded_open_hedge_arm.py: RED->GREEN unit suite + real-data
warmed-session integration.
Genuine safety/harm hazards are unaffected: they ride separate axes
(SafetyVerdict refusal, logos-morph override) that supersede this surface.
[Verification]: hedge suite 15 passed; warmed_session lane pin + architectural
invariants 85 passed (155.8s); GSM8K wrong=0 preserved (16 passed, 3 skipped);
smoke 180 passed (131.65s), exit 0. worktree core-wt-hedge @ base 19847f90.