@zktx.io/sui-mcp 0.5.0
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/LICENSE +21 -0
- package/LICENSES/@mysten-bcs-2.0.5-Apache-2.0.txt +205 -0
- package/LICENSES/@mysten-sui-2.17.0-Apache-2.0.txt +201 -0
- package/LICENSES/@mysten-utils-0.3.3-Apache-2.0.txt +205 -0
- package/LICENSES/fancy-canvas-2.1.0-MIT.txt +7 -0
- package/LICENSES/lightweight-charts-5.2.0-NOTICE.txt +2 -0
- package/README.md +288 -0
- package/dist/adapters/adapterLifecycleValidators.js +5 -0
- package/dist/adapters/adapterPromptSurfaces.js +60 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +178 -0
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
- package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
- package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +254 -0
- package/dist/adapters/intentPlanFactories.js +51 -0
- package/dist/adapters/reviewAdapters.js +47 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/contractNameRegistry.js +151 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +193 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +71 -0
- package/dist/core/action/receiptPtbGraph.js +29 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +470 -0
- package/dist/core/action/schemas.js +491 -0
- package/dist/core/action/signableAdapterContract.js +998 -0
- package/dist/core/action/suiChainReceiptEvidence.js +106 -0
- package/dist/core/action/suiChainReceiptMapping.js +107 -0
- package/dist/core/action/suiChainReceiptReader.js +211 -0
- package/dist/core/action/suiChainReceiptVerifier.js +178 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +130 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +271 -0
- package/dist/core/action/transactionFunding.js +244 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +256 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +323 -0
- package/dist/core/action/types.js +33 -0
- package/dist/core/action/walletReviewContractAssembler.js +286 -0
- package/dist/core/activity/accountAssetTimeline.js +157 -0
- package/dist/core/activity/accountAssetTimelineUsdcReferences.js +120 -0
- package/dist/core/activity/activityStore.js +16 -0
- package/dist/core/activity/externalActivityCoverage.js +76 -0
- package/dist/core/activity/externalActivityTransactionStream.js +82 -0
- package/dist/core/activity/localDataService.js +296 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +439 -0
- package/dist/core/activity/schemaVersion.js +2 -0
- package/dist/core/activity/sqliteActivityStore.js +1068 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +389 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +367 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +186 -0
- package/dist/core/activity/transactionActivityAnalysis.js +295 -0
- package/dist/core/activity/transactionActivityClassifier.js +306 -0
- package/dist/core/activity/transactionActivityDetails.js +229 -0
- package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
- package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
- package/dist/core/activity/transactionActivityService.js +379 -0
- package/dist/core/activity/transactionActivityTypes.js +18 -0
- package/dist/core/eventlog/sink.js +35 -0
- package/dist/core/evidence/settlementFamilies.js +85 -0
- package/dist/core/evidence/userAnswerUse.js +1 -0
- package/dist/core/numeric/rawU64.js +63 -0
- package/dist/core/preferences/preferencesStore.js +26 -0
- package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
- package/dist/core/proposal/externalProposalReview.js +347 -0
- package/dist/core/proposal/schemas.js +208 -0
- package/dist/core/proposal/types.js +35 -0
- package/dist/core/read/amounts.js +14 -0
- package/dist/core/read/coinMetadata.js +60 -0
- package/dist/core/read/deepbookOfficialIndexerSource.js +373 -0
- package/dist/core/read/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +321 -0
- package/dist/core/read/deepbookRegistry.js +157 -0
- package/dist/core/read/deepbookSourceOwners.js +159 -0
- package/dist/core/read/deepbookUsdcChartService.js +342 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readCardInputs.js +19 -0
- package/dist/core/read/readCardResult.js +40 -0
- package/dist/core/read/readResponseGuidance.js +402 -0
- package/dist/core/read/readService.js +1639 -0
- package/dist/core/read/readServiceTypes.js +88 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +45 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/cardSession.js +31 -0
- package/dist/core/session/cardSessionStore.js +204 -0
- package/dist/core/session/keyedRecordStore.js +18 -0
- package/dist/core/session/liveReviewSessionContract.js +3 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +75 -0
- package/dist/core/session/reviewValidity.js +49 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionRecordStore.js +40 -0
- package/dist/core/session/sessionStore.js +655 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +45 -0
- package/dist/core/session/sqliteCardStore.js +88 -0
- package/dist/core/session/sqliteSessionStore.js +213 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +64 -0
- package/dist/core/session/sqliteWalletWorkflowStore.js +821 -0
- package/dist/core/session/status.js +60 -0
- package/dist/core/session/transactionMaterialStore.js +221 -0
- package/dist/core/session/transactionRequest.js +72 -0
- package/dist/core/session/wait.js +79 -0
- package/dist/core/session/walletConnection.js +92 -0
- package/dist/core/session/walletRuntime.js +38 -0
- package/dist/core/session/walletWorkflow.js +608 -0
- package/dist/core/session/workflowState.js +80 -0
- package/dist/core/session/workflowView.js +150 -0
- package/dist/core/suiAddress.js +18 -0
- package/dist/core/suiEndpoint.js +72 -0
- package/dist/mcp/activeAccountResponse.js +24 -0
- package/dist/mcp/prompts.js +146 -0
- package/dist/mcp/registerTool.js +19 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/responseGuidance.js +326 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +11 -0
- package/dist/mcp/server.js +32 -0
- package/dist/mcp/serverInfo.js +131 -0
- package/dist/mcp/toolErrors.js +118 -0
- package/dist/mcp/toolNames.js +58 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +126 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +686 -0
- package/dist/mcp/tools/read/index.js +14 -0
- package/dist/mcp/tools/read/readToolHelpers.js +97 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +179 -0
- package/dist/mcp/tools/read/serverStatusTools.js +100 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +371 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +858 -0
- package/dist/mcp/tools/read/walletReadTools.js +734 -0
- package/dist/mcp/tools/session/executionResultTools.js +44 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +36 -0
- package/dist/mcp/tools/session/statusTools.js +69 -0
- package/dist/mcp/tools/session/walletConnectionTools.js +69 -0
- package/dist/mcp/tools/settings/index.js +66 -0
- package/dist/mcp-app/account.html +3133 -0
- package/dist/mcp-app/account.notices.txt +973 -0
- package/dist/mcp-app/chart.html +3354 -0
- package/dist/mcp-app/chart.notices.txt +1194 -0
- package/dist/mcp-app/connect.html +3179 -0
- package/dist/mcp-app/connect.notices.txt +1012 -0
- package/dist/mcp-app/receipt.html +8430 -0
- package/dist/mcp-app/receipt.notices.txt +3549 -0
- package/dist/mcp-app/review.html +8430 -0
- package/dist/mcp-app/review.notices.txt +3549 -0
- package/dist/mcp-ui/contracts.js +31 -0
- package/dist/mcp-ui/readCards.js +28 -0
- package/dist/mcp-ui/tools.js +134 -0
- package/dist/review-app/brand-dark.svg +1 -0
- package/dist/review-app/brand-light.svg +1 -0
- package/dist/review-app/favicon.svg +1 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/ui.css +809 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +45 -0
- package/dist/review-server/http.js +47 -0
- package/dist/review-server/middleware/hostOrigin.js +48 -0
- package/dist/review-server/middleware/reviewToken.js +7 -0
- package/dist/review-server/reviewServerPolicy.js +1 -0
- package/dist/review-server/server.js +72 -0
- package/dist/review-server/settingsApi.js +158 -0
- package/dist/runtime/application.js +203 -0
- package/dist/runtime/config.js +97 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +66 -0
- package/dist/runtime/reviewDependencies.js +58 -0
- package/dist/runtime/reviewServerAcquire.js +93 -0
- package/dist/runtime/runtimeTermination.js +60 -0
- package/dist/runtime/shared/authenticatedFetch.js +127 -0
- package/dist/runtime/shared/control.js +93 -0
- package/dist/runtime/shared/dataAccess.js +66 -0
- package/dist/runtime/shared/mcpHttp.js +84 -0
- package/dist/runtime/shared/ownerLease.js +50 -0
- package/dist/runtime/shared/server.js +98 -0
- package/dist/runtime/shared/stdio.js +113 -0
- package/dist/runtime/smokeMainnetRead.js +391 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +306 -0
- package/dist/runtime/smokeReview.js +40 -0
- package/dist/runtime/start.js +90 -0
- package/dist/runtime/suiEndpoint.js +97 -0
- package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
- package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
- package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
- package/dist/runtime/walletConnectConfig.js +3 -0
- package/dist/runtime/walletConnectStorage.js +99 -0
- package/dist/runtime/walletConnectTransport.js +128 -0
- package/dist/runtime/walletSdkChild.js +3 -0
- package/dist/runtime/walletSdkChildRuntime.js +160 -0
- package/dist/runtime/walletSdkIpc.js +26 -0
- package/dist/runtime/walletSdkProcess.js +358 -0
- package/docs/AGENT_BEHAVIOR.md +524 -0
- package/docs/AGENT_DEVELOPMENT_POLICY.md +815 -0
- package/docs/FRONTEND_POLICY.md +534 -0
- package/docs/LOCAL_DB_ARCHITECTURE.md +381 -0
- package/docs/MCP_SETUP.md +742 -0
- package/docs/MCP_TOOLS.md +1318 -0
- package/docs/SDK_API.md +206 -0
- package/docs/SIGNABLE_ADAPTER_CONTRACT.md +396 -0
- package/docs/TRANSACTION_ACTIVITY_LOG.md +194 -0
- package/docs/UTILITY_INDEX.md +275 -0
- package/docs/WALLET_CONNECTION.md +238 -0
- package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +166 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +161 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +259 -0
- package/package.json +79 -0
- package/protocols/deepbook-margin.md +15 -0
- package/protocols/deepbook-v3.md +24 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cloneLocalSession, createLocalSessionBase, isLocalSessionExpired, tokenMatchesHash } from "./localSession.js";
|
|
2
|
+
import { InMemoryKeyedRecordStore } from "./keyedRecordStore.js";
|
|
3
|
+
export class SettingsSessionManager {
|
|
4
|
+
options;
|
|
5
|
+
sessions;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.sessions = options.recordStore ?? new InMemoryKeyedRecordStore();
|
|
9
|
+
}
|
|
10
|
+
async create(now) {
|
|
11
|
+
const { base, token } = createLocalSessionBase(now, this.options.ttlMs);
|
|
12
|
+
const session = {
|
|
13
|
+
...base,
|
|
14
|
+
type: "local_settings"
|
|
15
|
+
};
|
|
16
|
+
this.sessions.set(session.id, session);
|
|
17
|
+
await this.options.appendEventLog({
|
|
18
|
+
type: "settings_session.created",
|
|
19
|
+
sessionId: session.id,
|
|
20
|
+
at: now.toISOString()
|
|
21
|
+
});
|
|
22
|
+
return { session: cloneLocalSession(session), token };
|
|
23
|
+
}
|
|
24
|
+
async get(id, now) {
|
|
25
|
+
const session = this.sessions.get(id);
|
|
26
|
+
if (!session) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (isLocalSessionExpired(session, now)) {
|
|
30
|
+
this.sessions.delete(id);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return cloneLocalSession(session);
|
|
34
|
+
}
|
|
35
|
+
async validateToken(id, token, now) {
|
|
36
|
+
const session = await this.get(id, now);
|
|
37
|
+
if (!session) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return tokenMatchesHash(session.tokenHash, token);
|
|
41
|
+
}
|
|
42
|
+
clear() {
|
|
43
|
+
this.sessions.clear();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { cardStateSchema, receiptDisplaySchema } from "./cardSession.js";
|
|
2
|
+
export class SqliteCardRecordStore {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
hasReviewInput(reviewSessionId, ownerId, now) {
|
|
8
|
+
return !!this.db.prepare(`SELECT 1 FROM live_read_cards WHERE owner_id=? AND scope='review'
|
|
9
|
+
AND state='ready' AND accepted_input_json IS NULL AND expires_at>?
|
|
10
|
+
AND json_extract(input_json,'$.reviewSessionId')=? LIMIT 1`).get(ownerId, now.toISOString(), reviewSessionId);
|
|
11
|
+
}
|
|
12
|
+
get(id) {
|
|
13
|
+
const row = this.db.prepare("SELECT * FROM live_read_cards WHERE id = ?").get(id);
|
|
14
|
+
if (!row)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
state: cardStateSchema.parse({ cardId: row.id, kind: row.kind, state: row.state, revision: row.revision,
|
|
18
|
+
createdAt: row.created_at, expiresAt: row.expires_at, input: JSON.parse(row.input_json),
|
|
19
|
+
...(row.reason === null ? {} : { reason: row.reason }), ...(row.error === null ? {} : { error: row.error }),
|
|
20
|
+
...(row.result_json === null ? {} : { data: JSON.parse(row.result_json) }) }),
|
|
21
|
+
tokenHash: row.token_hash, ownerId: row.owner_id,
|
|
22
|
+
scope: row.scope, ...(row.operation_id === null ? {} : { operationId: row.operation_id }),
|
|
23
|
+
...(row.accepted_input_json === null ? {} : { acceptedInput: JSON.parse(row.accepted_input_json) }),
|
|
24
|
+
...(row.receipt_display_json === null ? {} : { receiptDisplay: receiptDisplaySchema.parse(JSON.parse(row.receipt_display_json)) })
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
evaluate(expected, clock) {
|
|
28
|
+
return this.db.transaction(() => {
|
|
29
|
+
const now = clock();
|
|
30
|
+
let record = this.get(expected.state.cardId);
|
|
31
|
+
if (!record || record.tokenHash !== expected.tokenHash || record.ownerId !== expected.ownerId)
|
|
32
|
+
throw new Error("Saved card access is unavailable.");
|
|
33
|
+
if (record.state.state === "ready" && Date.parse(record.state.expiresAt) <= now.getTime()) {
|
|
34
|
+
const next = { ...record, state: { ...record.state, state: "closed", reason: "expired", revision: record.state.revision + 1 } };
|
|
35
|
+
if (!this.replace(record, next))
|
|
36
|
+
throw new Error("Card expiry could not be committed.");
|
|
37
|
+
record = next;
|
|
38
|
+
}
|
|
39
|
+
return { record, evaluatedAt: now.toISOString() };
|
|
40
|
+
}).immediate();
|
|
41
|
+
}
|
|
42
|
+
admit(expected, input, ownerId, clock) {
|
|
43
|
+
return this.db.transaction(() => {
|
|
44
|
+
const { record } = this.evaluate(expected, clock);
|
|
45
|
+
if (record.ownerId !== ownerId || record.state.state !== "ready" || record.acceptedInput !== undefined ||
|
|
46
|
+
record.state.revision !== expected.state.revision)
|
|
47
|
+
return undefined;
|
|
48
|
+
const next = { ...record, acceptedInput: input, state: { ...record.state, input,
|
|
49
|
+
state: "running", revision: record.state.revision + 1 } };
|
|
50
|
+
if (!this.replace(record, next))
|
|
51
|
+
throw new Error("Card admission could not be committed.");
|
|
52
|
+
return next;
|
|
53
|
+
}).immediate();
|
|
54
|
+
}
|
|
55
|
+
create(record) {
|
|
56
|
+
this.db.prepare(`INSERT INTO live_read_cards
|
|
57
|
+
(id, owner_id, token_hash, kind, state, reason, error, revision, created_at, expires_at,
|
|
58
|
+
input_json, accepted_input_json, result_json, receipt_display_json, scope, operation_id)
|
|
59
|
+
VALUES (@id, @owner, @token, @kind, @state, @reason, @error, @revision, @created, @expires,
|
|
60
|
+
@input, @accepted, @result, @display, @scope, @operation)`).run(values(record));
|
|
61
|
+
}
|
|
62
|
+
replace(expected, next) {
|
|
63
|
+
// This single statement commits admission or a result atomically. A removed
|
|
64
|
+
// card is never recreated by a delayed network response.
|
|
65
|
+
return this.db.prepare(`UPDATE live_read_cards SET state=@state, reason=@reason, error=@error,
|
|
66
|
+
revision=@revision, input_json=@input, accepted_input_json=@accepted, result_json=@result,
|
|
67
|
+
receipt_display_json=@display, scope=@scope, operation_id=@operation
|
|
68
|
+
WHERE id=@id AND owner_id=@owner AND token_hash=@token AND revision=@expectedRevision
|
|
69
|
+
AND state=@expectedState`).run({ ...values(next), expectedRevision: expected.state.revision,
|
|
70
|
+
expectedState: expected.state.state }).changes === 1;
|
|
71
|
+
}
|
|
72
|
+
recover(ownerId, now) {
|
|
73
|
+
this.db.prepare(`UPDATE live_read_cards SET
|
|
74
|
+
reason=CASE WHEN state='ready' AND expires_at<=@now THEN 'expired' ELSE 'server_restarted' END,
|
|
75
|
+
state='closed', revision=revision+1, owner_id=@owner
|
|
76
|
+
WHERE state!='closed' AND owner_id!=@owner`).run({ owner: ownerId, now: now.toISOString() });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function values(record) {
|
|
80
|
+
const state = cardStateSchema.parse(record.state);
|
|
81
|
+
return { id: state.cardId, owner: record.ownerId, token: record.tokenHash, kind: state.kind,
|
|
82
|
+
scope: record.scope ?? "read", operation: record.operationId ?? null,
|
|
83
|
+
state: state.state, reason: state.reason ?? null, error: state.error ?? null, revision: state.revision,
|
|
84
|
+
created: state.createdAt, expires: state.expiresAt, input: JSON.stringify(state.input),
|
|
85
|
+
accepted: record.acceptedInput === undefined ? null : JSON.stringify(record.acceptedInput),
|
|
86
|
+
result: state.data === undefined ? null : JSON.stringify(state.data),
|
|
87
|
+
display: record.receiptDisplay === undefined ? null : JSON.stringify(receiptDisplaySchema.parse(record.receiptDisplay)) };
|
|
88
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { clonePrivateReviewArtifacts } from "./privateReviewArtifacts.js";
|
|
3
|
+
import { LIVE_REVIEW_SESSION_WRITE_CONTRACT_VERSION } from "./liveReviewSessionContract.js";
|
|
4
|
+
import { INITIAL_CHAIN_OBSERVATION_STATUSES } from "./transactionRequest.js";
|
|
5
|
+
export function insertLiveReviewSessionRow(db, session) {
|
|
6
|
+
const serialized = serializeSessionForLiveReviewSessionRow(session);
|
|
7
|
+
db.prepare(`INSERT INTO live_review_sessions
|
|
8
|
+
(id, token_hash, status, account, owner_id, review_revision, preparation_id, preparation_error, wallet_connection_id, wallet_connection_revision, current_attempt_id,
|
|
9
|
+
plans_json, review_state_json,
|
|
10
|
+
created_at, expires_at, last_activity_at, revision, write_contract_version)
|
|
11
|
+
VALUES
|
|
12
|
+
(@id, @tokenHash, @status, @account, @ownerId, @reviewRevision, @preparationId, @preparationError, @walletConnectionId, @walletConnectionRevision, @currentAttemptId,
|
|
13
|
+
@plansJson, @reviewStateJson,
|
|
14
|
+
@createdAt, @expiresAt, @lastActivityAt, 0, @writeContractVersion)`).run(serialized);
|
|
15
|
+
}
|
|
16
|
+
export function updateLiveReviewSessionRow(db, expectedRevision, session) {
|
|
17
|
+
const serialized = serializeSessionForLiveReviewSessionRow(session);
|
|
18
|
+
const result = db
|
|
19
|
+
.prepare(`UPDATE live_review_sessions
|
|
20
|
+
SET token_hash = @tokenHash,
|
|
21
|
+
status = @status,
|
|
22
|
+
account = @account,
|
|
23
|
+
owner_id = @ownerId,
|
|
24
|
+
review_revision = @reviewRevision,
|
|
25
|
+
preparation_id = @preparationId,
|
|
26
|
+
preparation_error = @preparationError,
|
|
27
|
+
wallet_connection_id = @walletConnectionId,
|
|
28
|
+
wallet_connection_revision = @walletConnectionRevision,
|
|
29
|
+
current_attempt_id = @currentAttemptId,
|
|
30
|
+
plans_json = @plansJson,
|
|
31
|
+
review_state_json = @reviewStateJson,
|
|
32
|
+
created_at = @createdAt,
|
|
33
|
+
expires_at = @expiresAt,
|
|
34
|
+
last_activity_at = @lastActivityAt,
|
|
35
|
+
revision = revision + 1,
|
|
36
|
+
write_contract_version = @writeContractVersion
|
|
37
|
+
WHERE id = @id AND revision = @expectedRevision`)
|
|
38
|
+
.run({ ...serialized, expectedRevision });
|
|
39
|
+
return result.changes > 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* SQLite-backed live review-session records. Runtime product transitions are
|
|
43
|
+
* committed by SqliteActivityStore when the store is created through that owner,
|
|
44
|
+
* so live session state and review activity stay in one SQLite transaction.
|
|
45
|
+
* Direct instances keep a low-level transition commit path for tests and mixed
|
|
46
|
+
* harnesses that do not couple live state with activity rows.
|
|
47
|
+
*/
|
|
48
|
+
export class SqliteSessionRecordStore {
|
|
49
|
+
db;
|
|
50
|
+
usesActivityStoreLiveSessionMutations;
|
|
51
|
+
constructor(db, options = {}) {
|
|
52
|
+
this.db = db;
|
|
53
|
+
this.usesActivityStoreLiveSessionMutations = options.usesActivityStoreLiveSessionMutations === true;
|
|
54
|
+
}
|
|
55
|
+
get(id) {
|
|
56
|
+
const row = this.db
|
|
57
|
+
.prepare(`SELECT * FROM live_review_sessions WHERE id = ?`)
|
|
58
|
+
.get(id);
|
|
59
|
+
return row ? sessionFromLiveReviewSessionRow(row) : undefined;
|
|
60
|
+
}
|
|
61
|
+
revision(id) { return this.getRow(id)?.revision; }
|
|
62
|
+
hasAdmittedRevision(id, reviewRevision) {
|
|
63
|
+
return !!this.db.prepare("SELECT 1 FROM review_requests WHERE review_session_id=? AND review_revision=?").get(id, reviewRevision);
|
|
64
|
+
}
|
|
65
|
+
create(id, session) {
|
|
66
|
+
if (this.getRow(id)) {
|
|
67
|
+
throw new Error(`Review session already exists: ${id}`);
|
|
68
|
+
}
|
|
69
|
+
insertLiveReviewSessionRow(this.db, session);
|
|
70
|
+
}
|
|
71
|
+
commitReviewSessionTransition(id, expected, next) {
|
|
72
|
+
const commit = this.db.transaction(() => {
|
|
73
|
+
const row = this.getRow(id);
|
|
74
|
+
if (!row || !isDeepStrictEqual(sessionFromLiveReviewSessionRow(row), expected)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return updateLiveReviewSessionRow(this.db, row.revision, next);
|
|
78
|
+
});
|
|
79
|
+
return commit.immediate();
|
|
80
|
+
}
|
|
81
|
+
ids() {
|
|
82
|
+
const rows = this.db.prepare(`SELECT id FROM live_review_sessions`).all();
|
|
83
|
+
return rows.map((row) => row.id);
|
|
84
|
+
}
|
|
85
|
+
clear() {
|
|
86
|
+
this.db.prepare(`DELETE FROM live_review_sessions`).run();
|
|
87
|
+
}
|
|
88
|
+
hasUnsettledRequest(id, now) {
|
|
89
|
+
return !!this.db.prepare(`SELECT 1 FROM live_request_authority l JOIN review_requests r ON r.attempt_id=l.attempt_id
|
|
90
|
+
WHERE r.review_session_id=? AND (l.sdk_pending=1 OR l.submit_pending=1 OR l.lookup_pending=1
|
|
91
|
+
OR (r.request_status IN (SELECT value FROM json_each(?)) AND (l.lookup_deadline IS NULL OR l.lookup_deadline>?))) LIMIT 1`)
|
|
92
|
+
.get(id, JSON.stringify(INITIAL_CHAIN_OBSERVATION_STATUSES), now.toISOString());
|
|
93
|
+
}
|
|
94
|
+
getRow(id) {
|
|
95
|
+
return this.db.prepare("SELECT * FROM live_review_sessions WHERE id=?").get(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class SqlitePrivateReviewArtifactStore {
|
|
99
|
+
db;
|
|
100
|
+
constructor(db) {
|
|
101
|
+
this.db = db;
|
|
102
|
+
}
|
|
103
|
+
get(reviewSessionId) {
|
|
104
|
+
const row = this.db
|
|
105
|
+
.prepare(`SELECT artifacts_json FROM live_private_review_artifacts WHERE review_session_id = ?`)
|
|
106
|
+
.get(reviewSessionId);
|
|
107
|
+
if (!row) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
// clonePrivateReviewArtifacts re-parses/validates the evidence on read, matching
|
|
111
|
+
// the in-memory store's clone-on-read behaviour.
|
|
112
|
+
return clonePrivateReviewArtifacts(JSON.parse(row.artifacts_json));
|
|
113
|
+
}
|
|
114
|
+
set(reviewSessionId, artifacts) {
|
|
115
|
+
const json = JSON.stringify(clonePrivateReviewArtifacts(artifacts));
|
|
116
|
+
this.db
|
|
117
|
+
.prepare(`INSERT INTO live_private_review_artifacts (review_session_id, artifacts_json)
|
|
118
|
+
VALUES (?, ?)
|
|
119
|
+
ON CONFLICT(review_session_id) DO UPDATE SET artifacts_json = excluded.artifacts_json`)
|
|
120
|
+
.run(reviewSessionId, json);
|
|
121
|
+
}
|
|
122
|
+
delete(reviewSessionId) {
|
|
123
|
+
this.db.prepare(`DELETE FROM live_private_review_artifacts WHERE review_session_id = ?`).run(reviewSessionId);
|
|
124
|
+
}
|
|
125
|
+
clear() {
|
|
126
|
+
this.db.prepare(`DELETE FROM live_private_review_artifacts`).run();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* SQLite-backed id-keyed store for the short-lived settings
|
|
131
|
+
* session manager. Each record is a JSON blob keyed by id; the table name is a
|
|
132
|
+
* trusted constant supplied by the factories below (never user input).
|
|
133
|
+
*/
|
|
134
|
+
export class SqliteKeyedRecordStore {
|
|
135
|
+
db;
|
|
136
|
+
table;
|
|
137
|
+
revive;
|
|
138
|
+
constructor(db, table, revive) {
|
|
139
|
+
this.db = db;
|
|
140
|
+
this.table = table;
|
|
141
|
+
this.revive = revive;
|
|
142
|
+
}
|
|
143
|
+
get(id) {
|
|
144
|
+
const row = this.db
|
|
145
|
+
.prepare(`SELECT session_json FROM ${this.table} WHERE id = ?`)
|
|
146
|
+
.get(id);
|
|
147
|
+
return row ? this.revive(JSON.parse(row.session_json)) : undefined;
|
|
148
|
+
}
|
|
149
|
+
set(id, value) {
|
|
150
|
+
this.db
|
|
151
|
+
.prepare(`INSERT INTO ${this.table} (id, session_json) VALUES (?, ?)
|
|
152
|
+
ON CONFLICT(id) DO UPDATE SET session_json = excluded.session_json`)
|
|
153
|
+
.run(id, JSON.stringify(value));
|
|
154
|
+
}
|
|
155
|
+
delete(id) {
|
|
156
|
+
this.db.prepare(`DELETE FROM ${this.table} WHERE id = ?`).run(id);
|
|
157
|
+
}
|
|
158
|
+
ids() {
|
|
159
|
+
const rows = this.db.prepare(`SELECT id FROM ${this.table}`).all();
|
|
160
|
+
return rows.map((row) => row.id);
|
|
161
|
+
}
|
|
162
|
+
clear() {
|
|
163
|
+
this.db.prepare(`DELETE FROM ${this.table}`).run();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function createSqliteSettingsRecordStore(db) {
|
|
167
|
+
return new SqliteKeyedRecordStore(db, "live_settings_sessions", (raw) => raw);
|
|
168
|
+
}
|
|
169
|
+
// Reconstruct the ReviewSession from columns + JSON blobs. Optional fields are
|
|
170
|
+
// omitted (not set to undefined) so the shape matches the in-memory store exactly.
|
|
171
|
+
export function sessionFromLiveReviewSessionRow(row) {
|
|
172
|
+
return {
|
|
173
|
+
id: row.id,
|
|
174
|
+
tokenHash: row.token_hash,
|
|
175
|
+
status: row.status,
|
|
176
|
+
createdAt: row.created_at,
|
|
177
|
+
expiresAt: row.expires_at,
|
|
178
|
+
lastActivityAt: row.last_activity_at,
|
|
179
|
+
plans: JSON.parse(row.plans_json),
|
|
180
|
+
...(row.account === null ? {} : { account: row.account }),
|
|
181
|
+
...(row.review_state_json === null
|
|
182
|
+
? {}
|
|
183
|
+
: { reviewState: JSON.parse(row.review_state_json) }),
|
|
184
|
+
ownerId: row.owner_id,
|
|
185
|
+
reviewRevision: row.review_revision,
|
|
186
|
+
...(row.wallet_connection_id === null ? {} : { walletConnectionId: row.wallet_connection_id }),
|
|
187
|
+
...(row.wallet_connection_revision === null ? {} : { walletConnectionRevision: row.wallet_connection_revision }),
|
|
188
|
+
...(row.preparation_error === null ? {} : { preparationError: row.preparation_error }),
|
|
189
|
+
...(row.preparation_id === null ? {} : { preparationId: row.preparation_id }),
|
|
190
|
+
...(row.current_attempt_id === null ? {} : { currentAttemptId: row.current_attempt_id })
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function serializeSessionForLiveReviewSessionRow(session) {
|
|
194
|
+
return {
|
|
195
|
+
id: session.id,
|
|
196
|
+
tokenHash: session.tokenHash,
|
|
197
|
+
status: session.status,
|
|
198
|
+
account: session.account ?? null,
|
|
199
|
+
ownerId: session.ownerId,
|
|
200
|
+
reviewRevision: session.reviewRevision,
|
|
201
|
+
preparationId: session.preparationId ?? null,
|
|
202
|
+
preparationError: session.preparationError ?? null,
|
|
203
|
+
walletConnectionId: session.walletConnectionId ?? null,
|
|
204
|
+
walletConnectionRevision: session.walletConnectionRevision ?? null,
|
|
205
|
+
currentAttemptId: session.currentAttemptId ?? null,
|
|
206
|
+
plansJson: JSON.stringify(session.plans),
|
|
207
|
+
reviewStateJson: session.reviewState ? JSON.stringify(session.reviewState) : null,
|
|
208
|
+
createdAt: session.createdAt,
|
|
209
|
+
expiresAt: session.expiresAt,
|
|
210
|
+
lastActivityAt: session.lastActivityAt,
|
|
211
|
+
writeContractVersion: LIVE_REVIEW_SESSION_WRITE_CONTRACT_VERSION
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { buildTransactionMaterialRecord, sameHandle, toMaterialHandle } from "./transactionMaterialStore.js";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite-backed transaction material store. Holds the same synchronous contract as
|
|
4
|
+
* InMemoryLocalTransactionMaterialStore so producers and the wallet handoff path are
|
|
5
|
+
* unchanged, but persists records (including the unsigned transaction BLOB) to the
|
|
6
|
+
* shared local database so any review-server process can read them. Validation,
|
|
7
|
+
* handle identity, and expiry semantics are shared via transactionMaterialStore.ts,
|
|
8
|
+
* so the two backends never drift. The unsigned bytes are protected at rest by the
|
|
9
|
+
* data directory (0700) and database file (0600) permissions set in SqliteActivityStore.
|
|
10
|
+
*/
|
|
11
|
+
export class SqliteTransactionMaterialStore {
|
|
12
|
+
db;
|
|
13
|
+
constructor(db) {
|
|
14
|
+
this.db = db;
|
|
15
|
+
}
|
|
16
|
+
recordTransactionMaterial(input, now = new Date()) {
|
|
17
|
+
const record = buildTransactionMaterialRecord(input, now);
|
|
18
|
+
this.db
|
|
19
|
+
.prepare(`INSERT INTO live_transaction_materials
|
|
20
|
+
(material_id, review_session_id, plan_id, account, kind, source,
|
|
21
|
+
transaction_bytes, redacted_diagnostics_json, created_at, expires_at)
|
|
22
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
23
|
+
.run(record.materialId, record.reviewSessionId, record.planId, record.account, record.kind, record.source, Buffer.from(record.transactionBytes), record.redactedDiagnostics === undefined ? null : JSON.stringify(record.redactedDiagnostics), record.createdAt, record.expiresAt);
|
|
24
|
+
return toMaterialHandle(record);
|
|
25
|
+
}
|
|
26
|
+
getTransactionMaterial(handle, now = new Date()) {
|
|
27
|
+
const row = this.db
|
|
28
|
+
.prepare(`SELECT * FROM live_transaction_materials WHERE material_id = ?`)
|
|
29
|
+
.get(handle.materialId);
|
|
30
|
+
if (!row) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (Date.parse(row.expires_at) <= now.getTime()) {
|
|
34
|
+
this.db.prepare(`DELETE FROM live_transaction_materials WHERE material_id = ?`).run(handle.materialId);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const record = recordFromRow(row);
|
|
38
|
+
if (!sameHandle(record, handle)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return record;
|
|
42
|
+
}
|
|
43
|
+
deleteReviewSessionTransactionMaterials(reviewSessionId) {
|
|
44
|
+
this.db.prepare(`DELETE FROM live_transaction_materials WHERE review_session_id = ?`).run(reviewSessionId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Each read rebuilds a fresh record (new Uint8Array from the BLOB, parsed
|
|
48
|
+
// diagnostics) so a caller mutating the result never touches the stored row.
|
|
49
|
+
function recordFromRow(row) {
|
|
50
|
+
return {
|
|
51
|
+
materialId: row.material_id,
|
|
52
|
+
reviewSessionId: row.review_session_id,
|
|
53
|
+
planId: row.plan_id,
|
|
54
|
+
account: row.account,
|
|
55
|
+
kind: row.kind,
|
|
56
|
+
source: row.source,
|
|
57
|
+
createdAt: row.created_at,
|
|
58
|
+
expiresAt: row.expires_at,
|
|
59
|
+
transactionBytes: new Uint8Array(row.transaction_bytes),
|
|
60
|
+
...(row.redacted_diagnostics_json == null
|
|
61
|
+
? {}
|
|
62
|
+
: { redactedDiagnostics: JSON.parse(row.redacted_diagnostics_json) })
|
|
63
|
+
};
|
|
64
|
+
}
|