@supercuts-walrus/backend 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +249 -2
  3. package/dist/audit.d.ts +52 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/backend.d.ts +27 -0
  6. package/dist/backend.js +62 -0
  7. package/dist/buyer-setup.d.ts +6 -0
  8. package/dist/buyer-setup.js +241 -0
  9. package/dist/capability.d.ts +32 -0
  10. package/dist/capability.js +87 -0
  11. package/dist/claude.d.ts +44 -0
  12. package/dist/claude.js +98 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +29 -0
  15. package/dist/config.d.ts +20 -0
  16. package/dist/config.js +58 -0
  17. package/dist/dashboard.d.ts +5 -0
  18. package/dist/dashboard.js +21 -0
  19. package/dist/deal-adapter.d.ts +3 -0
  20. package/dist/deal-adapter.js +39 -0
  21. package/dist/deal-engine.d.ts +62 -0
  22. package/dist/deal-engine.js +211 -0
  23. package/dist/deal-wire.d.ts +16 -0
  24. package/dist/deal-wire.js +65 -0
  25. package/dist/deal.d.ts +79 -0
  26. package/dist/deal.js +228 -0
  27. package/dist/demo.d.ts +2 -0
  28. package/dist/demo.js +449 -0
  29. package/dist/errors.d.ts +6 -0
  30. package/dist/errors.js +13 -0
  31. package/dist/events.d.ts +27 -0
  32. package/dist/events.js +68 -0
  33. package/dist/facilitator.d.ts +21 -0
  34. package/dist/facilitator.js +173 -0
  35. package/dist/finality.d.ts +2 -0
  36. package/dist/finality.js +27 -0
  37. package/dist/hosted-exit-cli.d.ts +2 -0
  38. package/dist/hosted-exit-cli.js +37 -0
  39. package/dist/hosted-exit.d.ts +24 -0
  40. package/dist/hosted-exit.js +70 -0
  41. package/dist/hosted-seller-import.d.ts +2 -0
  42. package/dist/hosted-seller-import.js +70 -0
  43. package/dist/hosted-seller-repository.d.ts +31 -0
  44. package/dist/hosted-seller-repository.js +214 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.js +37 -0
  47. package/dist/indexer.d.ts +63 -0
  48. package/dist/indexer.js +60 -0
  49. package/dist/live-deal-ports.d.ts +41 -0
  50. package/dist/live-deal-ports.js +212 -0
  51. package/dist/live-index.d.ts +28 -0
  52. package/dist/live-index.js +149 -0
  53. package/dist/live-runtime.d.ts +82 -0
  54. package/dist/live-runtime.js +570 -0
  55. package/dist/live-seller-transport.d.ts +30 -0
  56. package/dist/live-seller-transport.js +169 -0
  57. package/dist/live-seller-verifier.d.ts +11 -0
  58. package/dist/live-seller-verifier.js +65 -0
  59. package/dist/mandate.d.ts +34 -0
  60. package/dist/mandate.js +178 -0
  61. package/dist/mcp-bootstrap.d.ts +2 -0
  62. package/dist/mcp-bootstrap.js +39 -0
  63. package/dist/mcp.d.ts +2 -0
  64. package/dist/mcp.js +187 -0
  65. package/dist/memory-worker.d.ts +2 -0
  66. package/dist/memory-worker.js +38 -0
  67. package/dist/memory.d.ts +15 -0
  68. package/dist/memory.js +17 -0
  69. package/dist/memwal.d.ts +35 -0
  70. package/dist/memwal.js +83 -0
  71. package/dist/negotiation.d.ts +23 -0
  72. package/dist/negotiation.js +58 -0
  73. package/dist/notes.d.ts +11 -0
  74. package/dist/notes.js +26 -0
  75. package/dist/onboarding-doctor.d.ts +1 -0
  76. package/dist/onboarding-doctor.js +76 -0
  77. package/dist/phase3.d.ts +100 -0
  78. package/dist/phase3.js +1144 -0
  79. package/dist/procurement.d.ts +8 -0
  80. package/dist/procurement.js +19 -0
  81. package/dist/protocol-engine.d.ts +121 -0
  82. package/dist/protocol-engine.js +149 -0
  83. package/dist/prover.d.ts +28 -0
  84. package/dist/prover.js +70 -0
  85. package/dist/relayer.d.ts +32 -0
  86. package/dist/relayer.js +43 -0
  87. package/dist/remote-buyer.d.ts +67 -0
  88. package/dist/remote-buyer.js +231 -0
  89. package/dist/resource-server.d.ts +2 -0
  90. package/dist/resource-server.js +24 -0
  91. package/dist/resource.d.ts +22 -0
  92. package/dist/resource.js +55 -0
  93. package/dist/seam.d.ts +2 -0
  94. package/dist/seam.js +117 -0
  95. package/dist/seller-catalog-generate.d.ts +2 -0
  96. package/dist/seller-catalog-generate.js +68 -0
  97. package/dist/seller-database.d.ts +41 -0
  98. package/dist/seller-database.js +80 -0
  99. package/dist/seller-directory-cli.d.ts +2 -0
  100. package/dist/seller-directory-cli.js +24 -0
  101. package/dist/seller-directory.d.ts +5 -0
  102. package/dist/seller-directory.js +50 -0
  103. package/dist/seller-live-cli.d.ts +2 -0
  104. package/dist/seller-live-cli.js +53 -0
  105. package/dist/seller-provision.d.ts +2 -0
  106. package/dist/seller-provision.js +84 -0
  107. package/dist/seller-register-cli.d.ts +2 -0
  108. package/dist/seller-register-cli.js +40 -0
  109. package/dist/seller-runtime.d.ts +60 -0
  110. package/dist/seller-runtime.js +96 -0
  111. package/dist/seller-service.d.ts +34 -0
  112. package/dist/seller-service.js +416 -0
  113. package/dist/seller-state.d.ts +19 -0
  114. package/dist/seller-state.js +60 -0
  115. package/dist/seller-stdio.d.ts +6 -0
  116. package/dist/seller-stdio.js +29 -0
  117. package/dist/seller-v2-provision.d.ts +2 -0
  118. package/dist/seller-v2-provision.js +71 -0
  119. package/dist/seller-watch.d.ts +2 -0
  120. package/dist/seller-watch.js +45 -0
  121. package/dist/settlement.d.ts +95 -0
  122. package/dist/settlement.js +141 -0
  123. package/dist/store.d.ts +36 -0
  124. package/dist/store.js +193 -0
  125. package/dist/sui.d.ts +49 -0
  126. package/dist/sui.js +77 -0
  127. package/dist/tatum.d.ts +17 -0
  128. package/dist/tatum.js +83 -0
  129. package/dist/transcript.d.ts +31 -0
  130. package/dist/transcript.js +52 -0
  131. package/dist/v2-exit-relayer.d.ts +34 -0
  132. package/dist/v2-exit-relayer.js +102 -0
  133. package/dist/v2-exit-worker.d.ts +2 -0
  134. package/dist/v2-exit-worker.js +97 -0
  135. package/dist/v2-exits.d.ts +30 -0
  136. package/dist/v2-exits.js +78 -0
  137. package/dist/v2-prefund.d.ts +19 -0
  138. package/dist/v2-prefund.js +43 -0
  139. package/dist/v2-relayer-cli.d.ts +2 -0
  140. package/dist/v2-relayer-cli.js +39 -0
  141. package/dist/wallet.d.ts +32 -0
  142. package/dist/wallet.js +90 -0
  143. package/dist/workflow-memory.d.ts +22 -0
  144. package/dist/workflow-memory.js +49 -0
  145. package/dist/x402-resource-service.d.ts +34 -0
  146. package/dist/x402-resource-service.js +154 -0
  147. package/dist/x402.d.ts +46 -0
  148. package/dist/x402.js +136 -0
  149. package/package.json +60 -3
