@stelis/say-ur-intent 0.0.0 → 0.0.2

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.
Files changed (234) hide show
  1. package/README.md +4 -39
  2. package/dist/adapters/adapterLifecycleValidators.js +7 -0
  3. package/dist/adapters/adapterPromptSurfaces.js +71 -0
  4. package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +175 -0
  5. package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
  6. package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
  7. package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
  8. package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
  9. package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +269 -0
  10. package/dist/adapters/flowx/flowxSwapHumanReviewProducer.js +176 -0
  11. package/dist/adapters/flowx/flowxSwapIntent.js +79 -0
  12. package/dist/adapters/flowx/flowxSwapQuotePolicy.js +104 -0
  13. package/dist/adapters/flowx/flowxSwapReviewEvidence.js +468 -0
  14. package/dist/adapters/flowx/flowxSwapReviewLifecycle.js +85 -0
  15. package/dist/adapters/flowx/flowxSwapTransactionMaterialProducer.js +362 -0
  16. package/dist/adapters/intentPlanFactories.js +59 -0
  17. package/dist/adapters/reviewAdapters.js +81 -0
  18. package/dist/core/action/adapterLifecycleValidation.js +12 -0
  19. package/dist/core/action/forbiddenFields.js +43 -0
  20. package/dist/core/action/humanReadableReviewEvidence.js +203 -0
  21. package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
  22. package/dist/core/action/ptbVisualizationProducer.js +66 -0
  23. package/dist/core/action/reviewCheckResults.js +6 -0
  24. package/dist/core/action/reviewStateValidation.js +11 -0
  25. package/dist/core/action/reviewTimeSimulationEvidence.js +471 -0
  26. package/dist/core/action/schemas.js +529 -0
  27. package/dist/core/action/signableAdapterContract.js +993 -0
  28. package/dist/core/action/swapHumanReadableReviewProjection.js +124 -0
  29. package/dist/core/action/swapQuotePolicyEvidence.js +278 -0
  30. package/dist/core/action/transactionObjectOwnershipEvidence.js +247 -0
  31. package/dist/core/action/transactionObjectOwnershipProducer.js +329 -0
  32. package/dist/core/action/types.js +35 -0
  33. package/dist/core/action/walletReviewContractAssembler.js +282 -0
  34. package/dist/core/activity/activityStore.js +15 -0
  35. package/dist/core/activity/localDataService.js +258 -0
  36. package/dist/core/activity/localDataTypes.js +11 -0
  37. package/dist/core/activity/localDataValidation.js +396 -0
  38. package/dist/core/activity/schemaVersion.js +1 -0
  39. package/dist/core/activity/sqliteActivityStore.js +820 -0
  40. package/dist/core/activity/sqliteActivityStoreRows.js +430 -0
  41. package/dist/core/activity/sqliteActivityStoreSchema.js +258 -0
  42. package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
  43. package/dist/core/activity/suiFunctionTarget.js +43 -0
  44. package/dist/core/activity/transactionActivityAccountEffects.js +189 -0
  45. package/dist/core/activity/transactionActivityAnalysis.js +295 -0
  46. package/dist/core/activity/transactionActivityClassifier.js +306 -0
  47. package/dist/core/activity/transactionActivityDetails.js +229 -0
  48. package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
  49. package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
  50. package/dist/core/activity/transactionActivityService.js +379 -0
  51. package/dist/core/activity/transactionActivityTypes.js +18 -0
  52. package/dist/core/eventlog/sink.js +35 -0
  53. package/dist/core/evidence/settlementFamilies.js +87 -0
  54. package/dist/core/evidence/userAnswerUse.js +1 -0
  55. package/dist/core/numeric/rawU64.js +63 -0
  56. package/dist/core/preferences/preferencesStore.js +26 -0
  57. package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
  58. package/dist/core/proposal/externalProposalReview.js +347 -0
  59. package/dist/core/proposal/schemas.js +208 -0
  60. package/dist/core/proposal/types.js +35 -0
  61. package/dist/core/read/amounts.js +14 -0
  62. package/dist/core/read/coinMetadata.js +60 -0
  63. package/dist/core/read/deepbookRawQuoteClient.js +86 -0
  64. package/dist/core/read/deepbookReadHelpers.js +265 -0
  65. package/dist/core/read/deepbookRegistry.js +133 -0
  66. package/dist/core/read/flowxQuoteClient.js +117 -0
  67. package/dist/core/read/flowxReadHelpers.js +145 -0
  68. package/dist/core/read/flowxRegistry.js +174 -0
  69. package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
  70. package/dist/core/read/readResponseGuidance.js +451 -0
  71. package/dist/core/read/readService.js +1164 -0
  72. package/dist/core/read/readServiceTypes.js +59 -0
  73. package/dist/core/read/settlementParityFormatting.js +82 -0
  74. package/dist/core/read/walletReadHelpers.js +99 -0
  75. package/dist/core/review/reviewChecks.js +54 -0
  76. package/dist/core/review/reviewComputation.js +38 -0
  77. package/dist/core/review/reviewComputationResult.js +87 -0
  78. package/dist/core/session/localSession.js +31 -0
  79. package/dist/core/session/privateReviewArtifacts.js +73 -0
  80. package/dist/core/session/sessionErrors.js +9 -0
  81. package/dist/core/session/sessionStore.js +821 -0
  82. package/dist/core/session/settingsSession.js +1 -0
  83. package/dist/core/session/settingsSessions.js +43 -0
  84. package/dist/core/session/status.js +86 -0
  85. package/dist/core/session/transactionMaterialStore.js +205 -0
  86. package/dist/core/session/wait.js +102 -0
  87. package/dist/core/session/walletIdentity.js +103 -0
  88. package/dist/core/session/walletIdentitySessions.js +189 -0
  89. package/dist/core/suiAddress.js +18 -0
  90. package/dist/core/suiEndpoint.js +72 -0
  91. package/dist/mcp/activeAccountResponse.js +24 -0
  92. package/dist/mcp/prompts.js +146 -0
  93. package/dist/mcp/registerTool.js +19 -0
  94. package/dist/mcp/resources.js +72 -0
  95. package/dist/mcp/responseGuidance.js +381 -0
  96. package/dist/mcp/result.js +17 -0
  97. package/dist/mcp/schemas.js +8 -0
  98. package/dist/mcp/server.js +30 -0
  99. package/dist/mcp/serverInfo.js +123 -0
  100. package/dist/mcp/toolErrors.js +105 -0
  101. package/dist/mcp/toolNames.js +50 -0
  102. package/dist/mcp/tools/account/index.js +44 -0
  103. package/dist/mcp/tools/action/prepareSuiActionReview.js +120 -0
  104. package/dist/mcp/tools/read/commonSchemas.js +43 -0
  105. package/dist/mcp/tools/read/deepbookReadTools.js +453 -0
  106. package/dist/mcp/tools/read/flowxReadTools.js +135 -0
  107. package/dist/mcp/tools/read/index.js +16 -0
  108. package/dist/mcp/tools/read/readToolHelpers.js +68 -0
  109. package/dist/mcp/tools/read/reviewActivityTools.js +176 -0
  110. package/dist/mcp/tools/read/serverStatusTools.js +103 -0
  111. package/dist/mcp/tools/read/transactionActivityOutput.js +300 -0
  112. package/dist/mcp/tools/read/transactionActivityTools.js +544 -0
  113. package/dist/mcp/tools/read/walletReadTools.js +733 -0
  114. package/dist/mcp/tools/session/executionResultTools.js +92 -0
  115. package/dist/mcp/tools/session/index.js +8 -0
  116. package/dist/mcp/tools/session/shared.js +79 -0
  117. package/dist/mcp/tools/session/statusTools.js +134 -0
  118. package/dist/mcp/tools/session/walletIdentityTools.js +119 -0
  119. package/dist/mcp/tools/settings/index.js +64 -0
  120. package/dist/review-app/analysis.css +1 -0
  121. package/dist/review-app/analysis.js +1 -0
  122. package/dist/review-app/arc-BjIacwQm.js +1 -0
  123. package/dist/review-app/architecture-U656AL7Q-aSB9x1OK.js +1 -0
  124. package/dist/review-app/architectureDiagram-VXUJARFQ-C5W6re2I.js +36 -0
  125. package/dist/review-app/array-BmXUUrU6.js +1 -0
  126. package/dist/review-app/blockDiagram-VD42YOAC-20MLNcUm.js +122 -0
  127. package/dist/review-app/c4Diagram-YG6GDRKO-BZXRrcck.js +10 -0
  128. package/dist/review-app/channel-lk2p_CUu.js +1 -0
  129. package/dist/review-app/chunk-4BX2VUAB-BPITOdjX.js +1 -0
  130. package/dist/review-app/chunk-55IACEB6-Dz-pyw5k.js +1 -0
  131. package/dist/review-app/chunk-76Q3JFCE-cK_X1P_l.js +1 -0
  132. package/dist/review-app/chunk-ABZYJK2D-Dt4W53JI.js +81 -0
  133. package/dist/review-app/chunk-ATLVNIR6-fZHLXURb.js +1 -0
  134. package/dist/review-app/chunk-B4BG7PRW-BbgcjusC.js +165 -0
  135. package/dist/review-app/chunk-BJD4TVEz.js +1 -0
  136. package/dist/review-app/chunk-CVBHYZKI-CViawAKX.js +1 -0
  137. package/dist/review-app/chunk-DI55MBZ5-C5aoul-d.js +220 -0
  138. package/dist/review-app/chunk-FMBD7UC4-Chxmw62A.js +15 -0
  139. package/dist/review-app/chunk-FPAJGGOC-DDHjQ09H.js +80 -0
  140. package/dist/review-app/chunk-FWNWRKHM-CVVQUptk.js +1 -0
  141. package/dist/review-app/chunk-HN2XXSSU-yzNpjaSZ.js +1 -0
  142. package/dist/review-app/chunk-JA3XYJ7Z-C5ZJdU01.js +70 -0
  143. package/dist/review-app/chunk-JZLCHNYA-BBST4Cnk.js +54 -0
  144. package/dist/review-app/chunk-LBM3YZW2-CdwAPuHr.js +1 -0
  145. package/dist/review-app/chunk-LHMN2FUI-BtB5uDcp.js +1 -0
  146. package/dist/review-app/chunk-O7ZBX7Z2-pxdK4Sa3.js +1 -0
  147. package/dist/review-app/chunk-QN33PNHL-CbVv3uGK.js +1 -0
  148. package/dist/review-app/chunk-QXUST7PY-DKM2-t2c.js +7 -0
  149. package/dist/review-app/chunk-QZHKN3VN-C5ni2pN_.js +1 -0
  150. package/dist/review-app/chunk-S3R3BYOJ-BWvOhDs0.js +2 -0
  151. package/dist/review-app/chunk-S6J4BHB3-D9Fk0YeD.js +1 -0
  152. package/dist/review-app/chunk-T53DSG4Q-C1qEyzyV.js +1 -0
  153. package/dist/review-app/chunk-TZMSLE5B-B--7eU69.js +1 -0
  154. package/dist/review-app/classDiagram-2ON5EDUG-DlL1m2bp.js +1 -0
  155. package/dist/review-app/classDiagram-v2-WZHVMYZB-FXRskT1j.js +1 -0
  156. package/dist/review-app/clone-BZZb7gpZ.js +1 -0
  157. package/dist/review-app/cose-bilkent-S5V4N54A-CRIb8XEO.js +1 -0
  158. package/dist/review-app/cytoscape.esm-C7jYqDP5.js +321 -0
  159. package/dist/review-app/dagre-6UL2VRFP-FNCAXbdE.js +4 -0
  160. package/dist/review-app/dagre-Be46QtUd.js +1 -0
  161. package/dist/review-app/defaultLocale-BaWNtAUL.js +1 -0
  162. package/dist/review-app/diagram-PSM6KHXK-ylLWjiNM.js +24 -0
  163. package/dist/review-app/diagram-QEK2KX5R-BCDcESxs.js +43 -0
  164. package/dist/review-app/diagram-S2PKOQOG-Vdrc-vrO.js +24 -0
  165. package/dist/review-app/dist-WPc74x_f.js +1 -0
  166. package/dist/review-app/erDiagram-Q2GNP2WA-E5ZsUbDF.js +60 -0
  167. package/dist/review-app/flatten-DHf9IeNI.js +1 -0
  168. package/dist/review-app/flowDiagram-NV44I4VS-DBSQuj6x.js +162 -0
  169. package/dist/review-app/ganttDiagram-LVOFAZNH-CKUOsqwl.js +267 -0
  170. package/dist/review-app/gitGraph-F6HP7TQM-DsAD6qK1.js +1 -0
  171. package/dist/review-app/gitGraphDiagram-NY62KEGX-BCeIMWdl.js +65 -0
  172. package/dist/review-app/graphlib-CiX5CXxR.js +1 -0
  173. package/dist/review-app/http-DMvwuuFk.js +1 -0
  174. package/dist/review-app/identity-DY8PXc6t.js +1 -0
  175. package/dist/review-app/info-NVLQJR56-Dlx1nZic.js +1 -0
  176. package/dist/review-app/infoDiagram-F6ZHWCRC-CAuANIrz.js +2 -0
  177. package/dist/review-app/init-BvqephKz.js +1 -0
  178. package/dist/review-app/journeyDiagram-XKPGCS4Q-C-Z9phnx.js +139 -0
  179. package/dist/review-app/kanban-definition-3W4ZIXB7-DufgZABq.js +89 -0
  180. package/dist/review-app/katex-B-Z-NXXN.js +257 -0
  181. package/dist/review-app/line-DiIv3Jgw.js +1 -0
  182. package/dist/review-app/linear-Cv-UPvo1.js +1 -0
  183. package/dist/review-app/math-kmyYrkHL.js +1 -0
  184. package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +4 -0
  185. package/dist/review-app/mindmap-definition-VGOIOE7T-TM_CqdmV.js +68 -0
  186. package/dist/review-app/ordinal-BliTlkoG.js +1 -0
  187. package/dist/review-app/packet-BFZMPI3H-DqbnU92v.js +1 -0
  188. package/dist/review-app/path-AEo9W6mQ.js +1 -0
  189. package/dist/review-app/pie-7BOR55EZ-LJzaLkgr.js +1 -0
  190. package/dist/review-app/pieDiagram-ADFJNKIX-BAs8OfRS.js +30 -0
  191. package/dist/review-app/quadrantDiagram-AYHSOK5B-CyUDZP5S.js +7 -0
  192. package/dist/review-app/radar-NHE76QYJ-DBpHc8_Y.js +1 -0
  193. package/dist/review-app/reduce-B-HuPpdd.js +1 -0
  194. package/dist/review-app/requirementDiagram-UZGBJVZJ-BEHix78P.js +64 -0
  195. package/dist/review-app/review.css +1 -0
  196. package/dist/review-app/review.js +43 -0
  197. package/dist/review-app/sankeyDiagram-TZEHDZUN-B2bKbmsm.js +10 -0
  198. package/dist/review-app/sequenceDiagram-WL72ISMW-DVLOORFJ.js +145 -0
  199. package/dist/review-app/settings.css +1 -0
  200. package/dist/review-app/settings.js +1 -0
  201. package/dist/review-app/src-Buml7cM5.js +1 -0
  202. package/dist/review-app/stateDiagram-FKZM4ZOC-sFGGp2kV.js +1 -0
  203. package/dist/review-app/stateDiagram-v2-4FDKWEC3-BHfCF4dX.js +1 -0
  204. package/dist/review-app/timeline-definition-IT6M3QCI-BESnBijC.js +61 -0
  205. package/dist/review-app/treemap-KMMF4GRG-wnVLBDeQ.js +1 -0
  206. package/dist/review-app/walletStatus-CcojOdGy.js +7 -0
  207. package/dist/review-app/xychartDiagram-PRI3JC2R-BGWVfCx4.js +7 -0
  208. package/dist/review-server/assets.js +48 -0
  209. package/dist/review-server/html.js +66 -0
  210. package/dist/review-server/http.js +47 -0
  211. package/dist/review-server/middleware/hostOrigin.js +48 -0
  212. package/dist/review-server/middleware/reviewToken.js +7 -0
  213. package/dist/review-server/reviewServerPolicy.js +10 -0
  214. package/dist/review-server/server.js +568 -0
  215. package/dist/review-server/settingsApi.js +182 -0
  216. package/dist/review-server/walletIdentityResponse.js +13 -0
  217. package/dist/runtime/config.js +103 -0
  218. package/dist/runtime/localSettingsService.js +198 -0
  219. package/dist/runtime/logger.js +50 -0
  220. package/dist/runtime/reviewServerAcquire.js +128 -0
  221. package/dist/runtime/smokeMainnetRead.js +529 -0
  222. package/dist/runtime/smokeMainnetReadAssertions.js +308 -0
  223. package/dist/runtime/start.js +295 -0
  224. package/dist/runtime/suiEndpoint.js +97 -0
  225. package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
  226. package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
  227. package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
  228. package/docs/AGENT_BEHAVIOR.md +1 -1
  229. package/docs/AGENT_DEVELOPMENT_POLICY.md +20 -0
  230. package/docs/FRONTEND_POLICY.md +4 -3
  231. package/docs/MCP_SETUP.md +59 -7
  232. package/docs/MCP_TOOLS.md +1 -1
  233. package/docs/SDK_API.md +5 -1
  234. package/package.json +3 -2
