docs(kernel): authorize unary state-change delta slice

This commit is contained in:
Shay 2026-06-20 22:09:38 -07:00
parent ca83d3846c
commit 7ee849970b
4 changed files with 1453 additions and 0 deletions

View file

@ -769,3 +769,44 @@ counts. That PR is the explicit authorization boundary for this rebaseline.
### Workbench / UI Alignment Scope Note
The alignment scope for the CORE Workbench/UI under the latest proposal-first pivots is documented in `docs/sessions/workbench-proposal-first-alignment-scope-2026-06-20.md`. This is a docs-only session note that identifies UI gaps, non-goals, future acceptance criteria, and next PR briefs to align the frontend observatory with the backend's proposal-first and contract assessment authority architecture.
---
## Unary state-change delta authorization pointer
The 2026-06-21 design investigation found that broad
`state_change.transition` is not one safe organ. It combines unary delta,
binary transfer, containment movement, and before/after state topology.
Authorized for one future diagnostic-only implementation PR:
- `state_change.unary_delta` only;
- exact cue inventory `gained → increase`, `lost → decrease`;
- one exact scalar/object `quantity_entity` edge;
- one family-local exact action-cue record;
- `ConstructionProposal(status="proposed")` before binding;
- `ContractAssessment` as the sole runnable/refused authority;
- `diagnostic_only=True`, `serving_allowed=False`.
Explicitly not authorized: broad `state_change.transition`, transfer,
containment, before/after state, measurement delta, actor/owner inference,
coreference, cross-sentence binding, arithmetic, answers, serving, derivation
ledger integration, or quantity-family widening.
Controlling artifacts:
- `docs/sessions/unary-state-change-delta-authorization-2026-06-21.md`
- `docs/sessions/unary-state-change-delta-preflight-2026-06-21.md`
- `docs/specs/foundational-families/unary-state-change-delta.md`
The next PR should implement the preflight's copy-paste brief. Stop if it
requires changes to `_quantity_entity_proposals()`,
`_quantity_kind_dispositions()`, `generate.derivation.state`, actor/owner
parsing, a broader cue inventory, synthetic spans, serving paths, or any
non-empty wrong-ID list.
Validation performed in the fresh worktree: startup guard passed with
`HEAD == origin/main == ca83d384`; baseline and post-doc
`uv run python -m core.cli test --suite smoke -q` completed successfully;
requested positive/confuser ProblemFrame probes were run read-only;
`git diff --check` passed. No subagents were used.

View file

