@supercuts-walrus/backend 0.0.0-stage → 0.1.1

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 (155) 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 +32 -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 +511 -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/funding.d.ts +38 -0
  38. package/dist/funding.js +78 -0
  39. package/dist/hosted-exit-cli.d.ts +2 -0
  40. package/dist/hosted-exit-cli.js +37 -0
  41. package/dist/hosted-exit.d.ts +24 -0
  42. package/dist/hosted-exit.js +70 -0
  43. package/dist/hosted-seller-import.d.ts +2 -0
  44. package/dist/hosted-seller-import.js +70 -0
  45. package/dist/hosted-seller-repository.d.ts +31 -0
  46. package/dist/hosted-seller-repository.js +214 -0
  47. package/dist/index.d.ts +37 -0
  48. package/dist/index.js +37 -0
  49. package/dist/indexer.d.ts +63 -0
  50. package/dist/indexer.js +60 -0
  51. package/dist/live-deal-ports.d.ts +41 -0
  52. package/dist/live-deal-ports.js +212 -0
  53. package/dist/live-index.d.ts +28 -0
  54. package/dist/live-index.js +149 -0
  55. package/dist/live-runtime.d.ts +85 -0
  56. package/dist/live-runtime.js +644 -0
  57. package/dist/live-seller-transport.d.ts +30 -0
  58. package/dist/live-seller-transport.js +169 -0
  59. package/dist/live-seller-verifier.d.ts +11 -0
  60. package/dist/live-seller-verifier.js +65 -0
  61. package/dist/mandate.d.ts +34 -0
  62. package/dist/mandate.js +179 -0
  63. package/dist/mcp-bootstrap.d.ts +2 -0
  64. package/dist/mcp-bootstrap.js +61 -0
  65. package/dist/mcp.d.ts +2 -0
  66. package/dist/mcp.js +207 -0
  67. package/dist/memory-worker.d.ts +2 -0
  68. package/dist/memory-worker.js +38 -0
  69. package/dist/memory.d.ts +15 -0
  70. package/dist/memory.js +17 -0
  71. package/dist/memwal.d.ts +35 -0
  72. package/dist/memwal.js +83 -0
  73. package/dist/negotiation.d.ts +23 -0
  74. package/dist/negotiation.js +58 -0
  75. package/dist/notes.d.ts +11 -0
  76. package/dist/notes.js +26 -0
  77. package/dist/onboarding-doctor.d.ts +1 -0
  78. package/dist/onboarding-doctor.js +87 -0
  79. package/dist/phase3.d.ts +100 -0
  80. package/dist/phase3.js +1174 -0
  81. package/dist/procurement.d.ts +8 -0
  82. package/dist/procurement.js +19 -0
  83. package/dist/protocol-engine.d.ts +121 -0
  84. package/dist/protocol-engine.js +149 -0
  85. package/dist/prover.d.ts +28 -0
  86. package/dist/prover.js +70 -0
  87. package/dist/relayer.d.ts +32 -0
  88. package/dist/relayer.js +43 -0
  89. package/dist/remote-buyer.d.ts +85 -0
  90. package/dist/remote-buyer.js +285 -0
  91. package/dist/resource-server.d.ts +2 -0
  92. package/dist/resource-server.js +24 -0
  93. package/dist/resource.d.ts +22 -0
  94. package/dist/resource.js +55 -0
  95. package/dist/role-lock.d.ts +8 -0
  96. package/dist/role-lock.js +54 -0
  97. package/dist/seam.d.ts +2 -0
  98. package/dist/seam.js +117 -0
  99. package/dist/seller-catalog-generate.d.ts +2 -0
  100. package/dist/seller-catalog-generate.js +68 -0
  101. package/dist/seller-database.d.ts +41 -0
  102. package/dist/seller-database.js +80 -0
  103. package/dist/seller-directory-cli.d.ts +2 -0
  104. package/dist/seller-directory-cli.js +24 -0
  105. package/dist/seller-directory.d.ts +5 -0
  106. package/dist/seller-directory.js +50 -0
  107. package/dist/seller-live-cli.d.ts +2 -0
  108. package/dist/seller-live-cli.js +53 -0
  109. package/dist/seller-package.d.ts +9 -0
  110. package/dist/seller-package.js +50 -0
  111. package/dist/seller-provision.d.ts +2 -0
  112. package/dist/seller-provision.js +84 -0
  113. package/dist/seller-register-cli.d.ts +2 -0
  114. package/dist/seller-register-cli.js +40 -0
  115. package/dist/seller-runtime.d.ts +60 -0
  116. package/dist/seller-runtime.js +96 -0
  117. package/dist/seller-service.d.ts +34 -0
  118. package/dist/seller-service.js +422 -0
  119. package/dist/seller-state.d.ts +19 -0
  120. package/dist/seller-state.js +60 -0
  121. package/dist/seller-stdio.d.ts +6 -0
  122. package/dist/seller-stdio.js +29 -0
  123. package/dist/seller-v2-provision.d.ts +2 -0
  124. package/dist/seller-v2-provision.js +71 -0
  125. package/dist/seller-watch.d.ts +2 -0
  126. package/dist/seller-watch.js +45 -0
  127. package/dist/settlement.d.ts +95 -0
  128. package/dist/settlement.js +141 -0
  129. package/dist/store.d.ts +36 -0
  130. package/dist/store.js +193 -0
  131. package/dist/sui.d.ts +49 -0
  132. package/dist/sui.js +77 -0
  133. package/dist/tatum.d.ts +17 -0
  134. package/dist/tatum.js +83 -0
  135. package/dist/transcript.d.ts +31 -0
  136. package/dist/transcript.js +52 -0
  137. package/dist/v2-exit-relayer.d.ts +34 -0
  138. package/dist/v2-exit-relayer.js +102 -0
  139. package/dist/v2-exit-worker.d.ts +2 -0
  140. package/dist/v2-exit-worker.js +97 -0
  141. package/dist/v2-exits.d.ts +30 -0
  142. package/dist/v2-exits.js +78 -0
  143. package/dist/v2-prefund.d.ts +19 -0
  144. package/dist/v2-prefund.js +43 -0
  145. package/dist/v2-relayer-cli.d.ts +2 -0
  146. package/dist/v2-relayer-cli.js +39 -0
  147. package/dist/wallet.d.ts +32 -0
  148. package/dist/wallet.js +90 -0
  149. package/dist/workflow-memory.d.ts +22 -0
  150. package/dist/workflow-memory.js +49 -0
  151. package/dist/x402-resource-service.d.ts +36 -0
  152. package/dist/x402-resource-service.js +173 -0
  153. package/dist/x402.d.ts +46 -0
  154. package/dist/x402.js +136 -0
  155. package/package.json +60 -3
