@supercuts-walrus/backend 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +249 -2
  3. package/dist/audit.d.ts +52 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/backend.d.ts +27 -0
  6. package/dist/backend.js +62 -0
  7. package/dist/buyer-setup.d.ts +6 -0
  8. package/dist/buyer-setup.js +241 -0
  9. package/dist/capability.d.ts +32 -0
  10. package/dist/capability.js +87 -0
  11. package/dist/claude.d.ts +44 -0
  12. package/dist/claude.js +98 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +32 -0
  15. package/dist/config.d.ts +20 -0
  16. package/dist/config.js +58 -0
  17. package/dist/dashboard.d.ts +5 -0
  18. package/dist/dashboard.js +21 -0
  19. package/dist/deal-adapter.d.ts +3 -0
  20. package/dist/deal-adapter.js +39 -0
  21. package/dist/deal-engine.d.ts +62 -0
  22. package/dist/deal-engine.js +211 -0
  23. package/dist/deal-wire.d.ts +16 -0
  24. package/dist/deal-wire.js +65 -0
  25. package/dist/deal.d.ts +79 -0
  26. package/dist/deal.js +228 -0
  27. package/dist/demo.d.ts +2 -0
  28. package/dist/demo.js +511 -0
  29. package/dist/errors.d.ts +6 -0
  30. package/dist/errors.js +13 -0
  31. package/dist/events.d.ts +27 -0
  32. package/dist/events.js +68 -0
  33. package/dist/facilitator.d.ts +21 -0
  34. package/dist/facilitator.js +173 -0
  35. package/dist/finality.d.ts +2 -0
  36. package/dist/finality.js +27 -0
  37. package/dist/funding.d.ts +38 -0
  38. package/dist/funding.js +78 -0
  39. package/dist/hosted-exit-cli.d.ts +2 -0
  40. package/dist/hosted-exit-cli.js +37 -0
  41. package/dist/hosted-exit.d.ts +24 -0
  42. package/dist/hosted-exit.js +70 -0
  43. package/dist/hosted-seller-import.d.ts +2 -0
  44. package/dist/hosted-seller-import.js +70 -0
  45. package/dist/hosted-seller-repository.d.ts +31 -0
  46. package/dist/hosted-seller-repository.js +214 -0
  47. package/dist/index.d.ts +37 -0
  48. package/dist/index.js +37 -0
  49. package/dist/indexer.d.ts +63 -0
  50. package/dist/indexer.js +60 -0
  51. package/dist/live-deal-ports.d.ts +41 -0
  52. package/dist/live-deal-ports.js +212 -0
  53. package/dist/live-index.d.ts +28 -0
  54. package/dist/live-index.js +149 -0
  55. package/dist/live-runtime.d.ts +85 -0
  56. package/dist/live-runtime.js +644 -0
  57. package/dist/live-seller-transport.d.ts +30 -0
  58. package/dist/live-seller-transport.js +169 -0
  59. package/dist/live-seller-verifier.d.ts +11 -0
  60. package/dist/live-seller-verifier.js +65 -0
  61. package/dist/mandate.d.ts +34 -0
  62. package/dist/mandate.js +179 -0
  63. package/dist/mcp-bootstrap.d.ts +2 -0
  64. package/dist/mcp-bootstrap.js +61 -0
  65. package/dist/mcp.d.ts +2 -0
  66. package/dist/mcp.js +207 -0
  67. package/dist/memory-worker.d.ts +2 -0
  68. package/dist/memory-worker.js +38 -0
  69. package/dist/memory.d.ts +15 -0
  70. package/dist/memory.js +17 -0
  71. package/dist/memwal.d.ts +35 -0
  72. package/dist/memwal.js +83 -0
  73. package/dist/negotiation.d.ts +23 -0
  74. package/dist/negotiation.js +58 -0
  75. package/dist/notes.d.ts +11 -0
  76. package/dist/notes.js +26 -0
  77. package/dist/onboarding-doctor.d.ts +1 -0
  78. package/dist/onboarding-doctor.js +87 -0
  79. package/dist/phase3.d.ts +100 -0
  80. package/dist/phase3.js +1174 -0
  81. package/dist/procurement.d.ts +8 -0
  82. package/dist/procurement.js +19 -0
  83. package/dist/protocol-engine.d.ts +121 -0
  84. package/dist/protocol-engine.js +149 -0
  85. package/dist/prover.d.ts +28 -0
  86. package/dist/prover.js +70 -0
  87. package/dist/relayer.d.ts +32 -0
  88. package/dist/relayer.js +43 -0
  89. package/dist/remote-buyer.d.ts +85 -0
  90. package/dist/remote-buyer.js +285 -0
  91. package/dist/resource-server.d.ts +2 -0
  92. package/dist/resource-server.js +24 -0
  93. package/dist/resource.d.ts +22 -0
  94. package/dist/resource.js +55 -0
  95. package/dist/role-lock.d.ts +8 -0
  96. package/dist/role-lock.js +54 -0
  97. package/dist/seam.d.ts +2 -0
  98. package/dist/seam.js +117 -0
  99. package/dist/seller-catalog-generate.d.ts +2 -0
  100. package/dist/seller-catalog-generate.js +68 -0
  101. package/dist/seller-database.d.ts +41 -0
  102. package/dist/seller-database.js +80 -0
  103. package/dist/seller-directory-cli.d.ts +2 -0
  104. package/dist/seller-directory-cli.js +24 -0
  105. package/dist/seller-directory.d.ts +5 -0
  106. package/dist/seller-directory.js +50 -0
  107. package/dist/seller-live-cli.d.ts +2 -0
  108. package/dist/seller-live-cli.js +53 -0
  109. package/dist/seller-package.d.ts +9 -0
  110. package/dist/seller-package.js +50 -0
  111. package/dist/seller-provision.d.ts +2 -0
  112. package/dist/seller-provision.js +84 -0
  113. package/dist/seller-register-cli.d.ts +2 -0
  114. package/dist/seller-register-cli.js +40 -0
  115. package/dist/seller-runtime.d.ts +60 -0
  116. package/dist/seller-runtime.js +96 -0
  117. package/dist/seller-service.d.ts +34 -0
  118. package/dist/seller-service.js +422 -0
  119. package/dist/seller-state.d.ts +19 -0
  120. package/dist/seller-state.js +60 -0
  121. package/dist/seller-stdio.d.ts +6 -0
  122. package/dist/seller-stdio.js +29 -0
  123. package/dist/seller-v2-provision.d.ts +2 -0
  124. package/dist/seller-v2-provision.js +71 -0
  125. package/dist/seller-watch.d.ts +2 -0
  126. package/dist/seller-watch.js +45 -0
  127. package/dist/settlement.d.ts +95 -0
  128. package/dist/settlement.js +141 -0
  129. package/dist/store.d.ts +36 -0
  130. package/dist/store.js +193 -0
  131. package/dist/sui.d.ts +49 -0
  132. package/dist/sui.js +77 -0
  133. package/dist/tatum.d.ts +17 -0
  134. package/dist/tatum.js +83 -0
  135. package/dist/transcript.d.ts +31 -0
  136. package/dist/transcript.js +52 -0
  137. package/dist/v2-exit-relayer.d.ts +34 -0
  138. package/dist/v2-exit-relayer.js +102 -0
  139. package/dist/v2-exit-worker.d.ts +2 -0
  140. package/dist/v2-exit-worker.js +97 -0
  141. package/dist/v2-exits.d.ts +30 -0
  142. package/dist/v2-exits.js +78 -0
  143. package/dist/v2-prefund.d.ts +19 -0
  144. package/dist/v2-prefund.js +43 -0
  145. package/dist/v2-relayer-cli.d.ts +2 -0
  146. package/dist/v2-relayer-cli.js +39 -0
  147. package/dist/wallet.d.ts +32 -0
  148. package/dist/wallet.js +90 -0
  149. package/dist/workflow-memory.d.ts +22 -0
  150. package/dist/workflow-memory.js +49 -0
  151. package/dist/x402-resource-service.d.ts +36 -0
  152. package/dist/x402-resource-service.js +173 -0
  153. package/dist/x402.d.ts +46 -0
  154. package/dist/x402.js +136 -0
  155. package/package.json +60 -3
