Skip to content

Commit f7b91fe

Browse files
Merge pull request #38 from engineeredsoftware/v30/gate-6-source-to-shares-proof-cleanup
V30 Gate 6: Source-To-Shares Proof Cleanup
2 parents a91d313 + 6987c6b commit f7b91fe

16 files changed

Lines changed: 1719 additions & 4 deletions

.github/workflows/bitcode-gate-quality.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ jobs:
8484
node scripts/check-v30-gate3-bitcoin-taproot-psbt-fee-rigor.mjs --skip-branch-check
8585
node scripts/check-v30-gate4-btd-assetpack-mint-read-receipts.mjs --skip-branch-check
8686
node scripts/check-v30-gate5-testnet-ledger-projection-hardening.mjs --skip-branch-check
87+
node scripts/check-v30-gate6-source-to-shares-proof-cleanup.mjs --skip-branch-check
8788
else
8889
echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2
8990
exit 1

BITCODE_SPEC_V30.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,27 @@ Gate 4 receipt precision:
218218
root, read right, and ledger projection root.
219219
- Rights-transfer receipts require confirmed BTC fee finality before the BTD
220220
right/license transfer can admit protected source visibility to the Reader.
221+
222+
Gate 6 source-to-shares precision:
223+
224+
- `SourceToSharesProof` is package-owned in `@bitcode/btd` and is the reusable
225+
accounting boundary for later Exchange work.
226+
- Proof inputs bind accepted Need root, Finding Fits result root, admitted fit
227+
deposits, per-deposit measurement roots, normalized measurement units,
228+
fit/provenance quality basis points, BTD range projection, accepted BTC fee
229+
quote, payment observation, and optional ancestry review.
230+
- Contribution weights are deterministic largest-remainder basis points over
231+
measured fit-deposit weights. The trace records total clipped weight,
232+
remainder order, tie-break policy, and provisional/final shares.
233+
- BTD range slices and BTC fee allocations are both derived from the same
234+
contribution weights. Range slices may be zero-cell/refit-tail slices when the
235+
measuremint tail or small range leaves a contributor with no cell.
236+
- Settlement conservation carries distinct no-overpayment and no-underpayment
237+
theorem verdicts, exact allocation conservation, blocker reasons, a
238+
conservation root, and a reconciliation-compatible check.
239+
- A source-to-shares proof may represent balanced, overpayment, underpayment, or
240+
drifted state, but only balanced state is settlement-admissible for protected
241+
source unlock and delivery.
221242
Prepared, signed, broadcast, replaced, reorged, and failed fee receipts do
222243
not unlock protected source.
223244

@@ -688,9 +709,9 @@ Terminal renders those roots with blockers and decision rows so operators can un
688709
- current member verdict shape: prepared, signed, broadcast, confirmed, blocked, reconciled.
689710
- current theorem-by-theorem closure reading: no paid source unlock without fee and right-transfer readback.
690711
- current theorem-to-replay grouping: quote root, payment path, ledger observation, BTD state, reconciliation.
691-
- minimum artifact/replay binding set: fee quote root, txid or blocked receipt, range id, license id, journal id.
692-
- current proof-object fields: quote, payment, range, license, reconciliation, blocker.
693-
- generated-artifact and test bindings: BTD tests, settlement route tests, staging readback.
712+
- minimum artifact/replay binding set: fee quote root, payment receipt root, txid or blocked receipt, BTD range slice roots, contribution weights, conservation root, license id, journal id.
713+
- current proof-object fields: quote, payment observation, fit deposits, contribution weights, range slices, settlement allocations, no-overpayment, no-underpayment, zero-cell/refit tail, ancestry evidence, reconciliation check, blocker.
714+
- generated-artifact and test bindings: BTD source-to-shares tests, settlement route tests, staging readback.
694715
- fail-closed conditions: settlement conservation drift, stale quote, or chain reorg.
695716

696717
### Disclosure-boundary

BITCODE_SPEC_V30_DELTA.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,29 @@ Closure acceptance:
221221
- no-overpayment and no-underpayment invariants are testable;
222222
- later Exchange work can reuse the proof without reinterpreting V30 accounting.
223223