@ -0,0 +1,477 @@
# Authorization: Unary State-Change Delta Foundational Slice
**Status:** Authorized for one future diagnostic-only implementation PR after
this authorization PR merges
**Date:** 2026-06-21
**Authorized family:** `state_change.unary_delta`
**Broad family:** `state_change.transition` remains unauthorized and
unimplemented
**Serving status:** `diagnostic_only=True`, `serving_allowed=False`
**Governing doctrine:** ADR-0223, ADR-0224, PRs #851 and #853
## 1. Decision
Do not implement the broad `state_change.transition` family next.
Authorize only `state_change.unary_delta`: one explicit, local, count-valued
gain or loss event over one exactly grounded quantity/object pair. The first
cue inventory is deliberately closed:
| Exact cue | Direction disposition |
|---|---|
| `gained` | `increase` |
| `lost` | `decrease` |
The authorized construction recognizes that an explicit delta event is locally
present. It does not construct a before state, after state, owner state,
transfer, containment move, arithmetic equation, answer, or serving action.
The safe first relation is:
```text
one exact action cue
+ one exact scalar mention
+ one exact changed-object mention
+ one exact local quantity_entity edge
------------------------------------------------
one proposed unary-delta relation, assessed diagnostically
```
This is the exact capability unlocked by the hardened quantity-entity seam:
CORE can now distinguish a quantity-bearing object from an unbound number. The
next valid step is to bind that grounded pair to one explicit local change cue,
not to parse an entire story.
## 2. Serious design finding
`state_change.transition` is not one organ. The current constitutional spec
combines at least four distinct geometries:
1. unary delta: `gained/lost N object`;
2. containment movement: `put/took N object in/from container`;
3. binary transfer: `gave/sent N object to target`;
4. before/after state relation: `was N, now M`.
They do not share the same role topology. Unary delta has one changed object and
one signed delta. Transfer has at least two state owners plus a conservation
relation. Containment movement has source/target containers. Before/after has
two temporal states and may have no delta cue at all. Treating these as one
family would force optional roles to carry the semantics and would make illegal
partial states representable.
The current code confirms the split:
- `ProblemFrame` can expose exact scalar/object mentions and
`MentionBinding(binding_type="quantity_entity")` for `Ana gained 3 marbles.`;
- it does not expose a typed exact event/action cue or a bound unary-delta
relation;
- it already contains a separate narrow `BoundRelation(relation_type="transfer")`
regex path for `Tom gave Ana 3 apples.`;
- `generate.derivation.state` contains a legacy arithmetic-oriented
`SemanticLedger`, but it uses a different raw-text path, float quantities,
loose subject/pronoun continuation, and a replay bridge to derivation
candidates. It is not suitable as exact `ProblemFrame` grounding evidence.
Reusing the transfer relation would silently define every change as transfer.
Reusing the semantic ledger would bypass the proposal-first substrate and
couple diagnostic recognition to arithmetic candidate production. Both are
rejected.
## 3. Map: directions considered
The intrinsic space is not a story. It is a typed local event relation over an
already grounded scalar/object edge, with exact source coordinates and a
conjugate assessment that refuses incomplete topology.
| Direction | Structural effect | Decision |
|---|---|---|
| Implement broad `state_change.transition` | Mixes unary delta, transfer, containment, and temporal-state topology | Rejected |
| Reuse `generate.derivation.state.SemanticLedger` | Bypasses `ProblemFrame`; imports legacy raw parsing and arithmetic candidate semantics | Rejected |
| Add event-cue proposals only, with no closable relation | Safe but does not prove the new quantity/entity seam composes into a useful organ | Deferred as an internal stage, not the family boundary |
| Add one unary-delta relation over exact local evidence | Preserves proposal → grounding → assessment and has a closed first topology | Selected |
## 4. Safe capability ladder
The broad family is split into separately authorized rungs:
| Rung | Family | Status after this PR |
|---|---|---|
| SCT-0 | exact local action-cue publication | Internal prerequisite to SCT-1; no independent serving or assessment authority |
| SCT-1 | `state_change.unary_delta` | Authorized for one future diagnostic implementation PR |
| SCT-2 | containment movement | Not authorized |
| SCT-3 | binary transfer | Not authorized |
| SCT-4 | before/after state relation | Not authorized |
| SCT-5 | arithmetic state equation or answer production | Not authorized |
`state_change.transition` remains the broad constitutional umbrella only. Its
existing foundational registry entry must remain
`implementation_authorized=False`.
## 5. Why now, and why only after #853
#851 proved that a local quantity/object edge can be proposed, grounded, and
assessed. #853 then closed two authority leaks:
- `ConstructionProposal` can represent only `status="proposed"`;
- quantity-kind dispositions are emitted only inside their exact proposal
boundary, so no-proposal percent, rate, pronoun, list, or transfer confusers
receive quantity-family authority.
Those corrections are prerequisites. A state-change slice built before #853
could have mistaken proposal state for an assessment verdict or consumed a
quantity-kind disposition synthesized for a confuser. This authorization
therefore treats #853's hardened seam as immutable:
- no proposal carries `runnable`, `refused`, missing-role, or hazard authority;
- `ContractAssessment` is the sole diagnostic runnable/refused authority;
- the unary family must not widen `_quantity_entity_proposals()`;
- the unary family must not widen `_quantity_kind_dispositions()`;
- a raw `MentionBinding` may be consumed as grounding evidence, but the
`binding.quantity_entity` proposal and assessment are not backdoored.
## 6. Family metadata
| Field | Authorized value |
|---|---|
| `family_id` | `state_change.unary_delta` |
| display name | Unary state-change delta |
| relation type | `unary_delta` |
| candidate organ | `unary_delta_transition` |
| proposal status | Always `proposed` |
| diagnostic posture | `diagnostic_only=True` |
| serving posture | `serving_allowed=False` |
| implementation authorization | Authorized only by this merged document for one future bounded PR |
| implementation status after this docs PR | Unimplemented; no runtime registry/catalog/dispatch change |
| relation to `binding.quantity_entity` | Consumes a compatible exact local `quantity_entity` edge; does not require or create its proposal, disposition, or assessment |
The future implementation may add a new descriptive foundational registry
entry for `state_change.unary_delta`. It must not flip the existing broad
`state_change.transition` entry to authorized.
## 7. Authority gradient
The only authorized flow is:
```text
exact lexical cue (`gained` or `lost`)
→ ConstructionProposal(
family_id="state_change.unary_delta",
status="proposed",
diagnostic_only=True,
serving_allowed=False,
)
→ exact GroundedActionCue
→ existing exact GroundedScalar / GroundedMention / quantity_entity binding
→ BoundRelation(relation_type="unary_delta")
→ assess_unary_delta(frame)
→ ContractAssessment(runnable or refused)
```
The proposal is a hypothesis only. Exact role bindings ground it. The
assessment is its corrective/conjugate and is the only runnable/refused
authority. No step derives an answer.
## 8. Minimum evidence tuple
A proposed candidate may close only from this tuple:
```text
(
action_cue_span,
action_kind,
explicit_direction,
delta_quantity_mention,
changed_object_mention,
quantity_entity_binding,
)
```
Every member is source-backed. Every `SourceSpan` must satisfy:
```text
problem_text[span.start:span.end] == span.text
```
No composite or synthetic span may be manufactured by concatenating role
surfaces. A contiguous event envelope may be computed as offsets for locality
checking, but it is not a new semantic fact and must not replace the individual
role spans.
## 9. Required roles
| Role | Requirement |
|---|---|
| `action_cue` | Exactly one exact `gained` or `lost` span |
| `changed_object` | Exactly one local object mention targeted by the selected quantity/entity edge |
| `delta_quantity` | Exactly one source-grounded scalar mention |
| `direction` | Required in SCT-1 because the authorized cue itself explicitly states `increase` or `decrease`; it may not be inferred from context |
| `local_binding_relation` | Exactly one `MentionBinding(binding_type="quantity_entity")` joining the selected delta to the changed object |
| `local_event_containment` | Cue, quantity, and object occur in that order in one sentence/clause with no sentence boundary |
| `provenance_span` | Exact action, quantity, object, and binding evidence spans |
SCT-1 is count-valued only. A quantity/unit/object span collision, including
`3 pounds` where `pounds` is both unit and object under current extraction,
refuses. Measurement-valued state change requires a separate extension after
unit/property topology is designed.
## 10. Optional and deferred roles that must not be invented
`actor` is the only optional SCT-1 role. It may be carried only when an exact
actor mention is already grounded by the existing frame; the implementation is
not authorized to widen actor extraction to obtain it. Its absence does not
block the relation and does not license an owner inference.
The following are optional in the broader state-change problem space but are
not representable by SCT-1 at all:
- source;
- target;
- before quantity;
- after quantity;
- container or location;
- temporal marker;
- unit/measurement property.
Their presence therefore refuses or routes to a future family. In particular,
`Ana` in `Ana gained 3 marbles.` is not automatically asserted as a state owner.
SCT-1 asserts only that the local text contains an increase cue over the
explicitly quantified object `marbles`.
## 11. Non-inference rule
The family cannot infer or compute:
- an implicit source or target;
- a pronoun antecedent;
- a cross-sentence event binding;
- an initial, before, final, or after quantity;
- a total, remainder, result, or answer;
- a transfer partner or conservation law;
- ownership or possession continuity;
- a containment path;
- arithmetic sign application (`+N` or `-N`) to stored state;
- that `gave` subtracts from a giver or adds to a receiver;
- that `put` moves an object into a container;
- that `bought`, `received`, `ate`, `spent`, `added`, or `removed` are synonyms
for an authorized cue;
- any event hidden behind passive voice, negation, modality, or temporal order.
Unknown is not false. Missing or ambiguous evidence refuses.
## 12. Relationship to quantity/entity grounding
The relationship is monotonic but non-authoritative:
1. Existing `GroundedScalar`, `GroundedMention`, and
`MentionBinding(binding_type="quantity_entity")` may ground the delta and
changed-object roles.
2. The presence of such a binding does not imply an event. `There are 12
apples.` remains only a quantity/entity construction.
3. The presence of an event cue does not imply a valid delta relation. `Ana
gained apples.` lacks an explicit quantity and must refuse.
4. A unary-delta proposal does not create a `binding.quantity_entity` proposal,
`QuantityKindDisposition`, or quantity-entity assessment.
5. The quantity-entity family does not create a unary-delta proposal.
6. No-proposal quantity-entity confusers must continue to have no
quantity-kind disposition, exactly as hardened by #853.
## 13. Interaction safety with existing families
The future implementation must preserve these families byte-for-byte in
behavior and authority:
- `proportional_change.decrease_to_fraction`;
- `partition.percent_partition`;
- `binding.quantity_entity`.
Unary delta must not propose when any of these competing surfaces are active:
- percent or percentage;
- rate, `per`, or `each`;
- comparison (`more than`, `less than`, `fewer than`, `times as`);
- proportional `decrease to`, `decrease by`, or fraction-scale language;
- plain quantity/entity binding with no authorized action cue;
- transfer, transaction, or containment movement;
- list/enumeration topology;
- more than one state-change cue.
The generic `consumption` process-frame candidate may coexist because `gained`
and `lost` currently surface it. No other process-frame family may coexist in
the first slice. The process frame is candidate context only; it is not the
proposal, relation, or assessment authority.
## 14. ContractAssessment obligations
The future `assess_unary_delta(frame)` may report `runnable=True` only when:
- exactly one unary-delta proposal exists;
- exactly one authorized exact action cue exists;
- exactly one scalar mention and one compatible quantity/entity binding exist;
- the binding selects exactly one changed object;
- the cue direction is explicit and unambiguous;
- cue, delta, and object are ordered locally in one sentence/clause;
- all role and relation spans are exact slices of `problem_text`;
- no unit/object collision exists;
- no competing family, actor coordination, object list, negation, modality,
passive voice, temporal ambiguity, or second change cue is present.
The proposal must remain `status="proposed"` whether the assessment is runnable
or refused. A proposal-free frame must not dispatch the unary-delta contract.
The assessment grants diagnostic readiness only, never serving authority.
## 15. Stable refusal taxonomy
The first implementation must use stable organ-specific blocker/hazard codes.
The exact storage field follows current `ContractAssessment` conventions;
missing structural evidence belongs in `missing_bindings`, while active
ambiguity/context belongs in `unresolved_hazards`.
| Class | Required code | Expected locus |
|---|---|---|
| missing event cue | `unary_delta_proposal_required` / no proposal | No dispatch |
| missing quantity | `delta_quantity_unbound` | Proposed then refused |
| missing changed object | `changed_object_unbound` | Proposed then refused |
| multiple actors/coordination | `multiple_actor_surface` | No proposal or refused |
| multiple objects/list | `changed_object_ambiguous` / `list_context_ambiguous` | No proposal or refused |
| ambiguous direction | `delta_direction_ambiguous` | Refused |
| source/target ambiguity | `source_target_unlicensed` | Refused |
| pronoun without exact antecedent | `pronoun_antecedent_unresolved` | Refused |
| cross-sentence event | `cross_sentence_event` | Refused |
| implicit total | `implicit_total_unlicensed` | No proposal or refused |
| comparative phrasing | `comparative_context` | No proposal |
| rate phrasing | `rate_context` | No proposal |
| percent phrasing | `percent_context` | No proposal |
| unit conflict | `unit_object_conflict` | Refused |
| passive voice | `passive_voice_unsupported` | Refused |
| temporal ambiguity | `temporal_context_ambiguous` | Refused |
| quantity/entity binding but no event | no unary proposal or assessment | No dispatch |
| event cue but no grounded pair | `local_binding_relation_unbound` | Proposed then refused |
| transfer verb without explicit roles | `transfer_unlicensed` / no unary proposal | No dispatch |
| two state changes | `multiple_state_change_cues` | No proposal |
| chained state changes | `chained_state_changes` | No proposal |
| inexact/synthetic evidence | `provenance_span_inexact` | Refused |
| negated or modal event | `event_assertion_unlicensed` | Refused |
No blocker may be silently repaired by choosing the nearest mention.
## 16. Tiny positive seed set
These are illustrative future fixtures, not implementation in this PR:
| Surface | Expected diagnostic roles |
|---|---|
| `Ana gained 3 marbles.` | cue=`gained`, direction=`increase`, delta=`3`, changed_object=`marbles` |
| `The jar lost 2 cookies.` | cue=`lost`, direction=`decrease`, delta=`2`, changed_object=`cookies` |
| `The team gained 4 points.` | cue=`gained`, direction=`increase`, delta=`4`, changed_object=`points` |
The subject is not asserted as owner or actor. The result is not computed.
## 17. Negative and confuser set
The future PR must prove these dispositions:
| Surface | Required result |
|---|---|
| `There are 12 apples.` | No unary proposal: binding exists, event absent |
| `Tom has 12 apples.` | No unary proposal: state description, not change |
| `Tom gave Ana 3 apples.` | No unary proposal: binary transfer is deferred |
| `Tom gave her 3 apples.` | No unary proposal: transfer plus unresolved pronoun |
| `Tom gave Ana some apples.` | No unary proposal: no explicit quantity |
| `Tom had 12 apples and gave Ana 3.` | No unary proposal: transfer and unbound changed object |
| `Ana has 3 more apples than Tom.` | No unary proposal: comparison |
| `20% of the apples are red.` | No unary proposal: percent partition surface |
| `3 apples per child.` | No unary proposal: rate |
| `Tom bought 3 apples and 2 oranges.` | No unary proposal: transaction and list ambiguity |
| `The basket was filled with apples.` | No unary proposal: passive containment, no quantity |
| `The tank has 84 degrees.` | No unary proposal; existing quantity assessment remains refused |
| `There are 12 apples. Tom ate 3.` | No unary proposal: disallowed cue and cross-sentence binding |
| `Tom and Ana each got 3 apples.` | No unary proposal: multiple actors/rate-like `each` |
| `Tom moved 3 apples from the box to the bag.` | No unary proposal: containment movement |
| `The box now has 5 more pencils.` | No unary proposal: comparison/current-state surface |
| `Tom put 4 shells in the bag.` | No unary proposal: containment movement |
| `Ana gained apples.` | Proposal may exist; assessment refuses missing quantity |
| `Ana gained 3.` | Proposal may exist; assessment refuses missing object/binding |
| `Ana gained 3 apples and lost 2.` | No proposal: two/chained state-change cues |
| `Ana did not gain 3 apples.` | No proposal because `gain` is outside v1; future widening must also preserve negation refusal |
| `3 apples were gained by Ana.` | Proposal may exist; assessment refuses passive/order topology |
| `Ana gained 3 pounds.` | Proposal may exist; assessment refuses unit/object collision in count-only SCT-1 |
## 18. Explicit non-authorization
This PR does not authorize:
- `state_change.transition` implementation;
- the existing broad `state-change.md` required-role topology;
- source/target, ownership, possession, or conservation modeling;
- `gave`, `received`, `bought`, `sold`, `ate`, `spent`, `put`, `took`, `moved`,
`added`, or `removed` cues;
- initial/final or before/after state binding;
- measurement-valued delta events;
- multiple events, multiple quantities, multiple objects, or coordinated actors;
- pronoun/coreference resolution;
- cross-sentence inference;
- `SemanticLedger` integration or derivation candidate production;
- arithmetic, answer production, eval score movement, report mutation, serving,
teaching, memory, policy, identity, recall, Vault, field, or algebra changes.
## 19. Future implementation gates
The implementation PR may begin only after this authorization and its preflight
map merge. It must:
1. start from fresh `origin/main` and pass the startup guard;
2. add `state_change.unary_delta` without authorizing
`state_change.transition`;
3. introduce only the minimum exact action-cue type and ProblemFrame publication
path defined by the preflight;
4. preserve proposal-first ordering and #853's proposal schema;
5. keep all three current proposal-first families green;
6. prove deterministic replay and exact spans;
7. prove every confuser above refuses or does not propose as specified;
8. preserve train and holdout `wrong_ids == []` without updating reports;
9. avoid all serving and derivation paths;
10. stop if the positive cases require actor parsing, coreference, a broad verb
parser, synthetic spans, or quantity-family widening.
## 20. Stop conditions
Stop implementation and return to design if any of these becomes necessary:
- changing `_quantity_entity_proposals()` or `_quantity_kind_dispositions()`;
- consuming `QuantityKindDisposition` without its quantity-family proposal;
- importing `generate.derivation.state` into the ProblemFrame path;
- adding a generic verb/event parser or universal event IR;
- inferring an actor, owner, source, target, initial value, or final value;
- accepting more than the exact `gained`/`lost` cue inventory;
- touching serving, eval/report artifacts, teaching, policy, identity, recall,
Vault, field, or algebra;
- producing a runnable assessment with inexact, synthetic, ambiguous, or
cross-sentence evidence;
- any non-empty wrong-ID list.
## 21. Justification: the masterstroke
The masterstroke is the refusal to model a story. The new organ models a local
field differential: a direction-bearing action cue is conjugated with one
grounded delta/object edge. The proposal propagates a hypothesis; exact role
bindings reconstruct its coordinates; `ContractAssessment` opposes and
corrects every incomplete topology.
This preserves the intrinsic geometry. Unary delta, transfer, containment, and
before/after state are not optional-role variants of one object. They are
different manifolds and receive separate future contracts. The resulting slice
is small enough to make illegal states unrepresentable, yet powerful enough to
establish the first event-bearing composition over the quantity/entity seam.
## 22. Remaining risks discovered
- `docs/sessions/workbench-proposal-first-alignment-scope-2026-06-20.md`
predates #853 and still describes proposal statuses such as `partial`,
`closed`, and `refused`. That text is stale and must not be copied into this
implementation. Updating Workbench docs is a separate PR.
- The legacy semantic-state ledger is semantically adjacent and therefore a
likely source of accidental coupling. The implementation preflight requires
an explicit no-import/no-call proof.
- Existing mention extraction does not ground the subject of `gained/lost` as
an actor. This is a deliberate first-slice limit, not a gap to repair in the
implementation PR.

