@paybond/kit 0.11.11 → 0.12.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 (132) hide show
  1. package/completion-presets/catalog.json +10 -5
  2. package/completion-presets/catalog.sha256 +1 -1
  3. package/dist/agent/evidence.d.ts +10 -0
  4. package/dist/agent/evidence.js +16 -0
  5. package/dist/agent/index.d.ts +3 -2
  6. package/dist/agent/index.js +2 -1
  7. package/dist/agent/interceptor.d.ts +9 -0
  8. package/dist/agent/interceptor.js +181 -1
  9. package/dist/agent/receipt-client.d.ts +64 -0
  10. package/dist/agent/receipt-client.js +45 -0
  11. package/dist/agent/registry.js +1 -0
  12. package/dist/agent/run.d.ts +5 -0
  13. package/dist/agent/run.js +52 -0
  14. package/dist/agent/types.d.ts +70 -0
  15. package/dist/agent-mandate.d.ts +103 -0
  16. package/dist/agent-mandate.js +423 -0
  17. package/dist/agent-receipt-external-attestations.d.ts +56 -0
  18. package/dist/agent-receipt-external-attestations.js +185 -0
  19. package/dist/agent-receipt-pdf-export.d.ts +43 -0
  20. package/dist/agent-receipt-pdf-export.js +109 -0
  21. package/dist/agent-receipt-prevalidate.d.ts +7 -0
  22. package/dist/agent-receipt-prevalidate.js +63 -0
  23. package/dist/agent-receipt.d.ts +154 -0
  24. package/dist/agent-receipt.js +639 -0
  25. package/dist/audit/exports.d.ts +15 -1
  26. package/dist/audit/exports.js +55 -8
  27. package/dist/audit/index.d.ts +2 -2
  28. package/dist/audit/wire.d.ts +12 -0
  29. package/dist/audit/wire.js +13 -1
  30. package/dist/cli/command-spec.js +54 -3
  31. package/dist/cli/commands/dev.js +10 -3
  32. package/dist/cli/commands/discovery.js +17 -6
  33. package/dist/cli/commands/setup.js +19 -1
  34. package/dist/cli/commands/shopify.d.ts +53 -0
  35. package/dist/cli/commands/shopify.js +808 -0
  36. package/dist/cli/help.d.ts +1 -1
  37. package/dist/cli/help.js +15 -5
  38. package/dist/cli/router.js +15 -1
  39. package/dist/commerce-binding.d.ts +59 -0
  40. package/dist/commerce-binding.js +129 -0
  41. package/dist/completion-catalog-digest.d.ts +1 -1
  42. package/dist/completion-catalog-digest.js +1 -1
  43. package/dist/doctor-completion.d.ts +16 -0
  44. package/dist/doctor-completion.js +157 -1
  45. package/dist/index.d.ts +108 -7
  46. package/dist/index.js +221 -18
  47. package/dist/mcp-receipt-resource.d.ts +10 -0
  48. package/dist/mcp-receipt-resource.js +32 -0
  49. package/dist/mcp-server.d.ts +7 -0
  50. package/dist/mcp-server.js +81 -1
  51. package/dist/mpp-commercial.d.ts +19 -0
  52. package/dist/mpp-commercial.js +34 -0
  53. package/dist/mpp-funding.d.ts +71 -0
  54. package/dist/mpp-funding.js +192 -0
  55. package/dist/payment-transport.d.ts +30 -0
  56. package/dist/payment-transport.js +56 -0
  57. package/dist/policy/intent-spec.js +2 -0
  58. package/dist/policy/presets.d.ts +1 -1
  59. package/dist/policy/presets.js +1 -0
  60. package/dist/principal-intent.d.ts +1 -1
  61. package/dist/principal-intent.js +4 -1
  62. package/dist/project-init.d.ts +1 -1
  63. package/dist/project-init.js +1 -0
  64. package/dist/protocol-receipt.d.ts +129 -0
  65. package/dist/protocol-receipt.js +620 -0
  66. package/dist/shopify/checkout.d.ts +29 -0
  67. package/dist/shopify/checkout.js +79 -0
  68. package/dist/shopify/evidence.d.ts +13 -0
  69. package/dist/shopify/evidence.js +85 -0
  70. package/dist/shopify/index.d.ts +8 -0
  71. package/dist/shopify/index.js +6 -0
  72. package/dist/shopify/instrument.d.ts +61 -0
  73. package/dist/shopify/instrument.js +52 -0
  74. package/dist/shopify/order.d.ts +8 -0
  75. package/dist/shopify/order.js +110 -0
  76. package/dist/shopify/types.d.ts +82 -0
  77. package/dist/shopify/types.js +4 -0
  78. package/dist/solutions/catalog.d.ts +1 -1
  79. package/dist/solutions/catalog.js +1 -1
  80. package/dist/stripe-commerce/evidence.d.ts +13 -0
  81. package/dist/stripe-commerce/evidence.js +164 -0
  82. package/dist/stripe-commerce/index.d.ts +3 -0
  83. package/dist/stripe-commerce/index.js +2 -0
  84. package/dist/stripe-commerce/metadata.d.ts +11 -0
  85. package/dist/stripe-commerce/metadata.js +34 -0
  86. package/dist/stripe-commerce/types.d.ts +38 -0
  87. package/dist/stripe-commerce/types.js +1 -0
  88. package/dist/template-init.d.ts +1 -1
  89. package/dist/template-init.js +6 -1
  90. package/package.json +1 -1
  91. package/policy/presets/stripe-commerce.yaml +19 -0
  92. package/solutions/stripe-commerce.json +19 -0
  93. package/templates/manifest.json +82 -10
  94. package/templates/openai-shopping-agent/package-lock.json +13 -13
  95. package/templates/openai-shopping-agent/src/paybond.config.ts +1 -1
  96. package/templates/paybond-aws-operator/package-lock.json +7 -7
  97. package/templates/paybond-aws-operator/src/paybond.config.ts +1 -1
  98. package/templates/paybond-claude-agents-demo/package-lock.json +10 -10
  99. package/templates/paybond-claude-agents-demo/src/paybond.config.ts +1 -1
  100. package/templates/paybond-mastra-travel-agent/package-lock.json +33 -33
  101. package/templates/paybond-mastra-travel-agent/src/paybond.config.ts +1 -1
  102. package/templates/paybond-mcp-coding-agent/package-lock.json +7 -7
  103. package/templates/paybond-mcp-coding-agent/src/paybond.config.ts +1 -1
  104. package/templates/paybond-openai-agents-demo/package-lock.json +13 -13
  105. package/templates/paybond-openai-agents-demo/src/paybond.config.ts +1 -1
  106. package/templates/paybond-procurement-agent/package-lock.json +7 -7
  107. package/templates/paybond-procurement-agent/src/paybond.config.ts +1 -1
  108. package/templates/paybond-shopify-shopping-agent/.env.example +3 -0
  109. package/templates/paybond-shopify-shopping-agent/.github/workflows/smoke.yml +20 -0
  110. package/templates/paybond-shopify-shopping-agent/LICENSE +201 -0
  111. package/templates/paybond-shopify-shopping-agent/README.md +29 -0
  112. package/templates/paybond-shopify-shopping-agent/package-lock.json +223 -0
  113. package/templates/paybond-shopify-shopping-agent/package.json +20 -0
  114. package/templates/paybond-shopify-shopping-agent/paybond.policy.yaml +22 -0
  115. package/templates/paybond-shopify-shopping-agent/src/index.ts +54 -0
  116. package/templates/paybond-shopify-shopping-agent/src/paybond.config.ts +51 -0
  117. package/templates/paybond-shopify-shopping-agent/tsconfig.json +13 -0
  118. package/templates/paybond-stripe-agent-demo/.env.example +9 -0
  119. package/templates/paybond-stripe-agent-demo/.github/workflows/smoke.yml +20 -0
  120. package/templates/paybond-stripe-agent-demo/LICENSE +201 -0
  121. package/templates/paybond-stripe-agent-demo/README.md +50 -0
  122. package/templates/paybond-stripe-agent-demo/package-lock.json +223 -0
  123. package/templates/paybond-stripe-agent-demo/package.json +20 -0
  124. package/templates/paybond-stripe-agent-demo/paybond.policy.yaml +22 -0
  125. package/templates/paybond-stripe-agent-demo/src/charge-customer.ts +237 -0
  126. package/templates/paybond-stripe-agent-demo/src/index.ts +78 -0
  127. package/templates/paybond-stripe-agent-demo/src/paybond.config.ts +51 -0
  128. package/templates/paybond-stripe-agent-demo/tsconfig.json +13 -0
  129. package/templates/paybond-travel-agent/package-lock.json +13 -13
  130. package/templates/paybond-travel-agent/src/paybond.config.ts +1 -1
  131. package/templates/paybond-vercel-shopping-agent/package-lock.json +7 -7
  132. package/templates/paybond-vercel-shopping-agent/src/paybond.config.ts +1 -1