224+
Gate 6 implementation centers on `packages/btd/src/source-to-shares.ts`.
225+
`SourceToSharesProof` is package-owned and binds accepted Need roots, Finding
226+
Fits result roots, admitted fit deposits, per-deposit measurement roots,
227+
normalized measurement units, fit/provenance basis points, deterministic
228+
largest-remainder contribution weights, BTD range slices, accepted BTC fee
229+
quote, payment observation, exact source credit allocation, settlement
230+
conservation, zero-cell/refit tail posture, and ancestry review evidence when
231+
available.
232+
233+
The proof separates conservation from settlement admission. It can represent a
234+
balanced, overpayment, underpayment, or drifted settlement without losing the
235+
proof roots needed for repair. `noOverpayment` and `noUnderpayment` are separate
236+
theorem verdicts, while `allocationConserved` proves that source credit
237+
allocation sums to the accepted BTC fee quote. Only a balanced proof is
238+
settlement-admissible. The proof exposes a reconciliation-compatible
239+
`SettlementConservationCheck` so ledger/database repair can consume the same
240+
accounting without route-local reinterpretation.
241+
242+
Gate 6 evidence is covered by `packages/btd/__tests__/source-to-shares.test.ts`,
243+
`packages/api/src/routes/__tests__/btd-crypto.test.ts`,
244+
`uapi/app/api/btd/source-to-shares-proof/route.ts`, and
245+
`scripts/check-v30-gate6-source-to-shares-proof-cleanup.mjs`.
246+
224247
### Gate 7: Bridge Readiness Research Boundaries
225248

226249
Gate 7 records bridge-readiness without false chain-of-record claims.

BITCODE_SPEC_V30_NOTES.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,30 @@ persists schema-compatible repair receipts; richer drift classes, repair
167167
actions, readback receipts, and proof roots remain in the report payload until a
168168
future schema expansion admits more columns.
169169

170+
## Gate 6 source-to-shares cleanup notes
171+
172+
Gate 6 moves settlement-source-to-shares accounting into the BTD package instead
173+
of relying on older protocol-local artifact builders. The package primitive
174+
normalizes accepted fit deposits into contribution weights through a deterministic
175+
largest-remainder method, allocates BTD range slices with zero-cell/refit-tail
176+
posture when a fit receives no cell, allocates BTC fee credits exactly by the
177+
same contribution weights, and emits one proof root that binds the fee quote,
178+
payment observation, measurements, fit deposits, range slices, allocation roots,
179+
settlement conservation, and ancestry evidence.
180+
181+
The proof intentionally keeps no-overpayment and no-underpayment as distinct
182+
theorem verdicts. This lets a repair cockpit distinguish a Reader overpayment
183+
from an inadmissible underpayment instead of collapsing both into generic
184+
conservation drift. The same proof emits a reconciliation-compatible settlement
185+
conservation check so Gate 5 ledger/database repair can pause unlock or delivery
186+
from the source-to-shares proof directly.
187+
188+
The source-to-shares API route does not write a new registry table in Gate 6.
189+
It returns a JSON-safe proof settlement and a Terminal journal entry. Later
190+
Exchange work may persist richer proof rows after schema admission, but it must
191+
reuse the package proof rather than recomputing contribution weights, range
192+
slices, or BTC fee allocations.
193+
170194
4. **Gate 4: BTD AssetPack Mint And Read Receipts**
171195
- Make BTD mint, read, and rights-transfer receipts typed, proof-rooted, stored, streamed, and source-safe.
172196

BITCODE_SPEC_V30_PARITY_MATRIX.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ No `_legacy/` source is active source truth.
5959
| Bitcoin Taproot PSBT fee rigor | Gate 3 | `packages/btd/src/btc-fee-operation.ts`, `packages/btd/src/bitcoin-fees.ts`, BTD/API tests, gate checker | drafted | BTC fee and signer states are typed, testnet/mainnet-safe, no-custody, Taproot/PSBT aware, and proof-rooted. |
6060
| BTD AssetPack mint/read receipts | Gate 4 | `packages/btd/src/receipts.ts`, `packages/btd/src/api-boundaries.ts`, asset-pack harness evidence, Terminal detail snapshot/read model tests, gate checker | drafted | Mint, read, and rights-transfer receipts bind BTD range, preview, paid unlock, delivery, and ledger projection. |
6161
| Testnet ledger projection hardening | Gate 5 | `packages/btd/src/reconciliation.ts`, API route tests, asset-pack harness evidence, Terminal journal reconciliation UI/tests, gate checker | drafted | Ledger/database/object-storage/private facts are distinct; staging-testnet readback is secret-free; drift, quarantine, retry, and unlock blocking are tested. |
62-
| Source-to-shares proof cleanup | Gate 6 | BTD/source-to-shares proof primitives, settlement conservation tests | pending | Measurement contribution, fee allocation, zero-cell/refit tail, and conservation invariants are testable. |
62+
| Source-to-shares proof cleanup | Gate 6 | `packages/btd/src/source-to-shares.ts`, API route boundary, focused BTD/API tests, gate checker | drafted | Measurement contribution, fee allocation, zero-cell/refit tail, ancestry evidence, and conservation invariants are testable. |
6363
| Bridge-readiness research boundaries | Gate 7 | Protocol/BTD research notes, policy posture tests, docs | pending | Bridge paths are documented as research until admitted by explicit future proof and policy. |
6464
| Protocol telemetry/proof hooks | Gate 8 | telemetry schema, proof hooks, generated-artifact inventory, tests | pending | Receipts, fee states, projections, and source-to-shares facts emit source-safe telemetry and proof hooks. |
6565
| Interface integration regression | Gate 9 | Terminal/API/MCP/ChatGPT App adapters and tests | pending | Existing interfaces consume package-owned objects without regressing V29 behavior. |
@@ -175,6 +175,23 @@ No `_legacy/` source is active source truth.
175175
- Gate 5 does not implement source-to-shares contribution accounting; Gate 6 owns that.
176176
- Gate 5 does not promote V30 or change the active canon pointer.
177177

