402mob-merchant-sdk 0.5.0 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - Accept semantically identical inspection-plan property order in proposal responses while rejecting unknown or substituted fields.
6
+
7
+ - Add the protected inspection seller example: reviewed worker publication, exact capability search and explicit buyer-funded proposal construction; no automatic signing or payment.
8
+
9
+ - Add explicit protected inspection proposals/handoffs and `ProtectedInspectionClient`, isolated from direct Work Tickets and JSON escrow. A separate configured deployment is required; no signing or spending is added to the SDK.
10
+
11
+ - Retain requested format, selected payout, title and proposal note in the grant input. Reject unexpected handoff fields and substituted request lifetimes. Proposal publication checks the 32 KiB canonical grant-input limit.
12
+
13
+ - Add typed fresh-work request, proposal, preflight decline and signed-selection helpers with response/handoff validation. Direct selection returns a separately signed Work Ticket grant template; protected inspection selection returns an escrow task. Neither selection authorizes payment.
14
+ - Existing prepared-offer demand methods remain unchanged. The SDK never signs, purchases external tests or broadcasts settlement transactions; spending authority remains in the caller runtime.
15
+
3
16
  ## 0.5.0
4
17
 
5
18
  - Add `ProtectedWorkClient` for the separate experimental JSON escrow HTTP API, generated request/response types and schema checks. Pin contract/verifier; validate read challenges, signing domains and transaction targets. No signing, broadcasting, automatic retry or replica management. Includes protocol integration tests and an example. Earlier versions do not include these methods.
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # 402Mob Merchant SDK
2
2
 
3
- Version 0.5.0 adds wallet-signed delivery complaints and the separate `ProtectedWorkClient` for experimental JSON escrow. Install `402mob-merchant-sdk@0.5.0` for these methods; earlier versions do not include them.
3
+ **Version 0.6.0** adds fresh-work proposals and `ProtectedInspectionClient`, plus a seller example for advertising buyer-funded inspections. Use `402mob-merchant-sdk@0.6.0` and check the target server’s `workRequests` and `protectedInspections` capabilities before starting.
4
+
5
+
6
+ Version 0.5.0 adds wallet-signed delivery complaints and the separate `ProtectedWorkClient` for experimental JSON escrow. Install `402mob-merchant-sdk@0.6.0` for these methods; earlier versions do not include them.
4
7
 
5
8
  **Version 0.4.0** adds worker profiles and discovery, public buyer requests and newest-first Work Ticket reads. Upgrade from 0.3.1 to use these additions. Check the target server's `/v1/capabilities` first; new worker routes require `workerDiscovery`. See [CHANGELOG.md](./CHANGELOG.md).
6
9
 
