@paybond/kit 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -22,12 +22,24 @@ npm install @paybond/kit
22
22
  - A `paybond_sk_sandbox_...` or `paybond_sk_live_...` service-account API key
23
23
  - For intent creation or evidence submission: 32-byte Ed25519 signing seeds owned by your application
24
24
 
25
- Minimal environment for the quick start:
25
+ Create a sandbox key for local development:
26
26
 
27
27
  ```bash
28
- export PAYBOND_API_KEY="paybond_sk_sandbox_..."
28
+ npx -p @paybond/kit paybond login
29
29
  ```
30
30
 
31
+ `paybond login` writes `PAYBOND_API_KEY` to `.env.local` with file mode `0600`, refuses to overwrite an existing key unless `--force` is passed, and refuses env files that are not ignored by git. Live production keys are created by tenant admins in Console and stored in deployment secret managers.
32
+
33
+ ## First guardrail scaffold
34
+
35
+ Use this first when you have a paid tool and want Paybond guardrails in the sandbox:
36
+
37
+ ```bash
38
+ npx -p @paybond/kit paybond-init --preset paid-tool-guard --framework provider-agnostic --out paybond-guardrail-demo.ts
39
+ ```
40
+
41
+ The generated demo opens Paybond, bootstraps a sandbox guardrail intent, wraps one replaceable paid-tool handler, submits sandbox evidence, and prints the lifecycle result. Free Developer is sandbox-only; live settlement rails start on paid production plans.
42
+
31
43
  ## Tenant isolation
32
44
 
33
45
  Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection.
@@ -73,31 +85,34 @@ const paybond = await Paybond.open({
73
85
  expectedEnvironment: "sandbox",
74
86
  });
75
87
 
