The burn a holder can actually perform, and the layer the Trading half is missing — 2026-08-31

Written by FRACCHECK-2, from FRACCHECK's ten-commit Trading half (docs/evidence/FRACTIONAL_CLAIM_CHECK_2026_08_30.md) and docs/design/CLAIM_CHECK_COMPACTION_V1.md §17.4.

Result

No. The sleeping shard holder does not get paid by a stranger's compaction end to end in the campaign, because there is no compaction to run — and the reason there is none is one layer deeper than the re-size modelled.

What the campaign does now prove, end to end and against the audited Token program, is the leg that was actually in doubt: the burn is performable. A Mint carrying the whole shard profile has its permissioned-burn authority moved from a program-derived root to a program-derived escrow while the root is still alive; the old authority is powerless afterwards; and the holder's own signature plus the escrow's invoke_signed burns the shards. §17.4's sound shape is no longer a design sentence.

What is not proved is everything upstream of that burn, and the sizing of it moves again. FRACCHECK re-sized the fractional half from eight commits to fourteen because the redemption route it costed could not exist. The remaining ten assumed the Trading half was adding a route to a composition path that RetireCoordinate already used. That path does not exist either. RetireCoordinate is composed at one layer of three, and the two it is missing are the two a compaction route would need most.

Three commits landed. The re-size below says what the rest costs and why the number moved.

What landed

commitwhat
fb125a44read_compacted_shard_mint in dclutch-token-svm: the split-controller sibling, and the disjointness that keeps it from weakening read_mint
ee3d1e52the hand-off campaign with a derived escrow and a whole-profile Mint, with the behavior profile run over the bytes Token-2022 wrote
this onethe amendment and the re-size

The split-controller arm, and why it is a disjointness

read_mint requires one controller to be the Mint authority and the close authority and the burn authority. After compaction's SetAuthority the burn authority is the escrow and the other two are still the root, so the sibling nominates the burn role separately:

Token2022BehaviorProfileV2::read_compacted_shard_mint(
    program_id, mint_key, mint_data, expected_controller, expected_burn_authority,
) -> Result<Token2022CompactedShardMintFactsV2>

Both entry points now go through one walk, read_profile_mint, which authors the base state, the padding, the account-type byte, the freeze refusal, the extension set and the optional metadata pair for both. read_mint names the controller twice; the sibling names the burn separately and refuses burn == controller.

That refusal is what makes the property a theorem rather than an intention:

A Mint the live arm admits has burn == mint authority. A Mint the compacted arm admits has burn != controller == mint authority. No byte string is admitted by both, whatever either caller nominates.

no_mint_bytes_are_admitted_by_both_arms asserts it over eight fixtures crossed with every nomination pair, and counts the admissions on each side — two live, four compacted — so a disjointness that held because nothing was ever admitted fails instead of passing.

Two decisions worth naming. A second facts type, because Token2022BehaviorMintFactsV2::controller promises one key bound identically as Mint, close and burn authority, and a compacted Mint is precisely where that promise stops holding; widening the old type would have made its own doc false. No check_compacted_shard_mint, because a compacted coordinate's supply is the durable claim and any holder's redemption lowers it between a request being built and it landing — pinning it would refuse an honest retirement because somebody else redeemed first. read_mint's doc says an on-chain caller with an independent expectation must use check_mint; this is a third caller class, and it is argued in the doc rather than left as a hole.

The profile preimage and its digest are untouched. This is a second reader of one profile, not a second profile.

The escrow is derived now, and the profile ran on real bytes

FRACCHECK named two gaps in its own campaign. Both are closed.

The escrow was a keypair. It is now built by dclutch-claim-check-escrow-signer-test-sbf from ClaimCheckEscrowSeedsV1 — the shipped recipe, bump included — and signed with invoke_signed. The campaign re-derives the same address a second time from the published CLAIM_CHECK_ESCROW_SEED_V1 constant and asserts address and bump agree, so a change to the domain or the seed order fails the campaign rather than quietly testing a different address. Both sides of the hand-off are program-derived, which is production's shape: an authority that cannot sign for itself hands to one that can.

The Mint carried only the burn half. It now carries the whole shard profile — root as Mint authority, MintCloseAuthority naming the root, PermissionedBurn naming the root, no freeze authority. That is why the signer program needs a mint action at all: a Mint funded by a convenient keypair authority is not a shard Mint, and running the profile over one would prove nothing about the family's Mints.

And the third thing, which neither campaign had. The behavior profile now runs against the bytes Token-2022 itself wrote, at every stage:

stageread_mint(root)read_compacted_shard_mint(root, escrow)
before the hand-offadmits, supply 5,000refuses AuthorityMismatch
after SetAuthorityrefuses AuthorityMismatch — and under escrow tooadmits, controller root, burn escrow
after the burn—admits, supply 4,000

fb125a44 proved the two arms disjoint over fixtures this repository builds. This proves the swap happens on a real SetAuthority — the half a hand-built fixture can never supply, that the shape the profile refuses is the shape the chain actually produces.

Also pinned, because without it the campaign would be equally consistent with a hand-off that had relaxed the extension: after the re-point a standard burn is still InvalidInstruction and a permissioned burn naming the escrow without its signature is still MissingRequiredSignature. The hand-off moves which second signature a burn needs, and never removes it.

