Skip to content

Commit a86cd90

Browse files
V45 Gate 14: Harden BTC Settlement Readback
Add explicit BTC settlement readback across quote acceptance, non-custodial wallet posture, PSBT posture, broadcast/observation/finality, rights transfer, delivery, and compensation routing. Expand settlement tests for non-final prepared/signed/broadcast/observed states, stale quote repair, compensation conservation failure, projection drift, missing repository delivery, persistence, and source-safe non-unlock behavior; add the Gate 14 checker and parity readback.
1 parent 6ea0619 commit a86cd90

5 files changed

Lines changed: 440 additions & 22 deletions

File tree

BITCODE_SPEC_V45_PARITY_MATRIX.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ Acceptance:
167167
- Repair tests cover underpayment, stale quote, missing finality, projection drift, missing delivery, and compensation conservation failure.
168168
- `check:v45-gate14` passes locally and in gate-quality CI.
169169

170+
Gate 14 implementation readback:
171+
172+
- BTC settlement, rights, delivery, repair, and source-to-shares readback binding: `packages/pipelines/asset-pack/src/asset-pack-settlement-rights-delivery.ts`.
173+
- Settlement success, non-finality, stale quote, underpayment, compensation failure, projection drift, missing delivery, persistence, and source-safety coverage: `packages/pipelines/asset-pack/src/__tests__/asset-pack-settlement-rights-delivery.test.ts`.
174+
- Closure checker: `check:v45-gate14`.
175+
170176
### Gate 15: Interface Disclosure, Route Vocabulary, And Public Documentation
171177

172178
Gate class: `interface-only`.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@
399399
"check:v45-gate11": "node scripts/check-v45-gate11-implementation-parity-audit.mjs",
400400
"check:v45-gate12": "node scripts/check-v45-gate12-state-vocabulary-commodity-model.mjs",
401401
"check:v45-gate13": "node scripts/check-v45-gate13-btd-scalar-volume-quote-conservation.mjs",
402+
"check:v45-gate14": "node scripts/check-v45-gate14-btc-settlement-rights-delivery-readback.mjs",
402403
"generate:v38-inference-surface-inventory": "node scripts/generate-v38-inference-surface-inventory.mjs",
403404
"check:v38-inference-surface-inventory": "node scripts/generate-v38-inference-surface-inventory.mjs --check",
404405
"check:v38-gate2": "node scripts/check-v38-gate2-inference-surface-inventory.mjs",

