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
20 changes: 20 additions & 0 deletions BITCODE_SPEC_V28.md
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,26 @@ Acceptance criteria:
- repair receipts and blocking drift reasons are visible.
- operator can distinguish retryable, repairable, and approval-required states.

Implementation posture:

- `/api/executions/history/[runId]` must return the selected execution with
Terminal journal readback, related BTC fee row, ledger anchor row,
AssetPack range row, ownership/license projection rows, recent
reconciliation repair receipts, and readback errors as typed detail payload
fields rather than requiring browser-network raw JSON inspection.
- Terminal detail owns a Journal section that separates ledger observed facts,
database projected facts, and metaphysical canonical root facts. The visual
surface must preserve the raw payload accordion for audit while making the
operator state readable at a glance.
- Confirmed, reorged, and failed finality observations are blocking facts for
contradictory projections. Reorged/failed observations block unlock;
confirmed observations that contradict missing projections require explicit
approval or repair rather than silent retry.
- Retryable means expected rows or readback are not visible yet without a
final blocking observation. Repairable means reconciliation receipts or drift
evidence can be applied without override. Approval-required means a
confirmed ledger observation contradicts the projected database state.

### Gate 6: Terminal Organization And Access Policy

Purpose:
Expand Down
6 changes: 3 additions & 3 deletions BITCODE_SPEC_V28_PARITY_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ It links manual screenshots, console logs, server logs, Supabase SQL checks, and
| access policy id/hash shown before commitment | V27 access primitives and `/btd/[assetPackId]` disclosure route | implemented prerequisite | Gate 3 |
| AssetPack range detail from registry state | `range.ts`, registry route, `/btd/[assetPackId]` | implemented prerequisite | Gate 4 |
| owner-read/licensed-read/denied branches | `access.ts`, read-access route, licensed-read revenue route | implemented prerequisite | Gate 4 |
| Terminal journal rows as transaction detail | `terminal-journal.ts`, terminal-journal route | implemented prerequisite | Gate 5 |
| ledger/database reconciliation as operator read | `reconciliation.ts`, reconciliation route | implemented prerequisite | Gate 5 |
| Terminal journal rows as transaction detail | `packages/api/src/routes/executions.ts`, `uapi/app/terminal/terminal-transaction-detail-snapshot.ts`, `uapi/app/terminal/TerminalTransactionJournalReconciliationCard.tsx`, `terminal-journal.ts` | implemented | Gate 5 |
| ledger/database reconciliation as operator read | `uapi/app/terminal/terminal-journal-reconciliation.ts`, reconciliation repair table readback, `reconciliation.ts` | implemented | Gate 5 |
| organization holdings and read-license usage from registry | organization BTD models plus V27 registry docs | pending | Gate 6 |
| MCP authorization based on range/read-license/policy truth | MCP holding-gate work remains aggregate-compatibility oriented; retained in V28 MVP scope. | pending | Gate 6 |
| ChatGPT App authorization based on range/read-license/policy truth | ChatGPT App MVP parity must use the same registry-derived access posture as MCP and Terminal; retained in V28 MVP scope. | not yet implemented | Gate 6 |
Expand Down Expand Up @@ -178,7 +178,7 @@ It links manual screenshots, console logs, server logs, Supabase SQL checks, and
| Terminal BTC fees | PSBT/finality UX built over V27 primitives | pending |
| Terminal Read/Fit | Read, Fit, semantic volume, measuremint, policy UX | pending |
| Terminal range/read | AssetPack range and read-right detail | pending |
| Terminal journal/reconcile | journal diff and repair detail | pending |
| Terminal journal/reconcile | selected transaction Journal section separates ledger observations, database projections, canonical roots, repair receipts, and blocking drift reasons | substantially advanced |
| Terminal operations | telemetry, lanes, upgrade, migration readiness | pending |

## Later-Version Deferrals
Expand Down
24 changes: 24 additions & 0 deletions BITCODE_V28_QA.md
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,30 @@ Saved query name: `v28_qa_terminal_03_btd_ledger_after_terminal`

Use `supabase/queries/v28_qa_terminal_03_btd_ledger_after_terminal.sql` after any Terminal action that claims Fit closure, AssetPack minting, BTC fee payment, ledger anchoring, settlement, or reconciliation.

Terminal transaction detail journal diff:

