@supercuts-walrus/backend 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +249 -2
  3. package/dist/audit.d.ts +52 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/backend.d.ts +27 -0
  6. package/dist/backend.js +62 -0
  7. package/dist/buyer-setup.d.ts +6 -0
  8. package/dist/buyer-setup.js +241 -0
  9. package/dist/capability.d.ts +32 -0
  10. package/dist/capability.js +87 -0
  11. package/dist/claude.d.ts +44 -0
  12. package/dist/claude.js +98 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +29 -0
  15. package/dist/config.d.ts +20 -0
  16. package/dist/config.js +58 -0
  17. package/dist/dashboard.d.ts +5 -0
  18. package/dist/dashboard.js +21 -0
  19. package/dist/deal-adapter.d.ts +3 -0
  20. package/dist/deal-adapter.js +39 -0
  21. package/dist/deal-engine.d.ts +62 -0
  22. package/dist/deal-engine.js +211 -0
  23. package/dist/deal-wire.d.ts +16 -0
  24. package/dist/deal-wire.js +65 -0
  25. package/dist/deal.d.ts +79 -0
  26. package/dist/deal.js +228 -0
  27. package/dist/demo.d.ts +2 -0
  28. package/dist/demo.js +449 -0
  29. package/dist/errors.d.ts +6 -0
  30. package/dist/errors.js +13 -0
  31. package/dist/events.d.ts +27 -0
  32. package/dist/events.js +68 -0
  33. package/dist/facilitator.d.ts +21 -0
  34. package/dist/facilitator.js +173 -0
  35. package/dist/finality.d.ts +2 -0
  36. package/dist/finality.js +27 -0
  37. package/dist/hosted-exit-cli.d.ts +2 -0
  38. package/dist/hosted-exit-cli.js +37 -0
  39. package/dist/hosted-exit.d.ts +24 -0
  40. package/dist/hosted-exit.js +70 -0
  41. package/dist/hosted-seller-import.d.ts +2 -0
  42. package/dist/hosted-seller-import.js +70 -0
  43. package/dist/hosted-seller-repository.d.ts +31 -0
  44. package/dist/hosted-seller-repository.js +214 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.js +37 -0
  47. package/dist/indexer.d.ts +63 -0
  48. package/dist/indexer.js +60 -0
  49. package/dist/live-deal-ports.d.ts +41 -0
  50. package/dist/live-deal-ports.js +212 -0
  51. package/dist/live-index.d.ts +28 -0
  52. package/dist/live-index.js +149 -0
  53. package/dist/live-runtime.d.ts +82 -0
  54. package/dist/live-runtime.js +570 -0
  55. package/dist/live-seller-transport.d.ts +30 -0
  56. package/dist/live-seller-transport.js +169 -0
  57. package/dist/live-seller-verifier.d.ts +11 -0
  58. package/dist/live-seller-verifier.js +65 -0
  59. package/dist/mandate.d.ts +34 -0
  60. package/dist/mandate.js +178 -0
  61. package/dist/mcp-bootstrap.d.ts +2 -0
  62. package/dist/mcp-bootstrap.js +39 -0
  63. package/dist/mcp.d.ts +2 -0
  64. package/dist/mcp.js +187 -0
  65. package/dist/memory-worker.d.ts +2 -0
  66. package/dist/memory-worker.js +38 -0
  67. package/dist/memory.d.ts +15 -0
  68. package/dist/memory.js +17 -0
  69. package/dist/memwal.d.ts +35 -0
  70. package/dist/memwal.js +83 -0
  71. package/dist/negotiation.d.ts +23 -0
  72. package/dist/negotiation.js +58 -0
  73. package/dist/notes.d.ts +11 -0
  74. package/dist/notes.js +26 -0
  75. package/dist/onboarding-doctor.d.ts +1 -0
  76. package/dist/onboarding-doctor.js +76 -0
  77. package/dist/phase3.d.ts +100 -0
  78. package/dist/phase3.js +1144 -0
  79. package/dist/procurement.d.ts +8 -0
  80. package/dist/procurement.js +19 -0
  81. package/dist/protocol-engine.d.ts +121 -0
  82. package/dist/protocol-engine.js +149 -0
  83. package/dist/prover.d.ts +28 -0
  84. package/dist/prover.js +70 -0
  85. package/dist/relayer.d.ts +32 -0
  86. package/dist/relayer.js +43 -0
  87. package/dist/remote-buyer.d.ts +67 -0
  88. package/dist/remote-buyer.js +231 -0
  89. package/dist/resource-server.d.ts +2 -0
  90. package/dist/resource-server.js +24 -0
  91. package/dist/resource.d.ts +22 -0
  92. package/dist/resource.js +55 -0
  93. package/dist/seam.d.ts +2 -0
  94. package/dist/seam.js +117 -0
  95. package/dist/seller-catalog-generate.d.ts +2 -0
  96. package/dist/seller-catalog-generate.js +68 -0
  97. package/dist/seller-database.d.ts +41 -0
  98. package/dist/seller-database.js +80 -0
  99. package/dist/seller-directory-cli.d.ts +2 -0
  100. package/dist/seller-directory-cli.js +24 -0
  101. package/dist/seller-directory.d.ts +5 -0
  102. package/dist/seller-directory.js +50 -0
  103. package/dist/seller-live-cli.d.ts +2 -0
  104. package/dist/seller-live-cli.js +53 -0
  105. package/dist/seller-provision.d.ts +2 -0
  106. package/dist/seller-provision.js +84 -0
  107. package/dist/seller-register-cli.d.ts +2 -0
  108. package/dist/seller-register-cli.js +40 -0
  109. package/dist/seller-runtime.d.ts +60 -0
  110. package/dist/seller-runtime.js +96 -0
  111. package/dist/seller-service.d.ts +34 -0
  112. package/dist/seller-service.js +416 -0
  113. package/dist/seller-state.d.ts +19 -0
  114. package/dist/seller-state.js +60 -0
  115. package/dist/seller-stdio.d.ts +6 -0
  116. package/dist/seller-stdio.js +29 -0
  117. package/dist/seller-v2-provision.d.ts +2 -0
  118. package/dist/seller-v2-provision.js +71 -0
  119. package/dist/seller-watch.d.ts +2 -0
  120. package/dist/seller-watch.js +45 -0
  121. package/dist/settlement.d.ts +95 -0
  122. package/dist/settlement.js +141 -0
  123. package/dist/store.d.ts +36 -0
  124. package/dist/store.js +193 -0
  125. package/dist/sui.d.ts +49 -0
  126. package/dist/sui.js +77 -0
  127. package/dist/tatum.d.ts +17 -0
  128. package/dist/tatum.js +83 -0
  129. package/dist/transcript.d.ts +31 -0
  130. package/dist/transcript.js +52 -0
  131. package/dist/v2-exit-relayer.d.ts +34 -0
  132. package/dist/v2-exit-relayer.js +102 -0
  133. package/dist/v2-exit-worker.d.ts +2 -0
  134. package/dist/v2-exit-worker.js +97 -0
  135. package/dist/v2-exits.d.ts +30 -0
  136. package/dist/v2-exits.js +78 -0
  137. package/dist/v2-prefund.d.ts +19 -0
  138. package/dist/v2-prefund.js +43 -0
  139. package/dist/v2-relayer-cli.d.ts +2 -0
  140. package/dist/v2-relayer-cli.js +39 -0
  141. package/dist/wallet.d.ts +32 -0
  142. package/dist/wallet.js +90 -0
  143. package/dist/workflow-memory.d.ts +22 -0
  144. package/dist/workflow-memory.js +49 -0
  145. package/dist/x402-resource-service.d.ts +34 -0
  146. package/dist/x402-resource-service.js +154 -0
  147. package/dist/x402.d.ts +46 -0
  148. package/dist/x402.js +136 -0
  149. package/package.json +60 -3