package/dist/deal.js ADDED
@@ -0,0 +1,228 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { fail } from "./errors.js";
3
+ import { canonicalJson } from "./settlement.js";
4
+ export const DEAL_VERSION = "supercuts.deal.v2";
5
+ const digest = (domain, value) => createHash("sha256").update(canonicalJson({ domain, value })).digest("hex");
6
+ const sha256Bytes = (value) => createHash("sha256").update(value).digest("hex");
7
+ const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
+ export function validateDealInput(value) {
9
+ if (!isObject(value) || Object.keys(value).some((key) => !["operationId", "capabilityId", "capabilityClass", "input", "budget", "privacyHint", "requestedRail"].includes(key)))
10
+ fail("DEAL_INPUT_INVALID", "deal input contains invalid fields");
11
+ const item = value;
12
+ if (typeof item.operationId !== "string" || item.operationId.length < 1 || item.operationId.length > 256 || Number(typeof item.capabilityId === "string") + Number(typeof item.capabilityClass === "string") !== 1 || item.capabilityId !== undefined && (!item.capabilityId || item.capabilityId.length > 256) || item.capabilityClass !== undefined && (!item.capabilityClass || item.capabilityClass.length > 256))
13
+ fail("DEAL_INPUT_INVALID", "deal requires one capability selector and a stable operation ID");
14
+ if (!isObject(item.input) || !isObject(item.budget) || Object.keys(item.budget).some((key) => !["asset", "maximum"].includes(key)) || typeof item.budget.asset !== "string" || !item.budget.asset || typeof item.budget.maximum !== "string" || !/^[1-9][0-9]*$/.test(item.budget.maximum))
15
+ fail("DEAL_INPUT_INVALID", "deal input or budget is invalid");
16
+ if (item.privacyHint !== undefined && !["any", "shielded"].includes(item.privacyHint) || item.requestedRail !== undefined && !["shielded", "x402"].includes(item.requestedRail))
17
+ fail("DEAL_INPUT_INVALID", "deal privacy or rail hint is invalid");
18
+ const encoded = canonicalJson(item);
19
+ if (Buffer.byteLength(encoded) > 64 * 1024)
20
+ fail("DEAL_INPUT_TOO_LARGE", "deal input exceeds 64 KiB");
21
+ }
22
+ export function dealChildId(operationId, step) {
23
+ if (!operationId || operationId.length > 256)
24
+ fail("DEAL_INPUT_INVALID", "operation ID is invalid");
25
+ return `deal:${step}:${digest("supercuts-deal-child-v1", operationId).slice(0, 48)}`;
26
+ }
27
+ const transitions = {
28
+ created: ["discovered", "failed", "unresolved"], discovered: ["planned", "failed", "unresolved"],
29
+ planned: ["awaiting_approval", "review_required", "funding_required", "negotiating", "failed", "unresolved"],
30
+ awaiting_approval: ["negotiating", "failed"], review_required: ["planned", "failed"],
31
+ funding_required: ["planned", "payment_pending", "failed", "unresolved"],
32
+ negotiating: ["agreed", "failed", "unresolved"], agreed: ["funding_required", "evidence_ready", "failed", "unresolved"],
33
+ evidence_ready: ["payment_pending", "failed", "unresolved"], payment_pending: ["paid", "funding_required", "unresolved"],
34
+ paid: ["delivery_pending", "unresolved"], delivery_pending: ["delivered", "unresolved"],
35
+ delivered: ["outcome_pending", "unresolved"], outcome_pending: ["complete", "unresolved"],
36
+ complete: [], failed: [], unresolved: ["discovered", "planned", "negotiating", "agreed", "evidence_ready", "payment_pending", "paid", "delivery_pending", "delivered", "outcome_pending"],
37
+ };
38
+ export class DealJournal {
39
+ store;
40
+ constructor(store) {
41
+ this.store = store;
42
+ }
43
+ create(input) {
44
+ validateDealInput(input);
45
+ const inputHash = digest("supercuts-deal-input-v2", input);
46
+ this.store.db.exec("BEGIN IMMEDIATE");
47
+ try {
48
+ const prior = this.store.db.prepare("SELECT input_hash FROM deal_operations WHERE operation_id=?").get(input.operationId);
49
+ if (prior && prior.input_hash !== inputHash)
50
+ fail("DEAL_OPERATION_CONFLICT", "operation ID identifies a different deal input");
51
+ if (!prior) {
52
+ const now = Date.now();
53
+ this.store.putSecretOnce(`deal:input:${input.operationId}`, Buffer.from(canonicalJson(input)));
54
+ this.store.db.prepare("INSERT INTO deal_operations(operation_id,deal_id,input_hash,state,payment_operation_id,created_at,updated_at) VALUES(?,?,?,'created',?,?,?)")
55
+ .run(input.operationId, randomUUID(), inputHash, dealChildId(input.operationId, "payment"), now, now);
56
+ }
57
+ this.store.db.exec("COMMIT");
58
+ }
59
+ catch (error) {
60
+ this.store.db.exec("ROLLBACK");
61
+ throw error;
62
+ }
63
+ return this.get(input.operationId);
64
+ }
65
+ get(operationId) {
66
+ const row = this.store.db.prepare("SELECT deal_id,input_hash,state,rail,plan_hash,payment_operation_id FROM deal_operations WHERE operation_id=?").get(operationId);
67
+ if (!row)
68
+ return fail("DEAL_NOT_FOUND", "deal operation was not found");
69
+ const agreement = this.store.db.prepare("SELECT status FROM deal_blobs WHERE operation_id=? AND kind='agreement'").get(operationId);
70
+ const delivery = this.store.db.prepare("SELECT status FROM deal_blobs WHERE operation_id=? AND kind='delivery'").get(operationId);
71
+ const memory = this.store.db.prepare("SELECT status FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(operationId);
72
+ const timings = this.store.db.prepare("SELECT stage,duration_ms FROM deal_timings WHERE operation_id=?").all(operationId);
73
+ const payment = this.store.getOperation(row.payment_operation_id);
74
+ const hasLiveTransactions = Boolean(this.store.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='live_signed_transactions'").get());
75
+ const signed = hasLiveTransactions ? this.store.db.prepare("SELECT status,digest FROM live_signed_transactions WHERE operation_id=?").get(row.payment_operation_id) : undefined;
76
+ const verifiedPayment = this.step(operationId, "payment");
77
+ const finalized = Boolean(verifiedPayment?.finalized && verifiedPayment.tatumVerified && ((payment?.status === "confirmed" || payment?.status === "delivered") && payment.txDigest === verifiedPayment.transactionDigest || signed?.status === "finalized" && signed.digest === verifiedPayment.transactionDigest));
78
+ return { version: DEAL_VERSION, dealId: row.deal_id, operationId, state: row.state, ...(row.rail ? { rail: row.rail, visibility: row.rail === "shielded" ? "shielded-direct-payment" : "public-x402" } : {}), ...(row.plan_hash ? { planHash: row.plan_hash } : {}), ...(finalized ? { paymentDigest: verifiedPayment.transactionDigest } : {}), evidence: { agreement: agreement?.status === "verified" ? "verified" : "pending", payment: finalized ? "finalized" : "pending", delivery: delivery?.status === "verified" ? "verified" : "pending", outcomeMemory: memory?.status === "indexed" ? "indexed" : memory?.status === "unresolved" ? "unresolved" : "pending" }, ...(timings.length ? { timingsMs: Object.fromEntries(timings.map((item) => [item.stage, item.duration_ms])) } : {}), ...(row.state === "funding_required" ? { nextAction: "fund_buyer" } : row.state === "awaiting_approval" ? { nextAction: "owner_approval" } : row.state === "review_required" ? { nextAction: "owner_review" } : row.state === "outcome_pending" ? { nextAction: memory?.status === "unresolved" ? "reconcile_memory" : "check_memory" } : !["complete", "failed"].includes(row.state) ? { nextAction: "resume" } : {}) };
79
+ }
80
+ input(operationId) {
81
+ this.get(operationId);
82
+ const encoded = this.store.getSecret(`deal:input:${operationId}`) ?? fail("DEAL_INPUT_MISSING", "encrypted deal input was not found");
83
+ let value;
84
+ try {
85
+ value = JSON.parse(encoded.toString());
86
+ }
87
+ catch (error) {
88
+ return fail("DEAL_INPUT_CORRUPT", "encrypted deal input is invalid", error);
89
+ }
90
+ validateDealInput(value);
91
+ return value;
92
+ }
93
+ saveStep(operationId, step, input, result) {
94
+ this.get(operationId);
95
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(step))
96
+ fail("DEAL_STEP_INVALID", "deal step name is invalid");
97
+ const inputHash = digest(`supercuts-deal-step-input-v1:${step}`, input);
98
+ const resultBytes = Buffer.from(canonicalJson(result));
99
+ const resultHash = digest(`supercuts-deal-step-result-v1:${step}`, result);
100
+ this.store.db.exec("BEGIN IMMEDIATE");
101
+ try {
102
+ const previous = this.store.db.prepare("SELECT input_hash,result_hash FROM deal_steps WHERE operation_id=? AND step=?").get(operationId, step);
103
+ if (previous && (previous.input_hash !== inputHash || previous.result_hash !== resultHash))
104
+ fail("DEAL_STEP_CONFLICT", "deal step already has a different input or result");
105
+ this.store.putSecretOnce(`deal:step:${operationId}:${step}`, resultBytes);
106
+ if (!previous)
107
+ this.store.db.prepare("INSERT INTO deal_steps(operation_id,step,input_hash,state,result_hash,updated_at) VALUES(?,?,?,'complete',?,?)").run(operationId, step, inputHash, resultHash, Date.now());
108
+ this.store.db.exec("COMMIT");
109
+ }
110
+ catch (error) {
111
+ this.store.db.exec("ROLLBACK");
112
+ throw error;
113
+ }
114
+ }
115
+ step(operationId, step) {
116
+ const row = this.store.db.prepare("SELECT result_hash FROM deal_steps WHERE operation_id=? AND step=? AND state='complete'").get(operationId, step);
117
+ if (!row)
118
+ return undefined;
119
+ const bytes = this.store.getSecret(`deal:step:${operationId}:${step}`) ?? fail("DEAL_STEP_CORRUPT", "deal step result is missing");
120
+ let result;
121
+ try {
122
+ result = JSON.parse(bytes.toString());
123
+ }
124
+ catch (error) {
125
+ return fail("DEAL_STEP_CORRUPT", "deal step result is invalid", error);
126
+ }
127
+ if (digest(`supercuts-deal-step-result-v1:${step}`, result) !== row.result_hash)
128
+ fail("DEAL_STEP_CORRUPT", "deal step result hash differs");
129
+ return result;
130
+ }
131
+ recordBlob(operationId, kind, ciphertextHash, blobId) {
132
+ this.get(operationId);
133
+ if (!/^[0-9a-f]{64}$/.test(ciphertextHash) || !blobId)
134
+ fail("DEAL_BLOB_INVALID", "deal ciphertext hash or blob ID is invalid");
135
+ const prior = this.store.db.prepare("SELECT ciphertext_hash,blob_id FROM deal_blobs WHERE operation_id=? AND kind=?").get(operationId, kind);
136
+ if (prior && (prior.ciphertext_hash !== ciphertextHash || prior.blob_id !== blobId))
137
+ fail("DEAL_BLOB_CONFLICT", "deal evidence already identifies different ciphertext");
138
+ this.store.db.prepare("INSERT INTO deal_blobs(operation_id,kind,ciphertext_hash,blob_id,status) VALUES(?,?,?,?,'verified') ON CONFLICT(operation_id,kind) DO UPDATE SET status='verified'").run(operationId, kind, ciphertextHash, blobId);
139
+ }
140
+ recordOutcome(operationId, payloadHash, blobId, indexed) {
141
+ this.get(operationId);
142
+ if (!/^[0-9a-f]{64}$/.test(payloadHash) || !blobId)
143
+ fail("DEAL_MEMORY_INVALID", "outcome hash or blob ID is invalid");
144
+ const prior = this.store.db.prepare("SELECT payload_hash,blob_id FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(operationId);
145
+ if (prior && (prior.payload_hash !== payloadHash || prior.blob_id && prior.blob_id !== blobId))
146
+ fail("DEAL_MEMORY_CONFLICT", "outcome memory already identifies a different write");
147
+ this.store.db.prepare("INSERT INTO deal_memory_outbox(operation_id,event_kind,payload_hash,blob_id,status,updated_at) VALUES(?,'outcome',?,?,?,?) ON CONFLICT(operation_id,event_kind) DO UPDATE SET blob_id=COALESCE(deal_memory_outbox.blob_id,excluded.blob_id),status=CASE WHEN deal_memory_outbox.status='indexed' THEN 'indexed' ELSE excluded.status END,updated_at=excluded.updated_at")
148
+ .run(operationId, payloadHash, blobId, indexed ? "indexed" : "pending", Date.now());
149
+ }
150
+ outcomeStatus(operationId) {
151
+ const row = this.store.db.prepare("SELECT payload_hash,blob_id,status FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(operationId);
152
+ return row ? { payloadHash: row.payload_hash, blobId: row.blob_id, status: row.status } : undefined;
153
+ }
154
+ queueOutcome(operationId, payloadHash) {
155
+ this.get(operationId);
156
+ if (!/^[0-9a-f]{64}$/.test(payloadHash))
157
+ fail("DEAL_MEMORY_INVALID", "outcome hash is invalid");
158
+ const prior = this.store.db.prepare("SELECT payload_hash FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(operationId);
159
+ if (prior && prior.payload_hash !== payloadHash)
160
+ fail("DEAL_MEMORY_CONFLICT", "outcome memory identifies different evidence");
161
+ this.store.db.prepare("INSERT OR IGNORE INTO deal_memory_outbox(operation_id,event_kind,payload_hash,blob_id,status,updated_at) VALUES(?,'outcome',?,NULL,'pending',?)").run(operationId, payloadHash, Date.now());
162
+ }
163
+ setOutcomeUnresolved(operationId) {
164
+ this.store.db.prepare("UPDATE deal_memory_outbox SET status='unresolved',updated_at=? WHERE operation_id=? AND event_kind='outcome' AND status='pending'").run(Date.now(), operationId);
165
+ }
166
+ recordTiming(operationId, stage, durationMs) {
167
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(stage) || !Number.isSafeInteger(durationMs) || durationMs < 0)
168
+ fail("DEAL_TIMING_INVALID", "deal timing is invalid");
169
+ this.store.db.prepare("INSERT OR IGNORE INTO deal_timings(operation_id,stage,duration_ms) VALUES(?,?,?)").run(operationId, stage, durationMs);
170
+ }
171
+ evidence(dealId) {
172
+ const row = this.store.db.prepare("SELECT operation_id,payment_operation_id FROM deal_operations WHERE deal_id=?").get(dealId);
173
+ if (!row)
174
+ return fail("DEAL_NOT_FOUND", "deal was not found");
175
+ const view = this.get(row.operation_id);
176
+ const blobs = this.store.db.prepare("SELECT kind,blob_id,status FROM deal_blobs WHERE operation_id=?").all(row.operation_id);
177
+ const memory = this.store.db.prepare("SELECT blob_id,status FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(row.operation_id);
178
+ const transcript = this.step(row.operation_id, "negotiation");
179
+ return { version: "supercuts.deal-evidence.v1", dealId, transcriptBlobIds: transcript?.transcriptBlobs?.map((item) => item.blobId) ?? [], ...(blobs.find((item) => item.kind === "agreement")?.blob_id ? { agreementBlobId: blobs.find((item) => item.kind === "agreement").blob_id } : {}), ...(blobs.find((item) => item.kind === "delivery")?.blob_id ? { deliveryBlobId: blobs.find((item) => item.kind === "delivery").blob_id } : {}), ...(memory?.blob_id ? { outcomeMemoryBlobId: memory.blob_id } : {}), ciphertextReadback: blobs.some((item) => item.status === "unresolved") ? "unresolved" : ["agreement", "delivery"].every((kind) => blobs.some((item) => item.kind === kind && item.status === "verified")) ? "verified" : "pending", inventory: memory?.status === "indexed" ? "indexed" : memory?.status === "unresolved" ? "unresolved" : "pending", paymentFinalized: view.evidence.payment === "finalized", ...(view.paymentDigest ? { paymentDigest: view.paymentDigest } : {}) };
180
+ }
181
+ delivery(dealId) {
182
+ const row = this.store.db.prepare("SELECT operation_id FROM deal_operations WHERE deal_id=?").get(dealId);
183
+ if (!row)
184
+ return fail("DEAL_NOT_FOUND", "deal was not found");
185
+ const view = this.get(row.operation_id);
186
+ if (view.evidence.payment !== "finalized" || view.evidence.delivery !== "verified")
187
+ fail("DEAL_DELIVERY_UNAVAILABLE", "payment and delivery must be verified before artifact access");
188
+ const saved = this.step(row.operation_id, "delivery") ?? fail("DEAL_DELIVERY_UNAVAILABLE", "encrypted delivery is missing");
189
+ const artifact = Buffer.from(saved.artifactBase64, "base64");
190
+ if (sha256Bytes(artifact) !== saved.artifactSha256)
191
+ fail("DEAL_DELIVERY_UNVERIFIED", "saved artifact differs from its delivery hash");
192
+ return { dealId, contentType: saved.contentType, artifactSha256: saved.artifactSha256, artifact };
193
+ }
194
+ advance(operationId, expected, next, result) {
195
+ if (!transitions[expected].includes(next))
196
+ fail("DEAL_TRANSITION_INVALID", `${expected} cannot transition to ${next}`);
197
+ this.store.db.exec("BEGIN IMMEDIATE");
198
+ try {
199
+ const row = this.store.db.prepare("SELECT state,rail,plan_hash FROM deal_operations WHERE operation_id=?").get(operationId);
200
+ if (!row)
201
+ return fail("DEAL_STATE_CONFLICT", "deal state changed before the transition");
202
+ if (result?.rail && row.rail && result.rail !== row.rail || result?.planHash && row.plan_hash && result.planHash !== row.plan_hash)
203
+ fail("DEAL_BINDING_CONFLICT", "deal rail or plan cannot change after binding");
204
+ if (row.state === next) {
205
+ this.store.db.exec("COMMIT");
206
+ return this.get(operationId);
207
+ }
208
+ if (row.state !== expected)
209
+ return fail("DEAL_STATE_CONFLICT", "deal state changed before the transition");
210
+ if (next === "evidence_ready" && !(this.store.db.prepare("SELECT 1 FROM deal_blobs WHERE operation_id=? AND kind='agreement' AND status='verified'").get(operationId)))
211
+ fail("DEAL_EVIDENCE_REQUIRED", "agreement ciphertext readback is required before payment");
212
+ if (next === "paid" && this.get(operationId).evidence.payment !== "finalized")
213
+ fail("DEAL_PAYMENT_UNVERIFIED", "confirmed payment evidence is required");
214
+ if (next === "delivered" && !(this.store.db.prepare("SELECT 1 FROM deal_blobs WHERE operation_id=? AND kind='delivery' AND status='verified'").get(operationId)))
215
+ fail("DEAL_DELIVERY_UNVERIFIED", "verified delivery evidence is required");
216
+ if (next === "complete" && !(this.store.db.prepare("SELECT 1 FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome' AND status='indexed'").get(operationId)))
217
+ fail("DEAL_MEMORY_UNVERIFIED", "indexed outcome memory is required");
218
+ this.store.db.prepare("UPDATE deal_operations SET state=?,rail=COALESCE(?,rail),plan_hash=COALESCE(?,plan_hash),updated_at=? WHERE operation_id=?")
219
+ .run(next, result?.rail ?? null, result?.planHash ?? null, Date.now(), operationId);
220
+ this.store.db.exec("COMMIT");
221
+ }
222
+ catch (error) {
223
+ this.store.db.exec("ROLLBACK");
224
+ throw error;
225
+ }
226
+ return this.get(operationId);
227
+ }
228
+ }
package/dist/demo.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};