@farthershore/backend 0.15.0 → 0.17.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, credentialKind, isPortalSession, 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,8 +17,8 @@ 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 { 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";
20
+ export { createExpressMiddleware, createExpressHandler, type ExpressMiddleware, type ExpressRequestLike, type ExpressResponseLike, type ExpressNext, type MiddlewareOptions, type VerifiedExpressHandler, type VerifiedPrincipalContext, } from "./adapters/express.js";
21
+ export { FS_RUNTIME_TOKEN_ENV, RUNTIME_TOKEN_PREFIXES, RUNTIME_TOKEN_OPERATIONS, RUNTIME_HEADER_NAMES, RUNTIME_CLOCK_SKEW_SECONDS, RUNTIME_REPLAY_WINDOW_SECONDS, EMPTY_BODY_SHA256, STREAMING_EXEMPT_BODY_HASH, MAX_BODY_BYTES, type RuntimeErrorCode, type RuntimeTokenOperation, 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";
23
24
  export { createUsage, withUsage, computeMeteringHeaders, MeteringError, METERING_PAYLOAD_HEADER, METERING_SIGNATURE_HEADER, METERING_TOKEN_HEADER, DEFAULT_TOKEN_ENV, type UsageMap, type UsageReporter, type MeteringOptions, type MeteringHeaders, type ComputeMeteringOptions, type ResponseMeteringUsagePayload, } from "./response-metering.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
@@ -119,8 +119,8 @@ export declare const RUNTIME_TOKEN_PREFIXES: {
119
119
  readonly test: "fsrt_test_";
120
120
  };
121
121
  export type RuntimeTokenKind = keyof typeof RUNTIME_TOKEN_PREFIXES;
122
- export declare const RUNTIME_TOKEN_CAPABILITIES: readonly ["gateway_verification", "metering", "health", "tunnel", "drift_report"];
123
- export type RuntimeTokenCapability = (typeof RUNTIME_TOKEN_CAPABILITIES)[number];
122
+ export declare const RUNTIME_TOKEN_OPERATIONS: readonly ["gateway_verification", "metering", "health", "tunnel", "drift_report"];
123
+ export type RuntimeTokenOperation = (typeof RUNTIME_TOKEN_OPERATIONS)[number];
124
124
  export declare const RUNTIME_HEADER_NAMES: {
125
125
  readonly signature: "x-fs-signature";
126
126
  readonly keyId: "x-fs-key-id";
@@ -133,11 +133,6 @@ export declare const RUNTIME_HEADER_NAMES: {
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;
@@ -171,6 +166,14 @@ export type CanonicalSigningInput = {
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";
@@ -234,7 +237,7 @@ export type RuntimeBootstrapResponse = {
234
237
  id: string | null;
235
238
  kind: RuntimeEnvironmentKind;
236
239
  };
237
- capabilities: RuntimeTokenCapability[];
240
+ operations: RuntimeTokenOperation[];
238
241
  verification: RuntimeVerificationConfig;
239
242
  metering: RuntimeMeteringConfig;
240
243
  transport: RuntimeTransportConfig;
@@ -270,8 +273,6 @@ export type RuntimePostStreamUsageEvent = {
270
273
  measureContext?: Record<string, unknown>;
271
274
  signature: string;
272
275
  };
273
- export declare const RUNTIME_READINESS_STATES: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
274
- export type RuntimeReadinessState = (typeof RUNTIME_READINESS_STATES)[number];
275
276
  export type RuntimeHealthReport = {
276
277
  runtimeToken: boolean;
277
278
  bootstrap: boolean;
@@ -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
  };
@@ -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.15.0",
3
+ "version": "0.17.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,16 +30,17 @@
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"
37
38
  },
38
39
  "optionalDependencies": {
39
40
  "@farthershore/cloudflared-linux-x64": "0.0.0",
40
- "@farthershore/cloudflared-darwin-arm64": "0.0.0",
41
41
  "@farthershore/cloudflared-linux-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"