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.
63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
"""Typed, immutable record of one comprehension organ's attempt at a problem (N2).
|
|
|
|
A small normalization layer over the R1 (`generate.quantitative_comprehension`) and R2
|
|
(`generate.constraint_comprehension`) setup compilers: it turns each organ's heterogeneous
|
|
output (a typed setup, or a typed `Refusal`) into one uniform, frozen `ComprehensionAttempt`.
|
|
Nothing here changes reader behavior — it only *describes* an outcome so the router (N3),
|
|
failure-family registry (N4), and contemplation pass manager (N6) can reason over both organs
|
|
uniformly.
|
|
|
|
Outcome semantics. `classify` (N2) produces **produce-mode** outcomes — what the organ did on
|
|
its own gates, with no gold in hand: `setup_refused` (the organ refused) or `setup_correct`
|
|
(an admissible setup was produced). The gold-relative outcomes (`setup_wrong`, `answer_wrong`)
|
|
are representable here but are emitted only in **eval mode** by the lanes that hold gold — never
|
|
fabricated by `classify`. `answer_*` / `contradiction` are reached when the solver / answer-choice
|
|
verifier run downstream (N6), not at setup classification time.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Literal
|
|
|
|
from generate.binding_graph.model import SourceSpanLink
|
|
|
|
Organ = Literal["r1_quantitative", "r2_constraints", "r3_rate", "r4_combined_rate"]
|
|
|
|
Outcome = Literal[
|
|
"setup_correct", # an admissible setup was produced (produce-mode) / matches gold (eval-mode)
|
|
"setup_refused", # the organ refused to assemble a setup
|
|
"setup_wrong", # eval-mode only: produced setup diverges from gold (a wrong=0 breach)
|
|
"answer_correct", # a value was produced and self-verified / matches gold
|
|
"answer_refused", # setup produced but the solver/verifier refused
|
|
"answer_wrong", # eval-mode only: produced value diverges from gold
|
|
"contradiction", # a verified value contradicts a supplied answer key
|
|
]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ComprehensionAttempt:
|
|
"""One organ's attempt at one problem. Immutable; carries the outcome, the refusal reason
|
|
(if any), a deterministic setup signature (for cross-organ comparison), the answer (if a
|
|
value was produced), and source-span evidence. ``family`` is left ``None`` by ``classify``
|
|
and resolved later by the N4 failure-family registry."""
|
|
|
|
organ: Organ
|
|
outcome: Outcome
|
|
case_id: str | None = None
|
|
refusal_reason: str | None = None
|
|
family: str | None = None
|
|
setup_signature: str | None = None
|
|
answer: int | None = None
|
|
evidence: tuple[SourceSpanLink, ...] = ()
|
|
|
|
@property
|
|
def is_setup_correct(self) -> bool:
|
|
return self.outcome == "setup_correct"
|
|
|
|
@property
|
|
def is_refusal(self) -> bool:
|
|
return self.outcome in ("setup_refused", "answer_refused")
|
|
|
|
|
|
__all__ = ["ComprehensionAttempt", "Organ", "Outcome"]
|