76
- const created = await paybond.intents.create({
77
- // principal, payee, budget, predicate, evidence schema, deadline...
78
- allowedTools: ["travel.book_hotel"],
79
- settlementRail: "stripe_connect",
88
+ const guardrail = await paybond.guardrails.bootstrapSandbox({
89
+ operation: "travel.book_hotel",
90
+ requestedSpendCents: 20_000,
91
+ currency: "usd",
80
92
  });
81
93
 
82
- const intentId = String(created.intent_id);
83
- const capabilityToken = String(created.capability_token ?? "");
84
- if (!capabilityToken) {
85
- throw new Error("fund the intent before guarding tools");
86
- }
87
-
88
- const guard = paybond.spendGuard(intentId, capabilityToken);
94
+ const guard = paybond.spendGuard(guardrail.intent_id, guardrail.capability_token);
89
95
  const guardedTool = guard.guardTool(
90
- { operation: "travel.book_hotel", requestedSpendCents: 20_000 },
96
+ {
97
+ operation: guardrail.operation,
98
+ requestedSpendCents: guardrail.requested_spend_cents,
99
+ },
91
100
  async (input) => bookHotel(input),
92
101
  );
102
+
103
+ const result = await guardedTool({ hotelId: "hotel_demo", maxPriceCents: 20_000 });
104
+ await paybond.guardrails.submitSandboxEvidence({
105
+ intentId: guardrail.intent_id,
106
+ payload: { result, sandbox: true },
107
+ });
93
108
  ```
94
109
 
95
- The `paybond.harbor` client is created by `Paybond.open(...)` and bound to the tenant resolved from the service-account API key. Normal integrations read `capability_token` from `paybond.intents.create(...)`, or from `paybond.intents.fund(...)` after an `x402_usdc_base` payment challenge is satisfied.
110
+ The `paybond.harbor` and `paybond.guardrails` clients are created by `Paybond.open(...)` and bound to the tenant resolved from the service-account API key. Production integrations read `capability_token` from `paybond.intents.create(...)`, or from `paybond.intents.fund(...)` after an `x402_usdc_base` payment challenge is satisfied.
96
111
 
97
- Scaffold a wrapper:
112
+ Scaffold a guardrail integration:
98
113
 
99
114
  ```bash
100
- npx -p @paybond/kit paybond-init --framework provider-agnostic --out paybond-spend-guard.ts
115
+ npx -p @paybond/kit paybond-init --preset paid-tool-guard --framework provider-agnostic --out paybond-guardrail-demo.ts
101
116
  ```
102
117
 
103
118
  ## What the package includes
@@ -117,8 +132,9 @@ Gateway and trust helpers:
117
132
  - `GatewaySignalClient` and `ServiceAccountSignalSession` for tenant-scoped Signal reads and signed portfolio artifacts
118
133
  - `GatewayFraudClient` and `ServiceAccountFraudSession` for tenant-scoped fraud assessments, review queues, review events, metrics, and release-gate config
119
134
  - Protocol-v2 helpers for mandate verification, replay-safe recognition proof verification, receipt reads, and A2A discovery
135
+ - `paybond login` for sandbox device approval and local `.env.local` API-key setup
120
136
  - `paybond-mcp-server` for tenant-bound MCP tool exposure to any MCP-compatible host
121
- - `paybond-init` for generating a small spend guard wrapper
137
+ - `paybond-init` for generating a Paybond guardrail integration with a sandbox smoke path
122
138
 
123
139
  Agent-facing surfaces are model-provider agnostic. Paybond verifies tool operations and tenant scope, not whether a tool call came from OpenAI, Anthropic, Gemini, a local model, or another runtime.
124
140
 
@@ -143,6 +159,7 @@ Gateway-backed protocol helpers throw `ProtocolHttpError` with parsed `errorCode
143
159
  ## Docs
144
160
 
145
161
  - Long-form docs: https://paybond.ai/docs/kit
162
+ - One-command guardrails: https://paybond.ai/docs/kit/one-command-guardrails
146
163
  - TypeScript quickstart: https://paybond.ai/docs/kit/quickstart-typescript
147
164
  - TypeScript SDK reference: https://paybond.ai/docs/kit/sdk-reference-typescript
148
165
  - MCP server guide: https://paybond.ai/docs/kit/mcp-server
package/dist/index.d.ts CHANGED
@@ -72,6 +72,49 @@ export type FundIntentResult = {
72
72
  capabilityToken?: string;
73
73
  funding?: IntentFundingResult;
74
74
  };
75
+ export type SandboxGuardrailBootstrapInput = {
76
+ operation: string;
77
+ requestedSpendCents: number;
78
+ currency?: string;
79
+ evidenceSchema?: Record<string, unknown>;
80
+ metadata?: Record<string, unknown>;
81
+ idempotencyKey?: string;
82
+ };
83
+ export type SandboxGuardrailEvidenceInput = {
84
+ intentId: string;
85
+ payload?: Record<string, unknown>;
86
+ artifacts?: string[];
87
+ operation?: string;
88
+ requestedSpendCents?: number;
89
+ metadata?: Record<string, unknown>;
90
+ idempotencyKey?: string;
91
+ };
92
+ export type SandboxGuardrailBootstrapResult = {
93
+ tenant_id: string;
94
+ intent_id: string;
95
+ capability_token: string;
96
+ operation: string;
97
+ requested_spend_cents: number;
98
+ sandbox_lifecycle_status: string;
99
+ currency?: string;
100
+ settlement_rail?: string;
101
+ settlement_mode?: string;
102
+ simulator_event?: unknown;
103
+ };
104
+ export type SandboxGuardrailEvidenceResult = {
105
+ tenant_id: string;
106
+ intent_id: string;
107
+ capability_token?: string;
108
+ operation: string;
109
+ requested_spend_cents: number;
110
+ sandbox_lifecycle_status: string;
111
+ settlement_rail?: string;
112
+ settlement_mode?: string;
113
+ predicate_passed?: boolean | null;
114
+ payload_digest?: string;
115
+ artifacts_digest?: string;
116
+ simulator_event?: unknown;
117
+ };
75
118
  export declare const DEFAULT_PAYBOND_GATEWAY_BASE_URL = "https://api.paybond.ai";
76
119
  /** Async supplier for upstream Harbor bearer tokens on low-level direct clients. */
77
120
  export type HarborBearerSupplier = () => Promise<string | null | undefined>;
@@ -878,6 +921,28 @@ export declare class GatewayHarborClient {
878
921
  }): Promise<FundIntentResult>;
879
922
  submitEvidence(intentId: string, evidenceBody: Record<string, unknown>, options: GatewayHarborMutationOptions): Promise<SubmitEvidenceResult>;
880
923
  }