@@ -0,0 +1,231 @@
1
+ import { createDecipheriv, createHash, createPrivateKey, createPublicKey, diffieHellman, hkdfSync, randomBytes } from "node:crypto";
2
+ import { isValidPersonalMessageSignature } from "@mysten/sui/verify";
3
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
4
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
5
+ import { validateCapabilityManifest, verifySignedCapability } from "./capability.js";
6
+ import { fail } from "./errors.js";
7
+ import { executeMandated } from "./mandate.js";
8
+ import { decryptSessionMessage, encryptSessionMessage } from "./negotiation.js";
9
+ const sha256 = (value) => createHash("sha256").update(value).digest("hex");
10
+ const num = (value, name) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : fail("SELLER_PREVIEW_INVALID", `${name} must be between zero and one`);
11
+ const halves = (content) => { const hash = sha256(content); return [BigInt(`0x${hash.slice(0, 32)}`).toString(), BigInt(`0x${hash.slice(32)}`).toString()]; };
12
+ export class RemoteBuyer {
13
+ app;
14
+ mandate;
15
+ http;
16
+ #origin;
17
+ constructor(app, mandate, origin, http = fetch) {
18
+ this.app = app;
19
+ this.mandate = mandate;
20
+ this.http = http;
21
+ this.#origin = new URL(origin);
22
+ if (this.#origin.protocol !== "https:" && !(this.#origin.protocol === "http:" && ["127.0.0.1", "localhost", "::1"].includes(this.#origin.hostname)))
23
+ fail("SELLER_ORIGIN_INVALID", "seller origin must use HTTPS except on loopback");
24
+ }
25
+ async request(path, init = {}) {
26
+ let response;
27
+ for (let attempt = 0; attempt < 2; attempt++) {
28
+ try {
29
+ response = await this.http(new URL(path, this.#origin), { ...init, headers: { ...(init.body ? { "content-type": "application/json" } : {}), ...(init.headers ?? {}) }, signal: init.signal ?? AbortSignal.timeout(90_000), redirect: "error" });
30
+ }
31
+ catch (error) {
32
+ if (attempt === 0)
33
+ continue;
34
+ return fail("REMOTE_SELLER_UNAVAILABLE", "seller transport failed after retry", error);
35
+ }
36
+ if (response.status !== 503 || attempt === 1)
37
+ break;
38
+ await response.arrayBuffer();
39
+ await new Promise((done) => setTimeout(done, 3000));
40
+ }
41
+ const received = response ?? fail("REMOTE_SELLER_UNAVAILABLE", "seller did not respond");
42
+ const value = await received.json();
43
+ if (!received.ok && received.status !== 202)
44
+ fail("REMOTE_SELLER_ERROR", `seller returned ${received.status}: ${String(value.error ?? "unknown error")}`);
45
+ return value;
46
+ }
47
+ async catalog() {
48
+ const listed = await this.request("/v1/catalog"), entries = listed.capabilities;
49
+ if (!Array.isArray(entries) || !entries.length || entries.length > 100)
50
+ fail("SELLER_CATALOG_INVALID", "remote catalog is empty or too large");
51
+ const discovered = (await this.app.execute("capability.discover", { "blob-ids": Buffer.from(JSON.stringify(entries.map((entry) => entry.blobId))).toString("base64url") })).data.capabilities;
52
+ if (discovered.length !== entries.length)
53
+ fail("SELLER_CATALOG_INVALID", "Walrus discovery returned a different capability count");
54
+ for (let i = 0; i < entries.length; i++) {
55
+ const entry = entries[i];
56
+ validateCapabilityManifest(entry.manifest);
57
+ if (entry.manifest.manifestHash !== discovered[i].manifestHash || entry.blobId !== discovered[i].blobId || !this.mandate.allowedCounterparties.includes(entry.manifest.sellerId))
58
+ fail("SELLER_CATALOG_INVALID", "catalog differs from Walrus or owner allowlist");
59
+ await verifySignedCapability({ manifest: entry.manifest, manifestBlobId: entry.blobId, transportPublicKey: entry.transportPublicKey, sellerSignature: entry.signature });
60
+ }
61
+ return { capabilities: entries };
62
+ }
63
+ async rankBriefs(input) {
64
+ if (!/^[1-9][0-9]*$/.test(input.budget))
65
+ fail("BUYER_BUDGET_INVALID", "budget must be positive MIST");
66
+ const budget = BigInt(input.budget), catalog = await this.catalog();
67
+ const analysis = catalog.capabilities.find((entry) => entry.manifest.capability === "sui.competitor-analysis" && entry.manifest.supportedRails.includes("shielded")) ?? fail("ANALYSIS_UNAVAILABLE", "no shielded competitor analysis is available");
68
+ const analysisPrice = BigInt(analysis.manifest.priceBand.minimum), variants = analysis.preview.artifactHashes;
69
+ const candidates = catalog.capabilities.filter((entry) => {
70
+ const briefHash = entry.preview.artifactHashes?.default;
71
+ return entry.manifest.capability === "sui.market-brief" && entry.manifest.supportedRails.includes("x402") && typeof briefHash === "string" && /^[0-9a-f]{64}$/.test(briefHash) && typeof variants?.[briefHash] === "string" && BigInt(entry.manifest.priceBand.minimum) + analysisPrice <= budget;
72
+ });
73
+ if (candidates.length < 2)
74
+ fail("BRIEF_COMPARISON_UNAVAILABLE", "at least two eligible market briefs are required");
75
+ const scored = candidates.map((entry) => {
76
+ const preview = entry.preview, relevance = num(preview.relevance, "relevance"), sourceQuality = num(preview.sourceQuality, "sourceQuality"), freshnessDays = Number(preview.freshnessDays);
77
+ if (!Number.isFinite(freshnessDays) || freshnessDays < 0 || freshnessDays > 365)
78
+ fail("SELLER_PREVIEW_INVALID", "freshnessDays is invalid");
79
+ const freshness = Math.max(0, 1 - freshnessDays / 30), price = BigInt(entry.manifest.priceBand.minimum), totalPrice = price + analysisPrice, priceScore = Number((budget - totalPrice) * 1000n / budget) / 1000;
80
+ const score = Math.round((0.35 * relevance + 0.30 * sourceQuality + 0.20 * freshness + 0.15 * priceScore) * 1000) / 1000;
81
+ return { capabilityId: entry.manifest.capabilityId, sellerId: entry.manifest.sellerId, priceMist: price.toString(), projectedTotalMist: totalPrice.toString(), score, components: { relevance, sourceQuality, freshness, price: priceScore }, preview: { title: preview.title, summary: preview.summary, citations: preview.citations, freshnessDays }, manifestHash: entry.manifest.manifestHash };
82
+ }).sort((a, b) => b.score - a.score || (BigInt(a.priceMist) < BigInt(b.priceMist) ? -1 : BigInt(a.priceMist) > BigInt(b.priceMist) ? 1 : a.capabilityId.localeCompare(b.capabilityId)));
83
+ return { topic: input.topic, budgetMist: input.budget, analysisCapabilityId: analysis.manifest.capabilityId, analysisPriceMist: analysisPrice.toString(), rubric: { relevance: 0.35, sourceQuality: 0.30, freshness: 0.20, price: 0.15 }, rankings: scored, winner: scored[0], note: "Preview quality fields are seller supplied; the buyer checks artifact citations after delivery." };
84
+ }
85
+ async negotiate(input) {
86
+ const catalog = await this.catalog(), entry = catalog.capabilities.find((item) => item.manifest.capabilityId === input.capabilityId) ?? fail("SELLER_CAPABILITY_UNKNOWN", "capability is not in the verified catalog");
87
+ if (!/^[1-9][0-9]*$/.test(input.opening) || !/^[1-9][0-9]*$/.test(input.maximum) || BigInt(input.opening) > BigInt(input.maximum) || BigInt(input.maximum) < BigInt(entry.manifest.priceBand.minimum) || BigInt(input.maximum) > BigInt(entry.manifest.priceBand.maximum))
88
+ fail("NEGOTIATION_BOUNDS_INVALID", "offer bounds are outside the seller price band");
89
+ if (input.briefHash !== undefined && !/^[0-9a-f]{64}$/.test(input.briefHash))
90
+ fail("BUYER_BRIEF_HASH_INVALID", "brief hash is invalid");
91
+ const privateDer = this.app.store.getSecret("identity:x25519-private") ?? fail("IDENTITY_MISSING", "buyer negotiation key is missing"), publicKey = this.app.store.getSecret("identity:x25519-public")?.toString("base64url") ?? fail("IDENTITY_MISSING", "buyer negotiation key is missing");
92
+ const shared = diffieHellman({ privateKey: createPrivateKey({ key: privateDer, format: "der", type: "pkcs8" }), publicKey: createPublicKey({ key: Buffer.from(entry.transportPublicKey, "base64url"), format: "der", type: "spki" }) });
93
+ const key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(input.sessionId), Buffer.from("supercuts-negotiation-v1"), 32));
94
+ this.app.store.db.exec("CREATE TABLE IF NOT EXISTS remote_buyer_negotiations(session_id TEXT PRIMARY KEY, capability_id TEXT NOT NULL, run_id TEXT NOT NULL, offer_hash TEXT NOT NULL, request_ciphertext TEXT NOT NULL, buyer_signature TEXT NOT NULL, reply_json TEXT)");
95
+ const offer = { task: input.task.slice(0, 512), opening: input.opening, maximum: input.maximum, ...(input.briefHash ? { briefHash: input.briefHash } : {}) }, offerHash = sha256(JSON.stringify(offer));
96
+ const existing = this.app.store.db.prepare("SELECT capability_id AS capabilityId,run_id AS runId,offer_hash AS offerHash,request_ciphertext AS ciphertext,buyer_signature AS buyerSignature,reply_json AS replyJson FROM remote_buyer_negotiations WHERE session_id=?").get(input.sessionId);
97
+ if (existing && (existing.capabilityId !== input.capabilityId || existing.runId !== input.runId || existing.offerHash !== offerHash))
98
+ fail("NEGOTIATION_SESSION_CONFLICT", "session belongs to another offer or run");
99
+ const encrypted = existing?.ciphertext ?? encryptSessionMessage(key, input.sessionId, `${input.sessionId}:buyer:1`, Buffer.from(JSON.stringify(offer)));
100
+ const encodedSigner = this.app.store.getSecret("live:sui-private-key")?.toString() ?? fail("LIVE_CREDENTIAL_MISSING", "buyer signing key is missing"), decoded = decodeSuiPrivateKey(encodedSigner);
101
+ if (decoded.scheme !== "ED25519")
102
+ fail("SIGNER_UNSUPPORTED", "buyer requires an ED25519 signer");
103
+ const signer = Ed25519Keypair.fromSecretKey(decoded.secretKey), signedBytes = Buffer.from(`${input.sessionId}:${input.capabilityId}:${publicKey}:${sha256(Buffer.from(encrypted, "base64"))}`);
104
+ const buyerSignature = existing?.buyerSignature ?? (await signer.signPersonalMessage(signedBytes)).signature;
105
+ if (!existing)
106
+ this.app.store.db.prepare("INSERT INTO remote_buyer_negotiations(session_id,capability_id,run_id,offer_hash,request_ciphertext,buyer_signature) VALUES(?,?,?,?,?,?)").run(input.sessionId, input.capabilityId, input.runId, offerHash, encrypted, buyerSignature);
107
+ const result = existing?.replyJson ? JSON.parse(existing.replyJson) : await this.request("/v1/negotiate", { method: "POST", body: JSON.stringify({ capabilityId: input.capabilityId, sessionId: input.sessionId, runId: input.runId, buyerAddress: signer.toSuiAddress(), buyerPublicKey: publicKey, buyerSignature, ciphertext: encrypted }) });
108
+ const replyCiphertext = String(result.ciphertext ?? ""), transcriptHash = sha256(JSON.stringify({ request: sha256(Buffer.from(encrypted, "base64")), reply: sha256(Buffer.from(replyCiphertext, "base64")) }));
109
+ if (result.sessionId !== input.sessionId || result.capabilityId !== input.capabilityId || result.transcriptHash !== transcriptHash || !(await isValidPersonalMessageSignature(Buffer.from(`${input.sessionId}:${input.capabilityId}:${sha256(Buffer.from(replyCiphertext, "base64"))}:${transcriptHash}`), String(result.signature), { address: entry.manifest.sellerId })))
110
+ fail("NEGOTIATION_RESPONSE_INVALID", "seller response or signature is invalid");
111
+ const aggregator = this.app.profile.live?.walrusAggregatorUrl ?? fail("LIVE_WALRUS_CONFIG_MISSING", "buyer needs a Walrus aggregator");
112
+ for (const [blobId, ciphertext] of [[result.requestBlobId, encrypted], [result.replyBlobId, replyCiphertext]]) {
113
+ const read = await this.http(`${aggregator.replace(/\/$/, "")}/v1/blobs/${encodeURIComponent(String(blobId))}`, { signal: AbortSignal.timeout(60_000) });
114
+ if (!read.ok || !Buffer.from(await read.arrayBuffer()).equals(Buffer.from(ciphertext, "base64")))
115
+ fail("WALRUS_BLOB_MISMATCH", "encrypted offer or response differs from Walrus");
116
+ }
117
+ const response = JSON.parse(decryptSessionMessage(key, input.sessionId, `${input.sessionId}:seller:2`, replyCiphertext).toString());
118
+ if (response.capabilityId !== input.capabilityId || response.asset !== entry.manifest.asset || response.amount !== entry.manifest.priceBand.minimum || response.accepted !== true)
119
+ fail("NEGOTIATION_RESPONSE_INVALID", "seller response changed the approved price or capability");
120
+ if (!existing?.replyJson)
121
+ this.app.store.db.prepare("UPDATE remote_buyer_negotiations SET reply_json=? WHERE session_id=?").run(JSON.stringify(result), input.sessionId);
122
+ return { sessionId: input.sessionId, capabilityId: input.capabilityId, sellerId: entry.manifest.sellerId, opening: input.opening, amount: response.amount, asset: response.asset, accepted: true, transcriptHash, requestBlobId: result.requestBlobId, replyBlobId: result.replyBlobId };
123
+ }
124
+ async quote(input) {
125
+ const catalog = await this.catalog(), entry = catalog.capabilities.find((item) => item.manifest.capabilityId === input.capabilityId) ?? fail("SELLER_CAPABILITY_UNKNOWN", "capability is not in the verified catalog");
126
+ if (!entry.manifest.supportedRails.includes("x402"))
127
+ fail("SELLER_RAIL_UNSUPPORTED", "capability does not support public settlement");
128
+ const quote = await this.request("/v1/quote", { method: "POST", body: JSON.stringify(input) });
129
+ if (quote.capabilityId !== entry.manifest.capabilityId || quote.sellerId !== entry.manifest.sellerId || quote.asset !== entry.manifest.asset || quote.amount !== entry.manifest.priceBand.minimum || quote.requestId !== input.requestId || quote.operationId !== input.operationId)
130
+ fail("SELLER_QUOTE_INVALID", "seller quote differs from its manifest or request");
131
+ const endpoint = new URL(entry.manifest.endpoint), resource = new URL(String(quote.resourceUrl));
132
+ if (resource.origin !== endpoint.origin || resource.pathname !== `${endpoint.pathname.replace(/\/$/, "")}/${encodeURIComponent(input.requestId)}` || resource.search || resource.hash)
133
+ fail("SELLER_QUOTE_INVALID", "seller resource URL is outside its capability endpoint");
134
+ return quote;
135
+ }
136
+ async purchaseBrief(input) {
137
+ if (!/^[1-9][0-9]*$/.test(input.budget))
138
+ fail("BUYER_BUDGET_INVALID", "budget must be positive MIST");
139
+ const catalog = await this.catalog(), entry = catalog.capabilities.find((item) => item.manifest.capabilityId === input.capabilityId) ?? fail("SELLER_CAPABILITY_UNKNOWN", "capability is not in the verified catalog");
140
+ if (entry.manifest.capability !== "sui.market-brief")
141
+ fail("BUYER_CAPABILITY_INVALID", "selected capability is not a market brief");
142
+ const briefHash = entry.preview.artifactHashes?.default;
143
+ const analysis = catalog.capabilities.find((item) => item.manifest.capability === "sui.competitor-analysis" && typeof item.preview.artifactHashes?.[String(briefHash)] === "string") ?? fail("ANALYSIS_UNAVAILABLE", "analysis seller has no matching prepared variant");
144
+ if (BigInt(entry.manifest.priceBand.minimum) + BigInt(analysis.manifest.priceBand.minimum) > BigInt(input.budget))
145
+ fail("BUYER_BUDGET_EXCEEDED", "brief and analysis exceed the total task budget");
146
+ const minimum = BigInt(entry.manifest.priceBand.minimum), opening = minimum > 1n ? minimum - (minimum / 10n || 1n) : minimum;
147
+ const negotiated = await this.negotiate({ capabilityId: input.capabilityId, sessionId: input.requestId, runId: input.runId, task: input.task, opening: opening.toString(), maximum: (BigInt(input.budget) - BigInt(analysis.manifest.priceBand.minimum) < BigInt(entry.manifest.priceBand.maximum) ? BigInt(input.budget) - BigInt(analysis.manifest.priceBand.minimum) : BigInt(entry.manifest.priceBand.maximum)).toString() });
148
+ const quote = await this.quote(input);
149
+ if (BigInt(String(quote.amount)) > BigInt(input.budget))
150
+ fail("BUYER_BUDGET_EXCEEDED", "seller quote exceeds the budget");
151
+ const plan = await executeMandated(this.app, this.mandate, "settlement.plan", { "operation-id": input.operationId, manifest: entry.manifest, "requested-amount": String(quote.amount) });
152
+ if (plan.status !== "approved" || plan.rail !== "x402")
153
+ fail("BUYER_RAIL_DENIED", "public brief settlement plan was not approved for x402");
154
+ const paid = await executeMandated(this.app, this.mandate, "pay.x402", { "operation-id": input.operationId, "resource-url": String(quote.resourceUrl), amount: String(quote.amount), "plan-hash": String(plan.planHash) });
155
+ const artifact = Buffer.from(String(paid.response), "base64");
156
+ if (sha256(artifact) !== quote.artifactSha256 || sha256(artifact) !== paid.responseSha256)
157
+ fail("DELIVERY_HASH_MISMATCH", "delivered brief differs from the seller quote or payment receipt");
158
+ let report;
159
+ try {
160
+ report = JSON.parse(artifact.toString());
161
+ }
162
+ catch (error) {
163
+ return fail("BRIEF_INVALID", "paid brief is not JSON", error);
164
+ }
165
+ if (typeof report.title !== "string" || typeof report.body !== "string" || !Array.isArray(report.citations) || report.citations.length < 2 || report.citations.some((value) => typeof value !== "string" || !/^https:\/\//.test(value)))
166
+ fail("BRIEF_INVALID", "paid brief needs a title, body, and at least two HTTPS citations");
167
+ const recorded = (await this.app.execute("resource.fetch", { "request-id": input.requestId, "operation-id": input.operationId, response: artifact.toString("base64url") })).data;
168
+ this.app.store.db.exec("CREATE TABLE IF NOT EXISTS remote_purchases(run_id TEXT PRIMARY KEY,brief_hash TEXT NOT NULL,brief_amount TEXT NOT NULL,budget TEXT NOT NULL,capability_id TEXT NOT NULL,operation_id TEXT NOT NULL)");
169
+ const prior = this.app.store.db.prepare("SELECT brief_hash AS briefHash,brief_amount AS briefAmount,budget,capability_id AS capabilityId,operation_id AS operationId FROM remote_purchases WHERE run_id=?").get(input.runId);
170
+ const purchase = { briefHash: sha256(artifact), briefAmount: String(paid.amount), budget: input.budget, capabilityId: input.capabilityId, operationId: input.operationId };
171
+ if (prior && JSON.stringify(prior) !== JSON.stringify(purchase))
172
+ fail("BUYER_RUN_CONFLICT", "run ID identifies a different purchased brief or budget");
173
+ if (!prior)
174
+ this.app.store.db.prepare("INSERT INTO remote_purchases(run_id,brief_hash,brief_amount,budget,capability_id,operation_id) VALUES(?,?,?,?,?,?)").run(input.runId, purchase.briefHash, purchase.briefAmount, purchase.budget, purchase.capabilityId, purchase.operationId);
175
+ return { capabilityId: input.capabilityId, sellerId: entry.manifest.sellerId, negotiation: negotiated, artifact: report, artifactSha256: sha256(artifact), deliveryStatus: recorded.deliveryStatus, transactionDigest: paid.transactionDigest, checkpoint: paid.checkpoint, rail: "x402", amount: paid.amount, payee: paid.counterparty, planHash: plan.planHash };
176
+ }
177
+ async agreement(input) {
178
+ if (!/^[0-9a-f]{64}$/.test(input.briefHash))
179
+ fail("BUYER_BRIEF_HASH_INVALID", "brief hash is invalid");
180
+ const catalog = await this.catalog(), entry = catalog.capabilities.find((item) => item.manifest.capabilityId === input.capabilityId) ?? fail("SELLER_CAPABILITY_UNKNOWN", "capability is not in the verified catalog");
181
+ this.app.store.db.exec("CREATE TABLE IF NOT EXISTS remote_purchases(run_id TEXT PRIMARY KEY,brief_hash TEXT NOT NULL,brief_amount TEXT NOT NULL,budget TEXT NOT NULL,capability_id TEXT NOT NULL,operation_id TEXT NOT NULL)");
182
+ const purchased = this.app.store.db.prepare("SELECT brief_hash AS briefHash,brief_amount AS briefAmount,budget FROM remote_purchases WHERE run_id=?").get(input.runId);
183
+ if (!purchased || purchased.briefHash !== input.briefHash || BigInt(purchased.briefAmount) + BigInt(entry.manifest.priceBand.minimum) > BigInt(purchased.budget))
184
+ fail("BUYER_PURCHASE_REQUIRED", "analysis requires the matching paid brief within the total budget");
185
+ const hashes = entry.preview.artifactHashes, artifactSha256 = hashes?.[input.briefHash];
186
+ if (!artifactSha256 || !/^[0-9a-f]{64}$/.test(artifactSha256))
187
+ fail("SELLER_ARTIFACT_MISSING", "seller has no prepared analysis for the purchased brief");
188
+ const analysisMinimum = BigInt(entry.manifest.priceBand.minimum), analysisOpening = analysisMinimum > 1n ? analysisMinimum - (analysisMinimum / 10n || 1n) : analysisMinimum;
189
+ const negotiated = await this.negotiate({ capabilityId: input.capabilityId, sessionId: input.sessionId, runId: input.runId, task: "Confidential competitor analysis of the purchased Sui brief", opening: analysisOpening.toString(), maximum: entry.manifest.priceBand.minimum, briefHash: input.briefHash });
190
+ const plan = await executeMandated(this.app, this.mandate, "settlement.plan", { "operation-id": input.operationId, manifest: entry.manifest, "requested-amount": entry.manifest.priceBand.minimum });
191
+ if (plan.status !== "approved" || plan.rail !== "shielded" || !Array.isArray(plan.reasonCodes) || !plan.reasonCodes.includes("MEMORY_PRIVACY_APPLIED"))
192
+ fail("BUYER_PRIVACY_REQUIRED", "saved owner privacy rule did not select shielded settlement");
193
+ const publicKey = this.app.store.getSecret("identity:x25519-public")?.toString("base64url") ?? fail("IDENTITY_MISSING", "buyer delivery key is missing");
194
+ const identity = (await this.app.execute("doctor", {})).data;
195
+ const [transcriptRootLo, transcriptRootHi] = [BigInt(`0x${String(negotiated.transcriptHash).slice(0, 32)}`).toString(), BigInt(`0x${String(negotiated.transcriptHash).slice(32)}`).toString()];
196
+ const [serviceDigestLo, serviceDigestHi] = halves(JSON.stringify({ capabilityId: input.capabilityId, briefHash: input.briefHash, artifactSha256, deliveryPublicKey: publicKey }));
197
+ const scalar = () => BigInt(`0x${randomBytes(16).toString("hex")}`).toString();
198
+ const draft = { dealId: scalar(), revision: "1", transcriptRootLo, transcriptRootHi, serviceDigestLo, serviceDigestHi, settlementNonceLo: scalar(), settlementNonceHi: scalar(), paymentAmount: entry.manifest.priceBand.minimum, blinding: scalar(), expiry: String(Date.now() + 30 * 60_000), buyerPublicKey: identity.babyJubPublicKey, counterparty: entry.manifest.sellerId, capabilityId: entry.manifest.capabilityId, manifestHash: entry.manifest.manifestHash };
199
+ const signed = await this.request("/v1/agreement", { method: "POST", body: JSON.stringify({ capabilityId: input.capabilityId, sessionId: input.sessionId, runId: input.runId, briefHash: input.briefHash, deliveryPublicKey: publicKey, agreement: Buffer.from(JSON.stringify(draft)).toString("base64url") }) });
200
+ if (signed.artifactSha256 !== artifactSha256 || signed.capabilityId !== input.capabilityId || signed.sessionId !== input.sessionId)
201
+ fail("AGREEMENT_BINDING_MISMATCH", "seller response changed the prepared artifact or session");
202
+ const accepted = await executeMandated(this.app, this.mandate, "offer.accept", { "operation-id": `accept-${input.sessionId}`, session: input.sessionId, agreement: signed.agreement, "plan-hash": String(plan.planHash) });
203
+ return { plan, accepted, negotiation: negotiated, sessionId: input.sessionId, artifactSha256, memoryCitationCount: Array.isArray(plan.citations) ? plan.citations.length : 0 };
204
+ }
205
+ async settleAnalysis(input) {
206
+ const agreement = this.app.store.db.prepare("SELECT agreement FROM live_agreements WHERE session_id=?").get(input.sessionId) ?? fail("AGREEMENT_NOT_ACCEPTED", "analysis session has no accepted seller agreement");
207
+ const accepted = JSON.parse(agreement.agreement);
208
+ const binding = this.app.store.db.prepare("SELECT plan_hash FROM settlement_agreement_bindings WHERE session_id=?").get(input.sessionId) ?? fail("SETTLEMENT_PLAN_REQUIRED", "analysis agreement is not bound to an approved plan");
209
+ const receipt = await executeMandated(this.app, this.mandate, "pay.shielded", { "operation-id": input.operationId, session: input.sessionId, amount: accepted.paymentAmount, counterparty: accepted.counterparty, "plan-hash": binding.plan_hash });
210
+ return { sessionId: input.sessionId, rail: "shielded", transactionDigest: receipt.transactionDigest, checkpoint: receipt.checkpoint, finalized: receipt.finalized, tatumVerified: receipt.tatumVerified, dealCommitment: receipt.dealCommitment, planHash: binding.plan_hash, visibility: "pool transaction and gas sender visible; direct amount and counterparty absent" };
211
+ }
212
+ async delivery(input) {
213
+ const response = await this.request(`/v1/delivery/${encodeURIComponent(input.sessionId)}`);
214
+ if (response.status === "payment-pending")
215
+ return response;
216
+ if (response.status !== "delivered" || response.sessionId !== input.sessionId || !/^[0-9a-f]{64}$/.test(String(response.artifactSha256)))
217
+ fail("DELIVERY_INVALID", "seller delivery is invalid");
218
+ const privateDer = this.app.store.getSecret("identity:x25519-private") ?? fail("IDENTITY_MISSING", "buyer delivery key is missing");
219
+ const privateKey = createPrivateKey({ key: privateDer, format: "der", type: "pkcs8" }), ephemeral = createPublicKey({ key: Buffer.from(response.ephemeralPublicKey, "base64url"), format: "der", type: "spki" });
220
+ const shared = diffieHellman({ privateKey, publicKey: ephemeral }), key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(input.sessionId), Buffer.from("supercuts-delivery-v1"), 32));
221
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(response.nonce, "base64url"));
222
+ decipher.setAAD(Buffer.from(`${input.sessionId}:${String(response.capabilityId)}:${String(response.artifactSha256)}`));
223
+ decipher.setAuthTag(Buffer.from(response.tag, "base64url"));
224
+ const artifact = Buffer.concat([decipher.update(Buffer.from(response.ciphertext, "base64url")), decipher.final()]);
225
+ if (sha256(artifact) !== response.artifactSha256)
226
+ fail("DELIVERY_HASH_MISMATCH", "seller artifact differs from its accepted hash");
227
+ const recorded = (await this.app.execute("resource.fetch", { "request-id": input.sessionId, "operation-id": input.operationId, response: artifact.toString("base64url") })).data;
228
+ return { status: "delivered", sessionId: input.sessionId, artifactSha256: response.artifactSha256, body: artifact.toString("utf8"), record: { deliveryStatus: recorded.deliveryStatus, responseHash: recorded.responseHash } };
229
+ }
230
+ async events(runId) { return this.request(`/v1/events?runId=${encodeURIComponent(runId)}`); }
231
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { Phase3App, errorOutput, homePath, parseArgs } from "./phase3.js";
5
+ import { X402ResourceService } from "./x402-resource-service.js";
6
+ import { fail } from "./errors.js";
7
+ const args = parseArgs(process.argv.slice(2)), required = (name) => typeof args[name] === "string" ? args[name] : fail("ARGUMENT_REQUIRED", `--${name} is required`);
8
+ try {
9
+ const app = new Phase3App(homePath(args));
10
+ if (app.profile.role !== "seller")
11
+ fail("ROLE_FORBIDDEN", "resource service requires a seller role home");
12
+ const document = JSON.parse(readFileSync(resolve(required("config")), "utf8"));
13
+ if (!Array.isArray(document.artifacts) || !document.artifacts.length)
14
+ fail("RESOURCE_CONFIG_INVALID", "resource config needs at least one artifact");
15
+ const service = new X402ResourceService(app.store, document.service), registered = document.artifacts.map((item) => ({ requestId: item.requestId, operationId: item.operationId, ...service.register(item.requestId, item.operationId, readFileSync(resolve(item.file))) })), server = service.createServer(), origin = new URL(document.service.origin), port = Number(origin.port || (origin.protocol === "https:" ? 443 : 80));
16
+ server.listen(port, origin.hostname, () => process.stdout.write(`${JSON.stringify({ schemaVersion: "supercuts.resource.v1", ok: true, origin: document.service.origin, payTo: document.service.payTo, artifacts: registered })}\n`));
17
+ const close = () => server.close(() => { app.close(); process.exit(0); });
18
+ process.on("SIGINT", close);
19
+ process.on("SIGTERM", close);
20
+ }
21
+ catch (error) {
22
+ process.stdout.write(`${JSON.stringify(errorOutput(error))}\n`);
23
+ process.exitCode = 1;
24
+ }
@@ -0,0 +1,22 @@
1
+ import { ExactRequirements, FinalPayment, PaymentBinding, PaymentConsumptionLedger } from "./x402.js";
2
+ export interface PaymentSubmission {
3
+ binding: PaymentBinding;
4
+ }
5
+ export type PayerSignatureVerifier = (binding: PaymentBinding, requirements: ExactRequirements) => Promise<boolean>;
6
+ export type PaymentSettlementReader = (digest: string) => Promise<FinalPayment | undefined>;
7
+ export interface PaymentChallenge {
8
+ status: 402;
9
+ requestId: string;
10
+ nonce: string;
11
+ requirements: ExactRequirements;
12
+ }
13
+ export declare function verifySuiPaymentBinding(binding: PaymentBinding): Promise<boolean>;
14
+ export declare class ExactResourceServer {
15
+ private readonly ledger;
16
+ private readonly requirements;
17
+ private readonly verifyPayer;
18
+ private readonly readSettlement;
19
+ constructor(ledger: PaymentConsumptionLedger, requirements: ExactRequirements, verifyPayer: PayerSignatureVerifier, readSettlement: PaymentSettlementReader);
20
+ challenge(requestId: string): PaymentChallenge;
21
+ deliver(requestId: string, submission: PaymentSubmission, produce: () => Promise<Uint8Array>): Promise<Buffer>;
22
+ }
@@ -0,0 +1,55 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { isValidPersonalMessageSignature } from "@mysten/sui/verify";
3
+ import { fail } from "./errors.js";
4
+ import { requirementsHash, verifyBinding, verifyExactPayment } from "./x402.js";
5
+ import { paymentBindingBytes } from "./x402.js";
6
+ export async function verifySuiPaymentBinding(binding) {
7
+ const { payerSignature: _signature, ...unsigned } = binding;
8
+ return isValidPersonalMessageSignature(paymentBindingBytes(unsigned), binding.payerSignature, { address: binding.payer });
9
+ }
10
+ export class ExactResourceServer {
11
+ ledger;
12
+ requirements;
13
+ verifyPayer;
14
+ readSettlement;
15
+ constructor(ledger, requirements, verifyPayer, readSettlement) {
16
+ this.ledger = ledger;
17
+ this.requirements = requirements;
18
+ this.verifyPayer = verifyPayer;
19
+ this.readSettlement = readSettlement;
20
+ }
21
+ challenge(requestId) {
22
+ const nonce = this.ledger.challenge(requestId, requirementsHash(this.requirements), () => randomBytes(24).toString("hex"));
23
+ return { status: 402, requestId, nonce, requirements: structuredClone(this.requirements) };
24
+ }
25
+ async deliver(requestId, submission, produce) {
26
+ const stored = this.ledger.getChallenge(requestId, requirementsHash(this.requirements)) ?? fail("X402_CHALLENGE_MISSING", "request has no active payment challenge");
27
+ const challenge = { requestId, nonce: stored.nonce, requirements: this.requirements };
28
+ verifyBinding(submission.binding, requestId, challenge.nonce, challenge.requirements, submission.binding.transactionDigest);
29
+ if (!(await this.verifyPayer(submission.binding, challenge.requirements)))
30
+ fail("X402_PAYER_SIGNATURE_INVALID", "payer did not authorize this request binding");
31
+ if (!submission.binding.payer)
32
+ fail("X402_PAYER_SIGNATURE_INVALID", "payment binding has no payer identity");
33
+ const cached = this.ledger.getDelivery(requestId);
34
+ if (this.ledger.isDeliveryComplete(requestId))
35
+ return cached ?? fail("DELIVERY_STATE_INVALID", "completed delivery has no persisted response");
36
+ if (Date.now() > stored.createdAt + this.requirements.maxTimeoutSeconds * 1_000)
37
+ fail("X402_CHALLENGE_EXPIRED", "payment challenge has expired");
38
+ const payment = await this.readSettlement(submission.binding.transactionDigest) ?? fail("PAYMENT_NOT_FOUND", "payment transaction was not found");
39
+ if (payment.digest !== submission.binding.transactionDigest)
40
+ fail("PAYMENT_DIGEST_MISMATCH", "settlement reader returned another transaction");
41
+ verifyExactPayment(payment, challenge.requirements);
42
+ this.ledger.claim(payment, requestId);
43
+ if (!this.ledger.acquireDelivery(requestId))
44
+ fail("DELIVERY_IN_PROGRESS", "resource delivery is already being produced");
45
+ try {
46
+ const response = Buffer.from(await produce());
47
+ this.ledger.completeDelivery(requestId, response);
48
+ return response;
49
+ }
50
+ catch (error) {
51
+ this.ledger.releaseDelivery(requestId);
52
+ throw error;
53
+ }
54
+ }
55
+ }
package/dist/seam.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/seam.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import { Phase3App, errorOutput, homePath, initialize } from "./phase3.js";
3
+ import { DealJournal } from "./deal.js";
4
+ import { createConfiguredDealEngine } from "./deal-adapter.js";
5
+ import { loadMandate } from "./mandate.js";
6
+ import { LiveDealPorts } from "./live-deal-ports.js";
7
+ import { createRequire } from "node:module";
8
+ const PROTOCOL = 1;
9
+ const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
10
+ const envelope = (requestId, value) => ({ protocol: PROTOCOL, requestId, ...value });
11
+ let responded = false;
12
+ const write = (value, status = 0) => { if (responded)
13
+ return; responded = true; process.stdout.write(`${JSON.stringify(value)}\n`); process.exitCode = status; };
14
+ const asArgs = (value) => {
15
+ const result = {};
16
+ for (const [key, item] of Object.entries(value ?? {})) {
17
+ if (typeof item === "boolean" || typeof item === "string")
18
+ result[key] = item;
19
+ else if (typeof item === "number" && Number.isSafeInteger(item))
20
+ result[key] = String(item);
21
+ else if (item !== undefined)
22
+ result[key] = Buffer.from(JSON.stringify(item)).toString("base64url");
23
+ }
24
+ return result;
25
+ };
26
+ let raw = "";
27
+ process.stdin.setEncoding("utf8");
28
+ process.stdin.on("data", (chunk) => { raw += chunk; if (raw.length > 1024 * 1024)
29
+ write(envelope(null, { ok: false, error: { code: "SEAM_REQUEST_TOO_LARGE", message: "request exceeds 1 MiB; reduce the input", retryable: false } }), 1); });
30
+ process.stdin.on("error", () => write(envelope(null, { ok: false, error: { code: "SEAM_REQUEST_INVALID", message: "request stream failed", retryable: false } }), 1));
31
+ process.stdin.on("end", async () => {
32
+ if (responded)
33
+ return;
34
+ let request;
35
+ try {
36
+ request = JSON.parse(raw);
37
+ }
38
+ catch {
39
+ return write(envelope(null, { ok: false, error: { code: "SEAM_REQUEST_INVALID", message: "request is not valid JSON", retryable: false } }), 1);
40
+ }
41
+ if (!request || request.protocol !== PROTOCOL || typeof request.requestId !== "string" || !request.requestId || request.requestId.length > 256 || typeof request.method !== "string" || !request.method || request.method.length > 128)
42
+ return write(envelope(typeof request?.requestId === "string" ? request.requestId : null, { ok: false, error: { code: "SEAM_PROTOCOL_MISMATCH", message: `supercuts seam protocol ${PROTOCOL} and a bounded request identity and method are required`, retryable: false } }), 1);
43
+ try {
44
+ if (request.method === "version")
45
+ return write(envelope(request.requestId, { ok: true, result: { protocol: PROTOCOL, packageVersion: PACKAGE_VERSION } }));
46
+ if (typeof request.home !== "string" || !request.home)
47
+ return write(envelope(request.requestId, { ok: false, error: { code: "SEAM_HOME_REQUIRED", message: "an explicit role home is required", retryable: false } }), 1);
48
+ if (request.params !== undefined && (!request.params || typeof request.params !== "object" || Array.isArray(request.params)))
49
+ return write(envelope(request.requestId, { ok: false, error: { code: "SEAM_REQUEST_INVALID", message: "params must be an object", retryable: false } }), 1);
50
+ const params = asArgs(request.params), home = homePath({ home: request.home ?? "" });
51
+ if (request.method === "initialize") {
52
+ const result = initialize(home, { ...params, role: String(request.params?.role ?? "") });
53
+ return write(envelope(request.requestId, { ok: true, result: result.data }));
54
+ }
55
+ const app = new Phase3App(home);
56
+ try {
57
+ if (request.method.startsWith("deal.")) {
58
+ if (app.profile.role !== "buyer")
59
+ throw Object.assign(new Error("deal methods require a buyer role home"), { code: "ROLE_FORBIDDEN" });
60
+ const journal = new DealJournal(app.store);
61
+ if (request.method === "deal.catalog") {
62
+ const mandate = loadMandate(home);
63
+ const entries = await new LiveDealPorts(app, mandate).catalog();
64
+ return write(envelope(request.requestId, { ok: true, result: { capabilities: entries.map(({ origin, registration }) => ({ origin, manifest: registration.manifest, manifestBlobId: registration.manifestBlobId })) } }));
65
+ }
66
+ if (request.method === "deal.policy") {
67
+ const mandate = loadMandate(home), hasTable = Boolean(app.store.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mandate_operations'").get());
68
+ const reserved = hasTable ? app.store.db.prepare("SELECT amount FROM mandate_operations").all().reduce((sum, row) => sum + BigInt(row.amount), 0n) : 0n;
69
+ return write(envelope(request.requestId, { ok: true, result: { perDealCeiling: mandate.perDealCeiling, totalBudget: mandate.totalRunBudget, remainingBudget: (BigInt(mandate.totalRunBudget) - reserved).toString(), expiresAt: mandate.expiresAt, defaultRail: "shielded", allowPublicX402: mandate.allowPublicX402 === true, allowedAssets: mandate.allowedAssets, allowedCapabilityIds: mandate.allowedCapabilityIds, allowedCapabilityClasses: mandate.allowedCapabilityClasses } }));
70
+ }
71
+ if (request.method === "deal.execute") {
72
+ const engine = await createConfiguredDealEngine(app);
73
+ return write(envelope(request.requestId, { ok: true, result: await engine.execute(request.params?.input) }));
74
+ }
75
+ if (request.method === "deal.resume") {
76
+ const engine = await createConfiguredDealEngine(app);
77
+ return write(envelope(request.requestId, { ok: true, result: await engine.resume(String(request.params?.operationId ?? "")) }));
78
+ }
79
+ if (request.method === "deal.reconcile") {
80
+ if (!loadMandate(home).allowedCommands.includes("deal.resume"))
81
+ throw Object.assign(new Error("deal reconciliation requires owner deal authority"), { code: "MANDATE_COMMAND_FORBIDDEN" });
82
+ return write(envelope(request.requestId, { ok: true, result: await app.reconcileDealOutcome(String(request.params?.operationId ?? "")) }));
83
+ }
84
+ if (request.method === "deal.get")
85
+ return write(envelope(request.requestId, { ok: true, result: journal.get(String(request.params?.operationId ?? "")) }));
86
+ if (request.method === "deal.evidence")
87
+ return write(envelope(request.requestId, { ok: true, result: journal.evidence(String(request.params?.dealId ?? "")) }));
88
+ if (request.method === "deal.delivery") {
89
+ const delivery = journal.delivery(String(request.params?.dealId ?? ""));
90
+ return write(envelope(request.requestId, { ok: true, result: { ...delivery, artifact: Buffer.from(delivery.artifact).toString("base64") } }));
91
+ }
92
+ }
93
+ if (request.method === "authorization.preview") {
94
+ const command = String(request.params?.command ?? ""), commandArgs = asArgs(request.params?.arguments);
95
+ return write(envelope(request.requestId, { ok: true, result: app.preview(command, commandArgs) }));
96
+ }
97
+ if (request.method === "authorization.approve")
98
+ return write(envelope(request.requestId, { ok: true, result: app.approve(String(request.params?.challengeId ?? "")) }));
99
+ if (request.method === "command.execute") {
100
+ const command = String(request.params?.command ?? ""), commandArgs = asArgs(request.params?.arguments);
101
+ if (typeof request.params?.approvalToken === "string")
102
+ commandArgs["approval-token"] = request.params.approvalToken;
103
+ const result = await app.execute(command, commandArgs);
104
+ return write(envelope(request.requestId, { ok: true, result: result.data }));
105
+ }
106
+ return write(envelope(request.requestId, { ok: false, error: { code: "SEAM_METHOD_UNKNOWN", message: `unknown seam method ${request.method}`, retryable: false } }), 1);
107
+ }
108
+ finally {
109
+ app.close();
110
+ }
111
+ }
112
+ catch (error) {
113
+ const shaped = errorOutput(error);
114
+ const code = String(shaped.error.code ?? "INTERNAL_ERROR");
115
+ return write(envelope(request.requestId, { ok: false, error: { ...shaped.error, retryable: ["DELIVERY_IN_PROGRESS", "MEMORY_JOB_PENDING", "TATUM_TIMEOUT"].includes(code) } }), 1);
116
+ }
117
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { readFileSync, statSync, writeFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { createCapabilityManifest } from "./capability.js";
6
+ import { fail } from "./errors.js";
7
+ import { parseArgs } from "./phase3.js";
8
+ const args = parseArgs(process.argv.slice(2));
9
+ const required = (name) => typeof args[name] === "string" ? args[name] : fail("ARGUMENT_REQUIRED", `--${name} is required`);
10
+ const report = (name) => {
11
+ const path = resolve(required(name));
12
+ if ((statSync(path).mode & 0o077) !== 0)
13
+ fail("ARTIFACT_PERMISSIONS", `${name} report must be owner-only`);
14
+ const body = readFileSync(path);
15
+ if (!body.length || body.length > 10 * 1024 * 1024)
16
+ fail("ARTIFACT_INVALID", `${name} report size is invalid`);
17
+ let data;
18
+ try {
19
+ data = JSON.parse(body.toString());
20
+ }
21
+ catch (error) {
22
+ return fail("ARTIFACT_INVALID", `${name} report must be JSON`, error);
23
+ }
24
+ return { path, body, data, hash: createHash("sha256").update(body).digest("hex") };
25
+ };
26
+ const price = (name) => { const value = required(name); if (!/^[1-9][0-9]*$/.test(value))
27
+ fail("PRICE_INVALID", `${name} must be positive MIST`); return value; };
28
+ try {
29
+ const run = JSON.parse(readFileSync(resolve(required("run-json")), "utf8"));
30
+ const origin = new URL(required("seller-url"));
31
+ if (origin.protocol !== "https:" && !(origin.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(origin.hostname)))
32
+ fail("SELLER_ORIGIN_INVALID", "seller URL must use HTTPS except on loopback");
33
+ const briefA = report("brief-a"), briefB = report("brief-b"), analysisA = report("analysis-a"), analysisB = report("analysis-b");
34
+ for (const item of [briefA, briefB, analysisA, analysisB])
35
+ if (item.data.reviewStatus !== "approved")
36
+ fail("ARTIFACT_REVIEW_REQUIRED", `${item.path} must be owner-reviewed before live provisioning`);
37
+ for (const brief of [briefA, briefB]) {
38
+ if (typeof brief.data.title !== "string" || typeof brief.data.body !== "string" || !Array.isArray(brief.data.citations) || brief.data.citations.length < 2 || typeof brief.data.preview !== "object")
39
+ fail("BRIEF_INVALID", "brief reports need title, body, two citations, and preview metadata");
40
+ for (const key of ["relevance", "sourceQuality"])
41
+ if (typeof brief.data.preview[key] !== "number" || brief.data.preview[key] < 0 || brief.data.preview[key] > 1)
42
+ fail("BRIEF_INVALID", `preview ${key} must be in [0,1]`);
43
+ if (!Number.isFinite(brief.data.preview.freshnessDays) || brief.data.preview.freshnessDays < 0 || brief.data.preview.freshnessDays > 365)
44
+ fail("BRIEF_INVALID", "preview freshnessDays is invalid");
45
+ }
46
+ for (const analysis of [analysisA, analysisB])
47
+ if (typeof analysis.data.title !== "string" || typeof analysis.data.body !== "string")
48
+ fail("ANALYSIS_INVALID", "analysis reports need a title and body");
49
+ const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000;
50
+ const capability = (id, sellerId, description, rails, amount) => createCapabilityManifest({ capabilityId: id, sellerId, capability: id === "competitor-analysis" ? "sui.competitor-analysis" : "sui.market-brief", description, inputSchema: { type: "object", properties: { topic: { type: "string" } } }, outputSchema: { type: "object", properties: { title: { type: "string" }, body: { type: "string" }, citations: { type: "array" } } }, supportedRails: rails, priceBand: { minimum: amount, maximum: amount }, asset: "0x2::sui::SUI", endpoint: new URL(`/resource/${id}`, origin).toString(), expiresAt });
51
+ const publicA = run.addresses.publicSeller ?? fail("SELLER_CONFIG_INVALID", "first brief seller is missing"), publicB = run.addresses.secondPublicSeller ?? fail("SELLER_CONFIG_INVALID", "second brief seller is missing"), analysisSeller = run.addresses.confidentialSeller ?? fail("SELLER_CONFIG_INVALID", "analysis seller is missing");
52
+ const manifestA = capability("market-brief-a", publicA, String(briefA.data.title), ["x402"], price("brief-a-price"));
53
+ const manifestB = capability("market-brief-b", publicB, String(briefB.data.title), ["x402"], price("brief-b-price"));
54
+ const manifestAnalysis = capability("competitor-analysis", analysisSeller, String(analysisA.data.title), ["x402", "shielded"], price("analysis-price"));
55
+ const config = { sellers: [
56
+ { sellerId: run.addresses.publicSeller, home: run.homes.publicSeller, capabilities: [{ manifest: manifestA, preview: { title: briefA.data.title, summary: briefA.data.preview.summary, citations: briefA.data.citations, relevance: briefA.data.preview.relevance, sourceQuality: briefA.data.preview.sourceQuality, freshnessDays: briefA.data.preview.freshnessDays }, artifact: briefA.path }] },
57
+ { sellerId: run.addresses.secondPublicSeller, home: run.homes.secondPublicSeller, capabilities: [{ manifest: manifestB, preview: { title: briefB.data.title, summary: briefB.data.preview.summary, citations: briefB.data.citations, relevance: briefB.data.preview.relevance, sourceQuality: briefB.data.preview.sourceQuality, freshnessDays: briefB.data.preview.freshnessDays }, artifact: briefB.path }] },
58
+ { sellerId: run.addresses.confidentialSeller, home: run.homes.confidentialSeller, capabilities: [{ manifest: manifestAnalysis, preview: { title: analysisA.data.title, summary: "Prepared competitor analysis matched to the purchased brief" }, variants: { [briefA.hash]: analysisA.path, [briefB.hash]: analysisB.path } }] },
59
+ ] };
60
+ const output = resolve(required("output")), workflow = resolve(required("workflow-output"));
61
+ writeFileSync(output, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600, flag: "wx" });
62
+ writeFileSync(workflow, `${JSON.stringify({ capabilities: [manifestA, manifestB, manifestAnalysis] }, null, 2)}\n`, { mode: 0o600, flag: "wx" });
63
+ process.stdout.write(`${JSON.stringify({ config: output, workflow, briefHashes: [briefA.hash, briefB.hash], capabilityIds: [manifestA.capabilityId, manifestB.capabilityId, manifestAnalysis.capabilityId] })}\n`);
64
+ }
65
+ catch (error) {
66
+ process.stderr.write(`${error instanceof Error ? error.message : "catalog generation failed"}\n`);
67
+ process.exitCode = 1;
68
+ }
@@ -0,0 +1,41 @@
1
+ import type { EncryptedSellerSnapshot } from "./seller-state.js";
2
+ import type { SignedCapabilityRegistration } from "./capability.js";
3
+ /** Server-only PostgREST access. The service-role key must never reach a buyer tool. */
4
+ export declare class SellerDatabase {
5
+ private readonly origin;
6
+ private readonly serviceKey;
7
+ private readonly http;
8
+ constructor(origin: string, serviceKey: string, http?: typeof fetch);
9
+ private request;
10
+ getSnapshot(sellerId: string): Promise<{
11
+ payload: EncryptedSellerSnapshot;
12
+ revision: number;
13
+ } | undefined>;
14
+ putSnapshot(snapshot: EncryptedSellerSnapshot, expectedRevision?: number): Promise<number>;
15
+ listCatalog(): Promise<Array<Record<string, unknown>>>;
16
+ listDirectory(now?: number, offset?: number): Promise<SignedCapabilityRegistration[]>;
17
+ putDirectory(registration: SignedCapabilityRegistration): Promise<void>;
18
+ putCatalog(row: {
19
+ seller_id: string;
20
+ capability_id: string;
21
+ manifest: unknown;
22
+ blob_id: string;
23
+ signature: string;
24
+ transport_public_key: string;
25
+ preview: unknown;
26
+ }): Promise<void>;
27
+ addEvent(row: {
28
+ run_id: string;
29
+ seller_id: string;
30
+ stage: string;
31
+ status: string;
32
+ details: Record<string, unknown>;
33
+ }): Promise<void>;
34
+ listEvents(runId: string): Promise<Array<Record<string, unknown>>>;
35
+ listPaymentClaims(): Promise<Array<{
36
+ digest: string;
37
+ request_id: string;
38
+ seller_id: string;
39
+ }>>;
40
+ claimPayment(digest: string, requestId: string, sellerId: string): Promise<void>;
41
+ }