packages/pipelines/asset-pack/src/__tests__/asset-pack-settlement-rights-delivery.test.ts

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ describe('AssetPack settlement rights delivery boundary', () => {
180180
finalityReceipt: {
181181
finalityState: 'confirmed',
182182
},
183+
btcSettlementReadback: {
184+
quoteAcceptanceState: 'accepted',
185+
walletReadinessState: 'wallet_ready_non_custodial',
186+
psbtPreparationState: 'psbt_prepared_source_safe',
187+
psbtSignatureState: 'psbt_signed_by_reader_wallet',
188+
settlementFinalizationState: 'settlement_finalized',
189+
rightsTransferState: 'rights_transferred',
190+
deliveryState: 'source_unlocked_delivery',
191+
compensationRoutingState: 'compensation_routable',
192+
sourceUnlockAdmissible: true,
193+
serverCustody: false,
194+
walletPrivateMaterialVisible: false,
195+
},
183196
deliveryUnlock: {
184197
state: 'source_bearing_pull_request_ready',
185198
sourceBearingDeliveryVisibleToReader: true,
@@ -204,6 +217,7 @@ describe('AssetPack settlement rights delivery boundary', () => {
204217
expect(boundary.storageProjection.map((record) => record.recordKind)).toEqual(
205218
expect.arrayContaining([
206219
'btc_payment_observation',
220+
'btc_settlement_readback',
207221
'settlement_finality',
208222
'source_to_shares_compensation',
209223
'btd_read_receipt',
@@ -240,25 +254,83 @@ describe('AssetPack settlement rights delivery boundary', () => {
240254
expect(boundary.replayReceipt.verified.paymentMatchesQuote).toBe(false);
241255
});
242256

243-
it('fails closed until BTC finality is confirmed', () => {
257+
it.each(['prepared', 'signed', 'broadcast', 'observed'] as const)(
258+
'fails closed for %s BTC state before finality',
259+
(finalityState) => {
260+
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
261+
previewBoundary: previewBoundary(),
262+
finality: {
263+
finalityState,
264+
confirmations: 0,
265+
blockHeight: null,
266+
},
267+
createdAt: '2026-05-22T00:00:00.000Z',
268+
});
269+
270+
expect(boundary.state).toBe('blocked_until_payment_finality');
271+
expect(boundary.rightsTransferReceipt).toBeNull();
272+
expect(boundary.btdReadReceipt).toBeNull();
273+
expect(boundary.deliveryUnlock.sourceBearingDeliveryVisibleToReader).toBe(false);
274+
expect(boundary.btcSettlementReadback).toMatchObject({
275+
finalityState,
276+
settlementFinalizationState: 'not_finalized',
277+
rightsTransferState: 'rights_withheld',
278+
deliveryState: 'delivery_withheld',
279+
sourceUnlockAdmissible: false,
280+
rightsTransferAdmissible: false,
281+
});
282+
expect(boundary.repairPosture.nextActions).toEqual(
283+
expect.arrayContaining(['observe_btc_payment', 'wait_for_btc_finality']),
284+
);
285+
},
286+
);
287+
288+
it('fails closed when payment references a stale accepted BTC quote', () => {
289+
const preview = previewBoundary();
244290
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
245-
previewBoundary: previewBoundary(),
246-
finality: {
247-
finalityState: 'broadcast',
248-
confirmations: 0,
249-
blockHeight: null,
291+
previewBoundary: preview,
292+
paymentObservation: {
293+
expectedSats: preview.quoteReceipt.sats - 10,
294+
observedDebitSats: preview.quoteReceipt.sats - 10,
295+
observedCreditSats: preview.quoteReceipt.sats - 10,
250296
},
251297
createdAt: '2026-05-22T00:00:00.000Z',
252298
});
253299

254-
expect(boundary.state).toBe('blocked_until_payment_finality');
300+
expect(boundary.state).toBe('blocked_until_compensation_conservation');
255301
expect(boundary.rightsTransferReceipt).toBeNull();
256302
expect(boundary.deliveryUnlock.sourceBearingDeliveryVisibleToReader).toBe(false);
303+
expect(boundary.btcSettlementReadback.quoteAcceptanceState).toBe('stale_quote_repair_required');
304+
expect(boundary.btcSettlementReadback.blockerCodes).toContain('stale_btc_quote_or_payment_mismatch');
305+
expect(boundary.repairPosture.blockers).toContain('stale_btc_quote_or_payment_mismatch');
257306
expect(boundary.repairPosture.nextActions).toEqual(
258-
expect.arrayContaining(['observe_btc_payment', 'wait_for_btc_finality']),
307+
expect.arrayContaining(['refresh_stale_btc_quote', 'observe_btc_payment']),
259308
);
260309
});
261310

311+
it('fails closed when contributor compensation conservation fails after debit observation', () => {
312+
const preview = previewBoundary();
313+
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
314+
previewBoundary: preview,
315+
paymentObservation: {
316+
observedDebitSats: preview.quoteReceipt.sats,
317+
observedCreditSats: preview.quoteReceipt.sats - 1,
318+
},
319+
createdAt: '2026-05-22T00:00:00.000Z',
320+
});
321+
322+
expect(boundary.state).toBe('blocked_until_compensation_conservation');
323+
expect(boundary.sourceToSharesProof).toMatchObject({
324+
settlementConservation: {
325+
state: 'underpayment',
326+
settlementAdmissible: false,
327+
},
328+
});
329+
expect(boundary.rightsTransferReceipt).toBeNull();
330+
expect(boundary.btcSettlementReadback.compensationRoutingState).toBe('compensation_withheld');
331+
expect(boundary.repairPosture.blockers).toContain('source_to_shares_conservation_failed');
332+
});
333+
262334
it('withholds delivery when ledger, database, or object storage projections drift', () => {
263335
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
264336
previewBoundary: previewBoundary(),
@@ -274,6 +346,28 @@ describe('AssetPack settlement rights delivery boundary', () => {
274346
expect(boundary.repairPosture.nextActions).toContain('repair_ledger_database_storage_projection');
275347
});
276348

349+
it('withholds source unlock and rights delivery when repository delivery is missing', () => {
350+
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
351+
previewBoundary: previewBoundary(),
352+
pullRequestTarget: null,
353+
createdAt: '2026-05-22T00:00:00.000Z',
354+
});
355+
356+
expect(boundary.state).toBe('blocked_until_pull_request_delivery');
357+
expect(boundary.rightsTransferReceipt?.kind).toBe('btd.rights_transfer_receipt');
358+
expect(boundary.deliveryUnlock).toMatchObject({
359+
state: 'withheld',
360+
sourceBearingDeliveryVisibleToReader: false,
361+
pullRequestTarget: null,
362+
blockerCodes: expect.arrayContaining(['pull_request_target_missing']),
363+
});
364+
expect(boundary.btcSettlementReadback).toMatchObject({
365+
deliveryState: 'delivery_withheld',
366+
sourceUnlockAdmissible: false,
367+
});
368+
expect(boundary.repairPosture.blockers).toContain('pull_request_delivery_missing');
369+
});
370+
277371
it('persists settlement and rights delivery records onto execution state', () => {
278372
const execution = new Execution('asset-pack-settlement-test');
279373
const boundary = buildAssetPackSettlementRightsDeliveryBoundary({
@@ -293,5 +387,9 @@ describe('AssetPack settlement rights delivery boundary', () => {
293387
expect(execution.get('asset-pack/settlement', 'deliveryUnlock')).toMatchObject({
294388
state: 'source_bearing_pull_request_ready',
295389
});
390+
expect(execution.get('asset-pack/settlement', 'btcSettlementReadback')).toMatchObject({
391+
schema: 'bitcode.asset-pack.btc-settlement-readback',
392+
settlementFinalizationState: 'settlement_finalized',
393+
});
296394
});
297395
});

0 commit comments

Comments
 (0)