One refusal shape, pinned rather than worked around

A failed CPI is not recoverable. The runtime propagates the inner program's refusal and never consults what the caller returns, so a stranger's hand-off surfaces Token-2022's own OwnerMismatch (0x4), not the caller's band. The campaign asserts the Token code for that reason, and the signer program's TokenCpi arm is documented as unreachable-in-a-transaction-result rather than deleted. This is what a validator log will show for the real Claims route too — a fractional compaction whose hand-off is refused reports 0x4, not a Claims code, and a runbook that promised otherwise would be wrong.

The finding: RetireCoordinate is composed at one layer of three

§17.4 costs the Trading half as "a route in a second ELF", on the precedent that fractional_retirement_v3.rs is permissionless and Trading-composed today. The precedent is weaker than the sentence.

A Claims route reached from Trading's Hot path has to be admitted at three layers. RetireCoordinate is present at one:

layerwhereRetireCoordinate
executionprograms/dclutch-trading-sbf/src/claims_composition_v3.rs — route_authority, ReceiptKindV3, fractional_root_signer, the receipt verifierpresent (route_authority:646, fractional_root_signer:869)
composition decodecrates/dclutch-claims-svm/src/composition_v3.rs::decode_selected_with_external, and hot_v3.rs::decode_claims_composition_boxed_v3absent
artifact geometrycrates/dclutch-fractional-claim-operator/src/artifacts_v4.rs::action_geometry / encode_effect / encode_account_profileabsent

Verified rather than inferred: FRACTIONAL_RETIREMENT_REQUEST_MAGIC_V3 occurs in programs/dclutch-trading-sbf/src/ exactly twice, both inside claims_composition_v3.rs — the import and the route_authority arm. It occurs zero times in composition_v3.rs. decode_claims_composition_boxed_v3 builds a ClaimsExternalOnceV3 only when the family request carries FRACTIONAL_EXPOSURE_REQUEST_MAGIC_V2; every other magic yields None, and decode_selected_with_external's magic chain then falls through to ClaimsCompositionErrorV3::Route. action_geometry matches four FractionalExposureActionV2 arms and returns InvalidInput for anything else, so no AccountProfileV2 or EffectProgram bundle exists that could name a retirement frame.

So the only thing that drives RetireCoordinate today is a test-only caller (test-programs/liability-basis-caller), exactly as fractional_atomic_v3's only production composition is the exposure magic. "Trading-composed" is true of the signature propagation and not yet true of the route selection. A fractional compaction route inherits that debt rather than borrowing a solved problem: it needs its own arm at all three layers, and the two missing ones have no precedent in this family to copy.

This is not an argument against the design. §17.4's shape is right and the burn leg is now executed. It is a correction to what the remaining work is.

The re-size, again

FRACCHECK: fourteen commits, two programs, two cohorts; four landed, ten remaining. Three more have landed and the remaining count does not fall by three, because the composition gap adds work the ten did not contain.

#commitstatusnote
1–3brecord, plans, sub-bands, burn campaignlandedFRACCHECK, 2c3b0934..afd422d1
4read_compacted_shard_mintlandedfb125a44; grew a disjointness theorem the re-size did not ask for, and it is the part worth keeping
10anew — the hand-off on a derived escrow and a whole-profile Mintlandedee3d1e52; was inside "the campaign" in the re-size, and is separable because it needs no dClutch route
12the design amendmentlandedthis document and §17.5
5a Trading route that composes fractional compactionnot writtenand now three arms, not one — see the finding
5bnew — the composition-decode armnot writtendecode_selected_with_external + decode_claims_composition_boxed_v3; no precedent in this family, because retirement never got one either
5cnew — the artifact geometrynot writtenaction_geometry, encode_effect, encode_account_profile, plus a FractionalFrameKindV3 lock-count row
5dnew — the compaction requestlanded6c26d01a; embeds TerminalSettlementRequestV3 verbatim, for the reason the native request does. The receipt is deliberately not written: a request's field set is determined by what the route must authenticate, which is knowable now; a receipt's is determined by what the route produces, which is not
6the Claims compaction routenot writtenthe largest single piece: a ~48-account frame wrapping the 36-account terminal frame, the SetAuthority leg, the record write, the escrow update
7the burn-and-pay redemption routenot writtenthe escrow signs as approver — the leg ee3d1e52 now proves is sound
8RetireCoordinate's compacted armnot writtenreads #4 instead of check_mint, admits nonzero supply, skips execute_mint_close; its frame gains the record and the escrow, which ripples into 5c
9the fractional escrow closenot writtena mixed escrow's outstanding count, as before
10the route campaignnot writtensmaller than sized now: the Token-level legs are paid
11the operator surfacenot writtenas sized

Seventeen commits, of which seven have landed. Ten remain — the same number FRACCHECK handed over, after three landed, because the composition gap put three back. The honest reading is that the code remaining is about what was estimated and the surface is wider: three composition arms in two crates neither FRACCHECK nor FRACR3 had looked at.

If the ten was meant as "one lane", it is wrong by roughly a factor of three. Commit 6 alone is a lane. The four that landed here are the four that could be finished without the route existing, and choosing them over a partially built route was deliberate: a half-written 48-account frame would have to be read before it could be trusted, and nobody would trust it.

Not verified