178+
## Gate 6 Parity
179+
180+
| Requirement | Source evidence | Current V30 judgment |
181+
| --- | --- | --- |
182+
| Source-to-shares proof is package-owned | `packages/btd/src/source-to-shares.ts`, `packages/btd/src/api-boundaries.ts` | drafted |
183+
| Contribution weights, range slices, BTC fee allocation, and conservation are deterministic | `packages/btd/__tests__/source-to-shares.test.ts` | drafted |
184+
| API and Next route consume the package proof without route-local accounting | `packages/api/src/routes/btd-crypto.ts`, `uapi/app/api/btd/source-to-shares-proof/route.ts`, `packages/api/src/routes/__tests__/btd-crypto.test.ts` | drafted |
185+
| Gate checker protects source-to-shares cleanup | `scripts/check-v30-gate6-source-to-shares-proof-cleanup.mjs`, `pnpm run check:v30-gate6`, gate-quality workflow | drafted |
186+
187+
## Gate 6 accepted boundaries
188+
189+
- Gate 6 does not create a new physical source-to-shares registry table; the API route returns proof payloads and Terminal journal entries until schema admission.
190+
- Gate 6 does not expose protected source before paid unlock and delivery admission.
191+
- Gate 6 does not alter `$BTD` supply law or measureminting curve.
192+
- Gate 6 does not admit bridge chain-of-record semantics; Gate 7 owns bridge-readiness boundaries.
193+
- Gate 6 does not promote V30 or change the active canon pointer.
194+
178195
## completion condition
179196

180197
Gate 1 is complete when the V30 draft family validates, `check:v30-gate1` passes, workflow posture is V30-aware, README and roadmap reflect V30 initiation, V31-V37 scopes are current enough to guide future gates, diff hygiene passes, and the gate branch is committed and pushed for review into `version/v30`.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"check:v30-gate3": "node scripts/check-v30-gate3-bitcoin-taproot-psbt-fee-rigor.mjs",
6060
"check:v30-gate4": "node scripts/check-v30-gate4-btd-assetpack-mint-read-receipts.mjs",
6161
"check:v30-gate5": "node scripts/check-v30-gate5-testnet-ledger-projection-hardening.mjs",
62+
"check:v30-gate6": "node scripts/check-v30-gate6-source-to-shares-proof-cleanup.mjs",
6263
"check:spec-quality": "node scripts/run-bitcode-spec-quality.mjs --mode basic",
6364
"check:spec-quality:title": "node scripts/run-bitcode-spec-quality.mjs --mode strict-from-title",
6465
"check:spec-quality:v24": "node scripts/run-bitcode-spec-quality.mjs --mode strict-version --version V24",

packages/api/src/routes/__tests__/btd-crypto.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
buildBtdMintDraft,
2626
buildBtdOrganizationInterfaceAuthorityDecision,
2727
buildBtdReadAccessDecision,
28+
buildBtdSourceToSharesProofSettlement,
2829
buildBtdTerminalJournalSettlement,
2930
buildBtcFeeQuote,
3031
createBtdMeasureMintState,
@@ -41,6 +42,7 @@ import {
4142
buildPostBtdMintDraftRoute,
4243
buildPostBtdOrganizationInterfaceAuthorityRoute,
4344
buildPostBtdReadAccessRoute,
45+
buildPostBtdSourceToSharesProofRoute,
4446
buildPostBtdTerminalJournalRoute,
4547
} from '../btd-crypto';
4648

@@ -147,6 +149,63 @@ function mintDraftRequestBody(overrides: Record<string, unknown> = {}) {
147149
};
148150
}
149151

