@farthershore/backend 0.16.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to the runtime backend SDK are documented here. This SDK
4
4
  versions independently from the frontend and business SDKs. Pre-1.0: minor
5
5
  versions may include breaking changes.
6
6
 
7
+ ## [0.17.0]
8
+
9
+ ### Changed — BREAKING
10
+
11
+ - `RUNTIME_TOKEN_CAPABILITIES` is renamed `RUNTIME_TOKEN_OPERATIONS`, and the
12
+ type `RuntimeTokenCapability` is renamed `RuntimeTokenOperation`. The values
13
+ are operations, not capabilities, and the old names contradicted the runtime
14
+ contract they mirror. Update imports; no behaviour change.
15
+
16
+ ### Added
17
+
18
+ - `credentialKind()` and `isPortalSession()` — route-surface credential-kind
19
+ derivation from the signed principal. No new claim is required; both are
20
+ derived from what the gateway already signs.
21
+
7
22
  ## [0.16.0]
8
23
 
9
24
  Consumer-principal runtime. Every verified request now carries a typed
package/README.md CHANGED
@@ -12,7 +12,7 @@ graceful lifecycle (health + shutdown). Everything else — your business, backe
12
12
  and environment ids, the verification keys, and the metering endpoint — is
13
13
  fetched automatically from the token at startup.
14
14
 
15
- > **Status: `0.16.0`.** Pre-1.0: minor releases may include breaking changes, so
15
+ > **Status: `0.17.0`.** Pre-1.0: minor releases may include breaking changes, so
16
16
  > pin this package to an exact version (or a patch-only range) and upgrade
17
17
  > deliberately.
18
18
 
@@ -18,7 +18,9 @@ var FartherShoreError = class extends Error {
18
18
  }
19
19
  };
20
20
  function statusForCode(code) {
21
- return code === "body_too_large" ? 413 : 401;
21
+ if (code === "body_too_large") return 413;
22
+ if (code === "surface_not_allowed") return 403;
23
+ return 401;
22
24
  }
23
25
 
24
26
  // src/core/permissions.ts
@@ -166,7 +166,8 @@ var RUNTIME_ERROR_CODES = {
166
166
  invalidToken: "invalid_token",
167
167
  contextUnverified: "context_unverified",
168
168
  memberSubjectRequired: "member_subject_required",
169
- serviceSubjectRequired: "service_subject_required"
169
+ serviceSubjectRequired: "service_subject_required",
170
+ surfaceNotAllowed: "surface_not_allowed"
170
171
  };