@@ -1,3 +1,4 @@
1
+ import type { AgentReceiptExternalAttestationV1 } from "../agent-receipt.js";
1
2
  /** Context passed to evidence mappers after a successful side-effecting tool call. */
2
3
  export type PaybondToolCallContext = {
3
4
  toolName: string;
@@ -9,6 +10,8 @@ export type PaybondToolCallContext = {
9
10
  export type PaybondSpendResolver<TArgs = unknown> = (args: TArgs) => number | undefined;
10
11
  /** Customize auto-evidence payload extraction from a tool result. */
11
12
  export type PaybondEvidenceMapper<TResult = unknown> = (result: TResult, ctx: PaybondToolCallContext) => Record<string, unknown>;
13
+ /** Optional partner attestation material for Agent Receipt `external_attestations`. */
14
+ export type PaybondExternalAttestationMapper<TResult = unknown> = (result: TResult, ctx: PaybondToolCallContext) => PaybondExternalAttestationInput | PaybondExternalAttestationInput[] | undefined;
12
15
  /** Policy for one registered side-effecting tool. */
13
16
  export type PaybondSideEffectingToolPolicy<TArgs = unknown, TResult = unknown> = {
14
17
  /** Harbor `allowed_tools` operation; defaults to the registry key (tool name). */
@@ -17,6 +20,7 @@ export type PaybondSideEffectingToolPolicy<TArgs = unknown, TResult = unknown> =
17
20
  /** Required completion catalog preset id for auto-evidence. */
18
21
  evidencePreset: string;
19
22
  evidenceMapper?: PaybondEvidenceMapper<TResult>;
23
+ externalAttestationMapper?: PaybondExternalAttestationMapper<TResult>;
20
24
  };
21
25
  export type PaybondToolRegistryConfig = {
22
26
  sideEffecting?: Record<string, PaybondSideEffectingToolPolicy>;
@@ -29,6 +33,7 @@ export type PaybondSideEffectingToolEntry = {
29
33
  spendCents?: number | PaybondSpendResolver;
30
34
  evidencePreset: string;
31
35
  evidenceMapper?: PaybondEvidenceMapper;
36
+ externalAttestationMapper?: PaybondExternalAttestationMapper;
32
37
  };
33
38
  export type PaybondToolResolution = {
34
39
  kind: "passthrough";
@@ -50,6 +55,8 @@ export declare class PaybondToolRegistryValidationError extends Error {
50
55
  import type { PaybondToolRegistry } from "./registry.js";
51
56
  import type { PaybondPolicySnapshot } from "../policy/snapshot.js";
52
57
  import type { PaybondPolicyReloadBindConfig } from "../policy/reload.js";
58
+ import type { AgentReceiptV1 } from "../agent-receipt.js";
59
+ import type { PaybondExternalAttestationInput } from "../agent-receipt-external-attestations.js";
53
60
  /** Spend authorization input forwarded to the run-bound guard. */
54
61
  export type PaybondRunGuardAuthInput = {
55
62
  operation: string;
@@ -63,6 +70,54 @@ export type PaybondRunGuardAuthInput = {
63
70
  agentSubject?: string;
64
71
  approvalToken?: string;
65
72
  idempotencyKey?: string;
73
+ /** Agent Receipt Standard context forwarded to Gateway `/verify` for audit correlation. */
74
+ modelFamily?: string;
75
+ configHashHex?: string;
76
+ promptHashHex?: string;
77
+ };
78
+ /**
79
+ * Materials to auto-compute `config_hash_hex` as `sha256(JCS({ system_prompt, tools_manifest,
80
+ * policy_snapshot_id }))` per the Agent Receipt Standard spec. `policySnapshotId` defaults to
81
+ * the bound policy snapshot digest (without the `sha256:` prefix) when omitted.
82
+ */
83
+ export type PaybondRunConfigHashMaterials = {
84
+ systemPrompt: string;
85
+ toolsManifest: unknown;
86
+ policySnapshotId?: string;
87
+ };
88
+ /**
89
+ * Optional agent identity/config context for {@link PaybondAgentRun.bind}. Threaded onto every
90
+ * spend verify call and used to compose the unsigned Agent Receipt Standard draft returned from
91
+ * {@link PaybondToolInterceptor.wrapExecute}. Raw prompts are hashed locally and never retained
92
+ * or transmitted; only `promptHashHex` is kept on the resolved binding.
93
+ */
94
+ export type PaybondRunAgentContextInput = {
95
+ modelFamily: string;
96
+ modelInstanceId?: string;
97
+ /** Precomputed sha256 hex digest; mutually exclusive with {@link configHashMaterials}. */
98
+ configHashHex?: string;
99
+ /** Auto-computed into `configHashHex` at bind time when `configHashHex` is omitted. */
100
+ configHashMaterials?: PaybondRunConfigHashMaterials;
101
+ /** Precomputed sha256 hex digest; mutually exclusive with {@link normalizedUserPrompt}. */
102
+ promptHashHex?: string;
103
+ /** Hashed locally at bind time into `promptHashHex`; never stored or transmitted raw. */
104
+ normalizedUserPrompt?: string;
105
+ /** Agent receipt `authorization.principal_did`; omitted from receipt drafts when unset. */
106
+ principalDid?: string;
107
+ /** Agent receipt `authorization.agent.operator_did`; omitted from receipt drafts when unset. */
108
+ operatorDid?: string;
109
+ /** Agent receipt `authorization.policy.template_id`; omitted from receipt drafts when unset. */
110
+ policyTemplateId?: string;
111
+ };
112
+ /** Resolved agent context stored on the run binding after bind-time hash computation. */
113
+ export type PaybondRunAgentContext = {
114
+ modelFamily: string;
115
+ modelInstanceId?: string;
116
+ configHashHex?: string;
117
+ promptHashHex?: string;
118
+ principalDid?: string;
119
+ operatorDid?: string;
120
+ policyTemplateId?: string;
66
121
  };
67
122
  /** Spend authorization result from Harbor verify. */
68
123
  export type PaybondRunGuardAuthResult = {
@@ -116,6 +171,10 @@ export type PaybondInterceptEvidenceResult = {
116
171
  predicatePassed?: boolean | null;
117
172
  sandboxLifecycleStatus?: string;
118
173
  intentState?: string;
174
+ /** sha256 hex digest of the submitted evidence payload, when returned by Harbor. */
175
+ payloadDigestSha256Hex?: string;
176
+ /** sha256 hex digest of submitted evidence artifacts, when returned by Harbor. */
177
+ artifactsDigestSha256Hex?: string;
119
178
  };
120
179
  export type PaybondInterceptWrapExecuteResult<TResult> = {
121
180
  toolResult: TResult;
@@ -126,6 +185,12 @@ export type PaybondInterceptWrapExecuteResult<TResult> = {
126
185
  policyDigest?: string;
127
186
  };
128
187
  evidence?: PaybondInterceptEvidenceResult;
188
+ /**
189
+ * Unsigned Agent Receipt Standard draft (Phase 1: composed locally, never signed or persisted).
190
+ * Omitted when required context (agentContext hashes, principal/operator DID, policy template,
191
+ * decision id) is unavailable — receipt composition is always best-effort and never throws.
192
+ */
193
+ receiptDraft?: AgentReceiptV1;
129
194
  };
130
195
  export type PaybondInterceptWrapExecuteInput<TResult> = {
131
196
  toolName: string;
@@ -241,6 +306,7 @@ export type PaybondTraceEvidenceSubmittedEvent = {
241
306
  evidencePreset?: string;
242
307
  sandboxLifecycleStatus?: string;
243
308
  predicatePassed?: boolean | null;
309
+ externalAttestations?: AgentReceiptExternalAttestationV1[];
244
310
  recordedAt: string;
245
311
  };
246
312
  export type PaybondTraceSpendFinalizedEvent = {
@@ -275,6 +341,8 @@ export type PaybondRunBinding = {
275
341
  policySnapshot?: PaybondPolicySnapshot;
276
342
  /** Optional trace sink for dev observability (see {@link PaybondAgentRunBindInput.traceSink}). */
277
343
  onTrace?: PaybondTraceSink;
344
+ /** Resolved Agent Receipt Standard context, when bound with {@link PaybondAgentRunBindInput.agentContext}. */
345
+ agentContext?: PaybondRunAgentContext;
278
346
  };
279
347
  export type PaybondAgentRunBindInput = {
280
348
  bootstrap?: PaybondRunBindingSandboxBootstrapInput;
@@ -295,6 +363,8 @@ export type PaybondAgentRunBindInput = {
295
363
  traceSink?: PaybondTraceSink;
296
364
  /** @deprecated Use {@link PaybondAgentRunBindInput.traceSink}. */
297
365
  onTrace?: PaybondTraceSink;
366
+ /** Optional Agent Receipt Standard agent identity/config context (see {@link PaybondRunAgentContextInput}). */
367
+ agentContext?: PaybondRunAgentContextInput;
298
368
  };
299
369
  export declare class PaybondAgentRunBindError extends Error {
300
370
  constructor(message: string);
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Local verification for protocol-v2 signed AgentMandateV1 envelopes.
3
+ *
4
+ * Canonical JSON matches Go gateway `marshalCanonicalAgentMandate` (fixed struct field
5
+ * order and HTML-safe `\u` escapes), not Kit's generic JCS-style `normalizeJson`.
6
+ */
7
+ export declare const AGENT_MANDATE_SCHEMA_VERSION = 1;
8
+ export declare const AGENT_MANDATE_KIND_V1 = "paybond.agent_mandate_v1";
9
+ export declare const AGENT_MANDATE_SIGNING_ALGORITHM_ED25519_SHA256 = "ed25519-sha256-json-v1";
10
+ export declare const AGENT_AUTHORIZATION_KIND_PRINCIPAL = "principal";
11
+ export declare const AGENT_AUTHORIZATION_KIND_TENANT = "tenant";
12
+ export declare const HUMAN_PRESENCE_MODE_HUMAN_PRESENT = "human_present";
13
+ export declare const HUMAN_PRESENCE_MODE_HUMAN_NOT_PRESENT = "human_not_present";
14
+ export declare const CONSTRAINT_REFERENCE_KIND_PREDICATE = "predicate";
15
+ export declare const CONSTRAINT_REFERENCE_KIND_POLICY = "policy";
16
+ export declare const SCOPE_TOKEN_RE: RegExp;
17
+ export declare const CURRENCY_RE: RegExp;
18
+ export declare const HEX64_RE: RegExp;
19
+ export declare const TENANT_ID_RE: RegExp;
20
+ export declare const MAX_TENANT_ID_LEN = 256;
21
+ export type AgentMandateAuthorization = {
22
+ kind: string;
23
+ tenant_id: string;
24
+ principal_subject: string;
25
+ principal_type: string;
26
+ };
27
+ export type AgentMandateAgentIdentity = {
28
+ subject: string;
29
+ issuer: string;
30
+ key_id: string;
31
+ display_name: string;
32
+ };
33
+ export type AgentMandateSpendCeiling = {
34
+ amount_minor: number;
35
+ currency: string;
36
+ };
37
+ export type AgentMandateSettlementRailPolicy = {
38
+ default_rail: string;
39
+ allowed_rails: string[];
40
+ };
41
+ export type AgentMandateConstraintReference = {
42
+ kind: string;
43
+ id: string;
44
+ version: string;
45
+ digest_sha256_hex: string;
46
+ uri: string;
47
+ };
48
+ /** Normalized mandate core used for canonical JSON hashing and signing. */
49
+ export type AgentMandateV1 = {
50
+ schema_version: number;
51
+ kind: string;
52
+ authorization: AgentMandateAuthorization;
53
+ agent: AgentMandateAgentIdentity;
54
+ allowed_actions: string[];
55
+ allowed_tools: string[];
56
+ spend_ceiling: AgentMandateSpendCeiling;
57
+ settlement: AgentMandateSettlementRailPolicy;
58
+ constraint: AgentMandateConstraintReference;
59
+ expires_at: string;
60
+ nonce: string;
61
+ human_presence_mode: string;
62
+ };
63
+ export type SignedAgentMandateV1 = AgentMandateV1 & {
64
+ signing_algorithm: string;
65
+ message_digest_sha256_hex: string;
66
+ signing_public_key_ed25519_hex: string;
67
+ ed25519_signature_hex: string;
68
+ };
69
+ type AgentMandateWireInput = AgentMandateV1 | Record<string, unknown>;
70
+ export declare function readObject(value: unknown): Record<string, unknown> | undefined;
71
+ export declare function readString(value: unknown, fallback?: string): string;
72
+ export declare function readNumber(value: unknown, fallback?: number): number;
73
+ export declare function readStringArray(value: unknown): string[];
74
+ export declare function hexToBytes(hex: string): Uint8Array;
75
+ export declare function sha256Hex(data: Uint8Array): string;
76
+ /** Matches Go `encoding/json` HTML-safe escaping on top of standard JSON encoding. */
77
+ export declare function goJsonEscape(json: string): string;
78
+ export declare function roundUtcToSeconds(date: Date): Date;
79
+ /** Formats UTC timestamps like Go `time.RFC3339Nano` with zero sub-second precision omitted. */
80
+ export declare function formatRfc3339NanoUtc(date: Date): string;
81
+ /**
82
+ * Validates and canonicalizes mandate fields for hashing and signing.
83
+ *
84
+ * @throws When structure or field values are invalid.
85
+ */
86
+ export declare function normalizeAgentMandateV1(input: AgentMandateWireInput): AgentMandateV1;
87
+ /** Returns canonical mandate bytes used for digesting and Ed25519 signing. */
88
+ export declare function canonicalAgentMandateJsonBytes(input: AgentMandateWireInput): Uint8Array;
89
+ /** Returns the portable SHA-256 digest over canonical mandate JSON as lowercase hex. */
90
+ export declare function agentMandateDigestSha256Hex(input: AgentMandateWireInput): string;
91
+ /**
92
+ * Validates, canonicalizes, and signs a mandate with Ed25519-over-SHA-256(canonical JSON).
93
+ *
94
+ * @param signingSeed - 32-byte Ed25519 seed (noble private key).
95
+ */
96
+ export declare function signAgentMandateV1(signingSeed: Uint8Array, input: AgentMandateWireInput): SignedAgentMandateV1;
97
+ /**
98
+ * Checks structure, expiry, digest recompute, and detached Ed25519 signature.
99
+ *
100
+ * @param now - Verification instant (defaults to current UTC time).
101
+ */
102
+ export declare function verifySignedAgentMandateV1(signed: SignedAgentMandateV1 | Record<string, unknown>, now?: Date): void;
103
+ export {};
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Local verification for protocol-v2 signed AgentMandateV1 envelopes.
3
+ *
4
+ * Canonical JSON matches Go gateway `marshalCanonicalAgentMandate` (fixed struct field
5
+ * order and HTML-safe `\u` escapes), not Kit's generic JCS-style `normalizeJson`.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { getPublicKey, sign, verify as ed25519Verify } from "@noble/ed25519";
9
+ import { ensureEd25519Sha512Sync } from "./ed25519-sync.js";
10
+ export const AGENT_MANDATE_SCHEMA_VERSION = 1;
11
+ export const AGENT_MANDATE_KIND_V1 = "paybond.agent_mandate_v1";
12
+ export const AGENT_MANDATE_SIGNING_ALGORITHM_ED25519_SHA256 = "ed25519-sha256-json-v1";
13
+ export const AGENT_AUTHORIZATION_KIND_PRINCIPAL = "principal";
14
+ export const AGENT_AUTHORIZATION_KIND_TENANT = "tenant";
15
+ export const HUMAN_PRESENCE_MODE_HUMAN_PRESENT = "human_present";
16
+ export const HUMAN_PRESENCE_MODE_HUMAN_NOT_PRESENT = "human_not_present";
17
+ export const CONSTRAINT_REFERENCE_KIND_PREDICATE = "predicate";
18
+ export const CONSTRAINT_REFERENCE_KIND_POLICY = "policy";
19
+ export const SCOPE_TOKEN_RE = /^[a-z0-9][a-z0-9._:/-]{0,127}$/;
20
+ export const CURRENCY_RE = /^[a-z0-9_]{3,16}$/;
21
+ export const HEX64_RE = /^[a-f0-9]{64}$/;
22
+ export const TENANT_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
23
+ export const MAX_TENANT_ID_LEN = 256;
24
+ const SETTLEMENT_RAILS = new Set([
25
+ "stripe_connect",
26
+ "stripe_ach_debit",
27
+ "stripe_mpp",
28
+ "x402_usdc_base",
29
+ ]);
30
+ export function readObject(value) {
31
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
32
+ return value;
33
+ }
34
+ return undefined;
35
+ }
36
+ export function readString(value, fallback = "") {
37
+ return typeof value === "string" ? value : fallback;
38
+ }
39
+ export function readNumber(value, fallback = 0) {
40
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
41
+ }
42
+ export function readStringArray(value) {
43
+ if (!Array.isArray(value)) {
44
+ return [];
45
+ }
46
+ return value.filter((item) => typeof item === "string");
47
+ }
48
+ export function hexToBytes(hex) {
49
+ const trimmed = hex.trim().toLowerCase();
50
+ if (trimmed.length % 2 !== 0) {
51
+ throw new Error("invalid hex length");
52
+ }
53
+ const out = new Uint8Array(trimmed.length / 2);
54
+ for (let i = 0; i < out.length; i++) {
55
+ out[i] = Number.parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);
56
+ }
57
+ return out;
58
+ }
59
+ export function sha256Hex(data) {
60
+ return createHash("sha256").update(data).digest("hex");
61
+ }
62
+ /** Matches Go `encoding/json` HTML-safe escaping on top of standard JSON encoding. */
63
+ export function goJsonEscape(json) {
64
+ return json
65
+ .replaceAll("&", "\\u0026")
66
+ .replaceAll("<", "\\u003c")
67
+ .replaceAll(">", "\\u003e")
68
+ .replaceAll("\u2028", "\\u2028")
69
+ .replaceAll("\u2029", "\\u2029");
70
+ }
71
+ function parseTenantId(raw) {
72
+ const trimmed = raw.trim();
73
+ if (!trimmed) {
74
+ throw new Error("agent mandate: authorization.tenant_id: tenant: id is missing");
75
+ }
76
+ if (trimmed.length > MAX_TENANT_ID_LEN) {
77
+ throw new Error(`agent mandate: authorization.tenant_id: tenant: id exceeds max length (${MAX_TENANT_ID_LEN})`);
78
+ }
79
+ if (!TENANT_ID_RE.test(trimmed)) {
80
+ throw new Error("agent mandate: authorization.tenant_id: tenant: id must match [a-z0-9][a-z0-9._-]* (lowercase alphanumeric, dots, underscores, hyphens)");
81
+ }
82
+ return trimmed;
83
+ }
84
+ function normalizeScopeSet(raw, field) {
85
+ const seen = new Set();
86
+ const out = [];
87
+ for (const item of raw) {
88
+ const value = item.trim().toLowerCase();
89
+ if (value === "") {
90
+ throw new Error(`agent mandate: ${field} contains an empty value`);
91
+ }
92
+ if (!SCOPE_TOKEN_RE.test(value)) {
93
+ throw new Error(`agent mandate: ${field} value ${JSON.stringify(value)} is not canonical`);
94
+ }
95
+ if (seen.has(value)) {
96
+ continue;
97
+ }
98
+ seen.add(value);
99
+ out.push(value);
100
+ }
101
+ out.sort();
102
+ return out;
103
+ }
104
+ function normalizeAllowedRails(raw) {
105
+ const seen = new Set();
106
+ const out = [];
107
+ for (const item of raw) {
108
+ const rail = item.trim().toLowerCase();
109
+ if (!SETTLEMENT_RAILS.has(rail)) {
110
+ throw new Error(`agent mandate: settlement.allowed_rails: unknown settlement rail ${JSON.stringify(item)}`);
111
+ }
112
+ if (seen.has(rail)) {
113
+ continue;
114
+ }
115
+ seen.add(rail);
116
+ out.push(rail);
117
+ }
118
+ out.sort();
119
+ return out;
120
+ }
121
+ function parseExpiresAt(value) {
122
+ if (value instanceof Date) {
123
+ if (Number.isNaN(value.getTime())) {
124
+ throw new Error("agent mandate: expires_at is required");
125
+ }
126
+ return value;
127
+ }
128
+ if (typeof value === "string") {
129
+ const trimmed = value.trim();
130
+ if (!trimmed) {
131
+ throw new Error("agent mandate: expires_at is required");
132
+ }
133
+ const parsed = new Date(trimmed);
134
+ if (Number.isNaN(parsed.getTime())) {
135
+ throw new Error("agent mandate: expires_at is required");
136
+ }
137
+ return parsed;
138
+ }
139
+ throw new Error("agent mandate: expires_at is required");
140
+ }
141
+ export function roundUtcToSeconds(date) {
142
+ const rounded = new Date(date.getTime());
143
+ rounded.setUTCMilliseconds(0);
144
+ return rounded;
145
+ }
146
+ /** Formats UTC timestamps like Go `time.RFC3339Nano` with zero sub-second precision omitted. */
147
+ export function formatRfc3339NanoUtc(date) {
148
+ const iso = date.toISOString();
149
+ return iso.endsWith(".000Z") ? `${iso.slice(0, -5)}Z` : iso;
150
+ }
151
+ function coerceAgentMandateWire(input) {
152
+ return readObject(input) ?? {};
153
+ }
154
+ function readAgentMandateFields(raw) {
155
+ return {
156
+ schema_version: readNumber(raw.schema_version),
157
+ kind: readString(raw.kind),
158
+ authorization: readObject(raw.authorization) ?? {},
159
+ agent: readObject(raw.agent) ?? {},
160
+ allowed_actions: readStringArray(raw.allowed_actions),
161
+ allowed_tools: readStringArray(raw.allowed_tools),
162
+ spend_ceiling: readObject(raw.spend_ceiling) ?? {},
163
+ settlement: readObject(raw.settlement) ?? {},
164
+ constraint: readObject(raw.constraint) ?? {},
165
+ expires_at: raw.expires_at,
166
+ nonce: readString(raw.nonce),
167
+ human_presence_mode: readString(raw.human_presence_mode),
168
+ };
169
+ }
170
+ /**
171
+ * Validates and canonicalizes mandate fields for hashing and signing.
172
+ *
173
+ * @throws When structure or field values are invalid.
174
+ */
175
+ export function normalizeAgentMandateV1(input) {
176
+ const raw = readAgentMandateFields(coerceAgentMandateWire(input));
177
+ let schemaVersion = raw.schema_version;
178
+ if (schemaVersion === 0 || schemaVersion === AGENT_MANDATE_SCHEMA_VERSION) {
179
+ schemaVersion = AGENT_MANDATE_SCHEMA_VERSION;
180
+ }
181
+ else {
182
+ throw new Error(`agent mandate: unsupported schema_version ${schemaVersion}`);
183
+ }
184
+ let kind = raw.kind.trim();
185
+ if (kind === "" || kind === AGENT_MANDATE_KIND_V1) {
186
+ kind = AGENT_MANDATE_KIND_V1;
187
+ }
188
+ else {
189
+ throw new Error(`agent mandate: unsupported kind ${JSON.stringify(kind)}`);
190
+ }
191
+ const authorizationKind = readString(raw.authorization.kind).trim().toLowerCase();
192
+ if (authorizationKind !== AGENT_AUTHORIZATION_KIND_PRINCIPAL &&
193
+ authorizationKind !== AGENT_AUTHORIZATION_KIND_TENANT) {
194
+ throw new Error(`agent mandate: authorization.kind must be ${JSON.stringify(AGENT_AUTHORIZATION_KIND_PRINCIPAL)} or ${JSON.stringify(AGENT_AUTHORIZATION_KIND_TENANT)}`);
195
+ }
196
+ const tenantId = parseTenantId(readString(raw.authorization.tenant_id));
197
+ const principalSubject = readString(raw.authorization.principal_subject).trim();
198
+ const principalType = readString(raw.authorization.principal_type).trim().toLowerCase();
199
+ if (authorizationKind === AGENT_AUTHORIZATION_KIND_PRINCIPAL) {
200
+ if (!principalSubject) {
201
+ throw new Error("agent mandate: authorization.principal_subject is required for principal-scoped mandates");
202
+ }
203
+ if (principalType && !SCOPE_TOKEN_RE.test(principalType)) {
204
+ throw new Error(`agent mandate: authorization.principal_type ${JSON.stringify(principalType)} is not canonical`);
205
+ }
206
+ }
207
+ else if (principalSubject || principalType) {
208
+ throw new Error("agent mandate: tenant-scoped mandates must not set principal_subject or principal_type");
209
+ }
210
+ const agentSubject = readString(raw.agent.subject).trim();
211
+ const agentIssuer = readString(raw.agent.issuer).trim();
212
+ const agentKeyId = readString(raw.agent.key_id).trim();
213
+ const agentDisplayName = readString(raw.agent.display_name).trim();
214
+ if (!agentSubject) {
215
+ throw new Error("agent mandate: agent.subject is required");
216
+ }
217
+ const allowedActions = normalizeScopeSet(raw.allowed_actions, "allowed_actions");
218
+ const allowedTools = normalizeScopeSet(raw.allowed_tools, "allowed_tools");
219
+ if (allowedActions.length === 0 && allowedTools.length === 0) {
220
+ throw new Error("agent mandate: at least one allowed action or allowed tool is required");
221
+ }
222
+ const amountMinor = readNumber(raw.spend_ceiling.amount_minor);
223
+ if (amountMinor <= 0) {
224
+ throw new Error("agent mandate: spend_ceiling.amount_minor must be greater than zero");
225
+ }
226
+ const currency = readString(raw.spend_ceiling.currency).trim().toLowerCase();
227
+ if (!CURRENCY_RE.test(currency)) {
228
+ throw new Error(`agent mandate: spend_ceiling.currency ${JSON.stringify(currency)} is not canonical`);
229
+ }
230
+ const defaultRail = readString(raw.settlement.default_rail).trim().toLowerCase();
231
+ const allowedRails = normalizeAllowedRails(readStringArray(raw.settlement.allowed_rails));
232
+ if (!defaultRail) {
233
+ throw new Error("agent mandate: settlement.default_rail is required");
234
+ }
235
+ if (allowedRails.length === 0) {
236
+ throw new Error("agent mandate: settlement.allowed_rails must contain at least one rail");
237
+ }
238
+ if (!allowedRails.includes(defaultRail)) {
239
+ throw new Error("agent mandate: settlement.default_rail must be present in settlement.allowed_rails");
240
+ }
241
+ const constraintKind = readString(raw.constraint.kind).trim().toLowerCase();
242
+ if (constraintKind !== CONSTRAINT_REFERENCE_KIND_PREDICATE &&
243
+ constraintKind !== CONSTRAINT_REFERENCE_KIND_POLICY) {
244
+ throw new Error(`agent mandate: constraint.kind must be ${JSON.stringify(CONSTRAINT_REFERENCE_KIND_PREDICATE)} or ${JSON.stringify(CONSTRAINT_REFERENCE_KIND_POLICY)}`);
245
+ }
246
+ const constraintId = readString(raw.constraint.id).trim();
247
+ const constraintVersion = readString(raw.constraint.version).trim();
248
+ const constraintDigest = readString(raw.constraint.digest_sha256_hex).trim().toLowerCase();
249
+ const constraintUri = readString(raw.constraint.uri).trim();
250
+ if (constraintDigest && !HEX64_RE.test(constraintDigest)) {
251
+ throw new Error("agent mandate: constraint.digest_sha256_hex must be a lowercase 64-byte hex SHA-256 digest");
252
+ }
253
+ if (!constraintId && !constraintUri && !constraintDigest) {
254
+ throw new Error("agent mandate: constraint must set id, uri, or digest_sha256_hex");
255
+ }
256
+ const nonce = raw.nonce.trim();
257
+ if (!nonce) {
258
+ throw new Error("agent mandate: nonce is required");
259
+ }
260
+ if (nonce.length > 256) {
261
+ throw new Error("agent mandate: nonce must be 256 bytes or fewer");
262
+ }
263
+ const humanPresenceMode = raw.human_presence_mode.trim().toLowerCase();
264
+ if (humanPresenceMode !== HUMAN_PRESENCE_MODE_HUMAN_PRESENT &&
265
+ humanPresenceMode !== HUMAN_PRESENCE_MODE_HUMAN_NOT_PRESENT) {
266
+ throw new Error(`agent mandate: human_presence_mode must be ${JSON.stringify(HUMAN_PRESENCE_MODE_HUMAN_PRESENT)} or ${JSON.stringify(HUMAN_PRESENCE_MODE_HUMAN_NOT_PRESENT)}`);
267
+ }
268
+ const expiresAt = formatRfc3339NanoUtc(roundUtcToSeconds(parseExpiresAt(raw.expires_at)));
269
+ return {
270
+ schema_version: schemaVersion,
271
+ kind,
272
+ authorization: {
273
+ kind: authorizationKind,
274
+ tenant_id: tenantId,
275
+ principal_subject: principalSubject,
276
+ principal_type: principalType,
277
+ },
278
+ agent: {
279
+ subject: agentSubject,
280
+ issuer: agentIssuer,
281
+ key_id: agentKeyId,
282
+ display_name: agentDisplayName,
283
+ },
284
+ allowed_actions: allowedActions,
285
+ allowed_tools: allowedTools,
286
+ spend_ceiling: {
287
+ amount_minor: amountMinor,
288
+ currency,
289
+ },
290
+ settlement: {
291
+ default_rail: defaultRail,
292
+ allowed_rails: allowedRails,
293
+ },
294
+ constraint: {
295
+ kind: constraintKind,
296
+ id: constraintId,
297
+ version: constraintVersion,
298
+ digest_sha256_hex: constraintDigest,
299
+ uri: constraintUri,
300
+ },
301
+ expires_at: expiresAt,
302
+ nonce,
303
+ human_presence_mode: humanPresenceMode,
304
+ };
305
+ }
306
+ function marshalCanonicalAgentMandate(mandate) {
307
+ const payload = {
308
+ schema_version: mandate.schema_version,
309
+ kind: mandate.kind,
310
+ authorization: {
311
+ kind: mandate.authorization.kind,
312
+ tenant_id: mandate.authorization.tenant_id,
313
+ principal_subject: mandate.authorization.principal_subject,
314
+ principal_type: mandate.authorization.principal_type,
315
+ },
316
+ agent: {
317
+ subject: mandate.agent.subject,
318
+ issuer: mandate.agent.issuer,
319
+ key_id: mandate.agent.key_id,
320
+ display_name: mandate.agent.display_name,
321
+ },
322
+ allowed_actions: [...mandate.allowed_actions],
323
+ allowed_tools: [...mandate.allowed_tools],
324
+ spend_ceiling: {
325
+ amount_minor: mandate.spend_ceiling.amount_minor,
326
+ currency: mandate.spend_ceiling.currency,
327
+ },
328
+ settlement: {
329
+ default_rail: mandate.settlement.default_rail,
330
+ allowed_rails: [...mandate.settlement.allowed_rails],
331
+ },
332
+ constraint: {
333
+ kind: mandate.constraint.kind,
334
+ id: mandate.constraint.id,
335
+ version: mandate.constraint.version,
336
+ digest_sha256_hex: mandate.constraint.digest_sha256_hex,
337
+ uri: mandate.constraint.uri,
338
+ },
339
+ expires_at: mandate.expires_at,
340
+ nonce: mandate.nonce,
341
+ human_presence_mode: mandate.human_presence_mode,
342
+ };
343
+ return new TextEncoder().encode(goJsonEscape(JSON.stringify(payload)));
344
+ }
345
+ /** Returns canonical mandate bytes used for digesting and Ed25519 signing. */
346
+ export function canonicalAgentMandateJsonBytes(input) {
347
+ return marshalCanonicalAgentMandate(normalizeAgentMandateV1(input));
348
+ }
349
+ /** Returns the portable SHA-256 digest over canonical mandate JSON as lowercase hex. */
350
+ export function agentMandateDigestSha256Hex(input) {
351
+ return sha256Hex(canonicalAgentMandateJsonBytes(input));
352
+ }
353
+ /**
354
+ * Validates, canonicalizes, and signs a mandate with Ed25519-over-SHA-256(canonical JSON).
355
+ *
356
+ * @param signingSeed - 32-byte Ed25519 seed (noble private key).
357
+ */
358
+ export function signAgentMandateV1(signingSeed, input) {
359
+ ensureEd25519Sha512Sync();
360
+ if (signingSeed.length !== 32) {
361
+ throw new Error("agent mandate: signing key must be an ed25519 private key");
362
+ }
363
+ const normalized = normalizeAgentMandateV1(input);
364
+ const body = marshalCanonicalAgentMandate(normalized);
365
+ const digest = sha256Hex(body);
366
+ const digestBytes = hexToBytes(digest);
367
+ const signature = sign(digestBytes, signingSeed);
368
+ const publicKey = getPublicKey(signingSeed);
369
+ return {
370
+ ...normalized,
371
+ signing_algorithm: AGENT_MANDATE_SIGNING_ALGORITHM_ED25519_SHA256,
372
+ message_digest_sha256_hex: digest,
373
+ signing_public_key_ed25519_hex: Buffer.from(publicKey).toString("hex"),
374
+ ed25519_signature_hex: Buffer.from(signature).toString("hex"),
375
+ };
376
+ }
377
+ /**
378
+ * Checks structure, expiry, digest recompute, and detached Ed25519 signature.
379
+ *
380
+ * @param now - Verification instant (defaults to current UTC time).
381
+ */
382
+ export function verifySignedAgentMandateV1(signed, now = new Date()) {
383
+ ensureEd25519Sha512Sync();
384
+ const raw = coerceAgentMandateWire(signed);
385
+ const mandateFields = readAgentMandateFields(raw);
386
+ const normalized = normalizeAgentMandateV1(mandateFields);
387
+ const nowUtc = new Date(now.getTime());
388
+ const expiresAt = parseExpiresAt(normalized.expires_at);
389
+ if (expiresAt.getTime() <= nowUtc.getTime()) {
390
+ throw new Error(`agent mandate: expired at ${normalized.expires_at}`);
391
+ }
392
+ const signingAlgorithm = readString(raw.signing_algorithm).trim();
393
+ if (signingAlgorithm !== AGENT_MANDATE_SIGNING_ALGORITHM_ED25519_SHA256) {
394
+ throw new Error(`agent mandate: signing_algorithm must be ${JSON.stringify(AGENT_MANDATE_SIGNING_ALGORITHM_ED25519_SHA256)}`);
395
+ }
396
+ const body = marshalCanonicalAgentMandate(normalized);
397
+ const digest = sha256Hex(body);
398
+ const messageDigest = readString(raw.message_digest_sha256_hex).trim().toLowerCase();
399
+ if (!HEX64_RE.test(messageDigest)) {
400
+ throw new Error("agent mandate: message_digest_sha256_hex must be a lowercase 64-byte hex SHA-256 digest");
401
+ }
402
+ if (messageDigest !== digest) {
403
+ throw new Error("agent mandate: message digest mismatch");
404
+ }
405
+ let publicKey;
406
+ let signature;
407
+ try {
408
+ publicKey = hexToBytes(readString(raw.signing_public_key_ed25519_hex));
409
+ signature = hexToBytes(readString(raw.ed25519_signature_hex));
410
+ }
411
+ catch {
412
+ throw new Error("agent mandate: invalid signing_public_key_ed25519_hex");
413
+ }
414
+ if (publicKey.length !== 32) {
415
+ throw new Error("agent mandate: invalid signing_public_key_ed25519_hex");
416
+ }
417
+ if (signature.length !== 64) {
418
+ throw new Error("agent mandate: invalid ed25519_signature_hex");
419
+ }
420
+ if (!ed25519Verify(signature, hexToBytes(digest), publicKey)) {
421
+ throw new Error("agent mandate: ed25519 signature verification failed");
422
+ }
423
+ }