Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/bitcode-gate-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ jobs:
node scripts/check-v31-gate2-auxillaries-package-route-contracts.mjs --skip-branch-check
node scripts/check-v31-gate3-profile-account-state.mjs --skip-branch-check
node scripts/check-v31-gate4-connects-provider-readiness.mjs --skip-branch-check
node scripts/check-v31-gate5-wallet-btd-pane-readiness.mjs --skip-branch-check
else
echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2
exit 1
Expand Down Expand Up @@ -139,6 +140,7 @@ jobs:
run: |
pnpm --dir uapi exec jest --runTestsByPath \
tests/userDataRoute.test.ts \
tests/auxillariesWalletPane.test.tsx \
tests/api/auxillariesGithubConnectionRoute.test.ts \
tests/api/vcsRoutes.test.ts \
tests/auxillariesExternalsPane.test.tsx \
Expand Down
7 changes: 7 additions & 0 deletions BITCODE_SPEC_V31.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ Gate 5 Wallet/BTD precision:
- Wallet panes consume no-custody wallet capability and signer posture from V30 BTD/wallet primitives.
- BTD panes expose range/read-right/treasury/settlement readiness summaries without revealing protected AssetPack source.
- Treasury and organization BTD posture remains distinct from Exchange market state.
- `@bitcode/btd` owns the source-safe `BtdWalletBtdSupportProjection` used by Auxillaries to summarize wallet capability, signer readiness, network readiness, BTD read-right counts, range cell counts, account treasury posture, settlement blockers, and proof roots.
- `AuxillariesWalletBtdPaneState` is the API-facing support object that carries that projection into `/api/auxillaries/data`; UI panes may render it but may not recalculate no-custody, read-right, or settlement law locally.
- The Wallet pane may show account BTC fee posture, BTD range/read-right counts, roots, and blockers, but it must keep protected source visibility false before paid unlock and must identify that the support pane is not an Exchange market state surface.

Gate 6 policy precision:

Expand Down Expand Up @@ -418,6 +421,10 @@ The pane support model must contain:
- organization treasury posture kept distinct from Exchange market state;
- explicit proof that no server component custodies private keys or displays wallet secrets.

Gate 5 binds this model through `BtdWalletBtdSupportProjection` in `@bitcode/btd` and `AuxillariesWalletBtdPaneState` in the package-owned Auxillaries route contract.
The projection carries `walletCapabilityRoot` and `btdSupportRoot`, names signer capabilities such as message signing, PSBT signing, and rights transfer readiness, counts owner-read/licensed-read/pending/denied/unknown AssetPack read-right posture, sums range cells, and always marks `protectedSourceVisible: false` for pre-settlement support views.
Its treasury summary is account-scoped, no-custody, and explicitly `not_exchange_market_state`.

No server component may custody the Reader private key.
Server-side routes may prepare receipts, validate proofs, serialize quote/posture evidence, and persist registry rows only when explicitly requested.
They must not claim signature, broadcast, finality, settlement unlock, or rights transfer without matching receipt and readback evidence.
Expand Down
7 changes: 7 additions & 0 deletions BITCODE_SPEC_V31_DELTA.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ Closure acceptance:
- BTD pane state consumes range/read-right/treasury/settlement readiness without protected source;
- treasury and organization BTD support remain distinct from Exchange market state.

Gate 5 implementation centers:

- `packages/btd/src/auxillaries-support.ts` owns `BtdWalletBtdSupportProjection`, deriving no-custody wallet capability, signer posture, network readiness, BTD range/read-right counts, account treasury posture, settlement blockers, and source-safe roots;
- `packages/api/src/routes/auxillaries-contract.ts` maps that BTD package projection into `AuxillariesWalletBtdPaneState` so `/api/auxillaries/data` returns one package-owned Wallet/BTD support object;
- `uapi/app/auxillaries/components/AuxillariesWalletPane.tsx` renders signer, network, range, read-right, settlement, treasury-boundary, and proof-root readouts from `walletBtdPaneState` rather than locally rederiving BTD law;
- focused BTD, API, route, and pane tests prove no-custody posture, range/read-right summary, protected-source non-disclosure, and explicit non-Exchange treasury classification.