View file

@ -0,0 +1,580 @@
# Preflight: `state_change.unary_delta` Implementation Map
**Status:** Design complete; implementation remains future work
**Date:** 2026-06-21
**Controlling authorization:**
`docs/sessions/unary-state-change-delta-authorization-2026-06-21.md`
**Target future PR:** `feat(kernel): introduce diagnostic unary-delta proposal seam`
## 1. Preflight result
The implementation is feasible without a broad story parser, but only after
splitting `state_change.transition` into the narrower
`state_change.unary_delta` family.
The current substrate already carries the scalar, object, and exact
`quantity_entity` edge needed by the two seed positives. It does not carry an
exact typed action cue. The minimum necessary type addition is therefore one
family-local cue record, not a universal event IR.
No implementation is present in this documentation PR.
## 2. Current relevant code map
### 2.1 Descriptive family gate
`generate/foundational_families.py`
- contains exactly two current registry entries;
- marks `binding.quantity_entity` implemented/authorized;
- keeps broad `state_change.transition` proposed and
`implementation_authorized=False`;
- is descriptive metadata only and is not runtime dispatch.
Future action: add a third exact entry for `state_change.unary_delta`. Do not
authorize or repurpose `state_change.transition`.
### 2.2 Construction proposal catalog
`generate/construction_affordances.py`
- `ConstructionProposal.status` is structurally restricted to `"proposed"`;
- proposals cannot store missing roles or active hazards;
- all catalog entries are diagnostic-only and serving-disallowed;
- `propose_construction(family_id, evidence_spans)` has hypothesis-only inputs;
- `make_proposal()` rejects all current catalog families and must remain a
loud legacy fence.
Future action: add `_UNARY_DELTA_FAMILY`, catalog it, and include it in the
proposal-first set. Do not change proposal schema or `make_proposal()`.
### 2.3 ProblemFrame types
`generate/problem_frame.py`
- carries exact mentions, bindings, bound relations, proposals, and
quantity-kind dispositions;
- has no event/action cue record;
- has no unary-delta-specific state;
- `QuantityKindDisposition` is family-local to quantity/entity and is not
available for state-change confusers after #853.
Future action: add one family-local `GroundedUnaryDeltaCue` and a
`ProblemFrame.unary_delta_cues` tuple with a matching builder method. Do not
widen `MentionKind` to a generic `event`/`action` universe.
### 2.4 Frame builder
`generate/problem_frame_builder.py::build_problem_frame`
Current order:
```text
scalars / units / hazards / process-frame candidates
→ proposal-first construction proposals
→ mentions / bindings / quantity disposition / bound relations / target
→ initial ProblemFrame
→ ContractAssessment dispatch
```
Relevant existing facts:
- `Ana gained 3 marbles.` produces one `consumption` process-frame candidate,
one exact scalar mention, one exact object mention, and one exact
`quantity_entity` binding; it produces no proposal or bound relation.
- `The jar lost 2 cookies.` has the same topology.
- `_quantity_entity_proposals()` intentionally refuses any active process
frame, so the state-change positive has no quantity-family proposal.
- `_quantity_kind_dispositions()` now requires exactly one
`binding.quantity_entity` proposal. This is the #853 hardening boundary and
must not be widened.
- `_TRANSFER_RE` separately constructs a `transfer` relation for a narrow
`gave` surface. It must not be reused as unary state change.
Future action: add one cue-proposal helper before mention binding, then publish
the exact cue and unary relation after mentions/bindings exist.
### 2.5 Contract assessment
`generate/problem_frame_contracts.py`
- `assess_contracts()` dispatches current catalog contracts only from an
existing family proposal;
- dedicated assessment functions hold family-specific closure logic;
- `ContractAssessment` is the sole runnable/refused authority;
- output ordering is deterministic by `candidate_organ`.
Future action: register `unary_delta_transition`, implement
`assess_unary_delta(frame)`, and dispatch only when the exact proposal exists.
### 2.6 Process-frame candidates
`generate/process_frames.py`
- the broad `consumption` candidate frame includes both `gain*` and `lose*`;
- its own `not_licensed` declarations forbid deciding gain/loss without exact
surface evidence and forbid computing remaining quantities;
- it is candidate context, not a grounded event role or contract.
Future action: allow the existing `consumption` candidate to coexist, but do
not treat it as cue, proposal, relation, or closure authority.
### 2.7 Legacy semantic-state derivation path
`generate/derivation/state/*`
- already models `set`, `gain`, and `loss` transitions for accumulation;
- parses raw clauses independently of `ProblemFrame`;
- stores numeric values as float-backed `SemanticQuantity` rather than exact
`GroundedScalar` fractions;
- permits loose pronoun continuation;
- replays to `GroundedDerivation` candidates.
Future action: none. The new diagnostic family must not import, call, wrap,
compare against, or emit `SemanticLedger`, `StateTransition`, or
`GroundedDerivation`.
### 2.8 Callers and consumers
The import/call-site sweep found:
- `build_problem_frame()` and `assess_contracts()` are consumed by focused
tests plus `scripts/gsm8k_problem_frame_adequacy.py` and
`scripts/gsm8k_substrate_morphology.py`;
- no `calibration/` module directly consumes this seam;
- no serving module directly imports `problem_frame_contracts`;
- current proposal-first tests enforce proposal ordering, exact spans,
diagnostic posture, and proposal-gated dispatch.
The future PR must preserve these callers and must not add a serving import.
## 3. Required type addition
Add only this family-local shape in `generate/problem_frame.py`:
```python
UnaryDeltaDirection = Literal["increase", "decrease"]
UnaryDeltaActionKind = Literal["gain", "loss"]
@dataclass(frozen=True, slots=True)
class GroundedUnaryDeltaCue:
cue_id: str
surface: str
action_kind: UnaryDeltaActionKind
direction: UnaryDeltaDirection
span: SourceSpan
```
Construction obligations:
- `surface` must equal `span.text`;
- `gained` must map only to `action_kind="gain"`,
`direction="increase"`;
- `lost` must map only to `action_kind="loss"`,
`direction="decrease"`;
- no other cue value is constructible;
- the frame carries cues as `tuple[GroundedUnaryDeltaCue, ...]` in stable source
order.
This type is intentionally not:
- a generic `EventMention`;
- a verb parse tree;
- an actor/source/target model;
- a temporal graph;
- a `SemanticLedger` transition;
- an arithmetic operator.
Do not add a new `SubstrateFact` variant or widen `MentionKind` in the first PR.
## 4. Closed cue inventory and recognition
The future builder owns one immutable mapping:
```text
gained → (gain, increase)
lost → (loss, decrease)
```
Recognition rules:
1. enumerate exact lexical-boundary occurrences using the existing lexical
boundary helper style;
2. zero hits: no proposal;
3. more than one hit, including repeated identical cues: no proposal;
4. one hit: use only its exact `SourceSpan` to create the proposal;
5. do not capture surrounding grammar or synthesize an event chunk;
6. block proposal creation when an explicit competing surface is already
visible: percent, rate/per/each, comparison, transfer, transaction,
containment, list coordination, or multiple event cues;
7. the only allowed process-frame set is empty or exactly `{"consumption"}`;
the two authorized cues currently produce `consumption`.
The proposal helper must be a closed lexical recognizer, not a broad regex with
verb, subject, object, source, target, and clause capture groups.
## 5. Required frame publication path
The future build order is fixed:
```text
1. Existing scalar/unit/hazard/process-frame extraction
2. Detect exactly one authorized cue span
3. ConstructionProposal(status="proposed") from cue span only
4. Existing mention and MentionBinding extraction
5. GroundedUnaryDeltaCue from the proposal-backed exact cue
6. BoundRelation(relation_type="unary_delta") from cue + existing binding
7. ProblemFrame publication
8. Proposal-gated ContractAssessment
```
The unary relation uses existing `BoundRelation` / `BoundRole`:
| Bound role | Target |
|---|---|
| `action_cue` | `GroundedUnaryDeltaCue.cue_id`, kind `unary_delta_cue` |
| `delta_quantity` | selected quantity `GroundedMention.mention_id` |
| `changed_object` | selected object `GroundedMention.mention_id` |
`direction` is an explicit field of the exact cue. The construction catalog
still declares direction as a required semantic obligation; assessment checks
the cue's typed disposition.
Relation publication requirements:
- exactly one `quantity_entity` binding;
- its source resolves to exactly one source-grounded scalar mention;
- its target resolves to exactly one object mention;
- cue precedes quantity, which precedes object;
- no `.`, `?`, or `!` boundary lies between the three spans;
- relation evidence is the ordered tuple of exact cue, quantity, and object
spans;
- no actor, owner, source, target, before, after, container, or temporal role
is synthesized.
If any requirement fails, publish no bound relation. The proposal may still be
assessed and refused from the missing evidence.
## 6. Construction catalog addition
The future `_UNARY_DELTA_FAMILY` must declare:
```text
family_id: state_change.unary_delta
relation_type: unary_delta
candidate_organ: unary_delta_transition
required roles: action_cue, changed_object, delta_quantity, direction,
local_binding_relation, local_event_containment,
provenance_span
optional roles: actor
diagnostic_only: True
serving_allowed: False
```
`unit`, `source`, `target`, `before_quantity`, `after_quantity`, `container`,
and `temporal_marker` stay absent from the first catalog signature. Listing
them as optional would imply the first relation can lawfully carry those
states; it cannot. They remain future-family roles, not optional SCT-1 data.
## 7. Contract assessment path
Add:
```python
def assess_unary_delta(frame: ProblemFrame) -> ContractAssessment:
...
```
Assessment order:
1. require exactly one `state_change.unary_delta` proposal;
2. require exactly one `GroundedUnaryDeltaCue` contained by proposal evidence;
3. require exactly one `BoundRelation(relation_type="unary_delta")`;
4. require exactly one action, delta, and changed-object role target;
5. prove delta mention → scalar provenance;
6. prove the exact `quantity_entity` edge joins the selected mentions;
7. prove typed cue/action/direction consistency;
8. prove local ordering and sentence containment;
9. prove every proposal, cue, role, binding, and relation span is an exact
`problem_text` slice;
10. reject unit/object collisions and all active competing contexts;
11. report `runnable=True` only if both blocker and hazard sets are empty.
The function does not calculate a signed number. It does not inspect a question
target. It does not produce a target value. Its meaning is only “this local
unary-delta construction is role-complete.”
## 8. Refusal code placement
### `missing_bindings`
- `unary_delta_proposal_required`
- `action_cue_unbound`
- `action_cue_ambiguous`
- `delta_quantity_unbound`
- `delta_quantity_ambiguous`
- `changed_object_unbound`
- `changed_object_ambiguous`
- `local_binding_relation_unbound`
- `local_binding_relation_ambiguous`
- `local_event_containment_unproven`
- `delta_direction_unbound`
- `unit_object_conflict`
- `provenance_span_inexact`
### `unresolved_hazards`
- `multiple_actor_surface`
- `delta_direction_ambiguous`
- `source_target_unlicensed`
- `pronoun_antecedent_unresolved`
- `cross_sentence_event`
- `implicit_total_unlicensed`
- `comparative_context`
- `rate_context`
- `percent_context`
- `passive_voice_unsupported`
- `temporal_context_ambiguous`
- `list_context_ambiguous`
- `transfer_unlicensed`
- `multiple_state_change_cues`
- `chained_state_changes`
- `event_assertion_unlicensed`
The implementation may use a strict subset on naturally unreachable
no-proposal paths, but direct assessment of a deliberately malformed frame must
remain fail-closed and legible.
## 9. Relationship to current quantity disposition
Do not reuse or widen `QuantityKindDisposition` in SCT-1.
Reason: #853 made that disposition valid only when an exact
`binding.quantity_entity` proposal exists. State-change text deliberately has a
process-frame context, so the quantity family does not propose. Making its
disposition generic again would recreate the authority leak #853 removed.
SCT-1 instead closes only a family-specific count-valued delta relation from
its own proposal plus the exact existing scalar/object binding. Measurement
support is deferred.
## 10. Required future code files
The bounded implementation is expected to touch only:
- `generate/foundational_families.py`
- `generate/construction_affordances.py`
- `generate/problem_frame.py`
- `generate/problem_frame_builder.py`
- `generate/problem_frame_contracts.py`
- focused tests listed below
Do not touch:
- `generate/derivation/*`
- `generate/math_candidate_graph.py`
- `evals/*` or reports
- runtime/serving paths
- teaching/proposals, packs, policy, identity, recall, Vault, field, algebra,
sealed artifacts, or `report.json`
## 11. Likely test files
Extend:
- `tests/test_foundational_families.py`
- `tests/test_construction_affordances.py`
- `tests/test_construction_proposal_seam.py`
- `tests/test_problem_frame_builder.py`
- `tests/test_problem_frame_contracts.py`
- `tests/test_quantity_entity_proposal.py`
- `tests/test_proportional_decrease_proposal.py`
- `tests/test_percent_partition_proposal.py`
Add:
- `tests/test_unary_delta_proposal.py`
No eval fixture or report file is required.
## 12. Exact future tests
### Type and catalog
1. only `gained` and `lost` construct `GroundedUnaryDeltaCue`;
2. cue action kind/direction pairs cannot be mismatched;
3. catalog entry is diagnostic-only and serving-disallowed;
4. broad `state_change.transition` stays implementation-unauthorized;
5. `state_change.unary_delta` is a distinct foundational entry;
6. `ConstructionProposal` remains proposed-only.
### Ordering and authority
7. proposal is published before cue binding/relation assessment;
8. proposal-free frame does not dispatch `unary_delta_transition`;
9. positive proposal remains `status="proposed"` after runnable assessment;
10. legacy `make_proposal()` is never used;
11. direct malformed-frame assessment refuses with stable codes.
### Exact evidence
12. every proposal/cue/relation/assessment span equals its source slice;
13. synthetic proposal span refuses;
14. synthetic cue span refuses;
15. mismatched relation role target refuses;
16. cue/quantity/object crossing a sentence boundary refuses;
17. repeated build produces identical cue, proposal, relation, and assessment.
### Positive fixtures
18. `Ana gained 3 marbles.` is diagnostically runnable;
19. `The jar lost 2 cookies.` is diagnostically runnable;
20. `The team gained 4 points.` is diagnostically runnable;
21. no positive produces an answer, target, serving flag, or derivation candidate.
### Negative/confuser fixtures
22. every table entry in authorization §17 has the specified no-proposal or
refusal disposition;
23. `Ana gained apples.` refuses `delta_quantity_unbound`;
24. `Ana gained 3.` refuses `changed_object_unbound`;
25. `3 apples were gained by Ana.` refuses passive/order topology;
26. `Ana gained 3 pounds.` refuses unit/object conflict;
27. `Ana gained 3 apples and lost 2.` has no proposal;
28. `Tom gave Ana 3 apples.` still has no quantity disposition and no unary
proposal;
29. plain `There are 12 apples.` keeps its quantity proposal and gains no
unary assessment;
30. current proportional-decrease and percent-partition positives remain
unchanged.
### Structural firewall
31. an AST/import guard proves the new ProblemFrame/catalog/contract path does
not import `generate.derivation.state`, `generate.derivation.accumulate`,
`generate.derivation.pool`, or `generate.derivation.verify`;
32. no new raw-prose parser appears under `generate/derivation/`;
33. the existing no-new-legacy guard remains green.
## 13. Manual probe plan
For every seed/confuser, print only:
```text
process frame names
proposal family IDs + exact evidence spans
unary cue records
mention IDs/kinds/spans
quantity_entity bindings
unary bound roles + spans
ContractAssessment organ/runnable/blockers/hazards
```
Required assertions:
- positives: exactly one unary proposal, cue, relation, and assessment;
- no-proposal confusers: no unary cue authority, relation, or assessment;
- proposed/refused cases: proposal remains proposed and blockers are exact;
- no case produces a quantity-kind disposition unless the independent
quantity-family proposal exists;
- no case touches or compares against legacy semantic-ledger output.
## 14. Validation plan
Run targeted tests first:
```bash
uv run python -m pytest -q \
tests/test_foundational_families.py \
tests/test_construction_affordances.py \
tests/test_construction_proposal_seam.py \
tests/test_problem_frame_builder.py \
tests/test_problem_frame_contracts.py \
tests/test_quantity_entity_proposal.py \
tests/test_proportional_decrease_proposal.py \
tests/test_percent_partition_proposal.py \
tests/test_unary_delta_proposal.py \
tests/test_kernel_no_new_legacy_derivation_surfaces.py
uv run python -m core.cli test --suite smoke -q
uv run python -m compileall -q generate tests
uv run ruff check \
generate/foundational_families.py \
generate/construction_affordances.py \
generate/problem_frame.py \
generate/problem_frame_builder.py \
generate/problem_frame_contracts.py \
tests/test_foundational_families.py \
tests/test_construction_affordances.py \
tests/test_construction_proposal_seam.py \
tests/test_problem_frame_builder.py \
tests/test_problem_frame_contracts.py \
tests/test_quantity_entity_proposal.py \
tests/test_proportional_decrease_proposal.py \
tests/test_percent_partition_proposal.py \
tests/test_unary_delta_proposal.py
git diff --check
```
Run the canonical train and holdout serving probes without writing reports.
Both must retain:
```text
wrong_ids == []
```
The PR must state any correct/refused count movement observed live, but it may
not update governed report artifacts.
## 15. Implementation stop conditions
Stop immediately if implementation requires:
- actor, owner, source, target, pronoun, or temporal resolution;
- more than the exact `gained`/`lost` cue inventory;
- a generic action/event parser;
- widening quantity-entity proposal or disposition generation;
- reusing `transfer` as unary delta;
- importing or invoking the semantic-state derivation ledger;
- synthetic spans or nearest-mention selection;
- question-target or arithmetic logic;
- serving, report, eval fixture, teaching, memory, policy, identity, recall,
Vault, field, or algebra changes;
- a non-empty wrong-ID list.
If exact local topology cannot make both seed positives runnable under these
constraints, return to design. Do not widen the parser to make fixtures pass.
## 16. Copy-paste future implementation brief
> **PR title:** `feat(kernel): introduce diagnostic unary-delta proposal seam`
>
> **Dependency:** Begin only after
> `docs(kernel): authorize unary state-change delta slice` merges. Create a
> fresh worktree from current `origin/main`; run `source
> scripts/agent_startup.sh`; prove the authorization and preflight documents
> are in the base.
>
> Implement only `state_change.unary_delta`, not broad
> `state_change.transition`. Add a family-local frozen
> `GroundedUnaryDeltaCue` with the closed exact cue mapping `gained →
> gain/increase`, `lost → loss/decrease`. Create
> `ConstructionProposal(status="proposed", diagnostic_only=True,
> serving_allowed=False)` from the exact cue span before mention/relation
> binding. Reuse the existing exact `GroundedScalar`, `GroundedMention`, and
> `MentionBinding(binding_type="quantity_entity")` to publish one
> `BoundRelation(relation_type="unary_delta")` with action cue, delta quantity,
> and changed object roles. Add proposal-gated `assess_unary_delta(frame)` as
> the sole runnable/refused authority.
>
> Keep the first slice count-only, single-cue, single-scalar, single-object,
> and single-sentence. Do not assert actor/ownership, source/target,
> before/after values, containment, transfer, arithmetic, answers, or serving.
> Do not widen `_quantity_entity_proposals()` or
> `_quantity_kind_dispositions()`. Do not import or call
> `generate.derivation.state`. Use the exact refusal taxonomy, tests, manual
> probes, validation commands, and stop conditions in this preflight. Preserve
> every current proposal-first family and train/holdout `wrong_ids == []`.