- Open a completed Read/Fit or AssetPack pipeline execution in Terminal and
switch `transactionDetail=journal`.
- Expected payload carriers are `run.ledger_settlement` and
`run.terminal_journal` from `/api/executions/history/[runId]`.
- The Journal section must show ledger observed facts separately from database
projected facts and metaphysical canonical facts. Operators should not need
the browser Network panel to answer whether journal rows, BTC fee rows,
ledger anchors, ownership/license projections, and repair receipts were read.
- Expected state labels:
- `Aligned`: settled status, all readback booleans present, expected journal
entries observed, no blocking repairs.
- `Retryable`: missing rows or readback while no confirmed/reorged/failed
observation blocks the projection.
- `Repairable`: reconciliation repair receipts or drift evidence are present
and do not require override.
- `Approval required`: a confirmed ledger fact contradicts a missing or
conflicting database projection.
- `Blocked`: reorged/failed finality or a blocking repair receipt prevents
unlock.
- The raw payload accordion remains available for audit, but the default visual
state must surface blocking drift reasons and repair receipts directly.

### Top Chrome Wallet Readiness

Expected V28 behavior:
Expand Down
179 changes: 178 additions & 1 deletion packages/api/src/routes/executions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ type ExecutionEventRow = {
phase: string | null;
};

type TerminalJournalReadback = {
expectedJournalEntryIds: string[];
entries: JsonRecord[];
repairs: JsonRecord[];
ledgerRows: {
assetPackRanges: JsonRecord[];
btcFeeTransactions: JsonRecord[];
ledgerAnchors: JsonRecord[];
ownershipEvents: JsonRecord[];
readLicenses: JsonRecord[];
};
readErrors: string[];
};

function toErrorMessage(error: unknown, fallback: string) {
if (error instanceof Error && error.message) return error.message;
if (typeof error === 'string' && error.trim()) return error;
Expand Down Expand Up @@ -427,6 +441,17 @@ function buildNormalizedAssetPackCompletion(row: ExecutionHistoryRow) {
};
}

function buildLedgerSettlement(row: ExecutionHistoryRow) {
const output = readOutputRecord(row);
const assetPackCompletion = readAssetPackCompletion(row);

return (
asRecord(assetPackCompletion?.ledgerSettlement) ||
asRecord(output?.ledgerSettlement) ||
null
);
}

export function normalizeExecutionHistoryRow(row: ExecutionHistoryRow) {
const agenticExecution = buildAgenticExecutionSummary({
type: row.type,
Expand Down Expand Up @@ -457,6 +482,7 @@ export function normalizeExecutionHistoryRow(row: ExecutionHistoryRow) {
written_asset_type: buildWrittenAssetType(row),
asset_pack: buildAssetPack(row),
asset_pack_completion: buildNormalizedAssetPackCompletion(row),
ledger_settlement: buildLedgerSettlement(row),
error: row.error ?? null,
};
}
Expand Down Expand Up @@ -628,8 +654,159 @@ export async function getExecutionHistoryRunRoute(
);
}

const normalizedRun = normalizeExecutionHistoryRow(run);
const terminalJournal = await fetchTerminalJournalReadback(run.id, normalizedRun);

return createJsonResponse({
run: normalizeExecutionHistoryRow(run),
run: {
...normalizedRun,
terminal_journal: terminalJournal,
},
events: (events || []).map(normalizeExecutionEventRow),
terminal_journal: terminalJournal,
});
}

function readNormalizedLedgerSettlement(run: JsonRecord) {
return (
asRecord(run.ledger_settlement) ||
asRecord(asRecord(run.asset_pack_completion)?.ledgerSettlement) ||
asRecord(asRecord(run.output)?.ledgerSettlement) ||
null
);
}

function readStringArray(value: unknown) {
return asArray(value)
.map((entry) => asString(entry))
.filter((entry): entry is string => Boolean(entry));
}

function dedupeStrings(values: Array<string | null | undefined>) {
return Array.from(new Set(values.filter((value): value is string => Boolean(value))));
}

function expectedHarnessJournalEntryIds(runId: string, ledgerSettlement: JsonRecord | null) {
if (!ledgerSettlement) return [];

return dedupeStrings([
...readStringArray(ledgerSettlement?.journalEntryIds),
`journal-mint-${runId}`,
`journal-btc-fee-${runId}`,
`journal-anchor-${runId}`,
`journal-settlement-${runId}`,
]);
}

async function readRowsByIds(table: string, column: string, ids: string[], readErrors: string[]) {
if (!ids.length) return [];

const { data, error } = await supabaseAdmin
.from(table)
.select('*')
.in(column, ids);

if (error) {
readErrors.push(`${table} readback failed: ${toErrorMessage(error, 'unknown error')}`);
return [];
}

return asArray<JsonRecord>(data).filter((entry) => asRecord(entry));
}

