@paybond/kit 0.12.0 → 0.12.2
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/agent/evidence.d.ts +10 -0
- package/dist/agent/evidence.js +16 -0
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/interceptor.js +4 -0
- package/dist/agent/receipt-client.d.ts +16 -1
- package/dist/agent/run.d.ts +3 -0
- package/dist/agent/run.js +1 -1
- package/dist/agent-mandate.d.ts +103 -0
- package/dist/agent-mandate.js +423 -0
- package/dist/agent-receipt-external-attestations.d.ts +27 -0
- package/dist/agent-receipt-external-attestations.js +61 -0
- package/dist/agent-receipt-pdf-export.d.ts +43 -0
- package/dist/agent-receipt-pdf-export.js +109 -0
- package/dist/agent-receipt-prevalidate.d.ts +7 -0
- package/dist/agent-receipt-prevalidate.js +63 -0
- package/dist/agent-receipt.d.ts +21 -1
- package/dist/agent-receipt.js +63 -4
- package/dist/cli/command-spec.js +50 -1
- package/dist/cli/commands/dev.js +10 -3
- package/dist/cli/commands/setup.js +19 -1
- package/dist/cli/commands/shopify.d.ts +53 -0
- package/dist/cli/commands/shopify.js +808 -0
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +13 -3
- package/dist/cli/router.js +15 -1
- package/dist/commerce-binding.d.ts +59 -0
- package/dist/commerce-binding.js +129 -0
- package/dist/doctor-completion.d.ts +16 -0
- package/dist/doctor-completion.js +157 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +43 -7
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +38 -11
- package/dist/policy/presets.d.ts +1 -1
- package/dist/policy/presets.js +1 -0
- package/dist/project-init.d.ts +1 -1
- package/dist/project-init.js +1 -0
- package/dist/protocol-receipt.d.ts +129 -0
- package/dist/protocol-receipt.js +620 -0
- package/dist/shopify/checkout.d.ts +29 -0
- package/dist/shopify/checkout.js +79 -0
- package/dist/shopify/evidence.d.ts +13 -0
- package/dist/shopify/evidence.js +85 -0
- package/dist/shopify/index.d.ts +8 -0
- package/dist/shopify/index.js +6 -0
- package/dist/shopify/instrument.d.ts +61 -0
- package/dist/shopify/instrument.js +52 -0
- package/dist/shopify/order.d.ts +8 -0
- package/dist/shopify/order.js +110 -0
- package/dist/shopify/types.d.ts +82 -0
- package/dist/shopify/types.js +4 -0
- package/dist/solutions/catalog.d.ts +1 -1
- package/dist/solutions/catalog.js +1 -1
- package/dist/stripe-commerce/evidence.d.ts +13 -0
- package/dist/stripe-commerce/evidence.js +164 -0
- package/dist/stripe-commerce/index.d.ts +3 -0
- package/dist/stripe-commerce/index.js +2 -0
- package/dist/stripe-commerce/metadata.d.ts +11 -0
- package/dist/stripe-commerce/metadata.js +34 -0
- package/dist/stripe-commerce/types.d.ts +38 -0
- package/dist/stripe-commerce/types.js +1 -0
- package/dist/template-init.d.ts +1 -1
- package/dist/template-init.js +6 -1
- package/package.json +1 -1
- package/policy/presets/stripe-commerce.yaml +19 -0
- package/solutions/stripe-commerce.json +19 -0
- package/templates/manifest.json +82 -10
- package/templates/openai-shopping-agent/package-lock.json +10 -10
- package/templates/openai-shopping-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-aws-operator/package-lock.json +7 -7
- package/templates/paybond-aws-operator/src/paybond.config.ts +1 -1
- package/templates/paybond-claude-agents-demo/package-lock.json +7 -7
- package/templates/paybond-claude-agents-demo/src/paybond.config.ts +1 -1
- package/templates/paybond-mastra-travel-agent/package-lock.json +30 -30
- package/templates/paybond-mastra-travel-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-mcp-coding-agent/package-lock.json +7 -7
- package/templates/paybond-mcp-coding-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-openai-agents-demo/package-lock.json +10 -10
- package/templates/paybond-openai-agents-demo/src/paybond.config.ts +1 -1
- package/templates/paybond-procurement-agent/package-lock.json +7 -7
- package/templates/paybond-procurement-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-shopify-shopping-agent/.env.example +3 -0
- package/templates/paybond-shopify-shopping-agent/.github/workflows/smoke.yml +20 -0
- package/templates/paybond-shopify-shopping-agent/LICENSE +201 -0
- package/templates/paybond-shopify-shopping-agent/README.md +29 -0
- package/templates/paybond-shopify-shopping-agent/package-lock.json +223 -0
- package/templates/paybond-shopify-shopping-agent/package.json +20 -0
- package/templates/paybond-shopify-shopping-agent/paybond.policy.yaml +22 -0
- package/templates/paybond-shopify-shopping-agent/src/index.ts +54 -0
- package/templates/paybond-shopify-shopping-agent/src/paybond.config.ts +51 -0
- package/templates/paybond-shopify-shopping-agent/tsconfig.json +13 -0
- package/templates/paybond-stripe-agent-demo/.env.example +9 -0
- package/templates/paybond-stripe-agent-demo/.github/workflows/smoke.yml +20 -0
- package/templates/paybond-stripe-agent-demo/LICENSE +201 -0
- package/templates/paybond-stripe-agent-demo/README.md +50 -0
- package/templates/paybond-stripe-agent-demo/package-lock.json +223 -0
- package/templates/paybond-stripe-agent-demo/package.json +20 -0
- package/templates/paybond-stripe-agent-demo/paybond.policy.yaml +22 -0
- package/templates/paybond-stripe-agent-demo/src/charge-customer.ts +237 -0
- package/templates/paybond-stripe-agent-demo/src/index.ts +78 -0
- package/templates/paybond-stripe-agent-demo/src/paybond.config.ts +51 -0
- package/templates/paybond-stripe-agent-demo/tsconfig.json +13 -0
- package/templates/paybond-travel-agent/package-lock.json +13 -13
- package/templates/paybond-travel-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-vercel-shopping-agent/package-lock.json +7 -7
- package/templates/paybond-vercel-shopping-agent/src/paybond.config.ts +1 -1
package/dist/agent/evidence.d.ts
CHANGED
|
@@ -3,7 +3,17 @@ export type BuiltAutoEvidence = {
|
|
|
3
3
|
payload: Record<string, unknown>;
|
|
4
4
|
vendorPayload?: Record<string, unknown>;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Reject Stripe funding webhook envelopes before they become completion evidence.
|
|
8
|
+
*
|
|
9
|
+
* Funding webhooks (`payment_intent.succeeded`, `charge.*`, event envelopes) fund Harbor
|
|
10
|
+
* intents; tool-completion evidence must use SDK charge/result shapes (or mapped
|
|
11
|
+
* {@link mapStripeToolResultToEvidence} outputs), not webhook bodies.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertToolResultNotFundingWebhook(toolResult: unknown): void;
|
|
6
14
|
/**
|
|
7
15
|
* Build canonical evidence payloads from a tool result using the completion catalog.
|
|
16
|
+
*
|
|
17
|
+
* Fail-closed: Stripe funding webhook-shaped tool results are rejected before mapping.
|
|
8
18
|
*/
|
|
9
19
|
export declare function buildAutoEvidencePayload(entry: Pick<PaybondSideEffectingToolEntry, "evidencePreset" | "evidenceMapper">, toolResult: unknown, ctx: PaybondToolCallContext): Record<string, unknown>;
|
package/dist/agent/evidence.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { getCompletionPreset, } from "../completion-catalog.js";
|
|
2
2
|
import { isVendorPack, mapVendorEvidenceToCanonical, resolveCompletionPreset, } from "../completion-resolve.js";
|
|
3
|
+
import { assertNotStripeFundingWebhook } from "../stripe-commerce/evidence.js";
|
|
3
4
|
function assertEvidenceRecord(value, message) {
|
|
4
5
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5
6
|
throw new Error(message);
|
|
6
7
|
}
|
|
7
8
|
return value;
|
|
8
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Reject Stripe funding webhook envelopes before they become completion evidence.
|
|
12
|
+
*
|
|
13
|
+
* Funding webhooks (`payment_intent.succeeded`, `charge.*`, event envelopes) fund Harbor
|
|
14
|
+
* intents; tool-completion evidence must use SDK charge/result shapes (or mapped
|
|
15
|
+
* {@link mapStripeToolResultToEvidence} outputs), not webhook bodies.
|
|
16
|
+
*/
|
|
17
|
+
export function assertToolResultNotFundingWebhook(toolResult) {
|
|
18
|
+
if (typeof toolResult === "object" && toolResult !== null && !Array.isArray(toolResult)) {
|
|
19
|
+
assertNotStripeFundingWebhook(toolResult);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
9
22
|
/**
|
|
10
23
|
* Build canonical evidence payloads from a tool result using the completion catalog.
|
|
24
|
+
*
|
|
25
|
+
* Fail-closed: Stripe funding webhook-shaped tool results are rejected before mapping.
|
|
11
26
|
*/
|
|
12
27
|
export function buildAutoEvidencePayload(entry, toolResult, ctx) {
|
|
28
|
+
assertToolResultNotFundingWebhook(toolResult);
|
|
13
29
|
if (entry.evidenceMapper) {
|
|
14
30
|
return assertEvidenceRecord(entry.evidenceMapper(toolResult, ctx), "evidenceMapper must return a JSON object payload");
|
|
15
31
|
}
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { formatPaybondAttachEnvSnippet, openPaybondAttachBundle, PAYBOND_ATTACH_
|
|
|
6
6
|
export { createPaybondGenericAgentConfig, createPaybondGenericInputGuard, type PaybondGenericAgentConfig, } from "./generic-runner.js";
|
|
7
7
|
export { runGenericSandboxDemo, type RunGenericSandboxDemoInput, type RunGenericSandboxDemoResult, } from "./generic-sandbox-demo.js";
|
|
8
8
|
export { PaybondFrameworkAdapter, createGenericToolExecutor, createToolInputGuardAdapter, paybondGenericToolExecutorAdapter, paybondToolInputGuardAdapter, type PaybondGenericToolCall, type PaybondGenericToolDefinition, type PaybondGenericWrappedToolDefinition, type PaybondToolInputGuardAdapter, } from "./adapter.js";
|
|
9
|
-
export { buildAutoEvidencePayload, type BuiltAutoEvidence } from "./evidence.js";
|
|
9
|
+
export { assertToolResultNotFundingWebhook, buildAutoEvidencePayload, type BuiltAutoEvidence, } from "./evidence.js";
|
|
10
10
|
export { PaybondToolInterceptor } from "./interceptor.js";
|
|
11
11
|
export { PaybondToolRegistry, createPaybondToolRegistry, } from "./registry.js";
|
|
12
12
|
export { PaybondAgentRun, PaybondAgentRunFacade, type PaybondAgentRunHost, } from "./run.js";
|
package/dist/agent/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export { formatPaybondAttachEnvSnippet, openPaybondAttachBundle, PAYBOND_ATTACH_
|
|
|
6
6
|
export { createPaybondGenericAgentConfig, createPaybondGenericInputGuard, } from "./generic-runner.js";
|
|
7
7
|
export { runGenericSandboxDemo, } from "./generic-sandbox-demo.js";
|
|
8
8
|
export { createGenericToolExecutor, createToolInputGuardAdapter, paybondGenericToolExecutorAdapter, paybondToolInputGuardAdapter, } from "./adapter.js";
|
|
9
|
-
export { buildAutoEvidencePayload } from "./evidence.js";
|
|
9
|
+
export { assertToolResultNotFundingWebhook, buildAutoEvidencePayload, } from "./evidence.js";
|
|
10
10
|
export { PaybondToolInterceptor } from "./interceptor.js";
|
|
11
11
|
export { PaybondToolRegistry, createPaybondToolRegistry, } from "./registry.js";
|
|
12
12
|
export { PaybondAgentRun, PaybondAgentRunFacade, } from "./run.js";
|
|
@@ -289,6 +289,7 @@ export class PaybondToolInterceptor {
|
|
|
289
289
|
toolResult,
|
|
290
290
|
auth,
|
|
291
291
|
evidenceId,
|
|
292
|
+
externalAttestations,
|
|
292
293
|
});
|
|
293
294
|
this.emitTrace({
|
|
294
295
|
type: "evidence_submitted",
|
|
@@ -429,6 +430,7 @@ export class PaybondToolInterceptor {
|
|
|
429
430
|
tool_call_id: options.toolCallId,
|
|
430
431
|
evidence_preset: options.entry.evidencePreset,
|
|
431
432
|
decision_id: options.auth.decisionId,
|
|
433
|
+
external_attestations: options.externalAttestations ?? [],
|
|
432
434
|
},
|
|
433
435
|
idempotencyKey,
|
|
434
436
|
});
|
|
@@ -457,6 +459,8 @@ export class PaybondToolInterceptor {
|
|
|
457
459
|
const result = await this.host.harbor.submitEvidence(this.binding.intentId, wire, {
|
|
458
460
|
idempotencyKey,
|
|
459
461
|
recognitionProof,
|
|
462
|
+
agentReceiptAttestations: options.externalAttestations ?? [],
|
|
463
|
+
agentReceiptSourceRunId: this.binding.runId,
|
|
460
464
|
});
|
|
461
465
|
const resultRecord = result;
|
|
462
466
|
const intentState = typeof resultRecord.intent_state === "string"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentReceiptV1 } from "../agent-receipt.js";
|
|
2
|
+
import type { PaybondGenericWrappedToolDefinition } from "./adapter.js";
|
|
2
3
|
import type { PaybondAgentInput, PaybondAgentResult } from "./facade.js";
|
|
3
4
|
export type VerifyAgentReceiptV1Result = {
|
|
4
5
|
valid: boolean;
|
|
@@ -24,7 +25,21 @@ export type GetAgentReceiptInput = {
|
|
|
24
25
|
toolCallId: string;
|
|
25
26
|
receiptId?: never;
|
|
26
27
|
};
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Callable `paybond.agent({...})` surface with receipt helpers attached.
|
|
30
|
+
*
|
|
31
|
+
* The generic framework (the default when `framework` is omitted or `"generic"`)
|
|
32
|
+
* normalizes a `{ name: execute }` record or `{ name, execute }[]` into
|
|
33
|
+
* middleware-wrapped tools, so `result.tools` is a
|
|
34
|
+
* {@link PaybondGenericWrappedToolDefinition} array regardless of the input shape.
|
|
35
|
+
* Framework-native paths (`vercel-ai`, `openai-agents`, `langgraph`, ...) preserve
|
|
36
|
+
* the caller's tool shape, so `result.tools` matches the input tools type.
|
|
37
|
+
*/
|
|
38
|
+
export type PaybondAgentCallable = {
|
|
39
|
+
<TTools = unknown>(input: PaybondAgentInput<TTools> & {
|
|
40
|
+
framework?: "generic";
|
|
41
|
+
}): Promise<PaybondAgentResult<PaybondGenericWrappedToolDefinition[]>>;
|
|
42
|
+
<TTools>(input: PaybondAgentInput<TTools>): Promise<PaybondAgentResult<TTools>>;
|
|
28
43
|
getReceipt(input: GetAgentReceiptInput): Promise<AgentReceiptV1>;
|
|
29
44
|
verifyReceipt(receipt: AgentReceiptV1, options?: {
|
|
30
45
|
offline?: boolean;
|
package/dist/agent/run.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { PaybondToolInterceptor } from "./interceptor.js";
|
|
|
2
2
|
import { PaybondToolRegistry } from "./registry.js";
|
|
3
3
|
import { PaybondRunBinding, PaybondAgentRunBindInput, PaybondRunGuard } from "./types.js";
|
|
4
4
|
import type { PaybondPolicySnapshot } from "../policy/snapshot.js";
|
|
5
|
+
import { type AgentReceiptExternalAttestationV1 } from "../agent-receipt.js";
|
|
5
6
|
import { type PaybondPolicyReloadFailedEvent, type PaybondPolicyReloadOptions, type PaybondPolicyReloadedEvent, type PaybondPolicyReloadResult } from "../policy/reload.js";
|
|
6
7
|
type SandboxBootstrapResult = {
|
|
7
8
|
tenant_id: string;
|
|
@@ -33,6 +34,8 @@ type AgentRunHarbor = {
|
|
|
33
34
|
submitEvidence(intentId: string, evidenceBody: Record<string, unknown>, options: {
|
|
34
35
|
idempotencyKey?: string;
|
|
35
36
|
recognitionProof: Record<string, unknown>;
|
|
37
|
+
agentReceiptAttestations?: AgentReceiptExternalAttestationV1[];
|
|
38
|
+
agentReceiptSourceRunId?: string;
|
|
36
39
|
}): Promise<Record<string, unknown>>;
|
|
37
40
|
};
|
|
38
41
|
type AgentRunGuardrails = {
|
package/dist/agent/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PaybondToolInterceptor } from "./interceptor.js";
|
|
2
2
|
import { PaybondAgentRunBindError, } from "./types.js";
|
|
3
|
-
import { configHashSha256Hex, promptHashSha256Hex } from "../agent-receipt.js";
|
|
3
|
+
import { configHashSha256Hex, promptHashSha256Hex, } from "../agent-receipt.js";
|
|
4
4
|
import { reloadPolicyOnRun, } from "../policy/reload.js";
|
|
5
5
|
import { PaybondPolicyReloadController } from "../policy/watcher.js";
|
|
6
6
|
import { resolveDevTraceSink } from "../dev/trace-buffer.js";
|
|
@@ -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
|
+
}
|