Fractional twin V3: selected-coordinate physics and bounded retirement

Date: 2026-08-28 Status: executable Claims/Trading child rung; not a live capability

Decision

Fractional keeps its own terms-owned K shard model. It does not lower through Rational's receipt recipe.

FractionalExposureTermsV2 is the sole durable owner of:

divide_exposure_shards_v2 remains the sole quotient/remainder boundary:

whole    = shard_atoms / D
consumed = whole * D
change   = shard_atoms - consumed

The division refuses a zero input, a sub-denominator input, an invalid coordinate, and checked-integer overflow. change remains in the same source Token account under the same Mint. There is no rounding credit, remainder ledger, or second supply projection.

Why the Rational child ABI is refused here

Rational's descriptor requires a receipt Mint and a K-wide coefficient vector. Generic Fractional V2 terms contain neither fact. They instead name K independent shard instruments and one exposure bundle. Supplying an arbitrary coefficient vector or making every coefficient equal to D would create a second Structured receipt recipe with no Fractional semantic owner. It would also create receipt/custody resources that the Fractional model does not need.

That is the divergence audit required by decision 0011's Option-A rule. The result is a refusal, not a hand-filled descriptor.

Selected-coordinate topology

plan_fractional_physical_v3 binds the exact V2 request to authenticated terms and produces this closed topology:

ActionExact quantity boundaryChild routeOuter authority
Wrapmint D * whole; no remainderone Claims call atomically mutates native Claims and invokes Token-2022holder
Transferraw same-Mint atoms; no divisionordinary Token-2022 TransferCheckedsource holder
WholeUnwrapburn consumed, keep change, return whole native Claimsone Claims + Token-2022 atomic callholder
TerminalRedeemsame division; positive payout remains Claims terminal-evaluator-ownedone Claims + Token-2022 + Custody atomic callholder
TerminalZeroBurnsame division; payout is exactly zeroone Claims + Token-2022 atomic callholder
Terminalizeno Token effectFractional state onlypermissionless
V2 ZeroSupplyRetirerefuseduse ordered V3 retirementpermissionless

This module does not accept a caller-supplied payout and does not reproduce the denominator arithmetic. Transfer intentionally bypasses the family caller: it changes only Token-owned balances.

Durable retirement split

The V2 retirement planner expands all K Mint closures into one route. That is not a viable runtime-width topology: an ALT can reduce packet bytes but cannot raise devnet's 64-account lock limit.

V3 adds a 296-byte exact cursor and a 288-byte exact request with three actions:

  1. Begin binds release, Market, terms, Token program/behavior, exposure, producer root, RentCredit, width, cursor rent principal, and the next root revision.
  2. RetireCoordinate accepts only coordinate == next_coordinate, rebinds all identities to the immutable terms, authenticates the exact terms-owned Mint and Claims reserve, requires both supply and reserve balance to be zero, and advances the cursor and revision by exactly one.
  3. Finish has no K-account tail. It is available only at next_coordinate == K and produces fixed-width terminal evidence before the cursor/root and their historical rent principal are closed.

Replay, skip, Mint substitution, nonzero supply, nonzero reserve, missing Mint or Claims authentication, stale revision, reserved-byte mutation, and terms/exposure substitution all refuse in the hostile corpus.

The cursor is a progress fact only. Token-2022 remains the owner of Mint supply, Claims remains the owner of native reserve balances, and Lifecycle Rent remains the owner of rent refund accounting.

Exact candidate frame census

topology_v3.rs freezes the maximum-distinct account geometry for the missing caller. The fixture gives every permitted role a distinct key, compiles a single-instruction v0 message against an activated-table shape, includes the required 64-byte signature, and asserts exact wire bytes. These are frame contract numbers, not real-ELF execution evidence:

TransactionUnique locksInstruction dataFully signed v0 bytesPacket margin
Wrap / WholeUnwrap31417682550
Direct TransferChecked5102221,010
TerminalRedeem / TerminalZeroBurn44417708524
Terminalize18417656576
Retirement Begin8289508724
Retirement Coordinate22289536696
Retirement Finish10289512720

