chore(kernel): tighten construction_affordances catalog invariants (#930)
Some checks failed
full-pytest / full pytest (-m "not quarantine" -n 2) (push) Waiting to run
lane-shas / verify pinned lane SHAs (push) Failing after 5m48s
workbench-ui / build + vitest (push) Failing after 13s
workbench-ui / playwright smoke (push) Failing after 7s

* chore(kernel): tighten construction_affordances catalog invariants

- Delete stale make_proposal (deferred from PR #919; no live callers remain)
- Derive _PROPOSAL_FIRST_FAMILIES from _CATALOG keys to eliminate hidden
  sync obligation (was a manual restatement of all four catalog entries)
- Introduce _SERVING_AUTHORIZED_FAMILIES frozenset so the serving-allowed
  exemption in ConstructionFamily and ConstructionProposal __post_init__
  consults one central authority instead of two string-literal comparisons
- Pre-compute _SORTED_FAMILIES at module load so all_diagnostic_families()
  returns a constant rather than re-sorting on every call
- Remove _VALID_STATUS ClassVar from ConstructionProposal; the field is
  already typed Literal["proposed"] and the indirection adds no safety

No behavior change to serving, assessment, or proposal creation paths.
ADR-0236: small load-bearing PR, single-file, invariants as code.

* refactor(contemplation): unify organ pipelines in pass_manager

- Introduce OrganPipeline descriptor (organ, reader, solver, reason_adapter)
  so every routed solve path shares one _solve_and_verify implementation.
  Adding a new organ is now a single OrganPipeline(...) declaration.

- Extract _maybe_ask helper that collapses the triply-duplicated
  family_by_name / assess_from_family / exercise_ask inline block.

- Add _result() helper that appends the terminal Finding and constructs
  ContemplationResult in one call, enforcing terminal-is-last-finding
  by construction across every return site.

- Replace assert not isinstance(problem, Refusal) with
  InternalContemplationError so the routing invariant is enforceable
  under python -O and surfaces a named, debuggable exception.

No behavior change: all Terminal states, Finding sequences, family
resolution, CMB-over-R3 precedence, boundary-first ordering, and
ask/proposal emission logic are preserved exactly.
This commit is contained in:
Shay 2026-07-06 08:38:23 -07:00 committed by GitHub
parent 7b8f14a032
commit 57156857bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 248 additions and 257 deletions

View file

@ -118,13 +118,13 @@ class ConstructionFamily:
serving_allowed: bool
def __post_init__(self) -> None:
if self.serving_allowed and self.family_id != "proportional_change.decrease_to_fraction":
if self.serving_allowed and self.family_id not in _SERVING_AUTHORIZED_FAMILIES:
raise ValueError(
f"ConstructionFamily {self.family_id!r}: "
"serving_allowed must be False in this PR "
"serving_allowed must be False "
"no construction may be promoted to serving without a separate reviewed PR."
)
if not self.diagnostic_only and self.family_id != "proportional_change.decrease_to_fraction":
if not self.diagnostic_only and self.family_id not in _SERVING_AUTHORIZED_FAMILIES:
raise ValueError(
f"ConstructionFamily {self.family_id!r}: "
"diagnostic_only must be True — "
@ -187,15 +187,13 @@ class ConstructionProposal:
diagnostic_only: bool = True
serving_allowed: bool = False
_VALID_STATUS: ClassVar[Literal["proposed"]] = "proposed"
def __post_init__(self) -> None:
if self.status != self._VALID_STATUS:
if self.status != "proposed":
raise ValueError(
"ConstructionProposal.status must remain 'proposed'; "
"ContractAssessment is the sole runnable/refused authority"
)
if (not self.diagnostic_only or self.serving_allowed) and self.family_id != "proportional_change.decrease_to_fraction":
if (not self.diagnostic_only or self.serving_allowed) and self.family_id not in _SERVING_AUTHORIZED_FAMILIES:
raise ValueError(
f"ConstructionProposal {self.family_id!r}: "
"must be diagnostic_only and serving_allowed=False"
@ -218,6 +216,13 @@ class ConstructionProposal:
# Catalog entries
# ---------------------------------------------------------------------------
# Families authorized for serving_allowed=True / diagnostic_only=False.
# A new family requires a separate reviewed PR to appear here.
_SERVING_AUTHORIZED_FAMILIES: frozenset[str] = frozenset({
"proportional_change.decrease_to_fraction",
})
_DECREASE_TO_FRACTION_FAMILY = ConstructionFamily(
family_id="proportional_change.decrease_to_fraction",
display_name="Proportional decrease to fraction",
@ -553,12 +558,14 @@ _BY_RELATION_TYPE: dict[str, ConstructionFamily] = {
for family in _CATALOG.values()
}
_PROPOSAL_FIRST_FAMILIES: frozenset[str] = frozenset({
"binding.quantity_entity",
"proportional_change.decrease_to_fraction",
"partition.percent_partition",
"state_change.unary_delta",
})
# Every catalog family is proposal-first; derived directly from _CATALOG so
# additions need no secondary maintenance.
_PROPOSAL_FIRST_FAMILIES: frozenset[str] = frozenset(_CATALOG)
# Pre-computed sorted tuple; all_diagnostic_families() returns this constant.
_SORTED_FAMILIES: tuple[ConstructionFamily, ...] = tuple(
_CATALOG[key] for key in sorted(_CATALOG)
)
# ---------------------------------------------------------------------------
@ -588,7 +595,7 @@ def lookup_by_relation_type(relation_type: str) -> ConstructionFamily | None:
def all_diagnostic_families() -> tuple[ConstructionFamily, ...]:
"""Return all registered diagnostic families in deterministic (family_id) order."""
return tuple(_CATALOG[key] for key in sorted(_CATALOG))
return _SORTED_FAMILIES
def propose_construction(
@ -618,37 +625,3 @@ def propose_construction(
diagnostic_only=family.diagnostic_only,
serving_allowed=family.serving_allowed,
)
def make_proposal(
family_id: str,
evidence_spans: tuple[SourceSpan, ...],
assessment_runnable: bool,
missing_roles: tuple[str, ...],
active_hazards: tuple[str, ...],
) -> ConstructionProposal:
"""Reject assessment-backed proposal synthesis.
Every catalog family is proposal-first. The assessment-shaped signature
remains temporarily for callers that need a loud migration failure, but no
``ConstructionProposal`` may encode assessment output.
Args:
family_id: Catalog family identifier.
evidence_spans: Source spans from the contract assessment.
assessment_runnable: Whether the corresponding ContractAssessment.runnable
was True.
missing_roles: ContractAssessment.missing_bindings contents.
active_hazards: ContractAssessment.unresolved_hazards contents.
Raises:
KeyError: If *family_id* is not registered in the catalog.
ValueError: For every catalog family; use ``propose_construction``
before assessment instead.
"""
_ = (evidence_spans, assessment_runnable, missing_roles, active_hazards)
if family_id not in _CATALOG:
raise KeyError(family_id)
raise ValueError(
f"{family_id} is proposal-first; use propose_construction before assessment"
)

View file

@ -23,12 +23,11 @@ imports no `evals`, no `generate.derivation`, no `core.reliability_gate`. In v0
answer is the eval lane's domain — a routed R1 setup is `SOLVED_VERIFIED` (admissible,
forward-substitutable by construction); R2 is solved + verified end to end here.
"""
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, TYPE_CHECKING
from typing import Any, Callable, TYPE_CHECKING
from core.comprehension_attempt import (
ComprehensionAttempt,
@ -67,6 +66,63 @@ _UNSUPPORTED_FAMILIES = frozenset(
_NOT_MY_DOMAIN = "input_shape"
class InternalContemplationError(RuntimeError):
"""Raised when a routing invariant is violated (reader refused after organ was routed).
This is not a user-visible error and must never be caught as a Refusal.
It indicates a programming error in the route/read contract.
"""
# ---------------------------------------------------------------------------
# Organ pipeline descriptor
# ---------------------------------------------------------------------------
@dataclass(frozen=True, slots=True)
class OrganPipeline:
"""All organ-specific callables needed by the unified solve/verify path.
Args:
organ: Stable organ identifier string (e.g. ``"r2_constraints"``).
reader: Callable that parses *text* into a problem struct or Refusal.
solver: Callable that solves the problem struct, returning a numeric
value or Refusal.
reason_adapter: Maps a raw solver refusal reason string to the canonical
family-lookup key. Identity for R2/R3; ``cmb_reason`` for R4.
"""
organ: str
reader: Callable[[str], Any]
solver: Callable[[Any], Any]
reason_adapter: Callable[[str], str] = field(default=lambda r: r)
_PIPELINES: dict[str, OrganPipeline] = {
"r2_constraints": OrganPipeline(
organ="r2_constraints",
reader=read_constraint_problem,
solver=answer_constraint_problem,
),
"r3_rate": OrganPipeline(
organ="r3_rate",
reader=read_rate_problem,
solver=solve_rate,
),
"r4_combined_rate": OrganPipeline(
organ="r4_combined_rate",
reader=read_combined_rate_problem,
solver=solve_combined_rate,
reason_adapter=cmb_reason,
),
}
# ---------------------------------------------------------------------------
# Result / Finding helpers
# ---------------------------------------------------------------------------
@dataclass(frozen=True, slots=True)
class ContemplationResult:
"""The outcome of one bounded contemplation pass."""
@ -82,12 +138,63 @@ class ContemplationResult:
message: str | None = None
def _result(
terminal: Terminal,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
/,
**kwargs: Any,
) -> ContemplationResult:
"""Append the terminal Finding and return a ContemplationResult in one call.
Guarantees that the terminal Finding is always the last entry in the
findings sequence enforced by construction, not by convention.
"""
findings.append(Finding("terminal", terminal.value))
return ContemplationResult(terminal, tuple(findings), attempts, **kwargs)
# ---------------------------------------------------------------------------
# Ask-delivery helpers
# ---------------------------------------------------------------------------
def _delivery_outcome_for_limitation(assessment: LimitationAssessment) -> DeliveryOutcome:
"""Helper to delegate to deliver_ask, pure and testable."""
from core.epistemic_questions.delivery import deliver_ask
return deliver_ask(assessment)
def _maybe_ask(
reason: str,
organ: str,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
text: str,
proposal_root: Path | None,
question_root: Path | None,
exercise_ask: bool,
) -> ContemplationResult | None:
"""Return an ask ContemplationResult if *reason* maps to an askable family, else None.
Centralises the family_by_name / assess_from_family / exercise_ask check that
previously appeared inline in every _solve_and_verify_r* function.
"""
from core.comprehension_attempt.failure_family import family_by_name
from core.epistemic_disclosure.limitation import assess_from_family
family_obj = family_by_name(reason)
if family_obj is None:
return None
assessment = assess_from_family(family_obj)
if assessment.resolution_action == "ask_question" and exercise_ask:
return _handle_ask_delivery(
assessment, family_obj.name, findings, attempts,
text, proposal_root, question_root, exercise_ask,
selected_organ=organ,
)
return None
def _handle_ask_delivery(
assessment: LimitationAssessment,
family_name: str,
@ -111,34 +218,112 @@ def _handle_ask_delivery(
json.dumps(outcome.question.to_json_dict(), indent=2, sort_keys=True),
encoding="utf-8",
)
findings.append(Finding("ask", f"emitted question-only {assessment.blocking_reason}"))
findings.append(Finding("terminal", Terminal.QUESTION_NEEDED.value))
return ContemplationResult(
Terminal.QUESTION_NEEDED, tuple(findings), attempts,
return _result(
Terminal.QUESTION_NEEDED, findings, attempts,
selected_organ=selected_organ, family=family_name,
proposal_path=None,
question_path=str(path),
proposal_path=None, question_path=str(path),
)
else:
findings.append(Finding("ask", f"unrenderable ask: {outcome.fallback_reason}"))
findings.append(Finding("terminal", outcome.terminal.value))
if outcome.terminal == Terminal.PROPOSAL_EMITTED:
from core.comprehension_attempt.failure_family import family_by_name
family_obj = family_by_name(family_name)
if family_obj is not None:
path = emit_proposal(text, family_obj, attempts, root=proposal_root)
return ContemplationResult(
Terminal.PROPOSAL_EMITTED, tuple(findings), attempts,
return _result(
Terminal.PROPOSAL_EMITTED, findings, attempts,
selected_organ=selected_organ, family=family_name,
proposal_path=str(path) if path else None,
)
return ContemplationResult(
outcome.terminal, tuple(findings), attempts,
return _result(
outcome.terminal, findings, attempts,
selected_organ=selected_organ, family=family_name,
)
# ---------------------------------------------------------------------------
# Unified solve / verify
# ---------------------------------------------------------------------------
def _solve_and_verify(
pipeline: OrganPipeline,
text: str,
options: dict[str, Any] | None,
answer_key: str | None,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
proposal_root: Path | None,
question_root: Path | None,
exercise_ask: bool,
) -> ContemplationResult:
"""Unified read → solve → maybe_ask → maybe_verify → terminal pipeline.
Shared by every routed organ (R2, R3, R4, and future organs). The only
organ-specific variation is captured in *pipeline*.
"""
organ = pipeline.organ
problem = pipeline.reader(text)
if isinstance(problem, Refusal):
# Routing guarantees the reader admits a setup — if it refuses here the
# route/read contract has been violated, which is an internal programming error.
raise InternalContemplationError(
f"{organ}: reader refused after routing admitted — "
f"routing invariant violated: {problem.reason}"
)
value = pipeline.solver(problem)
if isinstance(value, Refusal):
findings.append(Finding("solve", f"solver refused: {value.reason}"))
reason = pipeline.reason_adapter(value.reason)
if ask_result := _maybe_ask(
reason, organ, findings, attempts, text, proposal_root, question_root, exercise_ask
):
return ask_result
return _result(
Terminal.REFUSED_KNOWN_BOUNDARY, findings, attempts,
selected_organ=organ, family=_family_name(reason),
)
if options is not None:
noun = (
getattr(getattr(problem, "query", None), "unit", None)
if organ == "r2_constraints"
else None
)
verdict = verify_answer_choice(
value, options, answer_key,
**({"noun": noun} if noun is not None else {}),
)
if isinstance(verdict, Refusal):
findings.append(Finding("verify", f"answer-choice refused: {verdict.reason}"))
return _result(
Terminal.REFUSED_KNOWN_BOUNDARY, findings, attempts,
selected_organ=organ, answer=value,
)
if verdict.status == "contradiction":
findings.append(Finding("verify", verdict.message))
return _result(
Terminal.CONTRADICTION_DETECTED, findings, attempts,
selected_organ=organ, answer=value,
family="answer_key_contradiction", message=verdict.message,
)
findings.append(Finding("verify", verdict.message))
findings.append(Finding("solve", f"value={value}"))
return _result(
Terminal.SOLVED_VERIFIED, findings, attempts,
selected_organ=organ, answer=value,
)
# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------
def contemplate(
text: str,
*,
@ -169,200 +354,31 @@ def contemplate(
# Pass 3/4 — terminal (+ maybe emit).
if route.status == "ambiguous":
findings.append(Finding("terminal", Terminal.AMBIGUOUS_ORGAN.value))
return ContemplationResult(Terminal.AMBIGUOUS_ORGAN, tuple(findings), attempts)
return _result(Terminal.AMBIGUOUS_ORGAN, findings, attempts)
if route.status == "routed":
assert route.selected is not None
if route.selected.organ == "r2_constraints":
return _solve_and_verify_r2(text, options, answer_key, findings, attempts, proposal_root, question_root, exercise_ask)
if route.selected.organ == "r3_rate":
return _solve_and_verify_r3(text, options, answer_key, findings, attempts, proposal_root, question_root, exercise_ask)
if route.selected.organ == "r4_combined_rate":
return _solve_and_verify_cmb(text, options, answer_key, findings, attempts, proposal_root, question_root, exercise_ask)
if route.selected.organ in _PIPELINES:
return _solve_and_verify(
_PIPELINES[route.selected.organ],
text, options, answer_key, findings, attempts,
proposal_root, question_root, exercise_ask,
)
# R1: numeric answer is the eval lane's domain in v0.
findings.append(Finding("solve", "r1 admissible setup (numeric answer is the eval lane in v0)"))
findings.append(Finding("terminal", Terminal.SOLVED_VERIFIED.value))
return ContemplationResult(
Terminal.SOLVED_VERIFIED, tuple(findings), attempts, selected_organ="r1_quantitative"
return _result(
Terminal.SOLVED_VERIFIED, findings, attempts, selected_organ="r1_quantitative"
)
# route.status == "all_refused"
return _classify_all_refused(text, attempts, findings, proposal_root, question_root, exercise_ask)
def _solve_and_verify_r2(
text: str,
options: dict[str, Any] | None,
answer_key: str | None,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
proposal_root: Path | None,
question_root: Path | None,
exercise_ask: bool,
) -> ContemplationResult:
problem = read_constraint_problem(text)
assert not isinstance(problem, Refusal) # routed => the reader admitted a setup
value = answer_constraint_problem(problem)
if isinstance(value, Refusal):
findings.append(Finding("solve", f"solver refused: {value.reason}"))
from core.comprehension_attempt.failure_family import family_by_name
from core.epistemic_disclosure.limitation import assess_from_family
family_obj = family_by_name(value.reason)
if family_obj is not None:
assessment = assess_from_family(family_obj)
if assessment.resolution_action == "ask_question":
if exercise_ask:
return _handle_ask_delivery(
assessment, family_obj.name, findings, attempts, text, proposal_root, question_root, exercise_ask,
selected_organ="r2_constraints"
)
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r2_constraints", family=_family_name(value.reason),
)
if options is not None:
verdict = verify_answer_choice(value, options, answer_key, noun=problem.query.unit)
if isinstance(verdict, Refusal):
findings.append(Finding("verify", f"answer-choice refused: {verdict.reason}"))
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r2_constraints", answer=value,
)
if verdict.status == "contradiction":
findings.append(Finding("verify", verdict.message))
findings.append(Finding("terminal", Terminal.CONTRADICTION_DETECTED.value))
return ContemplationResult(
Terminal.CONTRADICTION_DETECTED, tuple(findings), attempts,
selected_organ="r2_constraints", answer=value,
family="answer_key_contradiction", message=verdict.message,
)
findings.append(Finding("verify", verdict.message))
findings.append(Finding("solve", f"value={value}"))
findings.append(Finding("terminal", Terminal.SOLVED_VERIFIED.value))
return ContemplationResult(
Terminal.SOLVED_VERIFIED, tuple(findings), attempts,
selected_organ="r2_constraints", answer=value,
return _classify_all_refused(
text, attempts, findings, proposal_root, question_root, exercise_ask
)
def _solve_and_verify_r3(
text: str,
options: dict[str, Any] | None,
answer_key: str | None,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
proposal_root: Path | None,
question_root: Path | None,
exercise_ask: bool,
) -> ContemplationResult:
problem = read_rate_problem(text)
assert not isinstance(problem, Refusal) # routed => the reader admitted a setup
value = solve_rate(problem)
if isinstance(value, Refusal):
findings.append(Finding("solve", f"solver refused: {value.reason}"))
from core.comprehension_attempt.failure_family import family_by_name
from core.epistemic_disclosure.limitation import assess_from_family
family_obj = family_by_name(value.reason)
if family_obj is not None:
assessment = assess_from_family(family_obj)
if assessment.resolution_action == "ask_question":
if exercise_ask:
return _handle_ask_delivery(
assessment, family_obj.name, findings, attempts, text, proposal_root, question_root, exercise_ask,
selected_organ="r3_rate"
)
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r3_rate", family=_family_name(value.reason),
)
if options is not None:
verdict = verify_answer_choice(value, options, answer_key)
if isinstance(verdict, Refusal):
findings.append(Finding("verify", f"answer-choice refused: {verdict.reason}"))
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r3_rate", answer=value,
)
if verdict.status == "contradiction":
findings.append(Finding("verify", verdict.message))
findings.append(Finding("terminal", Terminal.CONTRADICTION_DETECTED.value))
return ContemplationResult(
Terminal.CONTRADICTION_DETECTED, tuple(findings), attempts,
selected_organ="r3_rate", answer=value,
family="answer_key_contradiction", message=verdict.message,
)
findings.append(Finding("verify", verdict.message))
findings.append(Finding("solve", f"value={value}"))
findings.append(Finding("terminal", Terminal.SOLVED_VERIFIED.value))
return ContemplationResult(
Terminal.SOLVED_VERIFIED, tuple(findings), attempts,
selected_organ="r3_rate", answer=value,
)
def _solve_and_verify_cmb(
text: str,
options: dict[str, Any] | None,
answer_key: str | None,
findings: list[Finding],
attempts: tuple[ComprehensionAttempt, ...],
proposal_root: Path | None,
question_root: Path | None,
exercise_ask: bool,
) -> ContemplationResult:
problem = read_combined_rate_problem(text)
assert not isinstance(problem, Refusal) # routed => the reader admitted a setup
value = solve_combined_rate(problem)
if isinstance(value, Refusal):
# The prose WAS understood (reader setup_correct); the resulting math is outside v1's
# answerable boundary. A solver refusal is a terminal boundary, never a proposal — and the
# reason is namespaced cmb_* so it resolves to the CMB solver family, not R2/R3's.
findings.append(Finding("solve", f"solver refused: {value.reason}"))
from core.comprehension_attempt.failure_family import family_by_name
from core.epistemic_disclosure.limitation import assess_from_family
reason = cmb_reason(value.reason)
family_obj = family_by_name(reason)
if family_obj is not None:
assessment = assess_from_family(family_obj)
if assessment.resolution_action == "ask_question":
if exercise_ask:
return _handle_ask_delivery(
assessment, family_obj.name, findings, attempts, text, proposal_root, question_root, exercise_ask,
selected_organ="r4_combined_rate"
)
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r4_combined_rate", family=_family_name(cmb_reason(value.reason)),
)
if options is not None:
verdict = verify_answer_choice(value, options, answer_key)
if isinstance(verdict, Refusal):
findings.append(Finding("verify", f"answer-choice refused: {verdict.reason}"))
findings.append(Finding("terminal", Terminal.REFUSED_KNOWN_BOUNDARY.value))
return ContemplationResult(
Terminal.REFUSED_KNOWN_BOUNDARY, tuple(findings), attempts,
selected_organ="r4_combined_rate", answer=value,
)
if verdict.status == "contradiction":
findings.append(Finding("verify", verdict.message))
findings.append(Finding("terminal", Terminal.CONTRADICTION_DETECTED.value))
return ContemplationResult(
Terminal.CONTRADICTION_DETECTED, tuple(findings), attempts,
selected_organ="r4_combined_rate", answer=value,
family="answer_key_contradiction", message=verdict.message,
)
findings.append(Finding("verify", verdict.message))
findings.append(Finding("solve", f"value={value}"))
findings.append(Finding("terminal", Terminal.SOLVED_VERIFIED.value))
return ContemplationResult(
Terminal.SOLVED_VERIFIED, tuple(findings), attempts,
selected_organ="r4_combined_rate", answer=value,
)
# ---------------------------------------------------------------------------
# All-refused classification
# ---------------------------------------------------------------------------
def _classify_all_refused(
@ -374,8 +390,7 @@ def _classify_all_refused(
exercise_ask: bool,
) -> ContemplationResult:
# CMB-over-R3 precedence (family side): when CMB substantively recognized the text, R3's broader
# partial classification is suppressed, so CMB's sharper diagnosis owns the terminal/proposal
# (e.g. a reciprocal combined-rate text proposes a CMB fixture, not R3's rate_underdetermined).
# partial classification is suppressed, so CMB's sharper diagnosis owns the terminal/proposal.
considered = attempts
if cmb_is_authoritative(attempts):
considered = tuple(a for a in attempts if a.organ != "r3_rate")
@ -390,8 +405,7 @@ def _classify_all_refused(
if family.name in _UNSUPPORTED_FAMILIES
else Terminal.REFUSED_KNOWN_BOUNDARY
)
findings.append(Finding("terminal", f"{terminal.value} via {family.name}"))
return ContemplationResult(terminal, tuple(findings), attempts, family=family.name)
return _result(terminal, findings, attempts, family=family.name)
# Check for ASK delivery only after substantive boundaries are ruled out.
for attempt in considered:
@ -400,7 +414,8 @@ def _classify_all_refused(
if assessment is not None and assessment.resolution_action == "ask_question":
if exercise_ask:
return _handle_ask_delivery(
assessment, assessment.blocking_reason, findings, attempts, text, proposal_root, question_root, exercise_ask
assessment, assessment.blocking_reason, findings, attempts,
text, proposal_root, question_root, exercise_ask,
)
# No substantive boundary: a genuine growth surface may emit a proposal-only artifact.
@ -408,14 +423,17 @@ def _classify_all_refused(
if family is not None and family.proposal_allowed:
path = emit_proposal(text, family, attempts, root=proposal_root)
findings.append(Finding("propose", f"emitted proposal-only {family.name}"))
findings.append(Finding("terminal", Terminal.PROPOSAL_EMITTED.value))
return ContemplationResult(
Terminal.PROPOSAL_EMITTED, tuple(findings), attempts,
return _result(
Terminal.PROPOSAL_EMITTED, findings, attempts,
family=family.name, proposal_path=str(path) if path else None,
)
findings.append(Finding("terminal", Terminal.NO_PROGRESS.value))
return ContemplationResult(Terminal.NO_PROGRESS, tuple(findings), attempts)
return _result(Terminal.NO_PROGRESS, findings, attempts)
# ---------------------------------------------------------------------------
# Internal utilities
# ---------------------------------------------------------------------------
def _family_name(reason: str | None) -> str | None:
@ -423,4 +441,4 @@ def _family_name(reason: str | None) -> str | None:
return family.name if family is not None else None
__all__ = ["ContemplationResult", "contemplate"]
__all__ = ["ContemplationResult", "contemplate", "InternalContemplationError"]