View file

@ -0,0 +1,355 @@
# Foundational Family Specification: Unary State-Change Delta
**Family ID:** `state_change.unary_delta`
**Status:** Authorized for one future diagnostic-only implementation slice;
currently unimplemented
**Related ADRs:** ADR-0223, ADR-0224
**Controlling authorization:**
`docs/sessions/unary-state-change-delta-authorization-2026-06-21.md`
**Serving:** `diagnostic_only=True`, `serving_allowed=False`
## Purpose
This family represents the smallest locally groundable state-change event: one
explicit unary gain or loss cue applied to one explicit quantity/object pair.
It answers only this diagnostic question:
> Does this one local clause contain enough exact evidence to ground a unary
> delta relation?
It does not answer a word problem and does not construct a complete state
history. It is a subordinate family beneath the broad constitutional
`state_change.transition` idea. The broad family remains implementation-
unauthorized because transfer, containment movement, before/after state, and
unary delta have different role geometries.
## Family shape
```text
Signature: unary_delta
Candidate organ: unary_delta_transition
Relation type: unary_delta
```
The proposal-first chain is:
```text
exact action cue
→ ConstructionProposal(status="proposed")
→ exact cue + scalar/object binding
→ BoundRelation(relation_type="unary_delta")
→ ContractAssessment
→ diagnostic runnable/refused
```
`ConstructionProposal` is hypothesis only. `ContractAssessment` is the sole
runnable/refused authority.
## Allowed cue surface
The first implementation has a closed inventory:
| Surface | Action kind | Explicit direction |
|---|---|---|
| `gained` | `gain` | `increase` |
| `lost` | `loss` | `decrease` |
No stemming, lemmatization, synonym expansion, semantic similarity, or broad
verb classification is authorized. Exact lexical-boundary matching is allowed.
The following are explicitly outside the first inventory even when they may
describe change in ordinary language:
```text
gain, gains, gaining
lose, loses, losing
gave, received, bought, sold
ate, spent, used
put, took, moved
added, removed
grew, heated, cooled
was, became, now
```
Each widening requires evidence, confusers, and separate authorization.
## Required roles
| Role | Type and obligation |
|---|---|
| `action_cue` | Exactly one source-backed `GroundedUnaryDeltaCue` |
| `delta_quantity` | Exactly one quantity mention resolving to one exact `GroundedScalar` |
| `changed_object` | Exactly one exact local object mention |
| `direction` | `increase` or `decrease`, carried only by the explicit authorized cue |
| `local_binding_relation` | Exactly one existing `quantity_entity` edge from delta to changed object |
| `local_event_containment` | Cue, quantity, and object in that order inside one sentence/clause |
| `provenance_span` | Exact source spans for cue, scalar, object, binding, and relation |
The first slice is count-valued. Unit/object collision refuses.
## Optional and deferred roles
`actor` is the only catalog-visible optional SCT-1 role. It may be carried only
when exact grounded evidence already exists. The first implementation is not
required to publish it and may not add parsing to obtain it.
These are broader state-change roles, not roles in the first relation, and must
not be invented:
- owner;
- source;
- target;
- before quantity;
- after quantity;
- container or location;
- temporal marker.
- unit or measurement property.
They belong to future family geometries.
## Minimal typed cue
The future ProblemFrame may add one family-local frozen record:
```python
@dataclass(frozen=True, slots=True)
class GroundedUnaryDeltaCue:
cue_id: str
surface: str
action_kind: Literal["gain", "loss"]
direction: Literal["increase", "decrease"]
span: SourceSpan
```
This is not a generic event IR. It cannot represent tense, modality, negation,
actors, source/target, temporal ordering, causality, or arithmetic effects.
## Bound relation
The future relation uses existing `BoundRelation` / `BoundRole`:
```text
BoundRelation(
relation_type="unary_delta",
roles=(
action_cue → GroundedUnaryDeltaCue.cue_id,
delta_quantity → GroundedMention(quantity).mention_id,
changed_object → GroundedMention(object).mention_id,
),
evidence_spans=(action_span, quantity_span, object_span),
)
```
The relation carries no signed arithmetic operand. `increase` and `decrease`
are lexical event dispositions, not instructions to mutate state.
## Evidence span rules
Every evidence span must be an exact original-text slice:
```text
0 <= start <= end <= len(problem_text)
problem_text[start:end] == text
```
Additional rules:
- action, quantity, and object spans are non-overlapping;
- source order is `action < quantity < object`;
- no sentence terminator occurs between them;
- binding evidence is exactly `(quantity_span, object_span)`;
- relation evidence contains only exact role spans in deterministic order;
- proposal evidence is the action cue span only;
- no span is widened, concatenated, repaired, or synthesized;
- a computed event envelope is locality telemetry only and is not a semantic
evidence replacement.
## Proposal conditions
A proposal may be created only when:
- exactly one authorized cue occurs;
- no second/repeated unary cue occurs;
- no explicit percent, rate, comparison, transfer, transaction, containment,
actor-coordination, or list surface competes;
- the only compatible process-frame candidate is `consumption`.
A cue may propose even if downstream quantity/object evidence is missing. That
is the purpose of the proposal/assessment split. Missing evidence then refuses
at `ContractAssessment`.
## Assessment obligations
`assess_unary_delta(frame)` may report `runnable=True` only if:
1. exactly one proposal exists;
2. exactly one typed cue exists and is contained by proposal evidence;
3. cue surface, action kind, and direction match the closed mapping;
4. exactly one unary-delta bound relation exists;
5. the selected delta resolves to exactly one source-grounded scalar;
6. the selected changed object is exact and unambiguous;
7. exactly one existing `quantity_entity` edge joins them;
8. cue, delta, and object satisfy exact local order/containment;
9. every proposal, cue, binding, role, and relation span is exact;
10. no blocking context or ambiguity remains.
`runnable=True` means only that the diagnostic relation is role-complete. It
does not authorize a derived value, answer, state mutation, or serving path.
## Refusal classes
### Structural blockers
- `unary_delta_proposal_required`
- `action_cue_unbound`
- `action_cue_ambiguous`
- `delta_quantity_unbound`
- `delta_quantity_ambiguous`
- `changed_object_unbound`
- `changed_object_ambiguous`
- `local_binding_relation_unbound`
- `local_binding_relation_ambiguous`
- `local_event_containment_unproven`
- `delta_direction_unbound`
- `unit_object_conflict`
- `provenance_span_inexact`
### Ambiguity/context hazards
- `multiple_actor_surface`
- `delta_direction_ambiguous`
- `source_target_unlicensed`
- `pronoun_antecedent_unresolved`
- `cross_sentence_event`
- `implicit_total_unlicensed`
- `comparative_context`
- `rate_context`
- `percent_context`
- `passive_voice_unsupported`
- `temporal_context_ambiguous`
- `list_context_ambiguous`
- `transfer_unlicensed`
- `multiple_state_change_cues`
- `chained_state_changes`
- `event_assertion_unlicensed`
No-proposal confusers do not dispatch the assessment. Direct assessment of a
malformed frame remains fail-closed.
## Relationship to `binding.quantity_entity`
The family may consume the existing exact scalar/object
`MentionBinding(binding_type="quantity_entity")` as grounding evidence.
It must not:
- create or require a `binding.quantity_entity` proposal;
- create or consume a quantity-family `QuantityKindDisposition` outside its
proposal boundary;
- dispatch `assess_quantity_entity()`;
- make a plain quantity/entity surface look eventful;
- make an event cue sufficient without the exact binding.
This separation preserves #853. Shared evidence is not shared authority.
## Interaction exclusions
The family must not capture:
- `proportional_change.decrease_to_fraction`;
- `partition.percent_partition`;
- plain `binding.quantity_entity`;
- comparison language;
- percent or rate language;
- transfer or transaction language;
- containment movement;
- measurement changes;
- multiple or chained events.
## Positive examples
```text
Ana gained 3 marbles.
The jar lost 2 cookies.
The team gained 4 points.
```
For each, only cue, direction, delta quantity, and changed object are asserted.
No owner, before state, after state, or result is inferred.
## Negative examples
These must not propose:
```text
There are 12 apples.
Tom has 12 apples.
Tom gave Ana 3 apples.
Tom gave her 3 apples.
Tom gave Ana some apples.
Tom had 12 apples and gave Ana 3.
Ana has 3 more apples than Tom.
20% of the apples are red.
3 apples per child.
Tom bought 3 apples and 2 oranges.
The basket was filled with apples.
The tank has 84 degrees.
There are 12 apples. Tom ate 3.
Tom and Ana each got 3 apples.
Tom moved 3 apples from the box to the bag.
The box now has 5 more pencils.
Tom put 4 shells in the bag.
Ana gained 3 apples and lost 2.
```
These may propose from the exact cue but must refuse:
```text
Ana gained apples. # no explicit delta
Ana gained 3. # no changed object/binding
3 apples were gained by Ana. # passive/order topology
Ana gained 3 pounds. # unit/object collision in count-only SCT-1
```
## Non-goals
- broad `state_change.transition`;
- event/story parsing;
- lemmatization or synonym expansion;
- actor/owner or pronoun resolution;
- source/target and transfer conservation;
- containment movement;
- before/after or initial/final state;
- multiple events;
- measurement-valued delta;
- arithmetic or answer production;
- `SemanticLedger` or derivation integration;
- serving, eval/report mutation, teaching, memory, packs, policy, identity,
recall, Vault, field, or algebra changes.
## Implementation status and future gates
After this documentation PR:
- this family is authorized for one future bounded implementation PR;
- no code implements it;
- broad `state_change.transition` stays unauthorized;
- no serving behavior changes.
Implementation requires all gates in
`docs/sessions/unary-state-change-delta-preflight-2026-06-21.md`, including:
- fresh-base startup guard;
- exact proposal-first ordering;
- family-local cue type only;
- full positive/confuser tests;
- no legacy semantic-state imports;
- deterministic replay;
- exact span proof;
- current proposal-first family preservation;
- train and holdout `wrong_ids == []`;
- no governed report changes.
Any need to widen cue inventory, parse actors, infer cross-sentence roles, or
touch serving returns the work to design.