924
+ type GatewaySandboxGuardrailsClientOptions = {
925
+ staticGatewayBearerToken: string;
926
+ maxRetries?: number;
927
+ };
928
+ /**
929
+ * Gateway sandbox guardrail helpers.
930
+ *
931
+ * These routes derive tenant scope only from the service-account bearer token and intentionally
932
+ * reject caller-supplied tenant IDs, including the normal `x-tenant-id` Gateway Harbor header.
933
+ */
934
+ export declare class PaybondGuardrails {
935
+ private readonly base;
936
+ readonly tenantId: string;
937
+ private readonly bearerToken;
938
+ private readonly maxRetries;
939
+ constructor(gatewayBaseUrl: string, tenantId: string, options: GatewaySandboxGuardrailsClientOptions);
940
+ private headers;
941
+ private fetchWithRetries;
942
+ private postJSON;
943
+ bootstrapSandbox(input: SandboxGuardrailBootstrapInput): Promise<SandboxGuardrailBootstrapResult>;
944
+ submitSandboxEvidence(input: SandboxGuardrailEvidenceInput): Promise<SandboxGuardrailEvidenceResult>;
945
+ }
881
946
  type GatewaySignalClientOptions = {
882
947
  staticGatewayBearerToken: string;
883
948
  maxRetries?: number;
@@ -1071,6 +1136,7 @@ export declare class PaybondIntents {
1071
1136
  */
1072
1137
  export declare class Paybond {
1073
1138
  readonly harbor: GatewayHarborClient;
1139
+ readonly guardrails: PaybondGuardrails;
1074
1140
  readonly signal: GatewaySignalClient;
1075
1141
  readonly fraud: GatewayFraudClient;
1076
1142
  readonly a2a: GatewayA2AClient;
package/dist/index.js CHANGED
@@ -801,6 +801,132 @@ export class GatewayHarborClient {
801
801
  return parseSubmitEvidenceResponse(JSON.parse(text));
802
802
  }
803
803
  }
804
+ /**
805
+ * Gateway sandbox guardrail helpers.
806
+ *
807
+ * These routes derive tenant scope only from the service-account bearer token and intentionally
808
+ * reject caller-supplied tenant IDs, including the normal `x-tenant-id` Gateway Harbor header.
809
+ */
810
+ export class PaybondGuardrails {
811
+ base;
812
+ tenantId;
813
+ bearerToken;
814
+ maxRetries;
815
+ constructor(gatewayBaseUrl, tenantId, options) {
816
+ this.base = normalizeBase(gatewayBaseUrl) + "/";
817
+ this.tenantId = tenantId.trim();
818
+ this.bearerToken = options.staticGatewayBearerToken.trim();
819
+ this.maxRetries = Math.max(1, options.maxRetries ?? 3);
820
+ }
821
+ headers(extra) {
822
+ const headers = new Headers(extra);
823
+ headers.set("accept", "application/json");
824
+ headers.set("authorization", `Bearer ${this.bearerToken}`);
825
+ return headers;
826
+ }
827
+ async fetchWithRetries(url, init) {
828
+ let lastErr;
829
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
830
+ let res;
831
+ try {
832
+ res = await fetch(url, init);
833
+ }
834
+ catch (e) {
835
+ lastErr = e;
836
+ if (attempt + 1 >= this.maxRetries)
837
+ throw e;
838
+ await new Promise((r) => setTimeout(r, backoffMs(attempt)));
839
+ continue;
840
+ }
841
+ if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
842
+ const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
843
+ const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
844
+ await new Promise((r) => setTimeout(r, delayMs));
845
+ continue;
846
+ }
847
+ return res;
848
+ }
849
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
850
+ }
851
+ async postJSON(path, payload, options) {
852
+ const url = `${this.base}${path.replace(/^\/+/, "")}`;
853
+ const extraHeaders = {
854
+ "content-type": "application/json",
855
+ };
856
+ if (options?.idempotencyKey?.trim()) {
857
+ extraHeaders["idempotency-key"] = options.idempotencyKey.trim();
858
+ }
859
+ const res = await this.fetchWithRetries(url, {
860
+ method: "POST",
861
+ headers: this.headers(extraHeaders),
862
+ body: JSON.stringify(payload),
863
+ });
864
+ const text = await res.text();
865
+ return { res, text, url };
866
+ }
867
+ async bootstrapSandbox(input) {
868
+ const payload = {
869
+ operation: input.operation,
870
+ requested_spend_cents: input.requestedSpendCents,
871
+ };
872
+ if (input.currency !== undefined) {
873
+ payload.currency = input.currency;
874
+ }
875
+ if (input.evidenceSchema !== undefined) {
876
+ payload.evidence_schema = input.evidenceSchema;
877
+ }
878
+ if (input.metadata !== undefined) {
879
+ payload.metadata = input.metadata;
880
+ }
881
+ const { res, text, url } = await this.postJSON("/v1/sandbox/guardrails/bootstrap", payload, { idempotencyKey: input.idempotencyKey });
882
+ if (!res.ok) {
883
+ throw new HarborHttpError(`Gateway sandbox guardrail bootstrap HTTP ${res.status}: ${text}`, {
884
+ statusCode: res.status,
885
+ url,
886
+ bodyText: text,
887
+ });
888
+ }
889
+ return parseSandboxGuardrailBootstrapResponse(JSON.parse(text), {
890
+ tenantId: this.tenantId,
891
+ url,
892
+ bodyText: text,
893
+ statusCode: res.status,
894
+ });
895
+ }
896
+ async submitSandboxEvidence(input) {
897
+ const payload = {};
898
+ if (input.payload !== undefined) {
899
+ payload.payload = input.payload;
900
+ }
901
+ if (input.artifacts !== undefined) {
902
+ payload.artifacts = input.artifacts;
903
+ }
904
+ if (input.operation !== undefined) {
905
+ payload.operation = input.operation;
906
+ }
907
+ if (input.requestedSpendCents !== undefined) {
908
+ payload.requested_spend_cents = input.requestedSpendCents;
909
+ }
910
+ if (input.metadata !== undefined) {
911
+ payload.metadata = input.metadata;
912
+ }
913
+ const { res, text, url } = await this.postJSON(`/v1/sandbox/guardrails/${encodeURIComponent(input.intentId)}/evidence`, payload, { idempotencyKey: input.idempotencyKey });
914
+ if (!res.ok) {
915
+ throw new HarborHttpError(`Gateway sandbox guardrail evidence HTTP ${res.status}: ${text}`, {
916
+ statusCode: res.status,
917
+ url,
918
+ bodyText: text,
919
+ });
920
+ }
921
+ return parseSandboxGuardrailEvidenceResponse(JSON.parse(text), {
922
+ tenantId: this.tenantId,
923
+ intentId: input.intentId,
924
+ url,
925
+ bodyText: text,
926
+ statusCode: res.status,
927
+ });
928
+ }
929
+ }
804
930
  const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