### Gate 6: Organization Team Role Policy Authority

Gate 6 deepens organization and policy authority.
Expand Down
8 changes: 8 additions & 0 deletions BITCODE_SPEC_V31_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ Route hydration must include template preference and notification readback where
## Gate 4 closure note

Gate 4 is closed only when Connects provider state is a source-safe readiness contract rather than a raw OAuth/VCS status blob.
That source-safe provider connection posture remains closed after later V31 gates advance the roadmap.
Provider readiness must name provider id/name, installation state, credential posture, token presence class, scopes class, last readback status, blocker, repair action, and proof root without exposing raw provider credentials.
VCS and GitHub Auxillaries routes may use credentials internally to validate or repair a connection, but responses, UI metadata, telemetry, and recovery proof hooks must carry only classified posture and before/after readiness roots.
The Externals pane can display compact provider proof detail, but it must consume `connectionReadiness` and `recoveryRuns` from the package-owned Auxillaries contract instead of locally deciding provider law.

## Gate 5 closure note

Gate 5 is closed only when Wallet/BTD support is package-owned and source-safe.
`@bitcode/btd` must derive no-custody wallet capability, signer posture, network readiness, BTD range/read-right counts, account treasury posture, settlement blockers, and support roots.
`packages/api` may map that projection into `AuxillariesWalletBtdPaneState`, but UI and routes must not rederive BTD read-access, settlement, rights-transfer, or no-custody law locally.
The Wallet pane must consume `walletBtdPaneState` from `/api/auxillaries/data`, render signer/network/range/read-right/settlement/treasury readiness, keep protected source invisible before paid unlock, and state that account treasury support is not Exchange market state.
6 changes: 3 additions & 3 deletions BITCODE_SPEC_V31_PARITY_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ No `_legacy/` source is active source truth.

| Requirement | Source evidence | Current V31 judgment |
| --- | --- | --- |
| Wallet panes consume no-custody signer posture | BTD wallet primitives and pane tests | pending |
| BTD panes consume range/read-right/treasury summaries | BTD access and treasury tests | pending |
| Settlement-readiness state remains distinct from Exchange market state | package and UI tests | pending |
| Wallet panes consume no-custody signer posture | `packages/btd/src/auxillaries-support.ts`, `packages/api/src/routes/auxillaries-contract.ts`, and `uapi/tests/auxillariesWalletPane.test.tsx` project no-custody capability, signer readiness, signer actions, roots, and UI readout | drafted |
| BTD panes consume range/read-right/treasury summaries | `BtdWalletBtdSupportProjection`, `AuxillariesWalletBtdPaneState`, `packages/btd/__tests__/btd.test.ts`, and `packages/api/src/routes/__tests__/auxillaries-contract.test.ts` cover range counts, read-right counts, BTC fee treasury, and protected-source invisibility | drafted |
| Settlement-readiness state remains distinct from Exchange market state | Wallet pane and package tests assert account-scoped treasury posture, settlement blockers, and `not_exchange_market_state` classification | drafted |

## Gate 6 Parity

Expand Down
2 changes: 1 addition & 1 deletion SPECIFICATIONS_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Current active canonical pointer: `BITCODE_SPEC.txt` -> `V30`
- Current active canon: `BITCODE_SPEC_V30.md`
- Current draft target: `BITCODE_SPEC_V31.md`
- Current working gate: V31 Gate 4 Connects provider readiness and recovery, which deepens source-safe provider connection posture with provider id/name, installation state, token presence class, scopes class, last readback status, blocker, repair action, readiness roots, and before/after recovery evidence over the V30 Protocol/BTD substrate.
- Current working gate: V31 Gate 5 Wallet and BTD pane readiness, which connects Auxillaries Wallet/BTD UI to package-owned V30 wallet, signer, network, BTD range, read-right, treasury, no-custody, and settlement-readiness projections without protected source leakage or Exchange market inference.
- Purpose: concise running index of Bitcode/ENGI specification history, current work, and planned work.