171
172
  var RUNTIME_METERING_CONTRACT = {
172
173
  endpoint: "/v1/metering/events",
package/dist/index.js CHANGED
@@ -217,7 +217,8 @@ var RUNTIME_ERROR_CODES = {
217
217
  invalidToken: "invalid_token",
218
218
  contextUnverified: "context_unverified",
219
219
  memberSubjectRequired: "member_subject_required",
220
- serviceSubjectRequired: "service_subject_required"
220
+ serviceSubjectRequired: "service_subject_required",
221
+ surfaceNotAllowed: "surface_not_allowed"
221
222
  };
222
223
  var RUNTIME_RESPONSE_METERING_CONTRACT = {
223
224
  headers: {
@@ -284,7 +285,9 @@ var RUNTIME_ERROR_CODE_TO_ERROR_CODE = {
284
285
  [RUNTIME_ERROR_CODES.bodyTooLarge]: "VALIDATION_ERROR",
285
286
  // Consumer-principal wave — route subject-requirement faults → FORBIDDEN (403).
286
287
  [RUNTIME_ERROR_CODES.memberSubjectRequired]: "FORBIDDEN",
287
- [RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN"
288
+ [RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN",
289
+ // A visible route whose surface set excludes the caller → 403.
290
+ [RUNTIME_ERROR_CODES.surfaceNotAllowed]: "FORBIDDEN"
288
291
  };
289
292
  function runtimeErrorToErrorCode(code) {
290
293
  return RUNTIME_ERROR_CODE_TO_ERROR_CODE[code] ?? "INTERNAL_ERROR";
@@ -294,16 +297,16 @@ var RUNTIME_TOKEN_PREFIXES = {
294
297
  live: "fsrt_live_",
295
298
  test: "fsrt_test_"
296
299
  };
297
- var RUNTIME_TOKEN_CAPABILITIES = [
300
+ var RUNTIME_TOKEN_OPERATIONS = [
298
301
  "gateway_verification",
299
302
  "metering",
300
303
  "health",
301
304
  "tunnel",
302
- // Hand-maintained mirror of @farthershore/contracts RUNTIME_TOKEN_CAPABILITIES
305
+ // Hand-maintained mirror of @farthershore/contracts RUNTIME_TOKEN_OPERATIONS
303
306
  // (`runtime.ts`). Bound to that source by the SET-EQUALITY + ORDER assertions
304
307
  // in `deny-taxonomy-drift.test.ts` (test-only contracts devDep) — NOT by the
305
308
  // generated runtime-contract.ts, which mirrors only RUNTIME_ERROR_CODES.
306
- // `drift_report` is the opt-in capability for reporting route drift.
309
+ // `drift_report` is the opt-in operation for reporting route drift.
307
310
  "drift_report"
308
311
  ];
309
312
  var RUNTIME_HEADER_NAMES = {
@@ -457,7 +460,9 @@ var FartherShoreError = class extends Error {
457
460
  }
458
461
  };
459
462
  function statusForCode(code) {
460
- return code === "body_too_large" ? 413 : 401;
463
+ if (code === "body_too_large") return 413;
464
+ if (code === "surface_not_allowed") return 403;
465
+ return 401;
461
466
  }
462
467
 
463
468
  // src/core/bootstrap.ts
@@ -1913,8 +1918,7 @@ function headerGetter(headers) {
1913
1918
 
1914
1919
  // src/core/runtime.ts
1915
1920
  var DEFAULT_CORE_URL = "https://core.farthershore.com";
1916
- var SDK_VERSION = "0.16.0".length > 0 ? "0.16.0" : "0.0.0-dev";
1917
- var CONTRACTS_FP = "c3961d4ea07ff178".length > 0 ? "c3961d4ea07ff178" : "0000000000000000";
1921
+ var SDK_VERSION = "0.17.0".length > 0 ? "0.17.0" : "0.0.0-dev";
1918
1922
  var FartherShore = class {
1919
1923
  bootstrapClient;
1920
1924
  fetchImpl;
@@ -2378,6 +2382,16 @@ function requireService(ctx) {
2378
2382
  }
2379
2383
  return subject;
2380
2384
  }
2385
+ function credentialKind(ctx) {
2386
+ const subject = ctx.principal?.subject;
2387
+ if (!subject) return void 0;
2388
+ if (subject.kind === "service") return "api_key";
2389
+ return subject.via === "session" ? "portal_session" : "api_key";
2390
+ }
2391
+ function isPortalSession(ctx) {
2392
+ const kind = credentialKind(ctx);
2393
+ return kind === void 0 ? void 0 : kind === "portal_session";
2394
+ }
2381
2395
 
2382
2396
  // src/testing/signers.ts
2383
2397
  import { generateKeyPairSync, randomBytes } from "node:crypto";
@@ -2652,7 +2666,7 @@ function createDevGateway(options) {
2652
2666
  name: "Dev Backend"
2653
2667
  },
2654
2668
  environment: { id: null, kind: "test" },
2655
- capabilities: ["gateway_verification", "metering", "health"],
2669
+ operations: ["gateway_verification", "metering", "health"],
2656
2670
  verification: {
2657
2671
  required: options.mode === "simulated",
2658
2672
  jwksUrl: DEV_JWKS_URL,
@@ -3185,7 +3199,7 @@ export {
3185
3199
  RUNTIME_ERROR_CODE_TO_ERROR_CODE,
3186
3200
  RUNTIME_HEADER_NAMES,
3187
3201
  RUNTIME_REPLAY_WINDOW_SECONDS,
3188
- RUNTIME_TOKEN_CAPABILITIES,
3202
+ RUNTIME_TOKEN_OPERATIONS,
3189
3203
  RUNTIME_TOKEN_PREFIXES,
3190
3204
  STREAMING_EXEMPT_BODY_HASH,
3191
3205
  ShutdownManager,
@@ -3196,11 +3210,13 @@ export {
3196
3210
  createExpressHandler,
3197
3211
  createExpressMiddleware,
3198
3212
  createUsage,
3213
+ credentialKind,
3199
3214
  decodeContextClaims,
3200
3215
  fartherShore,
3201
3216
  hasPermission,
3202
3217
  hashBody2 as hashBody,
3203
3218
  initFromEnv2 as initFromEnv,
3219
+ isPortalSession,
3204
3220
  nodeSpawn,
3205
3221
  permissionGrants,
3206
3222
  permissionSatisfies,
@@ -220,7 +220,8 @@ var RUNTIME_ERROR_CODES = {
220
220
  invalidToken: "invalid_token",
221
221
  contextUnverified: "context_unverified",
222
222
  memberSubjectRequired: "member_subject_required",
223
- serviceSubjectRequired: "service_subject_required"
223
+ serviceSubjectRequired: "service_subject_required",
224
+ surfaceNotAllowed: "surface_not_allowed"
224
225
  };
225
226
  var RUNTIME_RESPONSE_METERING_CONTRACT = {
226
227
  headers: {
@@ -287,7 +288,9 @@ var RUNTIME_ERROR_CODE_TO_ERROR_CODE = {
287
288
  [RUNTIME_ERROR_CODES.bodyTooLarge]: "VALIDATION_ERROR",
288
289
  // Consumer-principal wave — route subject-requirement faults → FORBIDDEN (403).
289
290
  [RUNTIME_ERROR_CODES.memberSubjectRequired]: "FORBIDDEN",
290
- [RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN"
291
+ [RUNTIME_ERROR_CODES.serviceSubjectRequired]: "FORBIDDEN",
292
+ // A visible route whose surface set excludes the caller → 403.
293
+ [RUNTIME_ERROR_CODES.surfaceNotAllowed]: "FORBIDDEN"
291
294
  };
292
295
  var FS_RUNTIME_TOKEN_ENV = "FS_RUNTIME_TOKEN";
293
296
  var RUNTIME_TOKEN_PREFIXES = {
@@ -444,7 +447,9 @@ var FartherShoreError = class extends Error {
444
447
  }
445
448
  };
446
449
  function statusForCode(code) {
447
- return code === "body_too_large" ? 413 : 401;
450
+ if (code === "body_too_large") return 413;
451
+ if (code === "surface_not_allowed") return 403;
452
+ return 401;
448
453
  }
449
454
 
450
455
  // src/core/jwks.ts
@@ -868,7 +873,7 @@ function createDevGateway(options) {
868
873
  name: "Dev Backend"
869
874
  },
870
875
  environment: { id: null, kind: "test" },
871
- capabilities: ["gateway_verification", "metering", "health"],
876
+ operations: ["gateway_verification", "metering", "health"],
872
877
  verification: {
873
878
  required: options.mode === "simulated",
874
879
  jwksUrl: DEV_JWKS_URL,
@@ -2144,8 +2149,7 @@ function headerGetter(headers) {
2144
2149
 
2145
2150
  // src/core/runtime.ts
2146
2151
  var DEFAULT_CORE_URL = "https://core.farthershore.com";
2147
- var SDK_VERSION = "0.16.0".length > 0 ? "0.16.0" : "0.0.0-dev";
2148
- var CONTRACTS_FP = "c3961d4ea07ff178".length > 0 ? "c3961d4ea07ff178" : "0000000000000000";
2152
+ var SDK_VERSION = "0.17.0".length > 0 ? "0.17.0" : "0.0.0-dev";
2149
2153
  var FartherShore = class {
2150
2154
  bootstrapClient;
2151
2155
  fetchImpl;
@@ -21,6 +21,9 @@ export declare class FartherShoreError extends Error {
21
21
  }
22
22
  /**
23
23
  * Map a runtime error code to its fail-closed HTTP status. Oversized bodies are
24
- * the only non-401 (413); all other verification failures are 401.
24
+ * 413; a wrong-credential-surface denial is 403 (the caller IS authenticated,
25
+ * just not on a surface this route admits — mirrors the canonical
26
+ * `surface_not_allowed → FORBIDDEN` mapping in contracts/error-codes.ts);
27
+ * all other verification failures are 401.
25
28
  */
26
29
  export declare function statusForCode(code: RuntimeErrorCode): number;
@@ -69,7 +69,6 @@ export type FartherShoreInitOptions = {
69
69
  nonceStore?: NonceStore;
70
70
  };
71
71
  export declare const SDK_VERSION: string;
72
- export declare const CONTRACTS_FP: string;
73
72
  /**
74
73
  * The runtime instance. Lazily bootstraps; holds the JWKS client, nonce cache,
75
74
  * metering buffer, and shutdown hooks.
@@ -23,3 +23,23 @@ export declare function requireMember(ctx: PrincipalCarrier): MemberSubject;
23
23
  * Throws `service_subject_required` when the subject is a member (or absent).
24
24
  */
25
25
  export declare function requireService(ctx: PrincipalCarrier): ServiceSubject;
26
+ /**
27
+ * The credential SURFACE behind a verified request, DERIVED from the signed
28
+ * principal (route-surfaces wave) — no new claim, no spoofable header. A member
29
+ * subject carries `via`; a service subject is always key-borne. Returns
30
+ * `undefined` when the request carried no verified principal (identity-less), so
31
+ * a caller can distinguish "not a portal session" from "unknown".
32
+ *
33
+ * - `"portal_session"` ⟺ a member via a browser session (`fsc_`).
34
+ * - `"api_key"` ⟺ a member's personal key OR any service key (`fsk_`).
35
+ */
36
+ export declare function credentialKind(ctx: PrincipalCarrier): "portal_session" | "api_key" | undefined;
37
+ /**
38
+ * True when the verified request came from the managed portal UI (a member
39
+ * browser session), false when it came from an API key, and `undefined` when
40
+ * there is no verified principal. Convenience over {@link credentialKind} for
41
+ * the common portal-vs-API branch (e.g. richer UI payloads for portal callers).
42
+ * The gateway's `enforce-surface` middleware is the SECURITY boundary; this is
43
+ * for in-handler ergonomics.
44
+ */
45
+ export declare function isPortalSession(ctx: PrincipalCarrier): boolean | undefined;
@@ -140,6 +140,7 @@ export declare const RUNTIME_ERROR_CODES: {
140
140
  readonly contextUnverified: "context_unverified";
141
141
  readonly memberSubjectRequired: "member_subject_required";
142
142
  readonly serviceSubjectRequired: "service_subject_required";
143
+ readonly surfaceNotAllowed: "surface_not_allowed";
143
144
  };
144
145
  export type RuntimeErrorCode = (typeof RUNTIME_ERROR_CODES)[keyof typeof RUNTIME_ERROR_CODES];
145
146
  export declare const RUNTIME_METERING_CONTRACT: {
@@ -6,7 +6,7 @@ export { FartherShoreError, statusForCode } from "./core/errors.js";
6
6
  export { verifyRequest, type VerifyRequestInput, type VerifyRequestDeps, type FartherShoreRequestContext, type HeadersLike, } from "./core/verifyRequest.js";
7
7
  export { verifyContext, decodeContextClaims, principalFromContextClaims, } from "./core/verifyContext.js";
8
8
  export type { FartherShoreSignedContext, ConsumerPrincipal, } from "./core/verifyContext.js";
9
- export { requireMember, requireService, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
9
+ export { requireMember, requireService, credentialKind, isPortalSession, type MemberSubject, type ServiceSubject, type PrincipalCarrier, } from "./core/subject.js";
10
10
  export { hasPermission, requirePermission, permissionGrants, permissionSatisfies, FartherShorePermissionError, type PermissionCarrier, } from "./core/permissions.js";
11
11
  export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
12
12
  export { NonceCache, type NonceCacheOptions, type NonceStore, } from "./core/nonceCache.js";
@@ -18,7 +18,7 @@ export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
18
18
  export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
19
19
  export type { FartherShoreTunnelOptions } from "./core/runtime.js";
20
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_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
+ 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";
22
22
  export { RUNTIME_ERROR_CODES } from "./generated/runtime-contract.js";
23
23
  export { hashBody, buildCanonicalSigningString, canonicalizeQuery, signCanonicalString, verifyCanonicalSignature, runtimeTokenKind, } from "./runtime-signing.js";
24
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";
@@ -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";
@@ -237,7 +237,7 @@ export type RuntimeBootstrapResponse = {
237
237
  id: string | null;
238
238
  kind: RuntimeEnvironmentKind;
239
239
  };
240
- capabilities: RuntimeTokenCapability[];
240
+ operations: RuntimeTokenOperation[];
241
241
  verification: RuntimeVerificationConfig;
242
242
  metering: RuntimeMeteringConfig;
243
243
  transport: RuntimeTransportConfig;
@@ -273,8 +273,6 @@ export type RuntimePostStreamUsageEvent = {
273
273
  measureContext?: Record<string, unknown>;
274
274
  signature: string;
275
275
  };
276
- export declare const RUNTIME_READINESS_STATES: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
277
- export type RuntimeReadinessState = (typeof RUNTIME_READINESS_STATES)[number];
278
276
  export type RuntimeHealthReport = {
279
277
  runtimeToken: boolean;
280
278
  bootstrap: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farthershore/backend",
3
- "version": "0.16.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",