async function readRowById(table: string, column: string, id: string | null, readErrors: string[]) {
if (!id) return [];

const { data, error } = await supabaseAdmin
.from(table)
.select('*')
.eq(column, id)
.maybeSingle();

if (error) {
readErrors.push(`${table} readback failed: ${toErrorMessage(error, 'unknown error')}`);
return [];
}

return asRecord(data) ? [data] : [];
}

async function readRecentRepairRows(runId: string, factIds: string[], readErrors: string[]) {
const { data, error } = await supabaseAdmin
.from('btd_ledger_database_reconciliation_repairs')
.select('*')
.order('issued_at', { ascending: false })
.limit(100);
Comment on lines +735 to +739

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter reconciliation repairs before limiting

This query takes only the newest 100 repair receipts globally and filters for the selected run afterward. In any environment with more than 100 newer repairs from other runs, an older selected run's blocking repair will be omitted, causing the Terminal journal detail to show no repair/blocking reason even though the database contains one. Apply the runId/factIds predicates in the Supabase query before the limit, or page until all matching repairs are found.

Useful? React with 👍 / 👎.


if (error) {
readErrors.push(`btd_ledger_database_reconciliation_repairs readback failed: ${toErrorMessage(error, 'unknown error')}`);
return [];
}

const factIdSet = new Set(factIds);
return asArray<JsonRecord>(data)
.filter((row) => {
const repairId = asString(row.repair_id);
const reconciliationId = asString(row.reconciliation_id);
const factId = asString(row.fact_id);
return (
Boolean(repairId?.includes(runId)) ||
Boolean(reconciliationId?.includes(runId)) ||
Boolean(factId && factIdSet.has(factId))
);
})
.slice(0, 25);
}