This roadmap is not an active system specification.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"check:v31-gate2": "node scripts/check-v31-gate2-auxillaries-package-route-contracts.mjs",
"check:v31-gate3": "node scripts/check-v31-gate3-profile-account-state.mjs",
"check:v31-gate4": "node scripts/check-v31-gate4-connects-provider-readiness.mjs",
"check:v31-gate5": "node scripts/check-v31-gate5-wallet-btd-pane-readiness.mjs",
"check:spec-quality": "node scripts/run-bitcode-spec-quality.mjs --mode basic",
"check:spec-quality:title": "node scripts/run-bitcode-spec-quality.mjs --mode strict-from-title",
"check:spec-quality:v24": "node scripts/run-bitcode-spec-quality.mjs --mode strict-version --version V24",
Expand Down
9 changes: 9 additions & 0 deletions packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ and a readiness root. Routes may validate credentials privately, but API
responses, UI metadata, telemetry, and recovery proof hooks must only serialize
classified readiness and `AuxillariesRecoveryRun` before/after roots.

Wallet/BTD support state is package-owned through `@bitcode/btd` and mapped
here as `AuxillariesWalletBtdPaneState`. Route payloads may include wallet
binding facts, BTD holding counts, recent AssetPack ids/ranges, and BTC fee
posture, but the no-custody signer posture, network readiness, range/read-right
summary, account treasury boundary, settlement blockers, and support roots must
come from the BTD projection. API responses must keep protected AssetPack source
hidden, keep wallet private material out of JSON, and mark the Wallet/BTD
treasury support state as separate from Exchange market state.

