* feat(determine): add transitive strict-order relational inference
Add sound transitive closure for declared strict-order relational predicates —
less_than, greater_than, before_event, after_event, and ONLY these. A query
p(a, c) determines True when a same-predicate chain p(a, b), p(b, c), ... over the
predicate's OWN realized edges is found (BFS reachability) and the proof_chain ROBDD
verifies the transitive entailment (search-then-verify, through the UNCHANGED
evaluate_entailment via a new lower_transitive_chain). Mirrors _determine_subsumption.
Scope / firewall:
- TRANSITIVE_PREDICATES is closed and default-off; sibling_of, parent_of,
left_of/right_of, inside_of, during_event, overlaps_event are EXCLUDED.
- Same-predicate edges only — no transitive-through-inverse, no cross-predicate
(triple firewall: recall filter + lowering re-reject + ROBDD re-verify).
- Open-world: asserts only answer=True via the shared _relational_determined
surface (rule="transitive"), so INV-30 stays at exactly 3 Determined sites and
no answer=False is added. One-hop inverse/symmetric (#775) is unchanged.
FrameVerdict / closed-world (ADR-0222) is untouched.
wrong=0 bite (unit + lane + independent oracle): non-transitive-predicate chains,
non-admitted spatial chains, mixed-predicate chains, disjoint chains, reflexive
cycles, and inverse+transitive composition all REFUSE.
Measurement: new evals/relational_transitive lane with an INDEPENDENT BFS
transitive-closure oracle (imports no engine module; INV-25/27), cross-checked both
directions (positives oracle-True, confusers oracle-False). Capability-index breadth
10 -> 11, wrong_total 0, deterministic digest re-frozen.
Cross-PR reconcile: migrated the one-hop confuser rinf-ref-001 (a less_than chain,
which now correctly determines transitively) to a B2 positive; repurposed its unit
test to a non-transitive parent_of chain; corrected a pre-existing stale breadth==9
assertion (#775 added a 10th domain but never updated test_capability_index).
Verified in the worktree: determine + transitive/one-hop lanes + capability
baseline/index + ProofWriter-OWA floor + INV-30/29/21/02 + full smoke — all green.
A 3-skeptic read-only adversarial review found no wrong=0 leak.
* test(determine): B2 hardening — TRANSITIVE_PREDICATES pin + lower_transitive defensive + budget
Add the required B2 hardening tests before merge:
- test_transitive_predicates_closed_and_excludes: the table is EXACTLY
{less_than, greater_than, before_event, after_event}, every member is in
RELATIONAL_PREDICATES, and every deliberately-excluded predicate (sibling_of,
spouse_of, parent_of, child_of, left_of, right_of, inside_of, during_event,
overlaps_event) is explicitly absent. (Closes the gap where relational.py's
comment claimed this test existed but it did not.)
- tests/test_composition_lower_transitive.py: direct defensive tests on
lower_transitive_chain — empty path, mislabeled (cross-predicate) edge, wrong
arity, non-contiguous path, and path-not-reaching-target all lower to None
(refuse); plus exact 2-hop and 3-hop theory pins.
- test_edge_budget_exhaustion_refuses: above _TRANSITIVE_EDGE_BUDGET the transitive
search declines (a safe coverage refusal), never proves.
* feat(eval): ProofWriter-OWA refusal-floor lane (B1) — independent oracle, measure-only
Mastery-v2 Step-3 Brief 1. Proves the open-world soundness floor: determine() never
asserts a query True when the open-world truth is Unknown or False. Hardens
"unknown != false" before the transitive-chain (B2) and closed-world (B4) work can
stress it.
- evals/proofwriter_owa/oracle.py: a SEPARATE minimal OWA label oracle (its own parser
+ reasoner + inverse/symmetric tables), importing NONE of determine, comprehend/
MeaningGraph realization, or production predicate-entailment helpers (INV-25/27 — the
gold producer is disjoint from the solver). Grammar: member/subset/is-a closure,
explicit negation (No X is a Y -> disjointness, the source of gold-False), and #775
inverse/symmetric ONE-HOP relational rules. No transitive relational chains.
- evals/proofwriter_owa/fixtures.jsonl: 19 hand-authored ProofWriter-OWA-style items
(9 True / 7 Unknown / 3 False), SHA-pinned. Gold computed by the oracle; the fixture's
hand-authored `expected` pins the oracle (so the oracle is itself verified).
- evals/proofwriter_owa/score.py: runs the production path (comprehend/comprehend_relational
-> realize -> determine) vs the oracle gold; wrong = determine asserted True on a non-True
gold.
- tests/test_proofwriter_owa_lane.py: SHA pin; oracle==expected; wrong==0; every
serving_support gold-True determines True (no coverage gaps); no answer=False (INV-30).
Result: 19 items -> 9 correct (all serving_support True), 10 refused (Unknown/False),
0 wrong. Measure-only: no engine code touched; deliberately NOT a capability-index domain
(a refusal floor has low coverage and would drag coverage_geomean). INV-30 green; smoke 99/99.
Note: no live ProofWriter dataset access in this environment, so items are hand-authored
in OWA style (provenance.md cites ProofWriter V2020.12.3 / arXiv:2012.13048 for semantics,
attribution only) — but the GOLD is oracle-computed and verified, not hand-asserted.
* harden(eval): OWA lane CLI fails on coverage_gaps too (review #779)
score.py::main() exited nonzero only on wrong>0; it now ALSO exits nonzero when
coverage_gaps is non-empty (serving-supported gold-True items that refused), matching
the acceptance rule the tests already enforce and the module docstring. Both failure
modes print to stderr; exit is 1 if either fires. Verified non-vacuously (injected gap
-> exit 1). Lane test 5/5 green.
* feat(determine): one-hop sound relational entailment — inverse/converse + symmetric
Mastery-v2 Step 3 lead capability (core). DETERMINE could perceive relational
structure (16 predicates, breadth-complete) but could not derive the simplest
entailed relational facts — it read only the stored direction. This adds two
SOUND one-hop rules that read a stored edge in its other lawful direction:
INVERSE/converse greater_than(a,b) <= told less_than(b,a)
SYMMETRIC sibling_of(b,a) <= told sibling_of(a,b)
Strictly scoped (per review): OPEN-WORLD (asserts only True, never False — the
answer=False path stays unbuilt, INV-30's firewall holds), ONE hop (NO transitive
chaining), DECLARED rules only. Ontology/metadata-driven, not prose intuition:
- generate/meaning_graph/relational.py: declarative algebra tables — _INVERSE_PAIRS
(the converse edges; the pack carries no inverse metadata) + INVERSE_OF (derived,
an involution) + SYMMETRIC_PREDICATES (mirrors the pack's graph.edge.symmetric tag).
load_relational_pack_symmetric() reads the pack ontology; a test pins the constant
equal to it (no silent divergence).
- generate/determine/determine.py: _relational_one_hop() between direct entailment
and transitive subsumption; new Determined.rule provenance field (direct/inverse/
symmetric/subsumption) — replay-safe (render reads only basis; trace hashes surface).
wrong=0 confuser block proves the rule cannot over-fire: less_than is not
self-inverse, sibling_of does not imply parent_of, greater_than does not imply
equal_to, NO transitive chain (direct or through-inverse), and no answer=False is
ever emitted. An obsolete pin (test_symmetric_converse_is_not_faked, which asserted
the pre-rule direct-only floor) is updated to the new sound behavior and a preserved
asymmetric-converse bite.
Tests: test_determine_relational_inference.py (13, the capability contract) +
updated test_relational_reader.py. Broad relational/determine/grounding +
capability_index baseline sweep: 114 green; baseline digest UNCHANGED (this is a
determination capability; the comprehension lanes are untouched).
REMAINING for the full lead PR (next): the measurement lane — an independent-oracle
evals/relational_inference lane + a capability_index adapter + baseline re-freeze
(breadth 9->10, wrong_total still 0), so the capability registers on the yardstick.
INTEGRATION NOTE (cross-PR): this adds 2 Determined() construction sites (now 4:
direct/inverse/symmetric/subsumption, ALL answer=True). When rebased onto main with
INV-30 (PR #770), update INV-30's test_determine_construction_sites_are_visible count
2 -> 4 and confirm all four assert True. Merge order: #770 -> this.
* feat(capability-index): relational-inference lane — breadth 9->10, wrong=0
Mastery-v2 Step 3 lead, measurement half. Puts the one-hop relational-inference
capability ON the capability index with an independent-oracle lane:
- evals/relational_inference/v1/: cases.jsonl (13 positive: 8 inverse + 5 symmetric,
authored from the relation algebra INDEPENDENTLY of determine/relational per
INV-25/27), refusals.jsonl (8 confusers that MUST refuse), provenance.md (incl. the
honest overlaps_event reader-surface coverage gap).
- evals/comprehension/relational_inference_runner.py: told fact(s) -> determine(query)
vs gold; refusal = coverage miss, disagreement = wrong (structurally 0 on positives).
- evals/capability_index/adapters.py: comprehension_relational_inference_result added
to ADAPTERS.
- baseline.json re-frozen: breadth 9 -> 10, capability_score 0.94403 -> 0.949483,
wrong_total still 0, digest deliberately changed (35dea2b2...).
- tests/test_relational_inference_lane.py: SHA-pinned gold, positive wrong=0 +
coverage>0, and the confuser wrong=0 BITE (every confuser must refuse).
Also reconciles INV-30 (now in main via #770): determine.py grew from 2 to 3
Determined() construction sites (direct, the shared relational one-hop constructor,
transitive subsumption) — ALL answer=True. test_determine_construction_sites_are_visible
updated 2 -> 3 (correcting the prior commit note's overcount of 4; inverse and symmetric
share the single _relational_determined constructor, so it is ONE new site).
Also fixes a .gitignore gap: ADR-0219 gen-dir checkpoints (engine_state/current,
engine_state/gen-*/) were not ignored (only the old flat engine_state/*.json patterns
were), so runtime checkpoints could be committed by accident.
Verification: new lane 13/0/0 coverage 1.0; capability_baseline digest matches the
re-freeze; INV-30 + architectural invariants green; smoke 99/99.
* docs+test: reconcile stale relational-inference docstrings; assert rule provenance (review #775)
Addresses two review-requested cleanups on #775:
1. Stale docstrings. generate/meaning_graph/relational.py said the symmetric converse
is "a sound-but-incomplete refusal at DETERMINE" and there is "NO transitive/
symmetric/rule inference"; generate/determine/determine.py said "symmetric-converse
questions are Undetermined", "no transitive/symmetric/rule inference", and "asserts
only answer=True on a direct hit". All false after the one-hop relational algebra
landed. Updated: the reader does direct-reading only; DETERMINE applies declared
one-hop inverse/converse + pack-declared symmetric (plus the existing member/subset
subsumption), still open-world / never answer=False; transitive relational closure,
negation, and closed-world falsehood remain out of scope.
2. Rule-provenance gold now meaningful. The relational_inference runner compared only
(answer, predicate, subject, object), ignoring res.rule though the gold carries
"rule": "inverse"/"symmetric". Added res.rule to the got/gold tuple so a "right
answer via the wrong rule path" can no longer pass silently. Lane still 13/0/0;
baseline counts (and digest) unchanged.
Verification: runner 13/0/0; lane + capability_baseline + unit + INV-30 green (22).
Removes code_revision from the engine-identity hash: engine_identity is now the
sha256 of the 5 ratified packs ONLY. The build revision is provenance (the
manifest's written_at_revision), not identity — so a behavior-neutral rebuild is
the SAME identity and the always-on daemon no longer flag-day strict-breaks on
every commit (the ADR-0220 defect).
Core:
- core/engine_identity.py: ratified_substrate/compute_engine_identity drop the
git_revision arg (packs-only); add compute_legacy_engine_identity (reproduces
the pre-split packs+rev hash, for migration verification), ENGINE_IDENTITY_SCHEME=2,
IdentityReconciliation enum, and reconcile_loaded_identity — the single source of
truth for the runtime guard AND the workbench reader.
- chat/runtime.py: the load guard reconciles via scheme. Current scheme -> direct
packs-only compare. Legacy (code_revision-folded) stamp -> a VERIFYING migration:
reconstruct the legacy hash from the persisted written_at_revision; a match proves
packs unchanged (warn + re-stamp, resume, no break) — a mismatch means the packs
genuinely changed (DIVERGED -> strict-refuse). Preserves 'distinct packs => refuse'.
- engine_state/save_manifest: stamp identity_scheme alongside engine_identity
(additive-optional; no schema_version bump).
- workbench/readers.py: continuity reader uses the same reconcile (no phantom break
on legacy checkpoints). cli.py break message reworded (packs, not 'build revision').
Callsite cleanup: drop the now-unused git_revision arg + imports across
always_on.py, evals/l10_always_on/runner.py, workbench/readers.py.
Tests:
- test_identity_provenance_split.py (new): 5 reconcile unit proofs + 3 runtime
integration proofs incl. the wrong=identity defense (legacy stamp of DIFFERENT
packs still strict-refuses) and the re-stamp migration.
- test_engine_identity.py: invert the code-revision test (rev no longer changes
identity); assert the substrate is packs-only.
- Restore 3 lineage tests silently red since ADR-0219 (flat-path _manifest helper
now resolves the gen-dir).
- Update remaining callsites/monkeypatches for the new signature.
Hygiene: .gitignore now covers the ADR-0219 gen-dir runtime files
(current, gen-*/, session_state.json, proposals.jsonl).
Verified: 59 identity/migration/lineage/workbench + 32 L10 + 76 invariants/cli +
34 smoke pass; serving lane SHAs unchanged (no derivation/reliability_gate touch).
Adds empirical coverage of the three ADR-0219 checkpoint sub-steps where
a process kill can occur: PARTIAL_GEN (gen dir exists but partially written),
FULL_GEN_BEFORE_SWAP (all four files written but current pointer not yet swapped),
and AFTER_SWAP (clean-commit control case, already covered by rec_a/rec_b).
For each of the two non-trivial cut-points the harness:
1. runs a probe soak to commit reboot_turn turns,
2. injects the corresponding orphan shape,
3. reads the on-disk manifest to confirm the loader follows `current` and reads
the committed generation (not the orphan),
4. runs two independent recovery soaks (each injecting the same orphan at the
reboot boundary) and verifies their post-reboot trace_hash tails converge,
5. checks versor_condition < 1e-6 throughout every recovery soak.
New predicate evaluate_p4_arbitrary_interruption (predicates.py) expresses
this as a PredicateOutcome with a CutPointEvidence carrier. It has:
- holds gate: recovered_turn_count == expected (orphan was ignored)
- holds gate: tail_hashes_a == tail_hashes_b (two recoveries converge)
- holds gate: all versor_conditions < ceiling (closure throughout)
- bites gate: tested by four mutation tests (wrong count, diverging tails,
vc violation, empty tails)
contract.md: all spec predicates P1-P5c are now covered (NOT_COVERED is empty).
Per CLAUDE.md schema-as-proof: every predicate gate is verified by a *_bites
mutation test — 9 tests total (3 injection unit, 2 real-soak holds, 4 bites)
all pass in 109s.
Adds the P5a recall-precision predicate that was listed as NOT_COVERED in
the L10 continuity lane. Closes the gap in the schema-as-proof discipline
(CLAUDE.md): every predicate must have both a *_holds test (real soak) and
a *_bites mutation test so a passing lane cannot silently miss the violation
it nominally catches.
Changes:
- runner.py: ProbeRecord dataclass + probe_at/verify_probes_at params on
run_soak(). Registers a field state (float32 bytes, matching vault's
_exact_index dtype) at a named turn and recalls it against the vault at a
later turn — including after a reboot, which is the cross-reboot claim.
- predicates.py: evaluate_p5a_recall_precision — fails if any ProbeRecord
has rank=None or rank>top_k, or if no cross-reboot probe was recorded.
- report.py: wires P5a into build_report(); probe registered at turn 1,
verified at reboot_turn+2 (intentionally before the vault's
null_project auto-reproject cycle at store_count=20, which would destroy
all CGA inner-product scores — documented as a real finding, deferred to
a follow-up increment); NOT_COVERED is now empty ().
- contract.md: P5a row in the predicate table + reprojection-boundary
scope note.
- test_l10_continuity.py: 4 tests — holds (real 6-turn soak across reboot)
+ 3 bites (rank=None, no cross-reboot probe, empty probe_records).
Key finding: vault.null_project() fires every vault_reproject_interval=20
stores and produces CGA-orthogonal versors (inner product → 0.0 with the
original), completely destroying both exact-match and ranked recall. This
is a long-horizon vault stability issue, recorded here rather than silently
avoided. The P5a probe window is constrained to the pre-reproject interval
to keep wrong=0 intact while documenting the gap.
Closes the cross-file checkpoint-atomicity gap in ADR-0156. The four
checkpoint files now live in a committed gen-NNNN/ directory; the single
atomic os.replace of a 'current' pointer file is the commit boundary.
A kill before the pointer swap leaves the prior committed generation
intact; a kill after commits the new generation. Unreferenced gen dirs
are ignored. ADR-0156's deferred parent-dir fsync is also closed.
Key changes:
- engine_state/__init__.py: begin_generation() + commit_generation() +
_resolve_dir() for all load_* methods. Flat-layout legacy checkpoints
migrate into gen-0000 on first begin_generation call. GC retains K=2
committed generations.
- chat/runtime.py: checkpoint_engine_state uses the two-phase commit;
finalize_turn_trace_hash no longer writes discovery_candidates outside
the generation sequence (the second unguarded write path is closed).
- evals/l10_continuity/runner.py: _inject_orphan_tmp updated to inject
the two orphan shapes of the generation model.
- tests/test_adr_0219_generation_checkpoint.py: 18 tests, one per
acceptance-gate bullet + biting mutation variant each.
L10 lane: all_gates_pass=true; versor_condition<1e-6 throughout.
Smoke: 95 passed. Runtime: 20 passed.
The always-on PROCESS shipped (run_continuous + daemon + CLI), but the claim "it holds
over uptime" was UNFALSIFIED: the existing evals/l10_continuity soak drives the TURN loop
and is disjoint from run_continuous; the daemon path was covered only by ≤5-beat unit
tests. This lane converts "built" → "proven over horizon" for the idle path.
evals/l10_always_on/ mirrors the l10_continuity predicate/mutation harness but drives the
IDLE heartbeat: it seeds a real continuous life (held self + a cognitive turn to excite the
field), runs N beats with NO user turn, and gates four falsifiable predicates, each with a
*_holds (real soak) AND a *_bites (mutation) test per CLAUDE.md schema-as-proof:
- H1 closure — every observed idle beat versor_condition < 1e-6 (held over uptime, READ
never repaired); bites on a breached beat AND on a vacuous no-field soak.
- H2 bounded idle — a no-work beat adds NOTHING to the vault (no idle leak — invisible at
5 beats, fatal at 100k); bites on idle vault growth.
- H3 convergence — a saturated idle life SETTLES and stays settled (no re-awakening), at
rest at the end, closure intact on the tail; bites on never-settling + on re-awakening.
- H4 reboot resume — a reboot mid-soak resumes the SAME life (strict identity guard passes,
pre-reboot DERIVED learning survives, post-reboot closure holds); bites on failed
resume + on lost learning.
Measured (5000 idle beats, reboot@2500, ~20s): ALL gates pass — versor_condition flat at
1.389e-07 (no drift, no repair), vault bounded at 6 (no idle leak), converged at beat 1
with a 4999-beat at-rest tail, reboot resumed the same life with learning intact. This is
the empirical resolution of the L10 riskiest-unknown FOR THE IDLE PATH (the closure-by-
construction ruling covered the field-transition walk; this covers indefinite idle uptime).
Honestly NOT covered (recorded in the report, no silent skip): the continuously-LEARNING
life's resource cost under a sustained new-fact stream (O(n²) snapshot; per-run lived_life)
— out of scope until an afferent/intake feed + incremental persistence exist (the next
frontier).
Run on demand: PYTHONPATH=. python -m evals.l10_always_on [n_beats] [reboot_beat]. Path-
scoped out of CI smoke (like l10_continuity); runs in post-merge full-pytest. Additive — a
new eval lane, no existing files touched. 12/12 lane tests + architectural invariants green.
Completes the Wave M B3.5 consolidation slice (b–e), built on #728.
B3.5-b — calibration as a first-class evidence subject (`calibration_class`,
address `calibration:<class_name>`): RightInspector projection + Evidence
Chain Rail semantics (serving-discipline evidence, not runtime truth).
B3.5-c / B4a — nullable `LeewayEvidence` read model threaded through turn,
replay, cognition-proposal, and math-proposal surfaces, with a shared
absence-honest card. B4 is gated correctly: the tuple exists in typed data but
no producer populates it, so the card renders absence (verified: no non-null
producer in workbench/core/chat).
B3.5-d/e — UI-UX-GUIDE.md, b4-leeway-feasibility-gate.md, phase-a-residue-ledger.md.
Practice artifact — earn-it-for-real (runner-reproducible). The committed
`report.json` (additive earns PROPOSE @0.861, 95/5/50) is now emitted by a
deterministic runner rather than copied from the queue. `propose_runner`
gains `regenerate_practice_artifacts()`, which runs ONE sealed `resolve_pooled`
practice pass and writes BOTH report.json (the per-class ledger the calibration
reader consumes) and ratification_queue.json — two projections of one ledger,
coherent by construction and byte-reproducible. `runner.main()` delegates to
it (lazy import, no cycle), so both entry points produce the identical pair.
This closes the gap where a hand-copied report.json agreed with the queue but
no runner produced it. `resolve_pooled` is the aggressive sealed PROPOSE-regime
scorer (proposal-only/HITL, unsafe for serving, legitimate for
attempt-and-eliminate); wrong=5 is the sealed-practice learning signal, NOT the
serving wrong=0. No serving/derivation/reliability_gate source touched; the
practice lane is not in the serving-frozen SHA gate.
Validated:
- python -m pytest tests/test_workbench_{calibration,journal,replay,schemas}.py -> 31 passed
- python -m pytest tests/ -k "workbench or propose or learning_arena or practice"
-> 190 passed (3 failing tests in test_adr_0175_phase2_practice_lane.py are
PRE-EXISTING reds on clean origin/main: stale 4/0/46 assertions on build_report,
which this change does not touch)
- report.json + ratification_queue.json: deterministic (run1==run2) and
reproduced byte-identically by both `python -m ...runner` and `...propose_runner`
- pnpm build green; 144 UI tests across calibration/leeway/evidence/replay/
doctrine-gates/routes-docs-drift all pass
ADR-0184 S4b — the replay/provenance equivalence boundary, the
precondition for any semantic-primary path. After #685 the in-repo
legacy path delegates to the semantic boundary, so old-vs-new is
vacuous; the durable reference is a pinned canonical trace artifact
(evals/gsm8k_math/equivalence/v1/, 937 problems) whose authority is
the #684/#685 cross-tree differentials (documented provenance chain).
- state/provenance.py: replay_is_faithful — the replay bridge's law as
a checkable structural property (no verify/pool import; covered by
the #685 authority scan).
- equivalence/trace.py: canonical traces (worlds, candidates, order,
multiplicity, classifications, resolutions) + authority_violations
re-deriving the pool commit law from trace content.
- scripts/verify_semantic_equivalence.py: check / --update gate
mirroring verify_lane_shas.py.
- tests/test_adr_0184_s4b_replay_equivalence.py: full-corpus live-vs-
pinned equality, corpus-wide authority + faithfulness, and 17
single-mutation non-vacuity proofs.
Pure additions; verify.py/pool.py untouched; no serving, CLAIMS,
metrics, or lane pins moved.
Adds bound_slots_digest as a distinct VerificationProof obligation before any serving wiring. Keeps the VERIFIED lane off-serving while separating derivation, stated-slot binding, and back-substitution.
The first consumer of the P1-A VERIFIED contract. Given an R2 problem (prose) +
the INDEPENDENTLY hand-authored gold SETUP, it builds a VerificationProof and runs it
through the contract. NOT a serving-time VERIFIED producer: it depends on the eval-lane
gold setup (off-serving only), cannot move any serving metric, cannot emit served
[verified]. Serving-time independence (a non-gold second reader) is a separate, larger
rung, explicitly deferred.
evals/constraint_oracle/verified_producer.py (placed in evals/, not core/, to avoid a
backwards core->evals import: it needs the reader+solver (generate), the canonical
signature + gold (evals), and the contract (core) — clean direction evals->core+generate):
- verify_r2(text, gold_setup) -> R2VerificationOutcome.
- Two independent reads: primary = read_constraint_problem(text) (lineage
constraint_reader_v1); independent = the gold-authored setup (lineage
constraint_gold_author_v1). Convergence = equal constraint_setup_signature digests
(the SAME function for both reads). The gold ANSWER never enters — only the gold
STRUCTURE, through the signature.
- A wrong reader structure diverges from the gold-authored structure -> reads_disagree
-> NOT_VERIFIED (the poison). Reader refusal -> no proof + a limitation. Solver
boundary -> boundary_clear False -> NOT_VERIFIED. VERIFIED state/claim reached ONLY
via disclosure_for_verification.
On the real r2_gold lane (13 records): VERIFIED=7 (exactly the 7 'solved'),
NOT_VERIFIED=6 (3 solver_refuses + 3 reader_refuses) — wrong=0.
11 tests: integration over real gold (verifies >=1, never on divergence); all 7 solved
verify; the poison (divergent independent read -> reads_disagree); reader-refusal -> no
proof + limitation; solver-boundary -> not verified; distinct/named lineages; the
sanctioned route; all digests populated; gold answer structurally cannot enter; off-serving.
NOTE (bound_slots_digest): the P1-A VerificationProof (merged, unchanged per scope) has
no bound_slots_digest field; the "bound to stated quantities" obligation is carried by
derivation_digest (hashed over the canonical constraints + solution). Flag to extend.
Off-serving; smoke 90/0.
The mandatory lookback before CMB-d (3+ PRs on one surface; next-phase boundary). A 5-dimension
read-only fan-out audit over the 10-item checklist found the CMB-a/b/c substrate composes cleanly:
25 solid / 4 drift / 8 gap / 1 reader hazard. Doc:
docs/analysis/cmb-lookback-review-2026-06-08.md.
Fixes the one real hazard (H3): a query attributing the answer to a SINGLE agent ('how many words
does Alice type' with a distractor second rate) wrongly claimed the substantive
combine_mode_ambiguous — a hygiene over-claim that would corrupt the shared router in CMB-d. The
combined-query gate now excludes single-agent 'does <Agent>' attribution (_SINGLE_AGENT_QUERY) and
steps aside as not_combined_rate_shaped; the genuinely-combined forms ('do they' / 'are produced' /
'does it') still yield combine_mode_ambiguous. Pinned by gold cmb-16 + a reader test.
The review also resolves the CMB-d failure-family classification (input_shape:
not_combined_rate_shaped; must_remain_refused incl. rate_unit_mismatch — DECISIVELY, the reader has
no dimension representation so it cannot tell convertible from incompatible units; proposal_allowed:
combine_mode_ambiguous, missing_second_rate) and enumerates the live CMB-d registry preconditions
(not_combined_rate_shaped mapping, rate_unit_mismatch/non_integer_solution string collisions,
non_positive_net_rate family, Organ/ALL_REASONS extension). Provenance false-alarm (stale local main
checkout) noted + verified against origin/main.
gold 19/19 (6/5/8); reader 11/0/0 + 8 refused-correct; solver 6/0 + 5/0; 0 hygiene breaches; 123
tests; serving + R1/R2/R3 unchanged.
Third rung of the CMB ladder. Reads explicit combined-rate prose into a canonical
CombinedRateProblem or a closed reader refusal; graded by the CMB-a ruler + CMB-b solver via a new
run_reader lane. Off-serving; no router/contemplation/serving/ledger. Claim: 'English prose reads
into a canonical CMB setup or a closed reader refusal.'
Enforces the CMB-a 2x2 domain-entry grid + the deferral taxonomy (rate_unit_mismatch,
combine_mode_ambiguous, missing_second_rate, three_or_more_rates, reciprocal_work_rate_deferred,
clock_interval_deferred, not_combined_rate_shaped). Non-positive-net / non-integer stay the SOLVER's
refusals (the reader parses them). Router-organ hygiene: foreign R1/R2/R3 text steps aside as
not_combined_rate_shaped (the input_shape family), never a substantive boundary (0 breaches on all
36 R1/R2/R3 gold).
THREE adversarial verification rounds found 11 real wrong=0/hygiene hazards a curated-gold lane
cannot surface; all fixed + regression-tested. Root-cause fixes (not point-patches):
- cues are whole-word; difference = a CLEAN fill-vs-drain OPPOSITION classified per rate clause by
the verb in its own lead-in; rate_a=fill/rate_b=drain BY ROLE (a drain listed first still
subtracts); incidental drain nouns / drain-verbs-between-clauses no longer flip the mode;
- query duration + time-query quantity are read only from AFTER the 'how many' question, so neither
a premise number nor a transitional time marker is mistaken for them;
- the rate regex rejects decimals; substantive refusals are gated behind a real combination cue;
combine_mode_ambiguous requires a genuine COMBINED query (a compared pair steps aside);
- sequential segments (each rate carrying its own duration, tolerant of 'also for') step aside.
reader lane 11/0/0 + 7 refused-correct; gold 18/18; 19+17+3 adversarial triggers across 3 rounds;
126 tests (CMB + router/hygiene + R1/R2/R3 readers); serving + siblings unchanged.
Next (ladder): CMB-d router/contemplation wiring -> CMB-e ledger.
solve_combined_rate(CombinedRateProblem) -> int | Refusal over effective_rate = rate_a +/- rate_b.
effective_rate query returns the net (even <=0); quantity/time queries need a positive net rate
(non_positive_net_rate) and exact division (non_integer_solution). Pure integer arithmetic, no
float/Fraction (CMB v1 crosses no units). Off-serving. Graded by a new run_solver lane in
evals/combined_rate_oracle against the committed gold.
Adversarial 5-lens(+adjudicator) verification returned fix_first; both real hazards fixed:
- wrong=0 breach: negative known-slot inputs (time<0/quantity<0) bypassed the eff<=0 guard and
produced negative answers. FIXED UPSTREAM in model.py __post_init__ — rate_a/rate_b and the known
time/quantity are now positive ints, so the illegal state is unrepresentable and the solver can
never receive a negative-yielding path. Added model + gold (cmb-07d eff<0/time) + lane coverage.
- doc overclaim: the solver lane does NOT grade 'two independent paths' (both solver and the
oracle's _canonical_outcome delegate to model.effective_rate). Corrected both docstrings to name
the true anchor (committed gold + inline-computed literal tests) and added a difference-mode
inline-computed test.
R3-vac came back SOLID (separate PR). gold 18/18 (6/5/7); solver lane 6/0 + 5/0; 36 CMB tests;
router-hygiene + serving unchanged.
Closes the sibling vacuity CMB-a surfaced: R3's evals/rate_oracle solver_refuses branch
accepted any fixture labelled non_integer_solution without checking the arithmetic, and the
solved branch never recomputed the gold. A curator could certify an arithmetically-impossible
refusal or a wrong gold (with a self-consistent answer key) as valid — the 'obligation that
can't meaningfully fail = decoration' case CLAUDE.md forbids.
Adds _canonical_outcome (independent reimplementation of the single-rate algebra WITH R3.2
conversion — NOT a call to generate.rate_comprehension.solver, which would be circular). The
validator now cross-checks every solved gold (== computed) and every solver_refuses reason
(genuine) against it, with dedicated meaningful-fail tests anchored on hand-written literals.
Also removes the dead determinism self-comparison.
R3 gold stays 13/13 valid (7/2/4); reader 9/0/4 unchanged; 26 oracle/reader tests pass.
Off-serving; serving + other organs untouched.
First rung of the combined-rate ladder, off-serving (imports no generate.derivation /
core.reliability_gate). Claims exactly: the combined-rate setup ruler is defined and
gold-valid. No reader/solver/wiring yet — no capability claim.
- generate/combined_rate_comprehension/: local RateUnit (decoupled from R3), CombinedRateProblem
(two-explicit-rates + per-query-slot guard; effective_rate = rate_a +/- rate_b; non-positive net
left to the solver, not the model).
- evals/combined_rate_oracle/: span-free signature (sum commutative / difference ordered) + a
NON-VACUOUS validator. 17 gold fixtures: 6 solved (full combine_mode x query grid), 4
solver_refuses, 7 reader_refuses (the complete refusal taxonomy + the 2x2 domain-entry grid).
Adversarial 5-lens verification returned fix_first; the validator now cross-checks every solved
gold and solver_refuses reason against the canonical arithmetic (_canonical_outcome) so a
mislabelled / arithmetically-impossible fixture is rejected (meaningful-fail per CLAUDE.md), with
dedicated tests. Added eff<0 and eff=0/time coverage fixtures; removed a dead determinism guard.
gold 17/17 valid; 25 oracle tests; R1/R2/R3 + router-hygiene + serving all unchanged.
Doc: docs/analysis/cmb-a-combined-rate-ruler-2026-06-08.md
Option A (text-faithful): RateProblem gains time_unit (the duration's ORIGINAL unit; time stays int; defaults to the rate denominator). The SOLVER converts via convert_time -> exact Fraction -> int-or-refuse(non_integer); Fraction never leaves the solver, no floats. The reader accepts a convertible (minute<->hour) duration mismatch (keeps original time_unit); a non-convertible one still refuses rate_unit_mismatch. Signature includes time_unit ('30 minutes' != '30 hours').
Gold: r3-09 flips reader_refuses -> SOLVED (60 mph for 30 min = 30 mile, exact 1/2 hour; distractor 1800 rejected); new r3-13 non-convertible (3 gallons) stays rate_unit_mismatch. R3 gold 13/13; reader 9/0/0 -> answers 7/0/6 (setup_correct 8->9). Failure-family: rate_unit_mismatch now non-convertible-only -> still unsupported_rate_duration proposal surface; router-hygiene invariant stays green; R1 7/0/3, R2 10/0/0 unchanged. 119-test smoke green incl. invariants. Off-serving, no float path.
evals/constraint_oracle: span-free canonical setup signature (unknowns/facts/constraints/query; terms merged+sorted, lhs constant folded into rhs), a 13-fixture gold (7 solved / 3 solver_refuses / 3 reader_refuses) with a closed expect+refusal taxonomy, and a gold-validation runner. No reader yet — this proves the ruler is coherent (13/13 valid), not capability.
ADR-0211 ratifies the R2 finite-integer two-category constraint compiler: the IR, the canonical signature, the closed taxonomy, the four wrong=0 gates (setup oracle / reader refusal / exact solver / answer-choice contradiction flag), off-serving disjointness, and the C1-C9 ladder. Cross-refs ADR-0207/0175/0083/0055-0057; reuses the R1 setup_oracle pattern. Off-serving (no generate.derivation / reliability_gate). 13 oracle tests incl. per-branch meaningful-fail.
r1-07 now reads setup-correct and answers 6 — 'Nia has 9 more beads than Omar. Nia has 15. -> Omar = 6'. The reader binds the unknown base's unit FROM the relation when its subject is a known fact and its referent is the otherwise-ungrounded query target, so the equation is admissible; the answer oracle reverse-solves it (PR-7a). Bounded: single base == query target (no chains), known subject value, base not otherwise grounded, <=1 inverse (multiple_inverse_bases else), never over times/divide.
R1 setup 6/0/4 -> 7/0/3; R1 answers -> 7 correct / 0 wrong; 15-case 15/0/0; setup_wrong stays 0. Off-serving. Refreshes the R1 ledger to 7/0/3 (R1 closed; the 3 remaining refusals are wrong=0 boundaries).
Pin the EXACT reverse-solve semantics in the independent answer oracle before the
reader learns the inverse frame (PR-7b). A single more_than/fewer_than whose entity
is grounded and whose ref is the (otherwise ungrounded) query target inverts to the
base: base = entity -/+ delta (always integer-exact for +/-).
Refuses everything wider (each a meaningful-fail oracle test): more than one inverse
constraint, an inverse base that is not the query target (a chain), an already-grounded
base, a negative count result, or an inverse over times/divide. The duplicate-definition
guard moved into the per-kind branches so a grounded entity on a more/fewer_than is read
as an inverse constraint, not a redefinition; forward more/fewer/times/divide/sum paths
are unchanged (regression-guarded).
Contract only — the reader is NOT touched: r1-07 still refuses (admissibility_refused),
so R1 stays 6 / 0 / 4 and 15-case 15 / 0 / 0. r1-07 gold drops the non-consumed ask_base
(aligning with the reader's future projection) and gains gold=6, dormant until PR-7b.
Off-serving (only evals/tests import the oracle). SHA gate 8/9 (public_demo env flake).
Widen the aggregate-query recognizer so a multi-part total may be asked with a
trailing qualifier after 'have': 'How many X do A and B have altogether?' and
'... in total?'. The qualifier is stripped and honored ONLY for the multi-part
(sumquery) form — a single-entity query carrying it refuses, guarded by
'not aggregate'.
Phrasing-only: no new arithmetic, no new relation kind, no inverse solving, no
distractor/pronoun handling. The parts still flow through the existing sum_of;
an ungrounded part (unit unbound) or a unit-incompatible part (unit mismatch) is
refused downstream by the REAL admissibility check, so the recognizer cannot
over-read. Off-serving organ only (no generate.derivation / reliability_gate).
Flips r1-03 (more+altogether -> 25) and r1-04 (fewer+in total -> 34):
R1 setup 6 / 0 / 4 R1 answers 6 / 0 / 4 (setup_wrong 0, gold_error 0)
15-case 15 / 0 / 0 29 quant tests, 102 affected-file tests green
Tests are meaningful-fail: the single-entity-qualifier, ungrounded-part, and
unit-incompatible-part refusals each fail loudly if their guard is removed.
PR-6d adds the partition frame: combine all parts into a total, then split that
total equally into N containers. r1-06-subtotal-reused moves refused → correct —
the FIRST case where the divisor applies to a DERIVED symbol (the total), not a
directly given fact. That is real progress toward GSM8K setup comprehension,
where intermediate quantities are the norm.
Scope (kept narrow on purpose):
No new relation kind.
No new arithmetic operation.
No rational support.
No rounding/flooring.
No serving path touched.
The frame reuses the already-ratified pieces — SumOf(parts) + Div(Symbol(total),
Literal(N)) → divide_by — so this PR is reader-only (no IR / admissibility /
oracle / signature change).
Frame grammar:
"They combine their <unit> and split them equally into N <containers>."
+ "How many <unit> are in each <container>?"
-> total = sum(all facts); per_<container> = total / N; ask per_<container>.
wrong=0 boundaries:
- Exact-divisibility still gates the ANSWER, now over a derived total: 5+6=11,
11/3 is non-exact -> the setup reads correctly but the answer REFUSES (never
floors). Setup comprehension and answer exactness are cleanly separated.
- Partition/query coherence: a partition is read ONLY together with its
"in each <container>" query (and vice versa); container mismatch (box vs jar)
refuses. Prevents over-reading a story detail into an unused derived value.
Meaningful-fail verified: disabling the guard makes a dangling partition
wrongly comprehend.
Gates:
R1 setup: 4 correct / 0 wrong / 6 refused
R1 answers: 4 correct / 0 wrong / 6 refused / setup_wrong 0 / gold_error 0
15-case setup: 15 / 0 / 0
97 PR-6d tests + 99 relational/invariant tests green. Reader is off-serving
(no generate.derivation / core.reliability_gate import).
PR-6c adds the divisive comparative frame: "half as many" read as EXACT INTEGER
DIVISION. It is the divisor twin of PR-5c's multiplicative frame, and moves the
independent R1 gold's r1-02-half from refused → correct.
No serving path touched. No rational/fractional answer support added. Non-exact
division refuses.
Design (ADR-0134 amended — divide made symmetric with multiply):
- `_check_divide` now admits a SINGLE-DEP divide-by-dimensionless-literal
(item / dimensionless = item), the exact twin of single-dep multiply. The
2-dep rate-divide path is untouched. This keeps the IR's "literal operands
are not deps" invariant (proven in PR-6a) uniform across Mul AND Div, so the
reader builds both without a per-op special case and WITHOUT synthesizing a
divisor symbol that would pollute the setup-oracle's unit signature.
- `Div(Symbol, Literal)` IR node: "ref / divisor", operation_kind "divide",
projects to `divide_by`. Divisor-only contract mirrors the scalar-only one.
- Reader: `_DIVISOR_WORDS={half:2}` slots into the same 8-token "<WORD> as many"
template as the factor words; graph carries only the two entities.
- Gold reconciliation: r1-02 placeholder `times_as_many factor 0.5` → exact
`divide_by divisor 2` (gold 4). Makes the INDEPENDENT gold integer-faithful.
The wrong=0 boundary — exact divisibility:
the oracle admits `divide_by` only when `base % divisor == 0`. An odd base
halved REFUSES (gold_error), never floors to a wrong integer. Divisor must be
a nonzero int (0, 0.5, 1.5, bool all refuse); divisor=1 is intentionally the
identity (pinned). admissibility proves DIMENSION; the oracle proves EXACT VALUE.
Meaningful-fail (CLAUDE.md Schema-Defined Proof Obligations), both verified red:
- drop the `% divisor` guard → test_oracle_refuses_non_exact_division fails (returns 3).
- disable the single-dep divide branch → the admissibility test AND the reader's
`half` test fail (admissibility refuses → reader refuses → half stays refused).
Gates:
R1 setup: 3 correct / 0 wrong / 7 refused
R1 answers: 3 correct / 0 wrong / 7 refused / setup_wrong 0 / gold_error 0
15-case setup: 15 / 0 / 0
91 PR-6c tests + 60 relational lanes + 56 architectural invariants + 502
binding-graph/proof-chain/adapter tests green. All 8 SHA-content lanes match
(serving unmoved; admissibility has no generate.derivation/reliability_gate consumer).
The first capability slice on the R1 arc, gated by the setup-oracle: turn the
"twice / N times as many" reading from REFUSED into a correct setup, without a single
misread. Builds on the typed IR (PR-4) and the R1 gold (PR-5b).
- IR: a Mul(symbol, literal-factor) node — to_canonical_string "ref * factor",
operation_kind "multiply", dependencies {ref}, to_relation -> times_as_many. The
product keeps the symbol's unit (count * scalar = count), admitted by the REAL
check_admissibility multiply path (the literal factor is dimensionless, not a dep).
- Reader: a multiplicative template "Y has <factor> as many <unit> as X" (factor word:
twice/double/triple/quadruple) and "Y has <N> times as many <unit> as X", checked
BEFORE the digit gate (the factor may be a word). 'half' (a /2) is deliberately
deferred — divide-by-literal is a separate admissibility path.
- setup-oracle: relation_signature now canonicalizes times_as_many.
Setup-oracle R1 result: 2 setup_correct (r1-01 twice; r1-05 the multi-step chain
ivy/jon=3*ivy/kim=jon+2), 0 setup_WRONG, 8 setup_refused. Every hard negative stays a
safe refusal: missing-base (Rosa ungrounded), ambiguous referent, distractor, inverse,
partition, 'altogether'/'in total' phrasings, and 'half' (divide). wrong=0 held through
the first capability addition.
Gates green: setup-oracle R1 setup_wrong=0; 15-case setup gate 15/15 setup_wrong=0;
relational_metric answer lane 15/15 wrong=0; binding-graph admissibility + realize +
architectural invariants + chat-runtime + pipeline (122+). No serving path touched (this
reader feeds the relational_metric / setup-oracle lanes, not the candidate-graph serving).
Pure-gold. Defines what "correct" means for the R1 (comparative / derived-symbol /
multi-step) shapes the reader cannot read yet, and verifies the current reader REFUSES
them rather than misreading them. NO frame extraction, no parser change, no serving.
- evals/setup_oracle/r1_gold.jsonl — 10 INDEPENDENT, self-contained fixtures (id, text,
relations, expected_units, query, notes) covering: twice/half-as-many (multiplicative),
more/fewer-than -> total, a multi-step derived chain (jon=3*ivy; kim=jon+2), a derived
subtotal reused downstream (total=lee+mae; per_box=total/3), an inverse target, and the
hard negatives (ambiguous referent, missing base quantity, distractor quantity). The
gold is the authority — each fixture is reviewable without joining files.
- run_r1() scores the CURRENT reader against this gold.
Investigative result (the deliverable): **10/10 setup_refused, setup_wrong=0, setup_correct=0.**
The reader refuses every R1 shape with a TYPED reason (non_digit_quantity /
no_quantity_template / unreadable_quantity_clause / admissibility_refused) — it NEVER
misreads an R1 case as a simpler form. So the wrong=0 posture holds all the way down, and
PR-5c (the R1 frame) starts from a clean foundation: every R1 case is currently a safe
refusal, and the frame's job is refusal -> correct, with the oracle ensuring no
setup_wrong is ever introduced.
`python -m evals.setup_oracle r1` prints the auditable R1 report (exit 0 iff
setup_wrong==0). The 15-case setup gate + relational_metric answer lane are untouched.
Strengthen the ruler before it judges real GSM8K frames — NOT a capability change, no
new parser behavior, no serving path. The setup-oracle now grades UNITS, read from the
binding-graph itself, against an independent expected-unit gold.
- New per-symbol unit axis (symbol_unit_signature) — read from the GRAPH's symbols
(reader_symbol_units), not the answer projection; covers fact + relation-operand units
(operands are symbols). A None unit canonicalizes to "unset" so an unmodelled symbol
never silently matches a declared gold unit.
- The question-target signature gains the target's expected_unit (reader from
BoundUnknown.expected_unit; gold from the fixture).
- expected_units.json — independent, hand-authored MODELING unit per entity for the 15
cases (a discrete sortal count -> the generic count unit "item"; money -> "dollars").
Authored from the PROBLEM, not copied from the reader.
- The reader's per-symbol units match the independent gold on all 15 (setup_wrong=0) —
agreement, not circularity (both apply the same correct count->item / money modelling).
- Meaningful-fail: a reading whose STRUCTURE matches but whose UNIT diverges now FAILS
(test_unit_mismatch_is_caught, test_target_unit_mismatch_is_caught). The ruler is not
decoration on the unit axis either.
Gate held: setup-oracle 15/15 setup_wrong=0 (now structure + units + target). The
relational_metric answer lane is untouched (oracle-only change). No generate/ change.
This is the unit-aware ruler PR-5b (independent R1 gold) and PR-5c (the first R1
comparative/derived-symbol frame) must pass before serving.
Two coupled, additive, off-serving changes toward the typed math-comprehension organ.
No serving path touched; the relational_metric answer lane stays 15/15 wrong=0.
PR-1 — QuantQuery → BoundUnknown. comprehend_quantitative now emits the question
target as a BoundUnknown INSIDE the binding-graph (symbol_id, state_index="terminal",
question_form "count"|"total", expected_unit), so the graph is a real question-bearing
mathematical object and its canonical serialization carries the target. The external
QuantQuery is RETAINED, consistent-by-construction, so the two consumers
(to_relational_metric, realize/quantitative) are byte-identical; a follow-up rewires
them onto graph.unknowns and drops the duplicate field.
Setup-oracle lane (evals/setup_oracle) — grade the READING, not the answer. The
relational_metric lane scores answers, which can bless a semantically-wrong derivation
that coincidentally lands on the right number (the exact hazard the held-out
measurements + the 2/87 resolve_pooled probe exposed). The setup-oracle compares the
reader's comprehended STRUCTURE — a span-free signature of facts + typed equations +
the BoundUnknown target — against the INDEPENDENT gold structure (the relational_metric
cases' own relations/query, authored separately from the binding-graph reader). A
structural mismatch is setup_wrong, the wrong=0-critical count, even when the answer
would be right. v1 grades structure (units deferred — covered by admissibility). The
reader reads all 15 cases with the gold structure (setup_wrong=0); a meaningful-fail
test proves the oracle catches a right-answer/wrong-structure reading (it is not
decoration). `python -m evals.setup_oracle` exits nonzero iff setup_wrong > 0.
This is the measurement rig BEFORE investing in frame families: setup_wrong=0 is the
gate; serving must not move while setup_wrong > 0. It is the first milestone of the
math-comprehension organ, not a path to "solve GSM8K".
Verified: setup-oracle 15/15 setup_correct wrong=0; quantitative + setup-oracle unit
tests (17); realize-binding-graph + binding-graph + architectural invariants (183).
The final AGI-spine step (A INSTRUMENT → B WIRE → C DEEPEN → D CLOSE → E ESTIMATION).
The engine may now SERVE a DISCLOSED estimate for a query it would otherwise refuse —
but only for a predicate-class that has measured itself reliable, and never as fact.
This executes the ADR-0206 §5 cognition-path widening: the bridge's LICENSE node
(reliability_gate.license_for), previously "built — not yet called from serving", is now
called. govern_response returns APPROXIMATE iff a genuine licensed Action.SERVE
LicenseDecision is passed (STRICT for every other input — so every existing serving call
site is byte-identical); shape_surface DISCLOSES the estimate as "[approximate] …".
Mechanism:
- generate/determine/estimate.py — a BLIND converse-guesser: told p(a,b), asked p(b,a),
it commits the converse. It never reads the pack's symmetry metadata; whether the guess
is right is MEASURED, not assumed.
- evals/determination_estimation/ — the gold lane: run_practice (sealed, ADR-0199) folds
the converse-guesser over symmetric (sibling_of) vs directed (parent_of) cases, scored
against the pack's graph.edge.symmetric truth (gold independent of the solver). The gate
DISCRIMINATES: sibling_of earns SERVE (660 correct → Wilson floor 0.990046 ≥ θ_SERVE),
parent_of does not (660 wrong → 0.0). The license is earned by VOLUME — 657 perfect
commits is the exact θ_SERVE=0.99 threshold (656 is below).
- generate/determine/data/estimation_ledger.json — the ratified committed ledger,
hash-verified on load (a hand-edited ledger raises RatifiedLedgerError); it IS the
deterministic sealed-practice output (a GSM8K-style --check test pins this).
- chat/runtime.py — when a converse query is refused and the class holds a SERVE license,
the disclosed estimate is surfaced through the bridge (gated by config.estimation_enabled,
default OFF; only meaningful with accrue_realized_knowledge).
Invariants:
- wrong=0 by construction — an estimate is ALWAYS disclosed ([approximate]), never a silent
commit (UNVERIFIED_POSSIBLE is never in APPROXIMATE's admissible set), and only a genuine
ratified license widens (a forged {"licensed":True} dict / a PROPOSE license / an
unlicensed SERVE all stay STRICT). Defense-in-depth: type-gate ∧ admissible-set ∧
hardcoded disclosed state.
- never self-authored — ceilings stay at safe defaults (θ_SERVE=0.99); the engine cannot
raise its own bar. The ledger is sealed practice, hash-verified.
- session/serving only — no corpus/pack/identity/proposal/vault mutation; the HITL teaching
path is untouched. Deterministic; no clock/random.
- byte-identical for every non-E turn (the 2643 govern_response call passes no license).
Out of scope (separate ADR-0206 §5 PRs): the math-serving seam (select_self_verified,
touches the sealed metric), SITUATE (stakes), and the live FEED-BACK loop.
Verified green: smoke (90), architectural invariants (56), response_governance (321,
incl. the new license-gated widening test), the determination-estimation lane (12), and
the B/D/determine regression net. Four-lens adversarial review (disclosure/wrong=0,
calibration integrity, byte-identity, boundary/determinism): all held. Design:
docs/analysis/E-estimation-design-2026-06-06.md.
CLOSE the autonomous-loop spine: when idle, the engine consolidates each
soundly-derived determination back into the held self, so the next determine()
reaches it directly and can chain one hop further. The directly-answerable set
climbs monotonically across idle ticks to the deductive-closure fixed point.
Mechanism — generate/determine/consolidate.py::consolidate_once runs ONE
semi-naive layer of the member/subset deductive closure (member∘subset → member,
subset∘subset → subset; NEVER member∘member — instance-of is not transitive).
Each one-hop conclusion not yet realized is VERIFIED by the sound+complete
proof_chain ROBDD (reusing C's single verifier _verify_subsumption) and written
back via generate/realize::realize_derived as a SPECULATIVE realized record
carrying derived-provenance (premise structure_keys + rule + the ENTAILED
verdict). idle_tick gains a consolidation pass gated by the new
config.consolidate_determinations (default OFF); IdleTickResult.facts_consolidated
reports the layer.
Invariants held:
- wrong=0 — every consolidated fact is a sound-rule conclusion confirmed by the
sound+complete decider; member∘member is structurally unreachable (a member fact
is only ever extended by a subset edge). _verify_subsumption now refuses a
mislabeled/wrong-arity path (belt-and-suspenders now that consolidation is a
second caller), so the fallacy cannot be laundered through a corrupted chain.
- honesty — a fact derived from SPECULATIVE premises stays SPECULATIVE / as-told;
the soundness of the inference never upgrades the standing of the premises.
COHERENT is never minted.
- teaching-safety — SESSION memory (immediate), an extension of the realize path;
NOT corpus mutation and NOT coupled to proposals. The HITL path is untouched.
- determinism/replay — pure function of the realized set; sorted write order;
derived structure_key identical to a told fact's; provenance round-trips through
the Shape B+ snapshot (consolidated facts resume the SAME life across reboot).
- no new normalization — writes reuse the INV-21-allowed vault writer;
algebra/versor.py keeps closure.
Falsification — evals/determination_closure: a frozen replay seeds a deep is-a
chain and runs idle ticks; asserts the closure climbs monotonically to a complete
fixed point (no-op final tick), wrong=0 (member∘member canary never derived, no
fabricated membership), all derived facts SPECULATIVE, and every derived record
re-verifies ENTAILED from its recorded premises.
Verified green: smoke, runtime, cognition, architectural invariants, plus the new
D unit + lane tests and the determine/realize/persistence regression net. Five-lens
adversarial review: 4 lenses held; the 5th (normalization) was a misattribution
(pre-existing vault reproject, triggered identically by the merged realize path,
on sanctioned null-vector storage). Design + findings: docs/analysis/
D-close-consolidation-design-2026-06-06.md.
A2 of the refined sequencing. Proves (deterministically, not by assertion) what a
long-running CORE life costs to persist per turn on a constrained offline device.
Measures the Shape B+ checkpoint BYTES per turn (session_state.json) over the real
turn loop — bytes, not wall-clock latency (machine-dependent → flaky). Reuses the L10
continuity corpus.
Measured cliff: save_session_state re-serializes the FULL snapshot every turn, so
per-turn bytes are O(n) in the accumulated life — 3,811 → 88,189 bytes (23x) over 24
turns, ~1.3KB/vault-entry re-written every turn. That blocks continuous-life at the edge.
The gate encodes the edge REQUIREMENT (≤16 KiB/turn regardless of session length) as
xfail(strict): it fails today (documenting the cliff), runs green in CI, and flips to
a hard failure the moment incremental/append-only persistence (O(Δ)/turn) lands —
forcing us to retire it. Plus a regression ceiling (passes today) and a determinism
check (the byte metric is reproducible → a valid gate).
The fix is algorithmic (incremental persistence, Python/Ring-2), NOT a language
rewrite. Tagged core/array_codec.py as the locked reference contract for a future
gated Ring-1 Zig byte-exact codec (ADR-0196 G0-G8) — step 3, only after the O(Δ) fix
and only if this gate proves the codec is still the bottleneck. See contract.md.
A1 of the refined sequencing — the binary-relation reader was inert w.r.t. the
yardstick (contributing 0). This adds a comprehension_relational_predicate domain:
binary-relation prose scored against hand-authored independent gold (predicate,
subject, object) triples — INV-25 independent / INV-27 reader-disjoint (the reader
never produced the gold). Index breadth 8->9, capability_score 0.937258->0.944030,
wrong_total still 0; baseline.json re-frozen to digest 1ea91c1e.
Rigor split: the index lane is POSITIVE-ONLY (clean coverage, consistent with the
other 8 lanes — mixing adversarial refuse-cases into the coverage denominator would
make 'added capability' read as a score drop). The #596 fabrication-catch lives in a
dedicated falsification test (evals/relational/v1/refusals.jsonl): the trailing-
qualifier / dangling-copula / negation / verb-form cases MUST refuse — bites if the
reader ever fabricates. Honest coverage gap recorded: overlaps_event has no copular
surface form (verb-form 'A overlaps B' refuses), so 17 positives cover 15/16 predicates.
The binding-graph's FIRST comprehension consumer (doctrine-aligned: quantities live
in binding_graph, NOT the MeaningGraph). generate/quantitative_comprehension.py
reads arithmetic prose into SymbolBinding/BoundFact/BoundEquation and runs the REAL
check_admissibility (shell -> verify -> rebuild with the actual UnitProof) — there
is NO stamped "admitted": an equation is admitted only if its operand units verify.
Then to_relational_metric projects the binding-graph to the independent
relational_metric oracle for the verdict.
Templates (digits only; non-digit quantity REFUSES):
"<X> has <N> <unit>" -> BoundFact(X = N)
"<Y> has <N> more <unit> than <X>" -> BoundEquation(Y = X + N) op=add
"<Y> has <N> fewer <unit> than <X>" -> BoundEquation(Y = X - N) op=subtract
"How many <unit> does <Y> have" -> ask Y
"How many <unit> do <X> and <Y> have"-> total = X + Y; ask total
Unit modelling (honest, not faked): a noun the closed en_units_v1 pack knows is
used verbatim (dollars -> dollar/money); an UNKNOWN sortal noun (stickers, coins)
is a count of discrete objects -> the existing 'item' lemma (dimension count). So
admissibility stays a REAL check: count+count admits, count+money (a mixed-unit
sum) REFUSES with unit_mismatch — verified to bite.
comprehension_relational_metric: 15/15 wrong=0 (full coverage). Located OUTSIDE
generate/meaning_graph (it targets binding_graph, not the MeaningGraph) so INV-28
neutrality stays intact; oracle imports none of the SUT (new INV-25 lane).
Capability index breadth 7->8, score 0.928622 -> 0.937258, wrong_total 0, digest
50e0675b…
Tests: reader templates + count/known-unit modelling + admissibility-bite (mixed
unit refuses) + non-digit refusal; end-to-end full-coverage wrong=0; arithmetic
added to the structure-preservation generative panel (projected relations+query ==
ground truth); capability breadth 7->8; INV-25 arithmetic lane. 93 targeted + 90
smoke green; lane SHAs 8/9 (sole miss = public_demo env flake; deductive_logic +
math_teaching unchanged -> no GSM8K coupling).
Adds comprehension_propositional — the comprehension organ now reads the classic
propositional ARGUMENT FORMS end-to-end into the flagship deductive_logic ROBDD
oracle (the most robustly independent gold in the repo). The neutral MeaningGraph
now feeds FOUR independent oracles (set-membership, syllogism-validity,
total-ordering, propositional-entailment) from one interlingua — the Option-B
interlingua thesis validated.
reader.py: propositional templates (atoms are chunked NP ids; fits the existing
entities + n-ary relations + negation model — NO interlingua change, propositional
is not arithmetic-quantities):
- "if <P> then <Q>" -> implies(P, Q)
- "not <P>" -> asserted(P, negated=True)
- "<P> or <Q>" -> or(P, Q)
- "<P>" (single token) -> asserted(P) (bare-atom, single-token only to
keep the parse-or-refuse floor)
- "therefore <prop>" -> query of the same predicate
Relations now carry a negated flag end-to-end (asserted negation).
projectors.py: to_deductive_logic serializes propositional relations/query into
formula strings (keyword operators the oracle tokenizer accepts); returns None
(refusal) unless the comprehension is purely propositional, so categorical/ordering
comprehensions never leak into the entailment oracle.
evals: new evals/propositional_logic/v1 (12 cases — modus ponens/tollens,
hypothetical & disjunctive syllogism, the affirming-consequent / denying-antecedent
fallacies which the oracle marks "unknown"; gold = oracle verdict) + gold-only
runner + evals/comprehension/propositional_runner.py. Oracle "refused" (formula
unevaluable) is treated as a decline, never a wrong.
Scores: comprehension_propositional 12/12 wrong=0 (full coverage); no regression on
the 3 existing lanes (8/8, 7/8, 7/8). Capability index breadth 6->7, score
0.917231 -> 0.928622, wrong_total 0, digest 51df7bba…
Tests: reader propositional templates; to_deductive_logic projector tests;
end-to-end full-coverage wrong=0; propositional generative round-trip added to the
wrong=0 property suite (verified to BITE under a reversed-implies mutation);
capability breadth 6->7. 115 targeted + 87 smoke green. Lane SHAs 8/9 (sole miss =
public_demo env wall-clock flake; deductive_logic_v1 unchanged).
Recovers the multi-word-NP cases the reader previously refused, by adopting ONE
principled canonicalization contract (evals/comprehension/CANONICALIZATION.md) that
the reader AND the gold lanes both follow — so a committed answer can only match
gold or refuse, never silently mean something else.
Contract: a noun-phrase slot -> tokens lowercased, joined with "_"; a plural class
slot singularizes its head first ("metal objects"->"metal_object",
"North station"->"north_station", "Level one"->"level_one"). JOIN is chosen over
head-word-only ("metal objects"->"metal") because head-word-only is
information-destroying — it collapses "metal objects" and "metal tools" into one
false identity, itself a wrong=0 hazard.
reader.py: slot-based templates chunk multi-token NPs (_chunk / _chunk_class
replace the single-token _one / _one_class). Reserved-function-word guard fires only
INSIDE a multi-token slot (a lone "A" item is content, not the article). Still
parse-or-refuse: reserved-word leaks ("Compare beta with beta in the same order"),
non-pluralizable class heads (adjectival "trained"), and the ambiguous adjacent
two-NP subset query ("Are all <Xs> <Ys>?") all REFUSE.
gold (the contract update, logic-preserving — only term NAMES change):
- sy-v1-0008: metal/soft -> metal_object/soft_object (was head-word-only)
- to-v1-0005: red -> red_rank (was head-word-only)
- to-v1-0004: prose made internally consistent ("is after", "north station") +
north -> north_station (original prose used "North station" in the fact but
"north" in the query — a latent inconsistency)
- to-v1-0007: already conformed (level_one…), no change
Gold-only integrity runners stay 8/8 both lanes (structure+query+gold consistent).
Scores: set_membership 8/8, syllogism 6/8->7/8, total_ordering 4/8->7/8, all
wrong=0. Capability index re-frozen: score 0.814356 -> 0.917231, breadth 6,
wrong_total 0, digest 13d7db6c…
Tests: reader chunking + refusal tests; multi-word generative round-trip added to
the wrong=0 property suite (verified to BITE under a head-word-only mutation —
collapsed ids produce a wrong verdict the test catches); pinned counts updated.
100 comprehension/capability targeted + 87 smoke green.