402mob-merchant-sdk 0.4.2 → 0.6.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.
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ function requireEip191Signature(signature) {
4
4
  }
5
5
  return signature;
6
6
  }
7
+ const disputeConsent = "This signature authorizes only this statement or private read. It does not authorize payment, refund or adjudication. Response targets: seller 48 hours after opening; buyer 7 days after first response. Silence is not acceptance.";
7
8
  function buyerRequestKey(key) {
8
9
  if (!/^[A-Za-z0-9_-]{16,128}$/.test(key))
9
10
  throw new TypeError("Persist an idempotency key of 16–128 letters, digits, underscores or hyphens.");
@@ -249,6 +250,23 @@ export class MerchantClient {
249
250
  return this.request("POST", `/v1/agent/work-tickets/${checkedWorkTicketId(ticketId)}/results`, input, true);
250
251
  }
251
252
  /** Free public demand. Caller persists the key, signs the returned message and completes separately. */
253
+ createWorkRequestChallenge(input, idempotencyKey) { return this.request("POST", "/v1/work-requests/challenges", input, false, { "Idempotency-Key": idempotencyKey }); }
254
+ completeWorkRequest(input, idempotencyKey) { return this.request("POST", "/v1/work-requests", input, false, { "Idempotency-Key": idempotencyKey }); }
255
+ listWorkRequests(input = {}) {
256
+ const q = new URLSearchParams();
257
+ if (input.query !== undefined)
258
+ q.set("q", input.query);
259
+ if (input.limit !== undefined)
260
+ q.set("limit", String(input.limit));
261
+ if (input.cursor !== undefined)
262
+ q.set("cursor", String(input.cursor));
263
+ return this.request("GET", "/v1/work-requests?" + q);
264
+ }
265
+ getWorkRequest(id) { return this.request("GET", `/v1/work-requests/${encodeURIComponent(id)}`); }
266
+ proposeWorkRequest(id, input) { return this.request("POST", `/v1/agent/work-requests/${encodeURIComponent(id)}/proposals`, input, true); }
267
+ declineWorkRequest(id, input) { return this.request("POST", `/v1/agent/work-requests/${encodeURIComponent(id)}/declines`, input, true); }
268
+ createWorkRequestSelectionChallenge(id, input) { return this.request("POST", `/v1/work-requests/${encodeURIComponent(id)}/selection-challenges`, input); }
269
+ selectWorkRequestProposal(id, input) { return this.request("POST", `/v1/work-requests/${encodeURIComponent(id)}/selections`, input); }
252
270
  createBuyerRequestChallenge(input, idempotencyKey) {
253
271
  return this.request("POST", "/v1/buyer-requests/challenges", input, false, { "Idempotency-Key": buyerRequestKey(idempotencyKey) });
254
272
  }
@@ -274,6 +292,18 @@ export class MerchantClient {
274
292
  selectBuyerRequestOffer(requestId, input) {
275
293
  return this.request("POST", `/v1/buyer-requests/${checkedWorkTicketId(requestId)}/selections`, { ...input, signature: requireEip191Signature(input.signature) });
276
294
  }
295
+ /** Free, wallet-signed complaint/response/closure/read. No bearer key or payment is sent. */
296
+ createDeliveryDisputeChallenge(operationId, input) {
297
+ if (!isDisputeRequest(input))
298
+ throw new TypeError("Provide a participant wallet, supported action, current revision and bounded statement/evidence hashes.");
299
+ return this.request("POST", `/v1/operations/${checkedWorkTicketId(operationId)}/dispute-challenges`, input);
300
+ }
301
+ /** Retry the same signed write after interruption; private reads require a fresh challenge. */
302
+ submitDeliveryDisputeAction(operationId, input) {
303
+ if (!digest(input.challengeId) || !disputeToken(input.challengeToken))
304
+ throw new TypeError("Return the exact challengeId and challengeToken from the challenge response.");
305
+ return this.request("POST", `/v1/operations/${checkedWorkTicketId(operationId)}/dispute-actions`, { ...input, signature: requireEip191Signature(input.signature) });
306
+ }
277
307
  async request(method, path, body, authenticated = false, extraHeaders = {}) {
278
308
  if (authenticated && !this.#apiKey) {
279
309
  throw new Error("This request requires a merchant API key. Use client.withApiKey(apiKey).");
@@ -305,6 +335,17 @@ export class MerchantClient {
305
335
  const invalidResponse = () => new MerchantApiError(response.status, "invalid_response", "402Mob returned an incomplete response. The request may have completed; preserve recovery data. If enrollment or rotation completed without a usable key, use credential rotation.");
306
336
  if (!isRecord(payload))
307
337
  throw invalidResponse();
338
+ if (path.endsWith("/dispute-challenges")) {
339
+ const expected = path.replace(/dispute-challenges$/, "dispute-actions"), id = path.split("/")[3];
340
+ if (!digest(payload.challengeId) || !disputeToken(payload.challengeToken) || !nonempty(payload.message) || !timestamp(payload.expiresAt) || payload.submit !== expected || !isDisputeRequest(body))
341
+ throw invalidResponse();
342
+ const statement = { ...body, wallet: body.wallet.toLowerCase(), ...(body.action !== "read" ? { detail: body.detail.trim(), binding: { ...body.binding, payer: body.binding.payer.toLowerCase(), recipient: body.binding.recipient.toLowerCase(), asset: body.binding.asset.toLowerCase() } } : {}) };
343
+ const fields = disputeMessage(payload.message);
344
+ if (!fields || fields.origin !== new URL(this.baseUrl).origin || fields.operation !== id || fields.nonce !== payload.challengeId || fields.expires !== payload.expiresAt || stableJson(fields.statement) !== stableJson(statement) || stableJson(fields.binding) !== stableJson(body.action === "read" ? null : statement.binding))
345
+ throw invalidResponse();
346
+ }
347
+ if (path.endsWith("/dispute-actions") && !isDisputeResponse(payload, path.split("/")[3], body, new URL(this.baseUrl).origin))
348
+ throw invalidResponse();
308
349
  if (path.startsWith("/v1/worker-directory?")) {
309
350
  if (!Array.isArray(payload.items) || !payload.items.every(item => isWorkerProfileView(item) && item.workerProfile.discoverable && item.workerProfile.availability === "accepting") || !(payload.nextCursor === null || Number.isSafeInteger(payload.nextCursor) && Number(payload.nextCursor) >= 0))
310
351
  throw invalidResponse();
@@ -322,6 +363,8 @@ export class MerchantClient {
322
363
  if (method === "PUT" && JSON.stringify(normalizedWorkerProfile(payload.workerProfile)) !== JSON.stringify(body))
323
364
  throw invalidResponse();
324
365
  }
366
+ if ((path.startsWith("/v1/work-requests") || path.startsWith("/v1/agent/work-requests/")) && !isFreshWorkResponse(method, path, payload, body))
367
+ throw invalidResponse();
325
368
  if ((path.startsWith("/v1/buyer-requests") || path.startsWith("/v1/agent/buyer-requests/")) && !isBuyerRequestResponse(method, path, payload, body))
326
369
  throw invalidResponse();
327
370
  if (path === "/v1/merchants/enrollment-challenges" || path === "/v1/merchants/credential-challenges") {
@@ -479,3 +522,199 @@ function checkedWorkTicketId(id) {
479
522
  }
480
523
  return id;
481
524
  }
525
+ function isDisputeRequest(v) {
526
+ if (!isRecord(v) || !address(v.wallet))
527
+ return false;
528
+ if (v.action === "read")
529
+ return onlyKeys(v, ["wallet", "action"]);
530
+ if (!["open", "respond", "close"].includes(String(v.action)) || !Number.isInteger(v.expectedRevision) || Number(v.expectedRevision) < 0 || Number(v.expectedRevision) > 32 || !boundedWorkerText(v.detail, 2000) || !isRecord(v.binding) || !validMessageBinding(v.binding) || !Array.isArray(v.evidenceHashes) || v.evidenceHashes.length > 8 || !v.evidenceHashes.every(digest))
531
+ return false;
532
+ const common = ["wallet", "action", "expectedRevision", "binding", "detail", "evidenceHashes"];
533
+ if (v.action === "open")
534
+ return v.expectedRevision === 0 && ["not_delivered", "unreadable", "terms_mismatch"].includes(String(v.reason)) && onlyKeys(v, [...common, "reason"]);
535
+ if (v.action === "close")
536
+ return ["resolved", "unresolved"].includes(String(v.outcome)) && onlyKeys(v, [...common, "outcome"]);
537
+ return onlyKeys(v, common);
538
+ }
539
+ function disputeToken(v) { return typeof v === "string" && /^[A-Za-z0-9_-]{40,40000}$/.test(v); }
540
+ function stableJson(v) {
541
+ if (Array.isArray(v))
542
+ return `[${v.map(stableJson).join(",")}]`;
543
+ if (isRecord(v))
544
+ return `{${Object.keys(v).sort().map(k => JSON.stringify(k) + ":" + stableJson(v[k])).join(",")}}`;
545
+ return JSON.stringify(v);
546
+ }
547
+ function disputeMessage(v) {
548
+ if (typeof v !== "string")
549
+ return;
550
+ const lines = v.split("\n");
551
+ if (lines.length !== 8 || lines[7] !== disputeConsent || lines[0] !== "402Mob delivery dispute authorization" || !lines[1]?.startsWith("Origin: ") || !lines[2]?.startsWith("Operation: ") || !lines[3]?.startsWith("Statement: ") || !lines[4]?.startsWith("Binding: ") || !lines[5]?.startsWith("Nonce: ") || !lines[6]?.startsWith("Expires: "))
552
+ return;
553
+ try {
554
+ const binding = JSON.parse(lines[4].slice(9));
555
+ if (binding !== null && !isRecord(binding))
556
+ return;
557
+ return { origin: lines[1].slice(8), operation: lines[2].slice(11), nonce: lines[5].slice(7), expires: lines[6].slice(9), statement: JSON.parse(lines[3].slice(11)), binding };
558
+ }
559
+ catch {
560
+ return;
561
+ }
562
+ }
563
+ function validDisputeBinding(b) {
564
+ return isRecord(b) && onlyKeys(b, ["requestHash", "workTicketId", "quoteTermsHash", "resultHashAtOpen", "transaction", "amountAtomic", "asset", "network"]) && digest(b.requestHash) &&
565
+ ((b.workTicketId === null && b.quoteTermsHash === null) || (uuid(b.workTicketId) && digest(b.quoteTermsHash))) && (b.resultHashAtOpen === null || digest(b.resultHashAtOpen)) &&
566
+ (b.transaction === null || typeof b.transaction === "string" && /^0x[0-9a-fA-F]{64}$/.test(b.transaction)) && atomicAmount(b.amountAtomic) && address(b.asset) && b.asset.toLowerCase() === baseUsdc && b.network === "eip155:8453";
567
+ }
568
+ function validMessageBinding(b) {
569
+ const { payer, recipient, ...rest } = b;
570
+ return address(payer) && address(recipient) && validDisputeBinding(rest);
571
+ }
572
+ function isDisputeResponse(v, operationId, proof, origin) {
573
+ if (v.protocol !== "402mob.delivery-dispute.v1" || v.operationId !== operationId || !isRecord(proof) || typeof v.replay !== "boolean")
574
+ return false;
575
+ if (v.kind === "acknowledgment") {
576
+ const r = v.receipt;
577
+ return onlyKeys(v, ["protocol", "kind", "operationId", "receipt", "replay"]) && isRecord(r) && onlyKeys(r, ["challengeId", "action", "revision", "createdAt"]) && r.challengeId === proof.challengeId && ["open", "respond", "close"].includes(String(r.action)) && Number.isInteger(r.revision) && Number(r.revision) >= 1 && Number(r.revision) <= 32 && timestamp(r.createdAt);
578
+ }
579
+ if (v.kind !== "evidence" || v.replay !== false || !onlyKeys(v, ["protocol", "kind", "operationId", "operationState", "binding", "dispute", "payment", "delivery", "responseOverdue", "buyerReviewOverdue", "scope", "replay"]) || !["executing", "staged", "settling", "pending", "settled", "failed", "expired"].includes(String(v.operationState)) || !isRecord(v.payment) || !onlyKeys(v.payment, ["state", "transaction"]) || !["settled", "pending", "failed", "unconfirmed"].includes(String(v.payment.state)) || !(v.payment.transaction === null || typeof v.payment.transaction === "string" && /^0x[0-9a-fA-F]{64}$/.test(v.payment.transaction)) || !isRecord(v.delivery) || !onlyKeys(v.delivery, ["resultHash", "resultAvailable"]) || typeof v.delivery.resultAvailable !== "boolean" || !(v.delivery.resultHash === null || digest(v.delivery.resultHash)) || typeof v.responseOverdue !== "boolean" || typeof v.buyerReviewOverdue !== "boolean" || !nonempty(v.scope))
580
+ return false;
581
+ if (!isRecord(v.binding) || !validMessageBinding(v.binding) || v.binding.transaction !== v.payment.transaction || v.binding.resultHashAtOpen !== v.delivery.resultHash)
582
+ return false;
583
+ if (v.payment.state === "settled" && v.payment.transaction === null)
584
+ return false;
585
+ const expectedPayment = { executing: "unconfirmed", staged: "unconfirmed", settling: "pending", pending: "pending", settled: "settled", failed: "failed" };
586
+ if (v.operationState !== "expired" && v.payment.state !== expectedPayment[String(v.operationState)])
587
+ return false;
588
+ if (v.operationState === "expired" && !["settled", "unconfirmed"].includes(String(v.payment.state)))
589
+ return false;
590
+ if (v.delivery.resultAvailable && (v.delivery.resultHash === null || v.payment.state !== "settled"))
591
+ return false;
592
+ if (v.binding.workTicketId === null && v.delivery.resultAvailable && v.operationState !== "settled")
593
+ return false;
594
+ if (v.dispute === null)
595
+ return !v.responseOverdue && !v.buyerReviewOverdue;
596
+ const d = v.dispute;
597
+ if (!isRecord(d) || d.operationId !== operationId || !address(d.buyer) || !address(d.seller) || !["open", "responded", "closed"].includes(String(d.status)) || !(d.outcome === null || ["resolved", "unresolved"].includes(String(d.outcome))) || !Number.isInteger(d.revision) || Number(d.revision) < 1 || Number(d.revision) > 32 || !timestamp(d.openedAt) || !timestamp(d.expiresAt) || !timestamp(d.responseDueAt) || !(d.buyerReviewDueAt === null || timestamp(d.buyerReviewDueAt)) || !validDisputeBinding(d.binding) || !Array.isArray(d.events) || d.events.length !== d.revision || v.payment.state !== "settled" || d.binding.transaction !== v.payment.transaction)
598
+ return false;
599
+ const currentBinding = v.binding, caseBinding = d.binding;
600
+ const immutable = ["requestHash", "workTicketId", "quoteTermsHash", "transaction", "amountAtomic", "asset", "network"];
601
+ if (v.binding.payer !== d.buyer || v.binding.recipient !== d.seller || immutable.some(k => currentBinding[k] !== caseBinding[k]))
602
+ return false;
603
+ if (Date.parse(d.expiresAt) !== Date.parse(d.openedAt) + 2592000000 || Date.parse(d.responseDueAt) !== Date.parse(d.openedAt) + 172800000)
604
+ return false;
605
+ let closed, firstResponse, lastTime = Date.parse(d.openedAt);
606
+ const seen = new Set();
607
+ for (let i = 0; i < d.events.length; i++) {
608
+ const e = d.events[i];
609
+ if (!isRecord(e) || e.revision !== i + 1 || !address(e.wallet) || !timestamp(e.createdAt) || !isDisputeRequest(e.statement) || e.statement.action === "read" || e.statement.action !== e.action || e.statement.expectedRevision !== i || e.wallet.toLowerCase() !== e.statement.wallet.toLowerCase() || typeof e.signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(e.signature) || !digest(e.challengeId) || seen.has(e.challengeId))
610
+ return false;
611
+ seen.add(e.challengeId);
612
+ const at = Date.parse(e.createdAt);
613
+ if (at < lastTime || at >= Date.parse(d.expiresAt))
614
+ return false;
615
+ lastTime = at;
616
+ const m = disputeMessage(e.message);
617
+ if (!m || m.origin !== origin || m.operation !== operationId || m.nonce !== e.challengeId || !timestamp(m.expires) || !isRecord(m.binding) || !validMessageBinding(m.binding) || m.binding.payer !== d.buyer || m.binding.recipient !== d.seller || m.binding.requestHash !== d.binding.requestHash || m.binding.quoteTermsHash !== d.binding.quoteTermsHash || immutable.some(k => m.binding[k] !== caseBinding[k]) || stableJson(m.binding) !== stableJson(e.statement.binding) || stableJson(m.statement) !== stableJson(e.statement))
618
+ return false;
619
+ if (i === 0) {
620
+ const { payer: _payer, recipient: _recipient, ...binding } = m.binding;
621
+ if (e.action !== "open" || e.wallet.toLowerCase() !== d.buyer.toLowerCase() || e.createdAt !== d.openedAt || stableJson(binding) !== stableJson(d.binding))
622
+ return false;
623
+ }
624
+ else if (e.action === "respond") {
625
+ if (e.wallet.toLowerCase() !== d.seller.toLowerCase())
626
+ return false;
627
+ if (!closed)
628
+ firstResponse ??= e;
629
+ }
630
+ else if (e.action === "close") {
631
+ if (closed || e.wallet.toLowerCase() !== d.buyer.toLowerCase())
632
+ return false;
633
+ closed = e;
634
+ }
635
+ else
636
+ return false;
637
+ }
638
+ if (d.status !== (closed ? "closed" : firstResponse ? "responded" : "open"))
639
+ return false;
640
+ if (d.outcome !== (closed ? closed.statement.outcome : null))
641
+ return false;
642
+ if (d.buyerReviewDueAt !== (firstResponse ? new Date(Date.parse(firstResponse.createdAt) + 604800000).toISOString() : null))
643
+ return false;
644
+ if ((d.status !== "open" && v.responseOverdue) || (d.status !== "responded" && v.buyerReviewOverdue))
645
+ return false;
646
+ return true;
647
+ }
648
+ export { ProtectedWorkClient, ProtectedWorkApiError } from "./protected-work.js";
649
+ function isFreshRequest(v) {
650
+ if (!isRecord(v) || Object.hasOwn(v, "buyer"))
651
+ return false;
652
+ return uuid(v.id) && v.kind === "fresh" && publicText(v.title, 100) && publicText(v.description, 2000) && usdcAtomic(v.maxPriceUSDC) !== undefined && Number(usdcAtomic(v.maxPriceUSDC)) > 0 && artifactType(v.contentType) && Array.isArray(v.acceptanceCriteria) && v.acceptanceCriteria.length >= 1 && v.acceptanceCriteria.length <= 10 && v.acceptanceCriteria.every(x => publicText(x, 300)) && Number.isInteger(v.expiresInSeconds) && Number(v.expiresInSeconds) >= 60 && Number(v.expiresInSeconds) <= 86400 && timestamp(v.createdAt) && timestamp(v.expiresAt) && ["open", "selected", "expired"].includes(String(v.state)) && (v.state === "selected" ? uuid(v.selectedProposalId) : v.selectedProposalId === null);
653
+ }
654
+ function isInspectionPlan(v) {
655
+ if (!isRecord(v) || !freshExactKeys(v, ["version", "targetUrl", "method", "paymentRecipient", "maxTestAmountAtomic", "maxPaidAttempts"]) || v.version !== "buyer-probe-v1" || v.method !== "GET" || !address(v.paymentRecipient) || v.maxPaidAttempts !== 1 || typeof v.maxTestAmountAtomic !== "string" || !/^(?:0|[1-9][0-9]{0,5}|1000000)$/.test(v.maxTestAmountAtomic) || typeof v.targetUrl !== "string" || v.targetUrl.length > 2048)
656
+ return false;
657
+ try {
658
+ const u = new URL(v.targetUrl);
659
+ return u.protocol === "https:" && !u.username && !u.password && !u.hash && (!u.port || u.port === "443") && u.href === v.targetUrl;
660
+ }
661
+ catch {
662
+ return false;
663
+ }
664
+ }
665
+ function sameInspectionPlan(a, b) { return isInspectionPlan(a) && isInspectionPlan(b) && Object.entries(a).every(([key, value]) => value === b[key]); }
666
+ function isFreshProposal(v) {
667
+ if (!isRecord(v) || !uuid(v.id) || !uuid(v.requestId) || !uuid(v.merchantId) || !address(v.payoutAddress) || !digest(v.termsHash) || !timestamp(v.createdAt) || !isRecord(v.terms) || !publicText(v.terms.note, 300))
668
+ return false;
669
+ const t = v.terms;
670
+ if (v.kind === "decline")
671
+ return ["endpoint_unavailable", "unsupported_payment", "scope_unsupported", "capacity_unavailable", "other"].includes(String(t.reason));
672
+ return v.kind === "proposal" && typeof t.capability === "string" && /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/.test(t.capability) && publicText(t.deliverable, 300) && usdcAtomic(t.priceUSDC) !== undefined && Number(usdcAtomic(t.priceUSDC)) > 0 && timestamp(t.deliveryDeadline) && usdcAtomic(t.testPurchaseBudgetUSDC) !== undefined && (t.settlementMode === "protected_inspection" ? t.testPurchasePayer === "buyer" && isInspectionPlan(t.inspection) && t.inspection.maxTestAmountAtomic === usdcAtomic(t.testPurchaseBudgetUSDC) : t.settlementMode === undefined && t.inspection === undefined && t.testPurchasePayer === "seller");
673
+ }
674
+ function freshExactKeys(value, keys) {
675
+ return Object.keys(value).length === keys.length && keys.every(k => Object.hasOwn(value, k));
676
+ }
677
+ function isFreshDetail(v, id) {
678
+ if (!isRecord(v) || !isFreshRequest(v.request) || id && v.request.id !== id || !Array.isArray(v.responses) || v.responses.length > 20 || !v.responses.every(isFreshProposal))
679
+ return false;
680
+ const r = v.request, responses = v.responses;
681
+ if (new Set(responses.map(p => p.merchantId)).size !== responses.length || new Set(responses.map(p => p.id)).size !== responses.length || responses.some(p => p.requestId !== r.id || p.kind === "proposal" && BigInt(usdcAtomic(p.terms.priceUSDC)) > BigInt(usdcAtomic(r.maxPriceUSDC))))
682
+ return false;
683
+ if (r.state !== "selected")
684
+ return v.workTicketHandoff === null && v.protectedWorkHandoff === null;
685
+ const p = responses.find(p => p.id === r.selectedProposalId), h = v.workTicketHandoff;
686
+ if (p?.kind === "proposal" && p.terms.settlementMode === "protected_inspection") {
687
+ const x = v.protectedWorkHandoff;
688
+ if (h !== null || !isRecord(x) || x.kind !== "protected_inspection" || x.path !== "/v1/protected-inspections/prepare" || !isRecord(x.task))
689
+ return false;
690
+ return r.contentType === "application/json" && x.seller === p.payoutAddress && x.amountAtomic === usdcAtomic(p.terms.priceUSDC) && x.deliveryDeadline === Math.floor(Date.parse(p.terms.deliveryDeadline) / 1000) && x.fundingRequired === true && x.buyerProbeRequired === true && x.paymentAuthorized === false && x.task.version === "funded-inspection-v1" && x.task.requestId === r.id && x.task.proposalId === p.id && x.task.proposalTermsHash === p.termsHash && sameInspectionPlan(x.task.plan, p.terms.inspection) && freshExactKeys(x, ["kind", "path", "task", "seller", "amountAtomic", "deliveryDeadline", "fundingRequired", "buyerProbeRequired", "paymentAuthorized"]) && freshExactKeys(x.task, ["version", "requestId", "proposalId", "proposalTermsHash", "plan"]);
691
+ }
692
+ if (v.protectedWorkHandoff !== null)
693
+ return false;
694
+ if (!p || p.kind !== "proposal" || !isRecord(h) || h.kind !== "work_ticket_grant" || h.method !== "POST" || h.path !== "/v1/work-tickets/challenges" || h.buyerWalletRequired !== true || h.grantSignatureRequired !== true || h.paymentAuthorized !== false || h.quoteAcceptanceRequired !== true || h.tracksTicketCreation !== false || !isRecord(h.body) || !isRecord(h.body.input))
695
+ return false;
696
+ const b = h.body, i = b.input;
697
+ if (!freshExactKeys(h, ["kind", "method", "path", "body", "buyerWalletRequired", "grantSignatureRequired", "paymentAuthorized", "quoteAcceptanceRequired", "tracksTicketCreation"]) || !freshExactKeys(b, ["merchantId", "capability", "input", "maxPriceUSDC", "expiresInSeconds"]) || !freshExactKeys(i, ["workRequestId", "proposalId", "proposalTermsHash", "title", "scope", "contentType", "acceptanceCriteria", "payoutAddress", "deliverable", "proposalNote", "deliveryDeadline", "testPurchaseBudgetUSDC", "testPurchasePayer"]))
698
+ return false;
699
+ return i.title === r.title && i.contentType === r.contentType && i.payoutAddress === p.payoutAddress && i.proposalNote === p.terms.note && b.merchantId === p.merchantId && b.capability === p.terms.capability && b.maxPriceUSDC === p.terms.priceUSDC && b.expiresInSeconds === 3600 && !Object.hasOwn(b, "buyer") && i.workRequestId === r.id && i.proposalId === p.id && i.proposalTermsHash === p.termsHash && i.scope === r.description && JSON.stringify(i.acceptanceCriteria) === JSON.stringify(r.acceptanceCriteria) && i.deliverable === p.terms.deliverable && i.deliveryDeadline === p.terms.deliveryDeadline && i.testPurchaseBudgetUSDC === p.terms.testPurchaseBudgetUSDC && i.testPurchasePayer === "seller";
700
+ }
701
+ function isFreshWorkResponse(method, path, v, body) {
702
+ const args = isRecord(body) ? body : {}, id = path.match(/^\/v1\/(?:agent\/)?work-requests\/([^/?]+)/)?.[1];
703
+ const challenge = () => uuid(v.challengeId) && nonempty(v.message) && timestamp(v.expiresAt);
704
+ if (path === "/v1/work-requests/challenges")
705
+ return challenge() && timestamp(v.requestExpiresAt) && digest(v.policyHash) && Date.parse(String(v.expiresAt)) <= Date.parse(String(v.requestExpiresAt));
706
+ if (path === "/v1/work-requests" && method === "POST")
707
+ return isFreshDetail(v) && typeof v.replay === "boolean" && v.request.title === String(args.title).trim() && v.request.description === String(args.description).trim() && v.request.contentType === args.contentType && v.request.expiresInSeconds === args.expiresInSeconds && usdcAtomic(v.request.maxPriceUSDC) === usdcAtomic(args.maxPriceUSDC) && JSON.stringify(v.request.acceptanceCriteria) === JSON.stringify(args.acceptanceCriteria?.map(x => typeof x === 'string' ? x.trim() : x));
708
+ if (path.startsWith("/v1/work-requests?") || path === "/v1/work-requests" && method === "GET")
709
+ return Array.isArray(v.items) && v.items.length <= 50 && v.items.every(isFreshRequest) && (v.nextCursor === null || Number.isSafeInteger(v.nextCursor) && Number(v.nextCursor) >= 0);
710
+ if (path.endsWith("/selection-challenges"))
711
+ return challenge() && isFreshProposal(v.proposal) && v.proposal.kind === "proposal" && v.proposal.requestId === id && v.proposal.id === args.proposalId;
712
+ if (path.endsWith("/proposals") || path.endsWith("/declines")) {
713
+ if (!isFreshProposal(v.response) || v.response.requestId !== id || v.response.kind !== (path.endsWith("/proposals") ? "proposal" : "decline") || typeof v.replay !== "boolean")
714
+ return false;
715
+ const terms = v.response.terms;
716
+ return Object.entries(args).every(([k, x]) => k === "inspection" ? sameInspectionPlan(terms[k], x) : ["priceUSDC", "testPurchaseBudgetUSDC"].includes(k) ? usdcAtomic(terms[k]) === usdcAtomic(x) : JSON.stringify(terms[k]) === JSON.stringify(typeof x === 'string' ? x.trim() : x));
717
+ }
718
+ return isFreshDetail(v, id) && (method === "GET" || v.request.state === "selected" && typeof v.replay === "boolean");
719
+ }
720
+ export { ProtectedInspectionClient, ProtectedInspectionApiError } from "./protected-inspection.js";
@@ -0,0 +1,2 @@
1
+ export declare const inspectionSchemaHash = "8ce432ff8bb61355db7c2d01e7a5048ec20ed0688087df7094134788a81c81e7";
2
+ export declare const inspectionSchemas: Record<string, unknown>;