async function fetchTerminalJournalReadback(runId: string, normalizedRun: JsonRecord): Promise<TerminalJournalReadback> {
const readErrors: string[] = [];
const ledgerSettlement = readNormalizedLedgerSettlement(normalizedRun);
const assetPackId = asString(ledgerSettlement?.assetPackId);
const ledgerAnchorId = asString(ledgerSettlement?.ledgerAnchorId);
const btcFeeReceiptId = asString(ledgerSettlement?.btcFeeReceiptId);
const ownershipEventId = asString(ledgerSettlement?.ownershipEventId) || `ownership-mint-${runId}`;
const readLicenseId = asString(ledgerSettlement?.readLicenseId) || `read-license-${runId}`;
const expectedJournalEntryIds = expectedHarnessJournalEntryIds(runId, ledgerSettlement);
const entries = await readRowsByIds(
'btd_terminal_journal_entries',
'journal_entry_id',
expectedJournalEntryIds,
readErrors,
);
const [
assetPackRanges,
btcFeeTransactions,
ledgerAnchors,
ownershipEvents,
readLicenses,
] = await Promise.all([
readRowById('btd_asset_pack_ranges', 'asset_pack_id', assetPackId, readErrors),
readRowById('btc_fee_transactions', 'receipt_id', btcFeeReceiptId, readErrors),
readRowById('btd_asset_pack_ledger_anchors', 'anchor_id', ledgerAnchorId, readErrors),
readRowById('btd_ownership_events', 'ownership_event_id', ownershipEventId, readErrors),
readRowById('btd_read_licenses', 'license_id', readLicenseId, readErrors),
Comment on lines +783 to +787

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict ledger readback to this run's facts

When a user can fetch a selected execution, these admin-client lookups trust the ledgerSettlement IDs stored in that execution and then return select('*') rows from global ledger tables. Because postExecutionHistoryRoute persists caller-supplied output/context for the user's own run, a caller can create a run whose assetPackId, btcFeeReceiptId, or other IDs point at another user's ledger facts and this route will bypass RLS via supabaseAdmin, exposing rows such as BTC fee wallet authorization/PSBT data and read-license wallet IDs. The readback should derive/verify ownership from the selected run's persisted settlement or constrain each lookup to facts created for that run/user before returning the rows.

Useful? React with 👍 / 👎.

]);
const factIds = dedupeStrings([
assetPackId,
ledgerAnchorId,
btcFeeReceiptId,
ownershipEventId,
readLicenseId,
...expectedJournalEntryIds,
]);
const repairs = await readRecentRepairRows(runId, factIds, readErrors);

return {
expectedJournalEntryIds,
entries: entries.sort((left, right) => Number(left.exchange_sequence || 0) - Number(right.exchange_sequence || 0)),
repairs,
ledgerRows: {
assetPackRanges,
btcFeeTransactions,
ledgerAnchors,
ownershipEvents,
readLicenses,
},
readErrors,
};
}
6 changes: 6 additions & 0 deletions packages/pipeline-hosts/src/asset-pack-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,9 @@ async function settleAssetPackLedger(pipelineResultState) {
},
ledgerAnchorId,
btcFeeReceiptId,
ownershipEventId,
readLicenseId,
journalEntryIds,
depositorWalletId,
readerWalletId,
ownershipBoundary: settlementOwnershipBoundary({
Expand Down Expand Up @@ -1445,6 +1448,9 @@ async function settleAssetPackLedger(pipelineResultState) {
},
ledgerAnchorId,
btcFeeReceiptId,
ownershipEventId,
readLicenseId,
journalEntryIds,
depositorWalletId,
readerWalletId,
btcFee: {
Expand Down
3 changes: 3 additions & 0 deletions uapi/app/api/pipeline-harness/asset-pack/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ function summarizeEvidence(evidence: unknown): Record<string, unknown> | null {
btdRange: ledgerSettlement.btdRange,
ledgerAnchorId: ledgerSettlement.ledgerAnchorId,
btcFeeReceiptId: ledgerSettlement.btcFeeReceiptId,
ownershipEventId: ledgerSettlement.ownershipEventId,
readLicenseId: ledgerSettlement.readLicenseId,
journalEntryIds: ledgerSettlement.journalEntryIds,
depositorWalletId: ledgerSettlement.depositorWalletId,
readerWalletId: ledgerSettlement.readerWalletId,
btcFee: ledgerSettlement.btcFee,
Expand Down
16 changes: 13 additions & 3 deletions uapi/app/terminal/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Terminal Implementation Module

This module owns the commercial Terminal implementation. The canonical product
This module owns the Terminal implementation. The canonical product
route is `/terminal`. No retained compatibility route belongs to this surface;
old route boundaries must be reformed in place into Terminal source.

Expand All @@ -13,7 +13,7 @@ Its job is to keep one operator route coherent:

## Main experience model

V28 commercial MVP hardening keeps these product experiences route-correct:
V28 MVP hardening keeps these product experiences route-correct:
- `terminal activity`
- `conversations`
- `auxillaries`
Expand All @@ -33,6 +33,9 @@ presenting them as part of Terminal itself.
Main Terminal activity and selected-result shell.
- `terminal-transaction-query.ts`
Route-owned filter, paging, and selected-activity state.
- `terminal-journal-reconciliation.ts` and `TerminalTransactionJournalReconciliationCard.tsx`
Selected-activity Journal section owner for ledger observations, database
projections, canonical root facts, repair receipts, and drift state.
- `TerminalCommandDeck.tsx`
Scenario, projection, branch mode, reset, and flow-guide entry posture.
- `TerminalDepositReadWorkbench.tsx`
Expand All @@ -44,7 +47,7 @@ presenting them as part of Terminal itself.

## Current V28 checkpoint expectations

Commercial MVP checkpoint confidence requires `/terminal` to be:
MVP checkpoint confidence requires `/terminal` to be:
- renderable in mock-mode review,
- route-query owned for activity selection and filtering,
- user-facing in copy and help posture,
Expand Down Expand Up @@ -88,6 +91,13 @@ Those queries are the operator evidence path for proving Terminal UI, API
history, GitHub source inventory, and BTD ledger projections stay synchronized
while the MVP is being QAed.

When `transactionDetail=journal` is selected, the detail route must read
Terminal journal rows and reconciliation repair receipts through
`/api/executions/history/[runId]`; the UI may expose the raw payload accordion
for audit, but the operator-facing visual state must make retryable,
repairable, approval-required, blocked, and aligned states distinguishable
without browser-network inspection.

## Related shared systems

- [../../components/base/bitcode/execution/README.md](../../components/base/bitcode/execution/README.md)
Expand Down
1 change: 1 addition & 0 deletions uapi/app/terminal/TerminalTransactionDetailActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const DETAIL_ACTIONS: DetailAction[] = [
{ id: 'closure', label: 'Closure' },
{ id: 'proofs', label: 'Proofs' },
{ id: 'history', label: 'History' },
{ id: 'journal', label: 'Journal' },
{ id: 'activity', label: 'Execution stream' },
{ id: 'console', label: 'Console' },
];
Expand Down
Loading
Loading