@supercuts-walrus/backend 0.0.0-stage → 0.1.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.
Files changed (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +249 -2
  3. package/dist/audit.d.ts +52 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/backend.d.ts +27 -0
  6. package/dist/backend.js +62 -0
  7. package/dist/buyer-setup.d.ts +6 -0
  8. package/dist/buyer-setup.js +241 -0
  9. package/dist/capability.d.ts +32 -0
  10. package/dist/capability.js +87 -0
  11. package/dist/claude.d.ts +44 -0
  12. package/dist/claude.js +98 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +29 -0
  15. package/dist/config.d.ts +20 -0
  16. package/dist/config.js +58 -0
  17. package/dist/dashboard.d.ts +5 -0
  18. package/dist/dashboard.js +21 -0
  19. package/dist/deal-adapter.d.ts +3 -0
  20. package/dist/deal-adapter.js +39 -0
  21. package/dist/deal-engine.d.ts +62 -0
  22. package/dist/deal-engine.js +211 -0
  23. package/dist/deal-wire.d.ts +16 -0
  24. package/dist/deal-wire.js +65 -0
  25. package/dist/deal.d.ts +79 -0
  26. package/dist/deal.js +228 -0
  27. package/dist/demo.d.ts +2 -0
  28. package/dist/demo.js +449 -0
  29. package/dist/errors.d.ts +6 -0
  30. package/dist/errors.js +13 -0
  31. package/dist/events.d.ts +27 -0
  32. package/dist/events.js +68 -0
  33. package/dist/facilitator.d.ts +21 -0
  34. package/dist/facilitator.js +173 -0
  35. package/dist/finality.d.ts +2 -0
  36. package/dist/finality.js +27 -0
  37. package/dist/hosted-exit-cli.d.ts +2 -0
  38. package/dist/hosted-exit-cli.js +37 -0
  39. package/dist/hosted-exit.d.ts +24 -0
  40. package/dist/hosted-exit.js +70 -0
  41. package/dist/hosted-seller-import.d.ts +2 -0
  42. package/dist/hosted-seller-import.js +70 -0
  43. package/dist/hosted-seller-repository.d.ts +31 -0
  44. package/dist/hosted-seller-repository.js +214 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.js +37 -0
  47. package/dist/indexer.d.ts +63 -0
  48. package/dist/indexer.js +60 -0
  49. package/dist/live-deal-ports.d.ts +41 -0
  50. package/dist/live-deal-ports.js +212 -0
  51. package/dist/live-index.d.ts +28 -0
  52. package/dist/live-index.js +149 -0
  53. package/dist/live-runtime.d.ts +82 -0
  54. package/dist/live-runtime.js +570 -0
  55. package/dist/live-seller-transport.d.ts +30 -0
  56. package/dist/live-seller-transport.js +169 -0
  57. package/dist/live-seller-verifier.d.ts +11 -0
  58. package/dist/live-seller-verifier.js +65 -0
  59. package/dist/mandate.d.ts +34 -0
  60. package/dist/mandate.js +178 -0
  61. package/dist/mcp-bootstrap.d.ts +2 -0
  62. package/dist/mcp-bootstrap.js +39 -0
  63. package/dist/mcp.d.ts +2 -0
  64. package/dist/mcp.js +187 -0
  65. package/dist/memory-worker.d.ts +2 -0
  66. package/dist/memory-worker.js +38 -0
  67. package/dist/memory.d.ts +15 -0
  68. package/dist/memory.js +17 -0
  69. package/dist/memwal.d.ts +35 -0
  70. package/dist/memwal.js +83 -0
  71. package/dist/negotiation.d.ts +23 -0
  72. package/dist/negotiation.js +58 -0
  73. package/dist/notes.d.ts +11 -0
  74. package/dist/notes.js +26 -0
  75. package/dist/onboarding-doctor.d.ts +1 -0
  76. package/dist/onboarding-doctor.js +76 -0
  77. package/dist/phase3.d.ts +100 -0
  78. package/dist/phase3.js +1144 -0
  79. package/dist/procurement.d.ts +8 -0
  80. package/dist/procurement.js +19 -0
  81. package/dist/protocol-engine.d.ts +121 -0
  82. package/dist/protocol-engine.js +149 -0
  83. package/dist/prover.d.ts +28 -0
  84. package/dist/prover.js +70 -0
  85. package/dist/relayer.d.ts +32 -0
  86. package/dist/relayer.js +43 -0
  87. package/dist/remote-buyer.d.ts +67 -0
  88. package/dist/remote-buyer.js +231 -0
  89. package/dist/resource-server.d.ts +2 -0
  90. package/dist/resource-server.js +24 -0
  91. package/dist/resource.d.ts +22 -0
  92. package/dist/resource.js +55 -0
  93. package/dist/seam.d.ts +2 -0
  94. package/dist/seam.js +117 -0
  95. package/dist/seller-catalog-generate.d.ts +2 -0
  96. package/dist/seller-catalog-generate.js +68 -0
  97. package/dist/seller-database.d.ts +41 -0
  98. package/dist/seller-database.js +80 -0
  99. package/dist/seller-directory-cli.d.ts +2 -0
  100. package/dist/seller-directory-cli.js +24 -0
  101. package/dist/seller-directory.d.ts +5 -0
  102. package/dist/seller-directory.js +50 -0
  103. package/dist/seller-live-cli.d.ts +2 -0
  104. package/dist/seller-live-cli.js +53 -0
  105. package/dist/seller-provision.d.ts +2 -0
  106. package/dist/seller-provision.js +84 -0
  107. package/dist/seller-register-cli.d.ts +2 -0
  108. package/dist/seller-register-cli.js +40 -0
  109. package/dist/seller-runtime.d.ts +60 -0
  110. package/dist/seller-runtime.js +96 -0
  111. package/dist/seller-service.d.ts +34 -0
  112. package/dist/seller-service.js +416 -0
  113. package/dist/seller-state.d.ts +19 -0
  114. package/dist/seller-state.js +60 -0
  115. package/dist/seller-stdio.d.ts +6 -0
  116. package/dist/seller-stdio.js +29 -0
  117. package/dist/seller-v2-provision.d.ts +2 -0
  118. package/dist/seller-v2-provision.js +71 -0
  119. package/dist/seller-watch.d.ts +2 -0
  120. package/dist/seller-watch.js +45 -0
  121. package/dist/settlement.d.ts +95 -0
  122. package/dist/settlement.js +141 -0
  123. package/dist/store.d.ts +36 -0
  124. package/dist/store.js +193 -0
  125. package/dist/sui.d.ts +49 -0
  126. package/dist/sui.js +77 -0
  127. package/dist/tatum.d.ts +17 -0
  128. package/dist/tatum.js +83 -0
  129. package/dist/transcript.d.ts +31 -0
  130. package/dist/transcript.js +52 -0
  131. package/dist/v2-exit-relayer.d.ts +34 -0
  132. package/dist/v2-exit-relayer.js +102 -0
  133. package/dist/v2-exit-worker.d.ts +2 -0
  134. package/dist/v2-exit-worker.js +97 -0
  135. package/dist/v2-exits.d.ts +30 -0
  136. package/dist/v2-exits.js +78 -0
  137. package/dist/v2-prefund.d.ts +19 -0
  138. package/dist/v2-prefund.js +43 -0
  139. package/dist/v2-relayer-cli.d.ts +2 -0
  140. package/dist/v2-relayer-cli.js +39 -0
  141. package/dist/wallet.d.ts +32 -0
  142. package/dist/wallet.js +90 -0
  143. package/dist/workflow-memory.d.ts +22 -0
  144. package/dist/workflow-memory.js +49 -0
  145. package/dist/x402-resource-service.d.ts +34 -0
  146. package/dist/x402-resource-service.js +154 -0
  147. package/dist/x402.d.ts +46 -0
  148. package/dist/x402.js +136 -0
  149. package/package.json +60 -3
package/dist/mcp.js ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline";
3
+ import { Phase3App, errorOutput, homePath } from "./phase3.js";
4
+ import { executeMandated, loadMandate, toolSchema } from "./mandate.js";
5
+ import { RemoteBuyer } from "./remote-buyer.js";
6
+ import { DemoEventLog } from "./events.js";
7
+ import { DealJournal } from "./deal.js";
8
+ import { createConfiguredDealEngine } from "./deal-adapter.js";
9
+ import { LiveDealPorts } from "./live-deal-ports.js";
10
+ import { createRequire } from "node:module";
11
+ const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
12
+ const MAX_REQUEST_BYTES = 1024 * 1024;
13
+ const home = homePath({});
14
+ const app = new Phase3App(home);
15
+ const mandate = loadMandate(home);
16
+ const remote = app.profile.role === "buyer" && !app.profile.mock && process.env.SUPERCUTS_SELLER_URL ? new RemoteBuyer(app, mandate, process.env.SUPERCUTS_SELLER_URL) : undefined;
17
+ const eventLog = process.env.SUPERCUTS_EVENT_PATH && process.env.SUPERCUTS_RUN_ID ? new DemoEventLog(process.env.SUPERCUTS_EVENT_PATH, process.env.SUPERCUTS_RUN_ID) : undefined;
18
+ if (eventLog)
19
+ eventLog.append({ actor: "operator", stage: "run", status: "started", publicDetails: { flow: remote ? "codex-remote" : "codex-deal-v2" }, evidenceHashes: [], explorerLinks: [] });
20
+ let memoryTickRunning = false;
21
+ const memoryTick = async () => {
22
+ if (memoryTickRunning)
23
+ return;
24
+ memoryTickRunning = true;
25
+ try {
26
+ await app.processMemoryOutbox();
27
+ }
28
+ catch (error) {
29
+ process.stderr.write(`${error && typeof error === "object" && "code" in error ? String(error.code) : "MEMORY_WORKER_ERROR"}\n`);
30
+ }
31
+ finally {
32
+ memoryTickRunning = false;
33
+ }
34
+ };
35
+ const memoryConfigured = app.profile.role === "buyer" && !app.profile.mock && Boolean(app.store.getSecret("live:memory-config"));
36
+ if (memoryConfigured)
37
+ app.recoverStaleMemorySubmissions();
38
+ const memoryTimer = memoryConfigured ? setInterval(() => { void memoryTick(); }, 30_000) : undefined;
39
+ if (memoryTimer) {
40
+ memoryTimer.unref();
41
+ void memoryTick();
42
+ }
43
+ const tools = {
44
+ buyer: "status,doctor,deal.catalog,deal.policy,deal.execute,deal.get,deal.resume,deal.reconcile,deal.evidence,deal.delivery,capability.discover,settlement.plan,wallet.deposit,wallet.balance,wallet.recover,wallet.withdraw,wallet.exit.queue,wallet.exit.status,wallet.exit.finalize,offer.open,offer.propose,offer.accept,offer.show,pay.quote,pay.shielded,pay.x402,pay.status,resource.fetch,memory.remember,memory.recall,memory.status,memory.inventory,remote.catalog,remote.rank-briefs,remote.negotiate,remote.quote,remote.purchase-brief,remote.agreement,remote.settle-analysis,remote.delivery,remote.events,disclose",
45
+ seller: "status,doctor,capability.publish,wallet.balance,wallet.recover,wallet.withdraw,wallet.exit.queue,wallet.exit.status,wallet.exit.finalize,offer.counter,offer.show,pay.status,resource.serve,resource.fetch,memory.remember,memory.recall,memory.status",
46
+ auditor: "status,doctor,audit.verify",
47
+ };
48
+ const send = (value) => process.stdout.write(`${JSON.stringify(value)}\n`);
49
+ function logTool(name, data) {
50
+ if (!eventLog)
51
+ return;
52
+ const append = (actor, stage, details, hashes = []) => eventLog.append({ actor, stage, status: "complete", publicDetails: details, evidenceHashes: hashes, explorerLinks: [] });
53
+ if (name === "remote.rank-briefs")
54
+ append("buyer", "seller-selection", { winner: data.winner?.capabilityId, rankings: data.rankings?.map((item) => ({ capabilityId: item.capabilityId, score: item.score, priceMist: item.priceMist })) });
55
+ if (name === "remote.negotiate")
56
+ append("buyer", "negotiation", { capabilityId: data.capabilityId, sessionId: data.sessionId, requestBlobId: data.requestBlobId, replyBlobId: data.replyBlobId, transcriptHash: data.transcriptHash }, typeof data.transcriptHash === "string" ? [data.transcriptHash] : []);
57
+ if (name === "remote.purchase-brief") {
58
+ const negotiation = data.negotiation;
59
+ if (negotiation)
60
+ append("buyer", "negotiation", { capabilityId: data.capabilityId, sessionId: negotiation.sessionId, requestBlobId: negotiation.requestBlobId, replyBlobId: negotiation.replyBlobId, transcriptHash: negotiation.transcriptHash }, typeof negotiation.transcriptHash === "string" ? [negotiation.transcriptHash] : []);
61
+ append("buyer", "public-settlement", { rail: "x402", capabilityId: data.capabilityId, transactionDigest: data.transactionDigest, amount: data.amount, payee: data.payee, planHash: data.planHash });
62
+ append("buyer", "artifact-delivery", { capabilityId: data.capabilityId, artifactSha256: data.artifactSha256, kind: "public-brief" }, typeof data.artifactSha256 === "string" ? [data.artifactSha256] : []);
63
+ }
64
+ if (name === "memory.remember")
65
+ append("buyer", "memory", { blobId: data.blobId, kind: data.kind, provenance: data.provenance });
66
+ if (name === "memory.recall")
67
+ append("supercuts", "memory-recall", { citations: data.citations, restarted: false });
68
+ if (name === "wallet.deposit" && typeof data.transactionDigest === "string")
69
+ append("buyer", "prefunding", { transactionDigest: data.transactionDigest, amount: data.amount });
70
+ if (name === "wallet.exit.queue")
71
+ append(app.profile.role === "seller" ? "confidential-seller" : "buyer", "delayed-exit", { state: data.status, amount: data.amount, operationId: data.operationId });
72
+ if (name === "remote.agreement") {
73
+ const plan = data.plan;
74
+ const negotiation = data.negotiation;
75
+ if (negotiation)
76
+ append("buyer", "negotiation", { capabilityId: plan.capabilityId, sessionId: negotiation.sessionId, requestBlobId: negotiation.requestBlobId, replyBlobId: negotiation.replyBlobId, transcriptHash: negotiation.transcriptHash }, typeof negotiation.transcriptHash === "string" ? [negotiation.transcriptHash] : []);
77
+ append("buyer", "settlement-plan", { phase: "after-memory", planHash: plan.planHash, capabilityId: plan.capabilityId, rail: plan.rail, openingAmount: plan.openingAmount, maximumAmount: plan.maximumAmount, riskLevel: plan.riskLevel, reasonCodes: plan.reasonCodes, blobIds: plan.citations?.map((item) => item.blobId) ?? [] }, typeof plan.manifestHash === "string" ? [plan.manifestHash] : []);
78
+ }
79
+ if (name === "remote.settle-analysis")
80
+ append("buyer", "private-settlement", { rail: "shielded", transactionDigest: data.transactionDigest, checkpoint: data.checkpoint, planHash: data.planHash, visibility: data.visibility }, typeof data.dealCommitment === "string" ? [data.dealCommitment] : []);
81
+ if (name === "remote.delivery" && data.status === "delivered")
82
+ append("buyer", "artifact-delivery", { kind: "competitor-analysis", artifactSha256: data.artifactSha256, sessionId: data.sessionId }, typeof data.artifactSha256 === "string" ? [data.artifactSha256] : []);
83
+ if ((name === "deal.execute" || name === "deal.resume" || name === "deal.get") && typeof data.dealId === "string") {
84
+ const journal = new DealJournal(app.store), evidence = journal.evidence(data.dealId);
85
+ const plan = journal.step(String(data.operationId), "planning");
86
+ if (plan)
87
+ append("supercuts", "settlement-plan", { phase: "after-memory", planHash: plan.planHash, capabilityId: plan.capabilityId, rail: plan.rail, openingAmount: plan.openingAmount, maximumAmount: plan.maximumAmount, riskLevel: plan.riskLevel, reasonCodes: plan.reasonCodes, blobIds: plan.citations.map((item) => item.blobId) });
88
+ append("supercuts", "deal-lifecycle", { dealId: data.dealId, state: data.state, rail: data.rail, agreement: data.evidence?.agreement, payment: data.evidence?.payment, delivery: data.evidence?.delivery, outcomeMemory: data.evidence?.outcomeMemory, agreementBlobId: evidence.agreementBlobId, deliveryBlobId: evidence.deliveryBlobId, outcomeMemoryBlobId: evidence.outcomeMemoryBlobId, paymentDigest: evidence.paymentDigest });
89
+ }
90
+ }
91
+ const rl = createInterface({ input: process.stdin });
92
+ let queue = Promise.resolve();
93
+ rl.on("line", (line) => {
94
+ queue = queue.then(async () => {
95
+ if (Buffer.byteLength(line) > MAX_REQUEST_BYTES)
96
+ return send({ jsonrpc: "2.0", error: { code: -32600, message: "Request exceeds 1 MiB" }, id: null });
97
+ let request;
98
+ try {
99
+ request = JSON.parse(line);
100
+ }
101
+ catch {
102
+ return send({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
103
+ }
104
+ if (!request || typeof request !== "object" || request.method !== undefined && typeof request.method !== "string" || request.id !== undefined && typeof request.id !== "string" && typeof request.id !== "number" && request.id !== null || request.params !== undefined && (!request.params || typeof request.params !== "object" || Array.isArray(request.params)))
105
+ return send({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid JSON-RPC request" }, id: null });
106
+ try {
107
+ if (request.method === "initialize")
108
+ return send({ jsonrpc: "2.0", id: request.id, result: { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: `supercuts-${app.profile.role}`, version: PACKAGE_VERSION } } });
109
+ if (request.method === "notifications/initialized")
110
+ return;
111
+ const available = tools[app.profile.role].split(",").filter((name) => (remote || !name.startsWith("remote.")) && (app.profile.mock || memoryConfigured || !name.startsWith("memory.")) && (!name.startsWith("deal.") || mandate.version === "supercuts.mandate.v2") && (["status", "doctor"].includes(name) || mandate.allowedCommands.includes(name === "deal.reconcile" ? "deal.resume" : name)));
112
+ if (request.method === "tools/list")
113
+ return send({ jsonrpc: "2.0", id: request.id, result: { tools: available.map((name) => { const spec = toolSchema(name); return { name, description: spec.description, inputSchema: spec.inputSchema }; }) } });
114
+ if (request.method === "tools/call") {
115
+ const name = String(request.params?.name ?? ""), raw = (request.params?.arguments ?? {});
116
+ if (!available.includes(name))
117
+ return send({ jsonrpc: "2.0", id: request.id, error: { code: -32601, message: "Tool is unavailable for this role or configuration" } });
118
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
119
+ return send({ jsonrpc: "2.0", id: request.id, error: { code: -32602, message: "Tool arguments must be an object" } });
120
+ let data;
121
+ if (name.startsWith("deal.")) {
122
+ const spec = toolSchema(name);
123
+ const { validateToolArguments } = await import("./mandate.js");
124
+ validateToolArguments(spec, raw);
125
+ if (mandate.version !== "supercuts.mandate.v2" || !mandate.allowedCommands.includes(name === "deal.reconcile" ? "deal.resume" : name) || (name === "deal.execute" || name === "deal.resume") && Date.now() > mandate.expiresAt)
126
+ throw new Error("deal tool is not authorized by an active v2 mandate");
127
+ if (name === "deal.catalog")
128
+ data = { capabilities: (await new LiveDealPorts(app, mandate).catalog()).map(({ origin, registration }) => ({ origin, manifest: registration.manifest, manifestBlobId: registration.manifestBlobId })) };
129
+ else if (name === "deal.policy") {
130
+ const hasTable = Boolean(app.store.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mandate_operations'").get());
131
+ const reserved = hasTable ? app.store.db.prepare("SELECT amount FROM mandate_operations").all().reduce((sum, row) => sum + BigInt(row.amount), 0n) : 0n;
132
+ data = { perDealCeiling: mandate.perDealCeiling, totalBudget: mandate.totalRunBudget, remainingBudget: (BigInt(mandate.totalRunBudget) - reserved).toString(), expiresAt: mandate.expiresAt, defaultRail: "shielded", allowPublicX402: mandate.allowPublicX402 === true, allowedAssets: mandate.allowedAssets, allowedCapabilityIds: mandate.allowedCapabilityIds, allowedCapabilityClasses: mandate.allowedCapabilityClasses };
133
+ }
134
+ else if (name === "deal.evidence")
135
+ data = new DealJournal(app.store).evidence(String(raw.dealId));
136
+ else if (name === "deal.reconcile")
137
+ data = await app.reconcileDealOutcome(String(raw.operationId));
138
+ else if (name === "deal.delivery") {
139
+ const delivery = new DealJournal(app.store).delivery(String(raw.dealId));
140
+ data = { dealId: delivery.dealId, contentType: delivery.contentType, artifactSha256: delivery.artifactSha256, artifactBase64: Buffer.from(delivery.artifact).toString("base64") };
141
+ }
142
+ else if (name === "deal.execute") {
143
+ const input = raw.input;
144
+ if (raw["operation-id"] !== input?.operationId)
145
+ throw new Error("deal operation ID does not match its input");
146
+ data = await (await createConfiguredDealEngine(app)).execute(input);
147
+ }
148
+ else if (name === "deal.resume")
149
+ data = await (await createConfiguredDealEngine(app)).resume(String(raw.operationId));
150
+ else
151
+ data = new DealJournal(app.store).get(String(raw.operationId));
152
+ }
153
+ else if (name.startsWith("remote.")) {
154
+ if (!remote || !mandate.allowedCommands.includes(name) || Date.now() > mandate.expiresAt)
155
+ throw new Error("remote buyer tool is not authorized or configured");
156
+ const spec = toolSchema(name);
157
+ const { validateToolArguments } = await import("./mandate.js");
158
+ validateToolArguments(spec, raw);
159
+ if (process.env.SUPERCUTS_RUN_ID && name !== "remote.catalog" && name !== "remote.rank-briefs" && raw.runId !== undefined && raw.runId !== process.env.SUPERCUTS_RUN_ID)
160
+ throw new Error("remote tool run ID differs from this buyer demo run");
161
+ data = name === "remote.catalog" ? await remote.catalog() :
162
+ name === "remote.rank-briefs" ? await remote.rankBriefs(raw) :
163
+ name === "remote.negotiate" ? await remote.negotiate(raw) :
164
+ name === "remote.quote" ? await remote.quote(raw) :
165
+ name === "remote.purchase-brief" ? await remote.purchaseBrief(raw) :
166
+ name === "remote.agreement" ? await remote.agreement(raw) :
167
+ name === "remote.settle-analysis" ? await remote.settleAnalysis(raw) :
168
+ name === "remote.delivery" ? await remote.delivery(raw) :
169
+ name === "remote.events" ? await remote.events(String(raw.runId)) : {};
170
+ }
171
+ else
172
+ data = await executeMandated(app, mandate, name, raw);
173
+ logTool(name, data);
174
+ const result = { schemaVersion: "supercuts.v1", ok: true, command: name, data };
175
+ return send({ jsonrpc: "2.0", id: request.id, result: { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result } });
176
+ }
177
+ return send({ jsonrpc: "2.0", id: request.id, error: { code: -32601, message: "Method not found" } });
178
+ }
179
+ catch (error) {
180
+ return send({ jsonrpc: "2.0", id: request.id, result: { isError: true, content: [{ type: "text", text: JSON.stringify(errorOutput(error)) }], structuredContent: errorOutput(error) } });
181
+ }
182
+ }).catch((error) => send({ jsonrpc: "2.0", error: { code: -32603, message: error instanceof Error ? error.message : "Internal error" }, id: null }));
183
+ });
184
+ const close = () => { if (memoryTimer)
185
+ clearInterval(memoryTimer); app.close(); process.exit(0); };
186
+ process.on("SIGINT", close);
187
+ process.on("SIGTERM", close);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { Phase3App } from "./phase3.js";
3
+ const home = process.argv[2];
4
+ if (!home) {
5
+ process.stderr.write("usage: supercuts-memory-worker <role-home>\n");
6
+ process.exitCode = 2;
7
+ }
8
+ else {
9
+ let stopped = false;
10
+ process.on("SIGINT", () => { stopped = true; });
11
+ process.on("SIGTERM", () => { stopped = true; });
12
+ const app = new Phase3App(home);
13
+ try {
14
+ if (app.profile.mock)
15
+ throw new Error("memory worker requires a live role home");
16
+ // Only stale claims are ambiguous; another process may be actively writing.
17
+ app.recoverStaleMemorySubmissions();
18
+ while (!stopped) {
19
+ try {
20
+ await app.processMemoryOutbox();
21
+ }
22
+ catch (error) {
23
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "MEMORY_WORKER_ERROR";
24
+ process.stderr.write(`${code}\n`);
25
+ }
26
+ if (!stopped)
27
+ await new Promise((resolve) => {
28
+ const done = () => { clearTimeout(timer); process.off("SIGINT", done); process.off("SIGTERM", done); resolve(); };
29
+ const timer = setTimeout(done, 30_000);
30
+ process.once("SIGINT", done);
31
+ process.once("SIGTERM", done);
32
+ });
33
+ }
34
+ }
35
+ finally {
36
+ app.close();
37
+ }
38
+ }
@@ -0,0 +1,15 @@
1
+ export type MemoryFactKind = "budget_preference" | "counterparty_reliability" | "delivery_observation" | "successful_counteroffer" | "privacy_choice";
2
+ export interface MemoryFact {
3
+ kind: MemoryFactKind;
4
+ subject: string;
5
+ observation: string;
6
+ operationId: string;
7
+ createdAt: number;
8
+ }
9
+ export declare function validateMemoryFact(fact: MemoryFact): void;
10
+ export interface RailPolicy {
11
+ hiddenAmountRequired: boolean;
12
+ recalledRulesAvailable: boolean;
13
+ contradictory: boolean;
14
+ }
15
+ export declare function selectRail(policy: RailPolicy, requested: "shielded" | "x402"): "shielded" | "x402";
package/dist/memory.js ADDED
@@ -0,0 +1,17 @@
1
+ import { fail } from "./errors.js";
2
+ const forbidden = [/private key/i, /spend secret/i, /note opening/i, /full transcript/i, /seed phrase/i];
3
+ export function validateMemoryFact(fact) {
4
+ if (!fact.subject || !fact.observation || !fact.operationId)
5
+ fail("MEMORY_FACT_INVALID", "memory facts require subject, observation, and operation ID");
6
+ if (!Number.isSafeInteger(fact.createdAt) || fact.createdAt < 0 || fact.subject.length > 256 || fact.observation.length > 4096 || fact.operationId.length > 256)
7
+ fail("MEMORY_FACT_INVALID", "memory fact timestamp or field length is invalid");
8
+ if (forbidden.some((pattern) => pattern.test(`${fact.subject}\n${fact.observation}`)))
9
+ fail("MEMORY_SECRET_REJECTED", "memory facts cannot include wallet or transcript secrets");
10
+ }
11
+ export function selectRail(policy, requested) {
12
+ if (!policy.recalledRulesAvailable || policy.contradictory)
13
+ fail("POLICY_RECALL_REQUIRED", "payment requires explicit policy because recall is missing or contradictory");
14
+ if (requested === "x402" && policy.hiddenAmountRequired)
15
+ fail("PUBLIC_RAIL_FORBIDDEN", "policy requires a hidden payment amount");
16
+ return requested;
17
+ }
@@ -0,0 +1,35 @@
1
+ import { MemoryFact } from "./memory.js";
2
+ import { type SettlementMemoryV1 } from "./settlement.js";
3
+ import { RoleStore } from "./store.js";
4
+ export interface ManualMemoryConfig {
5
+ key: string;
6
+ accountId: string;
7
+ serverUrl: string;
8
+ suiPrivateKey: string;
9
+ embeddingApiKey: string;
10
+ embeddingApiBase: string;
11
+ embeddingModel: string;
12
+ packageId: string;
13
+ registryId: string;
14
+ namespace: string;
15
+ walrusAggregatorUrl: string;
16
+ walrusPublisherUrl: string;
17
+ }
18
+ /** Client-managed memory facade; plaintext never crosses the relayer boundary. */
19
+ export declare class ManualMemory {
20
+ private readonly store;
21
+ private readonly client;
22
+ constructor(store: RoleStore, config: ManualMemoryConfig);
23
+ remember(fact: MemoryFact | SettlementMemoryV1): Promise<string>;
24
+ recall(query: string, limit?: number): Promise<Array<{
25
+ blobId: string;
26
+ text: string;
27
+ distance: number;
28
+ }>>;
29
+ listMetadata(config: ManualMemoryConfig): Promise<{
30
+ indexed: number;
31
+ deleted: number;
32
+ cursor: string | null;
33
+ }>;
34
+ destroy(): void;
35
+ }
package/dist/memwal.js ADDED
@@ -0,0 +1,83 @@
1
+ import { MemWalManual } from "@mysten-incubation/memwal/manual";
2
+ import { MemWal } from "@mysten-incubation/memwal";
3
+ import { validateMemoryFact } from "./memory.js";
4
+ import { SETTLEMENT_MEMORY_VERSION, validateSettlementMemory } from "./settlement.js";
5
+ /** Client-managed memory facade; plaintext never crosses the relayer boundary. */
6
+ export class ManualMemory {
7
+ store;
8
+ client;
9
+ constructor(store, config) {
10
+ this.store = store;
11
+ let embedding;
12
+ try {
13
+ embedding = new URL(config.embeddingApiBase);
14
+ }
15
+ catch (error) {
16
+ throw new TypeError(`embeddingApiBase is invalid: ${error instanceof Error ? error.message : "invalid URL"}`);
17
+ }
18
+ if (!["localhost", "127.0.0.1", "::1"].includes(embedding.hostname))
19
+ throw new TypeError("memory embeddings must use a loopback trusted endpoint");
20
+ this.client = MemWalManual.create({ ...config, suiNetwork: "mainnet", preflightTimeoutMs: 15_000 });
21
+ }
22
+ async remember(fact) {
23
+ if (fact.version === SETTLEMENT_MEMORY_VERSION)
24
+ validateSettlementMemory(fact);
25
+ else
26
+ validateMemoryFact(fact);
27
+ const text = JSON.stringify(fact);
28
+ const result = await this.client.rememberManual(text);
29
+ this.store.putSecret(`memory:${result.blob_id}`, Buffer.from(text));
30
+ return result.blob_id;
31
+ }
32
+ async recall(query, limit = 5) {
33
+ const result = await this.client.recallManual(query, { limit });
34
+ return result.results.map((item) => ({ blobId: item.blob_id, text: item.text, distance: item.distance }));
35
+ }
36
+ async listMetadata(config) {
37
+ const client = MemWal.create({ key: config.key, accountId: config.accountId, serverUrl: config.serverUrl, namespace: config.namespace });
38
+ let cursor = this.store.db.prepare("SELECT cursor FROM memory_inventory_cursor WHERE namespace=?").get(config.namespace)?.cursor ?? undefined;
39
+ let indexed = 0, deleted = 0, restarted = false;
40
+ try {
41
+ for (;;) {
42
+ const page = await client.listMemories({ ...(cursor ? { cursor } : {}), namespace: config.namespace });
43
+ if (page.must_resync) {
44
+ if (restarted)
45
+ throw new Error("Walrus Memory inventory repeatedly requested a full resync");
46
+ restarted = true;
47
+ cursor = undefined;
48
+ this.store.db.prepare("DELETE FROM memory_inventory WHERE namespace=?").run(config.namespace);
49
+ this.store.db.prepare("DELETE FROM memory_inventory_cursor WHERE namespace=?").run(config.namespace);
50
+ continue;
51
+ }
52
+ this.store.db.exec("BEGIN IMMEDIATE");
53
+ try {
54
+ for (const item of page.memories) {
55
+ this.store.db.prepare("INSERT INTO memory_inventory(blob_id,memory_id,namespace,status,expires_at,deleted,updated_at) VALUES(?,?,?,?,?,0,?) ON CONFLICT(blob_id) DO UPDATE SET memory_id=excluded.memory_id,namespace=excluded.namespace,status=excluded.status,expires_at=excluded.expires_at,deleted=0,updated_at=excluded.updated_at")
56
+ .run(item.blob_id, item.memory_id, item.namespace_id, item.status, item.expires_at, item.updated_at);
57
+ indexed++;
58
+ }
59
+ for (const item of page.deleted) {
60
+ this.store.db.prepare("UPDATE memory_inventory SET deleted=1,updated_at=? WHERE memory_id=? AND namespace=?").run(item.deleted_at, item.memory_id, item.namespace_id);
61
+ deleted++;
62
+ }
63
+ this.store.db.prepare("INSERT INTO memory_inventory_cursor(namespace,cursor,updated_at) VALUES(?,?,?) ON CONFLICT(namespace) DO UPDATE SET cursor=excluded.cursor,updated_at=excluded.updated_at")
64
+ .run(config.namespace, page.next_cursor, Date.now());
65
+ this.store.db.exec("COMMIT");
66
+ }
67
+ catch (error) {
68
+ this.store.db.exec("ROLLBACK");
69
+ throw error;
70
+ }
71
+ cursor = page.next_cursor ?? undefined;
72
+ if (!page.has_more)
73
+ return { indexed, deleted, cursor: page.next_cursor };
74
+ if (!cursor)
75
+ throw new Error("Walrus Memory inventory has more pages without a cursor");
76
+ }
77
+ }
78
+ finally {
79
+ client.destroy();
80
+ }
81
+ }
82
+ destroy() { this.client.destroy(); }
83
+ }
@@ -0,0 +1,23 @@
1
+ import { RoleStore } from "./store.js";
2
+ import { MessageEnvelope, SignatureVerifier } from "./transcript.js";
3
+ export interface BlobTransport {
4
+ put(id: string, ciphertext: string): Promise<string>;
5
+ get(blobId: string): Promise<string>;
6
+ resolve(id: string): Promise<string | undefined>;
7
+ }
8
+ export interface TranscriptManifestEntry extends Omit<MessageEnvelope, "ciphertext"> {
9
+ blobId: string;
10
+ }
11
+ export declare function encryptSessionMessage(key: Uint8Array, sessionId: string, id: string, plaintext: Uint8Array): string;
12
+ export declare function decryptSessionMessage(key: Uint8Array, sessionId: string, id: string, encoded: string): Buffer;
13
+ /** Persists exact ciphertext before transport and makes uncertain uploads idempotent. */
14
+ export declare class DurableTranscriptTransport {
15
+ private readonly store;
16
+ private readonly blobs;
17
+ constructor(store: RoleStore, blobs: BlobTransport);
18
+ upload(message: MessageEnvelope): Promise<TranscriptManifestEntry>;
19
+ restart(manifest: TranscriptManifestEntry[], verify: SignatureVerifier, sessionId: string, participants: ReadonlySet<string>): Promise<{
20
+ messages: MessageEnvelope[];
21
+ root: string;
22
+ }>;
23
+ }
@@ -0,0 +1,58 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ import { fail } from "./errors.js";
3
+ import { hydrateTranscript, sha256 } from "./transcript.js";
4
+ function sessionKey(key) {
5
+ if (key.length !== 32)
6
+ fail("SESSION_KEY_INVALID", "session key must contain 32 bytes");
7
+ return Buffer.from(key);
8
+ }
9
+ export function encryptSessionMessage(key, sessionId, id, plaintext) {
10
+ const nonce = randomBytes(12);
11
+ const cipher = createCipheriv("aes-256-gcm", sessionKey(key), nonce);
12
+ cipher.setAAD(Buffer.from(`${sessionId}:${id}`));
13
+ const body = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14
+ return Buffer.concat([nonce, cipher.getAuthTag(), body]).toString("base64");
15
+ }
16
+ export function decryptSessionMessage(key, sessionId, id, encoded) {
17
+ try {
18
+ const value = Buffer.from(encoded, "base64");
19
+ if (value.length < 29)
20
+ fail("SESSION_CIPHERTEXT_INVALID", "session ciphertext is truncated");
21
+ const decipher = createDecipheriv("aes-256-gcm", sessionKey(key), value.subarray(0, 12));
22
+ decipher.setAAD(Buffer.from(`${sessionId}:${id}`));
23
+ decipher.setAuthTag(value.subarray(12, 28));
24
+ return Buffer.concat([decipher.update(value.subarray(28)), decipher.final()]);
25
+ }
26
+ catch (error) {
27
+ return fail("SESSION_DECRYPT_FAILED", "participant could not authenticate the session message", error);
28
+ }
29
+ }
30
+ /** Persists exact ciphertext before transport and makes uncertain uploads idempotent. */
31
+ export class DurableTranscriptTransport {
32
+ store;
33
+ blobs;
34
+ constructor(store, blobs) {
35
+ this.store = store;
36
+ this.blobs = blobs;
37
+ }
38
+ async upload(message) {
39
+ const ciphertextHash = sha256(Buffer.from(message.ciphertext, "base64"));
40
+ if (ciphertextHash !== message.ciphertextHash)
41
+ fail("CIPHERTEXT_TAMPERED", "message ciphertext hash does not match");
42
+ this.store.prepareMessage({ id: message.id, sessionId: message.sessionId, sequence: message.sequence, ciphertextHash });
43
+ this.store.putSecretOnce(`message:${message.id}`, Buffer.from(message.ciphertext));
44
+ const prepared = this.store.getMessage(message.id);
45
+ let blobId = prepared.blobId ?? await this.blobs.resolve(message.id);
46
+ if (!blobId)
47
+ blobId = await this.blobs.put(message.id, this.store.getSecret(`message:${message.id}`).toString());
48
+ const remote = await this.blobs.get(blobId);
49
+ if (sha256(Buffer.from(remote, "base64")) !== ciphertextHash)
50
+ fail("WALRUS_BLOB_MISMATCH", "retrieved blob differs from durable ciphertext");
51
+ this.store.attachMessageBlob(message.id, ciphertextHash, blobId);
52
+ const { ciphertext: _ciphertext, ...metadata } = message;
53
+ return { ...metadata, blobId };
54
+ }
55
+ async restart(manifest, verify, sessionId, participants) {
56
+ return hydrateTranscript(manifest, (blobId) => this.blobs.get(blobId), verify, { sessionId, participants });
57
+ }
58
+ }
@@ -0,0 +1,11 @@
1
+ export interface NoteOpening {
2
+ deployment: bigint;
3
+ amount: bigint;
4
+ ownerAx: bigint;
5
+ ownerAy: bigint;
6
+ spendSecret: bigint;
7
+ salt: bigint;
8
+ }
9
+ export declare function encodeNoteOpening(opening: NoteOpening): Buffer;
10
+ /** Recomputes the exact Poseidon commitment enforced by protocol-v1 circuits. */
11
+ export declare function noteCommitment(opening: NoteOpening): Promise<string>;
package/dist/notes.js ADDED
@@ -0,0 +1,26 @@
1
+ import { buildPoseidon } from "circomlibjs";
2
+ import { fail } from "./errors.js";
3
+ const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
4
+ const U64_MAX = (1n << 64n) - 1n;
5
+ let poseidonPromise;
6
+ function field(name, value) {
7
+ if (typeof value !== "bigint" || value <= 0n || value >= FIELD_MODULUS)
8
+ fail("NOTE_OPENING_INVALID", `${name} must be a nonzero canonical BN254 field element`);
9
+ }
10
+ export function encodeNoteOpening(opening) {
11
+ return Buffer.from(JSON.stringify(Object.fromEntries(Object.entries(opening).map(([key, value]) => [key, value.toString()]))));
12
+ }
13
+ /** Recomputes the exact Poseidon commitment enforced by protocol-v1 circuits. */
14
+ export async function noteCommitment(opening) {
15
+ field("deployment", opening.deployment);
16
+ field("ownerAx", opening.ownerAx);
17
+ field("ownerAy", opening.ownerAy);
18
+ field("spendSecret", opening.spendSecret);
19
+ field("salt", opening.salt);
20
+ if (typeof opening.amount !== "bigint" || opening.amount <= 0n || opening.amount > U64_MAX)
21
+ fail("NOTE_OPENING_INVALID", "amount must be a positive u64");
22
+ const poseidon = await (poseidonPromise ??= buildPoseidon());
23
+ const spendTag = poseidon.F.toObject(poseidon([2006n, opening.spendSecret]));
24
+ const commitment = poseidon.F.toObject(poseidon([2010n, opening.deployment, opening.amount, opening.ownerAx, opening.ownerAy, spendTag, opening.salt]));
25
+ return commitment.toString(16).padStart(64, "0");
26
+ }
@@ -0,0 +1 @@
1
+ export declare function onboardingDoctor(home: string, sellerConfig?: string): Promise<Record<string, unknown>>;
@@ -0,0 +1,76 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { verifySignedCapability } from "./capability.js";
4
+ import { fail } from "./errors.js";
5
+ import { loadMandate } from "./mandate.js";
6
+ import { Phase3App } from "./phase3.js";
7
+ import { writeClient } from "./sui.js";
8
+ async function json(url) {
9
+ const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15_000) });
10
+ if (!response.ok)
11
+ fail("ONBOARDING_SERVICE_UNAVAILABLE", `${url.origin} returned HTTP ${response.status}`);
12
+ const result = await response.json();
13
+ if (!result || typeof result !== "object" || Array.isArray(result))
14
+ fail("ONBOARDING_SERVICE_INVALID", `${url.origin} returned invalid JSON`);
15
+ return result;
16
+ }
17
+ export async function onboardingDoctor(home, sellerConfig) {
18
+ const app = new Phase3App(home);
19
+ try {
20
+ if (app.profile.mock)
21
+ fail("LIVE_PROFILE_REQUIRED", "onboarding doctor requires a live Testnet role home");
22
+ const runtime = await app.execute("doctor", {});
23
+ if (runtime.data.readyForDemo === false)
24
+ fail("ONBOARDING_RUNTIME_NOT_READY", "live runtime doctor reports missing prerequisites");
25
+ if (app.profile.role === "buyer") {
26
+ const mandate = loadMandate(home);
27
+ if (mandate.version !== "supercuts.mandate.v2")
28
+ fail("DEAL_MANDATE_VERSION", "buyer needs a v2 standing mandate");
29
+ if (JSON.stringify([...mandate.allowedCounterparties].sort()) !== JSON.stringify([...app.profile.policy.allowedCounterparties].sort()))
30
+ fail("POLICY_COUNTERPARTY_MISMATCH", "buyer mandate and payment profile allow different sellers");
31
+ const path = join(home, "deal-sellers.json");
32
+ if ((statSync(path).mode & 0o077) !== 0)
33
+ fail("DEAL_SELLERS_PERMISSIONS", "buyer catalog config must be owner-only");
34
+ const source = JSON.parse(readFileSync(path, "utf8"));
35
+ const directoryUrl = Array.isArray(source) ? undefined : source.directoryUrl;
36
+ let directoryListings;
37
+ if (directoryUrl) {
38
+ const health = await json(new URL("/v2/directory/health", directoryUrl));
39
+ if (health.ok !== true)
40
+ fail("SELLER_DIRECTORY_UNAVAILABLE", "directory health check failed");
41
+ directoryListings = Number(health.listings);
42
+ }
43
+ const endpoint = app.profile.live?.writeGrpcEndpoint ?? fail("LIVE_PROFILE_INVALID", "buyer write RPC is missing");
44
+ const signer = String(runtime.data.signer ?? "");
45
+ const { balance } = await writeClient(endpoint).getBalance({ owner: signer, coinType: "0x2::sui::SUI" });
46
+ return { role: "buyer", healthy: true, runtime: runtime.data, approvedSellers: mandate.allowedCounterparties.length, nativeSuiMist: String(balance.balance), funded: BigInt(balance.balance) > 0n, ...(directoryListings !== undefined ? { directoryListings } : {}) };
47
+ }
48
+ if (app.profile.role === "seller") {
49
+ if (!sellerConfig)
50
+ fail("ARGUMENT_REQUIRED", "seller doctor requires --config <owner-only-seller-config.json>");
51
+ const path = resolve(sellerConfig);
52
+ if ((statSync(path).mode & 0o077) !== 0)
53
+ fail("SELLER_CONFIG_PERMISSIONS", "seller config must be owner-only");
54
+ const config = JSON.parse(readFileSync(path, "utf8"));
55
+ if (!Array.isArray(config.capabilities) || !config.capabilities.length)
56
+ fail("SELLER_CONFIG_INVALID", "seller has no registered capabilities");
57
+ const served = await json(new URL("/v2/catalog", config.origin));
58
+ if (!Array.isArray(served.capabilities))
59
+ fail("SELLER_CATALOG_INVALID", "seller endpoint has no valid catalog");
60
+ for (const item of config.capabilities) {
61
+ const registrationPath = resolve(item.registrationFile);
62
+ if ((statSync(registrationPath).mode & 0o077) !== 0)
63
+ fail("SELLER_CONFIG_PERMISSIONS", "registration file must be owner-only");
64
+ const registration = JSON.parse(readFileSync(registrationPath, "utf8"));
65
+ await verifySignedCapability(registration);
66
+ if (!served.capabilities.some((entry) => JSON.stringify(entry) === JSON.stringify(registration)))
67
+ fail("SELLER_CATALOG_INVALID", "seller endpoint does not serve its configured registration");
68
+ }
69
+ return { role: "seller", healthy: true, runtime: runtime.data, capabilities: config.capabilities.length, origin: config.origin };
70
+ }
71
+ return fail("ROLE_FORBIDDEN", "onboarding doctor supports buyer and seller homes");
72
+ }
73
+ finally {
74
+ app.close();
75
+ }
76
+ }