@@ -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
+ }
@@ -0,0 +1,80 @@
1
+ import { fail } from "./errors.js";
2
+ /** Server-only PostgREST access. The service-role key must never reach a buyer tool. */
3
+ export class SellerDatabase {
4
+ origin;
5
+ serviceKey;
6
+ http;
7
+ constructor(origin, serviceKey, http = fetch) {
8
+ this.origin = origin;
9
+ this.serviceKey = serviceKey;
10
+ this.http = http;
11
+ const url = new URL(origin);
12
+ if (url.protocol !== "https:")
13
+ fail("SELLER_DB_INVALID", "Supabase URL must use HTTPS");
14
+ if (!serviceKey)
15
+ fail("SELLER_DB_INVALID", "Supabase service key is required");
16
+ }
17
+ async request(path, init = {}) {
18
+ const response = await this.http(new URL(`/rest/v1/${path}`, this.origin), {
19
+ ...init,
20
+ headers: { apikey: this.serviceKey, Authorization: `Bearer ${this.serviceKey}`, ...(init.body ? { "content-type": "application/json" } : {}), ...(init.headers ?? {}) },
21
+ signal: init.signal ?? AbortSignal.timeout(30_000),
22
+ });
23
+ if (!response.ok) {
24
+ await response.arrayBuffer();
25
+ fail("SELLER_DB_UNAVAILABLE", `Supabase returned ${response.status}`);
26
+ }
27
+ const body = await response.text();
28
+ return body ? JSON.parse(body) : undefined;
29
+ }
30
+ async getSnapshot(sellerId) {
31
+ const rows = await this.request(`seller_state?seller_id=eq.${encodeURIComponent(sellerId)}&select=payload,revision`);
32
+ return rows[0];
33
+ }
34
+ async putSnapshot(snapshot, expectedRevision) {
35
+ if (expectedRevision === undefined) {
36
+ await this.request("seller_state?on_conflict=seller_id", { method: "POST", headers: { Prefer: "resolution=merge-duplicates,return=minimal" }, body: JSON.stringify({ seller_id: snapshot.sellerId, payload: snapshot, revision: 0, updated_at: new Date().toISOString() }) });
37
+ return 0;
38
+ }
39
+ const rows = await this.request(`seller_state?seller_id=eq.${encodeURIComponent(snapshot.sellerId)}&revision=eq.${expectedRevision}&select=revision`, { method: "PATCH", headers: { Prefer: "return=representation" }, body: JSON.stringify({ payload: snapshot, revision: expectedRevision + 1, updated_at: new Date().toISOString() }) });
40
+ if (!Array.isArray(rows) || rows.length !== 1 || rows[0]?.revision !== expectedRevision + 1)
41
+ fail("SELLER_STATE_CONFLICT", "another seller instance updated the durable snapshot");
42
+ return expectedRevision + 1;
43
+ }
44
+ async listCatalog() {
45
+ return await this.request("seller_catalog?select=seller_id,capability_id,manifest,blob_id,signature,transport_public_key,preview,updated_at&order=capability_id.asc");
46
+ }
47
+ async listDirectory(now = Date.now(), offset = 0) {
48
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > 100_000)
49
+ fail("SELLER_DIRECTORY_INVALID", "directory offset is invalid");
50
+ const rows = await this.request(`seller_directory?expires_at=gt.${now}&select=registration&order=capability_id.asc&limit=100&offset=${offset}`);
51
+ return rows.map((row) => row.registration);
52
+ }
53
+ async putDirectory(registration) {
54
+ const manifest = registration.manifest;
55
+ const existing = await this.request(`seller_directory?capability_id=eq.${encodeURIComponent(manifest.capabilityId)}&select=seller_id,registration`);
56
+ if (existing[0] && existing[0].seller_id !== manifest.sellerId)
57
+ fail("SELLER_DIRECTORY_CONFLICT", "capability ID belongs to another seller");
58
+ await this.request("seller_directory?on_conflict=capability_id", { method: "POST", headers: { Prefer: "resolution=merge-duplicates,return=minimal" }, body: JSON.stringify({ capability_id: manifest.capabilityId, seller_id: manifest.sellerId, registration, endpoint_origin: new URL(manifest.endpoint).origin, expires_at: manifest.expiresAt, updated_at: new Date().toISOString() }) });
59
+ }
60
+ async putCatalog(row) {
61
+ await this.request("seller_catalog?on_conflict=capability_id", { method: "POST", headers: { Prefer: "resolution=merge-duplicates,return=minimal" }, body: JSON.stringify({ ...row, updated_at: new Date().toISOString() }) });
62
+ }
63
+ async addEvent(row) {
64
+ await this.request("seller_events", { method: "POST", headers: { Prefer: "return=minimal" }, body: JSON.stringify({ ...row, created_at: new Date().toISOString() }) });
65
+ }
66
+ async listEvents(runId) {
67
+ return await this.request(`seller_events?run_id=eq.${encodeURIComponent(runId)}&select=seller_id,stage,status,details,created_at&order=id.asc`);
68
+ }
69
+ async listPaymentClaims() {
70
+ return await this.request("seller_payment_claims?select=digest,request_id,seller_id&order=digest.asc");
71
+ }
72
+ async claimPayment(digest, requestId, sellerId) {
73
+ const inserted = await this.request("seller_payment_claims?on_conflict=digest&select=digest,request_id,seller_id", { method: "POST", headers: { Prefer: "resolution=ignore-duplicates,return=representation" }, body: JSON.stringify({ digest, request_id: requestId, seller_id: sellerId }) });
74
+ if (Array.isArray(inserted) && inserted.length === 1)
75
+ return;
76
+ const existing = await this.request(`seller_payment_claims?digest=eq.${encodeURIComponent(digest)}&select=request_id,seller_id`);
77
+ if (existing[0]?.request_id !== requestId || existing[0]?.seller_id !== sellerId)
78
+ fail("PAYMENT_CONSUMED", "payment digest is already claimed by another resource");
79
+ }
80
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { verifySignedCapability } from "./capability.js";
5
+ import { fail } from "./errors.js";
6
+ async function main() {
7
+ const [command, file, directory] = process.argv.slice(2);
8
+ if (command !== "publish" || !file || !directory)
9
+ fail("ARGUMENT_REQUIRED", "usage: supercuts-seller-directory publish <owner-only-registration.json> <directory-origin>");
10
+ const path = resolve(file);
11
+ if ((statSync(path).mode & 0o077) !== 0)
12
+ fail("SELLER_CONFIG_PERMISSIONS", "registration file must be owner-only");
13
+ const registration = JSON.parse(readFileSync(path, "utf8"));
14
+ await verifySignedCapability(registration);
15
+ const url = new URL(directory);
16
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(url.hostname)) || url.username || url.password || url.pathname !== "/" || url.search || url.hash)
17
+ fail("SELLER_DIRECTORY_INVALID", "directory origin must be HTTPS or loopback HTTP without path or credentials");
18
+ const response = await fetch(new URL("/v2/directory/register", url), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ registration }), redirect: "error", signal: AbortSignal.timeout(90_000) });
19
+ const result = await response.json();
20
+ if (!response.ok || result.ok !== true)
21
+ fail("SELLER_DIRECTORY_UNAVAILABLE", String(result.message ?? `directory returned HTTP ${response.status}`));
22
+ process.stdout.write(`${JSON.stringify(result)}\n`);
23
+ }
24
+ main().catch((error) => { process.stderr.write(`${error instanceof Error ? error.message : "directory publication failed"}\n`); process.exitCode = 1; });
@@ -0,0 +1,5 @@
1
+ import { type SignedCapabilityRegistration } from "./capability.js";
2
+ import type { SellerDatabase } from "./seller-database.js";
3
+ export declare function validateDirectoryOrigin(endpoint: string, allowLoopback?: boolean): Promise<URL>;
4
+ /** The directory stores signed public metadata, never a seller role home. */
5
+ export declare function registerDirectoryCapability(db: Pick<SellerDatabase, "putDirectory">, registration: SignedCapabilityRegistration, aggregator: string, http?: typeof fetch, allowLoopback?: boolean): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { isIP } from "node:net";
3
+ import { verifySignedCapability } from "./capability.js";
4
+ import { fail } from "./errors.js";
5
+ import { canonicalJson } from "./settlement.js";
6
+ function publicAddress(address) {
7
+ if (address.includes(":"))
8
+ return !/^(::|::1$|fc|fd|fe80:|2001:db8:)/i.test(address) && !address.startsWith("::ffff:");
9
+ const octets = address.split(".").map(Number);
10
+ return octets.length === 4 && octets.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)
11
+ && octets[0] !== 0 && octets[0] !== 10 && octets[0] !== 127 && octets[0] < 224
12
+ && !(octets[0] === 169 && octets[1] === 254) && !(octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
13
+ && !(octets[0] === 192 && octets[1] === 168) && !(octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127);
14
+ }
15
+ export async function validateDirectoryOrigin(endpoint, allowLoopback = false) {
16
+ const url = new URL(endpoint);
17
+ if (url.username || url.password || url.hash || url.search || url.port && url.port !== "443" && !allowLoopback)
18
+ fail("SELLER_ENDPOINT_UNSAFE", "directory endpoint has credentials, query, fragment, or nonstandard port");
19
+ if (allowLoopback && url.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(url.hostname))
20
+ return url;
21
+ if (url.protocol !== "https:" || url.hostname === "localhost" || url.hostname.endsWith(".localhost"))
22
+ fail("SELLER_ENDPOINT_UNSAFE", "directory endpoint must use a public HTTPS origin");
23
+ const addresses = isIP(url.hostname) ? [{ address: url.hostname }] : await lookup(url.hostname, { all: true });
24
+ if (!addresses.length || addresses.some(({ address }) => !publicAddress(address)))
25
+ fail("SELLER_ENDPOINT_UNSAFE", "directory endpoint resolves to a nonpublic address");
26
+ return url;
27
+ }
28
+ /** The directory stores signed public metadata, never a seller role home. */
29
+ export async function registerDirectoryCapability(db, registration, aggregator, http = fetch, allowLoopback = false) {
30
+ await verifySignedCapability(registration);
31
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(registration.manifestBlobId))
32
+ fail("WALRUS_BLOB_INVALID", "directory registration has an invalid Walrus blob ID");
33
+ const endpoint = await validateDirectoryOrigin(registration.manifest.endpoint, allowLoopback);
34
+ const read = async (url, limit) => {
35
+ const response = await http(url, { redirect: "error", signal: AbortSignal.timeout(15_000) });
36
+ if (!response.ok)
37
+ fail("SELLER_DIRECTORY_UNAVAILABLE", `registration verification returned HTTP ${response.status}`);
38
+ const bytes = Buffer.from(await response.arrayBuffer());
39
+ if (!bytes.length || bytes.length > limit)
40
+ fail("SELLER_DIRECTORY_INVALID", "registration verification response is too large");
41
+ return JSON.parse(bytes.toString());
42
+ };
43
+ const blob = await read(new URL(`/v1/blobs/${encodeURIComponent(registration.manifestBlobId)}`, aggregator), 128 * 1024);
44
+ if (canonicalJson(blob) !== canonicalJson(registration.manifest))
45
+ fail("CAPABILITY_BLOB_MISMATCH", "Walrus manifest differs from the signed registration");
46
+ const catalog = await read(new URL("/v2/catalog", endpoint.origin), 512 * 1024);
47
+ if (!Array.isArray(catalog.capabilities) || catalog.capabilities.length > 100 || !catalog.capabilities.some((item) => canonicalJson(item) === canonicalJson(registration)))
48
+ fail("SELLER_DIRECTORY_UNAVAILABLE", "seller endpoint does not serve the exact signed registration");
49
+ await db.putDirectory(registration);
50
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { fail } from "./errors.js";
6
+ import { Phase3App, errorOutput } from "./phase3.js";
7
+ import { LiveSellerTransport } from "./live-seller-transport.js";
8
+ async function main() {
9
+ const home = process.argv[2], configPath = process.argv[3];
10
+ if (!home || !configPath)
11
+ return fail("ARGUMENT_REQUIRED", "usage: supercuts-seller-live <seller-home> <owner-only-config.json>");
12
+ if ((statSync(configPath).mode & 0o077) !== 0)
13
+ fail("SELLER_CONFIG_PERMISSIONS", "seller config must be owner-only");
14
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
15
+ if (!Array.isArray(config.capabilities) || !config.capabilities.length)
16
+ fail("SELLER_CONFIG_INVALID", "seller config must name registered capabilities and handlers");
17
+ const app = new Phase3App(home), transport = new LiveSellerTransport(app, config);
18
+ try {
19
+ for (const item of config.capabilities) {
20
+ let registration = item.registration;
21
+ if (item.registrationFile) {
22
+ const registrationPath = resolve(item.registrationFile);
23
+ if ((statSync(registrationPath).mode & 0o077) !== 0)
24
+ fail("SELLER_CONFIG_PERMISSIONS", "registration file must be owner-only");
25
+ registration = JSON.parse(readFileSync(registrationPath, "utf8"));
26
+ }
27
+ if (!registration)
28
+ return fail("SELLER_CONFIG_INVALID", "capability requires registration or registrationFile");
29
+ const path = resolve(item.handler);
30
+ if ((statSync(path).mode & 0o077) !== 0)
31
+ fail("SELLER_HANDLER_PERMISSIONS", "seller handler module must be owner-only");
32
+ const loaded = await import(pathToFileURL(path).href);
33
+ const handler = loaded.default ?? loaded.handle;
34
+ if (typeof handler !== "function")
35
+ return fail("SELLER_HANDLER_INVALID", "seller handler must export default or handle(request)");
36
+ await transport.register(registration, handler);
37
+ }
38
+ const origin = new URL(config.origin), server = transport.createServer();
39
+ const host = config.listenHost ?? "127.0.0.1", port = config.listenPort ?? (origin.protocol === "http:" && ["127.0.0.1", "localhost", "::1"].includes(origin.hostname) ? Number(origin.port || 80) : 8788);
40
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
41
+ fail("SELLER_CONFIG_INVALID", "seller listenPort must be a valid TCP port");
42
+ await new Promise((done) => server.listen(port, host, done));
43
+ process.stdout.write(`${JSON.stringify({ ok: true, service: "supercuts-seller-live", origin: config.origin, capabilities: transport.runtime.catalog().map((item) => item.manifest.capabilityId) })}\n`);
44
+ const stop = () => server.close(() => { app.close(); process.exit(0); });
45
+ process.on("SIGINT", stop);
46
+ process.on("SIGTERM", stop);
47
+ }
48
+ catch (error) {
49
+ app.close();
50
+ throw error;
51
+ }
52
+ }
53
+ main().catch((error) => { process.stdout.write(`${JSON.stringify(errorOutput(error))}\n`); process.exitCode = 1; });
@@ -0,0 +1,9 @@
1
+ export declare function packageHostedSeller(input: {
2
+ home: string;
3
+ spec: string;
4
+ artifact: string;
5
+ metadata: string;
6
+ registration: string;
7
+ bundle: string;
8
+ contentType?: string;
9
+ }): Promise<Record<string, unknown>>;
@@ -0,0 +1,50 @@
1
+ import { readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
4
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
5
+ import { createCapabilityManifest, validateCapabilityPayload } from "./capability.js";
6
+ import { fail } from "./errors.js";
7
+ import { executeMandated, loadMandate } from "./mandate.js";
8
+ import { Phase3App } from "./phase3.js";
9
+ const ownerFile = (path) => {
10
+ const value = resolve(path);
11
+ if ((statSync(value).mode & 0o077) !== 0)
12
+ fail("SELLER_CONFIG_PERMISSIONS", `${value} must be owner-only`);
13
+ return value;
14
+ };
15
+ export async function packageHostedSeller(input) {
16
+ if (![input.home, input.spec, input.artifact, input.metadata, input.registration, input.bundle].every((value) => typeof value === "string" && value.length > 0))
17
+ fail("ARGUMENT_REQUIRED", "seller package requires --home, --spec, --artifact, --metadata, --registration, and --bundle");
18
+ const specPath = ownerFile(input.spec), artifactPath = ownerFile(input.artifact), metadataPath = ownerFile(input.metadata);
19
+ const artifact = readFileSync(artifactPath);
20
+ if (!artifact.length || artifact.length > 10 * 1024 * 1024)
21
+ fail("SELLER_ARTIFACT_INVALID", "hosted artifact must contain 1 byte to 10 MiB");
22
+ const spec = JSON.parse(readFileSync(specPath, "utf8"));
23
+ const metadata = JSON.parse(readFileSync(metadataPath, "utf8"));
24
+ const home = resolve(input.home), app = new Phase3App(home);
25
+ try {
26
+ if (app.profile.role !== "seller" || app.profile.mock || app.profile.verifierVersion !== 2)
27
+ fail("SELLER_POOL_VERSION_MISMATCH", "hosted packaging requires a separate live v2 seller role home");
28
+ const encoded = app.store.getSecret("live:sui-private-key")?.toString() ?? fail("LIVE_CREDENTIAL_MISSING", "seller signer is missing");
29
+ const decoded = decodeSuiPrivateKey(encoded);
30
+ if (decoded.scheme !== "ED25519")
31
+ fail("SIGNER_UNSUPPORTED", "seller requires an ED25519 signer");
32
+ const signer = Ed25519Keypair.fromSecretKey(decoded.secretKey), manifest = createCapabilityManifest({ ...spec, sellerId: signer.toSuiAddress() });
33
+ validateCapabilityPayload(manifest, "output", metadata);
34
+ const published = await executeMandated(app, loadMandate(home), "capability.publish", { "operation-id": `publish-${manifest.manifestHash}`, manifest });
35
+ const manifestBlobId = String(published.blobId ?? "");
36
+ if (!manifestBlobId)
37
+ fail("WALRUS_PUBLISH_FAILED", "capability publication omitted a Walrus blob ID");
38
+ const transportPublicKey = app.store.getSecret("identity:x25519-public")?.toString("base64url") ?? fail("IDENTITY_MISSING", "seller transport key is missing");
39
+ const sellerSignature = (await signer.signPersonalMessage(Buffer.from(`${manifest.manifestHash}:${transportPublicKey}`))).signature;
40
+ const registration = { manifest, manifestBlobId, transportPublicKey, sellerSignature };
41
+ const registrationPath = resolve(input.registration), bundlePath = resolve(input.bundle);
42
+ writeFileSync(registrationPath, `${JSON.stringify(registration, null, 2)}\n`, { mode: 0o600, flag: "wx" });
43
+ const bundle = { sellers: [{ sellerId: manifest.sellerId, home, capabilities: [{ registrationFile: registrationPath, artifactFile: artifactPath, outputMetadata: metadata, contentType: input.contentType ?? "application/octet-stream" }] }] };
44
+ writeFileSync(bundlePath, `${JSON.stringify(bundle, null, 2)}\n`, { mode: 0o600, flag: "wx" });
45
+ return { sellerId: manifest.sellerId, capabilityId: manifest.capabilityId, manifestHash: manifest.manifestHash, manifestBlobId, registrationFile: registrationPath, provisioningBundle: bundlePath, nextCommand: `supercuts-seller-v2-provision ${JSON.stringify(bundlePath)}` };
46
+ }
47
+ finally {
48
+ app.close();
49
+ }
50
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
5
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
6
+ import { createHash } from "node:crypto";
7
+ import { validateCapabilityManifest } from "./capability.js";
8
+ import { fail } from "./errors.js";
9
+ import { executeMandated, loadMandate } from "./mandate.js";
10
+ import { Phase3App } from "./phase3.js";
11
+ import { SellerDatabase } from "./seller-database.js";
12
+ import { captureSellerHome, encryptSellerSnapshot } from "./seller-state.js";
13
+ async function main() {
14
+ const path = process.argv[2] ? resolve(process.argv[2]) : fail("ARGUMENT_REQUIRED", "usage: supercuts-seller-provision <owner-only-config.json>");
15
+ if ((statSync(path).mode & 0o077) !== 0)
16
+ fail("CREDENTIAL_PERMISSIONS", "seller provision config must be owner-only");
17
+ const config = JSON.parse(readFileSync(path, "utf8"));
18
+ if (!Array.isArray(config.sellers) || config.sellers.length !== 3 || new Set(config.sellers.map((seller) => seller.sellerId)).size !== 3)
19
+ fail("SELLER_CONFIG_INVALID", "exactly three distinct sellers are required");
20
+ const required = (key) => process.env[key] ?? fail("SELLER_CONFIG_MISSING", `${key} is required`);
21
+ const db = new SellerDatabase(required("SUPABASE_URL"), required("SUPABASE_SERVICE_ROLE_KEY"));
22
+ const key = required("SELLER_STATE_KEY");
23
+ for (const seller of config.sellers) {
24
+ const app = new Phase3App(resolve(seller.home));
25
+ try {
26
+ if (app.profile.mock || app.profile.role !== "seller")
27
+ fail("LIVE_PROFILE_REQUIRED", "provision only live seller role homes");
28
+ const encoded = app.store.getSecret("live:sui-private-key")?.toString() ?? fail("LIVE_CREDENTIAL_MISSING", "seller Sui key is missing");
29
+ const decoded = decodeSuiPrivateKey(encoded);
30
+ if (decoded.scheme !== "ED25519")
31
+ fail("SELLER_IDENTITY_MISMATCH", "seller requires an ED25519 signer");
32
+ const signer = Ed25519Keypair.fromSecretKey(decoded.secretKey);
33
+ if (signer.toSuiAddress() !== seller.sellerId)
34
+ fail("SELLER_IDENTITY_MISMATCH", "seller ID must equal its Sui signer address");
35
+ const artifacts = {};
36
+ for (const capability of seller.capabilities) {
37
+ const manifest = capability.manifest;
38
+ validateCapabilityManifest(manifest);
39
+ if (manifest.sellerId !== seller.sellerId)
40
+ fail("SELLER_IDENTITY_MISMATCH", "manifest seller ID differs from its role home");
41
+ if (capability.artifact) {
42
+ const body = readFileSync(resolve(capability.artifact));
43
+ if (manifest.capability === "sui.market-brief") {
44
+ let report;
45
+ try {
46
+ report = JSON.parse(body.toString());
47
+ }
48
+ catch (error) {
49
+ return fail("BRIEF_INVALID", "market brief must be JSON", error);
50
+ }
51
+ 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)))
52
+ fail("BRIEF_INVALID", "market brief needs a title, body, and at least two HTTPS citations");
53
+ }
54
+ artifacts[manifest.capabilityId] = body.toString("base64url");
55
+ }
56
+ for (const [briefHash, artifactPath] of Object.entries(capability.variants ?? {})) {
57
+ if (!/^[0-9a-f]{64}$/.test(briefHash))
58
+ fail("SELLER_CONFIG_INVALID", "variant brief hash is invalid");
59
+ artifacts[`${manifest.capabilityId}:${briefHash}`] = readFileSync(resolve(artifactPath)).toString("base64url");
60
+ }
61
+ if (!capability.artifact && !Object.keys(capability.variants ?? {}).length)
62
+ fail("SELLER_ARTIFACT_MISSING", "capability needs a prepared artifact or brief variants");
63
+ const published = await executeMandated(app, loadMandate(seller.home), "capability.publish", { "operation-id": `publish-${manifest.manifestHash}`, manifest });
64
+ const blobId = String(published.blobId ?? "");
65
+ if (!blobId)
66
+ fail("WALRUS_PUBLISH_FAILED", "capability publish returned no blob ID");
67
+ const hashes = {};
68
+ for (const [artifactKey, value] of Object.entries(artifacts))
69
+ if (artifactKey === manifest.capabilityId || artifactKey.startsWith(`${manifest.capabilityId}:`))
70
+ hashes[artifactKey.slice(manifest.capabilityId.length).replace(/^:/, "") || "default"] = createHash("sha256").update(Buffer.from(value, "base64url")).digest("hex");
71
+ const preview = { ...capability.preview, artifactHashes: hashes };
72
+ const transportPublicKey = app.store.getSecret("identity:x25519-public")?.toString("base64url") ?? fail("IDENTITY_MISSING", "seller transport key is missing");
73
+ const signature = (await signer.signPersonalMessage(Buffer.from(`${manifest.manifestHash}:${transportPublicKey}`))).signature;
74
+ await db.putCatalog({ seller_id: seller.sellerId, capability_id: manifest.capabilityId, manifest, blob_id: blobId, signature, transport_public_key: transportPublicKey, preview });
75
+ }
76
+ await db.putSnapshot(encryptSellerSnapshot(await captureSellerHome(seller.home, seller.sellerId, artifacts), key));
77
+ process.stdout.write(`${JSON.stringify({ sellerId: seller.sellerId, capabilities: seller.capabilities.map((item) => item.manifest.capabilityId), status: "provisioned" })}\n`);
78
+ }
79
+ finally {
80
+ app.close();
81
+ }
82
+ }
83
+ }
84
+ main().catch((error) => { process.stderr.write(`${error instanceof Error ? error.message : "provision failed"}\n`); process.exitCode = 1; });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
5
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
6
+ import { createCapabilityManifest } from "./capability.js";
7
+ import { fail } from "./errors.js";
8
+ import { Phase3App, errorOutput } from "./phase3.js";
9
+ import { executeMandated, loadMandate } from "./mandate.js";
10
+ async function main() {
11
+ const home = process.argv[2], specPath = process.argv[3], outputPath = process.argv[4];
12
+ if (!home || !specPath || !outputPath)
13
+ return fail("ARGUMENT_REQUIRED", "usage: supercuts-seller-register <seller-home> <capability-spec.json> <registration.json>");
14
+ if ((statSync(specPath).mode & 0o077) !== 0)
15
+ fail("SELLER_CONFIG_PERMISSIONS", "capability spec must be owner-only");
16
+ const spec = JSON.parse(readFileSync(specPath, "utf8"));
17
+ const app = new Phase3App(home);
18
+ try {
19
+ if (app.profile.role !== "seller" || app.profile.mock)
20
+ fail("SELLER_LIVE_REQUIRED", "registration requires a live seller role home");
21
+ const encoded = app.store.getSecret("live:sui-private-key")?.toString() ?? fail("LIVE_CREDENTIAL_MISSING", "seller signer is missing");
22
+ const decoded = decodeSuiPrivateKey(encoded);
23
+ if (decoded.scheme !== "ED25519")
24
+ fail("SIGNER_UNSUPPORTED", "seller requires an ED25519 signer");
25
+ const signer = Ed25519Keypair.fromSecretKey(decoded.secretKey), manifest = createCapabilityManifest({ ...spec, sellerId: signer.toSuiAddress() });
26
+ const published = await executeMandated(app, loadMandate(home), "capability.publish", { "operation-id": `publish-${manifest.manifestHash}`, manifest });
27
+ const blobId = String(published.blobId ?? "");
28
+ if (!blobId)
29
+ fail("WALRUS_PUBLISH_FAILED", "capability publication omitted a Walrus blob ID");
30
+ const transportPublicKey = app.store.getSecret("identity:x25519-public")?.toString("base64url") ?? fail("IDENTITY_MISSING", "seller transport key is missing");
31
+ const sellerSignature = (await signer.signPersonalMessage(Buffer.from(`${manifest.manifestHash}:${transportPublicKey}`))).signature;
32
+ const registration = { manifest, manifestBlobId: blobId, transportPublicKey, sellerSignature };
33
+ writeFileSync(resolve(outputPath), `${JSON.stringify(registration, null, 2)}\n`, { mode: 0o600, flag: "wx" });
34
+ process.stdout.write(`${JSON.stringify({ ok: true, capabilityId: manifest.capabilityId, manifestBlobId: blobId, registrationFile: resolve(outputPath) })}\n`);
35
+ }
36
+ finally {
37
+ app.close();
38
+ }
39
+ }
40
+ main().catch((error) => { process.stdout.write(`${JSON.stringify(errorOutput(error))}\n`); process.exitCode = 1; });
@@ -0,0 +1,60 @@
1
+ import { type SignedCapabilityRegistration } from "./capability.js";
2
+ import type { RoleStore } from "./store.js";
3
+ export interface ServiceRequest {
4
+ dealId: string;
5
+ capabilityId: string;
6
+ input: Record<string, unknown>;
7
+ requestId: string;
8
+ paymentDigest: string;
9
+ agreementHash: string;
10
+ agreedAmount: string;
11
+ rail: "shielded" | "x402";
12
+ }
13
+ export interface ServiceResult {
14
+ artifact: Uint8Array;
15
+ contentType: string;
16
+ metadata: Record<string, unknown>;
17
+ }
18
+ export type ServiceHandler = (request: ServiceRequest) => Promise<ServiceResult>;
19
+ export interface VerifiedSellerPayment {
20
+ finalized: true;
21
+ tatumVerified: true;
22
+ digest: string;
23
+ agreementHash: string;
24
+ sellerId: string;
25
+ asset: string;
26
+ amount: string;
27
+ rail: "shielded" | "x402";
28
+ }
29
+ export interface SellerRuntimePorts {
30
+ readBlob(blobId: string): Promise<Uint8Array>;
31
+ verifyPayment(request: ServiceRequest): Promise<VerifiedSellerPayment>;
32
+ encryptAndPublish(request: ServiceRequest, result: ServiceResult): Promise<{
33
+ blobId: string;
34
+ ciphertext: Uint8Array;
35
+ }>;
36
+ serve(runtime: SellerRuntime): Promise<void>;
37
+ }
38
+ export interface SellerDelivery {
39
+ dealId: string;
40
+ requestId: string;
41
+ blobId: string;
42
+ ciphertextHash: string;
43
+ artifactSha256: string;
44
+ contentType: string;
45
+ metadata: Record<string, unknown>;
46
+ }
47
+ /** Framework-neutral seller execution boundary. The trusted payment verifier
48
+ * must use chain finality and exact agreement binding, never a buyer's digest
49
+ * alone. The transport must decrypt/authenticate task input before calling
50
+ * fulfill and encrypt the artifact before Walrus publication. */
51
+ export declare class SellerRuntime {
52
+ #private;
53
+ private readonly store;
54
+ private readonly ports;
55
+ constructor(store: RoleStore, ports: SellerRuntimePorts);
56
+ registerCapability(registration: SignedCapabilityRegistration, handler: ServiceHandler): Promise<void>;
57
+ catalog(): Array<SignedCapabilityRegistration>;
58
+ fulfill(request: ServiceRequest): Promise<SellerDelivery>;
59
+ serve(): Promise<void>;
60
+ }