805
931
  const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "stripe_ach_debit", "x402_usdc_base"]);
806
932
  const FRAUD_REVIEW_EVENT_TYPES = new Set([
@@ -878,6 +1004,78 @@ function parseSubmitEvidenceResponse(value) {
878
1004
  predicatePassed: typeof body.predicate_passed === "boolean" ? body.predicate_passed : undefined,
879
1005
  };
880
1006
  }
1007
+ function parseSandboxGuardrailBootstrapResponse(value, init) {
1008
+ const body = assertJSONObject(value);
1009
+ const tenant = sandboxGuardrailString(body, "tenant_id", init);
1010
+ if (tenant !== init.tenantId) {
1011
+ throw new Error(`sandbox guardrail tenant mismatch: client=${init.tenantId} gateway=${tenant}`);
1012
+ }
1013
+ return {
1014
+ tenant_id: tenant,
1015
+ intent_id: sandboxGuardrailString(body, "intent_id", init),
1016
+ capability_token: sandboxGuardrailString(body, "capability_token", init),
1017
+ operation: sandboxGuardrailString(body, "operation", init),
1018
+ requested_spend_cents: sandboxGuardrailNumber(body, "requested_spend_cents", init),
1019
+ sandbox_lifecycle_status: sandboxGuardrailString(body, "sandbox_lifecycle_status", init),
1020
+ currency: sandboxGuardrailOptionalString(body.currency),
1021
+ settlement_rail: sandboxGuardrailOptionalString(body.settlement_rail),
1022
+ settlement_mode: sandboxGuardrailOptionalString(body.settlement_mode),
1023
+ simulator_event: body.simulator_event,
1024
+ };
1025
+ }
1026
+ function parseSandboxGuardrailEvidenceResponse(value, init) {
1027
+ const body = assertJSONObject(value);
1028
+ const tenant = sandboxGuardrailString(body, "tenant_id", init);
1029
+ if (tenant !== init.tenantId) {
1030
+ throw new Error(`sandbox guardrail tenant mismatch: client=${init.tenantId} gateway=${tenant}`);
1031
+ }
1032
+ const intentId = sandboxGuardrailString(body, "intent_id", init);
1033
+ if (intentId !== init.intentId) {
1034
+ throw new Error(`sandbox guardrail intent mismatch: requested=${init.intentId} gateway=${intentId}`);
1035
+ }
1036
+ const predicatePassedRaw = body.predicate_passed;
1037
+ return {
1038
+ tenant_id: tenant,
1039
+ intent_id: intentId,
1040
+ capability_token: sandboxGuardrailOptionalString(body.capability_token),
1041
+ operation: sandboxGuardrailString(body, "operation", init),
1042
+ requested_spend_cents: sandboxGuardrailNumber(body, "requested_spend_cents", init),
1043
+ sandbox_lifecycle_status: sandboxGuardrailString(body, "sandbox_lifecycle_status", init),
1044
+ settlement_rail: sandboxGuardrailOptionalString(body.settlement_rail),
1045
+ settlement_mode: sandboxGuardrailOptionalString(body.settlement_mode),
1046
+ predicate_passed: typeof predicatePassedRaw === "boolean" || predicatePassedRaw === null
1047
+ ? predicatePassedRaw
1048
+ : undefined,
1049
+ payload_digest: sandboxGuardrailOptionalString(body.payload_digest),
1050
+ artifacts_digest: sandboxGuardrailOptionalString(body.artifacts_digest),
1051
+ simulator_event: body.simulator_event,
1052
+ };
1053
+ }
1054
+ function sandboxGuardrailString(body, field, init) {
1055
+ const value = body[field];
1056
+ if (typeof value === "string" && value.trim()) {
1057
+ return value;
1058
+ }
1059
+ throw new HarborHttpError(`Gateway sandbox guardrail response missing ${field}`, {
1060
+ statusCode: init.statusCode,
1061
+ url: init.url,
1062
+ bodyText: init.bodyText,
1063
+ });
1064
+ }
1065
+ function sandboxGuardrailOptionalString(value) {
1066
+ return typeof value === "string" && value.trim() ? value : undefined;
1067
+ }
1068
+ function sandboxGuardrailNumber(body, field, init) {
1069
+ const value = body[field];
1070
+ if (typeof value === "number" && Number.isFinite(value)) {
1071
+ return value;
1072
+ }
1073
+ throw new HarborHttpError(`Gateway sandbox guardrail response missing ${field}`, {
1074
+ statusCode: init.statusCode,
1075
+ url: init.url,
1076
+ bodyText: init.bodyText,
1077
+ });
1078
+ }
881
1079
  function parseIntentFundingResult(value) {
882
1080
  const body = assertJSONObject(value);
883
1081
  const onchainRaw = body.onchain_transaction_hashes;
@@ -1795,13 +1993,15 @@ export class PaybondIntents {
1795
1993
  */
1796
1994
  export class Paybond {
1797
1995
  harbor;
1996
+ guardrails;
1798
1997
  signal;
1799
1998
  fraud;
1800
1999
  a2a;
1801
2000
  protocol;
1802
2001
  intents;
1803
- constructor(harbor, signal, fraud, a2a, protocol) {
2002
+ constructor(harbor, guardrails, signal, fraud, a2a, protocol) {
1804
2003
  this.harbor = harbor;
2004
+ this.guardrails = guardrails;
1805
2005
  this.signal = signal;
1806
2006
  this.fraud = fraud;
1807
2007
  this.a2a = a2a;
@@ -1817,6 +2017,10 @@ export class Paybond {
1817
2017
  staticGatewayBearerToken: init.apiKey,
1818
2018
  maxRetries,
1819
2019
  });
2020
+ const guardrails = new PaybondGuardrails(gatewayBaseUrl, tenantId, {
2021
+ staticGatewayBearerToken: init.apiKey,
2022
+ maxRetries,
2023
+ });
1820
2024
  const signal = new GatewaySignalClient(gatewayBaseUrl, tenantId, {
1821
2025
  staticGatewayBearerToken: init.apiKey,
1822
2026
  maxRetries,
@@ -1833,7 +2037,7 @@ export class Paybond {
1833
2037
  staticGatewayBearerToken: init.apiKey,
1834
2038
  maxRetries,
1835
2039
  });
1836
- return new Paybond(harbor, signal, fraud, a2a, protocol);
2040
+ return new Paybond(harbor, guardrails, signal, fraud, a2a, protocol);
1837
2041
  }
1838
2042
  /** Reserved for future HTTP client cleanup; safe to call after work completes. */
1839
2043
  async aclose() {