@supercuts-walrus/backend 0.1.1 → 0.1.3

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 (54) hide show
  1. package/README.md +28 -5
  2. package/config/phase1.testnet.json +20 -0
  3. package/config/proving-manifest.json +11 -0
  4. package/dist/buyer-setup.js +27 -13
  5. package/dist/cli.js +10 -2
  6. package/dist/dashboard.js +1 -1
  7. package/dist/deal-adapter.js +0 -2
  8. package/dist/deal-engine.d.ts +13 -0
  9. package/dist/deal-engine.js +64 -39
  10. package/dist/deal.d.ts +35 -0
  11. package/dist/deal.js +9 -3
  12. package/dist/embedding-adapter.d.ts +26 -0
  13. package/dist/embedding-adapter.js +116 -0
  14. package/dist/facilitator.js +2 -1
  15. package/dist/funding.js +10 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/live-deal-ports.d.ts +2 -1
  19. package/dist/live-deal-ports.js +31 -5
  20. package/dist/live-runtime.d.ts +1 -1
  21. package/dist/live-runtime.js +27 -11
  22. package/dist/live-seller-transport.js +5 -1
  23. package/dist/main-module.d.ts +5 -0
  24. package/dist/main-module.js +16 -0
  25. package/dist/mandate.js +1 -1
  26. package/dist/mcp-bootstrap.js +3 -3
  27. package/dist/mcp.js +2 -0
  28. package/dist/memory-scorecard.d.ts +32 -0
  29. package/dist/memory-scorecard.js +76 -0
  30. package/dist/memwal.d.ts +28 -0
  31. package/dist/memwal.js +81 -2
  32. package/dist/onboarding-doctor.js +12 -5
  33. package/dist/phase3.d.ts +43 -0
  34. package/dist/phase3.js +147 -32
  35. package/dist/role-lock.d.ts +1 -1
  36. package/dist/role-lock.js +1 -1
  37. package/dist/seam.js +2 -1
  38. package/dist/seller-catalog-generate.js +4 -4
  39. package/dist/seller-directory-cli.js +2 -1
  40. package/dist/seller-live-cli.js +10 -1
  41. package/dist/seller-provision.js +15 -1
  42. package/dist/seller-research.d.ts +50 -0
  43. package/dist/seller-research.js +210 -0
  44. package/dist/seller-scaffold.d.ts +1 -0
  45. package/dist/seller-scaffold.js +35 -0
  46. package/dist/seller-service.d.ts +1 -1
  47. package/dist/seller-service.js +8 -20
  48. package/dist/seller-setup.d.ts +3 -0
  49. package/dist/seller-setup.js +109 -0
  50. package/dist/settlement.d.ts +32 -3
  51. package/dist/settlement.js +56 -11
  52. package/dist/testnet-defaults.d.ts +21 -0
  53. package/dist/testnet-defaults.js +26 -0
  54. package/package.json +4 -2
package/README.md CHANGED
@@ -77,7 +77,7 @@ bytes under that operation ID; retrying another payload with the same ID fails.
77
77
 
78
78
  ## Procurement demo
79
79
 
80
- For the Codex buyer with three remote Claude-backed sellers, Supabase-backed seller state, and the separate Render facilitator, follow [the remote demo guide](../docs/remote-codex-demo.md). The remote path is live-only and gives Codex a buyer role MCP server; the three seller panes use the safe `supercuts-seller-watch` event stream.
80
+ For the Codex buyer with three remote sellers, Claude-researched and owner-reviewed artifacts, Supabase-backed seller state, and the separate Render facilitator, follow [the remote demo guide](../docs/remote-codex-demo.md). The remote path is live-only and gives Codex a buyer role MCP server; the three seller panes use the safe `supercuts-seller-watch` event stream.
81
81
 
82
82
  `supercuts-demo` is the operator entry point:
83
83
 
@@ -116,9 +116,11 @@ For a Claude-agent recording, export `CLAUDE_API_KEY` or provide
116
116
  `live --claude-key-file <owner-only-file>`; a repository `.env` file is not
117
117
  loaded automatically. Validate the credential against Anthropic before starting
118
118
  a funded take.
119
- The live path calls Claude for bounded buyer and seller decisions and records
120
- only the model, action, and token counts. The backend still enforces capability,
121
- amount, rail, and agreement bindings. Validate that recording with
119
+ The recorded workflow can call Claude for bounded buyer decisions and records
120
+ only the model, action, and token counts. Hosted seller price and agreement
121
+ decisions are deterministic after backend validation; Claude web research runs
122
+ before provisioning so model availability cannot interrupt a paid request. The
123
+ backend still enforces capability, amount, rail, and agreement bindings. Validate that recording with
122
124
  `supercuts-demo check --require-claude`; an earlier funded run without these
123
125
  agent-decision events does not satisfy that gate.
124
126
 
@@ -185,7 +187,28 @@ signer, pool notes, Walrus and Walrus Memory configuration. A missing file
185
187
  returns `DEAL_SELLERS_NOT_CONFIGURED`. An owner-only `deal-ports.mjs` exporting
186
188
  `createDealPorts(app, mandate)` remains available as an advanced override.
187
189
 
188
- The seller service is `supercuts-seller-live <seller-home> <config.json>`.
190
+ Create a seller home with `supercuts onboard seller --home <seller-home>` or
191
+ its noninteractive `--config <owner-only-setup.json>` form. It generates a
192
+ fresh seller signer by default, writes a seller MCP stanza, and explicitly
193
+ reports that the seller should not receive faucet funds. The seller service is
194
+ `supercuts-seller-live <seller-home> <config.json>`.
195
+ Use `supercuts seller scaffold` to generate the offer spec, replaceable handler,
196
+ future expiry, environment-based facilitator config, and exact next commands.
197
+ The npm tarball includes pinned metadata for x402-only sellers; shielded sellers
198
+ still install the proof artifacts required for recovery and exits.
199
+
200
+ Research sellers can create a source-backed draft before packaging:
201
+
202
+ ```sh
203
+ CLAUDE_API_KEY=... supercuts seller research --kind market-brief \
204
+ --topic "Current Sui ecosystem market structure and risks" \
205
+ --output <owner-only-draft.json>
206
+ ```
207
+
208
+ The command uses Anthropic web search, rejects citations absent from the search
209
+ evidence, and writes a mode-0600 draft marked `draft-needs-owner-review`.
210
+ Optional `--allowed-domains` constrains sources. It never publishes or changes
211
+ live seller state; an owner must review and approve the immutable bytes first.
189
212
  Register each capability first with
190
213
  `supercuts-seller-register <seller-home> <spec.json> <registration.json>`.
191
214
  The spec contains `capabilityId`, `capability`, `description`, `inputSchema`,
