@farthershore/backend 0.20.0 → 0.21.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,6 +1,6 @@
1
1
  import type { FartherShore } from "../core/runtime.js";
2
2
  import type { FartherShoreRequestContext } from "../core/verifyRequest.js";
3
- import type { ConsumerPrincipal } from "../core/verifyContext.js";
3
+ import type { ConsumerPrincipal, FartherShoreSignedContext } from "../core/verifyContext.js";
4
4
  /** Minimal Express-shaped types so we don't hard-depend on @types/express. */
5
5
  export type ExpressRequestLike = {
6
6
  method: string;
@@ -18,6 +18,13 @@ export type ExpressResponseLike = {
18
18
  status(code: number): ExpressResponseLike;
19
19
  json(body: unknown): unknown;
20
20
  setHeader(name: string, value: string): void;
21
+ /**
22
+ * Node's `ServerResponse.headersSent`. This is what makes the reporting verb's
23
+ * transport choice AUTOMATIC: while it is false the measurement rides signed
24
+ * response headers (no network call); once the response is on the wire
25
+ * `ctx.report()` transparently switches to the post-stream channel.
26
+ */
27
+ headersSent?: boolean;
21
28
  };
22
29
  export type ExpressNext = (err?: unknown) => void;
23
30
  export type ExpressMiddleware = (req: ExpressRequestLike, res: ExpressResponseLike, next: ExpressNext) => void;
@@ -69,14 +76,27 @@ export type MiddlewareOptions = {
69
76
  */
70
77
  export type VerifiedPrincipalContext = FartherShoreRequestContext & {
71
78
  principal: ConsumerPrincipal;
79
+ signedContext: FartherShoreSignedContext;
72
80
  };
73
81
  /**
74
82
  * A route handler that runs only with a GUARANTEED verified PRINCIPAL. The first
75
- * argument is the {@link VerifiedPrincipalContext} — read `ctx.principal` (and
76
- * narrow with `requireMember`/`requireService`) without any optional-chaining.
83
+ * argument is the {@link VerifiedPrincipalContext} — read `ctx.principal` and
84
+ * `ctx.signedContext` (and narrow with `requireMember`/`requireService`)
85
+ * without any optional-chaining.
77
86
  * See {@link createExpressHandler}.
78
87
  */
79
88
  export type VerifiedExpressHandler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike> = (ctx: VerifiedPrincipalContext, req: Req, res: Res, next: ExpressNext) => void | Promise<void>;
89
+ /** Options for the `fs.handler(options, cb)` overload. */
90
+ export type HandlerOptions = {
91
+ /**
92
+ * Permission key the verified principal must hold (unified grammar:
93
+ * `*` / `<subject>:*` / exact — custom strings work). Checked with the
94
+ * FAIL-CLOSED carrier gate (`requirePermission`): an ABSENT permission set
95
+ * denies. On failure the wrapper responds `403 { error: "permission_denied" }`
96
+ * before the callback runs.
97
+ */
98
+ permission?: string;
99
+ };
80
100
  /**
81
101
  * Build the Express middleware. Captures raw body bytes, calls verifyRequest,
82
102
  * and fail-closes on any error.
@@ -106,3 +126,12 @@ export declare function createExpressMiddleware(fs: FartherShore, options?: Midd
106
126
  * non-optional guarantee AND Express compatibility.
107
127
  */
108
128
  export declare function createExpressHandler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
129
+ /**
130
+ * Options-first overload: `fs.handler({ permission: "widgets:write" }, cb)`
131
+ * asserts the verified principal holds `permission` (via the fail-closed
132
+ * carrier gate — an absent permission set DENIES) BEFORE the callback runs,
133
+ * responding `403 permission_denied` otherwise. The key is checked with the
134
+ * unified grammar (`*` / `<subject>:*` / exact); derive route-shaped keys with
135
+ * `routePermission(subject, method)`.
136
+ */
137
+ export declare function createExpressHandler<Req extends ExpressRequestLike = ExpressRequestLike, Res extends ExpressResponseLike = ExpressResponseLike>(options: HandlerOptions, handler: VerifiedExpressHandler<Req, Res>): ExpressMiddleware;
@@ -13,21 +13,33 @@ export declare class FartherShorePermissionError extends Error {
13
13
  constructor(requiredPermission: string, message?: string);
14
14
  }
15
15
  /**
16
- * Pure grant check a FAITHFUL COPY of the canonical `permissionGrants` in
17
- * `@farthershore/contracts` (`rbac.ts`). Over a DEFINED array: a `"*"` entry
18
- * grants everything, otherwise the key must be an exact member. Its
19
- * `undefined → true` codomain is the PRIMITIVE's contract (kept byte-identical
20
- * to contracts for parity); it is NOT the SDK's carrier policy. Under FAR-723
21
- * the carrier gate {@link hasPermission} DENIES an absent permission set before
22
- * this primitive is consulted, so absence never grants at the boundary.
16
+ * HTTP verbs that classify as `:read`. Everything else including the
17
+ * route-catalog wildcard `*` classifies as `:write`.
23
18
  *
24
- * The published SDK surface must stay contracts-free, so it can't import the
25
- * canonical primitive. A TEST-ONLY parity guard (`permissions-parity.test.ts`,
26
- * which CAN import contracts as a devDependency) asserts this copy agrees with
27
- * the canonical primitive across a shared golden vector table, so the copy
28
- * can't silently drift.
19
+ * FAITHFUL COPY of the canonical `READ_METHODS` in the permissions kernel
20
+ * (`@farthershore/authz/grammar/route`, re-exported by
21
+ * `@farthershore/contracts/rbac`); the published bundle is contracts-free, so
22
+ * `permissions-parity.test.ts` asserts agreement and the full corpus replay
23
+ * lives in packages/authz/test-node/sdk-copy-parity.test.ts.
29
24
  */
30
- export declare function permissionGrants(permissions: readonly string[] | undefined, key: string): boolean;
25
+ export declare const READ_METHODS: ReadonlySet<string>;
26
+ /**
27
+ * Derive the permission string a credential must hold to call a route:
28
+ * `<subject>:read` for safe verbs (GET / HEAD / OPTIONS, any casing),
29
+ * `<subject>:write` for every other method INCLUDING the route-catalog
30
+ * wildcard `*`. Use it to build in-handler permission keys from the same
31
+ * grammar the edge `permission` constraint enforces — never re-spell the
32
+ * `:read`/`:write` suffix locally.
33
+ *
34
+ * FAITHFUL COPY of the canonical `routePermission` in the permissions kernel
35
+ * (`@farthershore/authz/grammar/route`, re-exported by
36
+ * `@farthershore/contracts/rbac`); parity asserted as for {@link READ_METHODS}.
37
+ *
38
+ * @param subject the matched route's permission subject (used verbatim)
39
+ * @param method the REQUEST method (case-insensitive) or a route-catalog
40
+ * method entry (`*` → `:write`)
41
+ */
42
+ export declare function routePermission(subject: string, method: string): string;
31
43
  /**
32
44
  * Whether the granted `permissions` satisfy the required `key` under the
33
45
  * unified grammar: `"*"` (global), `"<subject>:*"` (subject wildcard), or the
@@ -5,10 +5,23 @@ export type ReportUsageInput = {
5
5
  meters: Record<string, number>;
6
6
  creditUnitsConsumed?: Record<string, number>;
7
7
  measureContext?: Record<string, unknown>;
8
- };
9
- export type RequestScopedReportUsageInput = Omit<ReportUsageInput, "requestId" | "subscriptionId"> & {
10
- requestId?: string;
11
- subscriptionId?: string;
8
+ /** Schema version of {@link measurements}; currently `1`. */
9
+ measurementsVersion?: number;
10
+ /**
11
+ * The measurement lane (`{ meter, values, dims }`) — the authoritative rating
12
+ * input `ctx.report()` emits. Additive over `meters`, which remains the flat
13
+ * projection core's existing settlement path reads.
14
+ */
15
+ measurements?: {
16
+ meter: string;
17
+ values: Record<string, number>;
18
+ dims?: Record<string, string>;
19
+ }[];
20
+ /** Proposed rate input for a `backendQuoted` pricing rule (core clamps it). */
21
+ quote?: {
22
+ currency: string;
23
+ amountNanos: string;
24
+ };
12
25
  };
13
26
  export type ReportUsageResult = {
14
27
  ok: true;
@@ -45,5 +58,7 @@ export declare class PostStreamUsageClient {
45
58
  private readonly maxRetryDelayMs;
46
59
  constructor(options: PostStreamUsageClientOptions);
47
60
  reportUsage(input: ReportUsageInput): Promise<ReportUsageResult>;
61
+ /** Enforce the token's meter scope + per-event bounds on the measurement lane. */
62
+ private validateMeasurements;
48
63
  private retryDelayForAttempt;
49
64
  }
@@ -0,0 +1,133 @@
1
+ import { type ResponseMeteringUsagePayload } from "../response-metering.js";
2
+ /** Observed measurement values, keyed by measure key. */
3
+ export type MeasurementValues = Record<string, number>;
4
+ /** Catalog selectors the measurement was produced under, keyed by dimension. */
5
+ export type MeasurementDimensions = Record<string, string>;
6
+ /**
7
+ * The validated, transmitted quote — a PROPOSED rate input, never a charge.
8
+ * `amountNanos` is a decimal integer string of nanodollars **per unit of the
9
+ * entry's measure** (the platform's money unit is the nanodollar); core
10
+ * multiplies it by the measured quantity, clamps it into the pricing policy's
11
+ * declared per-unit `{min,max}`, and flags out-of-range proposals for dispute.
12
+ * Never send a total.
13
+ */
14
+ export type QuoteProposal = {
15
+ currency: string;
16
+ amountNanos: string;
17
+ };
18
+ /**
19
+ * Authoring shape of {@link QuoteProposal}. `amountNanos` (nanodollars PER
20
+ * UNIT of the entry's measure) accepts a number, bigint, or decimal integer
21
+ * string; anything else is rejected.
22
+ */
23
+ export type QuoteInput = {
24
+ currency: string;
25
+ amountNanos: number | bigint | string;
26
+ };
27
+ /** One measurement report. The complete argument surface of the verb. */
28
+ export type ReportInput = {
29
+ /** Meter key as declared in the business release (plain string at the wire). */
30
+ meter: string;
31
+ /** Observed values keyed by measure key, e.g. `{ input_tokens: 1200 }`. */
32
+ values: MeasurementValues;
33
+ /** Catalog selectors, e.g. `{ model: "acme-4", cache_status: "hit" }`. */
34
+ dims?: MeasurementDimensions;
35
+ /**
36
+ * OPTIONAL money proposal for a `backendQuoted` pricing rule: a PER-UNIT
37
+ * rate in nanodollars (multiplied by the measured quantity — never a
38
+ * total). Opaque at the authoring boundary (a job result carries it through
39
+ * untyped); validated against {@link QuoteInput} here and transmitted as a
40
+ * {@link QuoteProposal}. Applies to every backend-quoted component of this
41
+ * report; ignored by rules that are not backend-quoted.
42
+ */
43
+ quote?: unknown;
44
+ };
45
+ /** Which channel actually carried the measurement. */
46
+ export type ReportTransport = "in_band" | "post_stream";
47
+ /**
48
+ * Delivery outcome. Validation faults THROW (a malformed report is a builder
49
+ * bug worth surfacing); delivery faults resolve `ok: false` so a metering
50
+ * hiccup never breaks the builder's endpoint. A served request may own only
51
+ * one post-stream callback identity, so later calls fail explicitly.
52
+ */
53
+ export type ReportResult = {
54
+ ok: true;
55
+ transport: ReportTransport;
56
+ } | {
57
+ ok: false;
58
+ transport: ReportTransport;
59
+ reason: string;
60
+ };
61
+ /** The wire shape of one validated measurement. */
62
+ export type Measurement = {
63
+ meter: string;
64
+ values: MeasurementValues;
65
+ dims?: MeasurementDimensions;
66
+ };
67
+ /** Version of the `measurements` payload lane (additive over `rawDimsUnits`). */
68
+ export declare const MEASUREMENTS_VERSION = 1;
69
+ /** The transport seam a host adapter supplies for the in-band lane. */
70
+ export type ResponseSink = {
71
+ /** True while headers can still be stamped onto the outgoing response. */
72
+ canStampHeaders(): boolean;
73
+ /** Stamp the signed metering headers onto the outgoing response. */
74
+ stampHeaders(headers: Record<string, string>): void;
75
+ };
76
+ /** Everything `report()` needs that only the runtime can provide. */
77
+ export type ReportChannels = {
78
+ /** Sign the payload into `x-fs-metering*` headers (may resolve `{}`). */
79
+ computeHeaders(payload: ResponseMeteringUsagePayload): Promise<Record<string, string>>;
80
+ /** Deliver over the attested post-stream channel. */
81
+ postStream(input: {
82
+ measurements: Measurement[];
83
+ quote?: QuoteProposal;
84
+ }): Promise<{
85
+ ok: boolean;
86
+ reason?: string;
87
+ }>;
88
+ /** Method/path of the served request, for the in-band payload binding. */
89
+ request?: {
90
+ method: string;
91
+ path: string;
92
+ };
93
+ /** Host-supplied response seam; absent ⇒ the post-stream lane is used. */
94
+ responseSink?: ResponseSink;
95
+ };
96
+ /**
97
+ * `report()` accepts one measurement or an ARRAY of measurements. The array
98
+ * form is the sanctioned way to report several meters after the response is
99
+ * sent: one served request owns exactly ONE post-stream callback identity, so
100
+ * sequential awaited single reports after the first flush cannot be delivered
101
+ * — a batch rides the single callback atomically. All entries of a batch
102
+ * share one quote (at most one distinct quote may be supplied).
103
+ */
104
+ export type ReportFn = (input: ReportInput | readonly ReportInput[]) => Promise<ReportResult>;
105
+ /**
106
+ * Build the bound `report()` verb for a verified context. The returned function
107
+ * validates, picks its own transport, and never asks the caller for identity.
108
+ */
109
+ export declare function createReportFn(channels: ReportChannels): ReportFn;
110
+ /**
111
+ * The `report()` stub attached to a context produced by the BARE
112
+ * `verifyRequest()` primitive (no runtime, therefore no metering channel). It
113
+ * throws an error that names the fix instead of pretending the measurement was
114
+ * delivered.
115
+ */
116
+ export declare function unattachedReport(): ReportFn;
117
+ /**
118
+ * Project a measurement into the flat `rawDimsUnits` lane the gateway's
119
+ * in-band settlement path consumes.
120
+ *
121
+ * PROJECTION RULE (P0-2): the projection keys the METER id, and its scalar is
122
+ * the sum of the measurement's measure values — the meter's structural
123
+ * quantity. The gateway masks `rawDimsUnits` by the matched route's declared
124
+ * METER ids (`maskCostMapByMeters`), so measure-keyed entries would be
125
+ * silently discarded at the edge (UNBILLED). Per-measure detail is NOT lost:
126
+ * the full `{ meter, values, dims }` measurement rides alongside in the
127
+ * versioned `measurements` lane, which is the authoritative rating input.
128
+ */
129
+ export declare function rawDimsUnitsOf(measurement: Measurement): Record<string, number>;
130
+ /** Validate one report input into its wire {@link Measurement}. */
131
+ export declare function validateMeasurement(input: ReportInput): Measurement;
132
+ /** Validate an opaque authored quote into its wire {@link QuoteProposal}. */
133
+ export declare function validateQuote(quote: unknown): QuoteProposal;
@@ -1,7 +1,6 @@
1
1
  import { type RuntimeBootstrapResponse, type RuntimeHealthReport } from "../runtime-types.js";
2
2
  import type { ReconcileResult } from "../reflect/reconcile.js";
3
- import { type MeterOptions } from "./metering.js";
4
- import { type ReportUsageInput, type ReportUsageResult } from "./post-stream-usage.js";
3
+ import { type ResponseSink } from "./report.js";
5
4
  import { type NonceStore } from "./nonceCache.js";
6
5
  import { type ReplayProtectionDiagnostic } from "./replay-protection.js";
7
6
  import { type SpawnFn } from "./tunnel.js";
@@ -22,10 +21,7 @@ export type FartherShoreTunnelOptions = {
22
21
  export type FartherShoreInitOptions = {
23
22
  /** Explicit runtime token. Defaults to process.env.FS_RUNTIME_TOKEN. */
24
23
  runtimeToken?: string;
25
- /**
26
- * Core base URL. Defaults to FS_CORE_URL / FARTHERSHORE_CORE_URL or
27
- * https://core.farthershore.com.
28
- */
24
+ /** Core base URL. Defaults to FS_CORE_URL or https://core.farthershore.com. */
29
25
  coreUrl?: string;
30
26
  /** Env map (tests). Defaults to process.env. */
31
27
  env?: Record<string, string | undefined>;
@@ -71,6 +67,15 @@ export type FartherShoreInitOptions = {
71
67
  */
72
68
  nonceStore?: NonceStore;
73
69
  };
70
+ /**
71
+ * Host-adapter seams for `verifyRequest`. An adapter that owns the outgoing
72
+ * response (the Express middleware) supplies a {@link ResponseSink} so
73
+ * `ctx.report()` can choose the zero-network in-band header transport while the
74
+ * response is still open. Without one, reports take the post-stream channel.
75
+ */
76
+ export type VerifyRequestHostOptions = {
77
+ responseSink?: ResponseSink;
78
+ };
74
79
  export declare const SDK_VERSION: string;
75
80
  /**
76
81
  * The runtime instance. Lazily bootstraps; holds the JWKS client, nonce cache,
@@ -91,7 +96,6 @@ export declare class FartherShore {
91
96
  private readonly replayProtectionDiagnostic;
92
97
  private readonly shutdownManager;
93
98
  private jwks;
94
- private meteringClient;
95
99
  private postStreamUsageClient;
96
100
  private tunnel;
97
101
  private bootstrapped;
@@ -119,7 +123,14 @@ export declare class FartherShore {
119
123
  * Framework-neutral verification primitive. Fail-closed: throws a typed
120
124
  * FartherShoreError on any verification failure. Returns the verified context.
121
125
  */
122
- verifyRequest(input: VerifyRequestInput): Promise<FartherShoreRequestContext>;
126
+ verifyRequest(input: VerifyRequestInput, options?: VerifyRequestHostOptions): Promise<FartherShoreRequestContext>;
127
+ /**
128
+ * Bind the ONE reporting verb to a verified context. Identity comes from the
129
+ * context (`signedContext.subscriptionId` + `requestId`) — never from the
130
+ * caller — so a handler cannot forget it, and a background job that is handed
131
+ * this context keeps reporting against the SAME served identity.
132
+ */
133
+ private buildReportFn;
123
134
  /** Whether verification is required (bootstrap × opt-out). */
124
135
  verificationRequired(): Promise<boolean>;
125
136
  /**
@@ -133,10 +144,12 @@ export declare class FartherShore {
133
144
  * (request verification stays fail-closed regardless — a different axis).
134
145
  */
135
146
  start(): Promise<void>;
136
- /** Record metering usage (billing-only). */
137
- meter(meter: string, qty: number, options?: MeterOptions): Promise<void>;
138
- /** Best-effort attested post-stream usage callback. Never rejects. */
139
- reportUsage(input: ReportUsageInput): Promise<ReportUsageResult>;
147
+ /**
148
+ * PRIVATE transport for the post-stream lane of `ctx.report()`. Never rejects
149
+ * a metering hiccup must not break a builder's endpoint. This is machinery,
150
+ * not surface: the ONE public reporting verb is `ctx.report()`.
151
+ */
152
+ private reportPostStreamUsage;
140
153
  /**
141
154
  * How far replay protection actually reaches — `"shared"` (enforced across
142
155
  * every replica) or `"single-instance"` (this process only). Deployment
@@ -1,4 +1,4 @@
1
- import type { ReportUsageResult, RequestScopedReportUsageInput } from "./post-stream-usage.js";
1
+ import { type ReportFn } from "./report.js";
2
2
  import { type ConsumerPrincipal, type FartherShoreSignedContext } from "./verifyContext.js";
3
3
  import type { JwksClient } from "./jwks.js";
4
4
  import type { NonceStore } from "./nonceCache.js";
@@ -60,9 +60,27 @@ export type FartherShoreRequestContext = {
60
60
  signedContext?: FartherShoreSignedContext;
61
61
  /** Managed-RBAC role keys the acting subject holds (display/audit only). */
62
62
  roles?: string[];
63
- /** Request-bound post-stream reporter, attached by the runtime facade. */
64
- reportUsage?: (input: RequestScopedReportUsageInput) => Promise<ReportUsageResult>;
63
+ /**
64
+ * THE reporting verb. `report({ meter, values, dims?, quote? })` reports
65
+ * MEASUREMENTS against the served identity this context already carries —
66
+ * there is no subscription/release argument to forget. The SDK picks the
67
+ * transport (signed in-band headers before the response is sent, the attested
68
+ * post-stream channel after it, or from a background job), so the builder
69
+ * never chooses one.
70
+ *
71
+ * Attached by the runtime facade. A context produced by the BARE
72
+ * `verifyRequest()` primitive has no metering channel, so its `report()`
73
+ * throws an error naming the fix rather than dropping the measurement.
74
+ */
75
+ report: ReportFn;
65
76
  };
77
+ /**
78
+ * The verified Farther Shore context — the identity + reporting handle a
79
+ * handler (or a background job the handler hands it to) works with. Alias of
80
+ * {@link FartherShoreRequestContext}: a background job is simply given the
81
+ * already-verified context, so there is no second context type to learn.
82
+ */
83
+ export type FartherShoreContext = FartherShoreRequestContext;
66
84
  export type VerifyRequestDeps = {
67
85
  jwks: JwksClient;
68
86
  nonceCache: NonceStore;
@@ -1,127 +1,3 @@
1
- export declare const RUNTIME_CONTRACT_VERSION: 1;
2
- export declare const RUNTIME_TOKEN_ENV: "FS_RUNTIME_TOKEN";
3
- export declare const RUNTIME_TOKEN_CONTRACT: {
4
- readonly environmentVariable: "FS_RUNTIME_TOKEN";
5
- readonly prefixes: {
6
- readonly live: "fsrt_live_";
7
- readonly test: "fsrt_test_";
8
- };
9
- readonly opaque: true;
10
- readonly storage: "sha256-hash-only";
11
- readonly lastFour: true;
12
- readonly capabilities: readonly ["gateway_verification", "metering", "health", "tunnel"];
13
- };
14
- export declare const RUNTIME_BOOTSTRAP_CONTRACT: {
15
- readonly method: "POST";
16
- readonly path: "/v1/runtime/bootstrap";
17
- readonly authorization: "Bearer fsrt_...";
18
- readonly request: {
19
- readonly instanceId: "string?";
20
- readonly sdkVersion: "string?";
21
- readonly sdkLanguage: "string?";
22
- };
23
- readonly response: {
24
- readonly product: {
25
- readonly id: "string";
26
- readonly slug: "string";
27
- };
28
- readonly backend: {
29
- readonly id: "string";
30
- readonly slug: "string";
31
- readonly name: "string";
32
- };
33
- readonly environment: {
34
- readonly id: "string?";
35
- readonly kind: "live | test";
36
- };
37
- readonly capabilities: "string[]";
38
- readonly verification: {
39
- readonly required: "boolean";
40
- readonly jwksUrl: "string";
41
- readonly clockSkewSeconds: "number";
42
- readonly replayWindowSeconds: "number";
43
- readonly headerNames: "object";
44
- };
45
- readonly metering: {
46
- readonly enabled: "boolean";
47
- readonly endpoint: "string";
48
- readonly credential: "string";
49
- readonly allowedMeters: "string[]";
50
- readonly allowedRoutes: "string[]";
51
- readonly perEventMax: "number";
52
- };
53
- readonly transport: {
54
- readonly mode: "direct | tunnel";
55
- readonly runner: "embedded | sidecar | null";
56
- readonly originUrl: "string?";
57
- readonly originHostname: "string?";
58
- readonly localTarget: "string?";
59
- readonly cloudflared: "object?";
60
- };
61
- readonly routes: "object[]";
62
- readonly policyVersion: "string";
63
- readonly refreshAfterSeconds: "number";
64
- };
65
- };
66
- export declare const RUNTIME_SIGNING_CONTRACT: {
67
- readonly algorithm: "Ed25519";
68
- readonly encoding: "base64url";
69
- readonly keyMaterial: "service-jwt-jwks";
70
- readonly canonicalString: {
71
- readonly description: "Byte-exact, language-neutral serialization of the signed claim set. Fields are emitted in the FIXED order below, one per line, each as `name:value`, joined by a single newline (\\n, U+000A). NO trailing newline. The serialization depends on neither JSON key ordering nor any Node.js Buffer/serialization detail — only UTF-8 byte encoding of the field values. Empty/absent values are emitted as the empty string after the colon. Go/Python/Java/Rust reproduce this identically.";
72
- readonly fieldSeparator: "\n";
73
- readonly keyValueSeparator: ":";
74
- readonly trailingNewline: false;
75
- readonly fieldEncoding: "utf-8";
76
- readonly fields: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "business-id", "backend-id", "route-id", "policy-version", "context-hash"];
77
- readonly fieldRules: {
78
- readonly method: "Uppercased HTTP method (e.g. GET, POST).";
79
- readonly path: "Request path, percent-encoded as received, no host, no query string. Always begins with '/'.";
80
- readonly query: "The request's query string bytes VERBATIM, after the caller has stripped the URL's single leading '?' delimiter. Use the RAW wire query exactly as received — NO sorting, NO filtering, NO re-stripping a leading '?', NO dropping empty pairs, NO re-encoding. The signature binds to the exact query bytes: any reorder, added/removed/relocated pair, or re-encoding changes the bytes and MUST fail verification. Every SDK verifier MUST read the raw wire query (never a re-parsed/re-serialized form, which could reorder or re-encode and would then fail legitimate requests). Any normalization (sorting by name or value, dropping/moving empty pairs, re-stripping '?') is FORBIDDEN — each collapses distinct wire queries under one signature. Empty string when there is no query.";
81
- readonly "body-hash": "Lowercase hex SHA-256 of the RAW request body bytes. For an empty body, the SHA-256 of zero bytes (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855). Streaming-exempt requests use the literal token 'STREAM'.";
82
- readonly "request-id": "Opaque unique request id minted by the gateway (also the replay-cache nonce).";
83
- readonly timestamp: "Integer Unix epoch seconds (UTC) at signing time, as a base-10 string with no padding.";
84
- readonly "business-id": "Business id the request is routed to.";
85
- readonly "backend-id": "Backend id the route binds to.";
86
- readonly "route-id": "Resolved route id; empty string if the route is unresolved.";
87
- readonly "policy-version": "Tenant artifact / policy version the gateway signed under.";
88
- readonly "context-hash": "Lowercase hex SHA-256 of the presented X-Fs-Context JWT string (identity-context binding). For a request with no signed context, the SHA-256 of the empty string (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855). Binds the identity context into the request signature so one verification covers both.";
89
- };
90
- };
91
- };
92
- /**
93
- * Ordered list of fields in the canonical signing string. The order here is
94
- * load-bearing and identical across all language SDKs.
95
- */
96
- export declare const RUNTIME_CANONICAL_FIELDS: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "business-id", "backend-id", "route-id", "policy-version", "context-hash"];
97
- export declare const RUNTIME_BODY_HASH_CONTRACT: {
98
- readonly algorithm: "SHA-256";
99
- readonly encoding: "hex-lower";
100
- readonly source: "raw-request-bytes";
101
- readonly emptyBodyHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
102
- readonly maxBodyBytes: 10485760;
103
- readonly streamingExemptToken: "STREAM";
104
- readonly streamingExemptContentTypes: readonly ["text/event-stream", "application/octet-stream", "multipart/form-data"];
105
- readonly overMaxStatus: 413;
106
- };
107
- export declare const RUNTIME_HEADERS: {
108
- readonly signature: "x-fs-signature";
109
- readonly keyId: "x-fs-key-id";
110
- readonly requestId: "x-fs-request-id";
111
- readonly timestamp: "x-fs-timestamp";
112
- readonly businessId: "x-fs-business-id";
113
- readonly backendId: "x-fs-backend-id";
114
- readonly routeId: "x-fs-route-id";
115
- readonly policyVersion: "x-fs-policy-version";
116
- readonly bodyHash: "x-fs-body-hash";
117
- };
118
- export declare const RUNTIME_REPLAY_CONTRACT: {
119
- readonly windowSeconds: 300;
120
- readonly clockSkewSeconds: 5;
121
- readonly nonce: "x-fs-request-id";
122
- readonly nonceCache: "bounded-lru";
123
- readonly policy: "fail-closed";
124
- };
125
1
  export declare const RUNTIME_ERROR_CODES: {
126
2
  readonly missingSignature: "missing_signature";
127
3
  readonly malformedSignature: "malformed_signature";
@@ -143,40 +19,15 @@ export declare const RUNTIME_ERROR_CODES: {
143
19
  readonly surfaceNotAllowed: "surface_not_allowed";
144
20
  };
145
21
  export type RuntimeErrorCode = (typeof RUNTIME_ERROR_CODES)[keyof typeof RUNTIME_ERROR_CODES];
146
- export declare const RUNTIME_METERING_CONTRACT: {
147
- readonly endpoint: "/v1/metering/events";
148
- readonly method: "POST";
149
- readonly credential: "reusable-bearer";
150
- readonly event: {
151
- readonly event_id: "string";
152
- readonly business_id: "string";
153
- readonly backend_id: "string";
154
- readonly route_id: "string?";
155
- readonly request_id: "string?";
156
- readonly requestId: "string?";
157
- readonly subscriptionId: "string";
158
- readonly nonce: "string?";
159
- readonly meter: "string";
160
- readonly qty: "number";
161
- readonly timestamp: "string";
162
- };
163
- readonly postStreamEvent: {
164
- readonly requestId: "string";
165
- readonly subscriptionId: "string?";
166
- readonly nonce: "string";
167
- readonly meters: "Record<string, number>";
168
- readonly creditUnitsConsumed: "Record<string, number>?";
169
- readonly measureContext: "Record<string, unknown>?";
170
- readonly signature: "string";
171
- };
172
- readonly idempotencyKey: "event_id";
173
- readonly delivery: "at-least-once";
174
- readonly billingOnly: true;
175
- readonly realtimeEnforced: false;
176
- readonly postStreamBillingOnly: true;
177
- readonly postStreamRealtimeEnforced: false;
178
- readonly postStreamTrustModel: "HMAC-attested and bound to one served postStreamBilling gateway request. Core writes one billable UsageEvent using the served plan and time. The callback never mutates Durable Object enforcement windows.";
179
- readonly trustModel: "upstream-reported values are NOT cryptographically attested; a buggy or compromised upstream can self-report arbitrary values for its OWN product only. Core enforces allowedMeters/allowedRoutes from the authoritative token record at ingest, applies a per-event sanity max (perEventMax), and raises an implausible-volume alert.";
22
+ export declare const RUNTIME_BODY_HASH_CONTRACT: {
23
+ readonly algorithm: "SHA-256";
24
+ readonly encoding: "hex-lower";
25
+ readonly source: "raw-request-bytes";
26
+ readonly emptyBodyHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
27
+ readonly maxBodyBytes: 10485760;
28
+ readonly streamingExemptToken: "STREAM";
29
+ readonly streamingExemptContentTypes: readonly ["text/event-stream", "application/octet-stream", "multipart/form-data"];
30
+ readonly overMaxStatus: 413;
180
31
  };
181
32
  export declare const RUNTIME_RESPONSE_METERING_CONTRACT: {
182
33
  readonly headers: {
@@ -198,14 +49,18 @@ export declare const RUNTIME_RESPONSE_METERING_CONTRACT: {
198
49
  readonly payload: {
199
50
  readonly method: "string";
200
51
  readonly path: "string";
201
- readonly rawDimsUnits: "Record<string, number>";
52
+ readonly rawDimsUnits: "Record<string, number>?";
202
53
  readonly measureContext: "Record<string, unknown>?";
203
54
  readonly creditUnitsConsumed: "Record<string, number>?";
55
+ readonly measurementsVersion: "1?";
56
+ readonly measurements: "Array<{ meter: string; values: Record<string, number>; dims?: Record<string, string> }>?";
57
+ readonly quote: "{ currency: string; amountNanos: string }?";
204
58
  };
205
59
  readonly errors: {
206
60
  readonly missingToken: "missing_token";
207
61
  readonly invalidMeterKey: "invalid_meter_key";
208
62
  readonly invalidMeterValue: "invalid_meter_value";
63
+ readonly invalidQuote: "invalid_quote";
209
64
  };
210
65
  readonly httpAdapter: {
211
66
  readonly input: "Request";
@@ -215,33 +70,3 @@ export declare const RUNTIME_RESPONSE_METERING_CONTRACT: {
215
70
  readonly gatewayStripsInternalHeaders: true;
216
71
  };
217
72
  };
218
- export declare const RUNTIME_HEALTH_CONTRACT: {
219
- readonly endpoint: "/v1/runtime/health";
220
- readonly method: "POST";
221
- readonly request: {
222
- readonly instanceId: "string?";
223
- readonly status: "starting | ready | degraded | stopping";
224
- };
225
- readonly readinessStates: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
226
- readonly checks: readonly ["runtime_token_valid", "bootstrapped", "tunnel_running", "signed_request_2xx", "unsigned_request_401", "stale_signature_401", "wrong_route_401", "metering_observed"];
227
- readonly report: {
228
- readonly runtimeToken: "boolean";
229
- readonly bootstrap: "boolean";
230
- readonly tunnel: "string?";
231
- readonly verification: "boolean";
232
- readonly metering: "boolean";
233
- };
234
- };
235
- export declare const RUNTIME_TRANSPORT_CONTRACT: {
236
- readonly modes: {
237
- readonly direct: "Gateway fetches the builder's public origin URL; the SDK middleware fail-closed-verifies every request via Ed25519 request signing. Provisions zero Cloudflare objects. Available on all tiers; also the dev path.";
238
- readonly tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The Production-secure tier. Consumes Cloudflare tunnel/route slots.";
239
- };
240
- readonly runners: {
241
- readonly embedded: "fs.start() supervises cloudflared as a child process (default DX).";
242
- readonly sidecar: "Vanilla cloudflare/cloudflared container beside the app (production / non-Node).";
243
- };
244
- readonly channelTrust: readonly ["tunnel"];
245
- readonly requestTrust: "x-fs-signature";
246
- readonly invariant: "Channel trust (tunnel) and request trust (the X-FS-* signature) are distinct layers; both always apply. CF-Access-* headers are transport-layer only and are IGNORED by the SDK.";
247
- };