@farthershore/backend 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,15 @@
1
1
  import { FartherShore, type FartherShoreInitOptions } from "./core/runtime.js";
2
- import { type ExpressMiddleware, type MiddlewareOptions } from "./adapters/express.js";
2
+ import { type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type MiddlewareOptions, type VerifiedExpressHandler } from "./adapters/express.js";
3
3
  export { FartherShore } from "./core/runtime.js";
4
4
  export type { FartherShoreInitOptions } from "./core/runtime.js";
5
5
  export { FartherShoreError, statusForCode } from "./core/errors.js";
6
6
  export { verifyRequest, type VerifyRequestInput, type VerifyRequestDeps, type FartherShoreRequestContext, type HeadersLike, } from "./core/verifyRequest.js";
7
- export { verifyContext } from "./core/verifyContext.js";
8
- export type { FartherShoreSignedContext } from "./core/verifyContext.js";
9
- export { hasPermission, requirePermission, permissionGrants, permissionSatisfies, parsePermissionHeader, FartherShorePermissionError, IDENTITY_HEADER_NAMES, type PermissionCarrier, } from "./core/permissions.js";
7
+ export { verifyContext, decodeContextClaims, principalFromContextClaims, } from "./core/verifyContext.js";
8
+ export type { FartherShoreSignedContext, ConsumerPrincipal, } from "./core/verifyContext.js";
9
+ export { requireMember, requireService, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
10
+ export { hasPermission, requirePermission, permissionGrants, permissionSatisfies, FartherShorePermissionError, type PermissionCarrier, } from "./core/permissions.js";
10
11
  export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
11
- export { NonceCache, type NonceCacheOptions } from "./core/nonceCache.js";
12
+ export { NonceCache, type NonceCacheOptions, type NonceStore, } from "./core/nonceCache.js";
12
13
  export { BootstrapClient, type BootstrapClientOptions, } from "./core/bootstrap.js";
13
14
  export { MeteringClient, type MeteringClientOptions, type MeterOptions, } from "./core/metering.js";
14
15
  export { PostStreamUsageClient, type PostStreamUsageClientOptions, type ReportUsageInput, type RequestScopedReportUsageInput, type ReportUsageResult, } from "./core/post-stream-usage.js";
@@ -16,7 +17,7 @@ export { buildHealthReport, reportHealth, type HealthSnapshot, type HealthStatus
16
17
  export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
17
18
  export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
18
19
  export type { FartherShoreTunnelOptions } from "./core/runtime.js";
19
- export { createExpressMiddleware, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, } from "./adapters/express.js";
20
+ export { createExpressMiddleware, createExpressHandler, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, type VerifiedExpressHandler, type VerifiedPrincipalContext, } from "./adapters/express.js";
20
21
  export { FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_PREFIXES, RUNTIME_TOKEN_CAPABILITIES, RUNTIME_HEADER_NAMES, RUNTIME_CLOCK_SKEW_SECONDS, RUNTIME_REPLAY_WINDOW_SECONDS, EMPTY_BODY_SHA256, STREAMING_EXEMPT_BODY_HASH, MAX_BODY_BYTES, type RuntimeErrorCode, type RuntimeTokenCapability, type CanonicalSigningInput, type RuntimeBootstrapResponse, type RuntimeMeteringEvent, type RuntimeHealthReport, type TransportMode, RUNTIME_ERROR_CODE_TO_ERROR_CODE, runtimeErrorToErrorCode, type LimitDescriptor, type RuntimeMappedErrorCode, } from "./runtime-types.js";
21
22
  export { RUNTIME_ERROR_CODES } from "./generated/runtime-contract.js";
22
23
  export { hashBody, buildCanonicalSigningString, canonicalizeQuery, signCanonicalString, verifyCanonicalSignature, runtimeTokenKind, } from "./runtime-signing.js";
@@ -27,8 +28,16 @@ export { createUsage, withUsage, computeMeteringHeaders, MeteringError, METERING
27
28
  * (the Express adapter) bound to itself.
28
29
  */
29
30
  export type FartherShoreInstance = FartherShore & {
30
- /** Express middleware: fail-closed verify → req.fartherShore. */
31
+ /** Express middleware: strict fail-closed verify → req.fartherShore (+ strip x-fs-*). */
31
32
  middleware(options?: MiddlewareOptions): ExpressMiddleware;
33
+ /**
34
+ * Wrap a route handler so it runs only with a GUARANTEED verified PRINCIPAL —
35
+ * the handler's first argument is a {@link VerifiedPrincipalContext} whose
36
+ * `principal` is NON-OPTIONAL. Fails closed (401 `context_unverified` when the
37
+ * context is absent, `principal_required` when it is identity-less) before the
38
+ * callback runs. Pairs with the strict `middleware()`.
39
+ */
40
+ handler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
32
41
  };
33
42
  export declare const fartherShore: {
34
43
  /** Derive everything from FS_RUNTIME_TOKEN via bootstrap. */
@@ -48,6 +48,13 @@ export type BillableUsageMap = UsageMap;
48
48
  export type MeteringOptions = {
49
49
  token?: string;
50
50
  env?: Record<string, string | undefined>;
51
+ /**
52
+ * Gateway request id for dev-mode trace/usage association (never signed).
53
+ * Thread the VERIFIED `ctx.requestId` here — the strict middleware strips the
54
+ * inbound `x-fs-request-id` header, so the request object no longer carries it.
55
+ * Falls back to the request's `x-fs-request-id` header when unset.
56
+ */
57
+ requestId?: string;
51
58
  measureContext?: Record<string, unknown>;
52
59
  creditUnitsConsumed?: BillableUsageMap;
53
60
  /** Gateway-validated operation identity hint. The SDK signs and transports it
@@ -14,6 +14,13 @@ export declare const canonicalizeQuery: (query: string) => string;
14
14
  * dependence on JSON key order or Node Buffer serialization.
15
15
  */
16
16
  export declare const buildCanonicalSigningString: (input: CanonicalSigningInput) => string;
17
+ /**
18
+ * SHA-256 hex of the presented `X-Fs-Context` JWT string (identity-context
19
+ * binding). A missing/empty context hashes the EMPTY string, so signer and
20
+ * verifier agree byte-for-byte when there is no identity context. The value
21
+ * feeds {@link CanonicalSigningInput.contextHash}.
22
+ */
23
+ export declare const hashContextToken: (token: string | null | undefined) => Promise<string>;
17
24
  /** Sign the canonical string with an Ed25519 private JWK → base64url signature. */
18
25
  export declare const signCanonicalString: (canonical: string, privateJwk: JsonWebKey) => Promise<string>;
19
26
  /** Verify a base64url Ed25519 signature over the canonical string. */
@@ -99,7 +99,7 @@ export declare const DENY_ENVELOPE_FIELDS: {
99
99
  * the subset its bridge produces. Each is a verbatim core `ErrorCode` string —
100
100
  * the drift guard asserts membership in the canonical enum.
101
101
  */
102
- export type RuntimeMappedErrorCode = "UNAUTHORIZED" | "SERVICE_UNAVAILABLE" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
102
+ export type RuntimeMappedErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "SERVICE_UNAVAILABLE" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
103
103
  /**
104
104
  * C-2 — map every canonical {@link RuntimeErrorCode} (wire snake_case value) to
105
105
  * the core `ErrorCode` it belongs to. Total over `RuntimeErrorCode` (the
@@ -126,18 +126,13 @@ export declare const RUNTIME_HEADER_NAMES: {
126
126
  readonly keyId: "x-fs-key-id";
127
127
  readonly requestId: "x-fs-request-id";
128
128
  readonly timestamp: "x-fs-timestamp";
129
- readonly productId: "x-fs-product-id";
129
+ readonly businessId: "x-fs-business-id";
130
130
  readonly backendId: "x-fs-backend-id";
131
131
  readonly routeId: "x-fs-route-id";
132
132
  readonly policyVersion: "x-fs-policy-version";
133
133
  readonly bodyHash: "x-fs-body-hash";
134
134
  };
135
135
  export type RuntimeHeaderName = (typeof RUNTIME_HEADER_NAMES)[keyof typeof RUNTIME_HEADER_NAMES];
136
- export declare const RUNTIME_IDENTITY_HEADER_NAMES: {
137
- readonly permissions: "x-fs-permissions";
138
- readonly roles: "x-fs-roles";
139
- };
140
- export type RuntimeIdentityHeaderName = (typeof RUNTIME_IDENTITY_HEADER_NAMES)[keyof typeof RUNTIME_IDENTITY_HEADER_NAMES];
141
136
  /** Mirrors SERVICE_JWT_CLOCK_SKEW_SECONDS — the per-request signer reuses the
142
137
  * same Ed25519/JWKS infra so the skew allowance is kept identical. */
143
138
  export declare const RUNTIME_CLOCK_SKEW_SECONDS = 5;
@@ -166,11 +161,19 @@ export type CanonicalSigningInput = {
166
161
  requestId: string;
167
162
  /** Unix epoch seconds at signing time. */
168
163
  timestamp: number;
169
- productId: string;
164
+ businessId: string;
170
165
  backendId: string;
171
166
  /** Resolved route id; "" when unresolved. */
172
167
  routeId: string;
173
168
  policyVersion: string;
169
+ /**
170
+ * Consumer-principal wave (D3) — SHA-256 hex of the presented `X-Fs-Context`
171
+ * JWT string, or the SHA-256 of the EMPTY string when context signing is off.
172
+ * Bound into the canonical string so one request-signature verification
173
+ * covers both the request and the identity context. Compute via
174
+ * {@link hashContextToken}.
175
+ */
176
+ contextHash: string;
174
177
  };
175
178
  export type RuntimeEnvironmentKind = RuntimeTokenKind;
176
179
  export type TransportMode = "direct" | "tunnel";
@@ -221,7 +224,7 @@ export type RuntimeLockDescriptor = {
221
224
  lockVersion: number;
222
225
  };
223
226
  export type RuntimeBootstrapResponse = {
224
- product: {
227
+ business: {
225
228
  id: string;
226
229
  slug: string;
227
230
  };
@@ -247,10 +250,15 @@ export type RuntimeBootstrapResponse = {
247
250
  };
248
251
  export type RuntimeMeteringEvent = {
249
252
  event_id: string;
250
- product_id: string;
253
+ business_id: string;
251
254
  backend_id: string;
252
255
  route_id?: string;
253
256
  request_id?: string;
257
+ /** Subscription the usage belongs to (billing attribution). Optional and
258
+ * additive: legacy emitters omit it; core then falls back to resolving the
259
+ * served gateway request via `request_id`, and rows that resolve neither
260
+ * way are persisted unbilled + flagged unattributable. */
261
+ subscription_id?: string;
254
262
  meter: string;
255
263
  qty: number;
256
264
  timestamp: string;
@@ -7,9 +7,9 @@ export declare const DEV_METERING_ENDPOINT = "https://dev-gateway.farthershore.l
7
7
  export type DevGatewayOptions = {
8
8
  mode: DevMode;
9
9
  keys: DevSignerKeys;
10
- productId?: string;
10
+ businessId?: string;
11
11
  backendId?: string;
12
- productSlug?: string;
12
+ businessSlug?: string;
13
13
  backendSlug?: string;
14
14
  /** Extra route ids to expose in bootstrap for route-binding tests. */
15
15
  routeIds?: string[];
@@ -27,7 +27,7 @@ export type DevGateway = {
27
27
  meterEvents: RuntimeMeteringEvent[];
28
28
  /** Every captured attested post-stream usage report. */
29
29
  reportUsageEvents: RuntimePostStreamUsageEvent[];
30
- productId: string;
30
+ businessId: string;
31
31
  backendId: string;
32
32
  jwksUrl: string;
33
33
  };
@@ -1,5 +1,5 @@
1
1
  import { FartherShore } from "../core/runtime.js";
2
- import { type ExpressMiddleware, type MiddlewareOptions } from "../adapters/express.js";
2
+ import { type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type MiddlewareOptions, type VerifiedExpressHandler } from "../adapters/express.js";
3
3
  import { type PermissionCarrier } from "../core/permissions.js";
4
4
  import { type DevGateway } from "./devGateway.js";
5
5
  import { DevUsageSink } from "./usageSink.js";
@@ -13,6 +13,8 @@ export type TracedCarrier = PermissionCarrier & {
13
13
  /** The FartherShore instance augmented with a bound Express middleware. */
14
14
  export type FartherShoreDevInstance = FartherShore & {
15
15
  middleware(options?: MiddlewareOptions): ExpressMiddleware;
16
+ /** Strict-context handler wrapper (guaranteed non-optional ctx). */
17
+ handler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
16
18
  /** The dev harness attached to this runtime. */
17
19
  dev: DevRuntime;
18
20
  };
@@ -24,7 +26,7 @@ export type CreateDevRuntimeOptions = {
24
26
  routes?: string[];
25
27
  /** Meter keys (informational — the dev gateway accepts any meter). */
26
28
  meters?: string[];
27
- productId?: string;
29
+ businessId?: string;
28
30
  backendId?: string;
29
31
  /** Optional app transport for persona `.fetch()`; defaults to global fetch. */
30
32
  appFetch?: typeof fetch;
@@ -7,7 +7,7 @@ export type DevKeysFile = {
7
7
  version: 1;
8
8
  mode: DevMode;
9
9
  keys: DevSignerKeys;
10
- productId: string;
10
+ businessId: string;
11
11
  backendId: string;
12
12
  /** Persona definitions in effect for this dev session. */
13
13
  personas: Record<string, PersonaDefinition>;
@@ -53,7 +53,7 @@ export type PersonaRequestSpec = {
53
53
  /** The context a persona client signs against (bootstrap ids + dev keys). */
54
54
  export type PersonaClientContext = {
55
55
  keys: DevSignerKeys;
56
- productId: string;
56
+ businessId: string;
57
57
  backendId: string;
58
58
  contextSecret: string;
59
59
  contextKid: string;
@@ -96,7 +96,7 @@ export declare const SIGNED_HEADER_NAMES: {
96
96
  readonly keyId: "x-fs-key-id";
97
97
  readonly requestId: "x-fs-request-id";
98
98
  readonly timestamp: "x-fs-timestamp";
99
- readonly productId: "x-fs-product-id";
99
+ readonly businessId: "x-fs-business-id";
100
100
  readonly backendId: "x-fs-backend-id";
101
101
  readonly routeId: "x-fs-route-id";
102
102
  readonly policyVersion: "x-fs-policy-version";
@@ -13,7 +13,7 @@ export type SignedHeaderOverrides = Partial<{
13
13
  kid: string;
14
14
  requestId: string;
15
15
  timestamp: number;
16
- productId: string;
16
+ businessId: string;
17
17
  backendId: string;
18
18
  routeId: string;
19
19
  policyVersion: string;
@@ -25,7 +25,7 @@ export type SignedRequestSpec = {
25
25
  query?: string;
26
26
  body?: Uint8Array | null;
27
27
  streamingExempt?: boolean;
28
- productId?: string;
28
+ businessId?: string;
29
29
  backendId?: string;
30
30
  routeId?: string;
31
31
  policyVersion?: string;
@@ -38,6 +38,13 @@ export type SignedRequestSpec = {
38
38
  privateJwk?: JsonWebKey;
39
39
  /** Key id stamped into `x-fs-key-id`. Defaults to {@link TEST_KID}. */
40
40
  kid?: string;
41
+ /**
42
+ * Consumer-principal wave (D3) — the signed `X-Fs-Context` JWT to bind into
43
+ * the request signature (and stamp as the `x-fs-context` header). Its SHA-256
44
+ * is appended to the canonical string, exactly as the gateway signer does. A
45
+ * missing token binds the empty-string hash (identity-less request).
46
+ */
47
+ contextToken?: string;
41
48
  };
42
49
  /**
43
50
  * Produce a valid signed claim + the X-FS-* header bag. Returns both the verify
@@ -63,10 +70,11 @@ export declare function unreachableJwks(): JwksClient;
63
70
  /**
64
71
  * Mint an HS256-signed `X-Fs-Context` token — the exact INVERSE of
65
72
  * `verifyContext.ts`. The header is `{alg:"HS256",typ:"JWT",kid}` and the
66
- * payload is the `cv:1` claim shape (orgId / actor / productId / compiledPlanId
67
- * / subscriptionId / subscriberId / environmentId / subjectKey plus optional
68
- * permissions / roles). Signs with the same secret the SDK would verify against
69
- * (`contextSecrets` / `FS_CONTEXT_SECRETS`).
73
+ * payload is the cv=2 `ConsumerContextClaims` shape (sub / client_id? / act? /
74
+ * subjectKind / org / businessId / compiledPlanId / subscriptionId /
75
+ * subscriberId / environmentId / subjectKey plus optional permissions / roles).
76
+ * Signs with the same secret the SDK would verify against (`contextSecrets` /
77
+ * `FS_CONTEXT_SECRETS`).
70
78
  */
71
79
  export declare function signContextToken(claim: FartherShoreSignedContext, secret?: string, kid?: string): Promise<string>;
72
80
  export type DevSignerKeys = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farthershore/backend",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Farther Shore backend SDK for builder upstreams: signed response usage, fail-closed gateway request verification, health, and lifecycle from FS_RUNTIME_TOKEN",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,7 +30,8 @@
30
30
  },
31
31
  "files": [
32
32
  "dist",
33
- "README.md"
33
+ "README.md",
34
+ "CHANGELOG.md"
34
35
  ],
35
36
  "publishConfig": {
36
37
  "access": "public"
@@ -38,8 +39,8 @@
38
39
  "optionalDependencies": {
39
40
  "@farthershore/cloudflared-linux-x64": "0.0.0",
40
41
  "@farthershore/cloudflared-linux-arm64": "0.0.0",
41
- "@farthershore/cloudflared-darwin-arm64": "0.0.0",
42
- "@farthershore/cloudflared-darwin-x64": "0.0.0"
42
+ "@farthershore/cloudflared-darwin-x64": "0.0.0",
43
+ "@farthershore/cloudflared-darwin-arm64": "0.0.0"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "express": "^4.0.0 || ^5.0.0"