@@ -0,0 +1,20 @@
1
+ {
2
+ "network": "testnet",
3
+ "sender": "0x66fdfe84ff521627c93910c93ec0bc26934ee1fef3a1a0a5ce14b7dc4567ec00",
4
+ "coinType": "0x2::sui::SUI",
5
+ "coinTypeHash": "be4507e04f555448ca05c0c9f8345c5ea9f3e40e8616a20f5d5c18b40ef399eb",
6
+ "packageId": "0x32d104c1ab422d497df4ae3a6861258694949c86428f886ca338e07bd055cf76",
7
+ "poolId": "0x8fd22140c906495bb5334a497dcdd9112479c638e21c5df4b4cfb66f633a106b",
8
+ "poolCreateTransaction": "BnzMqokgQVWwSkRYCCrMTyWwHN6HVdDKWwVDJXTZqC6b",
9
+ "deploymentCheckpoint": "401762744",
10
+ "deploymentDomain": "3082f331660eb38075e250a810a701bc4b0de2ca329eda70b2d76e3795517305",
11
+ "initialRoot": "2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e",
12
+ "verifierVersion": 2,
13
+ "testOnly": true,
14
+ "artifactManifest": "config/proving-manifest.json",
15
+ "artifacts": {
16
+ "deposit": { "r1csSha256": "f36a1a18bf9d243e089e6ab14436e5a921d2b839d81bea95db319a4cdb80a197", "zkeySha256": "d398776a54628c390e8bf822007e47ed8fac00e92e8ea95bb17d20db517998dc", "verifyingKeySha256": "838404ff0ae3a9fc82da0856b68a11428cf3ffef335b4c6539fa1860d8e1ba26" },
17
+ "transfer": { "r1csSha256": "055ea2ec8f689c48e59f89fe06db6fb980dc575982214bfe60f675e8f08485c3", "zkeySha256": "6e7bcac98b8d2a78fd1811e2caadd83d0d6a301951f8cf27ac55a67006b001c2", "verifyingKeySha256": "8a6896f3f330769aa69e0dc1251a19fb69c4d6264b5729d0104df8d262e573ba" },
18
+ "withdraw": { "r1csSha256": "85cf1542ab2f2ece4bbd8757def48d526ba0951a832a2e0ebbf97ace2c611308", "zkeySha256": "fd303dd0dd53b9b752dac0de546dabdd03198a7a4defde5e8d6018bccd419c56", "verifyingKeySha256": "445c1982bea5e465fb0f5665023bb279637d820a9425f16a4c2f2ee7460e7e4a" }
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "supercuts-pool-v2",
3
+ "testOnly": true,
4
+ "warning": "Per-circuit contributions are locally generated and unaudited; never use these keys with production assets.",
5
+ "powersOfTauSha256": "3f428d1a407e4704ef906960e000b03089e5e6ec29bf65b07bb5e3de005f4700",
6
+ "circuits": {
7
+ "deposit": { "r1csSha256": "f36a1a18bf9d243e089e6ab14436e5a921d2b839d81bea95db319a4cdb80a197", "zkeySha256": "d398776a54628c390e8bf822007e47ed8fac00e92e8ea95bb17d20db517998dc", "verificationKeySha256": "838404ff0ae3a9fc82da0856b68a11428cf3ffef335b4c6539fa1860d8e1ba26", "proofSha256": "9b7835852cd9ced7c02828e5c563191ac597f7175b04fa9bb01c66cc93a93e58", "publicInputsSha256": "23569e5994ddd08b555dcbcf12c735ec303e000f6e7e90c9b63667dec60b6aeb" },
8
+ "transfer": { "r1csSha256": "055ea2ec8f689c48e59f89fe06db6fb980dc575982214bfe60f675e8f08485c3", "zkeySha256": "6e7bcac98b8d2a78fd1811e2caadd83d0d6a301951f8cf27ac55a67006b001c2", "verificationKeySha256": "8a6896f3f330769aa69e0dc1251a19fb69c4d6264b5729d0104df8d262e573ba", "proofSha256": "ff9a902e91e7d7b64e278cc7e17c22ad3f031f350aacb43f610771adafbf3f35", "publicInputsSha256": "903861e7ee9f792a17194f96649736d4916f45605918acac78e5d40a5b8f6170" },
9
+ "withdraw": { "r1csSha256": "85cf1542ab2f2ece4bbd8757def48d526ba0951a832a2e0ebbf97ace2c611308", "zkeySha256": "fd303dd0dd53b9b752dac0de546dabdd03198a7a4defde5e8d6018bccd419c56", "verificationKeySha256": "445c1982bea5e465fb0f5665023bb279637d820a9425f16a4c2f2ee7460e7e4a", "proofSha256": "4746ee54c04389929aad48c0c5a0cf18985b9dd6bcd7a3b83aa717d8847973d3", "publicInputsSha256": "d18d5852ac3c636c7b081bc52d367c896c95bd5a89feacc91014afefe3dcff54" }
10
+ }
11
+ }
@@ -14,6 +14,7 @@ import { verifySignedCapability } from "./capability.js";
14
14
  import { WalrusDealBlobs } from "./deal-wire.js";
15
15
  import { canonicalJson } from "./settlement.js";
16
16
  import { validateDirectoryOrigin } from "./seller-directory.js";
17
+ import { TESTNET_DEFAULTS, withTestnetDefaults } from "./testnet-defaults.js";
17
18
  function ownerFile(path) {
18
19
  if ((statSync(path).mode & 0o077) !== 0)
19
20
  fail("SETUP_PERMISSIONS", `${path} must be owner-only`);
@@ -34,6 +35,9 @@ function origins(values) {
34
35
  });
35
36
  }
