Adds a default-dark verified_serving_enabled helper with focused tests. The helper is unwired: no runtime integration, no verify.py wiring, no eval producer imports, no served VERIFIED behavior, and no CLAIMS/metric movement.
Adds a default-dark ask_serving_enabled helper with focused tests. The helper is unwired: no pass_manager integration, no runtime surface, no carve-out retirement, and no served question text.
Rewrites the Q1-D ASK bus delivery scoping document into the durable decision record for decisions implemented in #668. No code or runtime behavior changes.
Accepts the ASK + VERIFIED serving-integration scoping briefs.
No served-surface code is introduced. ASK is identified as the next gated serving slice; VERIFIED remains blocked on a gold-free second reader and explicit eval-gold serving ban.
Per review: 'proposal-only' described the Q1-D sink/artifact in two docstrings,
conflating two distinct lanes. A question is an intake request, not a capability
proposal; question_only is its own lane. The analogy to the proposal emitter is
structural (toothless, review-gated, never served), never semantic. Tighten the
sink/artifact docstrings and rename the sink test to *question_only* to match
_QUESTION_STATUS. No behavior change — docstrings + one test name only.
The fourth and final Q1 rung: route the Q1-C rendered question onto the
contemplation bus as the QUESTION_NEEDED tenant (sibling of PROPOSAL_EMITTED),
off-serving. Consumes render_question VERBATIM — Q1-D constructs no prose, so the
Q1-C grounded-rendering wrong=0 guard is never bypassed by a second surface.
- generate/contemplation/findings.py: add Terminal.QUESTION_NEEDED.
- core/epistemic_disclosure/limitation.py: ask_question's home terminal is now
QUESTION_NEEDED, making terminal_for_action total (all six actions map onto
shipped terminals — the consolidation proof is now complete, not 'five of six').
- core/epistemic_questions/delivery.py: DeliveredQuestion (proposal-only artifact,
cannot wrap an unrenderable question / cannot be served / answer_binding reserved
None — illegal states unrepresentable), DeliveryOutcome, deliver_ask, the
teaching/questions sink emitter, and AnswerBinding (reserved for Q2).
Rulings honored:
- D1: off-serving only — no served surface, no ask_serving_enabled, no chat wiring.
- D2: an unrenderable ASK falls back to the family's standing disposition
(PROPOSAL_EMITTED if it still proposes, else NO_PROGRESS) — never a contentless
QUESTION_NEEDED. Enforced twice: in deliver_ask and structurally in __post_init__.
- D3: Q1B_ASK_CARVE_OUT untouched — registry keeps proposal_allowed=True; both
signals coexist off-serving so no operational signal is lost.
- D4: sink under teaching/questions/ (sibling of teaching/proposals/).
- D5: strictly single-slot — multi-slot is unrenderable, takes the D2 fallback.
Tests: 16 delivery tests (happy path verbatim, both D2 fallback branches,
structural guards, idempotent sink, off-serving AST) + updated the two limitation
consolidation tests for the now-total terminal map. smoke 90/0, affected suites
128/0.
The template asserts 'one more value is still needed' (exactly one). Rendering
the first of several missing slots and ignoring the rest makes that claim
subtly false. Refuse multi-slot assessments with multi_slot_not_supported
(slot=None) rather than render-first-drop-rest; one-question-per-slot fan-out
is a later rung. Replaces the now-contradicting first-of-many test with a
test that pins the honest refusal.
Add core/epistemic_questions/ — the renderer rung of the ASK spine.
render_question(LimitationAssessment) -> EpistemicQuestion turns ASK typed
residue into a user-facing question or an explicit question_unrenderable verdict.
Render only: no bus delivery, no served disposition, no serving (that is Q1-D).
Policy (scoping §2 / session §1.5.7 wrong=0 invariant): a question may name a
problem entity only if it appears verbatim in grounded_terms. Two substrate facts
force a generic-structural policy today: grounded_terms is empty everywhere (the
readers do not yet emit verbatim evidence), and a *missing* slot's referent is by
definition absent from the trace. So the renderer:
- renders a generic question whose only variable content is a controlled English
phrase for the slot's expected_unit_or_type, from a closed audited map;
- never surfaces slot_name or binding_target (snake_case), never prettifies an
identifier into a natural-language entity, never names a problem entity;
- degrades unmapped types to question_unrenderable (renderability_gap) rather
than dumping raw snake_case;
- single-slot (first slot only); non-ASK / zero-slot -> unrenderable;
- post-render fabrication guard re-checks every token is closed-vocab scaffold or
grounded (defense in depth).
Off-serving: imports no generate.derivation / core.reliability_gate (AST-tested).
Tests: 9/9 green; smoke 90/0.
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.
Reclassify missing_total_count / missing_weighted_total in the limitation layer
from (capability_gap, emit_proposal) to (missing_information, ask_question) —
the user CAN state the total, so these are missing data, not capability gaps.
Add typed ASK residue (MissingSlot, grounded_terms) on LimitationAssessment;
defaults to empty so existing P0-1 callers continue to work.
Q1B_ASK_CARVE_OUT: the shipped failure_family REGISTRY still flags both as
proposal_allowed=True so existing consumers (proposal pile, contemplation
pass_manager) keep emitting proposal artifacts until Q1-C/Q1-D wires ASK
delivery. Flipping the registry flag before ASK is served would create a
no-proposal/no-question dead zone — a capability-regression window with no
compensating intake. The carve-out is named, enumerated, and explicit; once
ASK delivery is live the REGISTRY flag flips and the carve-out retires.
Wrong=0 invariant on residue (scoping §2): MissingSlot carries family-typed
structural identifiers only (snake_case); grounded_terms is verbatim text from
ComprehensionAttempt.evidence SourceSpanLinks — never fabricated from family
or refusal_reason. Today classify_* leaves evidence empty for refusals, so
grounded_terms is empty everywhere; the wiring is ready for the residue
upgrade to readers later.
Off-serving: limitation.py imports no generate.derivation / core.reliability_gate
(AST-guarded by test_limitation_module_is_off_serving). No renderer, no bus
delivery — those are Q1-C/Q1-D.
Tests:
tests/test_limitation_assessment.py — 39 passing
+ test_proposal_allowed_iff_emit_proposal_with_q1b_ask_carveout (amended INV-A)
+ test_missing_{total,weighted}_count_limitation_is_ask_oriented
+ test_q1b_ask_carveout_is_explicit_and_enumerated
+ test_registry_proposal_allowed_preserved_until_ask_delivery
+ test_no_signal_loss_before_question_bus_is_serving (live pass_manager check)
+ test_missing_{total,weighted}_count_has_typed_missing_slot_residue
+ test_grounded_terms_populated_only_from_evidence_spans
+ test_residue_never_contains_ungrounded_terms
+ test_residue_empty_for_non_ask_attempts
+ test_missing_slots_default_empty_for_non_ask_families
+ test_missing_slot_keys_are_subset_of_ask_families
+ test_limitation_module_is_off_serving (AST guard)
Suites:
tests/test_epistemic_*.py, test_disclosure_claim.py, test_served_disposition.py,
test_verified_contract.py, test_failure_family.py, test_failure_proposal.py,
test_r3_router_contemplation.py, test_proposal_review_*.py,
test_idle_proposal_review.py — 135 passing
core test --suite smoke -q — 90 passing
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.
Defines what it MEANS for a result to earn DisclosureClaim.VERIFIED /
EpistemicState.VERIFIED — the soundness≠correctness gate the whole VERIFIED lane
rests on. Contract ONLY: no producer, no reader, no solver, no serving, no
verify.py, no verified_serving_enabled, no claim emission in any serving path.
core/epistemic_disclosure/verified_contract.py:
- VerificationObligation — the declarative contract (4 load-bearing flags);
VERIFICATION_OBLIGATION is fully strict.
- VerificationProof — the replayable proof SHAPE P1-B+ will fill (two reads kept
separate: distinct lineages for independence, matching digests for convergence).
- evaluate_verification(proof, *, limitation, obligation) -> VerificationResult —
refuse-preferring: VERIFIED only if EVERY obligation survives.
- disclosure_for_verification(result) — the ONLY sanctioned route to
(EpistemicState.VERIFIED, DisclosureClaim.VERIFIED); anything else -> UNDETERMINED/NONE.
The mechanism that makes the lane safe: VERIFIED requires TWO INDEPENDENT reads
(distinct lineages) that CONVERGE on one canonical structure. A faithful solve of a
WRONG read is caught because the independent read disagrees (back-substitution alone
cannot catch a read error); "same answer twice" is rejected as not independent.
Neither gold-agreement nor no-refusal nor a second solver over one read can verify.
15 tests, all non-vacuous. THE central test rejects a faithful solve of a wrong read;
one proves the wrong-read obligation is load-bearing (relax it -> poison slips the
check); the full chain composes proof -> contract -> (VERIFIED, VERIFIED) -> P0-3
DISCLOSE, and the poison degrades to COMMIT. Off-serving; smoke 90/0.
The third axis of the bus: choose_served_disposition composes
EpistemicState × LimitationAssessment × DisclosureClaim → ServedDisposition.
Pure mapping — no rendering, no bus behaviour, no verify.py, no govern_response
mutation. Nothing consumes it yet.
core/epistemic_disclosure/disposition.py:
- ServedDisposition(str, Enum): commit | disclose | ask | propose | report |
explain | refuse | step_aside.
- choose_served_disposition(*, epistemic_state, limitation, disclosure_claim=NONE):
a blocking limitation dominates (its resolution_action -> ask/propose/report/
explain/refuse/step_aside; scope_boundary -> EXPLAIN, NOT a hard REFUSE);
otherwise the disclosure claim + state pick the serve mode.
The Phase-1 VERIFIED guard (load-bearing): a DisclosureClaim.VERIFIED discloses
ONLY when EpistemicState.VERIFIED backs it; an unbacked verified claim degrades to
a plain COMMIT — never served as verified before the producer exists.
30 tests: the load-bearing rules via REAL P0-1 assessments; the VERIFIED guard
parametrized across all 14 non-VERIFIED states; limitation-dominates-claim;
answer-action fall-through; totality over both axes; off-serving. Smoke 90/0.
A served response carries two ORTHOGONAL governance properties: ReachLevel (how
far past grounded fact it reaches) and DisclosureClaim (the epistemic claim it
makes about its own truth status). "verified" is NOT a reach level — it is a
proof-state claim. Keeping the axes separate is the locked Stage-2 decision (no
ReachLevel.VERIFIED; a proven answer never inherits an [approximate] surface).
core/epistemic_disclosure/disclosure_claim.py:
- DisclosureClaim(str, Enum): none | verified | approximate | proposal_only.
Default = none. str-valued for stable serialization (EpistemicState convention).
- Discipline "no claim without a producer": every member has a real (APPROXIMATE
= cognition Step E; PROPOSAL_ONLY = teaching/proposals) or imminent (VERIFIED =
Phase 1 target) emitter. PROVEN and ESTIMATED are intentionally ABSENT — nothing
emits them; ESTIMATED is a future split of APPROXIMATE, added only when a real
estimator producer exists (docs-note only).
Also folds the P0-1 review nit: _KIND_TO_STATE / _ACTION_TO_TERMINAL tightened
from dict[str, ...] to their Literal key types.
9 tests, non-vacuous: default is NONE; verified is not a ReachLevel (structural);
claim axis disjoint from ResolutionAction; PROVEN and ESTIMATED absent; exactly the
approved four; str-enum serialization; module imports nothing (off-serving).
No bus behaviour, no ServedDisposition mapping (that is P0-3). Off-serving; smoke
green (90 passed) locally.
The intake gate of the Epistemic Disclosure spine: classify WHAT KIND of
limitation blocks resolution before choosing a served disposition. Asking is
one resolution among six; mis-classifying breaks intake (refuse-when-should-ask
loses the unlocking datum; ask-when-should-propose wastes the channel).
New off-serving package core/epistemic_disclosure/:
- LimitationKind (8) / ResolutionAction (6) / LimitationAssessment
- the mapping DERIVED from the shipped failure_family REGISTRY + contemplation
Terminal set — a consolidating VIEW, not a fourth taxonomy. The only genuinely
new action is ask_question (the one action with no shipped terminal); every
other action maps onto an existing Terminal (terminal_for_action proves it).
- assess_from_family / assess_from_attempt; conservative refuse default
(an unclassified reason NEVER becomes an answerable question — wrong=0-safe).
The missing_total_count / missing_weighted_total reclassification
(capability_gap -> missing_information) is DEFERRED to Q1-B with its own tests,
not made here; PENDING_Q1B_RECLASSIFICATION + a tripwire test document it.
27 tests, all non-vacuous (each fails under a single mis-keyed entry): totality
over REGISTRY, flag-consistency invariants (proposal<->propose; contradiction
verdict reports; foreign text steps aside), the wrong=0 invariant (hard_boundary
never asks), load-bearing specific mappings, the consolidation proof, and the
attempt-level paths.
Off-serving: imports no generate.derivation / core.reliability_gate (AST-checked);
cannot move the sealed GSM8K metric. Nothing consumes resolution_action yet.
Smoke green (90 passed) locally.
Resolve the one open S2-1 design decision in the doc before it leaves draft:
VERIFIED must NOT reuse ReachLevel.APPROXIMATE or the [approximate] prefix.
- §0: add the claim-vs-reach caution — VERIFIED is a disclosed-surface
*license / verification claim*, not a point further out on the speculation
scale; it is more LICENSED than strict/gold, not more speculative. Record the
principled three-axis decomposition (Disposition × DisclosureClaim × ReachLevel)
and allow v1 to collapse to a distinct ReachLevel.VERIFIED iff it carries its
own admissible-set, rationale, and [verified] prefix.
- §4: replace the open question with the settled S2-1 decision (Shay's wording):
VERIFIED gets a distinct disclosed-surface mode with its own deterministic
[verified] prefix; APPROXIMATE is for bounded estimates, VERIFIED for
independently-confirmed answers under the canonical-comparison contract.
- §1 SQ-5, §9 S2-1, §11 non-claims: propagate — only the enum shape
(distinct ReachLevel.VERIFIED vs a separate DisclosureClaim axis) is left to S2-A.
Still scoping only — NO CODE. Stays DRAFT pending Shay's design-lock.
Scope the frontier after the frozen R1–R4 comprehension ladder: the
served-surface disclosure machine and its first two tenants. Scoping
only — NO CODE.
- session doc §1.5 — the pre-question limitation pass / intake gate:
classify the limitation KIND before choosing a disposition (asking is
one of several). Key phrase: question-asking is the intake mechanism
for resolvable missing state; limitation assessment is the gate that
decides whether intake is appropriate.
- Doc 1 (primary) — the Epistemic Disclosure Bus:
EpistemicState + LimitationAssessment → ServedDisposition, two axes
(DISPOSITION × the existing ReachLevel). VERIFIED is the ONLY v1
tenant; ASK/PROPOSE/REPORT/SCOPE reserved. Grounded in the built-but-
inert ADR-0206 seam (govern_response/shape_surface; _canonically_verified
returns None at verify.py:188). Honours the 2026-06-06 dead-path
verdict: v1's VERIFIED producer is a comprehension-organ answer (R2
first, R4 second) with independence in the READING not the solving,
validate-first holdout (INV-25) BEFORE any widening, behind a new
verified_serving_enabled=False, GSM8K seal untouched.
- Doc 2 (companion) — Q1 question articulation: maps the four ASK
dispositions onto the shipped failure_family registry; the
missing_total_count / missing_weighted_total reclassification (decide
with tests, never silently re-key); the grounded-rendering wrong=0
invariant; the structured-residue-on-refusal gap; Q2 re-enters the
gate (augment + rerun, never mutate). Second bus tenant.
HOLD for review.
Records the design reached in discussion: the first real skill of contemplation
is typed, deterministic, failure-family-driven question generation. A question is
a typed request for missing state, not a conversational habit. Proposes
core/epistemic_questions/ organ (EpistemicQuestion, MissingSlot, AnswerBinding),
the QUESTION_NEEDED terminal distinct from PROPOSAL_EMITTED, and the Q1/Q2 PR
ladder. Traces the connection to the ServabilityBlade clarify mode and the
minimal-sufficient-question discipline.
Closes the CMB ladder with a docs-only lockfile. No behavior change.
- New docs/analysis/combined-rate-capability-ledger-2026-06-08.md: the frozen
R4 v1 claim, supported solved grid, solver/reader boundaries, the three
proposal-allowed deferrals, router/contemplation integration, the
domain-precedence adjudication (named, with proof cases cmb-11/15/12·13·14),
a prominent non-claims list, and the verification lanes. Every cited number
re-confirmed against the lanes: gold 19/19 (6/5/8), solver 6/0/5, reader
11/0/8 -> answers 6/0/13; the 7 R4 test files 93 passed.
- Updates comprehension-organ-capability-ledger-2026-06-08.md to fold R4 in as a
fourth organ: organ block, 4-organ router + cmb_* namespacing, a named
domain-precedence section, R4 lane rows, and a reframed deferred/next that
points the next frontier at served-surface epistemology (not more comprehension).
Honest accounting: 1 pre-existing red (test_findings_reset_between_turns) is red
on clean origin/main and imports nothing from R4 — unrelated.
Off-serving unchanged (sealed); no generate.derivation / core.reliability_gate.
CMB (r4_combined_rate) now participates in route_setup and bounded contemplation. R1/R2/R3 readers/
solvers, the CMB reader/solver/oracle, and serving are all UNCHANGED — pure shared-infra integration,
no new comprehension capability (that was CMB-c).
- model.py: Organ += r4_combined_rate.
- classify.py: classify_cmb + _cmb_signature (organ-tagged, cross-organ-distinct) + cmb_reason —
namespaces CMB reasons cmb_* before the reason-string-keyed registry sees them, so a CMB boundary
never inherits R2/R3's family for the SAME bare string (R3 rate_unit_mismatch is a GROWTH surface;
R2/R3 non_integer_solution carry other owners). not_combined_rate_shaped/empty stay bare -> the
cross input_shape family (router hygiene).
- failure_family.py: Owner += r4; input_shape += not_combined_rate_shaped; 8 CMB families
(must_remain_refused: cmb_unit_mismatch [until a dimension registry exists — NOT forever],
cmb_combine_ambiguous, cmb_underdetermined, cmb_non_positive_net, cmb_non_integer; proposal_allowed
-> cmb_gold_fixture: cmb_unsupported_rate_count/_reciprocal/_clock_interval).
- pass_manager.py: _solve_and_verify_cmb (solver Refusal -> REFUSED_KNOWN_BOUNDARY, never a proposal).
CMB-over-R3 domain-precedence rule (cmb_is_authoritative): when CMB POSITIVELY recognizes
combined-rate shape (a setup, or a substantive cmb_* refusal — NOT the input_shape step-aside), R3's
broader single-rate over-read of the SAME text is inadmissible — for BOTH routing (veto R3
setup_correct) and family attribution (suppress R3 so CMB's sharper diagnosis owns the
terminal/proposal). This came from a pre-build cross-organ pressure-test: R3 reads 'Anna and Ben
paint together; Anna paints 3 rooms/hour. How many do THEY paint in 4 hours?' as a single rate and
would confidently answer a wrong 12. The rule fixes that (-> REFUSED) without touching R3. It does
NOT mean 'CMB always wins': on input_shape CMB cedes (a plain single-rate car problem routes to R3
and solves 180). Narrow CMB<->R3 instance of a future general domain-specificity adjudication.
Tests: new test_cmb_router_contemplation (full terminal matrix; cmb-11 veto-no-wrong-answer; cmb-15
cede-to-R3; solver-refusal-never-proposes; CMB-owned proposal-only artifacts mounted:false; no gold
ambiguous; off-serving AST). Extended router-organ-hygiene to CMB x R1/R2/R3 (0 breaches).
test_setup_router/test_r3_router_contemplation attempt-count 3->4; test_failure_family ALL_REASONS +
growth set. 97 CMB-d tests; 597 broad-net pass (1 unrelated pre-existing red); CMB organ lanes green.
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.
The 2026-06-08 practice-attempts/servability-blade session note under-referenced the substrate
that already ships the serving half of its design. Doctrine ('wrong=0 = no false presentation of
epistemic status') stands; the build plan must reuse existing organs, not fork parallel ones.
Adds a binding Amendment + reframes sequencing:
- ServabilityDecision/ServabilityBlade => advance ADR-0206 core/response_governance/ (ReachPolicy /
govern_response / shape_surface, ReachLevel STRICT<APPROXIMATE<EXTRAPOLATE<CREATIVE, gated STRICT,
already disclosing [approximate]) — NOT a new object.
- the epistemic_state Literal => reuse core.epistemic_state.EpistemicState (ratified taxonomy), not
a smaller renamed enum.
- ProblemAttemptSession => extend core/comprehension_attempt/, don't fork.
- first activation = produce reserved VERIFIED via canonical comparison (scoping doc already exists),
consume through govern_response/shape_surface, prove on the yardstick, one state at a time.
Preserves the research trail (amendment, not rewrite). Docs only.
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.
Records the wrong=0 refinement reached in discussion: wrong=0 is not a binary
answer/refuse gate but 'no false presentation of epistemic status'. Practice/
contemplation may explore typed, isolated candidate guesses; served output needs
a servability blade with more modes than verified|refuse. Proposes
ProblemAttemptSession (PRA) and ServabilityDecision (SRV), sequenced PRA -> REL ->
SRV, and traces the idea to the R1/R2/R3/CMB reader trajectory. Session doc, not
an ADR yet.
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