152+
function sourceToSharesProofBody(overrides: Record<string, unknown> = {}) {
153+
return {
154+
readId: 'read-api-source-to-shares-1',
155+
assetPackId: 'asset-pack-api-source-to-shares-1',
156+
acceptedNeedRoot: 'accepted-need-api-root',
157+
findingFitsResultRoot: 'finding-fits-api-root',
158+
fitDeposits: [
159+
{
160+
depositId: 'deposit-api-a',
161+
assetPackId: 'fit-asset-api-a',
162+
depositorWalletId: 'wallet-depositor-api-a',
163+
sourceManifestRoot: 'source-api-root-a',
164+
findingFitsResultRoot: 'finding-fits-api-root-a',
165+
measurementRoot: 'measurement-api-root-a',
166+
normalizedMeasurementUnits: '20000',
167+
fitQualityBps: 10000,
168+
provenanceBps: 10000,
169+
accepted: true,
170+
},
171+
{
172+
depositId: 'deposit-api-b',
173+
assetPackId: 'fit-asset-api-b',
174+
depositorWalletId: 'wallet-depositor-api-b',
175+
sourceManifestRoot: 'source-api-root-b',
176+
findingFitsResultRoot: 'finding-fits-api-root-b',
177+
measurementRoot: 'measurement-api-root-b',
178+
normalizedMeasurementUnits: '10000',
179+
fitQualityBps: 10000,
180+
provenanceBps: 10000,
181+
accepted: true,
182+
},
183+
],
184+
btdRange: {
185+
assetPackId: 'asset-pack-api-source-to-shares-1',
186+
rangeStart: 40,
187+
rangeEndExclusive: 43,
188+
tokenCount: 3,
189+
measureMintReceiptRoot: 'measuremint-api-root',
190+
},
191+
feeQuote: {
192+
quoteId: 'quote-api-source-to-shares-1',
193+
quoteRoot: 'quote-api-source-root',
194+
grossSats: '9000',
195+
},
196+
paymentObservation: {
197+
paymentReceiptRoot: 'payment-api-source-root',
198+
observedDebitSats: '9000',
199+
observedCreditSats: '9000',
200+
finalityState: 'confirmed',
201+
txid: 'txid-api-source-to-shares',
202+
},
203+
exchangeSequence: '18',
204+
issuedAt,
205+
...overrides,
206+
};
207+
}
208+
150209
describe('BTD crypto API builders', () => {
151210
it('builds a deterministic mint draft from accepted Read-Fit semantic units', () => {
152211
const draft = buildBtdMintDraft(mintDraftInput());
@@ -879,6 +938,35 @@ describe('BTD crypto API builders', () => {
879938
);
880939
});
881940

941+
it('builds source-to-shares proof settlements with exact conservation roots', () => {
942+
const settlement = buildBtdSourceToSharesProofSettlement({
943+
actorId: 'user-1',
944+
...sourceToSharesProofBody(),
945+
exchangeSequence: 18n,
946+
} as any);
947+
948+
expect(settlement.kind).toBe('btd_source_to_shares_proof_settlement');
949+
expect(settlement.proof.contributionWeights.map((entry) => entry.shareBps)).toEqual([
950+
6667,
951+
3333,
952+
]);
953+
expect(settlement.proof.settlementConservation).toMatchObject({
954+
state: 'balanced',
955+
settlementAdmissible: true,
956+
allocationConserved: true,
957+
});
958+
expect(
959+
settlement.proof.settlementAllocations.reduce(
960+
(sum, route) => sum + route.allocatedSats,
961+
0n,
962+
),
963+
).toBe(9000n);
964+
expect(settlement.terminalJournalEntry.transactionKind).toBe('settlement_finalization');
965+
expect(settlement.terminalJournalEntry.receiptRoots).toContain(
966+
settlement.proof.settlementConservation.conservationRoot,
967+
);
968+
});
969+
882970
it('builds deployment readiness, telemetry, and upgrade settlements', () => {
883971
const readiness = buildBtdDeploymentReadinessSettlement({
884972
actorId: 'user-1',
@@ -1425,6 +1513,28 @@ describe('BTD crypto API builders', () => {
14251513
);
14261514
});
14271515

1516+
it('returns JSON-safe source-to-shares proofs from the route boundary', async () => {
1517+
const route = buildPostBtdSourceToSharesProofRoute({
1518+
resolveAuthenticatedUser: async () => ({ userId: 'user-1' }),
1519+
});
1520+
const response = await route(
1521+
new Request('https://bitcode.test/api/btd/source-to-shares-proof', {
1522+
method: 'POST',
1523+
body: JSON.stringify(sourceToSharesProofBody()),
1524+
}),
1525+
);
1526+
const body = await response.json();
1527+
1528+
expect(response.status).toBe(200);
1529+
expect(body.kind).toBe('btd_source_to_shares_proof_settlement');
1530+
expect(body.committed).toBe(false);
1531+
expect(body.proof.feeQuote.priceAsset).toBe('BTC');
1532+
expect(body.proof.feeQuote.grossSats).toBe('9000');
1533+
expect(body.proof.settlementConservation.noOverpayment.passed).toBe(true);
1534+
expect(body.proof.settlementConservation.noUnderpayment.passed).toBe(true);
1535+
expect(body.terminalJournalEntry.exchangeSequence).toBe('18');
1536+
});
1537+
14281538
it('returns JSON-safe deployment readiness settlements and persists telemetry or upgrades', async () => {
14291539
const insertCryptoTelemetryEvent = jest.fn(async (row) => ({
14301540
event: row.event,

packages/api/src/routes/btd-crypto.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
type BtdReadAccessDecision,
2929
type BtdReadLicense,
3030
type BtdOwnershipClaim,
31+
type BtdSourceToSharesProofInput,
32+
type BtdSourceToSharesProofSettlement,
3133
type BtdTerminalJournalInput,
3234
type BtdTerminalJournalSettlement,
3335
type TerminalJournalEntry,
@@ -46,6 +48,7 @@ import {
4648
buildBtdOrganizationInterfaceAuthorityDecision,
4749
buildBtdReadAccessDecision,
4850
buildBtdRegistrySnapshot,
51+
buildBtdSourceToSharesProofSettlement,
4952
buildBtdStableId as stableId,
5053
buildBtdTerminalJournalSettlement,
5154
buildLicensedReadRevenueRoute,
@@ -67,6 +70,7 @@ export {
6770
buildBtdOrganizationInterfaceAuthorityDecision,
6871
buildBtdReadAccessDecision,
6972
buildBtdRegistrySnapshot,
73+
buildBtdSourceToSharesProofSettlement,
7074
buildBtdTerminalJournalSettlement,
7175
};
7276

@@ -564,6 +568,39 @@ export function buildPostBtdLedgerDatabaseReconciliationRoute(options: BtdRouteO
564568
});
565569
}
566570

571+
export function buildPostBtdSourceToSharesProofRoute(options: BtdRouteOptions = {}) {
572+
return traceRoute('/btd/source-to-shares-proof', async (request: Request) => {
573+
const user = await (options.resolveAuthenticatedUser ?? defaultResolveAuthenticatedUser)(
574+
request,
575+
);
576+
if (!user) {
577+
return createJsonResponse({ error: 'Unauthorized' }, 401);
578+
}
579+
580+
let body: Omit<BtdSourceToSharesProofInput, 'actorId' | 'exchangeSequence'> & {
581+
exchangeSequence: string | number;
582+
};
583+
try {
584+
body = await request.json();
585+
} catch {
586+
return createJsonResponse({ error: 'Invalid JSON body' }, 400);
587+
}
588+
589+
let settlement: BtdSourceToSharesProofSettlement;
590+
try {
591+
settlement = buildBtdSourceToSharesProofSettlement({
592+
...body,
593+
actorId: user.userId,
594+
exchangeSequence: parseBtdRequiredBigInt(body.exchangeSequence, 'exchangeSequence'),
595+
});
596+
} catch (error) {
597+
return createJsonResponse({ error: toBadRequestMessage(error) }, 400);
598+
}
599+
600+
return createJsonResponse(toJsonSafe(settlement));
601+
});
602+
}
603+
567604
export function buildPostBtdDeploymentReadinessRoute(options: BtdRouteOptions = {}) {
568605
return traceRoute('/btd/deployment-readiness', async (request: Request) => {
569606
const user = await (options.resolveAuthenticatedUser ?? defaultResolveAuthenticatedUser)(
@@ -623,6 +660,7 @@ export const postBtdAssetPackExchange = buildPostBtdAssetPackExchangeRoute();
623660
export const postBtdTerminalJournal = buildPostBtdTerminalJournalRoute();
624661
export const postBtdLedgerDatabaseReconciliation =
625662
buildPostBtdLedgerDatabaseReconciliationRoute();
663+
export const postBtdSourceToSharesProof = buildPostBtdSourceToSharesProofRoute();
626664
export const postBtdDeploymentReadiness = buildPostBtdDeploymentReadinessRoute();
627665

628666
function toBadRequestMessage(error: unknown): string {

0 commit comments

Comments
 (0)