core/generate/derivation/accumulate.py
Shay 951bcc0252 feat(derivation): define semantic-ledger candidate source boundary
ADR-0184 §7 S4: add generate/derivation/state/source.py — the single
boundary through which semantic-ledger worlds become pool candidates —
and swap pool.py's accumulation source to semantic_state_candidates().
accumulate.py keeps its public surfaces as thin compatibility wrappers
(ADR-0184 §10). Byte-identical over the 937-problem differential;
verify.py / pool.py remain the sole commit authority (structural
no-authority-import scan over state/, non-vacuous).

Also: docs/analysis design note for the S3–S5 boundary stack, and a
drive-by ruff F541 fix in r1_reconstruction.py (rf->r, zero behavior).
2026-06-10 13:17:53 -07:00

64 lines
3.2 KiB
Python

"""ADR-0178 GB-3b.1 — single-referent accumulation chaining.
The first cross-clause *comprehension* reading: one actor's quantity changes over
successive clauses (``Sam has 14 apples. He buys 9 more.`` -> ``14 + 9``). It is
the safe specialisation of the cross-clause sum that GB-3a's referent guard
correctly refuses wholesale (the ``Alice has 6 … Tom has 2 …`` hazard): we chain
across clauses **only** when (a) the later clause stays on the **same referent**
and (b) it carries a **licensed change cue** whose polarity is unambiguous.
Otherwise we refuse — the guard is generalised, never weakened.
ADR-0184 S1 extracted the reusable referent and change-cue helpers into
``generate.derivation.state``; S2 routed both accumulation readings through the
semantic ledger (SET/GAIN/LOSS over one entity/unit key) with replay back to
``GroundedDerivation``; §7 S4 moved world enumeration into the candidate-source
boundary, :mod:`generate.derivation.state.source`. This module remains the public
accumulation composer surface — :func:`compose_accumulation` (the gated strict
reading) and :func:`accumulation_candidates` (a thin compatibility wrapper over
:func:`generate.derivation.state.source.semantic_state_candidates`, per ADR-0184
§10). Behavior is intentionally unchanged, and every candidate still faces the
unchanged verifier/pool.
Sealed (no ``chat/`` import); deterministic; refuse-preferring.
"""
from __future__ import annotations
from generate.derivation.model import GroundedDerivation
from generate.derivation.state.replay import replay_accumulation_ledger
from generate.derivation.state.source import accumulation_world, semantic_state_candidates
from generate.derivation.verify import Resolution, select_self_verified
def _build_accumulation(
problem_text: str, *, drop_isolated_foreign: bool
) -> GroundedDerivation | None:
"""Construct the single-referent accumulation chain (ungated) — build the
semantic world at the S4 boundary, replay through the S2 bridge."""
ledger = accumulation_world(problem_text, drop_isolated_foreign=drop_isolated_foreign)
if ledger is None:
return None
return replay_accumulation_ledger(ledger)
def compose_accumulation(problem_text: str) -> Resolution | None:
"""GB-3b.1 composer — single-referent gain/loss accumulation. Refuse-preferring.
The strict (commit) reading: it gates the no-distractor-skip derivation through
the unchanged self-verification gate. Behavior is byte-identical to pre-ADR-0182.
"""
derivation = _build_accumulation(problem_text, drop_isolated_foreign=False)
if derivation is None:
return None
return select_self_verified([derivation], problem_text, target_units=())
def accumulation_candidates(problem_text: str) -> tuple[GroundedDerivation, ...]:
"""ADR-0182 — the ungated accumulation readings for cross-composer pooling.
Compatibility wrapper (ADR-0184 §10): the enumeration lives at the semantic
candidate-source boundary and is byte-identical to the pre-S4 behavior — three
readings (strict, distractor-skip, anchor-skip), ungated, deterministic order,
de-dup left to the pool.
"""
return semantic_state_candidates(problem_text)