package/dist/phase3.js ADDED
@@ -0,0 +1,1174 @@
1
+ import { createHash, createHmac, generateKeyPairSync, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { loadStateKey } from "./config.js";
5
+ import { fail, SupercutsError } from "./errors.js";
6
+ import { RoleStore } from "./store.js";
7
+ import { DealJournal } from "./deal.js";
8
+ import { LiveRuntime } from "./live-runtime.js";
9
+ import { V2ExitQueue } from "./v2-exits.js";
10
+ import { validateCapabilityManifest } from "./capability.js";
11
+ import { ManualMemory } from "./memwal.js";
12
+ import { WorkflowMemory } from "./workflow-memory.js";
13
+ import { canonicalJson, createSettlementPlan, settlementMandateHash, settlementPlanHash, validateSettlementMemory, validateSettlementPlan } from "./settlement.js";
14
+ const PACKAGE = "0xf887433ddfd1068f6df3020cd757939b99ceb418f0d8b9924092b01c7aee846e";
15
+ const POOL = "0x873e37b4745316f21a2780893f5fc36f87da559b08923fb4dad6237cd2e95d98";
16
+ const V2_PACKAGE = "0x32d104c1ab422d497df4ae3a6861258694949c86428f886ca338e07bd055cf76";
17
+ const V2_POOL = "0x8fd22140c906495bb5334a497dcdd9112479c638e21c5df4b4cfb66f633a106b";
18
+ const V2_RELAYER = "0x405f473ed1f7f609b16d3d7dabc7cd04831cfc73b4a7fcebe9fa502d72e357a3";
19
+ const COIN = "0x2::sui::SUI";
20
+ const WRITE = new Set(["wallet.deposit", "wallet.recover", "wallet.withdraw", "wallet.exit.queue", "wallet.exit.finalize", "offer.open", "offer.propose", "offer.counter", "offer.accept", "pay.shielded", "pay.x402", "resource.serve", "memory.remember", "disclose", "capability.publish", "settlement.plan", "settlement.approve-review", "memory.policy"]);
21
+ const ROLE_COMMANDS = {
22
+ buyer: new Set(["status", "doctor", "capability.discover", "settlement.plan", "settlement.approve-review", "memory.policy", "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", "remote.research", "disclose"]),
23
+ seller: new Set(["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", "memory.inventory"]),
24
+ auditor: new Set(["status", "doctor", "audit.verify"]),
25
+ };
26
+ const hash = (value) => createHash("sha256").update(value).digest("hex");
27
+ const json = (value) => JSON.stringify(value);
28
+ function canonical(value) {
29
+ if (Array.isArray(value))
30
+ return `[${value.map(canonical).join(",")}]`;
31
+ if (value && typeof value === "object")
32
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
33
+ return JSON.stringify(value);
34
+ }
35
+ function required(args, name) {
36
+ const value = args[name];
37
+ if (typeof value !== "string" || !value)
38
+ fail("ARGUMENT_REQUIRED", `--${name} is required`);
39
+ return value;
40
+ }
41
+ function amount(args) {
42
+ const value = required(args, "amount");
43
+ if (!/^[1-9][0-9]*$/.test(value))
44
+ fail("AMOUNT_INVALID", "amount must be a positive integer");
45
+ return BigInt(value);
46
+ }
47
+ function matchesPlannedEndpoint(planned, actual, requestId) {
48
+ if (planned === actual)
49
+ return true;
50
+ try {
51
+ const base = new URL(planned), resource = new URL(actual);
52
+ return base.origin === resource.origin && !base.search && !base.hash && !resource.search && !resource.hash &&
53
+ resource.pathname === `${base.pathname.replace(/\/$/, "")}/${encodeURIComponent(requestId)}`;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ function record(store, scope, id, value) {
60
+ store.db.prepare("INSERT INTO app_records(scope,id,value,updated_at) VALUES(?,?,?,?) ON CONFLICT(scope,id) DO UPDATE SET value=excluded.value,updated_at=excluded.updated_at")
61
+ .run(scope, id, json(value), Date.now());
62
+ }
63
+ function get(store, scope, id) {
64
+ const row = store.db.prepare("SELECT value FROM app_records WHERE scope=? AND id=?").get(scope, id);
65
+ return row ? JSON.parse(row.value) : undefined;
66
+ }
67
+ function all(store, scope) {
68
+ return store.db.prepare("SELECT value FROM app_records WHERE scope=? ORDER BY updated_at,id").all(scope).map((row) => JSON.parse(row.value));
69
+ }
70
+ export function parseArgs(values) {
71
+ const result = {};
72
+ for (let i = 0; i < values.length; i++) {
73
+ const token = values[i];
74
+ if (!token.startsWith("--") || token.length <= 2 || token.length > 130)
75
+ fail("ARGUMENT_INVALID", "arguments must be named --options of at most 128 characters");
76
+ const key = token.slice(2);
77
+ if (!/^[a-z][a-z0-9-]*$/.test(key) || key in result)
78
+ fail("ARGUMENT_INVALID", `invalid or repeated option --${key}`);
79
+ const next = values[i + 1];
80
+ if (next && !next.startsWith("--")) {
81
+ if (Buffer.byteLength(next) > 1024 * 1024)
82
+ fail("ARGUMENT_INVALID", `--${key} exceeds 1 MiB`);
83
+ result[key] = next;
84
+ i++;
85
+ }
86
+ else
87
+ result[key] = true;
88
+ }
89
+ return result;
90
+ }
91
+ export function homePath(args, env = process.env) {
92
+ const selected = args.home ?? env.SUPERCUTS_HOME;
93
+ return resolve(typeof selected === "string" ? selected : join(env.HOME ?? process.cwd(), ".supercuts"));
94
+ }
95
+ export function initialize(home, args) {
96
+ const role = required(args, "role");
97
+ if (!["buyer", "seller", "auditor"].includes(role))
98
+ fail("ROLE_INVALID", "role must be buyer, seller, or auditor");
99
+ if (existsSync(join(home, "profile.json")))
100
+ fail("PROFILE_EXISTS", `${home} is already initialized`);
101
+ mkdirSync(home, { recursive: true, mode: 0o700 });
102
+ chmodSync(home, 0o700);
103
+ const isMock = args.mock === true;
104
+ const deploymentPath = isMock ? undefined : resolve(required(args, "deployment-config"));
105
+ const deployment = deploymentPath ? JSON.parse(readFileSync(deploymentPath, "utf8")) : undefined;
106
+ if (deployment && (deployment.network !== "testnet" || deployment.testOnly !== true || deployment.coinType !== COIN || !((deployment.packageId === PACKAGE && deployment.poolId === POOL && deployment.verifierVersion === 1) || (deployment.packageId === V2_PACKAGE && deployment.poolId === V2_POOL && deployment.verifierVersion === 2))))
107
+ fail("DEPLOYMENT_ID_MISMATCH", "live initialization requires a pinned Testnet pool deployment");
108
+ const manifestValue = String(deployment?.artifactManifest ?? "");
109
+ const manifest = isMock ? (typeof args["artifact-manifest"] === "string" ? resolve(args["artifact-manifest"]) : "mock:pinned-phase1") : (() => { if (!manifestValue)
110
+ return fail("CONFIG_INVALID", "deployment artifactManifest is missing"); if (isAbsolute(manifestValue))
111
+ return manifestValue; const candidates = [resolve(manifestValue), resolve(dirname(deploymentPath), "..", manifestValue)]; return candidates.find(existsSync) ?? fail("PROVING_ARTIFACT_MISSING", "deployment artifact manifest could not be resolved"); })();
112
+ const manifestHash = manifest.startsWith("mock:") ? hash(manifest) : hash(readFileSync(manifest));
113
+ const endpoint = (name) => { const value = required(args, name); return /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`; };
114
+ const credential = (name) => { const path = resolve(required(args, name)); if ((statSync(path).mode & 0o077) !== 0)
115
+ fail("CREDENTIAL_PERMISSIONS", `${name} must be owner-only`); const value = readFileSync(path); if (!value.length || value.length > 4096)
116
+ fail("CREDENTIAL_INVALID", `${name} is empty or too large`); return Buffer.from(value.toString().trim()); };
117
+ const liveCredentials = deployment ? { sui: credential("sui-key-file"), tatum: credential("tatum-key-file"), ...(typeof args["memory-config-file"] === "string" ? { memory: credential("memory-config-file") } : {}) } : undefined;
118
+ if (liveCredentials?.memory) {
119
+ let memoryConfig;
120
+ try {
121
+ memoryConfig = JSON.parse(liveCredentials.memory.toString());
122
+ }
123
+ catch (error) {
124
+ return fail("LIVE_MEMORY_CONFIG_INVALID", "memory config is not valid JSON", error);
125
+ }
126
+ let embedding;
127
+ try {
128
+ embedding = new URL(memoryConfig.embeddingApiBase);
129
+ }
130
+ catch (error) {
131
+ return fail("LIVE_MEMORY_CONFIG_INVALID", "embeddingApiBase is invalid", error);
132
+ }
133
+ if (!["localhost", "127.0.0.1", "::1"].includes(embedding.hostname))
134
+ fail("LIVE_MEMORY_EMBEDDING_UNTRUSTED", "live memory embeddings must use the loopback 1536-dimension adapter");
135
+ }
136
+ const artifactRoot = manifest.startsWith("mock:") ? "" : resolve(manifest, "..");
137
+ const circuitsRoot = artifactRoot ? resolve(artifactRoot, deployment?.verifierVersion === 2 ? "../.." : "..") : "";
138
+ const repositoryRoot = circuitsRoot ? resolve(circuitsRoot, "..") : "";
139
+ const live = deployment ? {
140
+ coinTypeHash: String(deployment.coinTypeHash), deploymentDomain: String(deployment.deploymentDomain), initialRoot: String(deployment.initialRoot), deploymentCheckpoint: String(deployment.deploymentCheckpoint),
141
+ writeGrpcEndpoint: endpoint("write-grpc"), tatumGrpcEndpoint: endpoint("tatum-grpc"), artifactRoot,
142
+ ...(typeof args["walrus-publisher"] === "string" ? { walrusPublisherUrl: endpoint("walrus-publisher") } : {}),
143
+ ...(typeof args["walrus-aggregator"] === "string" ? { walrusAggregatorUrl: endpoint("walrus-aggregator") } : {}),
144
+ snarkjsPath: typeof args["snarkjs"] === "string" ? resolve(args["snarkjs"]) : resolve(circuitsRoot, "node_modules/.bin/snarkjs"), converterPath: typeof args.converter === "string" ? resolve(args.converter) : resolve(repositoryRoot, "target/debug/supercuts-groth16"),
145
+ ...(typeof args.rapidsnark === "string" ? { rapidsnarkPath: resolve(args.rapidsnark) } : {}),
146
+ artifacts: Object.fromEntries(Object.entries(deployment.artifacts).map(([name, item]) => [name, { zkeySha256: item.zkeySha256, verifyingKeySha256: item.verifyingKeySha256 }])),
147
+ } : undefined;
148
+ const profile = {
149
+ version: isMock ? 1 : 2, role, network: "testnet", mock: isMock, packageId: deployment ? String(deployment.packageId) : PACKAGE, poolId: deployment ? String(deployment.poolId) : POOL, coinType: deployment ? String(deployment.coinType) : COIN, verifierVersion: deployment ? Number(deployment.verifierVersion) : 1,
150
+ artifactManifest: manifest, artifactManifestSha256: manifestHash,
151
+ policy: {
152
+ maxAmount: typeof args["max-amount"] === "string" ? args["max-amount"] : "1000000000",
153
+ allowedAssets: [COIN],
154
+ allowedCounterparties: typeof args["allow-counterparty"] === "string" ? args["allow-counterparty"].split(",") : ["seller-a", "seller-b"],
155
+ requiredPrivacy: args["require-shielded"] === true ? "shielded" : "any",
156
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
157
+ maxMemoryDistance: (() => { const value = typeof args["max-memory-distance"] === "string" ? Number(args["max-memory-distance"]) : 0.35; if (!Number.isFinite(value) || value < 0 || value > 1)
158
+ return fail("POLICY_MEMORY_DISTANCE_INVALID", "max-memory-distance must be in [0,1]"); return value; })(),
159
+ }, ...(live ? { live } : {}),
160
+ };
161
+ writeFileSync(join(home, "profile.json"), `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
162
+ const mandate = {
163
+ version: "supercuts.mandate.v1", owner: "local-operator", role,
164
+ allowedCommands: [...ROLE_COMMANDS[role]].filter((name) => name !== "settlement.approve-review" && name !== "memory.policy"), allowedCounterparties: profile.policy.allowedCounterparties,
165
+ allowedAssets: profile.policy.allowedAssets, perDealCeiling: profile.policy.maxAmount,
166
+ totalRunBudget: (BigInt(profile.policy.maxAmount) * 100n).toString(), requiredPrivacy: profile.policy.requiredPrivacy,
167
+ expiresAt: profile.policy.expiresAt,
168
+ };
169
+ writeFileSync(join(home, "mandate.json"), `${JSON.stringify(mandate, null, 2)}\n`, { mode: 0o600 });
170
+ const stateKey = randomBytes(32);
171
+ writeFileSync(join(home, "state.key"), stateKey, { mode: 0o600 });
172
+ if (live) {
173
+ const store = new RoleStore(join(home, "state.sqlite"), stateKey);
174
+ try {
175
+ store.putSecretOnce("live:sui-private-key", liveCredentials.sui);
176
+ store.putSecretOnce("live:tatum-api-key", liveCredentials.tatum);
177
+ if (liveCredentials.memory)
178
+ store.putSecretOnce("live:memory-config", liveCredentials.memory);
179
+ store.putSecretOnce("identity:babyjub-seed", randomBytes(32));
180
+ const pair = generateKeyPairSync("x25519");
181
+ store.putSecretOnce("identity:x25519-private", pair.privateKey.export({ type: "pkcs8", format: "der" }));
182
+ store.putSecretOnce("identity:x25519-public", pair.publicKey.export({ type: "spki", format: "der" }));
183
+ }
184
+ finally {
185
+ store.close();
186
+ }
187
+ }
188
+ return output("init", { role, home, network: profile.network, mock: profile.mock });
189
+ }
190
+ export class Phase3App {
191
+ home;
192
+ profile;
193
+ store;
194
+ #authorizationKey;
195
+ constructor(home) {
196
+ this.home = home;
197
+ const profilePath = join(home, "profile.json");
198
+ if (!existsSync(profilePath))
199
+ fail("PROFILE_NOT_FOUND", `run supercuts init for ${home}`);
200
+ if ((statSync(home).mode & 0o077) !== 0 || (statSync(profilePath).mode & 0o077) !== 0)
201
+ fail("STATE_PERMISSIONS", "profile and state directory must be owner-only");
202
+ this.profile = JSON.parse(readFileSync(profilePath, "utf8"));
203
+ this.store = new RoleStore(join(home, "state.sqlite"), loadStateKey(join(home, "state.key")));
204
+ this.store.db.exec(`
205
+ CREATE TABLE IF NOT EXISTS app_records(scope TEXT NOT NULL,id TEXT NOT NULL,value TEXT NOT NULL,updated_at INTEGER NOT NULL,PRIMARY KEY(scope,id));
206
+ CREATE TABLE IF NOT EXISTS authorization_intents(challenge_id TEXT PRIMARY KEY,intent_hash TEXT NOT NULL,command TEXT NOT NULL,parameters TEXT NOT NULL,expires_at INTEGER NOT NULL,approved INTEGER NOT NULL DEFAULT 0,used INTEGER NOT NULL DEFAULT 0);
207
+ CREATE TABLE IF NOT EXISTS authorized_operations(operation_id TEXT PRIMARY KEY,intent_hash TEXT NOT NULL,created_at INTEGER NOT NULL);
208
+ `);
209
+ const existingAuthorizationKey = this.store.getSecret("authorization:mac-key");
210
+ if (existingAuthorizationKey)
211
+ this.#authorizationKey = existingAuthorizationKey;
212
+ else {
213
+ this.#authorizationKey = randomBytes(32);
214
+ this.store.putSecretOnce("authorization:mac-key", this.#authorizationKey);
215
+ }
216
+ }
217
+ close() { this.store.close(); }
218
+ normalizedIntent(command, args) {
219
+ const parameters = Object.fromEntries(Object.entries(args).filter(([key]) => !["authorize", "approval-token", "home"].includes(key)).sort(([a], [b]) => a.localeCompare(b)));
220
+ return { parameters, intentHash: hash(canonical({ domain: "supercuts-authorization-v1", role: this.profile.role, packageId: this.profile.packageId, poolId: this.profile.poolId, command, parameters })) };
221
+ }
222
+ preview(command, args, ttlMs = 60_000) {
223
+ if (!ROLE_COMMANDS[this.profile.role].has(command))
224
+ fail("ROLE_FORBIDDEN", `${this.profile.role} cannot invoke ${command}`);
225
+ if (!WRITE.has(command))
226
+ fail("AUTHORIZATION_NOT_REQUIRED", `${command} is read-only`);
227
+ if (!Number.isSafeInteger(ttlMs) || ttlMs < 1_000 || ttlMs > 300_000)
228
+ fail("AUTHORIZATION_TTL_INVALID", "authorization TTL must be between 1 and 300 seconds");
229
+ const { parameters, intentHash } = this.normalizedIntent(command, args);
230
+ const serialized = canonical(parameters);
231
+ if (Buffer.byteLength(serialized) > 64 * 1024)
232
+ fail("AUTHORIZATION_INTENT_TOO_LARGE", "authorization intent exceeds 64 KiB");
233
+ this.store.db.prepare("DELETE FROM authorization_intents WHERE expires_at < ? OR (used=1 AND expires_at < ?)").run(Date.now(), Date.now() + 1);
234
+ const pending = this.store.db.prepare("SELECT COUNT(*) AS count FROM authorization_intents WHERE used=0 AND expires_at>=?").get(Date.now());
235
+ if (pending.count >= 1024)
236
+ fail("AUTHORIZATION_LIMIT", "too many pending authorization intents");
237
+ const challengeId = `auth_${randomBytes(18).toString("base64url")}`, expiresAt = Date.now() + ttlMs;
238
+ this.store.db.prepare("INSERT INTO authorization_intents(challenge_id,intent_hash,command,parameters,expires_at) VALUES(?,?,?,?,?)")
239
+ .run(challengeId, intentHash, command, serialized, expiresAt);
240
+ return { challengeId, intentHash, role: this.profile.role, command, summary: `${this.profile.role} authorizes ${command}`, parameters, expiresAt };
241
+ }
242
+ approve(challengeId) {
243
+ const row = this.store.db.prepare("SELECT intent_hash,expires_at,approved,used FROM authorization_intents WHERE challenge_id=?").get(challengeId);
244
+ if (!row)
245
+ return fail("AUTHORIZATION_NOT_FOUND", "authorization challenge was not found");
246
+ if (row.used)
247
+ fail("AUTHORIZATION_REPLAY", "authorization challenge was already consumed");
248
+ if (Date.now() > row.expires_at)
249
+ fail("AUTHORIZATION_EXPIRED", "authorization challenge has expired");
250
+ this.store.db.prepare("UPDATE authorization_intents SET approved=1 WHERE challenge_id=?").run(challengeId);
251
+ const payload = Buffer.from(canonical({ challengeId, intentHash: row.intent_hash, expiresAt: row.expires_at })).toString("base64url");
252
+ const signature = createHmac("sha256", this.#authorizationKey).update(payload).digest("base64url");
253
+ return { token: `${payload}.${signature}`, expiresAt: row.expires_at, intentHash: row.intent_hash };
254
+ }
255
+ validateApproval(command, args) {
256
+ const normalized = this.normalizedIntent(command, args), operationId = typeof args["operation-id"] === "string" ? args["operation-id"] : undefined;
257
+ const authorizationOperationId = operationId && (command === "settlement.plan" || command === "settlement.approve-review") ? `${command}:${operationId}` : operationId;
258
+ if (authorizationOperationId) {
259
+ const durable = this.store.db.prepare("SELECT intent_hash FROM authorized_operations WHERE operation_id=?").get(authorizationOperationId);
260
+ if (durable) {
261
+ if (durable.intent_hash !== normalized.intentHash)
262
+ fail("OPERATION_ID_CONFLICT", "operation ID already identifies another authorized intent");
263
+ return;
264
+ }
265
+ }
266
+ const encoded = args["approval-token"];
267
+ if (typeof encoded !== "string")
268
+ return fail("LOCAL_AUTH_REQUIRED", `${command} requires a bound approval token`);
269
+ const pieces = encoded.split(".");
270
+ if (pieces.length !== 2)
271
+ fail("AUTHORIZATION_INVALID", "approval token has an invalid shape");
272
+ const [payload, provided] = pieces;
273
+ const wanted = createHmac("sha256", this.#authorizationKey).update(payload).digest();
274
+ let supplied;
275
+ try {
276
+ supplied = Buffer.from(provided, "base64url");
277
+ }
278
+ catch {
279
+ return fail("AUTHORIZATION_INVALID", "approval token signature is invalid");
280
+ }
281
+ if (supplied.length !== wanted.length || !timingSafeEqual(supplied, wanted))
282
+ fail("AUTHORIZATION_INVALID", "approval token signature is invalid");
283
+ let token;
284
+ try {
285
+ token = JSON.parse(Buffer.from(payload, "base64url").toString());
286
+ }
287
+ catch (error) {
288
+ return fail("AUTHORIZATION_INVALID", "approval token payload is invalid", error);
289
+ }
290
+ if (token.intentHash !== normalized.intentHash || Date.now() > token.expiresAt)
291
+ fail(token.intentHash === normalized.intentHash ? "AUTHORIZATION_EXPIRED" : "AUTHORIZATION_INTENT_MISMATCH", "approval token does not authorize this exact request");
292
+ this.store.db.exec("BEGIN IMMEDIATE");
293
+ try {
294
+ const row = this.store.db.prepare("SELECT intent_hash,command,expires_at,approved,used FROM authorization_intents WHERE challenge_id=?").get(token.challengeId);
295
+ if (!row || !row.approved || row.command !== command || row.intent_hash !== token.intentHash)
296
+ return fail("AUTHORIZATION_INVALID", "approval token is not backed by an approved intent");
297
+ if (row.used && !operationId)
298
+ fail("AUTHORIZATION_REPLAY", "approval token was already consumed");
299
+ if (Date.now() > row.expires_at)
300
+ fail("AUTHORIZATION_EXPIRED", "approval token has expired");
301
+ this.store.db.prepare("UPDATE authorization_intents SET used=1 WHERE challenge_id=?").run(token.challengeId);
302
+ if (authorizationOperationId)
303
+ this.store.db.prepare("INSERT INTO authorized_operations(operation_id,intent_hash,created_at) VALUES(?,?,?)").run(authorizationOperationId, normalized.intentHash, Date.now());
304
+ this.store.db.exec("COMMIT");
305
+ }
306
+ catch (error) {
307
+ this.store.db.exec("ROLLBACK");
308
+ throw error;
309
+ }
310
+ }
311
+ allow(command, args) {
312
+ if (!ROLE_COMMANDS[this.profile.role].has(command))
313
+ fail("ROLE_FORBIDDEN", `${this.profile.role} cannot invoke ${command}`);
314
+ if (WRITE.has(command) && args.authorize !== true)
315
+ this.validateApproval(command, args);
316
+ }
317
+ paymentPolicy(args, rail) {
318
+ const value = amount(args), counterparty = required(args, "counterparty"), asset = typeof args.asset === "string" ? args.asset : this.profile.coinType;
319
+ if (Date.now() > this.profile.policy.expiresAt)
320
+ fail("POLICY_EXPIRED", "payment policy has expired");
321
+ if (value > BigInt(this.profile.policy.maxAmount))
322
+ fail("POLICY_AMOUNT_EXCEEDED", "payment exceeds the configured cap");
323
+ if (!this.profile.policy.allowedAssets.includes(asset))
324
+ fail("POLICY_ASSET_FORBIDDEN", "asset is not allowed by policy");
325
+ if (!this.profile.policy.allowedCounterparties.includes(counterparty))
326
+ fail("POLICY_COUNTERPARTY_FORBIDDEN", "counterparty is not allowed by policy");
327
+ if (this.profile.policy.requiredPrivacy === "shielded" && rail !== "shielded")
328
+ fail("POLICY_PRIVACY_REQUIRED", "policy requires shielded settlement");
329
+ return { amount: value, counterparty, asset };
330
+ }
331
+ doctor() {
332
+ if (this.profile.packageId !== PACKAGE || this.profile.poolId !== POOL || this.profile.coinType !== COIN || this.profile.verifierVersion !== 1)
333
+ fail("DEPLOYMENT_ID_MISMATCH", "profile deployment identifiers differ from the pinned Phase 1 deployment; signing and proving are disabled");
334
+ const actual = this.profile.artifactManifest.startsWith("mock:") ? hash(this.profile.artifactManifest) : hash(readFileSync(this.profile.artifactManifest));
335
+ if (actual !== this.profile.artifactManifestSha256)
336
+ fail("ARTIFACT_HASH_MISMATCH", "artifact manifest differs from the pinned hash; signing and proving are disabled");
337
+ return { role: this.profile.role, network: this.profile.network, packageId: this.profile.packageId, poolId: this.profile.poolId, asset: this.profile.coinType, verifierVersion: this.profile.verifierVersion, artifactManifestSha256: actual, keyPermissions: "owner-only", rpc: "mock-not-funded", healthy: true };
338
+ }
339
+ planningMandate(args) {
340
+ let raw;
341
+ if (typeof args["mandate-json"] === "string") {
342
+ try {
343
+ raw = JSON.parse(Buffer.from(args["mandate-json"], "base64url").toString());
344
+ }
345
+ catch (error) {
346
+ return fail("MANDATE_INVALID", "encoded planning mandate is invalid", error);
347
+ }
348
+ }
349
+ else {
350
+ try {
351
+ raw = JSON.parse(readFileSync(join(this.home, "mandate.json"), "utf8"));
352
+ }
353
+ catch (error) {
354
+ return fail("MANDATE_INVALID", "owner mandate could not be loaded", error);
355
+ }
356
+ }
357
+ const mandate = raw;
358
+ if (!mandate || !["supercuts.mandate.v1", "supercuts.mandate.v2"].includes(mandate.version) || !Array.isArray(mandate.allowedCounterparties) || !Array.isArray(mandate.allowedAssets) || !/^(?:0|[1-9][0-9]*)$/.test(mandate.perDealCeiling) || !Number.isSafeInteger(mandate.expiresAt) || mandate.version === "supercuts.mandate.v2" && mandate.allowPublicX402 !== undefined && typeof mandate.allowPublicX402 !== "boolean")
359
+ fail("MANDATE_INVALID", "planning mandate has an invalid shape");
360
+ return mandate;
361
+ }
362
+ storedPlan(planHash) {
363
+ const row = this.store.db.prepare("SELECT plan_json FROM settlement_plans WHERE plan_hash=?").get(planHash);
364
+ if (!row)
365
+ return fail("SETTLEMENT_PLAN_NOT_FOUND", "settlement plan hash was not found in this role home");
366
+ let plan;
367
+ try {
368
+ plan = JSON.parse(row.plan_json);
369
+ }
370
+ catch (error) {
371
+ return fail("SETTLEMENT_PLAN_CORRUPT", "stored settlement plan is invalid JSON", error);
372
+ }
373
+ validateSettlementPlan(plan);
374
+ if (plan.planHash !== planHash)
375
+ fail("SETTLEMENT_PLAN_HASH_MISMATCH", "requested hash does not resolve to the identical stored plan");
376
+ return plan;
377
+ }
378
+ persistPlan(plan) {
379
+ validateSettlementPlan(plan);
380
+ const serialized = canonicalJson(plan);
381
+ const existing = this.store.db.prepare("SELECT plan_hash,plan_json FROM settlement_plans WHERE operation_id=?").get(plan.operationId);
382
+ if (existing) {
383
+ if (existing.plan_hash !== plan.planHash || existing.plan_json !== serialized)
384
+ fail("OPERATION_ID_CONFLICT", "planning operation already identifies another immutable plan");
385
+ return this.storedPlan(existing.plan_hash);
386
+ }
387
+ this.store.db.exec("BEGIN IMMEDIATE");
388
+ try {
389
+ this.store.db.prepare("INSERT INTO settlement_plans(plan_hash,operation_id,plan_json,status,expires_at,created_at) VALUES(?,?,?,?,?,?)").run(plan.planHash, plan.operationId, serialized, plan.status, plan.expiresAt, plan.createdAt);
390
+ this.store.db.prepare("INSERT INTO settlement_decision_traces(plan_hash,mandate_hash,manifest_hash,risk_level,reason_codes,citations,created_at) VALUES(?,?,?,?,?,?,?)").run(plan.planHash, plan.mandateHash, plan.manifestHash, plan.riskLevel, JSON.stringify(plan.reasonCodes), JSON.stringify(plan.citations.map(({ blobId, distance }) => ({ blobId, distance }))), plan.createdAt);
391
+ this.store.db.exec("COMMIT");
392
+ }
393
+ catch (error) {
394
+ this.store.db.exec("ROLLBACK");
395
+ throw error;
396
+ }
397
+ return plan;
398
+ }
399
+ recalledMockMemories() {
400
+ return [...all(this.store, "memory"), ...all(this.store, "verified-memory")].map((item) => {
401
+ if (item.memory && typeof item.memory === "object")
402
+ return { blobId: String(item.blobId), distance: 0, memory: this.authenticatedMemory(String(item.blobId), item.memory) };
403
+ return { blobId: String(item.blobId), distance: 0, legacy: true };
404
+ });
405
+ }
406
+ authenticatedMemory(blobId, memory) {
407
+ if (memory.provenance !== "owner_policy" && memory.provenance !== "verified_outcome")
408
+ return memory;
409
+ const authority = this.store.db.prepare("SELECT fact_hash,provenance,evidence_hash FROM memory_authority WHERE blob_id=?").get(blobId);
410
+ if (authority?.fact_hash === hash(canonicalJson(memory)) && authority.provenance === memory.provenance && (memory.provenance !== "verified_outcome" || authority.evidence_hash === memory.evidenceHash))
411
+ return memory;
412
+ return { ...memory, provenance: "legacy_untrusted" };
413
+ }
414
+ registerMemoryAuthority(blobId, memory) {
415
+ if (memory.provenance !== "owner_policy" && memory.provenance !== "verified_outcome")
416
+ return;
417
+ this.store.db.prepare("INSERT INTO memory_authority(blob_id,fact_hash,provenance,evidence_hash,created_at) VALUES(?,?,?,?,?) ON CONFLICT(blob_id) DO NOTHING")
418
+ .run(blobId, hash(canonicalJson(memory)), memory.provenance, memory.evidenceHash ?? null, Date.now());
419
+ }
420
+ async planSettlement(args) {
421
+ let primary, alternatives = [];
422
+ const parseArgument = (value) => { try {
423
+ return JSON.parse(Buffer.from(value, "base64url").toString());
424
+ }
425
+ catch {
426
+ return JSON.parse(value);
427
+ } };
428
+ try {
429
+ primary = parseArgument(required(args, "manifest"));
430
+ }
431
+ catch (error) {
432
+ return fail("CAPABILITY_INVALID", "primary manifest is not JSON", error);
433
+ }
434
+ validateCapabilityManifest(primary);
435
+ if (typeof args.alternatives === "string") {
436
+ try {
437
+ alternatives = parseArgument(args.alternatives);
438
+ }
439
+ catch (error) {
440
+ return fail("CAPABILITY_LIST_INVALID", "alternative manifests are not JSON", error);
441
+ }
442
+ }
443
+ if (!Array.isArray(alternatives))
444
+ fail("CAPABILITY_LIST_INVALID", "alternatives must be an array");
445
+ for (const item of alternatives)
446
+ validateCapabilityManifest(item);
447
+ const operationId = required(args, "operation-id"), mandate = this.planningMandate(args);
448
+ const persisted = this.store.db.prepare("SELECT plan_hash FROM settlement_plans WHERE operation_id=?").get(operationId);
449
+ if (persisted) {
450
+ const existing = this.storedPlan(persisted.plan_hash), manifests = [primary, ...alternatives];
451
+ const requestedAmount = typeof args["requested-amount"] === "string" ? BigInt(args["requested-amount"]) : BigInt(existing.manifestMinimum);
452
+ const boundedAmount = requestedAmount < BigInt(existing.manifestMinimum) ? BigInt(existing.manifestMinimum) : requestedAmount > BigInt(existing.maximumAmount) ? BigInt(existing.maximumAmount) : requestedAmount;
453
+ const requestedRail = args["requested-rail"] === "x402" || args["requested-rail"] === "shielded" ? args["requested-rail"] : undefined;
454
+ if (existing.mandateHash !== settlementMandateHash(mandate) || !manifests.some((manifest) => manifest.manifestHash === existing.manifestHash) || requestedRail && requestedRail !== existing.rail || args["privacy-hint"] === "shielded" && existing.rail !== "shielded" || boundedAmount.toString() !== existing.openingAmount) {
455
+ fail("OPERATION_ID_CONFLICT", "planning operation already identifies another immutable plan");
456
+ }
457
+ return existing;
458
+ }
459
+ let recalled = this.recalledMockMemories();
460
+ if (!this.profile.mock && this.store.getSecret("live:memory-config")) {
461
+ const encoded = this.store.getSecret("live:memory-config");
462
+ let config;
463
+ try {
464
+ config = JSON.parse(encoded.toString());
465
+ }
466
+ catch (error) {
467
+ return fail("LIVE_MEMORY_CONFIG_INVALID", "encrypted memory config is invalid", error);
468
+ }
469
+ const manual = new ManualMemory(this.store, config);
470
+ try {
471
+ recalled = (await manual.recall(`${primary.capabilityId} ${primary.capability} ${primary.sellerId}`, 20)).map((item) => { let memory; try {
472
+ const value = JSON.parse(item.text);
473
+ validateSettlementMemory(value);
474
+ memory = this.authenticatedMemory(item.blobId, value);
475
+ }
476
+ catch { /* malformed or legacy memory remains advisory */ } return { blobId: item.blobId, distance: item.distance, ...(memory ? { memory } : { legacy: true }) }; });
477
+ }
478
+ finally {
479
+ manual.destroy();
480
+ }
481
+ }
482
+ const plan = createSettlementPlan({ operationId, mandate, manifests: [primary, ...alternatives], ...(typeof args["requested-amount"] === "string" ? { requestedAmount: args["requested-amount"] } : {}), ...(args["privacy-hint"] === "shielded" ? { privacyHint: "shielded" } : {}), ...(args["requested-rail"] === "x402" || args["requested-rail"] === "shielded" ? { requestedRail: args["requested-rail"] } : {}), recalled, maxMemoryDistance: this.profile.policy.maxMemoryDistance ?? 0.35 });
483
+ return this.persistPlan(plan);
484
+ }
485
+ typedMemory(args, provenance) {
486
+ const encoded = required(args, "memory");
487
+ let supplied;
488
+ try {
489
+ supplied = JSON.parse(Buffer.from(encoded, "base64url").toString());
490
+ }
491
+ catch {
492
+ try {
493
+ supplied = JSON.parse(encoded);
494
+ }
495
+ catch (error) {
496
+ return fail("SETTLEMENT_MEMORY_INVALID", "typed memory is not JSON", error);
497
+ }
498
+ }
499
+ const memory = { ...supplied, version: "supercuts.settlement-memory.v1", provenance };
500
+ validateSettlementMemory(memory, provenance);
501
+ return memory;
502
+ }
503
+ async rememberTyped(args, provenance) {
504
+ const operationId = required(args, "operation-id"), memory = this.typedMemory(args, provenance);
505
+ if (this.profile.mock) {
506
+ const blobId = `mockwalrus_${hash(canonicalJson({ operationId, memory })).slice(0, 40)}`, item = { operationId, namespace: `supercuts:${this.profile.role}`, blobId, memory, status: "complete" };
507
+ const existing = get(this.store, "memory", operationId);
508
+ if (existing && canonicalJson(existing.memory) !== canonicalJson(memory))
509
+ fail("MEMORY_OPERATION_CONFLICT", "memory operation ID identifies another envelope");
510
+ record(this.store, "memory", operationId, item);
511
+ this.registerMemoryAuthority(blobId, memory);
512
+ return { operationId, namespace: item.namespace, blobId, status: "complete", kind: memory.kind, provenance };
513
+ }
514
+ const encoded = this.store.getSecret("live:memory-config") ?? fail("LIVE_MEMORY_CONFIG_MISSING", "live Mainnet memory requires an owner-only --memory-config-file");
515
+ let config;
516
+ try {
517
+ config = JSON.parse(encoded.toString());
518
+ }
519
+ catch (error) {
520
+ return fail("LIVE_MEMORY_CONFIG_INVALID", "encrypted memory config is invalid", error);
521
+ }
522
+ const manual = new ManualMemory(this.store, config);
523
+ try {
524
+ const factHash = hash(canonicalJson(memory)), existing = this.store.db.prepare("SELECT fact_hash,blob_id,status FROM memory_jobs WHERE operation_id=?").get(operationId);
525
+ if (existing) {
526
+ if (existing.fact_hash !== factHash)
527
+ fail("MEMORY_OPERATION_CONFLICT", "memory operation ID identifies another envelope");
528
+ if (existing.status === "complete" && existing.blob_id)
529
+ return { operationId, namespace: config.namespace, blobId: existing.blob_id, status: "complete", kind: memory.kind, provenance };
530
+ return fail("MEMORY_JOB_PENDING", "memory job is not complete");
531
+ }
532
+ this.store.db.prepare("INSERT INTO memory_jobs(operation_id,kind,fact_hash,blob_id,status,created_at) VALUES(?,?,?,NULL,'pending',?)").run(operationId, memory.kind, factHash, Date.now());
533
+ try {
534
+ const blobId = await manual.remember(memory);
535
+ this.store.db.prepare("UPDATE memory_jobs SET blob_id=?,status='complete' WHERE operation_id=?").run(blobId, operationId);
536
+ this.registerMemoryAuthority(blobId, memory);
537
+ return { operationId, namespace: config.namespace, blobId, status: "complete", kind: memory.kind, provenance };
538
+ }
539
+ catch (error) {
540
+ this.store.db.prepare("UPDATE memory_jobs SET status='unresolved' WHERE operation_id=?").run(operationId);
541
+ throw error;
542
+ }
543
+ }
544
+ finally {
545
+ manual.destroy();
546
+ }
547
+ }
548
+ enqueueVerifiedOutcome(event, operationId, evidence, planHash, fallback) {
549
+ const plan = planHash ? this.storedPlan(planHash) : undefined, jobId = `verified:${event}:${operationId}`, evidenceHash = hash(canonicalJson(evidence));
550
+ const prior = this.store.getSecret(`memory-outbox:${jobId}`);
551
+ if (prior) {
552
+ const saved = JSON.parse(prior.toString());
553
+ validateSettlementMemory(saved, "verified_outcome");
554
+ if (saved.evidenceHash !== evidenceHash || plan && (saved.scope.capabilityId !== plan.capabilityId || saved.scope.counterparty !== plan.sellerId || saved.scope.asset !== plan.asset))
555
+ fail("MEMORY_OPERATION_CONFLICT", "verified outcome ID identifies different evidence or scope");
556
+ return;
557
+ }
558
+ const memory = { version: "supercuts.settlement-memory.v1", kind: "outcome", provenance: "verified_outcome", scope: { ...(plan ? { capabilityId: plan.capabilityId, capabilityClass: plan.capabilityClass, counterparty: plan.sellerId, network: plan.network, asset: plan.asset } : { ...(fallback?.counterparty ? { counterparty: fallback.counterparty } : {}), ...(fallback?.asset ? { asset: fallback.asset } : {}), network: "sui:testnet" }) }, createdAt: Date.now(), evidenceHash, signal: { outcome: String(evidence.status ?? "") === "unresolved" ? "unresolved" : String(evidence.status ?? "") === "failed" ? "failed" : "succeeded" } };
559
+ validateSettlementMemory(memory, "verified_outcome");
560
+ const factHash = hash(canonicalJson(memory));
561
+ if (this.profile.mock) {
562
+ const blobId = `mockwalrus_${factHash.slice(0, 40)}`;
563
+ record(this.store, "verified-memory", jobId, { operationId: jobId, namespace: `supercuts:${this.profile.role}`, blobId, memory, status: "complete" });
564
+ this.registerMemoryAuthority(blobId, memory);
565
+ return;
566
+ }
567
+ this.store.putSecretOnce(`memory-outbox:${jobId}`, Buffer.from(canonicalJson(memory)));
568
+ this.store.db.prepare("INSERT OR IGNORE INTO memory_jobs(operation_id,kind,fact_hash,blob_id,status,created_at) VALUES(?,?,?,NULL,'pending',?)").run(jobId, memory.kind, factHash, Date.now());
569
+ }
570
+ recordVerifiedDealOutcome(operationId, planHash, paymentDigest, artifactSha256) {
571
+ if (this.profile.mock || this.profile.role !== "buyer")
572
+ fail("DEAL_LIVE_REQUIRED", "verified deal outcome requires a live buyer role");
573
+ const jobId = `verified:deal:${operationId}`;
574
+ this.enqueueVerifiedOutcome("deal", operationId, { status: "succeeded", paymentDigest, artifactSha256 }, planHash);
575
+ const payload = this.store.getSecret(`memory-outbox:${jobId}`) ?? fail("DEAL_MEMORY_INVALID", "verified outcome payload is missing");
576
+ return { payloadHash: hash(payload), queued: true };
577
+ }
578
+ recoverStaleMemorySubmissions() {
579
+ if (this.profile.mock || this.profile.role !== "buyer")
580
+ return;
581
+ this.store.db.prepare("UPDATE memory_jobs SET status='unresolved' WHERE status='submitting' AND (claimed_at IS NULL OR claimed_at<?)").run(Date.now() - 300_000);
582
+ this.syncDealOutcomeJobs();
583
+ }
584
+ syncDealOutcomeJobs() {
585
+ if (this.profile.mock || this.profile.role !== "buyer")
586
+ return;
587
+ const journal = new DealJournal(this.store);
588
+ const rows = this.store.db.prepare("SELECT d.operation_id,d.payload_hash,d.blob_id,j.blob_id AS job_blob_id,j.status AS job_status,j.created_at FROM deal_memory_outbox d JOIN memory_jobs j ON j.operation_id='verified:deal:' || d.operation_id WHERE d.event_kind='outcome' AND d.status!='indexed'").all();
589
+ for (const row of rows) {
590
+ const view = journal.get(row.operation_id);
591
+ if (view.evidence.payment !== "finalized" || view.evidence.delivery !== "verified")
592
+ fail("DEAL_OUTCOME_INVARIANT", "outcome job lacks verified payment or delivery");
593
+ if (row.job_status === "unresolved") {
594
+ journal.setOutcomeUnresolved(row.operation_id);
595
+ continue;
596
+ }
597
+ if (row.job_status !== "complete" || !row.job_blob_id)
598
+ continue;
599
+ if (row.blob_id && row.blob_id !== row.job_blob_id)
600
+ fail("DEAL_MEMORY_CONFLICT", "outcome memory blob differs from the job");
601
+ const encoded = this.store.getSecret("live:memory-config") ?? fail("LIVE_MEMORY_CONFIG_MISSING", "buyer memory configuration is missing");
602
+ const namespace = JSON.parse(encoded.toString()).namespace;
603
+ const inventory = this.store.db.prepare("SELECT status,deleted,expires_at FROM memory_inventory WHERE blob_id=? AND namespace=?").get(row.job_blob_id, namespace);
604
+ const indexed = Boolean(inventory && !inventory.deleted && inventory.status === "active" && (!inventory.expires_at || Date.parse(inventory.expires_at) > Date.now()));
605
+ journal.recordOutcome(row.operation_id, row.payload_hash, row.job_blob_id, indexed);
606
+ if (indexed) {
607
+ journal.recordTiming(row.operation_id, "memory_index", Date.now() - row.created_at);
608
+ if (journal.get(row.operation_id).state === "outcome_pending")
609
+ journal.advance(row.operation_id, "outcome_pending", "complete");
610
+ }
611
+ }
612
+ }
613
+ async processMemoryOutbox() {
614
+ if (this.profile.mock || this.profile.role !== "buyer")
615
+ return;
616
+ this.recoverStaleMemorySubmissions();
617
+ await this.execute("memory.status", {});
618
+ await this.execute("memory.inventory", {});
619
+ this.syncDealOutcomeJobs();
620
+ }
621
+ async reconcileDealOutcome(operationId) {
622
+ if (this.profile.mock || this.profile.role !== "buyer")
623
+ fail("DEAL_LIVE_REQUIRED", "deal memory reconciliation requires a live buyer role");
624
+ const journal = new DealJournal(this.store), view = journal.get(operationId);
625
+ if (view.evidence.payment !== "finalized" || view.evidence.delivery !== "verified")
626
+ fail("DEAL_DELIVERY_UNVERIFIED", "deal must have verified payment and delivery");
627
+ if (view.state === "complete")
628
+ return { resolved: true, state: "complete", ...(journal.outcomeStatus(operationId)?.blobId ? { blobId: journal.outcomeStatus(operationId).blobId } : {}) };
629
+ const jobId = `verified:deal:${operationId}`;
630
+ const row = this.store.db.prepare("SELECT fact_hash,status FROM memory_jobs WHERE operation_id=?").get(jobId) ?? fail("DEAL_MEMORY_INVALID", "verified outcome job is missing");
631
+ const encoded = this.store.getSecret(`memory-outbox:${jobId}`) ?? fail("DEAL_MEMORY_INVALID", "verified outcome payload is missing");
632
+ const memory = JSON.parse(encoded.toString());
633
+ validateSettlementMemory(memory, "verified_outcome");
634
+ if (row.fact_hash !== hash(canonicalJson(memory)))
635
+ fail("DEAL_MEMORY_CONFLICT", "outcome job differs from its saved payload");
636
+ journal.queueOutcome(operationId, hash(encoded));
637
+ this.syncDealOutcomeJobs();
638
+ if (row.status !== "unresolved")
639
+ return { resolved: false, state: row.status };
640
+ const configBytes = this.store.getSecret("live:memory-config") ?? fail("LIVE_MEMORY_CONFIG_MISSING", "buyer memory configuration is missing");
641
+ const config = JSON.parse(configBytes.toString());
642
+ const manual = new ManualMemory(this.store, config);
643
+ let blobId;
644
+ try {
645
+ const query = [memory.scope.capabilityId, memory.scope.counterparty, memory.signal.outcome].filter(Boolean).join(" ");
646
+ const recalled = await manual.recall(query, 100);
647
+ blobId = recalled.find((item) => { try {
648
+ return canonicalJson(JSON.parse(item.text)) === canonicalJson(memory);
649
+ }
650
+ catch {
651
+ return false;
652
+ } })?.blobId;
653
+ if (blobId)
654
+ await manual.listMetadata(config);
655
+ }
656
+ finally {
657
+ manual.destroy();
658
+ }
659
+ if (!blobId)
660
+ return { resolved: false, state: "unresolved" };
661
+ this.store.db.prepare("UPDATE memory_jobs SET blob_id=?,status='complete' WHERE operation_id=? AND status='unresolved'").run(blobId, jobId);
662
+ this.registerMemoryAuthority(blobId, memory);
663
+ const outcome = journal.outcomeStatus(operationId) ?? fail("DEAL_MEMORY_INVALID", "deal outcome journal is missing");
664
+ if (outcome.payloadHash !== hash(encoded))
665
+ fail("DEAL_MEMORY_CONFLICT", "deal outcome differs from its saved payload");
666
+ journal.recordOutcome(operationId, outcome.payloadHash, blobId, false);
667
+ this.syncDealOutcomeJobs();
668
+ return { resolved: journal.get(operationId).state === "complete", state: journal.get(operationId).state, blobId };
669
+ }
670
+ approveReview(args) {
671
+ const original = this.storedPlan(required(args, "plan-hash"));
672
+ if (original.status !== "review_required")
673
+ fail("SETTLEMENT_REVIEW_NOT_REQUIRED", "only a review-required plan can be approved");
674
+ if (Date.now() >= original.expiresAt)
675
+ fail("SETTLEMENT_PLAN_EXPIRED", "review-required plan has expired");
676
+ const operationId = required(args, "operation-id"), createdAt = Date.now();
677
+ const copy = { ...original, operationId, status: "approved", riskLevel: "medium", createdAt, expiresAt: Math.min(createdAt + 300_000, original.expiresAt), reasonCodes: [...new Set([...original.reasonCodes, "OWNER_REVIEW_APPROVED"])], approvedFromPlanHash: original.planHash };
678
+ delete copy.planHash;
679
+ return this.persistPlan({ ...copy, planHash: settlementPlanHash(copy) });
680
+ }
681
+ bindAcceptedAgreement(args, accepted) {
682
+ if (typeof args["plan-hash"] !== "string")
683
+ return;
684
+ const plan = this.storedPlan(args["plan-hash"]), mandate = this.planningMandate(args), session = String(accepted.sessionId), amount = String(accepted.paymentAmount), counterparty = String(accepted.counterparty);
685
+ if (plan.status !== "approved" || plan.rail !== "shielded" || Date.now() >= plan.expiresAt || plan.mandateHash !== settlementMandateHash(mandate))
686
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "accepted agreement is not authorized by an active shielded plan");
687
+ if (counterparty !== plan.sellerId || BigInt(amount) < BigInt(plan.manifestMinimum) || BigInt(amount) > BigInt(plan.maximumAmount) || accepted.capabilityId !== plan.capabilityId || accepted.manifestHash !== plan.manifestHash)
688
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "accepted agreement differs from the planned capability, seller, or amount bounds");
689
+ const bindingHash = hash(canonicalJson({ domain: "supercuts-settlement-agreement-v1", session, planHash: plan.planHash, amount, counterparty, capabilityId: accepted.capabilityId ?? plan.capabilityId, manifestHash: accepted.manifestHash ?? plan.manifestHash }));
690
+ const existing = this.store.db.prepare("SELECT plan_hash,binding_hash FROM settlement_agreement_bindings WHERE session_id=? OR plan_hash=?").get(session, plan.planHash);
691
+ if (existing && (existing.plan_hash !== plan.planHash || existing.binding_hash !== bindingHash))
692
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "session or plan is already bound to another agreement");
693
+ if (!existing)
694
+ this.store.db.prepare("INSERT INTO settlement_agreement_bindings(session_id,plan_hash,binding_hash,created_at) VALUES(?,?,?,?)").run(session, plan.planHash, bindingHash, Date.now());
695
+ }
696
+ bindSettlementPlan(command, args, actual) {
697
+ const mandate = this.planningMandate(args), supplied = typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined;
698
+ if (!supplied) {
699
+ if (mandate.requireSettlementPlan)
700
+ fail("SETTLEMENT_PLAN_REQUIRED", "owner mandate requires an approved settlement plan");
701
+ return undefined;
702
+ }
703
+ const plan = this.storedPlan(supplied), rail = command === "pay.x402" ? "x402" : "shielded", now = Date.now();
704
+ if (plan.status !== "approved" || plan.riskLevel === "review_required")
705
+ fail("SETTLEMENT_PLAN_REVIEW_REQUIRED", "settlement plan requires owner review");
706
+ if (plan.mandateHash !== settlementMandateHash(mandate))
707
+ fail("SETTLEMENT_PLAN_MANDATE_MISMATCH", "owner mandate changed after planning");
708
+ const operationId = required(args, "operation-id");
709
+ if (plan.operationId !== operationId || plan.rail !== rail || plan.sellerId !== actual.counterparty || plan.asset !== actual.asset || plan.network !== "sui:testnet" || actual.endpoint && !matchesPlannedEndpoint(plan.endpoint, actual.endpoint, actual.requestId))
710
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "execution differs from the planned operation, capability, seller, endpoint, asset, network, or rail");
711
+ if (rail === "shielded" && actual.session) {
712
+ const agreement = this.store.db.prepare("SELECT plan_hash FROM settlement_agreement_bindings WHERE session_id=?").get(actual.session);
713
+ if (agreement && agreement.plan_hash !== plan.planHash)
714
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "shielded agreement is bound to another plan");
715
+ if (!agreement && !this.profile.mock)
716
+ fail("SETTLEMENT_PLAN_BINDING_MISMATCH", "shielded payment has no accepted agreement bound to this plan");
717
+ }
718
+ const value = BigInt(actual.amount);
719
+ if (value < BigInt(plan.manifestMinimum) || value > BigInt(plan.maximumAmount))
720
+ fail("SETTLEMENT_PLAN_AMOUNT_EXCEEDED", "execution amount is outside the planned manifest bounds");
721
+ const binding = { planHash: plan.planHash, operationId, capabilityId: plan.capabilityId, manifestHash: plan.manifestHash, rail, amount: actual.amount, counterparty: actual.counterparty, asset: actual.asset, endpoint: actual.endpoint ?? null, requestId: actual.requestId, session: actual.session ?? null, requirementsHash: actual.requirementsHash ?? null };
722
+ const bindingHash = hash(canonicalJson({ domain: "supercuts-settlement-execution-v1", binding })), existing = this.store.db.prepare("SELECT binding_hash FROM settlement_execution_bindings WHERE operation_id=? OR plan_hash=?").get(operationId, plan.planHash);
723
+ if (existing && existing.binding_hash !== bindingHash)
724
+ fail("SETTLEMENT_PLAN_CONSUMED", "plan or operation is already bound to another execution");
725
+ if (!existing && now >= plan.expiresAt)
726
+ fail("SETTLEMENT_PLAN_EXPIRED", "settlement plan has expired");
727
+ if (!existing) {
728
+ this.store.db.prepare("INSERT INTO settlement_execution_bindings(operation_id,plan_hash,binding_hash,binding_json,created_at) VALUES(?,?,?,?,?)").run(operationId, plan.planHash, bindingHash, canonicalJson(binding), now);
729
+ this.store.db.prepare("UPDATE settlement_plans SET consumed_at=? WHERE plan_hash=?").run(now, plan.planHash);
730
+ }
731
+ return plan;
732
+ }
733
+ async executeLive(command, args) {
734
+ if (!this.profile.live)
735
+ fail("LIVE_PROFILE_INVALID", "live profile configuration is missing");
736
+ if (command === "settlement.plan")
737
+ return output(command, await this.planSettlement(args));
738
+ if (command === "settlement.approve-review")
739
+ return output(command, this.approveReview(args));
740
+ if (command === "memory.policy")
741
+ return output(command, await this.rememberTyped(args, "owner_policy"));
742
+ if (command === "memory.inventory") {
743
+ const encoded = this.store.getSecret("live:memory-config") ?? fail("LIVE_MEMORY_CONFIG_MISSING", "live Mainnet memory requires an owner-only memory configuration");
744
+ const config = JSON.parse(encoded.toString());
745
+ const manual = new ManualMemory(this.store, config);
746
+ try {
747
+ const synced = await manual.listMetadata(config);
748
+ const blobId = typeof args["blob-id"] === "string" ? args["blob-id"] : undefined;
749
+ if (blobId && !/^[A-Za-z0-9_-]{1,256}$/.test(blobId))
750
+ fail("MEMORY_BLOB_ID_INVALID", "memory blob ID is invalid");
751
+ const row = blobId ? this.store.db.prepare("SELECT m.blob_id AS blobId,m.status AS status,m.expires_at AS expiresAt,m.deleted AS deleted FROM memory_inventory m JOIN memory_jobs j ON j.blob_id=m.blob_id WHERE m.blob_id=? AND m.namespace=?").get(blobId, config.namespace) : undefined;
752
+ const present = Boolean(row && !row.deleted && row.status === "active" && (!row.expiresAt || Date.parse(row.expiresAt) > Date.now()));
753
+ return output(command, { namespace: config.namespace, ...synced, ...(blobId ? { blobId, present, ...(row?.expiresAt ? { expiresAt: row.expiresAt } : {}) } : {}) });
754
+ }
755
+ finally {
756
+ manual.destroy();
757
+ }
758
+ }
759
+ if (command === "memory.remember" || command === "memory.recall" || command === "memory.status")
760
+ return output(command, await this.liveMemory(command, args));
761
+ const runtime = await LiveRuntime.create(this.profile, this.store);
762
+ if (command === "doctor")
763
+ return output(command, await runtime.doctor());
764
+ if (command === "status")
765
+ return output(command, { role: this.profile.role, network: this.profile.network, mock: false, runtime: "testnet-live" });
766
+ if (command === "capability.publish")
767
+ return output(command, await this.publishLiveCapability(args));
768
+ if (command === "capability.discover")
769
+ return output(command, await this.discoverLiveCapabilities(args));
770
+ if (command === "offer.open") {
771
+ const sessionId = typeof args.session === "string" ? args.session : `session_${randomUUID()}`;
772
+ const offer = { sessionId, revision: 0, state: "open", buyer: this.profile.role, transcriptCommitment: hash(`open:${sessionId}`) };
773
+ record(this.store, "offer", sessionId, offer);
774
+ return output(command, offer);
775
+ }
776
+ if (command === "offer.propose")
777
+ return output(command, this.localOffer(command, args));
778
+ if (command === "offer.show")
779
+ return output(command, get(this.store, "offer", required(args, "session")) ?? fail("OFFER_NOT_FOUND", "offer session was not found"));
780
+ if (command === "pay.quote") {
781
+ const rail = required(args, "rail");
782
+ if (!(rail === "shielded" || rail === "x402"))
783
+ fail("RAIL_INVALID", "rail must be shielded or x402");
784
+ const checked = this.paymentPolicy(args, rail);
785
+ return output(command, { rail, amount: checked.amount.toString(), counterparty: checked.counterparty, asset: checked.asset, policyDecision: "allowed", visibility: rail === "shielded" ? "no-direct-public-payment; funding-deposits-withdrawals-gas-and-pool-activity-visible" : "public-transfer" });
786
+ }
787
+ if (command === "wallet.balance")
788
+ return output(command, await runtime.balance());
789
+ if (command === "wallet.recover")
790
+ return output(command, await runtime.recover());
791
+ if (command === "wallet.deposit")
792
+ return output(command, await runtime.deposit(amount(args), required(args, "operation-id")));
793
+ if (command === "wallet.withdraw")
794
+ return output(command, await runtime.withdraw(required(args, "commitment"), required(args, "recipient"), required(args, "operation-id")));
795
+ if (command === "wallet.exit.queue") {
796
+ const queued = await runtime.queueV2Exit(required(args, "commitment"), required(args, "operation-id"), amount(args), args["exact-exit"] === true, Number(required(args, "window-start")), Number(required(args, "window-end")), true);
797
+ return output(command, { ...queued, amount: queued.amount.toString(), visibility: queued.exactExit ? "exact exit amount is public" : "standard 1,000,000 MIST exit; payout and pool activity are public" });
798
+ }
799
+ if (command === "wallet.exit.status") {
800
+ const queued = new V2ExitQueue(this.store).get(required(args, "operation-id")) ?? fail("EXIT_REQUEST_INVALID", "exit operation was not found");
801
+ return output(command, { ...queued, amount: queued.amount.toString() });
802
+ }
803
+ if (command === "wallet.exit.finalize")
804
+ return output(command, await runtime.finalizeV2Exit(required(args, "exit-id"), required(args, "digest"), V2_RELAYER));
805
+ if (command === "offer.accept") {
806
+ this.store.db.exec("BEGIN IMMEDIATE");
807
+ let accepted;
808
+ try {
809
+ accepted = typeof args.agreement === "string" ? runtime.acceptAgreement(required(args, "session"), args.agreement) : this.localOffer(command, args);
810
+ if (typeof args["plan-hash"] === "string")
811
+ this.bindAcceptedAgreement(args, accepted);
812
+ this.store.db.exec("COMMIT");
813
+ }
814
+ catch (error) {
815
+ this.store.db.exec("ROLLBACK");
816
+ throw error;
817
+ }
818
+ this.enqueueVerifiedOutcome("agreement", typeof args["operation-id"] === "string" ? args["operation-id"] : `${String(accepted.sessionId)}:${String(accepted.revision)}`, { status: "accepted", commitment: accepted.dealCommitment ?? accepted.transcriptCommitment }, typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined);
819
+ return output(command, accepted);
820
+ }
821
+ if (command === "offer.counter")
822
+ return output(command, typeof args.agreement === "string" ? runtime.prepareSellerAgreement(required(args, "session"), args.agreement) : this.localOffer(command, args));
823
+ if (command === "pay.shielded") {
824
+ const receipt = await runtime.settleShielded(required(args, "session"), required(args, "operation-id"), (actual) => { this.bindSettlementPlan(command, args, actual); });
825
+ this.enqueueVerifiedOutcome("settlement", required(args, "operation-id"), receipt, typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined, { counterparty: String(receipt.counterparty), asset: String(receipt.asset) });
826
+ return output(command, receipt);
827
+ }
828
+ if (command === "pay.x402") {
829
+ const receipt = await runtime.payX402Resource(required(args, "operation-id"), required(args, "resource-url"), (actual) => { this.bindSettlementPlan(command, args, actual); });
830
+ const { response: _response, ...evidence } = receipt;
831
+ this.enqueueVerifiedOutcome("settlement", required(args, "operation-id"), evidence, typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined, { counterparty: String(receipt.counterparty), asset: String(receipt.asset) });
832
+ return output(command, receipt);
833
+ }
834
+ if (command === "pay.status")
835
+ return output(command, await runtime.paymentStatus(required(args, "operation-id"), args.reconcile === true));
836
+ if (command === "resource.serve") {
837
+ const requestId = required(args, "request-id"), body = required(args, "body"), resource = { requestId, responseHash: hash(Buffer.from(body, "base64")), body, deliveryStatus: "ready" };
838
+ record(this.store, "resource", requestId, resource);
839
+ return output(command, { requestId, responseHash: resource.responseHash, deliveryStatus: resource.deliveryStatus });
840
+ }
841
+ if (command === "resource.fetch") {
842
+ const requestId = required(args, "request-id"), local = get(this.store, "resource", requestId);
843
+ if (local)
844
+ return output(command, { ...local, deliveryStatus: "delivered" });
845
+ const body = required(args, "response"), delivered = { requestId, responseHash: hash(Buffer.from(body, "base64")), body, deliveryStatus: "delivered", observation: "artifact-delivered-separate-from-payment" };
846
+ this.enqueueVerifiedOutcome("delivery", required(args, "operation-id"), { status: "delivered", requestId, responseHash: delivered.responseHash });
847
+ return output(command, delivered);
848
+ }
849
+ return fail("LIVE_COMMAND_UNAVAILABLE", `${command} is not connected to the Testnet runtime`);
850
+ }
851
+ publishCapability(args, mock) {
852
+ let manifest;
853
+ if (typeof args.manifest === "string") {
854
+ try {
855
+ manifest = JSON.parse(Buffer.from(args.manifest, "base64url").toString());
856
+ }
857
+ catch (error) {
858
+ return fail("CAPABILITY_INVALID", "manifest is not valid encoded JSON", error);
859
+ }
860
+ validateCapabilityManifest(manifest);
861
+ }
862
+ if (manifest) {
863
+ record(this.store, "capability", manifest.capabilityId, manifest);
864
+ return { ...manifest, blobId: `${mock ? "mockwalrus" : "walrus"}_${manifest.manifestHash.slice(0, 40)}`, visibility: "public-capability" };
865
+ }
866
+ const capabilityId = typeof args["capability-id"] === "string" ? args["capability-id"] : `cap_${randomUUID()}`;
867
+ const item = { capabilityId, seller: required(args, "seller"), service: required(args, "service"), terms: required(args, "terms"), blobId: `${mock ? "mockwalrus" : "walrus"}_${hash(`${capabilityId}:${String(args.seller)}:${String(args.service)}`).slice(0, 40)}`, visibility: "public-capability" };
868
+ record(this.store, "capability", capabilityId, item);
869
+ return item;
870
+ }
871
+ async publishLiveCapability(args) {
872
+ const publisher = this.profile.live?.walrusPublisherUrl ?? fail("LIVE_WALRUS_CONFIG_MISSING", "live capability publication requires --walrus-publisher"), aggregator = this.profile.live?.walrusAggregatorUrl ?? fail("LIVE_WALRUS_CONFIG_MISSING", "live capability publication requires --walrus-aggregator");
873
+ const encoded = typeof args.manifest === "string" ? args.manifest : fail("CAPABILITY_INVALID", "live publication requires a supercuts.capability.v1 manifest");
874
+ let manifest;
875
+ try {
876
+ manifest = JSON.parse(Buffer.from(encoded, "base64url").toString());
877
+ }
878
+ catch (error) {
879
+ return fail("CAPABILITY_INVALID", "manifest is not valid encoded JSON", error);
880
+ }
881
+ validateCapabilityManifest(manifest);
882
+ const body = Buffer.from(JSON.stringify(manifest));
883
+ const response = await fetch(`${publisher.replace(/\/$/, "")}/v1/blobs?epochs=5`, { method: "PUT", body, signal: AbortSignal.timeout(60_000) });
884
+ if (!response.ok)
885
+ fail("WALRUS_PUBLISH_FAILED", `Walrus publisher returned ${response.status}`);
886
+ const result = await response.json(), blobId = result.newlyCreated?.blobObject?.blobId ?? result.alreadyCertified?.blobId ?? result.alreadyRegistered?.blobId ?? fail("WALRUS_PUBLISH_FAILED", "Walrus publisher omitted the blob ID");
887
+ const read = await fetch(`${aggregator.replace(/\/$/, "")}/v1/blobs/${encodeURIComponent(blobId)}`, { signal: AbortSignal.timeout(60_000) });
888
+ if (!read.ok || !Buffer.from(await read.arrayBuffer()).equals(body))
889
+ fail("WALRUS_BLOB_MISMATCH", "published capability did not round-trip through the Walrus aggregator");
890
+ record(this.store, "capability", manifest.capabilityId, { ...manifest, blobId });
891
+ return { ...manifest, blobId, visibility: "public-capability" };
892
+ }
893
+ async discoverLiveCapabilities(args) {
894
+ if (typeof args["blob-ids"] !== "string")
895
+ return this.discoverCapabilities(args, "configured-manifests");
896
+ const aggregator = this.profile.live?.walrusAggregatorUrl ?? fail("LIVE_WALRUS_CONFIG_MISSING", "live capability discovery requires --walrus-aggregator");
897
+ let ids;
898
+ try {
899
+ ids = JSON.parse(Buffer.from(args["blob-ids"], "base64url").toString());
900
+ }
901
+ catch (error) {
902
+ return fail("CAPABILITY_LIST_INVALID", "blob IDs are not valid encoded JSON", error);
903
+ }
904
+ const blobIds = Array.isArray(ids) && ids.length && ids.every((id) => typeof id === "string" && Boolean(id) && id.length <= 256) ? ids : fail("CAPABILITY_LIST_INVALID", "blob-ids must be a non-empty string array");
905
+ const manifests = await Promise.all(blobIds.map(async (blobId) => { const response = await fetch(`${aggregator.replace(/\/$/, "")}/v1/blobs/${encodeURIComponent(blobId)}`, { signal: AbortSignal.timeout(60_000) }); if (!response.ok)
906
+ return fail("WALRUS_READ_FAILED", `Walrus aggregator returned ${response.status}`); const manifest = await response.json(); validateCapabilityManifest(manifest); return { ...manifest, blobId }; }));
907
+ return { capabilities: manifests, source: "walrus" };
908
+ }
909
+ discoverCapabilities(args, source) {
910
+ const configured = typeof args.capabilities === "string" ? JSON.parse(Buffer.from(args.capabilities, "base64url").toString()) : [];
911
+ const list = Array.isArray(configured) ? configured : fail("CAPABILITY_LIST_INVALID", "configured capabilities must be an array");
912
+ for (const item of list)
913
+ if (item && typeof item === "object" && item.protocol === "supercuts.capability.v1")
914
+ validateCapabilityManifest(item);
915
+ return { capabilities: list, source };
916
+ }
917
+ localOffer(command, args) {
918
+ const sessionId = required(args, "session"), previous = get(this.store, "offer", sessionId);
919
+ const revision = Number(typeof args.revision === "string" ? args.revision : Number(previous?.revision ?? 0) + 1);
920
+ if (!Number.isSafeInteger(revision) || revision < 1)
921
+ fail("REVISION_INVALID", "revision must be a positive integer");
922
+ const state = command.split(".")[1], termsHash = hash(typeof args.terms === "string" ? args.terms : "accepted");
923
+ const offer = { sessionId, revision, state, termsHash, transcriptCommitment: hash(`${sessionId}:${revision}:${state}:${termsHash}`) };
924
+ record(this.store, "offer", sessionId, offer);
925
+ return offer;
926
+ }
927
+ async liveMemory(command, args) {
928
+ const encoded = this.store.getSecret("live:memory-config") ?? fail("LIVE_MEMORY_CONFIG_MISSING", "live Mainnet memory requires an owner-only --memory-config-file");
929
+ let config;
930
+ try {
931
+ config = JSON.parse(encoded.toString());
932
+ }
933
+ catch (error) {
934
+ return fail("LIVE_MEMORY_CONFIG_INVALID", "encrypted memory config is invalid", error);
935
+ }
936
+ const manual = new ManualMemory(this.store, config);
937
+ try {
938
+ if (command === "memory.remember") {
939
+ if (typeof args.memory === "string")
940
+ return this.rememberTyped(args, "agent_observation");
941
+ const operationId = required(args, "operation-id"), observation = required(args, "fact"), workflow = new WorkflowMemory(this.store, manual);
942
+ const blobId = await workflow.record({ kind: /privacy|sensitive|hide/i.test(observation) ? "privacy_choice" : "delivery_observation", subject: "procurement-demo", observation, operationId, createdAt: Date.now() });
943
+ return { operationId, namespace: config.namespace, blobId, status: "complete" };
944
+ }
945
+ if (command === "memory.recall") {
946
+ const recalled = await manual.recall(required(args, "query"));
947
+ const citations = recalled.filter((item) => item.distance <= (this.profile.policy.maxMemoryDistance ?? 0.35)).map((item) => { try {
948
+ const memory = JSON.parse(item.text);
949
+ validateSettlementMemory(memory);
950
+ const trusted = this.authenticatedMemory(item.blobId, memory);
951
+ return { blobId: item.blobId, kind: trusted.kind, provenance: trusted.provenance, distance: item.distance };
952
+ }
953
+ catch {
954
+ return { blobId: item.blobId, kind: "legacy", provenance: "legacy_untrusted", distance: item.distance };
955
+ } });
956
+ return { namespace: config.namespace, citations, effect: citations.length ? "candidate-preference-adjusted" : "no-policy-change" };
957
+ }
958
+ const target = typeof args["operation-id"] === "string" ? args["operation-id"] : undefined;
959
+ for (const row of (target ? this.store.db.prepare("SELECT operation_id FROM memory_jobs WHERE status='pending' AND operation_id=? ORDER BY created_at").all(target) : this.store.db.prepare("SELECT operation_id FROM memory_jobs WHERE status='pending' ORDER BY created_at").all())) {
960
+ const queued = this.store.getSecret(`memory-outbox:${row.operation_id}`);
961
+ if (!queued)
962
+ continue;
963
+ if (this.store.db.prepare("UPDATE memory_jobs SET status='submitting',claimed_at=? WHERE operation_id=? AND status='pending'").run(Date.now(), row.operation_id).changes !== 1)
964
+ continue;
965
+ try {
966
+ const memory = JSON.parse(queued.toString());
967
+ validateSettlementMemory(memory, "verified_outcome");
968
+ const blobId = await manual.remember(memory);
969
+ this.store.db.prepare("UPDATE memory_jobs SET blob_id=?,status='complete' WHERE operation_id=?").run(blobId, row.operation_id);
970
+ this.registerMemoryAuthority(blobId, memory);
971
+ }
972
+ catch {
973
+ this.store.db.prepare("UPDATE memory_jobs SET status='unresolved' WHERE operation_id=?").run(row.operation_id);
974
+ }
975
+ }
976
+ const jobs = new WorkflowMemory(this.store, manual).completed();
977
+ return { namespace: config.namespace, completed: jobs.length, jobs: jobs.map((item) => ({ ...item, status: "complete" })) };
978
+ }
979
+ finally {
980
+ manual.destroy();
981
+ }
982
+ }
983
+ execute(command, args) {
984
+ this.allow(command, args);
985
+ if (!this.profile.mock)
986
+ return this.executeLive(command, args);
987
+ if (command === "settlement.plan")
988
+ return this.planSettlement(args).then((data) => output(command, data));
989
+ if (command === "settlement.approve-review")
990
+ return output(command, this.approveReview(args));
991
+ if (command === "memory.policy")
992
+ return this.rememberTyped(args, "owner_policy").then((data) => output(command, data));
993
+ if (command === "doctor")
994
+ return output(command, this.doctor());
995
+ if (command === "status")
996
+ return output(command, { role: this.profile.role, network: this.profile.network, operations: all(this.store, "payment").length, mock: this.profile.mock });
997
+ if (command === "capability.publish") {
998
+ return output(command, this.publishCapability(args, true));
999
+ }
1000
+ if (command === "capability.discover") {
1001
+ return output(command, this.discoverCapabilities(args, "configured-mock"));
1002
+ }
1003
+ if (command === "wallet.deposit") {
1004
+ this.doctor();
1005
+ const value = amount(args), commitment = `note_${hash(`${value}:${randomUUID()}`).slice(0, 32)}`;
1006
+ record(this.store, "note", commitment, { commitment, amount: value.toString(), role: "input", spent: false });
1007
+ return output(command, { commitment, amount: value.toString(), finalized: true, transactionDigest: this.digest("deposit", commitment), evidence: this.evidence() });
1008
+ }
1009
+ if (command === "wallet.balance") {
1010
+ const notes = all(this.store, "note");
1011
+ return output(command, { available: notes.filter((n) => !n.spent).reduce((sum, n) => sum + BigInt(n.amount), 0n).toString(), notes: notes.length });
1012
+ }
1013
+ if (command === "wallet.recover") {
1014
+ if (typeof args.commitment === "string") {
1015
+ const recoveredAmount = amount(args);
1016
+ record(this.store, "note", args.commitment, { commitment: args.commitment, amount: recoveredAmount.toString(), role: "payment", spent: false });
1017
+ }
1018
+ return output(command, { recovered: all(this.store, "note").length, source: this.profile.mock ? "deterministic-mock-events" : "pinned-chain-events" });
1019
+ }
1020
+ if (command === "wallet.withdraw") {
1021
+ this.doctor();
1022
+ const commitment = required(args, "commitment"), note = get(this.store, "note", commitment) ?? fail("NOTE_NOT_FOUND", "note was not found");
1023
+ if (note.spent)
1024
+ fail("NOTE_SPENT", "note has already been spent");
1025
+ note.spent = true;
1026
+ record(this.store, "note", commitment, note);
1027
+ return output(command, { commitment, amount: note.amount, transactionDigest: this.digest("withdraw", commitment), finalized: true, evidence: this.evidence() });
1028
+ }
1029
+ if (command === "offer.open") {
1030
+ const sessionId = typeof args.session === "string" ? args.session : `session_${randomUUID()}`;
1031
+ const offer = { sessionId, revision: 0, state: "open", buyer: this.profile.role, transcriptCommitment: hash(`open:${sessionId}`) };
1032
+ record(this.store, "offer", sessionId, offer);
1033
+ return output(command, offer);
1034
+ }
1035
+ if (["offer.propose", "offer.counter", "offer.accept"].includes(command)) {
1036
+ const sessionId = required(args, "session"), previous = get(this.store, "offer", sessionId);
1037
+ const revision = Number(typeof args.revision === "string" ? args.revision : Number(previous?.revision ?? 0) + 1);
1038
+ if (!Number.isSafeInteger(revision) || revision < 1)
1039
+ fail("REVISION_INVALID", "revision must be a positive integer");
1040
+ const state = command.split(".")[1], termsHash = hash(typeof args.terms === "string" ? args.terms : "accepted");
1041
+ const offer = { sessionId, revision, state, termsHash, transcriptCommitment: hash(`${sessionId}:${revision}:${state}:${termsHash}`) };
1042
+ record(this.store, "offer", sessionId, offer);
1043
+ if (command === "offer.accept")
1044
+ this.enqueueVerifiedOutcome("agreement", typeof args["operation-id"] === "string" ? args["operation-id"] : `${sessionId}:${revision}`, { status: "accepted", transcriptCommitment: offer.transcriptCommitment }, typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined);
1045
+ return output(command, offer);
1046
+ }
1047
+ if (command === "offer.show")
1048
+ return output(command, get(this.store, "offer", required(args, "session")) ?? fail("OFFER_NOT_FOUND", "offer session was not found"));
1049
+ if (command === "pay.quote") {
1050
+ const rail = required(args, "rail");
1051
+ if (!["shielded", "x402"].includes(rail))
1052
+ fail("RAIL_INVALID", "rail must be shielded or x402");
1053
+ const checked = this.paymentPolicy(args, rail);
1054
+ return output(command, { rail, amount: checked.amount.toString(), counterparty: checked.counterparty, asset: checked.asset, policyDecision: "allowed", visibility: rail === "shielded" ? "no-direct-public-payment; funding-deposits-withdrawals-gas-and-pool-activity-visible" : "public-transfer" });
1055
+ }
1056
+ if (command === "pay.shielded" || command === "pay.x402")
1057
+ return this.pay(command, args);
1058
+ if (command === "pay.status") {
1059
+ const operationId = required(args, "operation-id"), payment = get(this.store, "payment", operationId) ?? fail("OPERATION_NOT_FOUND", "payment operation was not found");
1060
+ if (args.reconcile === true && payment.status === "broadcast") {
1061
+ payment.status = "finalized";
1062
+ payment.tatumVerified = true;
1063
+ record(this.store, "payment", operationId, payment);
1064
+ }
1065
+ return output(command, payment);
1066
+ }
1067
+ if (command === "resource.serve") {
1068
+ const requestId = required(args, "request-id"), body = required(args, "body"), resource = { requestId, responseHash: hash(body), body, deliveryStatus: "ready" };
1069
+ record(this.store, "resource", requestId, resource);
1070
+ return output(command, { requestId, responseHash: resource.responseHash, deliveryStatus: resource.deliveryStatus });
1071
+ }
1072
+ if (command === "resource.fetch") {
1073
+ const requestId = required(args, "request-id"), local = get(this.store, "resource", requestId);
1074
+ if (local)
1075
+ return output(command, { ...local, deliveryStatus: "delivered" });
1076
+ const body = required(args, "response"), operationId = required(args, "operation-id"), payment = get(this.store, "payment", operationId) ?? fail("OPERATION_NOT_FOUND", "payment operation was not found");
1077
+ if (payment.status !== "finalized" || payment.requestId !== requestId)
1078
+ fail("RESOURCE_PAYMENT_REQUIRED", "a matching finalized payment is required");
1079
+ const delivered = { requestId, responseHash: hash(body), body, deliveryStatus: "delivered" };
1080
+ this.enqueueVerifiedOutcome("delivery", operationId, { status: "delivered", requestId, responseHash: delivered.responseHash }, typeof payment.planHash === "string" ? payment.planHash : undefined);
1081
+ return output(command, delivered);
1082
+ }
1083
+ if (command === "memory.remember") {
1084
+ if (typeof args.memory === "string")
1085
+ return this.rememberTyped(args, "agent_observation").then((data) => output(command, data));
1086
+ const operationId = required(args, "operation-id"), fact = required(args, "fact");
1087
+ if (/private key|spend secret|seed phrase|full transcript/i.test(fact))
1088
+ fail("MEMORY_SECRET_REJECTED", "memory cannot contain wallet or transcript secrets");
1089
+ const item = { operationId, namespace: `supercuts:${this.profile.role}`, blobId: `mockwalrus_${hash(`${operationId}:${fact}`).slice(0, 40)}`, fact, status: "complete" };
1090
+ record(this.store, "memory", operationId, item);
1091
+ return output(command, { operationId, namespace: item.namespace, blobId: item.blobId, status: item.status, provenance: "legacy_untrusted" });
1092
+ }
1093
+ if (command === "memory.recall") {
1094
+ const items = [...all(this.store, "memory"), ...all(this.store, "verified-memory")], query = required(args, "query").toLowerCase();
1095
+ const found = items.filter((item) => String(item.fact ?? "").toLowerCase().includes(query) || item.memory);
1096
+ const citations = found.map((item) => { const memory = item.memory; return { blobId: String(item.blobId), kind: memory?.kind ?? "legacy", provenance: memory?.provenance ?? "legacy_untrusted", distance: 0 }; });
1097
+ return output(command, { namespace: `supercuts:${this.profile.role}`, citations, effect: citations.length ? "candidate-preference-adjusted" : "no-policy-change" });
1098
+ }
1099
+ if (command === "memory.status") {
1100
+ const items = all(this.store, "memory");
1101
+ return output(command, { namespace: `supercuts:${this.profile.role}`, completed: items.length, jobs: items.map(({ operationId, blobId, status }) => ({ operationId, blobId, status })) });
1102
+ }
1103
+ if (command === "disclose") {
1104
+ const dealId = required(args, "deal-id"), auditor = required(args, "auditor"), paymentDigest = required(args, "payment-digest");
1105
+ const pkg = { version: 1, dealId, auditor, paymentDigest, transcriptRoot: required(args, "transcript-root"), verifiedAuthors: ["buyer", "seller"], mockEvidence: this.profile.mock, packageHash: "" };
1106
+ pkg.packageHash = hash(json({ ...pkg, packageHash: undefined }));
1107
+ record(this.store, "disclosure", dealId, pkg);
1108
+ return output(command, { package: Buffer.from(json(pkg)).toString("base64"), packageHash: pkg.packageHash, auditor, dealId });
1109
+ }
1110
+ if (command === "audit.verify") {
1111
+ let pkg;
1112
+ try {
1113
+ pkg = JSON.parse(Buffer.from(required(args, "package"), "base64").toString());
1114
+ }
1115
+ catch (error) {
1116
+ return fail("AUDIT_PACKAGE_INVALID", "package is not valid base64 JSON", error);
1117
+ }
1118
+ if (pkg.auditor !== required(args, "auditor") || pkg.dealId !== required(args, "deal-id"))
1119
+ fail("AUDIT_SCOPE_MISMATCH", "package is for another auditor or deal");
1120
+ return output(command, { dealId: pkg.dealId, agreementVerified: true, paymentVerified: true, transactionDigest: pkg.paymentDigest, evidence: "mock-not-funded" });
1121
+ }
1122
+ return fail("COMMAND_UNKNOWN", `unknown command ${command}`);
1123
+ }
1124
+ pay(command, args) {
1125
+ this.doctor();
1126
+ const rail = command === "pay.shielded" ? "shielded" : "x402", checked = this.paymentPolicy(args, rail);
1127
+ const operationId = required(args, "operation-id"), requestId = typeof args["request-id"] === "string" ? args["request-id"] : operationId;
1128
+ this.bindSettlementPlan(command, args, { amount: checked.amount.toString(), counterparty: checked.counterparty, asset: checked.asset, ...(typeof args["resource-url"] === "string" ? { endpoint: args["resource-url"] } : {}), requestId, ...(typeof args.session === "string" ? { session: args.session } : {}) });
1129
+ const payload = hash(`${rail}:${checked.amount}:${checked.counterparty}:${checked.asset}:${requestId}`), existing = get(this.store, "payment", operationId);
1130
+ if (existing) {
1131
+ if (existing.payloadHash !== payload)
1132
+ fail("OPERATION_ID_CONFLICT", "operation ID already identifies another payment");
1133
+ return output(command, existing);
1134
+ }
1135
+ const transactionDigest = this.digest(rail, operationId), payment = { operationId, status: args["broadcast-only"] === true ? "broadcast" : "finalized", rail, amount: checked.amount.toString(), counterparty: checked.counterparty, asset: checked.asset, requestId, payloadHash: payload, transactionDigest, policyDecision: "allowed", tatumVerified: args["broadcast-only"] !== true, visibility: rail === "shielded" ? "no-direct-public-payment; funding-deposits-withdrawals-gas-and-pool-activity-visible" : "public-transfer", evidence: this.evidence(), ...(typeof args["plan-hash"] === "string" ? { planHash: args["plan-hash"] } : {}) };
1136
+ record(this.store, "payment", operationId, payment);
1137
+ this.enqueueVerifiedOutcome("settlement", operationId, payment, typeof args["plan-hash"] === "string" ? args["plan-hash"] : undefined, { counterparty: checked.counterparty, asset: checked.asset });
1138
+ return output(command, payment);
1139
+ }
1140
+ digest(kind, id) { return `mocktx_${hash(`${kind}:${id}`).slice(0, 44)}`; }
1141
+ evidence() { return "deterministic-mock-not-funded"; }
1142
+ }
1143
+ export function output(command, data) { return { schemaVersion: "supercuts.v1", ok: true, command, data }; }
1144
+ export function errorOutput(error) {
1145
+ const known = error instanceof SupercutsError ? error : new SupercutsError("INTERNAL_ERROR", error instanceof Error ? error.message : "unknown error");
1146
+ const retryable = ["BROADCAST_UNRESOLVED", "DELIVERY_IN_PROGRESS", "DELIVERY_PENDING", "ONBOARDING_SERVICE_UNAVAILABLE", "SELLER_DIRECTORY_UNAVAILABLE", "WALRUS_WRITE_FAILED", "WALRUS_READ_FAILED", "X402_FACILITATOR_UNAVAILABLE"].includes(known.code);
1147
+ const sameOperation = retryable || ["OPERATION_ID_CONFLICT", "DEAL_OPERATION_CONFLICT"].includes(known.code);
1148
+ return { schemaVersion: "supercuts.v1", ok: false, error: { code: known.code, message: known.message, recovery: recovery(known.code), retryable, retryWithSameOperationId: sameOperation } };
1149
+ }
1150
+ function recovery(code) {
1151
+ if (code === "LOCAL_AUTH_REQUIRED")
1152
+ return "repeat with --authorize after reviewing the operation";
1153
+ if (code === "PROFILE_NOT_FOUND")
1154
+ return "run supercuts init --role <role> --mock";
1155
+ if (code.startsWith("POLICY_"))
1156
+ return "change the request or update the owner-controlled profile policy";
1157
+ if (code === "ARTIFACT_HASH_MISMATCH")
1158
+ return "restore the pinned artifact or reinitialize with an audited manifest";
1159
+ if (code === "SESSION_MISMATCH")
1160
+ return "read status.session.runId and retry with that session, or omit runId on the v2 deal path";
1161
+ if (code === "DEAL_MANDATE_INACTIVE")
1162
+ return "renew the owner mandate locally, then resume the same deal operation ID";
1163
+ if (code === "BROADCAST_UNRESOLVED")
1164
+ return "reconcile or resume with the same operation ID; never create a replacement payment";
1165
+ if (code === "DELIVERY_PENDING")
1166
+ return "resume the same root operation ID; completed selection, payment, and agreement stages will not run again";
1167
+ if (code === "X402_FACILITATOR_UNAVAILABLE")
1168
+ return "the signed transaction remains journaled; resume the same operation ID after facilitator health recovers";
1169
+ if (code === "OPERATION_ID_CONFLICT" || code === "DEAL_OPERATION_CONFLICT")
1170
+ return "reuse the original exact input for this operation ID or start a deliberately new deal";
1171
+ if (code === "X402_TRANSACTION_EXPIRY_INVALID")
1172
+ return "run onboarding doctor against the configured Sui RPC and retry the same operation ID after compatibility is restored";
1173
+ return "inspect the stable error code and retry with corrected input";
1174
+ }