36
37
  function validateConfig(config) {
38
+ withTestnetDefaults(config);
39
+ if (!config.sellerOrigins?.length && !config.directoryUrl)
40
+ config.directoryUrl = TESTNET_DEFAULTS.directoryUrl;
37
41
  amount(config.perDealCeiling, "perDealCeiling");
38
42
  amount(config.totalBudget, "totalBudget");
39
43
  if (BigInt(config.totalBudget) < BigInt(config.perDealCeiling))
@@ -48,10 +52,10 @@ function validateConfig(config) {
48
52
  fail("SETUP_INVALID", "configure a directoryUrl or sellerOrigins");
49
53
  if (config.directoryUrl)
50
54
  origins([config.directoryUrl]);
51
- for (const key of ["deploymentConfig", "tatumKeyFile", "writeGrpc", "tatumGrpc", "walrusPublisher", "walrusAggregator", "memoryConfigFile"])
55
+ for (const key of ["deploymentConfig", "tatumKeyFile", "writeGrpc", "tatumGrpc", "walrusPublisher", "walrusAggregator"])
52
56
  if (typeof config[key] !== "string" || !config[key])
53
57
  fail("SETUP_INVALID", `${key} is required`);
54
- for (const path of [config.tatumKeyFile, config.memoryConfigFile, ...(config.suiKeyFile ? [config.suiKeyFile] : [])])
58
+ for (const path of [config.tatumKeyFile, ...(config.memoryConfigFile ? [config.memoryConfigFile] : []), ...(config.suiKeyFile ? [config.suiKeyFile] : [])])
55
59
  ownerFile(resolve(path));
56
60
  const deployment = JSON.parse(readFileSync(resolve(config.deploymentConfig), "utf8"));
57
61
  if (deployment.network !== "testnet" || deployment.verifierVersion !== 2)
@@ -86,22 +90,25 @@ export async function promptBuyerSetup(home) {
86
90
  fail("SETUP_INPUT_REQUIRED", "interactive setup requires a terminal; use --config with an owner-only JSON file");
87
91
  const rl = createInterface({ input: process.stdin, output: process.stderr });
88
92
  try {
89
- const ask = async (label) => (await rl.question(`${label}: `)).trim();
90
- const days = Number(await ask("Policy duration in days"));
93
+ const ask = async (label, fallback) => (await rl.question(fallback ? `${label} [${fallback}]: ` : `${label}: `)).trim() || fallback || "";
94
+ process.stderr.write("Press Enter to accept a bracketed default. Credentials are read from owner-only files, never typed here.\n");
95
+ const days = Number(await ask("Policy duration in days", "14"));
91
96
  if (!Number.isSafeInteger(days) || days < 1 || days > 365)
92
97
  fail("SETUP_INVALID", "policy duration must be 1..365 days");
93
98
  const config = {
94
- deploymentConfig: await ask("Pinned Testnet deployment config path"), tatumKeyFile: await ask("Tatum key file path"),
95
- writeGrpc: await ask("Sui write gRPC URL"), tatumGrpc: await ask("Tatum read gRPC URL"),
96
- walrusPublisher: await ask("Walrus publisher URL"), walrusAggregator: await ask("Walrus aggregator URL"),
97
- memoryConfigFile: await ask("Owner-only Walrus Memory config path"),
98
- directoryUrl: await ask("Shared directory origin (blank for direct sellers)"),
99
+ deploymentConfig: await ask("Pinned Testnet deployment config path", "bundled"), tatumKeyFile: await ask("Owner-only Tatum API key file path"),
100
+ writeGrpc: await ask("Sui write gRPC URL", TESTNET_DEFAULTS.writeGrpc), tatumGrpc: await ask("Tatum read gRPC URL", TESTNET_DEFAULTS.tatumGrpc),
101
+ walrusPublisher: await ask("Walrus publisher URL", TESTNET_DEFAULTS.walrusPublisher), walrusAggregator: await ask("Walrus aggregator URL", TESTNET_DEFAULTS.walrusAggregator),
102
+ memoryConfigFile: await ask("Owner-only Walrus Memory config path (blank to skip Memory)"),
103
+ directoryUrl: await ask("Shared directory origin (type none for direct sellers only)", TESTNET_DEFAULTS.directoryUrl),
99
104
  sellerOrigins: (await ask("Additional seller HTTPS origins, comma separated (or blank)")).split(",").map((item) => item.trim()).filter(Boolean),
100
105
  allowedCounterparties: (await ask("Initially approved seller Sui addresses, comma separated (or blank)")).split(",").map((item) => item.trim()).filter(Boolean),
101
- perDealCeiling: await ask("Maximum per deal in MIST"), totalBudget: await ask("Lifetime cumulative budget in MIST"),
106
+ perDealCeiling: await ask("Maximum per deal in MIST", "2000000"), totalBudget: await ask("Lifetime cumulative budget in MIST", "10000000"),
102
107
  expiresAt: Date.now() + days * 24 * 60 * 60 * 1000, allowPublicX402: (await ask("Allow public x402 when requested? yes/no")).toLowerCase() === "yes",
103
108
  };
104
- if (!config.directoryUrl)
109
+ if (config.deploymentConfig === "bundled")
110
+ config.deploymentConfig = "";
111
+ if (!config.directoryUrl || config.directoryUrl === "none")
105
112
  delete config.directoryUrl;
106
113
  return setupBuyerConfig(home, validateConfig(config));
107
114
  }
@@ -118,13 +125,20 @@ function setupBuyerConfig(home, config) {
118
125
  if (!config.suiKeyFile)
119
126
  writeFileSync(keyPath, Ed25519Keypair.generate().getSecretKey(), { mode: 0o600, flag: "wx" });
120
127
  ownerFile(keyPath);
121
- initialize(home, { role: "buyer", "deployment-config": resolve(config.deploymentConfig), "sui-key-file": keyPath, "tatum-key-file": resolve(config.tatumKeyFile), "write-grpc": config.writeGrpc, "tatum-grpc": config.tatumGrpc, "walrus-publisher": config.walrusPublisher, "walrus-aggregator": config.walrusAggregator, "memory-config-file": resolve(config.memoryConfigFile), "allow-counterparty": (config.allowedCounterparties ?? []).join(","), "max-amount": config.perDealCeiling, ...(config.rapidsnark ? { rapidsnark: resolve(config.rapidsnark) } : {}) });
128
+ initialize(home, { role: "buyer", "deployment-config": resolve(config.deploymentConfig), "sui-key-file": keyPath, "tatum-key-file": resolve(config.tatumKeyFile), "write-grpc": config.writeGrpc, "tatum-grpc": config.tatumGrpc, "walrus-publisher": config.walrusPublisher, "walrus-aggregator": config.walrusAggregator, ...(config.memoryConfigFile ? { "memory-config-file": resolve(config.memoryConfigFile) } : {}), "allow-counterparty": (config.allowedCounterparties ?? []).join(","), "max-amount": config.perDealCeiling, ...(config.rapidsnark ? { rapidsnark: resolve(config.rapidsnark) } : {}) });
122
129
  writePolicy(home, config);
123
130
  const session = createBuyerSession(home);
124
131
  const decoded = decodeSuiPrivateKey(readFileSync(keyPath, "utf8").trim());
125
132
  if (decoded.scheme !== "ED25519")
126
133
  fail("SETUP_INVALID", "buyer key must use ED25519");
127
- return { home, address: Ed25519Keypair.fromSecretKey(decoded.secretKey).toSuiAddress(), ...session, policy: "configured" };
134
+ const address = Ed25519Keypair.fromSecretKey(decoded.secretKey).toSuiAddress();
135
+ return { home, address, ...session, policy: "configured", nextSteps: [
136
+ `Fund only this buyer with Testnet SUI: supercuts funding execute --home ${home} --public-payment <MIST> --shielded-payment 0 --operation-id fund-1 --request-faucet --confirm (or use https://faucet.sui.io for ${address})`,
137
+ ...(config.allowedCounterparties?.length ? [] : [`Review the directory, then approve a seller: supercuts policy approve-seller --home ${home} --capability-id <id>`]),
138
+ ...(config.memoryConfigFile ? [`Keep the Walrus Memory embedding adapter running (supercuts-embedding-adapter). An MCP agent flushes outcome memories itself; SDK-only hosts also run: supercuts-memory-worker ${home}`] : []),
139
+ `Check readiness: supercuts onboarding doctor --home ${home}`,
140
+ `Connect an agent with ${session.mcpConfig}, or call BuyerClient.executeDeal from @supercuts-walrus/sdk.`,
141
+ ] };
128
142
  }
129
143
  export function createBuyerSession(home) {
130
144
  if (!existsSync(join(home, "profile.json")))
package/dist/cli.js CHANGED
@@ -4,19 +4,27 @@ import { addBuyerPrivacyRule, approveBuyerSeller, createBuyerSession, promptBuye
4
4
  import { onboardingDoctor } from "./onboarding-doctor.js";
5
5
  import { executeFunding, prepareFunding } from "./funding.js";
6
6
  import { packageHostedSeller } from "./seller-package.js";
7
+ import { promptSellerSetup, setupSeller } from "./seller-setup.js";
8
+ import { scaffoldSeller } from "./seller-scaffold.js";
9
+ import { generateSellerResearch } from "./seller-research.js";
7
10
  const words = process.argv.slice(2);
8
11
  let command = words.shift() ?? "";
9
12
  if (["wallet", "offer", "pay", "resource", "memory", "audit", "capability", "settlement", "setup", "policy", "onboarding", "onboard", "funding", "seller"].includes(command))
10
13
  command += `.${words.shift() ?? ""}`;
11
14
  try {
12
15
  if (!command || command === "help" || command === "--help" || command === "setup.") {
13
- process.stdout.write(`${JSON.stringify({ schemaVersion: "supercuts.help.v1", ok: true, commands: ["onboard buyer", "funding prepare", "funding execute", "seller package", "setup buyer", "setup renew", "setup session", "init", "doctor", "status", "wallet", "offer", "pay", "memory", "capability", "audit"], setup: "Run supercuts onboard buyer --home <private-directory> for guided live Testnet setup. Use seller package to validate and prepare an operator-assisted hosted seller bundle.", live: "Use funding prepare to calculate public gas/payment and shielded deposit requirements. Funding execute requires explicit confirmation and funds only the generated buyer address." })}\n`);
16
+ process.stdout.write(`${JSON.stringify({ schemaVersion: "supercuts.help.v1", ok: true, commands: ["onboard buyer", "onboard seller", "seller scaffold", "seller research", "seller package", "funding prepare", "funding execute", "setup buyer", "setup seller", "setup renew", "setup session", "init", "doctor", "status", "wallet", "offer", "pay", "memory", "memory insights", "capability", "audit"], setup: "Run supercuts onboard buyer or supercuts onboard seller with an isolated --home. Then use seller scaffold to generate a capability. Research sellers can use seller research to create a cited owner-review draft before packaging.", live: "Fund only the generated buyer address. A seller does not need faucet funds to publish or receive a capability payment." })}\n`);
14
17
  process.exit(0);
15
18
  }
16
19
  const args = parseArgs(words);
17
20
  const home = homePath(args);
18
21
  const fundingInput = { publicPayment: String(args["public-payment"] ?? "0"), shieldedPayment: String(args["shielded-payment"] ?? "0"), ...(typeof args["gas-reserve"] === "string" ? { gasReserve: args["gas-reserve"] } : {}), ...(typeof args["change-reserve"] === "string" ? { changeReserve: args["change-reserve"] } : {}) };
19
- const result = command === "init" ? initialize(home, args) : command === "setup.buyer" || command === "onboard.buyer" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupBuyer(home, args.config) : await promptBuyerSetup(home) } : command === "setup.renew" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: renewBuyer(home, String(args.config ?? "")) } : command === "setup.session" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: createBuyerSession(home) } : command === "funding.prepare" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await prepareFunding(home, fundingInput) } : command === "funding.execute" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await executeFunding(home, { ...fundingInput, operationId: String(args["operation-id"] ?? ""), requestFaucet: args["request-faucet"] === true, confirmed: args.confirm === true }) } : command === "seller.package" ? { schemaVersion: "supercuts.seller-package.v1", ok: true, command, data: await packageHostedSeller({ home, spec: String(args.spec ?? ""), artifact: String(args.artifact ?? ""), metadata: String(args.metadata ?? ""), registration: String(args.registration ?? ""), bundle: String(args.bundle ?? ""), ...(typeof args["content-type"] === "string" ? { contentType: args["content-type"] } : {}) }) } : command === "policy.add" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await addBuyerPrivacyRule(home, String(args["capability-id"] ?? "")) } : command === "policy.approve-seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await approveBuyerSeller(home, String(args["capability-id"] ?? "")) } : command === "onboarding.doctor" ? { schemaVersion: "supercuts.onboarding.v1", ok: true, command, data: await onboardingDoctor(home, typeof args.config === "string" ? args.config : undefined) } : await (async () => { const app = new Phase3App(home); try {
22
+ const result = command === "init" ? initialize(home, args) : command === "setup.buyer" || command === "onboard.buyer" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupBuyer(home, args.config) : await promptBuyerSetup(home) } : command === "setup.seller" || command === "onboard.seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupSeller(home, args.config) : await promptSellerSetup(home) } : command === "seller.scaffold" ? { schemaVersion: "supercuts.seller-scaffold.v1", ok: true, command, data: scaffoldSeller({ ...args, home }) } : command === "seller.research" ? { schemaVersion: "supercuts.seller-research.v1", ok: true, command, data: await generateSellerResearch({ kind: String(args.kind ?? ""), topic: String(args.topic ?? ""), output: String(args.output ?? ""), ...(typeof args["context-file"] === "string" ? { contextFile: args["context-file"] } : {}), ...(typeof args["allowed-domains"] === "string" ? { allowedDomains: args["allowed-domains"] } : {}), ...(typeof args.model === "string" ? { model: args.model } : {}) }) } : command === "setup.renew" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: renewBuyer(home, String(args.config ?? "")) } : command === "setup.session" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: createBuyerSession(home) } : command === "funding.prepare" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await prepareFunding(home, fundingInput) } : command === "funding.execute" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await executeFunding(home, { ...fundingInput, operationId: String(args["operation-id"] ?? ""), requestFaucet: args["request-faucet"] === true, confirmed: args.confirm === true }) } : command === "seller.package" ? { schemaVersion: "supercuts.seller-package.v1", ok: true, command, data: await packageHostedSeller({ home, spec: String(args.spec ?? ""), artifact: String(args.artifact ?? ""), metadata: String(args.metadata ?? ""), registration: String(args.registration ?? ""), bundle: String(args.bundle ?? ""), ...(typeof args["content-type"] === "string" ? { contentType: args["content-type"] } : {}) }) } : command === "policy.add" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await addBuyerPrivacyRule(home, String(args["capability-id"] ?? "")) } : command === "policy.approve-seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await approveBuyerSeller(home, String(args["capability-id"] ?? "")) } : command === "memory.insights" ? await (async () => { const app = new Phase3App(home); try {
23
+ return { schemaVersion: "supercuts.memory-insights.v1", ok: true, command, data: await app.memoryInsights() };
24
+ }
25
+ finally {
26
+ app.close();
27
+ } })() : command === "onboarding.doctor" ? { schemaVersion: "supercuts.onboarding.v1", ok: true, command, data: await onboardingDoctor(home, typeof args.config === "string" ? args.config : undefined) } : await (async () => { const app = new Phase3App(home); try {
20
28
  return await app.execute(command, args);
21
29
  }
22
30
  finally {
package/dist/dashboard.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createServer } from "node:http";
2
2
  import { readDemoEvents, recordedReplay } from "./events.js";
3
- const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Supercuts funded-run evidence</title><style>body{font:15px system-ui;margin:0;background:#0b1020;color:#eef2ff}header{position:sticky;top:0;padding:18px 24px;background:#111936;border-bottom:1px solid #334155}main{max-width:980px;margin:auto;padding:24px}.event{display:grid;grid-template-columns:9rem 10rem 1fr;gap:14px;padding:14px;border-bottom:1px solid #27334f}.mode{color:#fbbf24;font-weight:700}.complete,.allowed{color:#86efac}.failed,.rejected{color:#fca5a5}code{color:#bfdbfe;white-space:pre-wrap}</style></head><body><header><b>Supercuts</b> · <span class="mode" id="mode">loading evidence…</span></header><main id="events"></main><script>const out=document.querySelector('#events');const source=new EventSource('/events');source.onmessage=e=>{const v=JSON.parse(e.data);document.querySelector('#mode').textContent=v.mode==='live'?'LIVE FUNDED RUN':'RECORDED FUNDED RUN';const n=document.createElement('div');n.className='event';n.innerHTML='<span>#'+v.sequence+' '+v.actor+'</span><span class="'+v.status+'">'+v.stage+' · '+v.status+'</span><code></code>';n.querySelector('code').textContent=JSON.stringify(v.publicDetails,null,2);out.append(n)};</script></body></html>`;
3
+ const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Supercuts funded-run evidence</title><style>body{font:15px system-ui;margin:0;background:#0b1020;color:#eef2ff}header{position:sticky;top:0;padding:18px 24px;background:#111936;border-bottom:1px solid #334155}main{max-width:980px;margin:auto;padding:24px}.event{display:grid;grid-template-columns:9rem 10rem 1fr;gap:14px;padding:14px;border-bottom:1px solid #27334f}.mode{color:#fbbf24;font-weight:700}.complete,.allowed{color:#86efac}.failed,.rejected{color:#fca5a5}code{color:#bfdbfe;white-space:pre-wrap}#learned{margin:0 0 24px;padding:18px;border:1px solid #fbbf24;border-radius:10px;background:#1a1f3a}#learned h2{margin:0 0 6px;font-size:17px}#learned p{margin:0 0 12px;color:#fde68a;font-weight:600}table{width:100%;border-collapse:collapse;font-size:14px}th,td{text-align:left;padding:6px 8px;border-bottom:1px solid #27334f}th{color:#94a3b8;font-weight:500}.event.memory{background:#1a1f3a}</style></head><body><header><b>Supercuts</b> · <span class="mode" id="mode">loading evidence…</span></header><main><section id="learned" hidden><h2>What Supercuts learned from Walrus Memory</h2><p></p><table><thead><tr><th>Seller</th><th>Capability</th><th>Reliability</th><th>Succeeded / failed</th><th>Last outcome</th><th>Median latency</th></tr></thead><tbody></tbody></table></section><div id="events"></div></main><script>const out=document.querySelector('#events'),learned=document.querySelector('#learned');function renderLearned(d){learned.hidden=false;const w=d.withMemory||{},o=d.withoutMemory;learned.querySelector('p').textContent=d.changedSeller?'Memory changed who gets paid: '+(o?o.sellerId.slice(0,12):'no eligible seller')+' \u2192 '+String(w.sellerId).slice(0,12):(d.changedRail||d.changedRisk||d.changedMaximum)?'Memory changed how this payment settles: '+w.rail+', risk '+w.riskLevel+', max '+w.maximumAmount:'Memory confirmed the default choice';const t=learned.querySelector('tbody');t.textContent='';for(const s of d.scorecards||[]){const r=document.createElement('tr');for(const c of [String(s.counterparty).slice(0,12),s.capabilityId,Math.round(s.reliability*100)+'%',s.successes+' / '+s.failures,s.lastOutcome||'\u2014',s.medianLatencyMs!=null?(s.medianLatencyMs/1000).toFixed(1)+'s':'\u2014']){const td=document.createElement('td');td.textContent=c;r.append(td)}t.append(r)}}const source=new EventSource('/events');source.onmessage=e=>{const v=JSON.parse(e.data);document.querySelector('#mode').textContent=v.mode==='live'?'LIVE FUNDED RUN':'RECORDED FUNDED RUN';if(v.stage==='memory-influence')renderLearned(v.publicDetails);const n=document.createElement('div');n.className=v.stage.startsWith('memory')?'event memory':'event';n.innerHTML='<span>#'+v.sequence+' '+v.actor+'</span><span class="'+v.status+'">'+v.stage+' · '+v.status+'</span><code></code>';n.querySelector('code').textContent=JSON.stringify(v.publicDetails,null,2);out.append(n)};</script></body></html>`;
4
4
  export function serveEventDashboard(path, port, replay = false) {
5
5
  const server = createServer((request, response) => {
6
6
  if (request.url === "/") {
@@ -16,8 +16,6 @@ export async function createConfiguredDealEngine(app) {
16
16
  fail("MANDATE_COMMAND_FORBIDDEN", "owner mandate does not allow composite deal execution");
17
17
  const path = join(app.home, "deal-ports.mjs");
18
18
  if (!existsSync(path)) {
19
- if (!app.store.getSecret("live:memory-config"))
20
- fail("LIVE_MEMORY_CONFIG_MISSING", "live composite deals require an owner-only Walrus Memory configuration");
21
19
  return new DealEngine(new DealJournal(app.store), new LiveDealPorts(app, mandate), mandate);
22
20
  }
23
21
  if ((statSync(path).mode & 0o077) !== 0)
@@ -35,6 +35,16 @@ export interface DealOutcomeQueued {
35
35
  }
36
36
  /** A port owns transport, signing, and finality checks. It must be safe to call
37
37
  * again with the same child IDs after a process crash. */
38
+ export interface SellerFault {
39
+ stage: "negotiation" | "delivery";
40
+ code: string;
41
+ paymentDigest?: string;
42
+ }
43
+ /**
44
+ * Codes the buyer runtime verified against seller-supplied evidence. Buyer-side, network, and Walrus readback
45
+ * errors are excluded: a readback mismatch can come from an aggregator, not the seller.
46
+ */
47
+ export declare const SELLER_FAULT_CODES: Set<string>;
38
48
  export interface DealPorts {
39
49
  readBlob(blobId: string): Promise<Uint8Array>;
40
50
  discover(input: ExecuteDealInput): Promise<SignedCapabilityRegistration>;
@@ -50,6 +60,8 @@ export interface DealPorts {
50
60
  unresolved: true;
51
61
  }>;
52
62
  inventory(blobId: string): Promise<boolean>;
63
+ /** Optional: queue a verified seller-fault memory so later discovery demotes this seller. Best effort. */
64
+ rememberFailure?(input: ExecuteDealInput, plan: SettlementPlanV1, fault: SellerFault): Promise<void>;
53
65
  }
54
66
  export declare class DealEngine {
55
67
  private readonly journal;
@@ -58,5 +70,6 @@ export declare class DealEngine {
58
70
  constructor(journal: DealJournal, ports: DealPorts, mandate: AgentMandate);
59
71
  execute(input: ExecuteDealInput): Promise<DealView>;
60
72
  resume(operationId: string): Promise<DealView>;
73
+ private rememberSellerFault;
61
74
  private finishOutcome;
62
75
  }
@@ -3,6 +3,11 @@ import { validateCapabilityPayload, verifySignedCapability } from "./capability.
3
3
  import { dealChildId } from "./deal.js";
4
4
  import { fail, SupercutsError } from "./errors.js";
5
5
  import { canonicalJson, validateSettlementPlan } from "./settlement.js";
6
+ /**
7
+ * Codes the buyer runtime verified against seller-supplied evidence. Buyer-side, network, and Walrus readback
8
+ * errors are excluded: a readback mismatch can come from an aggregator, not the seller.
9
+ */
10
+ export const SELLER_FAULT_CODES = new Set(["AGREEMENT_BINDING_MISMATCH", "DEAL_RESOURCE_MISMATCH", "DEAL_AGREEMENT_INVALID", "DEAL_DELIVERY_UNVERIFIED", "CAPABILITY_PAYLOAD_INVALID"]);
6
11
  const hash = (value) => createHash("sha256").update(canonicalJson(value)).digest("hex");
7
12
  export class DealEngine {
8
13
  journal;
@@ -91,20 +96,26 @@ export class DealEngine {
91
96
  let negotiation = this.journal.step(operationId, "negotiation");
92
97
  if (view.state === "planned")
93
98
  view = this.journal.advance(operationId, "planned", "negotiating");
94
- if (!negotiation) {
95
- const started = Date.now();
96
- negotiation = await this.ports.negotiate(input, registration, plan, dealChildId(operationId, "agreement"));
97
- if (!negotiation.agreementBlobId || !/^[0-9a-f]{64}$/.test(negotiation.agreementCiphertextHash) || !Array.isArray(negotiation.transcriptBlobs) || !negotiation.transcriptBlobs.length)
98
- fail("DEAL_AGREEMENT_INVALID", "seller agreement or Walrus transcript evidence is incomplete");
99
- this.journal.saveStep(operationId, "negotiation", { planHash: plan.planHash }, negotiation);
100
- this.journal.recordTiming(operationId, "negotiation", Date.now() - started);
99
+ try {
100
+ if (!negotiation) {
101
+ const started = Date.now();
102
+ negotiation = await this.ports.negotiate(input, registration, plan, dealChildId(operationId, "agreement"));
103
+ if (!negotiation.agreementBlobId || !/^[0-9a-f]{64}$/.test(negotiation.agreementCiphertextHash) || !Array.isArray(negotiation.transcriptBlobs) || !negotiation.transcriptBlobs.length)
104
+ fail("DEAL_AGREEMENT_INVALID", "seller agreement or Walrus transcript evidence is incomplete");
105
+ this.journal.saveStep(operationId, "negotiation", { planHash: plan.planHash }, negotiation);
106
+ this.journal.recordTiming(operationId, "negotiation", Date.now() - started);
107
+ }
108
+ const agreementReadStarted = Date.now();
109
+ for (const item of [...negotiation.transcriptBlobs, { blobId: negotiation.agreementBlobId, ciphertextHash: negotiation.agreementCiphertextHash }]) {
110
+ if (!item.blobId || !/^[0-9a-f]{64}$/.test(item.ciphertextHash) || createHash("sha256").update(await this.ports.readBlob(item.blobId)).digest("hex") !== item.ciphertextHash)
111
+ fail("WALRUS_BLOB_MISMATCH", "encrypted negotiation or agreement differs from Walrus readback");
112
+ }
113
+ this.journal.recordTiming(operationId, "agreement_readback", Date.now() - agreementReadStarted);
101
114
  }
102
- const agreementReadStarted = Date.now();
103
- for (const item of [...negotiation.transcriptBlobs, { blobId: negotiation.agreementBlobId, ciphertextHash: negotiation.agreementCiphertextHash }]) {
104
- if (!item.blobId || !/^[0-9a-f]{64}$/.test(item.ciphertextHash) || createHash("sha256").update(await this.ports.readBlob(item.blobId)).digest("hex") !== item.ciphertextHash)
105
- fail("WALRUS_BLOB_MISMATCH", "encrypted negotiation or agreement differs from Walrus readback");
115
+ catch (error) {
116
+ await this.rememberSellerFault(input, plan, "negotiation", undefined, error);
117
+ throw error;
106
118
  }
107
- this.journal.recordTiming(operationId, "agreement_readback", Date.now() - agreementReadStarted);
108
119
  if (view.state === "negotiating")
109
120
  view = this.journal.advance(operationId, "negotiating", "agreed");
110
121
  this.journal.recordBlob(operationId, "agreement", negotiation.agreementCiphertextHash, negotiation.agreementBlobId);
@@ -139,29 +150,35 @@ export class DealEngine {
139
150
  let artifact = this.journal.step(operationId, "delivery");
140
151
  if (view.state === "paid")
141
152
  view = this.journal.advance(operationId, "paid", "delivery_pending");
142
- if (!artifact) {
143
- const started = Date.now();
144
- const candidate = await this.ports.deliver(input, plan, negotiation, payment, dealChildId(operationId, "delivery"));
145
- if ("pending" in candidate)
146
- return this.journal.get(operationId);
147
- if (!candidate.blobId || !/^[0-9a-f]{64}$/.test(candidate.ciphertextHash) || !/^[0-9a-f]{64}$/.test(candidate.artifactSha256))
148
- fail("DEAL_DELIVERY_UNVERIFIED", "seller delivery evidence is incomplete");
149
- if (typeof candidate.artifactBase64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(candidate.artifactBase64))
150
- fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact encoding is invalid");
151
- const artifactBytes = Buffer.from(candidate.artifactBase64, "base64");
152
- if (!artifactBytes.length || artifactBytes.length > 512 * 1024 || createHash("sha256").update(artifactBytes).digest("hex") !== candidate.artifactSha256)
153
- fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact hash or size is invalid");
154
- validateCapabilityPayload(registration.manifest, "output", candidate.metadata);
155
- artifact = candidate;
156
- this.journal.saveStep(operationId, "delivery", { paymentDigest: payment.transactionDigest }, artifact);
157
- this.journal.recordTiming(operationId, "delivery", Date.now() - started);
153
+ try {
154
+ if (!artifact) {
155
+ const started = Date.now();
156
+ const candidate = await this.ports.deliver(input, plan, negotiation, payment, dealChildId(operationId, "delivery"));
157
+ if ("pending" in candidate)
158
+ return this.journal.get(operationId);
159
+ if (!candidate.blobId || !/^[0-9a-f]{64}$/.test(candidate.ciphertextHash) || !/^[0-9a-f]{64}$/.test(candidate.artifactSha256))
160
+ fail("DEAL_DELIVERY_UNVERIFIED", "seller delivery evidence is incomplete");
161
+ if (typeof candidate.artifactBase64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(candidate.artifactBase64))
162
+ fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact encoding is invalid");
163
+ const artifactBytes = Buffer.from(candidate.artifactBase64, "base64");
164
+ if (!artifactBytes.length || artifactBytes.length > 512 * 1024 || createHash("sha256").update(artifactBytes).digest("hex") !== candidate.artifactSha256)
165
+ fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact hash or size is invalid");
166
+ validateCapabilityPayload(registration.manifest, "output", candidate.metadata);
167
+ artifact = candidate;
168
+ this.journal.saveStep(operationId, "delivery", { paymentDigest: payment.transactionDigest }, artifact);
169
+ this.journal.recordTiming(operationId, "delivery", Date.now() - started);
170
+ }
171
+ if (createHash("sha256").update(Buffer.from(artifact.artifactBase64, "base64")).digest("hex") !== artifact.artifactSha256)
172
+ fail("DEAL_DELIVERY_UNVERIFIED", "saved artifact hash differs from delivery evidence");
173
+ const deliveryReadStarted = Date.now();
174
+ if (createHash("sha256").update(await this.ports.readBlob(artifact.blobId)).digest("hex") !== artifact.ciphertextHash)
175
+ fail("WALRUS_BLOB_MISMATCH", "encrypted delivery differs from Walrus readback");
176
+ this.journal.recordTiming(operationId, "delivery_readback", Date.now() - deliveryReadStarted);
177
+ }
178
+ catch (error) {
179
+ await this.rememberSellerFault(input, plan, "delivery", payment.transactionDigest, error);
180
+ throw error;
158
181
  }
159
- if (createHash("sha256").update(Buffer.from(artifact.artifactBase64, "base64")).digest("hex") !== artifact.artifactSha256)
160
- fail("DEAL_DELIVERY_UNVERIFIED", "saved artifact hash differs from delivery evidence");
161
- const deliveryReadStarted = Date.now();
162
- if (createHash("sha256").update(await this.ports.readBlob(artifact.blobId)).digest("hex") !== artifact.ciphertextHash)
163
- fail("WALRUS_BLOB_MISMATCH", "encrypted delivery differs from Walrus readback");
164
- this.journal.recordTiming(operationId, "delivery_readback", Date.now() - deliveryReadStarted);
165
182
  this.journal.recordBlob(operationId, "delivery", artifact.ciphertextHash, artifact.blobId);
166
183
  if (view.state === "delivery_pending")
167
184
  view = this.journal.advance(operationId, "delivery_pending", "delivered");
@@ -169,6 +186,14 @@ export class DealEngine {
169
186
  view = this.journal.advance(operationId, "delivered", "outcome_pending");
170
187
  return this.finishOutcome(operationId, input);
171
188
  }
189
+ async rememberSellerFault(input, plan, stage, paymentDigest, error) {
190
+ if (!(error instanceof SupercutsError) || !SELLER_FAULT_CODES.has(error.code) || !this.ports.rememberFailure)
191
+ return;
192
+ try {
193
+ await this.ports.rememberFailure(input, plan, { stage, code: error.code, ...(paymentDigest ? { paymentDigest } : {}) });
194
+ }
195
+ catch { /* the original seller fault stays the reported error */ }
196
+ }
172
197
  async finishOutcome(operationId, input) {
173
198
  const view = this.journal.get(operationId);
174
199
  if (view.evidence.outcomeMemory === "indexed")
@@ -179,10 +204,10 @@ export class DealEngine {
179
204
  if (existing?.blobId) {
180
205
  const indexed = await this.ports.inventory(existing.blobId);
181
206
  this.journal.recordOutcome(operationId, existing.payloadHash, existing.blobId, indexed);
182
- return indexed ? this.journal.advance(operationId, "outcome_pending", "complete") : this.journal.get(operationId);
207
+ return this.journal.advance(operationId, "outcome_pending", "complete");
183
208
  }
184
209
  if (existing)
185
- return view;
210
+ return this.journal.advance(operationId, "outcome_pending", "complete");
186
211
  const plan = this.journal.step(operationId, "planning") ?? fail("DEAL_PLAN_MISSING", "saved deal plan is missing");
187
212
  const payment = this.journal.step(operationId, "payment") ?? fail("DEAL_PAYMENT_UNVERIFIED", "saved deal payment is missing");
188
213
  const artifact = this.journal.step(operationId, "delivery") ?? fail("DEAL_DELIVERY_UNVERIFIED", "saved deal delivery is missing");
@@ -193,11 +218,11 @@ export class DealEngine {
193
218
  this.journal.recordTiming(operationId, "outcome_queue", Date.now() - started);
194
219
  if ("unresolved" in candidate) {
195
220
  this.journal.setOutcomeUnresolved(operationId);
196
- return this.journal.get(operationId);
221
+ return this.journal.advance(operationId, "outcome_pending", "complete");
197
222
  }
198
223
  if ("queued" in candidate) {
199
224
  this.journal.queueOutcome(operationId, candidate.payloadHash);
200
- return this.journal.get(operationId);
225
+ return this.journal.advance(operationId, "outcome_pending", "complete");
201
226
  }
202
227
  if (!candidate.blobId || !/^[0-9a-f]{64}$/.test(candidate.payloadHash))
203
228
  fail("DEAL_MEMORY_INVALID", "outcome memory is incomplete");
@@ -206,6 +231,6 @@ export class DealEngine {
206
231
  }
207
232
  const indexed = await this.ports.inventory(outcome.blobId);
208
233
  this.journal.recordOutcome(operationId, outcome.payloadHash, outcome.blobId, indexed);
209
- return indexed ? this.journal.advance(operationId, "outcome_pending", "complete") : this.journal.get(operationId);
234
+ return this.journal.advance(operationId, "outcome_pending", "complete");
210
235
  }
211
236
  }
package/dist/deal.d.ts CHANGED
@@ -43,6 +43,41 @@ export interface DealEvidenceView {
43
43
  inventory: "pending" | "indexed" | "missing" | "unresolved";
44
44
  paymentFinalized: boolean;
45
45
  paymentDigest?: string;
46
+ /** What Walrus Memory changed about this payment versus the same mandate with no memory. */
47
+ memoryInfluence?: {
48
+ changedSeller: boolean;
49
+ changedRail: boolean;
50
+ changedRisk: boolean;
51
+ changedMaximum: boolean;
52
+ withoutMemory?: {
53
+ sellerId: string;
54
+ capabilityId: string;
55
+ rail: string;
56
+ riskLevel: string;
57
+ maximumAmount: string;
58
+ };
59
+ withMemory: {
60
+ sellerId: string;
61
+ capabilityId: string;
62
+ rail: string;
63
+ riskLevel: string;
64
+ maximumAmount: string;
65
+ };
66
+ citedBlobIds: string[];
67
+ };
68
+ /** Learned seller scorecards used for discovery; counts and blob IDs only, never memory plaintext. */
69
+ memoryRanking?: Array<{
70
+ capabilityId: string;
71
+ counterparty: string;
72
+ reliability: number;
73
+ observations: number;
74
+ successes: number;
75
+ failures: number;
76
+ unresolved: number;
77
+ lastOutcome?: string;
78
+ medianLatencyMs?: number;
79
+ priceMinimum: string;
80
+ }>;
46
81
  }
47
82
  export interface DealDelivery {
48
83
  dealId: string;
package/dist/deal.js CHANGED
@@ -176,7 +176,13 @@ export class DealJournal {
176
176
  const blobs = this.store.db.prepare("SELECT kind,blob_id,status FROM deal_blobs WHERE operation_id=?").all(row.operation_id);
177
177
  const memory = this.store.db.prepare("SELECT blob_id,status FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(row.operation_id);
178
178
  const transcript = this.step(row.operation_id, "negotiation");
179
- return { version: "supercuts.deal-evidence.v1", dealId, transcriptBlobIds: transcript?.transcriptBlobs?.map((item) => item.blobId) ?? [], ...(blobs.find((item) => item.kind === "agreement")?.blob_id ? { agreementBlobId: blobs.find((item) => item.kind === "agreement").blob_id } : {}), ...(blobs.find((item) => item.kind === "delivery")?.blob_id ? { deliveryBlobId: blobs.find((item) => item.kind === "delivery").blob_id } : {}), ...(memory?.blob_id ? { outcomeMemoryBlobId: memory.blob_id } : {}), ciphertextReadback: blobs.some((item) => item.status === "unresolved") ? "unresolved" : ["agreement", "delivery"].every((kind) => blobs.some((item) => item.kind === kind && item.status === "verified")) ? "verified" : "pending", inventory: memory?.status === "indexed" ? "indexed" : memory?.status === "unresolved" ? "unresolved" : "pending", paymentFinalized: view.evidence.payment === "finalized", ...(view.paymentDigest ? { paymentDigest: view.paymentDigest } : {}) };
179
+ const hasRecords = Boolean(this.store.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='app_records'").get());
180
+ const record = (scope, id) => { if (!hasRecords)
181
+ return undefined; const found = this.store.db.prepare("SELECT value FROM app_records WHERE scope=? AND id=?").get(scope, id); return found ? JSON.parse(found.value) : undefined; };
182
+ const influence = record("memory-influence", `deal:${row.operation_id}`);
183
+ const ranking = record("deal-ranking", row.operation_id);
184
+ const memoryRanking = ranking?.scorecards.map(({ capabilityId, counterparty, reliability, observations, successes, failures, unresolved, lastOutcome, medianLatencyMs, priceMinimum }) => ({ capabilityId, counterparty, reliability, observations, successes, failures, unresolved, ...(lastOutcome ? { lastOutcome } : {}), ...(medianLatencyMs !== undefined ? { medianLatencyMs } : {}), priceMinimum }));
185
+ return { version: "supercuts.deal-evidence.v1", dealId, transcriptBlobIds: transcript?.transcriptBlobs?.map((item) => item.blobId) ?? [], ...(blobs.find((item) => item.kind === "agreement")?.blob_id ? { agreementBlobId: blobs.find((item) => item.kind === "agreement").blob_id } : {}), ...(blobs.find((item) => item.kind === "delivery")?.blob_id ? { deliveryBlobId: blobs.find((item) => item.kind === "delivery").blob_id } : {}), ...(memory?.blob_id ? { outcomeMemoryBlobId: memory.blob_id } : {}), ciphertextReadback: blobs.some((item) => item.status === "unresolved") ? "unresolved" : ["agreement", "delivery"].every((kind) => blobs.some((item) => item.kind === kind && item.status === "verified")) ? "verified" : "pending", inventory: memory?.status === "indexed" ? "indexed" : memory?.status === "unresolved" ? "unresolved" : "pending", paymentFinalized: view.evidence.payment === "finalized", ...(view.paymentDigest ? { paymentDigest: view.paymentDigest } : {}), ...(influence ? { memoryInfluence: influence } : {}), ...(memoryRanking ? { memoryRanking } : {}) };
180
186
  }
181
187
  delivery(dealId) {
182
188
  const row = this.store.db.prepare("SELECT operation_id FROM deal_operations WHERE deal_id=?").get(dealId);
@@ -213,8 +219,8 @@ export class DealJournal {
213
219
  fail("DEAL_PAYMENT_UNVERIFIED", "confirmed payment evidence is required");
214
220
  if (next === "delivered" && !(this.store.db.prepare("SELECT 1 FROM deal_blobs WHERE operation_id=? AND kind='delivery' AND status='verified'").get(operationId)))
215
221
  fail("DEAL_DELIVERY_UNVERIFIED", "verified delivery evidence is required");
216
- if (next === "complete" && !(this.store.db.prepare("SELECT 1 FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome' AND status='indexed'").get(operationId)))
217
- fail("DEAL_MEMORY_UNVERIFIED", "indexed outcome memory is required");
222
+ if (next === "complete" && (this.get(operationId).evidence.payment !== "finalized" || this.get(operationId).evidence.delivery !== "verified"))
223
+ fail("DEAL_DELIVERY_UNVERIFIED", "finalized payment and verified delivery are required before deal completion");
218
224
  this.store.db.prepare("UPDATE deal_operations SET state=?,rail=COALESCE(?,rail),plan_hash=COALESCE(?,plan_hash),updated_at=? WHERE operation_id=?")
219
225
  .run(next, result?.rail ?? null, result?.planHash ?? null, Date.now(), operationId);
220
226
  this.store.db.exec("COMMIT");
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenAI-compatible embedding adapter for local models (`supercuts-embedding-adapter`).
4
+ *
5
+ * Walrus Mainnet Memory indexes 1536-dimensional embeddings. This loopback proxy forwards requests to a
6
+ * local OpenAI-compatible endpoint (Ollama by default) and zero-pads shorter vectors to 1536 dimensions.
7
+ * Zero-padding preserves cosine similarity and Euclidean distance as long as every write and recall uses
8
+ * this same adapter and upstream model. Memory plaintext only reaches the configured upstream.
9
+ */
10
+ import { type Server } from "node:http";
11
+ export declare const TARGET_DIMENSIONS = 1536;
12
+ export declare function padEmbedding(embedding: unknown): number[];
13
+ interface EmbeddingPayload {
14
+ data: Array<{
15
+ embedding: unknown;
16
+ } & Record<string, unknown>>;
17
+ }
18
+ export declare function transformEmbeddingResponse(payload: unknown): EmbeddingPayload;
19
+ export interface EmbeddingAdapterOptions {
20
+ upstreamBaseUrl: string;
21
+ upstreamModel: string;
22
+ upstreamApiKey?: string;
23
+ fetchImpl?: typeof fetch;
24
+ }
25
+ export declare function createEmbeddingAdapter({ fetchImpl, upstreamBaseUrl, upstreamApiKey, upstreamModel, }: EmbeddingAdapterOptions): Server;
26
+ export {};