otto-intel-mcp 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -18
- package/dist/account-userop-policy-definition.d.ts +14 -0
- package/dist/account-userop-policy-definition.js +12 -0
- package/dist/account-userop-policy-relation.d.ts +371 -0
- package/dist/account-userop-policy-relation.js +87 -0
- package/dist/adapter/cdp-signer.d.ts +76 -9
- package/dist/adapter/cdp-signer.js +274 -33
- package/dist/adapter/evm-call-failure.d.ts +29 -0
- package/dist/adapter/evm-call-failure.js +109 -0
- package/dist/adapter/refusal.d.ts +1 -1
- package/dist/adapter/refusal.js +13 -0
- package/dist/adapter/route-simulator.d.ts +91 -0
- package/dist/adapter/route-simulator.js +143 -0
- package/dist/adapter-cdp-index.d.ts +1 -1
- package/dist/executable-equities.d.ts +55 -0
- package/dist/executable-equities.js +116 -0
- package/dist/executable-yield-markets.d.ts +261 -0
- package/dist/executable-yield-markets.js +154 -0
- package/dist/execution-account-gas-quote.d.ts +141 -0
- package/dist/execution-account-gas-quote.js +443 -0
- package/dist/execution-account-gas-review.d.ts +123 -0
- package/dist/execution-account-gas-review.js +104 -0
- package/dist/execution-account-gas-runtime.d.ts +33 -0
- package/dist/execution-account-gas-runtime.js +131 -0
- package/dist/execution-account-gas-view.d.ts +936 -0
- package/dist/execution-account-gas-view.js +56 -0
- package/dist/execution-config.d.ts +3 -0
- package/dist/execution-config.js +4 -0
- package/dist/execution-delegated-definition.d.ts +7 -6
- package/dist/execution-delegated-definition.js +13 -2
- package/dist/execution-delegation-admin-definition.d.ts +127 -5
- package/dist/execution-delegation-admin-definition.js +155 -5
- package/dist/execution-delegation-admin.d.ts +48 -2
- package/dist/execution-delegation-admin.js +422 -12
- package/dist/execution-delegation-cap-store.d.ts +386 -0
- package/dist/execution-delegation-cap-store.js +1150 -0
- package/dist/execution-delegation-exit-definition.d.ts +309 -0
- package/dist/execution-delegation-exit-definition.js +204 -0
- package/dist/execution-delegation-exit-store.d.ts +114 -0
- package/dist/execution-delegation-exit-store.js +671 -0
- package/dist/execution-delegation-exit.d.ts +4 -0
- package/dist/execution-delegation-exit.js +614 -0
- package/dist/execution-delegation-finality.d.ts +22 -0
- package/dist/execution-delegation-finality.js +90 -0
- package/dist/execution-delegation-operation-store.d.ts +129 -0
- package/dist/execution-delegation-operation-store.js +1107 -0
- package/dist/execution-delegation-operations-definition.d.ts +282 -0
- package/dist/execution-delegation-operations-definition.js +117 -0
- package/dist/execution-delegation-operations.d.ts +6 -0
- package/dist/execution-delegation-operations.js +282 -0
- package/dist/execution-delegation-policy.d.ts +154 -8
- package/dist/execution-delegation-policy.js +273 -40
- package/dist/execution-delegation.d.ts +51 -0
- package/dist/execution-delegation.js +441 -31
- package/dist/execution-errors.d.ts +1 -1
- package/dist/execution-errors.js +122 -0
- package/dist/execution-index.d.ts +7 -2
- package/dist/execution-index.js +7 -2
- package/dist/execution-journal-schema.d.ts +96 -0
- package/dist/execution-journal-schema.js +368 -0
- package/dist/execution-journal-tables.d.ts +14 -0
- package/dist/execution-journal-tables.js +227 -0
- package/dist/execution-policy-compatibility.d.ts +34 -0
- package/dist/execution-policy-compatibility.js +76 -0
- package/dist/execution-registration.d.ts +54 -2
- package/dist/execution-registration.js +151 -5
- package/dist/execution-sponsored-account-profile.d.ts +32 -0
- package/dist/execution-sponsored-account-profile.js +59 -0
- package/dist/execution-sponsored-account.d.ts +1519 -0
- package/dist/execution-sponsored-account.js +222 -0
- package/dist/execution-sponsored-definition.d.ts +1713 -0
- package/dist/execution-sponsored-definition.js +185 -0
- package/dist/execution-sponsored-gas-policy.d.ts +27 -0
- package/dist/execution-sponsored-gas-policy.js +110 -0
- package/dist/execution-sponsored-hosted.d.ts +3 -0
- package/dist/execution-sponsored-hosted.js +71 -0
- package/dist/execution-sponsored-index.d.ts +1322 -0
- package/dist/execution-sponsored-index.js +102 -0
- package/dist/execution-sponsored-mcp-definition.d.ts +1662 -0
- package/dist/execution-sponsored-mcp-definition.js +94 -0
- package/dist/execution-sponsored-receipt.d.ts +47 -0
- package/dist/execution-sponsored-receipt.js +74 -0
- package/dist/execution-sponsored-registration.d.ts +14 -0
- package/dist/execution-sponsored-registration.js +77 -0
- package/dist/execution-sponsored-runtime.d.ts +84 -0
- package/dist/execution-sponsored-runtime.js +149 -0
- package/dist/execution-sponsored-settlement.d.ts +15 -0
- package/dist/execution-sponsored-settlement.js +88 -0
- package/dist/execution-sponsored-store.d.ts +67 -0
- package/dist/execution-sponsored-store.js +517 -0
- package/dist/execution-sponsored-transport.d.ts +90 -0
- package/dist/execution-sponsored-transport.js +180 -0
- package/dist/execution-tool-definitions.d.ts +21 -21
- package/dist/execution-tool-definitions.js +0 -7
- package/dist/execution-tools.d.ts +53 -1
- package/dist/execution-tools.js +171 -13
- package/dist/execution-worker-guard.d.ts +28 -0
- package/dist/execution-worker-guard.js +124 -0
- package/dist/hybrid-yield-index.d.ts +10 -0
- package/dist/hybrid-yield-index.js +11 -0
- package/dist/hybrid-yield-markets.d.ts +96 -0
- package/dist/hybrid-yield-markets.js +124 -0
- package/dist/hybrid-yield.d.ts +411 -0
- package/dist/hybrid-yield.js +155 -0
- package/dist/hyperliquid-order-builder.js +4 -0
- package/dist/lifi-execution-client.d.ts +6 -0
- package/dist/lifi-execution-client.js +3 -0
- package/dist/lp-userop-account.d.ts +461 -0
- package/dist/lp-userop-account.js +417 -0
- package/dist/lp-userop-authority.d.ts +49 -0
- package/dist/lp-userop-authority.js +211 -0
- package/dist/lp-userop-definition.d.ts +233 -0
- package/dist/lp-userop-definition.js +85 -0
- package/dist/lp-userop-hosted.d.ts +3 -0
- package/dist/lp-userop-hosted.js +73 -0
- package/dist/lp-userop-index.d.ts +1569 -0
- package/dist/lp-userop-index.js +112 -0
- package/dist/lp-userop-mcp-definition.d.ts +1220 -0
- package/dist/lp-userop-mcp-definition.js +46 -0
- package/dist/lp-userop-operation.d.ts +1230 -0
- package/dist/lp-userop-operation.js +140 -0
- package/dist/lp-userop-registration.d.ts +14 -0
- package/dist/lp-userop-registration.js +70 -0
- package/dist/lp-userop-settlement.d.ts +89 -0
- package/dist/lp-userop-settlement.js +184 -0
- package/dist/lp-userop.d.ts +643 -0
- package/dist/lp-userop.js +343 -0
- package/dist/native-yield-account-definition.d.ts +1192 -0
- package/dist/native-yield-account-definition.js +232 -0
- package/dist/native-yield-account-gas.d.ts +58 -0
- package/dist/native-yield-account-gas.js +228 -0
- package/dist/native-yield-account-index.d.ts +23 -0
- package/dist/native-yield-account-index.js +28 -0
- package/dist/native-yield-account-policy.d.ts +168 -0
- package/dist/native-yield-account-policy.js +525 -0
- package/dist/native-yield-account-rpc.d.ts +8 -0
- package/dist/native-yield-account-rpc.js +128 -0
- package/dist/native-yield-account-runtime.d.ts +71 -0
- package/dist/native-yield-account-runtime.js +125 -0
- package/dist/native-yield-account-store.d.ts +40 -0
- package/dist/native-yield-account-store.js +307 -0
- package/dist/native-yield-account.d.ts +662 -0
- package/dist/native-yield-account.js +371 -0
- package/dist/native-yield-consent-store.d.ts +20 -0
- package/dist/native-yield-consent-store.js +317 -0
- package/dist/native-yield-definition.d.ts +288 -0
- package/dist/native-yield-definition.js +105 -0
- package/dist/native-yield-reader.d.ts +4 -0
- package/dist/native-yield-reader.js +137 -0
- package/dist/native-yield-settlement.d.ts +48 -0
- package/dist/native-yield-settlement.js +167 -0
- package/dist/native-yield-simulation.d.ts +18 -0
- package/dist/native-yield-simulation.js +45 -0
- package/dist/native-yield-userop-account.d.ts +675 -0
- package/dist/native-yield-userop-account.js +357 -0
- package/dist/native-yield-userop-authority.d.ts +214 -0
- package/dist/native-yield-userop-authority.js +296 -0
- package/dist/native-yield-userop-definition.d.ts +331 -0
- package/dist/native-yield-userop-definition.js +105 -0
- package/dist/native-yield-userop-hosted.d.ts +3 -0
- package/dist/native-yield-userop-hosted.js +71 -0
- package/dist/native-yield-userop-index.d.ts +1522 -0
- package/dist/native-yield-userop-index.js +142 -0
- package/dist/native-yield-userop-mcp-definition.d.ts +1356 -0
- package/dist/native-yield-userop-mcp-definition.js +50 -0
- package/dist/native-yield-userop-operation.d.ts +845 -0
- package/dist/native-yield-userop-operation.js +127 -0
- package/dist/native-yield-userop-policy.d.ts +23 -0
- package/dist/native-yield-userop-policy.js +25 -0
- package/dist/native-yield-userop-registration.d.ts +17 -0
- package/dist/native-yield-userop-registration.js +76 -0
- package/dist/native-yield-userop-settlement.d.ts +8 -0
- package/dist/native-yield-userop-settlement.js +79 -0
- package/dist/native-yield-userop-simulation.d.ts +18 -0
- package/dist/native-yield-userop-simulation.js +34 -0
- package/dist/native-yield-userop.d.ts +35 -0
- package/dist/native-yield-userop.js +156 -0
- package/dist/native-yield-verify.d.ts +30 -0
- package/dist/native-yield-verify.js +141 -0
- package/dist/native-yield.d.ts +17 -0
- package/dist/native-yield.js +119 -0
- package/dist/stdio-main.js +0 -0
- package/dist/stock-buy-definition.d.ts +338 -0
- package/dist/stock-buy-definition.js +127 -0
- package/dist/stock-buy-oracle.d.ts +8 -0
- package/dist/stock-buy-oracle.js +22 -0
- package/dist/stock-buy.d.ts +17 -0
- package/dist/stock-buy.js +164 -0
- package/dist/tool-definitions.d.ts +3 -1
- package/dist/tool-definitions.js +55 -2
- package/dist/vendor/lp-v3/lp-v3-atomic-contract.d.ts +445 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-contract.js +90 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-evidence.d.ts +14 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-evidence.js +82 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-index.d.ts +80 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-index.js +19 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-plan.d.ts +43 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-plan.js +112 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.d.ts +312 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.js +41 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-index.d.ts +3 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-index.js +3 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.d.ts +49 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.js +88 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read.d.ts +17 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read.js +232 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-settlement.d.ts +105 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-settlement.js +162 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.d.ts +606 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.js +59 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.d.ts +168 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.js +23 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.d.ts +32 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.js +228 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.d.ts +11 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.js +91 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.d.ts +9 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.js +124 -0
- package/dist/vendor/lp-v3/lp-v3-contract.d.ts +538 -0
- package/dist/vendor/lp-v3/lp-v3-contract.js +137 -0
- package/dist/vendor/lp-v3/lp-v3-evidence.d.ts +25 -0
- package/dist/vendor/lp-v3/lp-v3-evidence.js +168 -0
- package/dist/vendor/lp-v3/lp-v3-plan.d.ts +22 -0
- package/dist/vendor/lp-v3/lp-v3-plan.js +135 -0
- package/dist/vendor/lp-v3/types.d.ts +131 -0
- package/dist/vendor/lp-v3/types.js +6 -0
- package/dist/vendor/lp-v3/v4-math.d.ts +18 -0
- package/dist/vendor/lp-v3/v4-math.js +90 -0
- package/dist/yield-deposit-definition.d.ts +374 -0
- package/dist/yield-deposit-definition.js +123 -0
- package/dist/yield-deposit.d.ts +16 -0
- package/dist/yield-deposit.js +149 -0
- package/dist/yield-route-semantics.d.ts +27 -0
- package/dist/yield-route-semantics.js +71 -0
- package/dist/yield-withdraw-definition.d.ts +357 -0
- package/dist/yield-withdraw-definition.js +90 -0
- package/dist/yield-withdraw.d.ts +11 -0
- package/dist/yield-withdraw.js +122 -0
- package/package.json +239 -7
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/** Shared operations.db schema and cross-rail claims. No transaction or receipt authority lives here. */
|
|
2
|
+
import { EXECUTION_STORAGE_SCHEMA, assertExecutionStorageSchema, assertDeclaredExecutionStorage } from './execution-journal-tables.js';
|
|
3
|
+
import { END_USER_ID_PATTERN } from './execution-delegation-cap-store.js';
|
|
4
|
+
/** Ordinary fresh initialization remains v4; v5 is an explicit offline semantic-reader migration. */
|
|
5
|
+
export const EXECUTION_JOURNAL_SCHEMA_VERSION = 4;
|
|
6
|
+
export const EXECUTION_JOURNAL_MAX_SCHEMA_VERSION = 6;
|
|
7
|
+
export const USER_EXECUTION_CLAIM_KINDS = ['mint', 'delegated', 'exit', 'native', 'sponsored'];
|
|
8
|
+
export class ExecutionJournalNotReadyError extends Error {
|
|
9
|
+
constructor() { super('the shared execution journal is not rollout-ready'); }
|
|
10
|
+
}
|
|
11
|
+
const CLAIM_COLUMNS = ['user_id', 'kind', 'claim_id', 'claim_generation', 'created_at_ms', 'updated_at_ms'];
|
|
12
|
+
const CLAIM_TABLE = `(
|
|
13
|
+
user_id TEXT NOT NULL PRIMARY KEY,
|
|
14
|
+
kind TEXT NOT NULL CHECK (kind IN ('mint','delegated','exit','native','sponsored')),
|
|
15
|
+
claim_id TEXT NOT NULL,
|
|
16
|
+
claim_generation INTEGER CHECK (claim_generation IS NULL OR claim_generation >= 0),
|
|
17
|
+
created_at_ms INTEGER NOT NULL,
|
|
18
|
+
updated_at_ms INTEGER NOT NULL
|
|
19
|
+
)`;
|
|
20
|
+
function corrupt(detail) { throw new Error(`the shared execution journal is corrupt or unsupported (${detail})`); }
|
|
21
|
+
function meta(db, key) {
|
|
22
|
+
const row = db.prepare('SELECT value FROM journal_meta WHERE key = ?').get(key);
|
|
23
|
+
if (!row)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof row.value !== 'string')
|
|
26
|
+
return corrupt('metadata value');
|
|
27
|
+
return row.value;
|
|
28
|
+
}
|
|
29
|
+
function validTime(value) {
|
|
30
|
+
return typeof value === 'string' && value.length > 0 && Number.isFinite(Date.parse(value));
|
|
31
|
+
}
|
|
32
|
+
function schemaVersion(db) {
|
|
33
|
+
const version = meta(db, 'schema_version');
|
|
34
|
+
if (version !== '2' && version !== '3' && version !== '4' && version !== '5' && version !== '6')
|
|
35
|
+
corrupt('schema version');
|
|
36
|
+
return Number(version);
|
|
37
|
+
}
|
|
38
|
+
/** Every writer checks the actual version under its own transaction. Opening is not migration authority. */
|
|
39
|
+
export function assertExecutionJournalCompatible(db, minimumVersion = 2) {
|
|
40
|
+
assertExecutionStorageSchema(db, 'CREATE TABLE IF NOT EXISTS journal_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL);');
|
|
41
|
+
const version = schemaVersion(db);
|
|
42
|
+
if (version < minimumVersion)
|
|
43
|
+
corrupt('schema version requires explicit operator migration');
|
|
44
|
+
const kinds = version === 2 ? "'mint','delegated','exit'" : version === 3
|
|
45
|
+
? "'mint','delegated','exit','native'" : "'mint','delegated','exit','native','sponsored'";
|
|
46
|
+
assertExecutionStorageSchema(db, `CREATE TABLE IF NOT EXISTS user_execution_claims ${CLAIM_TABLE.replace("'mint','delegated','exit','native','sponsored'", kinds)};`);
|
|
47
|
+
const coverage = meta(db, 'unified_claims_coverage_started_at');
|
|
48
|
+
const readyAt = meta(db, 'unified_claims_ready_at');
|
|
49
|
+
if (!validTime(coverage) || (readyAt !== undefined && !validTime(readyAt)))
|
|
50
|
+
corrupt('rollout markers');
|
|
51
|
+
if (readyAt !== undefined && Date.parse(readyAt) < Date.parse(coverage))
|
|
52
|
+
corrupt('ready marker predates coverage');
|
|
53
|
+
for (const [prefix, introduced] of [['native', 3], ['sponsored', 4], ['native_userop_records', 5], ['lp_userop_records', 6]]) {
|
|
54
|
+
const began = meta(db, `${prefix}_claims_coverage_started_at`), ready = meta(db, `${prefix}_claims_ready_at`);
|
|
55
|
+
if (version < introduced) {
|
|
56
|
+
if (began !== undefined || ready !== undefined)
|
|
57
|
+
corrupt(`${prefix} markers predate schema`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
if (!validTime(began) || (ready !== undefined && !validTime(ready)))
|
|
61
|
+
corrupt(`${prefix} rollout markers`);
|
|
62
|
+
if (ready !== undefined && Date.parse(ready) < Date.parse(began))
|
|
63
|
+
corrupt(`${prefix} ready marker predates coverage`);
|
|
64
|
+
if (ready !== undefined && readyAt === undefined)
|
|
65
|
+
corrupt(`${prefix} readiness has no legacy drain attestation`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function readExecutionJournalStatus(db) {
|
|
70
|
+
assertExecutionJournalCompatible(db);
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
schemaVersion: schemaVersion(db),
|
|
73
|
+
ready: meta(db, 'unified_claims_ready_at') !== undefined,
|
|
74
|
+
coverageStartedAt: meta(db, 'unified_claims_coverage_started_at'),
|
|
75
|
+
nativeReady: meta(db, 'native_claims_ready_at') !== undefined,
|
|
76
|
+
nativeCoverageStartedAt: meta(db, 'native_claims_coverage_started_at') ?? null,
|
|
77
|
+
sponsoredReady: meta(db, 'sponsored_claims_ready_at') !== undefined,
|
|
78
|
+
sponsoredCoverageStartedAt: meta(db, 'sponsored_claims_coverage_started_at') ?? null,
|
|
79
|
+
nativeUserOpReady: meta(db, 'native_userop_records_claims_ready_at') !== undefined,
|
|
80
|
+
nativeUserOpCoverageStartedAt: meta(db, 'native_userop_records_claims_coverage_started_at') ?? null,
|
|
81
|
+
lpUserOpReady: meta(db, 'lp_userop_records_claims_ready_at') !== undefined,
|
|
82
|
+
lpUserOpCoverageStartedAt: meta(db, 'lp_userop_records_claims_coverage_started_at') ?? null,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function requireExecutionJournalReady(db) {
|
|
86
|
+
if (!readExecutionJournalStatus(db).ready)
|
|
87
|
+
throw new ExecutionJournalNotReadyError();
|
|
88
|
+
}
|
|
89
|
+
export function requireNativeExecutionJournalReady(db) {
|
|
90
|
+
const status = readExecutionJournalStatus(db);
|
|
91
|
+
if (!status.ready || !status.nativeReady)
|
|
92
|
+
throw new ExecutionJournalNotReadyError();
|
|
93
|
+
}
|
|
94
|
+
export function requireSponsoredExecutionJournalReady(db) {
|
|
95
|
+
const status = readExecutionJournalStatus(db);
|
|
96
|
+
if (!status.ready || !status.sponsoredReady)
|
|
97
|
+
throw new ExecutionJournalNotReadyError();
|
|
98
|
+
}
|
|
99
|
+
/** v2 native records require a new reader rollout. Existing v4 sponsorship readiness is insufficient. */
|
|
100
|
+
export function requireNativeUserOpJournalReady(db) {
|
|
101
|
+
const status = readExecutionJournalStatus(db);
|
|
102
|
+
if (status.schemaVersion < 5 || !status.ready || !status.sponsoredReady || !status.nativeUserOpReady)
|
|
103
|
+
throw new ExecutionJournalNotReadyError();
|
|
104
|
+
}
|
|
105
|
+
/** Operator-only transaction after draining every v4 worker; no table, record or claim is rewritten. */
|
|
106
|
+
export function migrateNativeUserOpJournal(db, nowMs, expected) {
|
|
107
|
+
if (db.isTransaction !== true)
|
|
108
|
+
throw new Error('native UserOp migration requires the owning rollout transaction');
|
|
109
|
+
assertExecutionJournalCompatible(db, 4);
|
|
110
|
+
validateClaims(db);
|
|
111
|
+
assertDeclaredExecutionStorage(db, schemaVersion(db));
|
|
112
|
+
const before = readExecutionJournalStatus(db);
|
|
113
|
+
if (before.schemaVersion !== 4 || !before.ready || !before.sponsoredReady ||
|
|
114
|
+
before.coverageStartedAt !== expected.coverageStartedAt ||
|
|
115
|
+
before.sponsoredCoverageStartedAt !== expected.sponsoredCoverageStartedAt ||
|
|
116
|
+
!Number.isSafeInteger(nowMs) || nowMs < Math.max(Date.parse(before.sponsoredCoverageStartedAt), Date.parse(meta(db, 'unified_claims_ready_at')), Date.parse(meta(db, 'sponsored_claims_ready_at'))))
|
|
117
|
+
throw new Error('native UserOp migration precondition mismatch');
|
|
118
|
+
db.prepare("UPDATE journal_meta SET value='5' WHERE key='schema_version'").run();
|
|
119
|
+
db.prepare("INSERT INTO journal_meta(key,value) VALUES('native_userop_records_claims_coverage_started_at',?)")
|
|
120
|
+
.run(new Date(nowMs).toISOString());
|
|
121
|
+
assertExecutionJournalCompatible(db, 5);
|
|
122
|
+
assertDeclaredExecutionStorage(db, 5);
|
|
123
|
+
return readExecutionJournalStatus(db);
|
|
124
|
+
}
|
|
125
|
+
/** A separate explicit attestation that all deployed readers understand native version-2 records. */
|
|
126
|
+
export function markNativeUserOpReadyAfterDrain(db, nowMs) {
|
|
127
|
+
if (db.isTransaction !== true)
|
|
128
|
+
throw new Error('native UserOp readiness requires the owning rollout transaction');
|
|
129
|
+
assertExecutionJournalCompatible(db, 5);
|
|
130
|
+
requireSponsoredExecutionJournalReady(db);
|
|
131
|
+
const status = readExecutionJournalStatus(db), began = status.nativeUserOpCoverageStartedAt;
|
|
132
|
+
if (!began || !Number.isSafeInteger(nowMs) || nowMs < Date.parse(began))
|
|
133
|
+
throw new Error('invalid native UserOp drain attestation time');
|
|
134
|
+
db.prepare("INSERT OR IGNORE INTO journal_meta(key,value) VALUES('native_userop_records_claims_ready_at',?)")
|
|
135
|
+
.run(new Date(nowMs).toISOString());
|
|
136
|
+
return readExecutionJournalStatus(db);
|
|
137
|
+
}
|
|
138
|
+
/** LP record v3 is another explicit reader rollout; prior native readiness does not grant LP admission. */
|
|
139
|
+
export function requireLpUserOpJournalReady(db) {
|
|
140
|
+
const status = readExecutionJournalStatus(db);
|
|
141
|
+
if (status.schemaVersion !== 6 || !status.ready || !status.sponsoredReady || !status.lpUserOpReady)
|
|
142
|
+
throw new ExecutionJournalNotReadyError();
|
|
143
|
+
}
|
|
144
|
+
export function migrateLpUserOpJournal(db, nowMs, expected) {
|
|
145
|
+
if (db.isTransaction !== true)
|
|
146
|
+
throw new Error('LP UserOp migration requires the owning rollout transaction');
|
|
147
|
+
assertExecutionJournalCompatible(db, 5);
|
|
148
|
+
validateClaims(db);
|
|
149
|
+
assertDeclaredExecutionStorage(db, schemaVersion(db));
|
|
150
|
+
const before = readExecutionJournalStatus(db);
|
|
151
|
+
const lastMarker = Math.max(...['unified_claims_coverage_started_at', 'unified_claims_ready_at',
|
|
152
|
+
'native_claims_coverage_started_at', 'native_claims_ready_at', 'sponsored_claims_coverage_started_at',
|
|
153
|
+
'sponsored_claims_ready_at', 'native_userop_records_claims_coverage_started_at', 'native_userop_records_claims_ready_at']
|
|
154
|
+
.map(key => meta(db, key)).filter((value) => value !== undefined).map(Date.parse));
|
|
155
|
+
if (before.schemaVersion !== 5 || !before.ready || !before.sponsoredReady ||
|
|
156
|
+
before.coverageStartedAt !== expected.coverageStartedAt ||
|
|
157
|
+
before.sponsoredCoverageStartedAt !== expected.sponsoredCoverageStartedAt ||
|
|
158
|
+
before.nativeUserOpCoverageStartedAt !== expected.nativeUserOpCoverageStartedAt ||
|
|
159
|
+
!Number.isSafeInteger(nowMs) || nowMs < lastMarker)
|
|
160
|
+
throw new Error('LP UserOp migration precondition mismatch');
|
|
161
|
+
db.prepare("UPDATE journal_meta SET value='6' WHERE key='schema_version'").run();
|
|
162
|
+
db.prepare("INSERT INTO journal_meta(key,value) VALUES('lp_userop_records_claims_coverage_started_at',?)")
|
|
163
|
+
.run(new Date(nowMs).toISOString());
|
|
164
|
+
assertExecutionJournalCompatible(db, 6);
|
|
165
|
+
assertDeclaredExecutionStorage(db, 6);
|
|
166
|
+
return readExecutionJournalStatus(db);
|
|
167
|
+
}
|
|
168
|
+
export function markLpUserOpReadyAfterDrain(db, nowMs) {
|
|
169
|
+
if (db.isTransaction !== true)
|
|
170
|
+
throw new Error('LP UserOp readiness requires the owning rollout transaction');
|
|
171
|
+
assertExecutionJournalCompatible(db, 6);
|
|
172
|
+
requireSponsoredExecutionJournalReady(db);
|
|
173
|
+
const status = readExecutionJournalStatus(db), began = status.lpUserOpCoverageStartedAt;
|
|
174
|
+
if (!began || !Number.isSafeInteger(nowMs) || nowMs < Date.parse(began))
|
|
175
|
+
throw new Error('invalid LP UserOp drain attestation time');
|
|
176
|
+
db.prepare("INSERT OR IGNORE INTO journal_meta(key,value) VALUES('lp_userop_records_claims_ready_at',?)")
|
|
177
|
+
.run(new Date(nowMs).toISOString());
|
|
178
|
+
return readExecutionJournalStatus(db);
|
|
179
|
+
}
|
|
180
|
+
/** Explicit operator attestation that every worker understands sponsored claims; migration alone cannot enable sends. */
|
|
181
|
+
export function markSponsoredExecutionReadyAfterLegacyDrain(db, nowMs) {
|
|
182
|
+
if (db.isTransaction !== true)
|
|
183
|
+
throw new Error('sponsored readiness requires the owning rollout transaction');
|
|
184
|
+
requireExecutionJournalReady(db);
|
|
185
|
+
const coverage = readExecutionJournalStatus(db).sponsoredCoverageStartedAt;
|
|
186
|
+
if (coverage === null || !Number.isSafeInteger(nowMs) || nowMs < Date.parse(coverage))
|
|
187
|
+
throw new Error('invalid sponsored drain attestation time');
|
|
188
|
+
db.prepare("INSERT OR IGNORE INTO journal_meta (key,value) VALUES ('sponsored_claims_ready_at',?)").run(new Date(nowMs).toISOString());
|
|
189
|
+
return readExecutionJournalStatus(db);
|
|
190
|
+
}
|
|
191
|
+
/** Explicit native rollout attestation, separate from the existing public Base rollout action. */
|
|
192
|
+
export function markNativeExecutionReadyAfterLegacyDrain(db, nowMs) {
|
|
193
|
+
if (db.isTransaction !== true)
|
|
194
|
+
throw new Error('native readiness requires the owning rollout transaction');
|
|
195
|
+
requireExecutionJournalReady(db);
|
|
196
|
+
const coverage = readExecutionJournalStatus(db).nativeCoverageStartedAt;
|
|
197
|
+
if (coverage === null || !Number.isSafeInteger(nowMs) || nowMs < Date.parse(coverage))
|
|
198
|
+
throw new Error('invalid native drain attestation time');
|
|
199
|
+
db.prepare("INSERT OR IGNORE INTO journal_meta (key,value) VALUES ('native_claims_ready_at',?)")
|
|
200
|
+
.run(new Date(nowMs).toISOString());
|
|
201
|
+
return readExecutionJournalStatus(db);
|
|
202
|
+
}
|
|
203
|
+
export function executionClaimFromRow(row) {
|
|
204
|
+
if (row === undefined)
|
|
205
|
+
return undefined;
|
|
206
|
+
if (!row || typeof row !== 'object')
|
|
207
|
+
return corrupt('claim row');
|
|
208
|
+
const item = row;
|
|
209
|
+
if (typeof item.user_id !== 'string' || !END_USER_ID_PATTERN.test(item.user_id) ||
|
|
210
|
+
typeof item.kind !== 'string' || !USER_EXECUTION_CLAIM_KINDS.includes(item.kind) ||
|
|
211
|
+
typeof item.claim_id !== 'string' || item.claim_id.length < 1)
|
|
212
|
+
corrupt('claim identity');
|
|
213
|
+
const generation = item.claim_generation;
|
|
214
|
+
if (item.kind === 'mint'
|
|
215
|
+
? typeof generation !== 'number' || !Number.isSafeInteger(generation) || generation < 0
|
|
216
|
+
: generation !== null)
|
|
217
|
+
corrupt('claim Stop generation');
|
|
218
|
+
return Object.freeze({
|
|
219
|
+
userId: item.user_id,
|
|
220
|
+
kind: item.kind,
|
|
221
|
+
claimId: item.claim_id,
|
|
222
|
+
...(generation === null ? {} : { claimGeneration: generation }),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
export function readUserExecutionClaim(db, userId) {
|
|
226
|
+
if (!END_USER_ID_PATTERN.test(userId))
|
|
227
|
+
throw new Error('invalid claim end user');
|
|
228
|
+
assertExecutionJournalCompatible(db);
|
|
229
|
+
return executionClaimFromRow(db.prepare('SELECT user_id,kind,claim_id,claim_generation FROM user_execution_claims WHERE user_id = ?').get(userId));
|
|
230
|
+
}
|
|
231
|
+
function validateClaims(db) {
|
|
232
|
+
const version = schemaVersion(db);
|
|
233
|
+
const rows = db.prepare('SELECT * FROM user_execution_claims ORDER BY user_id').all();
|
|
234
|
+
for (const row of rows) {
|
|
235
|
+
const claim = executionClaimFromRow(row);
|
|
236
|
+
if ((claim.kind === 'native' && version < 3) || (claim.kind === 'sponsored' && version < 4))
|
|
237
|
+
corrupt('claim predates schema');
|
|
238
|
+
if (![row.created_at_ms, row.updated_at_ms].every((time) => typeof time === 'number' && Number.isSafeInteger(time) && time >= 0))
|
|
239
|
+
corrupt('claim timestamps');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function hasStorage(db) {
|
|
243
|
+
return db.prepare("SELECT name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' LIMIT 1").get() !== undefined;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Fresh databases are initialized atomically, unready, with all storage contracts. Existing databases
|
|
247
|
+
* are only validated unless the operator supplies the exact prior version and coverage after draining
|
|
248
|
+
* every old writer. Ordinary status, construction and hosted startup can never widen a live journal.
|
|
249
|
+
*/
|
|
250
|
+
export function initializeExecutionJournal(db, nowMs, expectedPrior) {
|
|
251
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0)
|
|
252
|
+
throw new Error('invalid journal migration time');
|
|
253
|
+
if (!expectedPrior && hasStorage(db)) {
|
|
254
|
+
assertExecutionJournalCompatible(db);
|
|
255
|
+
validateClaims(db);
|
|
256
|
+
assertDeclaredExecutionStorage(db, schemaVersion(db));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
db.exec('BEGIN IMMEDIATE');
|
|
260
|
+
try {
|
|
261
|
+
// Recheck after the write lock: another fresh opener or operator may have won.
|
|
262
|
+
if (!expectedPrior && hasStorage(db)) {
|
|
263
|
+
assertExecutionJournalCompatible(db);
|
|
264
|
+
validateClaims(db);
|
|
265
|
+
assertDeclaredExecutionStorage(db, schemaVersion(db));
|
|
266
|
+
db.exec('COMMIT');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (expectedPrior) {
|
|
270
|
+
if (!['2', '3'].includes(expectedPrior.schemaVersion) || !validTime(expectedPrior.coverageStartedAt) ||
|
|
271
|
+
new Date(expectedPrior.coverageStartedAt).toISOString() !== expectedPrior.coverageStartedAt)
|
|
272
|
+
throw new Error('invalid expected journal migration state');
|
|
273
|
+
if (meta(db, 'schema_version') !== expectedPrior.schemaVersion ||
|
|
274
|
+
meta(db, 'unified_claims_coverage_started_at') !== expectedPrior.coverageStartedAt)
|
|
275
|
+
throw new Error('journal migration precondition mismatch: schema or coverage changed; no migration was made');
|
|
276
|
+
assertExecutionJournalCompatible(db);
|
|
277
|
+
validateClaims(db);
|
|
278
|
+
assertDeclaredExecutionStorage(db, schemaVersion(db));
|
|
279
|
+
if (db.prepare("SELECT name FROM sqlite_master WHERE name='user_execution_claims_v4'").get())
|
|
280
|
+
corrupt('migration table already exists');
|
|
281
|
+
const before = db.prepare('SELECT * FROM user_execution_claims ORDER BY user_id').all();
|
|
282
|
+
db.exec(`CREATE TABLE user_execution_claims_v4 ${CLAIM_TABLE}`);
|
|
283
|
+
db.exec(`INSERT INTO user_execution_claims_v4 (${CLAIM_COLUMNS.join(',')}) SELECT ${CLAIM_COLUMNS.join(',')} FROM user_execution_claims`);
|
|
284
|
+
if (JSON.stringify(before) !== JSON.stringify(db.prepare('SELECT * FROM user_execution_claims_v4 ORDER BY user_id').all()))
|
|
285
|
+
corrupt('claim migration changed rows');
|
|
286
|
+
db.exec('DROP TABLE user_execution_claims; ALTER TABLE user_execution_claims_v4 RENAME TO user_execution_claims');
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
db.exec('CREATE TABLE journal_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL)');
|
|
290
|
+
db.exec(`CREATE TABLE user_execution_claims ${CLAIM_TABLE}`);
|
|
291
|
+
db.prepare("INSERT INTO journal_meta (key,value) VALUES ('unified_claims_coverage_started_at',?)").run(new Date(nowMs).toISOString());
|
|
292
|
+
}
|
|
293
|
+
db.exec(EXECUTION_STORAGE_SCHEMA);
|
|
294
|
+
db.prepare("INSERT OR REPLACE INTO journal_meta (key,value) VALUES ('schema_version','4')").run();
|
|
295
|
+
if (!expectedPrior || expectedPrior.schemaVersion === '2') {
|
|
296
|
+
db.prepare("INSERT INTO journal_meta (key,value) VALUES ('native_claims_coverage_started_at',?)").run(new Date(nowMs).toISOString());
|
|
297
|
+
}
|
|
298
|
+
db.prepare("INSERT INTO journal_meta (key,value) VALUES ('sponsored_claims_coverage_started_at',?)").run(new Date(nowMs).toISOString());
|
|
299
|
+
assertExecutionJournalCompatible(db);
|
|
300
|
+
validateClaims(db);
|
|
301
|
+
assertDeclaredExecutionStorage(db, 4);
|
|
302
|
+
db.exec('COMMIT');
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
try {
|
|
306
|
+
db.exec('ROLLBACK');
|
|
307
|
+
}
|
|
308
|
+
catch { /* preserve the original error */ }
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function assertNativeClaimTransaction(db, binding, minimumVersion = 3) {
|
|
313
|
+
if (db.isTransaction !== true)
|
|
314
|
+
throw new Error('native claims require the owning operation transaction');
|
|
315
|
+
if (!END_USER_ID_PATTERN.test(binding.userId) || !/^[a-zA-Z0-9_-]{8,128}$/.test(binding.operationId))
|
|
316
|
+
throw new Error('invalid native claim binding');
|
|
317
|
+
assertExecutionJournalCompatible(db, minimumVersion);
|
|
318
|
+
}
|
|
319
|
+
/** Call inside the transaction that also creates the native operation row. No standalone reservation. */
|
|
320
|
+
export function claimNativeExecution(db, binding, nowMs) {
|
|
321
|
+
assertNativeClaimTransaction(db, binding);
|
|
322
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0)
|
|
323
|
+
throw new Error('invalid native claim time');
|
|
324
|
+
const claim = readUserExecutionClaim(db, binding.userId);
|
|
325
|
+
if (claim)
|
|
326
|
+
return claim.kind === 'native' && claim.claimId === binding.operationId
|
|
327
|
+
? { outcome: 'existing' } : { outcome: 'conflict', claim };
|
|
328
|
+
requireNativeExecutionJournalReady(db);
|
|
329
|
+
db.prepare("INSERT INTO user_execution_claims (user_id,kind,claim_id,claim_generation,created_at_ms,updated_at_ms) VALUES (?,'native',?,NULL,?,?)")
|
|
330
|
+
.run(binding.userId, binding.operationId, nowMs, nowMs);
|
|
331
|
+
return { outcome: 'started' };
|
|
332
|
+
}
|
|
333
|
+
/** The caller must prove and record the native terminal outcome in this same transaction. */
|
|
334
|
+
export function releaseNativeExecutionClaim(db, binding) {
|
|
335
|
+
assertNativeClaimTransaction(db, binding);
|
|
336
|
+
const claim = readUserExecutionClaim(db, binding.userId);
|
|
337
|
+
if (claim?.kind !== 'native' || claim.claimId !== binding.operationId)
|
|
338
|
+
throw new Error('native execution claim mismatch');
|
|
339
|
+
const result = db.prepare("DELETE FROM user_execution_claims WHERE user_id=? AND kind='native' AND claim_id=?")
|
|
340
|
+
.run(binding.userId, binding.operationId);
|
|
341
|
+
if (Number(result.changes) !== 1)
|
|
342
|
+
throw new Error('native execution claim release did not match exactly once');
|
|
343
|
+
}
|
|
344
|
+
/** Atomic companion to inserting/consuming a sponsored review in the same operations.db transaction. */
|
|
345
|
+
export function claimSponsoredExecution(db, binding, nowMs) {
|
|
346
|
+
assertNativeClaimTransaction(db, binding, 4);
|
|
347
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0)
|
|
348
|
+
throw new Error('invalid sponsored claim time');
|
|
349
|
+
const claim = readUserExecutionClaim(db, binding.userId);
|
|
350
|
+
if (claim)
|
|
351
|
+
return claim.kind === 'sponsored' && claim.claimId === binding.operationId
|
|
352
|
+
? { outcome: 'existing' } : { outcome: 'conflict', claim };
|
|
353
|
+
requireSponsoredExecutionJournalReady(db);
|
|
354
|
+
db.prepare("INSERT INTO user_execution_claims (user_id,kind,claim_id,claim_generation,created_at_ms,updated_at_ms) VALUES (?,'sponsored',?,NULL,?,?)")
|
|
355
|
+
.run(binding.userId, binding.operationId, nowMs, nowMs);
|
|
356
|
+
return { outcome: 'started' };
|
|
357
|
+
}
|
|
358
|
+
/** Caller commits the proven terminal record in this same transaction. No TTL or provider-timeout release. */
|
|
359
|
+
export function releaseSponsoredExecutionClaim(db, binding) {
|
|
360
|
+
assertNativeClaimTransaction(db, binding, 4);
|
|
361
|
+
const claim = readUserExecutionClaim(db, binding.userId);
|
|
362
|
+
if (claim?.kind !== 'sponsored' || claim.claimId !== binding.operationId)
|
|
363
|
+
throw new Error('sponsored execution claim mismatch');
|
|
364
|
+
const result = db.prepare("DELETE FROM user_execution_claims WHERE user_id=? AND kind='sponsored' AND claim_id=?").run(binding.userId, binding.operationId);
|
|
365
|
+
if (Number(result.changes) !== 1)
|
|
366
|
+
throw new Error('sponsored execution claim release did not match exactly once');
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=execution-journal-schema.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Canonical storage contracts. DDL runs only for empty initialization or explicit operator migration. */
|
|
2
|
+
import type { ExecutionJournalDatabase } from './execution-journal-schema.js';
|
|
3
|
+
export declare const LEGACY_OPERATION_SCHEMA = "CREATE TABLE IF NOT EXISTS operations (\n artifact_id TEXT NOT NULL PRIMARY KEY,\n user_id TEXT NOT NULL,\n account TEXT NOT NULL,\n chain_id INTEGER NOT NULL CHECK (chain_id = 8453),\n fingerprint TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('prepared','submitted','confirmed','failed','unknown')),\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n recovery_context_json TEXT,\n result_json TEXT,\n failure_code TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS operations_one_unresolved_per_user\n ON operations (user_id) WHERE state IN ('prepared','submitted','unknown');\nCREATE INDEX IF NOT EXISTS operations_user_updated\n ON operations (user_id, updated_at_ms DESC);\nCREATE TABLE IF NOT EXISTS operation_steps (\n artifact_id TEXT NOT NULL,\n step_index INTEGER NOT NULL CHECK (step_index >= 0 AND step_index <= 3),\n step_kind TEXT NOT NULL CHECK (step_kind IN ('approve_reset','approve','call','allowance_clear')),\n state TEXT NOT NULL CHECK (state IN ('prepared','submitted','confirmed','failed','unknown')),\n request_json TEXT NOT NULL,\n request_digest TEXT NOT NULL,\n idempotency_key TEXT NOT NULL UNIQUE,\n transaction_hash TEXT,\n receipt_json TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (artifact_id, step_index),\n FOREIGN KEY (artifact_id) REFERENCES operations(artifact_id) ON DELETE CASCADE\n);\n";
|
|
4
|
+
export declare const LEGACY_EXIT_SCHEMA = "CREATE TABLE IF NOT EXISTS exit_operations (\n operation_id TEXT NOT NULL PRIMARY KEY,\n user_id TEXT NOT NULL,\n account TEXT NOT NULL,\n destination TEXT NOT NULL,\n amount_atomic TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('preparing','signing','signed','submitted','unknown','confirmed','reverted','replaced','canceled')),\n epoch INTEGER NOT NULL CHECK (epoch > 0),\n owner_token_digest TEXT,\n lease_expires_at_ms INTEGER,\n search_from_block TEXT,\n transaction_json TEXT,\n signed_transaction TEXT,\n transaction_hash TEXT,\n broadcast_attempts INTEGER NOT NULL DEFAULT 0 CHECK (broadcast_attempts >= 0),\n receipt_json TEXT,\n finalized_nonce TEXT,\n replacement_transaction_hash TEXT,\n replacement_block_number TEXT,\n replacement_block_hash TEXT,\n replacement_finalized_head_number TEXT,\n replacement_finalized_head_hash TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS exit_operations_user_updated\n ON exit_operations (user_id, updated_at_ms DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS one_unresolved_exit_per_user\n ON exit_operations (user_id)\n WHERE state IN ('preparing','signing','signed','submitted','unknown');\n";
|
|
5
|
+
export declare const NATIVE_CONSENT_SCHEMA = "\nCREATE TABLE IF NOT EXISTS native_yield_consent_subjects (\n user_id TEXT NOT NULL,\n account TEXT NOT NULL,\n generation INTEGER NOT NULL CHECK (generation >= 0),\n active_consent_id TEXT,\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n PRIMARY KEY (user_id, account)\n);\nCREATE TABLE IF NOT EXISTS native_yield_consents (\n consent_id TEXT NOT NULL PRIMARY KEY,\n user_id TEXT NOT NULL,\n account TEXT NOT NULL,\n authority_json TEXT NOT NULL,\n consent_commitment TEXT NOT NULL,\n generation INTEGER NOT NULL CHECK (generation > 0),\n state TEXT NOT NULL CHECK (state IN ('challenge','confirmed','revoked','superseded')),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n challenge_expires_at_ms INTEGER NOT NULL CHECK (challenge_expires_at_ms > created_at_ms),\n confirmed_at_ms INTEGER,\n FOREIGN KEY (user_id, account) REFERENCES native_yield_consent_subjects(user_id, account)\n);\nCREATE UNIQUE INDEX IF NOT EXISTS native_yield_consents_one_confirmed_subject\n ON native_yield_consents(user_id, account) WHERE state = 'confirmed';\nCREATE INDEX IF NOT EXISTS native_yield_consents_subject_generation\n ON native_yield_consents(user_id, account, generation);\nCREATE TRIGGER IF NOT EXISTS native_yield_consents_immutable_terms\nBEFORE UPDATE OF consent_id,user_id,account,authority_json,consent_commitment,generation,created_at_ms,challenge_expires_at_ms\nON native_yield_consents BEGIN\n SELECT RAISE(ABORT, 'native consent challenge terms are immutable');\nEND;\nCREATE TRIGGER IF NOT EXISTS native_yield_consents_preserve_history\nBEFORE DELETE ON native_yield_consents BEGIN\n SELECT RAISE(ABORT, 'native consent history is immutable');\nEND;\n";
|
|
6
|
+
export declare const NATIVE_OPERATION_SCHEMA = "CREATE TABLE IF NOT EXISTS native_yield_operations (\n operation_id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, record_json TEXT NOT NULL\n ); CREATE INDEX IF NOT EXISTS native_yield_user ON native_yield_operations(user_id);";
|
|
7
|
+
export declare const SPONSORED_OPERATION_SCHEMA = "CREATE TABLE IF NOT EXISTS sponsored_operations (\n operation_id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, artifact_key TEXT NOT NULL,\n record_json TEXT NOT NULL, UNIQUE(user_id,artifact_key));";
|
|
8
|
+
export declare const EXECUTION_STORAGE_SCHEMA: string;
|
|
9
|
+
/** Read-only: a missing/changed safety constraint requires operator repair, never a lazy CREATE. */
|
|
10
|
+
export declare function assertExecutionStorageSchema(db: ExecutionJournalDatabase, expectedSql: string): void;
|
|
11
|
+
/** Claim ownership must already be coherent. Startup never repairs history or invents claims. */
|
|
12
|
+
export declare function assertLegacyExecutionRows(db: ExecutionJournalDatabase): void;
|
|
13
|
+
/** Validate the declared source version BEFORE any migration can install missing later-rail objects. */
|
|
14
|
+
export declare function assertDeclaredExecutionStorage(db: ExecutionJournalDatabase, version: 2 | 3 | 4 | 5 | 6): void;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
export const LEGACY_OPERATION_SCHEMA = `CREATE TABLE IF NOT EXISTS operations (
|
|
2
|
+
artifact_id TEXT NOT NULL PRIMARY KEY,
|
|
3
|
+
user_id TEXT NOT NULL,
|
|
4
|
+
account TEXT NOT NULL,
|
|
5
|
+
chain_id INTEGER NOT NULL CHECK (chain_id = 8453),
|
|
6
|
+
fingerprint TEXT NOT NULL,
|
|
7
|
+
state TEXT NOT NULL CHECK (state IN ('prepared','submitted','confirmed','failed','unknown')),
|
|
8
|
+
created_at_ms INTEGER NOT NULL,
|
|
9
|
+
updated_at_ms INTEGER NOT NULL,
|
|
10
|
+
recovery_context_json TEXT,
|
|
11
|
+
result_json TEXT,
|
|
12
|
+
failure_code TEXT
|
|
13
|
+
);
|
|
14
|
+
CREATE UNIQUE INDEX IF NOT EXISTS operations_one_unresolved_per_user
|
|
15
|
+
ON operations (user_id) WHERE state IN ('prepared','submitted','unknown');
|
|
16
|
+
CREATE INDEX IF NOT EXISTS operations_user_updated
|
|
17
|
+
ON operations (user_id, updated_at_ms DESC);
|
|
18
|
+
CREATE TABLE IF NOT EXISTS operation_steps (
|
|
19
|
+
artifact_id TEXT NOT NULL,
|
|
20
|
+
step_index INTEGER NOT NULL CHECK (step_index >= 0 AND step_index <= 3),
|
|
21
|
+
step_kind TEXT NOT NULL CHECK (step_kind IN ('approve_reset','approve','call','allowance_clear')),
|
|
22
|
+
state TEXT NOT NULL CHECK (state IN ('prepared','submitted','confirmed','failed','unknown')),
|
|
23
|
+
request_json TEXT NOT NULL,
|
|
24
|
+
request_digest TEXT NOT NULL,
|
|
25
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
26
|
+
transaction_hash TEXT,
|
|
27
|
+
receipt_json TEXT,
|
|
28
|
+
created_at_ms INTEGER NOT NULL,
|
|
29
|
+
updated_at_ms INTEGER NOT NULL,
|
|
30
|
+
PRIMARY KEY (artifact_id, step_index),
|
|
31
|
+
FOREIGN KEY (artifact_id) REFERENCES operations(artifact_id) ON DELETE CASCADE
|
|
32
|
+
);
|
|
33
|
+
`;
|
|
34
|
+
export const LEGACY_EXIT_SCHEMA = `CREATE TABLE IF NOT EXISTS exit_operations (
|
|
35
|
+
operation_id TEXT NOT NULL PRIMARY KEY,
|
|
36
|
+
user_id TEXT NOT NULL,
|
|
37
|
+
account TEXT NOT NULL,
|
|
38
|
+
destination TEXT NOT NULL,
|
|
39
|
+
amount_atomic TEXT NOT NULL,
|
|
40
|
+
state TEXT NOT NULL CHECK (state IN ('preparing','signing','signed','submitted','unknown','confirmed','reverted','replaced','canceled')),
|
|
41
|
+
epoch INTEGER NOT NULL CHECK (epoch > 0),
|
|
42
|
+
owner_token_digest TEXT,
|
|
43
|
+
lease_expires_at_ms INTEGER,
|
|
44
|
+
search_from_block TEXT,
|
|
45
|
+
transaction_json TEXT,
|
|
46
|
+
signed_transaction TEXT,
|
|
47
|
+
transaction_hash TEXT,
|
|
48
|
+
broadcast_attempts INTEGER NOT NULL DEFAULT 0 CHECK (broadcast_attempts >= 0),
|
|
49
|
+
receipt_json TEXT,
|
|
50
|
+
finalized_nonce TEXT,
|
|
51
|
+
replacement_transaction_hash TEXT,
|
|
52
|
+
replacement_block_number TEXT,
|
|
53
|
+
replacement_block_hash TEXT,
|
|
54
|
+
replacement_finalized_head_number TEXT,
|
|
55
|
+
replacement_finalized_head_hash TEXT,
|
|
56
|
+
created_at_ms INTEGER NOT NULL,
|
|
57
|
+
updated_at_ms INTEGER NOT NULL
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS exit_operations_user_updated
|
|
60
|
+
ON exit_operations (user_id, updated_at_ms DESC);
|
|
61
|
+
CREATE UNIQUE INDEX IF NOT EXISTS one_unresolved_exit_per_user
|
|
62
|
+
ON exit_operations (user_id)
|
|
63
|
+
WHERE state IN ('preparing','signing','signed','submitted','unknown');
|
|
64
|
+
`;
|
|
65
|
+
export const NATIVE_CONSENT_SCHEMA = `
|
|
66
|
+
CREATE TABLE IF NOT EXISTS native_yield_consent_subjects (
|
|
67
|
+
user_id TEXT NOT NULL,
|
|
68
|
+
account TEXT NOT NULL,
|
|
69
|
+
generation INTEGER NOT NULL CHECK (generation >= 0),
|
|
70
|
+
active_consent_id TEXT,
|
|
71
|
+
updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),
|
|
72
|
+
PRIMARY KEY (user_id, account)
|
|
73
|
+
);
|
|
74
|
+
CREATE TABLE IF NOT EXISTS native_yield_consents (
|
|
75
|
+
consent_id TEXT NOT NULL PRIMARY KEY,
|
|
76
|
+
user_id TEXT NOT NULL,
|
|
77
|
+
account TEXT NOT NULL,
|
|
78
|
+
authority_json TEXT NOT NULL,
|
|
79
|
+
consent_commitment TEXT NOT NULL,
|
|
80
|
+
generation INTEGER NOT NULL CHECK (generation > 0),
|
|
81
|
+
state TEXT NOT NULL CHECK (state IN ('challenge','confirmed','revoked','superseded')),
|
|
82
|
+
created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),
|
|
83
|
+
challenge_expires_at_ms INTEGER NOT NULL CHECK (challenge_expires_at_ms > created_at_ms),
|
|
84
|
+
confirmed_at_ms INTEGER,
|
|
85
|
+
FOREIGN KEY (user_id, account) REFERENCES native_yield_consent_subjects(user_id, account)
|
|
86
|
+
);
|
|
87
|
+
CREATE UNIQUE INDEX IF NOT EXISTS native_yield_consents_one_confirmed_subject
|
|
88
|
+
ON native_yield_consents(user_id, account) WHERE state = 'confirmed';
|
|
89
|
+
CREATE INDEX IF NOT EXISTS native_yield_consents_subject_generation
|
|
90
|
+
ON native_yield_consents(user_id, account, generation);
|
|
91
|
+
CREATE TRIGGER IF NOT EXISTS native_yield_consents_immutable_terms
|
|
92
|
+
BEFORE UPDATE OF consent_id,user_id,account,authority_json,consent_commitment,generation,created_at_ms,challenge_expires_at_ms
|
|
93
|
+
ON native_yield_consents BEGIN
|
|
94
|
+
SELECT RAISE(ABORT, 'native consent challenge terms are immutable');
|
|
95
|
+
END;
|
|
96
|
+
CREATE TRIGGER IF NOT EXISTS native_yield_consents_preserve_history
|
|
97
|
+
BEFORE DELETE ON native_yield_consents BEGIN
|
|
98
|
+
SELECT RAISE(ABORT, 'native consent history is immutable');
|
|
99
|
+
END;
|
|
100
|
+
`;
|
|
101
|
+
export const NATIVE_OPERATION_SCHEMA = `CREATE TABLE IF NOT EXISTS native_yield_operations (
|
|
102
|
+
operation_id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, record_json TEXT NOT NULL
|
|
103
|
+
); CREATE INDEX IF NOT EXISTS native_yield_user ON native_yield_operations(user_id);`;
|
|
104
|
+
export const SPONSORED_OPERATION_SCHEMA = `CREATE TABLE IF NOT EXISTS sponsored_operations (
|
|
105
|
+
operation_id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, artifact_key TEXT NOT NULL,
|
|
106
|
+
record_json TEXT NOT NULL, UNIQUE(user_id,artifact_key));`;
|
|
107
|
+
export const EXECUTION_STORAGE_SCHEMA = LEGACY_OPERATION_SCHEMA + LEGACY_EXIT_SCHEMA +
|
|
108
|
+
NATIVE_CONSENT_SCHEMA + NATIVE_OPERATION_SCHEMA + SPONSORED_OPERATION_SCHEMA;
|
|
109
|
+
// Compare independent schema terms without depending on nullable-column insertion order.
|
|
110
|
+
// SQLite may quote a renamed table, but table constraints, indexes and triggers must be exact.
|
|
111
|
+
function compact(sql) {
|
|
112
|
+
// Whitespace inside SQL literals is data. Never normalize a changed CHECK/string into acceptance.
|
|
113
|
+
return sql.replace(/'(?:''|[^'])*'|"(?:[^"]|"")*"|\s+|IF\s+NOT\s+EXISTS/gi, (token) => token.startsWith("'") ? token : token.startsWith('"') ? token.slice(1, -1) : '').replace(/;$/, '');
|
|
114
|
+
}
|
|
115
|
+
function tableTerms(sql) {
|
|
116
|
+
const start = sql.indexOf('('), end = sql.lastIndexOf(')');
|
|
117
|
+
if (start < 0 || end <= start || compact(sql.slice(end + 1)) !== '')
|
|
118
|
+
throw new Error('the shared execution journal is corrupt or unsupported (storage table options)');
|
|
119
|
+
const parts = [];
|
|
120
|
+
let depth = 0, quote = false, from = start + 1;
|
|
121
|
+
for (let i = from; i < end; i++) {
|
|
122
|
+
if (sql[i] === "'")
|
|
123
|
+
quote = !quote;
|
|
124
|
+
if (!quote) {
|
|
125
|
+
if (sql[i] === '(')
|
|
126
|
+
depth++;
|
|
127
|
+
if (sql[i] === ')')
|
|
128
|
+
depth--;
|
|
129
|
+
if (sql[i] === ',' && depth === 0) {
|
|
130
|
+
parts.push(compact(sql.slice(from, i)));
|
|
131
|
+
from = i + 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
parts.push(compact(sql.slice(from, end)));
|
|
136
|
+
return parts;
|
|
137
|
+
}
|
|
138
|
+
function sameTable(actual, expected, name) {
|
|
139
|
+
const actualTerms = JSON.stringify(tableTerms(actual)), expectedTerms = tableTerms(expected);
|
|
140
|
+
if (actualTerms === JSON.stringify(expectedTerms))
|
|
141
|
+
return true;
|
|
142
|
+
// Historical ALTER TABLE appended these exact nullable recovery columns. No other reordering is supported.
|
|
143
|
+
const additions = name === 'operations' ? ['recovery_context_jsonTEXT'] : [];
|
|
144
|
+
return additions.length > 0 && actualTerms === JSON.stringify([
|
|
145
|
+
...expectedTerms.filter(term => !additions.includes(term)), ...additions,
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
/** Read-only: a missing/changed safety constraint requires operator repair, never a lazy CREATE. */
|
|
149
|
+
export function assertExecutionStorageSchema(db, expectedSql) {
|
|
150
|
+
const expected = [...expectedSql.matchAll(/CREATE\s+(?:UNIQUE\s+)?(TABLE|INDEX|TRIGGER)\s+IF NOT EXISTS\s+(\w+)[\s\S]*?(?=CREATE\s+(?:UNIQUE\s+)?(?:TABLE|INDEX|TRIGGER)\s+IF NOT EXISTS|$)/g)];
|
|
151
|
+
const names = new Set(expected.map(entry => entry[2]));
|
|
152
|
+
for (const entry of expected) {
|
|
153
|
+
const kind = entry[1].toLowerCase(), name = entry[2];
|
|
154
|
+
const row = db.prepare('SELECT sql FROM sqlite_master WHERE type=? AND name=?').get(kind, name);
|
|
155
|
+
if (typeof row?.sql !== 'string' || (kind === 'table'
|
|
156
|
+
? !sameTable(row.sql, entry[0], name) : compact(row.sql) !== compact(entry[0])))
|
|
157
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (storage contract ${name})`);
|
|
158
|
+
if (kind === 'table') {
|
|
159
|
+
const attached = db.prepare("SELECT name FROM sqlite_master WHERE tbl_name=? AND type IN ('index','trigger') AND name NOT LIKE 'sqlite_%'")
|
|
160
|
+
.all(name);
|
|
161
|
+
if (attached.some(object => !names.has(object.name)))
|
|
162
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (unrecognized storage index or trigger on ${name})`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Claim ownership must already be coherent. Startup never repairs history or invents claims. */
|
|
167
|
+
export function assertLegacyExecutionRows(db) {
|
|
168
|
+
for (const [table, id, kind, states] of [
|
|
169
|
+
['operations', 'artifact_id', 'delegated', "'prepared','submitted','unknown'"],
|
|
170
|
+
['exit_operations', 'operation_id', 'exit', "'preparing','signing','signed','submitted','unknown'"],
|
|
171
|
+
]) {
|
|
172
|
+
if (db.prepare(`SELECT o.${id} FROM ${table} o LEFT JOIN user_execution_claims c
|
|
173
|
+
ON c.user_id=o.user_id AND c.kind=? AND c.claim_id=o.${id}
|
|
174
|
+
WHERE o.state IN (${states}) AND c.user_id IS NULL LIMIT 1`).get(kind) ||
|
|
175
|
+
db.prepare(`SELECT c.claim_id FROM user_execution_claims c LEFT JOIN ${table} o
|
|
176
|
+
ON o.user_id=c.user_id AND o.${id}=c.claim_id AND o.state IN (${states})
|
|
177
|
+
WHERE c.kind=? AND o.${id} IS NULL LIMIT 1`).get(kind))
|
|
178
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (${kind} unresolved claim binding)`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Validate the declared source version BEFORE any migration can install missing later-rail objects. */
|
|
182
|
+
export function assertDeclaredExecutionStorage(db, version) {
|
|
183
|
+
assertExecutionStorageSchema(db, LEGACY_OPERATION_SCHEMA + LEGACY_EXIT_SCHEMA);
|
|
184
|
+
assertLegacyExecutionRows(db);
|
|
185
|
+
for (const [kind, table, introduced, sql] of [
|
|
186
|
+
['native', 'native_yield_operations', 3, NATIVE_CONSENT_SCHEMA + NATIVE_OPERATION_SCHEMA],
|
|
187
|
+
['sponsored', 'sponsored_operations', 4, SPONSORED_OPERATION_SCHEMA],
|
|
188
|
+
]) {
|
|
189
|
+
if (version < introduced) {
|
|
190
|
+
// A version label cannot hide storage from a newer rail. Even partial/empty tables may contain
|
|
191
|
+
// evidence no older writer can bind to a claim; startup and migration must leave them untouched.
|
|
192
|
+
for (const [, name] of sql.matchAll(/CREATE TABLE IF NOT EXISTS (\w+)/g)) {
|
|
193
|
+
if (db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=? COLLATE NOCASE").get(name))
|
|
194
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (${kind} storage predates schema)`);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
assertExecutionStorageSchema(db, sql);
|
|
199
|
+
// Inspect unresolved identity/state only; terminal financial records retain their on-demand validator.
|
|
200
|
+
if (db.prepare(`SELECT operation_id FROM ${table} WHERE NOT json_valid(record_json) LIMIT 1`).get())
|
|
201
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (${kind} operation JSON)`);
|
|
202
|
+
if (kind === 'sponsored' && db.prepare(`SELECT operation_id FROM sponsored_operations WHERE
|
|
203
|
+
json_type(record_json,'$.version') IS NOT 'integer' OR
|
|
204
|
+
json_extract(record_json,'$.version') NOT IN (${version < 5 ? '1' : version < 6 ? '1,2' : '1,2,3'}) LIMIT 1`).get())
|
|
205
|
+
throw new Error('the shared execution journal is corrupt or unsupported (sponsored record reader version)');
|
|
206
|
+
const states = kind === 'native' ? "'review','active','confirming','completed','failed','attention'"
|
|
207
|
+
: "'review','active','unknown','submitted','completed','failed'";
|
|
208
|
+
if (db.prepare(`SELECT operation_id FROM ${table} WHERE
|
|
209
|
+
json_type(record_json,'$.operationId') IS NOT 'text' OR json_extract(record_json,'$.operationId') IS NOT operation_id OR
|
|
210
|
+
json_type(record_json,'$.userId') IS NOT 'text' OR json_extract(record_json,'$.userId') IS NOT user_id OR
|
|
211
|
+
COALESCE(json_type(record_json,'$.reviewConsumed'),'missing') NOT IN ('true','false') OR
|
|
212
|
+
json_type(record_json,'$.state') IS NOT 'text' OR json_extract(record_json,'$.state') NOT IN (${states}) OR
|
|
213
|
+
(json_extract(record_json,'$.state')='review') IS NOT (json_extract(record_json,'$.reviewConsumed')=0)
|
|
214
|
+
LIMIT 1`).get())
|
|
215
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (${kind} operation header)`);
|
|
216
|
+
const unresolved = "json_extract(o.record_json,'$.reviewConsumed')=1 AND json_extract(o.record_json,'$.state') NOT IN ('completed','failed')";
|
|
217
|
+
if (db.prepare(`SELECT o.operation_id FROM ${table} o LEFT JOIN user_execution_claims c
|
|
218
|
+
ON c.user_id=o.user_id AND c.kind=? AND c.claim_id=o.operation_id
|
|
219
|
+
WHERE (${unresolved}) AND (c.user_id IS NULL OR json_extract(o.record_json,'$.userId')!=o.user_id
|
|
220
|
+
OR json_extract(o.record_json,'$.operationId')!=o.operation_id) LIMIT 1`).get(kind) ||
|
|
221
|
+
db.prepare(`SELECT c.claim_id FROM user_execution_claims c LEFT JOIN ${table} o
|
|
222
|
+
ON o.user_id=c.user_id AND o.operation_id=c.claim_id AND (${unresolved})
|
|
223
|
+
WHERE c.kind=? AND o.operation_id IS NULL LIMIT 1`).get(kind))
|
|
224
|
+
throw new Error(`the shared execution journal is corrupt or unsupported (${kind} unresolved claim binding)`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=execution-journal-tables.js.map
|