Every transaction is below 64 unique locks and 1,232 bytes. At K = 256, the retirement sequence is one Begin, 256 identical bounded Coordinate transactions, and one fixed Finish. Width changes transaction count, never a transaction frame. This is the architectural fix; the ALT is only packet compression.

Real SBF frame measurement

The Claims link was freshly rebuilt from this worktree with the repository's measurement flags and then read with tools/sbf-frame-sizes.py:

RUSTC_BOOTSTRAP=1 RUSTFLAGS="-Zemit-stack-sizes --emit=obj,link" \
  CARGO_TARGET_DIR=<fresh> cargo build-sbf \
  --manifest-path programs/dclutch-claims-sbf/Cargo.toml -- --locked

The build emitted zero SBF stack-overwrite diagnostics. The measurement object SHA-256 was 0172fc58454973ff637f9a91feb0f07ecfaba5af78e71cb42cab57aefeda0c85. It contained 201 measured frames, none at or over 4,096 bytes. The relevant Fractional frames were:

FunctionStatic frameMargin
open Claims + Token child3,904192
boxed terminal Claims/Custody child3,200896
terminal Fractional handler2,8801,216
terminal receipt emission1,6002,496

The public Claims selector is split into bounded no-inline routing frames. The split changes neither instruction bytes nor family order; it prevents adding the Fractional family from causing the otherwise-green legacy selector to be recompiled as one 4,672-byte frame.

What is implemented

What still blocks a live Fractional capability

The native Claims/Token/Custody child and its Trading composition arm now exist. Integration still requires:

  1. generated EffectProgram, AccountProfile, RequestProfile, and ExecutionStrategy artifacts whose exact expanded frame matches the census;
  2. the outer Trading transition that advances the sole Fractional-root revision only after the verified child receipt, plus its rollback campaign;
  3. a migration or new producer-root version that authenticates the V3 cursor PDA and its lifecycle rent;
  4. one-coordinate Claims Position close plus Token Mint close in the retirement step, and fixed final Core/Lifecycle-Rent closure;
  5. a real-ELF caller-backed late-Token-failure rollback campaign, 20-seed CU mean, and checked release bindings.

Until those exist, the current 14-action release remains refused and this code must not be described as a complete executable or deployed Fractional market.

Superseded 2026-08-29 by FRACTIONAL_LIFECYCLE_2026_08_29.md

The section below records the first execution and the width wall as they stood mid-morning. Both have moved since, so read the lifecycle doc instead:

* the 256/258 wall was ruled deliberate and closed — the campaign compiles its own geometry rather than widening the terms or the shared fixture; * all four published actions, including both terminal ones, now execute and commit; * the refusal blamed on width below turned out to be a shipped defect that made terminal settlement dead for every representation, fixed in 4953bada; * the operative width bound is 64, not 256 and not the arithmetic maximum 98.

Update 2026-08-29: first execution, and the width wall it found

programs/dclutch-claims-sbf/program-test/fractional-atomic/ now executes the production fractional_atomic_v3 handler against real ELFs. The route needed a caller able to sign the Trading caller-authority and the Fractional root in one invoke_signed; test-programs/fractional-atomic-caller/ is that caller.

Executed: the transaction enters the real Claims ELF at CPI depth two and consumes 37,281 compute units inside the Fractional route before refusing. The exact 31-account frame, both invoke_signed authorities, the four finalized record pairs, the activated release set, the decoded root, and the Token-2022 Mint controller all authenticate against real bytes.

Not executed, and blocked on a decision rather than on work:

So no Fractional capability can bind a Market wider than 256 outcomes, and the 258-outcome fixture cannot carry one at all. The wrap therefore refuses with ClaimsSbfError::Economic. Resolving it means either raising the terms ceiling or bounding Fractional Markets at 256 and giving the campaign its own narrower Product/LBV2 fixture. Until then Fractional Wrap has no committed on-chain effect, and no rollback, transfer, or terminal Claims/Custody/Token campaign exists.