@@ -0,0 +1,821 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { executionResultSchema } from "../action/schemas.js";
3
+ import { parseLifecycleValidatedReviewState } from "../action/reviewStateValidation.js";
4
+ import { hashEventValue, NullEventLogSink } from "../eventlog/sink.js";
5
+ import { LocalTransactionMaterialStoreError, verifyLocalTransactionMaterialArtifacts } from "./transactionMaterialStore.js";
6
+ import { verifyTransactionObjectOwnershipEvidence } from "../action/transactionObjectOwnershipEvidence.js";
7
+ import { verifySwapQuotePolicyEvidence } from "../action/swapQuotePolicyEvidence.js";
8
+ import { publicHumanReadableReviewFromEvidence } from "../action/humanReadableReviewEvidence.js";
9
+ import { publicTransactionSimulationSummaryFromEvidence, verifyReviewTimeSimulationEvidence } from "../action/reviewTimeSimulationEvidence.js";
10
+ import { verifySupportedHumanReadableReviewEvidence } from "../action/humanReadableReviewProjectionVerifier.js";
11
+ import { InMemoryPrivateReviewArtifactStore } from "./privateReviewArtifacts.js";
12
+ import { isFinalSessionStatus } from "./status.js";
13
+ import { parseSuiAddress } from "../suiAddress.js";
14
+ import { cloneLocalSession, createLocalSessionBase, DEFAULT_SESSION_TTL_MS, isLocalSessionExpired, tokenMatchesHash } from "./localSession.js";
15
+ import { SessionStoreError } from "./sessionErrors.js";
16
+ import { SettingsSessionManager } from "./settingsSessions.js";
17
+ import { transitionWalletIdentity, WalletIdentitySessionManager } from "./walletIdentitySessions.js";
18
+ export { transitionWalletIdentity };
19
+ const PRIVATE_DERIVED_REVIEW_FIELD_BINDINGS = [
20
+ {
21
+ field: "humanReadableReview",
22
+ getPublicState: (state) => state.humanReadableReview,
23
+ getPrivateEvidence: (artifacts) => artifacts.humanReadableReview,
24
+ projectPrivateEvidence: (evidence) => publicHumanReadableReviewFromEvidence(evidence)
25
+ },
26
+ {
27
+ field: "simulation",
28
+ getPublicState: (state) => state.simulation,
29
+ getPrivateEvidence: (artifacts) => artifacts.reviewTimeSimulation,
30
+ projectPrivateEvidence: (evidence) => publicTransactionSimulationSummaryFromEvidence(evidence)
31
+ }
32
+ ];
33
+ export { SessionStoreError } from "./sessionErrors.js";
34
+ const REVIEW_STATE_RECOMPUTE_STATUSES = new Set([
35
+ "wallet_connected",
36
+ "ready_for_wallet_review",
37
+ "refresh_required",
38
+ "blocked"
39
+ ]);
40
+ function canRetainPrivateReviewArtifacts(status) {
41
+ return status !== "signed_pending_result" && !isFinalSessionStatus(status);
42
+ }
43
+ const ALLOWED_TRANSITIONS = {
44
+ proposed: ["awaiting_wallet", "expired"],
45
+ awaiting_wallet: ["wallet_connected", "expired"],
46
+ wallet_connected: ["ready_for_wallet_review", "refresh_required", "blocked", "expired"],
47
+ ready_for_wallet_review: ["signed_pending_result", "failure", "refresh_required", "blocked", "expired"],
48
+ refresh_required: ["ready_for_wallet_review", "blocked", "expired"],
49
+ blocked: ["refresh_required", "expired"],
50
+ signed_pending_result: ["success", "failure", "expired"],
51
+ success: [],
52
+ failure: [],
53
+ expired: []
54
+ };
55
+ export class InMemorySessionStore {
56
+ sessions = new Map();
57
+ privateReviewArtifacts = new InMemoryPrivateReviewArtifactStore();
58
+ walletIdentity;
59
+ settings;
60
+ ttlMs;
61
+ eventLog;
62
+ activityStore;
63
+ transactionMaterialStore;
64
+ logger;
65
+ validateAdapterLifecycle;
66
+ constructor(options) {
67
+ this.ttlMs = options.ttlMs ?? DEFAULT_SESSION_TTL_MS;
68
+ this.walletIdentity = new WalletIdentitySessionManager({
69
+ ttlMs: this.ttlMs,
70
+ appendEventLog: (record) => this.appendEventLog(record),
71
+ setActiveAccount: async (account, now, wallet) => {
72
+ await this.activityStore.setActiveAccount(account, "wallet_identity", now, wallet);
73
+ }
74
+ });
75
+ this.settings = new SettingsSessionManager({
76
+ ttlMs: this.ttlMs,
77
+ appendEventLog: (record) => this.appendEventLog(record)
78
+ });
79
+ this.eventLog = options.eventLog ?? new NullEventLogSink();
80
+ this.activityStore = options.activityStore;
81
+ this.transactionMaterialStore = options.transactionMaterialStore;
82
+ this.logger = options.logger;
83
+ this.validateAdapterLifecycle = options.validateAdapterLifecycle;
84
+ }
85
+ async createReviewSession(plans, now = new Date()) {
86
+ if (plans.length !== 1) {
87
+ throw new SessionStoreError("input_invalid", "Exactly one action plan is required per review session");
88
+ }
89
+ const { base, token } = createLocalSessionBase(now, this.ttlMs);
90
+ const session = {
91
+ ...base,
92
+ status: "proposed",
93
+ plans
94
+ };
95
+ await this.activityStore.recordReviewSession({
96
+ reviewSessionId: session.id,
97
+ plan: plans[0],
98
+ currentStatus: session.status,
99
+ createdAt: session.createdAt
100
+ });
101
+ this.sessions.set(session.id, session);
102
+ await this.appendEventLog({
103
+ type: "session.created",
104
+ sessionId: session.id,
105
+ at: now.toISOString()
106
+ });
107
+ return { session: cloneLocalSession(session), token };
108
+ }
109
+ async getReviewSession(id, now = new Date()) {
110
+ const session = this.sessions.get(id);
111
+ if (!session) {
112
+ return undefined;
113
+ }
114
+ if (isLocalSessionExpired(session, now) && !isFinalSessionStatus(session.status)) {
115
+ return cloneLocalSession(await this.expireReviewSession(id, session, now));
116
+ }
117
+ return cloneLocalSession(await this.sanitizePrivateDerivedReviewState(id, session, now));
118
+ }
119
+ async listReviewSessions(now = new Date()) {
120
+ const sessions = [];
121
+ for (const id of this.sessions.keys()) {
122
+ const session = await this.getReviewSession(id, now);
123
+ if (session) {
124
+ sessions.push(session);
125
+ }
126
+ }
127
+ return sessions;
128
+ }
129
+ async validateReviewToken(id, token, _now = new Date()) {
130
+ const session = this.sessions.get(id);
131
+ if (!session) {
132
+ return false;
133
+ }
134
+ // Review sessions expose expired/final lifecycle states through read APIs after token validation.
135
+ // Mutable methods own expiry transitions and return lifecycle-specific errors.
136
+ return tokenMatchesHash(session.tokenHash, token);
137
+ }
138
+ async recordReviewPageOpened(id, now = new Date()) {
139
+ const session = this.sessions.get(id);
140
+ if (!session) {
141
+ throw new SessionStoreError("session_not_found", `Review session not found: ${id}`);
142
+ }
143
+ if (isLocalSessionExpired(session, now) && !isFinalSessionStatus(session.status)) {
144
+ return cloneLocalSession(await this.expireReviewSession(id, session, now));
145
+ }
146
+ if (session.status === "success" || session.status === "failure" || session.status === "expired") {
147
+ return cloneLocalSession(session);
148
+ }
149
+ const nextSession = cloneLocalSession(session);
150
+ if (nextSession.status === "proposed") {
151
+ transition(nextSession, "awaiting_wallet");
152
+ }
153
+ nextSession.lastActivityAt = now.toISOString();
154
+ await this.activityStore.recordReviewTransition({
155
+ reviewSessionId: id,
156
+ event: "opened",
157
+ fromStatus: session.status,
158
+ toStatus: nextSession.status,
159
+ transitionedAt: now.toISOString()
160
+ });
161
+ this.sessions.set(id, nextSession);
162
+ await this.appendEventLog({
163
+ type: "review.opened",
164
+ sessionId: id,
165
+ status: nextSession.status,
166
+ at: now.toISOString()
167
+ });
168
+ return cloneLocalSession(nextSession);
169
+ }
170
+ async recordWalletConnected(id, account, now = new Date()) {
171
+ const session = await this.requireMutableSession(id, now);
172
+ const normalizedAccount = parseSuiAddress(account);
173
+ if (!normalizedAccount) {
174
+ throw new SessionStoreError("input_invalid", "Invalid wallet account address");
175
+ }
176
+ const activeAccount = await this.activityStore.getActiveAccount();
177
+ if (!activeAccount) {
178
+ throw new SessionStoreError("active_account_not_set", "Review account binding requires an active wallet identity account");
179
+ }
180
+ if (activeAccount.address !== normalizedAccount) {
181
+ throw new SessionStoreError("invalid_session_transition", `Review account does not match active wallet identity account: ${id}`);
182
+ }
183
+ const nextSession = cloneLocalSession(session);
184
+ if (session.account && session.account !== normalizedAccount) {
185
+ throw new SessionStoreError("invalid_session_transition", `Review session already bound to a different account: ${id}`);
186
+ }
187
+ if (nextSession.status === "proposed") {
188
+ // Persisted wallet identity (active account already set) lets the review
189
+ // page skip the wallet-identity prompt, so the session is still
190
+ // "proposed" when it binds the account. Walk the canonical
191
+ // proposed -> awaiting_wallet -> wallet_connected path; the active-account
192
+ // match above already proved the binding is legitimate.
193
+ transition(nextSession, "awaiting_wallet");
194
+ transition(nextSession, "wallet_connected");
195
+ }
196
+ else if (nextSession.status === "awaiting_wallet") {
197
+ transition(nextSession, "wallet_connected");
198
+ }
199
+ else if (!REVIEW_STATE_RECOMPUTE_STATUSES.has(nextSession.status)) {
200
+ throw new SessionStoreError("invalid_session_transition", `Invalid session transition: ${nextSession.status} -> wallet_connected`);
201
+ }
202
+ nextSession.account = normalizedAccount;
203
+ nextSession.lastActivityAt = now.toISOString();
204
+ await this.activityStore.recordReviewTransition({
205
+ reviewSessionId: id,
206
+ event: "wallet_connected",
207
+ fromStatus: session.status,
208
+ toStatus: nextSession.status,
209
+ account: normalizedAccount,
210
+ transitionedAt: now.toISOString()
211
+ });
212
+ this.sessions.set(id, nextSession);
213
+ await this.appendEventLog({
214
+ type: "wallet.connected",
215
+ sessionId: id,
216
+ walletAddressHash: hashEventValue(normalizedAccount),
217
+ at: now.toISOString()
218
+ });
219
+ return cloneLocalSession(nextSession);
220
+ }
221
+ async recordReviewState(id, state, now = new Date()) {
222
+ return this.recordReviewStateInternal(id, state, undefined, now);
223
+ }
224
+ async recordReviewStateWithArtifacts(id, state, privateArtifacts, now = new Date()) {
225
+ const pendingSession = this.sessions.get(id);
226
+ if (pendingSession?.pendingHandoffDigest) {
227
+ if (await this.pendingHandoffMaterialAvailable(id, now)) {
228
+ throw new SessionStoreError("invalid_session_transition", "Signing is in progress for this review session; record the wallet result or cancel signing before recomputing");
229
+ }
230
+ // The handed-off material expired without a recorded result; release the
231
+ // lock so the session can recompute instead of being stuck.
232
+ await this.clearPendingHandoff(id, "material_expired", now);
233
+ }
234
+ return this.recordReviewStateInternal(id, state, privateArtifacts, now);
235
+ }
236
+ async getReviewSessionPrivateArtifacts(id, now = new Date()) {
237
+ const session = await this.getReviewSession(id, now);
238
+ if (!session) {
239
+ return undefined;
240
+ }
241
+ if (!canRetainPrivateReviewArtifacts(session.status)) {
242
+ this.deleteReviewSessionTransactionMaterials(id);
243
+ return undefined;
244
+ }
245
+ const artifacts = this.privateReviewArtifacts.get(id);
246
+ if (!artifacts) {
247
+ return undefined;
248
+ }
249
+ if (!session.reviewState) {
250
+ this.deleteReviewSessionTransactionMaterials(id);
251
+ return undefined;
252
+ }
253
+ try {
254
+ return await this.parseReviewSessionPrivateArtifacts(id, session.reviewState, artifacts, now);
255
+ }
256
+ catch (error) {
257
+ this.logger.error("private review artifact verification failed", {
258
+ reviewSessionId: id,
259
+ error: error instanceof Error ? error.message : String(error)
260
+ });
261
+ this.deleteReviewSessionTransactionMaterials(id);
262
+ return undefined;
263
+ }
264
+ }
265
+ async recordReviewStateInternal(id, state, privateArtifacts, now) {
266
+ const session = await this.requireMutableSession(id, now);
267
+ assertSameSessionId(id, state.reviewSessionId, "Review state");
268
+ assertPlanInSession(session, state.planId);
269
+ const parsedState = parseReviewState(state, this.validateAdapterLifecycle);
270
+ if (!session.account) {
271
+ throw new SessionStoreError("invalid_session_transition", `Review state requires a wallet-connected account: ${id}`);
272
+ }
273
+ if (session.account !== parsedState.account) {
274
+ throw new SessionStoreError("invalid_session_transition", `Review state account does not match the review session account: ${id}`);
275
+ }
276
+ await this.assertReviewSessionPrivateArtifacts(id, parsedState, privateArtifacts, now);
277
+ const nextSession = cloneLocalSession(session);
278
+ transition(nextSession, parsedState.status);
279
+ nextSession.account = session.account;
280
+ nextSession.reviewState = parsedState;
281
+ nextSession.lastActivityAt = now.toISOString();
282
+ try {
283
+ await this.activityStore.recordReviewStateSnapshot({
284
+ reviewSessionId: id,
285
+ fromStatus: session.status,
286
+ state: parsedState,
287
+ recordedAt: now.toISOString()
288
+ });
289
+ this.sessions.set(id, nextSession);
290
+ await this.appendEventLog({
291
+ type: "state.computed",
292
+ sessionId: id,
293
+ planId: parsedState.planId,
294
+ walletAddressHash: hashEventValue(parsedState.account),
295
+ status: parsedState.status,
296
+ at: now.toISOString()
297
+ });
298
+ this.replaceReviewSessionPrivateArtifacts(id, privateArtifacts);
299
+ return cloneLocalSession(nextSession);
300
+ }
301
+ catch (error) {
302
+ if (privateArtifacts) {
303
+ this.deleteReviewSessionTransactionMaterials(id);
304
+ }
305
+ throw error;
306
+ }
307
+ }
308
+ async recordExecutionResult(id, result, now = new Date()) {
309
+ const session = await this.requireMutableSession(id, now);
310
+ assertSameSessionId(id, result.reviewSessionId, "Execution result");
311
+ assertPlanInSession(session, result.planId);
312
+ const parsedResult = parseExecutionResult(result, session.executionResult);
313
+ if (session.executionResult?.status === "success" || session.executionResult?.status === "failure") {
314
+ this.deleteReviewSessionTransactionMaterials(id);
315
+ throw new SessionStoreError("execution_result_finalized", `Execution result already finalized: ${id}`);
316
+ }
317
+ if (session.executionResult?.status === "signed_pending_result" &&
318
+ parsedResult.status === "signed_pending_result") {
319
+ if (session.executionResult.planId !== parsedResult.planId ||
320
+ session.executionResult.txDigest !== parsedResult.txDigest) {
321
+ this.deleteReviewSessionTransactionMaterials(id);
322
+ throw new SessionStoreError("signed_pending_result_conflict", `Signed pending result already recorded: ${id}`);
323
+ }
324
+ this.deleteReviewSessionTransactionMaterials(id);
325
+ return cloneLocalSession(session);
326
+ }
327
+ if (session.executionResult?.status === "signed_pending_result" &&
328
+ parsedResult.status !== "signed_pending_result" &&
329
+ session.executionResult.txDigest !== parsedResult.txDigest) {
330
+ this.deleteReviewSessionTransactionMaterials(id);
331
+ throw new SessionStoreError("signed_pending_result_conflict", `Execution result digest does not match signed pending result: ${id}`);
332
+ }
333
+ const nextSession = cloneLocalSession(session);
334
+ transition(nextSession, parsedResult.status);
335
+ const reviewAccount = session.account;
336
+ if (!reviewAccount || session.reviewState?.account !== reviewAccount) {
337
+ throw new SessionStoreError("invalid_session_transition", `Execution result requires account-bound review state: ${id}`);
338
+ }
339
+ nextSession.account = reviewAccount;
340
+ nextSession.executionResult = parsedResult;
341
+ // The outstanding handoff is settled by this recorded result.
342
+ delete nextSession.pendingHandoffDigest;
343
+ nextSession.lastActivityAt = now.toISOString();
344
+ try {
345
+ await this.activityStore.recordReviewExecution({
346
+ reviewSessionId: parsedResult.reviewSessionId,
347
+ planId: parsedResult.planId,
348
+ account: reviewAccount,
349
+ fromStatus: session.status,
350
+ status: parsedResult.status,
351
+ txDigest: parsedResult.txDigest,
352
+ explorerUrl: parsedResult.explorerUrl,
353
+ failureReason: "failureReason" in parsedResult ? parsedResult.failureReason : undefined,
354
+ result: parsedResult,
355
+ recordedAt: parsedResult.recordedAt
356
+ });
357
+ this.sessions.set(id, nextSession);
358
+ this.deleteReviewSessionTransactionMaterials(id);
359
+ const event = {
360
+ type: "result.recorded",
361
+ sessionId: id,
362
+ planId: parsedResult.planId,
363
+ status: parsedResult.status,
364
+ at: now.toISOString()
365
+ };
366
+ await this.appendEventLog(parsedResult.txDigest ? { ...event, txDigest: parsedResult.txDigest } : event);
367
+ return cloneLocalSession(nextSession);
368
+ }
369
+ catch (error) {
370
+ this.deleteReviewSessionTransactionMaterials(id);
371
+ throw error;
372
+ }
373
+ }
374
+ async requireMutableSession(id, now) {
375
+ const session = this.sessions.get(id);
376
+ if (!session) {
377
+ throw new SessionStoreError("session_not_found", `Review session not found: ${id}`);
378
+ }
379
+ if (isLocalSessionExpired(session, now) && !isFinalSessionStatus(session.status)) {
380
+ await this.expireReviewSession(id, session, now);
381
+ throw new SessionStoreError("session_expired", `Review session expired: ${id}`);
382
+ }
383
+ if (session.status === "expired") {
384
+ throw new SessionStoreError("session_expired", `Review session expired: ${id}`);
385
+ }
386
+ return session;
387
+ }
388
+ async createWalletIdentitySession(now = new Date()) {
389
+ return this.walletIdentity.create(now);
390
+ }
391
+ async getWalletIdentitySession(id, now = new Date()) {
392
+ return this.walletIdentity.get(id, now);
393
+ }
394
+ async listWalletIdentitySessions(now = new Date()) {
395
+ return this.walletIdentity.list(now);
396
+ }
397
+ async validateWalletIdentityToken(id, token, _now = new Date()) {
398
+ return this.walletIdentity.validateToken(id, token);
399
+ }
400
+ async recordWalletIdentityOpened(id, now = new Date()) {
401
+ return this.walletIdentity.recordOpened(id, now);
402
+ }
403
+ async recordWalletIdentityConnecting(id, now = new Date()) {
404
+ return this.walletIdentity.recordConnecting(id, now);
405
+ }
406
+ async recordWalletIdentityResult(id, result, now = new Date()) {
407
+ return this.walletIdentity.recordResult(id, result, now);
408
+ }
409
+ async createSettingsSession(now = new Date()) {
410
+ return this.settings.create(now);
411
+ }
412
+ async getSettingsSession(id, now = new Date()) {
413
+ return this.settings.get(id, now);
414
+ }
415
+ async validateSettingsToken(id, token, now = new Date()) {
416
+ return this.settings.validateToken(id, token, now);
417
+ }
418
+ async prepareWalletHandoff(id, planId, account, now = new Date()) {
419
+ const session = await this.requireMutableSession(id, now);
420
+ assertPlanInSession(session, planId);
421
+ try {
422
+ return await this.gateWalletHandoff(id, session, planId, account, now);
423
+ }
424
+ catch (error) {
425
+ if (error instanceof SessionStoreError) {
426
+ await this.appendEventLog({
427
+ type: "handoff.refused",
428
+ sessionId: id,
429
+ planId,
430
+ reason: error.code,
431
+ at: now.toISOString()
432
+ });
433
+ }
434
+ throw error;
435
+ }
436
+ }
437
+ async pendingHandoffMaterialAvailable(id, now) {
438
+ if (!this.transactionMaterialStore) {
439
+ return false;
440
+ }
441
+ const artifacts = await this.getReviewSessionPrivateArtifacts(id, now);
442
+ const handle = artifacts?.transactionMaterial;
443
+ if (!handle) {
444
+ return false;
445
+ }
446
+ return this.transactionMaterialStore.getTransactionMaterial(handle, now) !== undefined;
447
+ }
448
+ async clearPendingHandoff(id, reason, now) {
449
+ const session = this.sessions.get(id);
450
+ if (!session || session.pendingHandoffDigest === undefined) {
451
+ return;
452
+ }
453
+ delete session.pendingHandoffDigest;
454
+ this.sessions.set(id, session);
455
+ await this.appendEventLog({
456
+ type: "handoff.cancelled",
457
+ sessionId: id,
458
+ reason,
459
+ at: now.toISOString()
460
+ });
461
+ }
462
+ async cancelWalletHandoff(id, now = new Date()) {
463
+ const session = this.sessions.get(id);
464
+ if (!session) {
465
+ throw new SessionStoreError("session_not_found", `Review session not found: ${id}`);
466
+ }
467
+ await this.clearPendingHandoff(id, "user_cancelled", now);
468
+ return cloneLocalSession(this.sessions.get(id));
469
+ }
470
+ async gateWalletHandoff(id, session, planId, account, now) {
471
+ const state = session.reviewState;
472
+ if (!state || state.status !== "ready_for_wallet_review" || state.planId !== planId) {
473
+ throw new SessionStoreError("invalid_session_transition", "Wallet handoff requires a ready_for_wallet_review state for this plan");
474
+ }
475
+ const normalizedAccount = parseSuiAddress(account);
476
+ if (!normalizedAccount || state.account !== normalizedAccount) {
477
+ throw new SessionStoreError("input_invalid", "Wallet handoff account does not match the reviewed account");
478
+ }
479
+ const contract = state.walletReviewAdapterContract;
480
+ if (!contract) {
481
+ throw new SessionStoreError("handoff_unavailable", "Wallet handoff requires an emitted wallet review contract");
482
+ }
483
+ const artifacts = await this.getReviewSessionPrivateArtifacts(id, now);
484
+ const handle = artifacts?.transactionMaterial;
485
+ const digest = artifacts?.transactionMaterialDigest;
486
+ if (!handle || !digest || !this.transactionMaterialStore) {
487
+ throw new SessionStoreError("handoff_unavailable", "Wallet handoff transaction material is unavailable");
488
+ }
489
+ // Bind at handoff: recompute the digest of the exact stored bytes and require
490
+ // it to equal the commitment the user reviewed before any bytes leave the store.
491
+ try {
492
+ await verifyLocalTransactionMaterialArtifacts({
493
+ materialStore: this.transactionMaterialStore,
494
+ transactionMaterial: handle,
495
+ transactionMaterialDigest: digest,
496
+ now
497
+ });
498
+ }
499
+ catch (error) {
500
+ if (error instanceof LocalTransactionMaterialStoreError) {
501
+ throw new SessionStoreError("handoff_unavailable", `Wallet handoff refused: ${error.message}`);
502
+ }
503
+ throw error;
504
+ }
505
+ if (digest.transactionDigest !== contract.transactionMaterialCommitment) {
506
+ throw new SessionStoreError("handoff_commitment_mismatch", "Wallet handoff refused: stored transaction digest does not match the reviewed contract commitment");
507
+ }
508
+ const material = this.transactionMaterialStore.getTransactionMaterial(handle, now);
509
+ if (!material) {
510
+ throw new SessionStoreError("handoff_unavailable", "Wallet handoff transaction material is unavailable");
511
+ }
512
+ // One-transaction lock: while a handoff is outstanding, state recomputes
513
+ // are refused so a second, different transaction cannot be signed from
514
+ // the same session. Cleared on result recording, cancel, or material expiry.
515
+ session.pendingHandoffDigest = contract.transactionMaterialCommitment;
516
+ this.sessions.set(id, session);
517
+ await this.appendEventLog({
518
+ type: "handoff.prepared",
519
+ sessionId: id,
520
+ planId,
521
+ at: now.toISOString()
522
+ });
523
+ return {
524
+ transactionBytesBase64: Buffer.from(material.transactionBytes).toString("base64"),
525
+ transactionMaterialCommitment: contract.transactionMaterialCommitment,
526
+ planId,
527
+ account: normalizedAccount
528
+ };
529
+ }
530
+ async invalidateAllLocalSessions(reason, now = new Date()) {
531
+ for (const id of this.sessions.keys()) {
532
+ this.deleteReviewSessionTransactionMaterials(id);
533
+ }
534
+ this.sessions.clear();
535
+ this.walletIdentity.clear();
536
+ this.settings.clear();
537
+ await this.appendEventLog({
538
+ type: "local_sessions.invalidated",
539
+ sessionId: "all",
540
+ reason,
541
+ at: now.toISOString()
542
+ });
543
+ }
544
+ async appendEventLog(record) {
545
+ try {
546
+ await this.eventLog.append(record);
547
+ }
548
+ catch (error) {
549
+ // Event logs are optional audit/debug sinks. SQLite and in-memory session state remain authoritative.
550
+ this.logger.error("event log append failed", {
551
+ eventType: record.type,
552
+ error: error instanceof Error ? error.message : String(error)
553
+ });
554
+ }
555
+ }
556
+ deleteReviewSessionTransactionMaterials(reviewSessionId) {
557
+ try {
558
+ this.transactionMaterialStore?.deleteReviewSessionTransactionMaterials(reviewSessionId);
559
+ }
560
+ catch (error) {
561
+ this.logger.error("transaction material cleanup failed", {
562
+ reviewSessionId,
563
+ error: error instanceof Error ? error.message : String(error)
564
+ });
565
+ }
566
+ this.privateReviewArtifacts.delete(reviewSessionId);
567
+ }
568
+ async expireReviewSession(id, session, now) {
569
+ const nextSession = cloneLocalSession(session);
570
+ transition(nextSession, "expired");
571
+ try {
572
+ await this.activityStore.recordReviewTransition({
573
+ reviewSessionId: id,
574
+ event: "expired",
575
+ fromStatus: session.status,
576
+ toStatus: nextSession.status,
577
+ transitionedAt: now.toISOString()
578
+ });
579
+ }
580
+ finally {
581
+ this.deleteReviewSessionTransactionMaterials(id);
582
+ }
583
+ this.sessions.set(id, nextSession);
584
+ return nextSession;
585
+ }
586
+ async sanitizePrivateDerivedReviewState(id, session, now) {
587
+ // While a wallet handoff is outstanding, the page is signing bytes that
588
+ // were already handed over; material expiry must not demote the session
589
+ // out from under that signature (slow hardware wallets legitimately take
590
+ // longer than the material TTL). The lock still releases on result
591
+ // recording, explicit cancel, or the recompute-time expiry self-heal.
592
+ if (session.pendingHandoffDigest !== undefined) {
593
+ return session;
594
+ }
595
+ if (!canRetainPrivateReviewArtifacts(session.status) ||
596
+ (!session.reviewState?.humanReadableReview && !session.reviewState?.simulation)) {
597
+ return session;
598
+ }
599
+ const artifacts = this.privateReviewArtifacts.get(id);
600
+ if (!artifacts) {
601
+ return await this.markPrivateDerivedReviewStateRefreshRequired(id, session, now);
602
+ }
603
+ try {
604
+ await this.parseReviewSessionPrivateArtifacts(id, session.reviewState, artifacts, now);
605
+ return session;
606
+ }
607
+ catch (error) {
608
+ this.logger.error("private-derived review state refresh required", {
609
+ reviewSessionId: id,
610
+ error: error instanceof Error ? error.message : String(error)
611
+ });
612
+ return await this.markPrivateDerivedReviewStateRefreshRequired(id, session, now);
613
+ }
614
+ }
615
+ async markPrivateDerivedReviewStateRefreshRequired(id, session, now) {
616
+ this.deleteReviewSessionTransactionMaterials(id);
617
+ const nextSession = cloneLocalSession(session);
618
+ transition(nextSession, "refresh_required");
619
+ if (nextSession.reviewState) {
620
+ nextSession.reviewState = {
621
+ planId: nextSession.reviewState.planId,
622
+ reviewSessionId: id,
623
+ account: nextSession.reviewState.account,
624
+ status: "refresh_required",
625
+ refreshReason: "quote_stale",
626
+ checks: [{
627
+ id: "private_review_artifacts_refresh_required",
628
+ label: "Review evidence refresh",
629
+ status: "fail",
630
+ message: "Private review evidence expired or no longer matches stored material; recompute the account-bound review before using human-readable review facts.",
631
+ source: "adapter"
632
+ }],
633
+ updatedAt: now.toISOString()
634
+ };
635
+ }
636
+ nextSession.lastActivityAt = now.toISOString();
637
+ this.sessions.set(id, nextSession);
638
+ await this.activityStore.recordReviewTransition({
639
+ reviewSessionId: id,
640
+ event: "state_computed",
641
+ fromStatus: session.status,
642
+ toStatus: nextSession.status,
643
+ reason: "private_review_artifacts_refresh_required",
644
+ transitionedAt: now.toISOString()
645
+ });
646
+ if (nextSession.reviewState) {
647
+ await this.activityStore.recordReviewStateSnapshot({
648
+ reviewSessionId: id,
649
+ fromStatus: session.status,
650
+ state: nextSession.reviewState,
651
+ recordedAt: now.toISOString()
652
+ });
653
+ }
654
+ await this.appendEventLog({
655
+ type: "state.computed",
656
+ sessionId: id,
657
+ status: nextSession.status,
658
+ reason: "private_review_artifacts_refresh_required",
659
+ at: now.toISOString(),
660
+ ...(nextSession.reviewState?.planId ? { planId: nextSession.reviewState.planId } : {}),
661
+ ...(nextSession.reviewState?.account
662
+ ? { walletAddressHash: hashEventValue(nextSession.reviewState.account) }
663
+ : {})
664
+ });
665
+ return nextSession;
666
+ }
667
+ replaceReviewSessionPrivateArtifacts(reviewSessionId, privateArtifacts) {
668
+ if (!privateArtifacts?.transactionMaterial || !privateArtifacts.transactionMaterialDigest) {
669
+ this.deleteReviewSessionTransactionMaterials(reviewSessionId);
670
+ return;
671
+ }
672
+ this.privateReviewArtifacts.set(reviewSessionId, privateArtifacts);
673
+ }
674
+ async assertReviewSessionPrivateArtifacts(reviewSessionId, state, privateArtifacts, now) {
675
+ if (!privateArtifacts) {
676
+ if (state.humanReadableReview || state.simulation) {
677
+ this.deleteReviewSessionTransactionMaterials(reviewSessionId);
678
+ throw new SessionStoreError("session_mismatch", `Review private-derived state requires matching private evidence: ${reviewSessionId}`);
679
+ }
680
+ return;
681
+ }
682
+ try {
683
+ await this.parseReviewSessionPrivateArtifacts(reviewSessionId, state, privateArtifacts, now);
684
+ }
685
+ catch (error) {
686
+ this.logger.error("private review artifact rejected", {
687
+ reviewSessionId,
688
+ error: error instanceof Error ? error.message : String(error)
689
+ });
690
+ this.deleteReviewSessionTransactionMaterials(reviewSessionId);
691
+ throw new SessionStoreError("session_mismatch", `Review private artifacts do not match the stored review state: ${reviewSessionId}`);
692
+ }
693
+ }
694
+ async parseReviewSessionPrivateArtifacts(reviewSessionId, state, privateArtifacts, now) {
695
+ const { transactionMaterial, transactionMaterialDigest } = privateArtifacts;
696
+ if (!transactionMaterial ||
697
+ !transactionMaterialDigest ||
698
+ !this.transactionMaterialStore) {
699
+ throw new Error("missing private artifact material, digest, or material store");
700
+ }
701
+ const parsed = await verifyLocalTransactionMaterialArtifacts({
702
+ materialStore: this.transactionMaterialStore,
703
+ transactionMaterial,
704
+ transactionMaterialDigest,
705
+ now
706
+ });
707
+ if (parsed.transactionMaterial.reviewSessionId !== reviewSessionId ||
708
+ parsed.transactionMaterial.planId !== state.planId ||
709
+ parsed.transactionMaterial.account !== state.account) {
710
+ throw new Error("private artifacts do not match review state identity");
711
+ }
712
+ const transactionObjectOwnership = privateArtifacts.transactionObjectOwnership
713
+ ? verifyTransactionObjectOwnershipEvidence({
714
+ transactionMaterial: parsed.transactionMaterial,
715
+ transactionMaterialDigest: parsed.transactionMaterialDigest,
716
+ evidence: privateArtifacts.transactionObjectOwnership,
717
+ now
718
+ })
719
+ : undefined;
720
+ const swapQuotePolicy = privateArtifacts.swapQuotePolicy
721
+ ? verifySwapQuotePolicyEvidence({
722
+ transactionMaterial: parsed.transactionMaterial,
723
+ evidence: privateArtifacts.swapQuotePolicy,
724
+ now
725
+ })
726
+ : undefined;
727
+ const humanReadableReview = privateArtifacts.humanReadableReview
728
+ ? verifySupportedHumanReadableReviewEvidence({
729
+ transactionMaterial: parsed.transactionMaterial,
730
+ transactionMaterialDigest: parsed.transactionMaterialDigest,
731
+ swapQuotePolicy,
732
+ transactionObjectOwnership,
733
+ evidence: privateArtifacts.humanReadableReview,
734
+ now
735
+ })
736
+ : undefined;
737
+ const reviewTimeSimulation = privateArtifacts.reviewTimeSimulation
738
+ ? verifyReviewTimeSimulationEvidence({
739
+ transactionMaterial: parsed.transactionMaterial,
740
+ transactionMaterialDigest: parsed.transactionMaterialDigest,
741
+ evidence: privateArtifacts.reviewTimeSimulation,
742
+ now
743
+ })
744
+ : undefined;
745
+ const verifiedArtifacts = {
746
+ ...parsed,
747
+ ...(swapQuotePolicy ? { swapQuotePolicy } : {}),
748
+ ...(transactionObjectOwnership ? { transactionObjectOwnership } : {}),
749
+ ...(humanReadableReview ? { humanReadableReview } : {}),
750
+ ...(reviewTimeSimulation ? { reviewTimeSimulation } : {})
751
+ };
752
+ assertPrivateDerivedReviewStateProjections(state, verifiedArtifacts);
753
+ return verifiedArtifacts;
754
+ }
755
+ }
756
+ function assertPrivateDerivedReviewStateProjections(state, privateArtifacts) {
757
+ for (const binding of PRIVATE_DERIVED_REVIEW_FIELD_BINDINGS) {
758
+ const publicValue = binding.getPublicState(state);
759
+ const privateEvidence = binding.getPrivateEvidence(privateArtifacts);
760
+ if (publicValue === undefined && privateEvidence === undefined) {
761
+ continue;
762
+ }
763
+ if (publicValue === undefined || privateEvidence === undefined) {
764
+ throw new Error(`review state ${binding.field} must match private ${binding.field} evidence`);
765
+ }
766
+ const projected = binding.projectPrivateEvidence(privateEvidence);
767
+ if (!isDeepStrictEqual(publicValue, projected)) {
768
+ throw new Error(`review state ${binding.field} must be projected from private ${binding.field} evidence`);
769
+ }
770
+ }
771
+ }
772
+ export function transition(session, next) {
773
+ if (session.status === next) {
774
+ return;
775
+ }
776
+ const allowed = ALLOWED_TRANSITIONS[session.status] ?? [];
777
+ if (!allowed.includes(next)) {
778
+ throw new SessionStoreError("invalid_session_transition", `Invalid session transition: ${session.status} -> ${next}`);
779
+ }
780
+ session.status = next;
781
+ }
782
+ function assertPlanInSession(session, planId) {
783
+ if (!session.plans.some((plan) => plan.id === planId)) {
784
+ throw new SessionStoreError("plan_not_in_session", `Action plan not found in review session: ${planId}`);
785
+ }
786
+ }
787
+ function assertSameSessionId(expected, actual, label) {
788
+ if (actual !== expected) {
789
+ throw new SessionStoreError("session_mismatch", `${label} session mismatch: expected ${expected}, got ${actual}`);
790
+ }
791
+ }
792
+ function parseReviewState(state, validateAdapterLifecycle) {
793
+ let parsed;
794
+ try {
795
+ parsed = parseLifecycleValidatedReviewState(state, validateAdapterLifecycle);
796
+ }
797
+ catch {
798
+ throw new SessionStoreError("input_invalid", "Invalid review state shape or adapter lifecycle");
799
+ }
800
+ const normalizedAccount = parseSuiAddress(parsed.account);
801
+ if (!normalizedAccount) {
802
+ throw new SessionStoreError("input_invalid", "Invalid review state account");
803
+ }
804
+ return { ...parsed, account: normalizedAccount };
805
+ }
806
+ function parseExecutionResult(result, previous) {
807
+ const candidate = result.status === "failure" &&
808
+ result.txDigest === undefined &&
809
+ previous?.status === "signed_pending_result"
810
+ ? { ...result, txDigest: previous.txDigest }
811
+ : result;
812
+ const parsed = executionResultSchema.safeParse(result);
813
+ const reparsed = executionResultSchema.safeParse(candidate);
814
+ if (!parsed.success && !reparsed.success) {
815
+ throw new SessionStoreError("input_invalid", "Invalid execution result shape");
816
+ }
817
+ return (reparsed.success ? reparsed.data : parsed.data);
818
+ }
819
+ function cloneSession(session) {
820
+ return cloneLocalSession(session);
821
+ }