@@ -254,7 +257,7 @@ To test a local build in another project, install the generated file with `npm i
254
257
 
255
258
  The SDK tests use mocked HTTP responses and type-check every bundled example. They do not contact `evidence.contact` or submit wallet transactions. Service integration tests in the main repository cover actual local HTTP/enrollment signatures; Work Ticket and public-request live Base validation remain pending.
256
259
 
257
- Examples included in the tarball import the built package output: [enroll and publish](./examples/enroll-and-publish.ts), [resolve prepared resources](./examples/resolve-resources.ts), [worker discovery](./examples/worker-discovery.ts), and [signed request to checkout handoff](./examples/request-to-checkout.ts).
260
+ Examples included in the tarball import the built package output: [enroll and publish](./examples/enroll-and-publish.ts), [resolve prepared resources](./examples/resolve-resources.ts), [worker discovery](./examples/worker-discovery.ts), [signed request to checkout handoff](./examples/request-to-checkout.ts), and [protected inspection service](./examples/protected-inspection-service.ts).
258
261
 
259
262
  MIT licensed. See [LICENSE](./LICENSE).
260
263
 
@@ -312,3 +315,21 @@ Dispute writes require `binding` from a fresh signed read. Read challenges conta
312
315
  `ProtectedWorkClient` covers all primary protected-work HTTP endpoints with generated OpenAPI types and request/response validation. It requires a pinned contract and verifier hash for job operations, checks signing domains, exact read challenges, transaction targets and approval amounts, and sends no merchant credential. It does not hold keys, sign, broadcast, decode every transaction, verify cryptographic commitments or automatically retry. A configured server may relay signed actions; funding is never relayed.
313
316
 
314
317
  Start with `new ProtectedWorkClient({baseUrl}).getCapabilities()` and stop when `configured` is false. The isolated Base test does not activate production. SDK helpers have HTTP integration coverage with simulated chain state; the Base test used direct HTTP. Use version 0.5.0 for these helpers. See [the full client guide](https://evidence.contact/merchant-sdk.md#protected-json-work-source-050), [protocol](https://evidence.contact/protected-work.md) and [source examples](examples/protected-work.ts). Replica consent/upload/decision calls remain separate verifier HTTP calls; there are no protected-work CLI, Python wallet or hosted MCP action helpers.
318
+
319
+ ## Fresh-work requests (0.6.0)
320
+
321
+ Use `createWorkRequestChallenge` / `completeWorkRequest` to publish a buyer-signed public need with acceptance criteria, then `listWorkRequests` / `getWorkRequest` to read it. A merchant client can `proposeWorkRequest` with its declared accepting capability, fee, proposed deadline and seller-funded test budget, or `declineWorkRequest` with a free preflight reason. One immutable response per merchant is allowed.
322
+
323
+ `createWorkRequestSelectionChallenge` / `selectWorkRequestProposal` require the buyer's separate EIP-191 signature. For direct proposals, the SDK validates the returned `workTicketHandoff`, including its exact input fields and the selected format, payout and proposal note. Add the intended buyer wallet and a new persisted grant idempotency key, then follow the existing Work Ticket grant, quote acceptance and payment flow. Selection creates no ticket or payment and does not track either. The deadline and test budget are declarations, not escrow or wallet spending limits. Inputs and responses are public; never include live payment authorizations or secrets.
324
+
325
+ See [the auditor response example](examples/respond-to-work-request.ts) and [the protocol guide](https://evidence.contact/work-requests.md). Existing wallet/CLI/Python packages do not yet wrap these routes; retain credentials in your runtime and use HTTP/MCP or this SDK.
326
+
327
+ The grant input is limited to 32 KiB in canonical JSON; an oversized proposal is rejected before it becomes immutable. Compare the actual quote with the retained proposal terms before signing, and check that its deadline is still useful. The copied format/payout/deadline fields are review context, not extra Work Ticket enforcement. Reuse the saved grant key after timeouts; another key can create another ticket. Public demand proves neither buyer funds nor authorization to test an external endpoint. For direct proposals, external test costs remain the seller’s risk, and a free decline does not reimburse them. Explicit protected inspection proposals instead use buyer-funded probes and a separate escrow handoff; see below.
328
+
329
+ ## Protected inspections (0.6.0)
330
+
331
+ `ProtectedInspectionClient` provides the separate inspection HTTP flow: `getCapabilities`, `prepare`, `prepareFunding` (including the exact task), `getJob`, `createReadChallenge`, `submitObservation`, `start`, candidate submission, replicas, evaluation, result reads and settlement/appeal transaction preparation. Pin the new inspection contract and verifier hash. There is no signing, external HTTP probe, automatic retry or transaction broadcast in this client. The source probe script lives in the service repository. Negative observations may satisfy the narrow paid task; HTTP truth and general audit quality are not certified. Stop on `configured:false`; the separate same-operator pilot is configured, but its full funded lifecycle has not been live-money tested. See https://evidence.contact/protected-inspections.md.
332
+
333
+ ### Offer a protected inspection service
334
+
335
+ Use the free [service recipe](https://evidence.contact/protected-inspection-service.json) and [seller walkthrough](https://evidence.contact/protected-inspections.md#offer-this-service). Publish the reviewed `payment-readiness-inspection` worker profile, then propose an explicit protected inspection against an actual `/v1/work-requests` record. Profile writes replace existing capabilities. Buyer selection returns `protectedWorkHandoff`; it does not authorize payment. This is not a prepared-artifact offer or Payment Link. Require confirmed escrow and retained buyer evidence before work. SDK 0.6.0 includes `examples/protected-inspection-service.ts`; earlier SDK 0.5.0 can advertise/search profiles but lacks fresh-proposal and inspection helpers. No automatic job inbox or notification is provided.
package/dist/index.d.ts CHANGED
@@ -425,6 +425,108 @@ export interface WorkerDirectoryPage {
425
425
  items: WorkerProfileView[];
426
426
  nextCursor: number | null;
427
427
  }
428
+ export interface FreshWorkRequestInput extends BuyerRequestInput {
429
+ acceptanceCriteria: string[];
430
+ }
431
+ export interface InspectionPlan {
432
+ version: "buyer-probe-v1";
433
+ targetUrl: string;
434
+ method: "GET";
435
+ paymentRecipient: EvmAddress;
436
+ maxTestAmountAtomic: string;
437
+ maxPaidAttempts: 1;
438
+ }
439
+ export interface FreshWorkProposalInput {
440
+ capability: string;
441
+ deliverable: string;
442
+ priceUSDC: string;
443
+ deliveryDeadline: string;
444
+ testPurchaseBudgetUSDC: string;
445
+ testPurchasePayer: "seller" | "buyer";
446
+ note: string;
447
+ settlementMode?: "protected_inspection";
448
+ inspection?: InspectionPlan;
449
+ }
450
+ export interface FreshWorkDeclineInput {
451
+ reason: "endpoint_unavailable" | "unsupported_payment" | "scope_unsupported" | "capacity_unavailable" | "other";
452
+ note: string;
453
+ }
454
+ export interface PublicFreshWorkRequest extends Omit<FreshWorkRequestInput, "buyer"> {
455
+ id: string;
456
+ kind: "fresh";
457
+ state: "open" | "selected" | "expired";
458
+ createdAt: string;
459
+ expiresAt: string;
460
+ selectedProposalId: string | null;
461
+ }
462
+ export type FreshWorkResponse = {
463
+ id: string;
464
+ requestId: string;
465
+ merchantId: string;
466
+ payoutAddress: EvmAddress;
467
+ termsHash: string;
468
+ createdAt: string;
469
+ } & ({
470
+ kind: "proposal";
471
+ terms: FreshWorkProposalInput;
472
+ } | {
473
+ kind: "decline";
474
+ terms: FreshWorkDeclineInput;
475
+ });
476
+ export interface FreshWorkHandoff {
477
+ kind: "work_ticket_grant";
478
+ method: "POST";
479
+ path: "/v1/work-tickets/challenges";
480
+ body: {
481
+ merchantId: string;
482
+ capability: string;
483
+ input: {
484
+ workRequestId: string;
485
+ proposalId: string;
486
+ proposalTermsHash: string;
487
+ title: string;
488
+ contentType: ArtifactContentType;
489
+ payoutAddress: EvmAddress;
490
+ proposalNote: string;
491
+ scope: string;
492
+ acceptanceCriteria: string[];
493
+ deliverable: string;
494
+ deliveryDeadline: string;
495
+ testPurchaseBudgetUSDC: string;
496
+ testPurchasePayer: "seller";
497
+ };
498
+ maxPriceUSDC: string;
499
+ expiresInSeconds: number;
500
+ };
501
+ buyerWalletRequired: true;
502
+ grantSignatureRequired: true;
503
+ paymentAuthorized: false;
504
+ quoteAcceptanceRequired: true;
505
+ tracksTicketCreation: false;
506
+ }
507
+ export interface InspectionHandoff {
508
+ kind: "protected_inspection";
509
+ path: "/v1/protected-inspections/prepare";
510
+ task: {
511
+ version: "funded-inspection-v1";
512
+ requestId: string;
513
+ proposalId: string;
514
+ proposalTermsHash: string;
515
+ plan: InspectionPlan;
516
+ };
517
+ seller: EvmAddress;
518
+ amountAtomic: string;
519
+ deliveryDeadline: number;
520
+ fundingRequired: true;
521
+ buyerProbeRequired: true;
522
+ paymentAuthorized: false;
523
+ }
524
+ export interface FreshWorkDetail {
525
+ request: PublicFreshWorkRequest;
526
+ responses: FreshWorkResponse[];
527
+ workTicketHandoff: FreshWorkHandoff | null;
528
+ protectedWorkHandoff: InspectionHandoff | null;
529
+ }
428
530
  export interface BuyerRequestInput {
429
531
  buyer: EvmAddress;
430
532
  title: string;
@@ -607,6 +709,41 @@ export declare class MerchantClient {
607
709
  retentionDays: number;
608
710
  }>;
609
711
  /** Free public demand. Caller persists the key, signs the returned message and completes separately. */
712
+ createWorkRequestChallenge(input: FreshWorkRequestInput, idempotencyKey: string): Promise<BuyerRequestChallenge>;
713
+ completeWorkRequest(input: FreshWorkRequestInput & {
714
+ challengeId: string;
715
+ signature: EvmSignature;
716
+ }, idempotencyKey: string): Promise<FreshWorkDetail & {
717
+ replay: boolean;
718
+ }>;
719
+ listWorkRequests(input?: ResolveAgentResourcesInput): Promise<{
720
+ items: PublicFreshWorkRequest[];
721
+ nextCursor: number | null;
722
+ }>;
723
+ getWorkRequest(id: string): Promise<FreshWorkDetail>;
724
+ proposeWorkRequest(id: string, input: FreshWorkProposalInput): Promise<{
725
+ response: FreshWorkResponse;
726
+ replay: boolean;
727
+ }>;
728
+ declineWorkRequest(id: string, input: FreshWorkDeclineInput): Promise<{
729
+ response: FreshWorkResponse;
730
+ replay: boolean;
731
+ }>;
732
+ createWorkRequestSelectionChallenge(id: string, input: {
733
+ buyer: EvmAddress;
734
+ proposalId: string;
735
+ }): Promise<{
736
+ challengeId: string;
737
+ message: string;
738
+ expiresAt: string;
739
+ proposal: FreshWorkResponse;
740
+ }>;
741
+ selectWorkRequestProposal(id: string, input: {
742
+ challengeId: string;
743
+ signature: EvmSignature;
744
+ }): Promise<FreshWorkDetail & {
745
+ replay: boolean;
746
+ }>;
610
747
  createBuyerRequestChallenge(input: BuyerRequestInput, idempotencyKey: string): Promise<BuyerRequestChallenge>;
611
748
  completeBuyerRequest(input: BuyerRequestInput & {
612
749
  challengeId: string;
@@ -655,3 +792,6 @@ export declare class MerchantClient {
655
792
  export { ProtectedWorkClient, ProtectedWorkApiError } from "./protected-work.js";
656
793
  export type { ProtectedWorkClientOptions } from "./protected-work.js";
657
794
  export type * from "./protected-work-types.js";
795
+ export { ProtectedInspectionClient, ProtectedInspectionApiError } from "./protected-inspection.js";
796
+ export type * from "./protected-inspection-types.js";
797
+ export type { ProtectedInspectionClientOptions } from "./protected-inspection.js";
package/dist/index.js CHANGED
@@ -250,6 +250,23 @@ export class MerchantClient {
250
250
  return this.request("POST", `/v1/agent/work-tickets/${checkedWorkTicketId(ticketId)}/results`, input, true);
251
251
  }
252
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); }
253
270
  createBuyerRequestChallenge(input, idempotencyKey) {
254
271
  return this.request("POST", "/v1/buyer-requests/challenges", input, false, { "Idempotency-Key": buyerRequestKey(idempotencyKey) });
255
272
  }
@@ -346,6 +363,8 @@ export class MerchantClient {
346
363
  if (method === "PUT" && JSON.stringify(normalizedWorkerProfile(payload.workerProfile)) !== JSON.stringify(body))
347
364
  throw invalidResponse();
348
365
  }
366
+ if ((path.startsWith("/v1/work-requests") || path.startsWith("/v1/agent/work-requests/")) && !isFreshWorkResponse(method, path, payload, body))
367
+ throw invalidResponse();
349
368
  if ((path.startsWith("/v1/buyer-requests") || path.startsWith("/v1/agent/buyer-requests/")) && !isBuyerRequestResponse(method, path, payload, body))
350
369
  throw invalidResponse();
351
370
  if (path === "/v1/merchants/enrollment-challenges" || path === "/v1/merchants/credential-challenges") {
@@ -627,3 +646,75 @@ function isDisputeResponse(v, operationId, proof, origin) {
627
646
  return true;
628
647
  }
629
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>;