In V26 fourth-gate this package is where merged-world Bitcode becomes concrete:
- `/conversations` continuity
- `/executions` compatibility and pipeline-run APIs
Expand Down
37 changes: 37 additions & 0 deletions packages/api/src/routes/__tests__/auxillaries-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('Auxillaries package route contracts', () => {
address: 'tb1pauxcontract',
verificationState: 'verified',
metadata: {
network: 'testnet',
private_key: 'wallet-secret',
connectedAt: '2026-05-21T00:00:00.000Z',
},
Expand Down Expand Up @@ -80,6 +81,8 @@ describe('Auxillaries package route contracts', () => {
assetPackId: 'asset-pack-1',
rangeStart: 1,
rangeEndExclusive: 3,
readRightState: 'owner_read',
sourceSafePreviewRoot: 'source-safe-preview-root',
},
],
modelPreferences: {
Expand Down Expand Up @@ -149,6 +152,39 @@ describe('Auxillaries package route contracts', () => {
expect(payload.walletBtdPaneState.signerPosture).toMatchObject({
ready: true,
state: 'verified',
canSignPsbt: true,
serverCustody: false,
});
expect(payload.walletBtdPaneState.walletCapability).toMatchObject({
network: 'testnet',
noCustody: true,
serverCustody: false,
capabilities: expect.arrayContaining(['message_sign', 'psbt_sign', 'rights_transfer']),
});
expect(payload.walletBtdPaneState.networkReadiness).toMatchObject({
state: 'ready',
network: 'testnet',
blocker: null,
});
expect(payload.walletBtdPaneState.btdReadRightSummary).toMatchObject({
aggregateBtd: 128,
assetPackCount: 1,
rangeCount: 1,
totalRangeCells: 2,
ownerReadCount: 1,
protectedSourceVisible: false,
sourceSafePreviewRoots: ['source-safe-preview-root'],
});
expect(payload.walletBtdPaneState.treasurySummary).toMatchObject({
feeAsset: 'BTC',
noCustody: true,
treasuryScope: 'account',
organizationTreasurySeparated: true,
exchangeMarketState: 'not_exchange_market_state',
});
expect(payload.walletBtdPaneState).toMatchObject({
settlementReadiness: 'ready',
settlementBlockers: [],
});
expect(payload.organizationAuthority.policyDecision).toBe('allowed');
expect(payload.auxillariesContract.contractVersion).toBe(AUXILLARIES_CONTRACT_VERSION);
Expand All @@ -163,6 +199,7 @@ describe('Auxillaries package route contracts', () => {
expect(JSON.stringify(payload)).not.toContain('wallet-secret');
expect(JSON.stringify(payload)).not.toContain('repository source');
expect(JSON.stringify(payload)).not.toContain('private prompt body');
expect(JSON.stringify(payload.walletBtdPaneState)).not.toContain('asset pack source');
expect(JSON.stringify(payload.connectionReadiness[0].metadata)).not.toContain('provider-token');
expect(assertAuxillariesJsonSafe(payload)).toBeUndefined();
});
Expand Down
103 changes: 56 additions & 47 deletions packages/api/src/routes/auxillaries-contract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash } from 'crypto';
import { buildBtdWalletBtdSupportProjection } from '@bitcode/btd';

export const AUXILLARY_FLOW_STEPS = ['wallet', 'externals', 'profile', 'interfaces'] as const;
export const AUXILLARIES_CONTRACT_VERSION = 'v31-draft-auxillaries-contracts' as const;
Expand Down Expand Up @@ -85,6 +86,9 @@ export interface AuxillaryBtdAssetPackSummary {
label?: string;
rangeStart?: number;
rangeEndExclusive?: number;
readRightState?: 'owner_read' | 'licensed_read' | 'pending_settlement' | 'denied' | 'unknown';
accessPolicyHash?: string | null;
sourceSafePreviewRoot?: string | null;
acquiredAt?: string | null;
}

Expand Down Expand Up @@ -234,24 +238,50 @@ export interface AuxillariesWalletBtdPaneState {
provider: string | null;
address: string | null;
verificationState: string | null;
network: string | null;
noCustody: true;
serverCustody: false;
capabilities: Array<'message_sign' | 'psbt_sign' | 'rights_transfer'>;
walletCapabilityRoot: string;
};
signerPosture: {
ready: boolean;
state: 'verified' | 'manual' | 'pending' | 'missing' | 'invalid';
requiredAction: 'none' | 'connect_wallet' | 'verify_wallet_signature' | 'repair_wallet_binding';
canSignPsbt: boolean;
canSignRightsTransfer: boolean;
serverCustody: false;
};
networkReadiness: {
state: AuxillariesReadinessState;
network: string | null;
requiredAction: 'none' | 'connect_wallet' | 'verify_network' | 'repair_wallet_binding';
blocker: string | null;
};
btdReadRightSummary: {
aggregateBtd: number;
assetPackCount: number;
recentAssetPackIds: string[];
rangeCount: number;
totalRangeCells: number;
ownerReadCount: number;
licensedReadCount: number;
pendingSettlementCount: number;
deniedCount: number;
unknownCount: number;
protectedSourceVisible: false;
sourceSafePreviewRoots: string[];
};
treasurySummary: {
btcFeeBalance: number | null;
feeAsset: 'BTC';
noCustody: true;
treasuryScope: 'account';
organizationTreasurySeparated: true;
exchangeMarketState: 'not_exchange_market_state';
};
settlementReadiness: AuxillariesReadinessState;
settlementBlockers: string[];
sourceSafetyClass: AuxillariesSourceSafetyClass;
btdSupportRoot: string;
}
Expand Down Expand Up @@ -1118,61 +1148,40 @@ export function buildAuxillariesWalletBtdPaneState(input: {
const verificationState = readString(walletStatus?.verificationState) ?? binding?.status ?? null;
const connected = readBoolean(walletStatus?.connected) ?? Boolean(address);
const valid = readBoolean(walletStatus?.valid) ?? verificationState === 'verified';
const signerState: AuxillariesWalletBtdPaneState['signerPosture']['state'] = !address
? 'missing'
: valid || verificationState === 'verified'
? 'verified'
: verificationState === 'pending'
? 'pending'
: verificationState === 'manual'
? 'manual'
: connected
? 'invalid'
: 'missing';
const recentAssetPacks = Array.isArray(input.recentBtdAssetPacks) ? input.recentBtdAssetPacks : [];
const aggregateBtd = typeof input.btdBalance === 'number' && Number.isFinite(input.btdBalance)
? input.btdBalance
: 0;
const withoutRoot = {
kind: 'AuxillariesWalletBtdPaneState' as const,
walletCapability: {
hasBinding: Boolean(address),
provider,
const walletMetadata = asRecord(walletStatus?.metadata);
const projection = buildBtdWalletBtdSupportProjection({
wallet: {
address,
provider,
verificationState,
noCustody: true as const,
},
signerPosture: {
ready: signerState === 'verified',
state: signerState,
requiredAction:
signerState === 'verified'
? 'none' as const
: signerState === 'missing'
? 'connect_wallet' as const
: signerState === 'manual' || signerState === 'pending'
? 'verify_wallet_signature' as const
: 'repair_wallet_binding' as const,
connected,
valid,
network:
readString(walletStatus?.network) ??
readString(walletMetadata?.network) ??
null,
},
btdReadRightSummary: {
aggregateBtd,
assetPackCount: recentAssetPacks.length,
recentAssetPackIds: recentAssetPacks.map((assetPack) => assetPack.assetPackId).filter(Boolean),
},
treasurySummary: {
btcFeeBalance: typeof input.btcFeeBalance === 'number' && Number.isFinite(input.btcFeeBalance)
? input.btcFeeBalance
: null,
feeAsset: 'BTC' as const,
noCustody: true as const,
},
settlementReadiness: signerState === 'verified' ? 'ready' as const : 'degraded' as const,
aggregateBtd: input.btdBalance,
btcFeeBalance: input.btcFeeBalance,
recentAssetPacks: Array.isArray(input.recentBtdAssetPacks)
? input.recentBtdAssetPacks
: [],
});
const withoutRoot: Omit<AuxillariesWalletBtdPaneState, 'btdSupportRoot'> = {
kind: 'AuxillariesWalletBtdPaneState' as const,
walletCapability: projection.walletCapability,
signerPosture: projection.signerPosture,
networkReadiness: projection.networkReadiness,
btdReadRightSummary: projection.btdReadRightSummary,
treasurySummary: projection.treasurySummary,
settlementReadiness: projection.settlementReadiness,
settlementBlockers: projection.settlementBlockers,
sourceSafetyClass: 'source_safe' as AuxillariesSourceSafetyClass,
};

return {
...withoutRoot,
btdSupportRoot: stableProofRoot('auxillaries-wallet-btd-pane-state', withoutRoot),
btdSupportRoot: projection.btdSupportRoot,
};
}

Expand Down
25 changes: 25 additions & 0 deletions packages/api/src/routes/auxillaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,36 @@ async function listRecentBtdAssetPacksForProfile(
const rangeStart = readNumberField(row, 'range_start', 'rangeStart');
const rangeEndExclusive = readNumberField(row, 'range_end_exclusive', 'rangeEndExclusive');
const acquiredAt = readStringField(row, 'issued_at', 'acquiredAt');
const readRightState = readStringField(
row,
'read_right_state',
'readRightState',
'settlement_state',
'settlementState',
);
const accessPolicyHash = readStringField(row, 'access_policy_hash', 'accessPolicyHash');
const sourceSafePreviewRoot = readStringField(
row,
'source_safe_preview_root',
'preview_root',
'sourceSafePreviewRoot',
);

if (label) summary.label = label;
if (typeof rangeStart === 'number') summary.rangeStart = rangeStart;
if (typeof rangeEndExclusive === 'number') summary.rangeEndExclusive = rangeEndExclusive;
if (acquiredAt) summary.acquiredAt = acquiredAt;
if (
readRightState === 'owner_read' ||
readRightState === 'licensed_read' ||
readRightState === 'pending_settlement' ||
readRightState === 'denied' ||
readRightState === 'unknown'
) {
summary.readRightState = readRightState;
}
if (accessPolicyHash) summary.accessPolicyHash = accessPolicyHash;
if (sourceSafePreviewRoot) summary.sourceSafePreviewRoot = sourceSafePreviewRoot;

return summary;
})
Expand Down
Loading
Loading