@farthershore/backend 0.13.0 → 0.15.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/README.md +47 -7
- package/dist/generated/runtime-contract.js +20 -5
- package/dist/index.js +388 -218
- package/dist/testing/index.js +240 -49
- package/dist/types/core/metering.d.ts +8 -2
- package/dist/types/core/post-stream-usage.d.ts +45 -0
- package/dist/types/core/runtime.d.ts +7 -3
- package/dist/types/core/verifyRequest.d.ts +6 -3
- package/dist/types/generated/runtime-contract.d.ts +20 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/runtime-types.d.ts +19 -4
- package/dist/types/testing/devGateway.d.ts +8 -4
- package/dist/types/testing/devRuntime.d.ts +1 -1
- package/dist/types/testing/keysFile.d.ts +1 -1
- package/dist/types/testing/personas.d.ts +2 -2
- package/dist/types/testing/signers.d.ts +2 -2
- package/dist/types/testing/usageSink.d.ts +9 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReportUsageResult, RequestScopedReportUsageInput } from "./post-stream-usage.js";
|
|
1
2
|
import { type FartherShoreSignedContext } from "./verifyContext.js";
|
|
2
3
|
import type { JwksClient } from "./jwks.js";
|
|
3
4
|
import type { NonceCache } from "./nonceCache.js";
|
|
@@ -22,7 +23,7 @@ export type HeadersLike = Headers | Record<string, string | string[] | undefined
|
|
|
22
23
|
/** The verified request context attached to req.fartherShore. */
|
|
23
24
|
export type FartherShoreRequestContext = {
|
|
24
25
|
requestId: string;
|
|
25
|
-
|
|
26
|
+
businessId: string;
|
|
26
27
|
backendId: string;
|
|
27
28
|
routeId: string;
|
|
28
29
|
policyVersion: string;
|
|
@@ -48,12 +49,14 @@ export type FartherShoreRequestContext = {
|
|
|
48
49
|
signedContext?: FartherShoreSignedContext;
|
|
49
50
|
/** Managed-RBAC role keys the acting user holds (display/audit only). */
|
|
50
51
|
roles?: string[];
|
|
52
|
+
/** Request-bound post-stream reporter, attached by the runtime facade. */
|
|
53
|
+
reportUsage?: (input: RequestScopedReportUsageInput) => Promise<ReportUsageResult>;
|
|
51
54
|
};
|
|
52
55
|
export type VerifyRequestDeps = {
|
|
53
56
|
jwks: JwksClient;
|
|
54
57
|
nonceCache: NonceCache;
|
|
55
|
-
/** Expected
|
|
56
|
-
|
|
58
|
+
/** Expected business id (from bootstrap). When set, must match the signed claim. */
|
|
59
|
+
businessId?: string;
|
|
57
60
|
/** Expected backend id (from bootstrap). When set, must match. */
|
|
58
61
|
backendId?: string;
|
|
59
62
|
/**
|
|
@@ -73,7 +73,7 @@ export declare const RUNTIME_SIGNING_CONTRACT: {
|
|
|
73
73
|
readonly keyValueSeparator: ":";
|
|
74
74
|
readonly trailingNewline: false;
|
|
75
75
|
readonly fieldEncoding: "utf-8";
|
|
76
|
-
readonly fields: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "
|
|
76
|
+
readonly fields: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "business-id", "backend-id", "route-id", "policy-version"];
|
|
77
77
|
readonly fieldRules: {
|
|
78
78
|
readonly method: "Uppercased HTTP method (e.g. GET, POST).";
|
|
79
79
|
readonly path: "Request path, percent-encoded as received, no host, no query string. Always begins with '/'.";
|
|
@@ -81,7 +81,7 @@ export declare const RUNTIME_SIGNING_CONTRACT: {
|
|
|
81
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
82
|
readonly "request-id": "Opaque unique request id minted by the gateway (also the replay-cache nonce).";
|
|
83
83
|
readonly timestamp: "Integer Unix epoch seconds (UTC) at signing time, as a base-10 string with no padding.";
|
|
84
|
-
readonly "
|
|
84
|
+
readonly "business-id": "Business id the request is routed to.";
|
|
85
85
|
readonly "backend-id": "Backend id the route binds to.";
|
|
86
86
|
readonly "route-id": "Resolved route id; empty string if the route is unresolved.";
|
|
87
87
|
readonly "policy-version": "Tenant artifact / policy version the gateway signed under.";
|
|
@@ -92,7 +92,7 @@ export declare const RUNTIME_SIGNING_CONTRACT: {
|
|
|
92
92
|
* Ordered list of fields in the canonical signing string. The order here is
|
|
93
93
|
* load-bearing and identical across all language SDKs.
|
|
94
94
|
*/
|
|
95
|
-
export declare const RUNTIME_CANONICAL_FIELDS: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "
|
|
95
|
+
export declare const RUNTIME_CANONICAL_FIELDS: readonly ["method", "path", "query", "body-hash", "request-id", "timestamp", "business-id", "backend-id", "route-id", "policy-version"];
|
|
96
96
|
export declare const RUNTIME_BODY_HASH_CONTRACT: {
|
|
97
97
|
readonly algorithm: "SHA-256";
|
|
98
98
|
readonly encoding: "hex-lower";
|
|
@@ -108,7 +108,7 @@ export declare const RUNTIME_HEADERS: {
|
|
|
108
108
|
readonly keyId: "x-fs-key-id";
|
|
109
109
|
readonly requestId: "x-fs-request-id";
|
|
110
110
|
readonly timestamp: "x-fs-timestamp";
|
|
111
|
-
readonly
|
|
111
|
+
readonly businessId: "x-fs-business-id";
|
|
112
112
|
readonly backendId: "x-fs-backend-id";
|
|
113
113
|
readonly routeId: "x-fs-route-id";
|
|
114
114
|
readonly policyVersion: "x-fs-policy-version";
|
|
@@ -145,18 +145,33 @@ export declare const RUNTIME_METERING_CONTRACT: {
|
|
|
145
145
|
readonly credential: "reusable-bearer";
|
|
146
146
|
readonly event: {
|
|
147
147
|
readonly event_id: "string";
|
|
148
|
-
readonly
|
|
148
|
+
readonly business_id: "string";
|
|
149
149
|
readonly backend_id: "string";
|
|
150
150
|
readonly route_id: "string?";
|
|
151
151
|
readonly request_id: "string?";
|
|
152
|
+
readonly requestId: "string?";
|
|
153
|
+
readonly subscriptionId: "string";
|
|
154
|
+
readonly nonce: "string?";
|
|
152
155
|
readonly meter: "string";
|
|
153
156
|
readonly qty: "number";
|
|
154
157
|
readonly timestamp: "string";
|
|
155
158
|
};
|
|
159
|
+
readonly postStreamEvent: {
|
|
160
|
+
readonly requestId: "string";
|
|
161
|
+
readonly subscriptionId: "string?";
|
|
162
|
+
readonly nonce: "string";
|
|
163
|
+
readonly meters: "Record<string, number>";
|
|
164
|
+
readonly creditUnitsConsumed: "Record<string, number>?";
|
|
165
|
+
readonly measureContext: "Record<string, unknown>?";
|
|
166
|
+
readonly signature: "string";
|
|
167
|
+
};
|
|
156
168
|
readonly idempotencyKey: "event_id";
|
|
157
169
|
readonly delivery: "at-least-once";
|
|
158
170
|
readonly billingOnly: true;
|
|
159
171
|
readonly realtimeEnforced: false;
|
|
172
|
+
readonly postStreamBillingOnly: true;
|
|
173
|
+
readonly postStreamRealtimeEnforced: false;
|
|
174
|
+
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.";
|
|
160
175
|
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.";
|
|
161
176
|
};
|
|
162
177
|
export declare const RUNTIME_RESPONSE_METERING_CONTRACT: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { JwksClient, type Jwk, type JwksClientOptions } from "./core/jwks.js";
|
|
|
11
11
|
export { NonceCache, type NonceCacheOptions } from "./core/nonceCache.js";
|
|
12
12
|
export { BootstrapClient, type BootstrapClientOptions, } from "./core/bootstrap.js";
|
|
13
13
|
export { MeteringClient, type MeteringClientOptions, type MeterOptions, } from "./core/metering.js";
|
|
14
|
+
export { PostStreamUsageClient, type PostStreamUsageClientOptions, type ReportUsageInput, type RequestScopedReportUsageInput, type ReportUsageResult, } from "./core/post-stream-usage.js";
|
|
14
15
|
export { buildHealthReport, reportHealth, type HealthSnapshot, type HealthStatus, type HeartbeatOptions, } from "./core/health.js";
|
|
15
16
|
export { ShutdownManager, type ShutdownHook } from "./core/shutdown.js";
|
|
16
17
|
export { CloudflaredSupervisor, nodeSpawn, REDACTED_TOKEN, type SpawnFn, type SpawnedTunnelProcess, type CloudflaredSupervisorOptions, type TunnelState, type TunnelStatus, } from "./core/tunnel.js";
|
|
@@ -126,7 +126,7 @@ 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
|
|
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";
|
|
@@ -166,7 +166,7 @@ export type CanonicalSigningInput = {
|
|
|
166
166
|
requestId: string;
|
|
167
167
|
/** Unix epoch seconds at signing time. */
|
|
168
168
|
timestamp: number;
|
|
169
|
-
|
|
169
|
+
businessId: string;
|
|
170
170
|
backendId: string;
|
|
171
171
|
/** Resolved route id; "" when unresolved. */
|
|
172
172
|
routeId: string;
|
|
@@ -221,7 +221,7 @@ export type RuntimeLockDescriptor = {
|
|
|
221
221
|
lockVersion: number;
|
|
222
222
|
};
|
|
223
223
|
export type RuntimeBootstrapResponse = {
|
|
224
|
-
|
|
224
|
+
business: {
|
|
225
225
|
id: string;
|
|
226
226
|
slug: string;
|
|
227
227
|
};
|
|
@@ -247,14 +247,29 @@ export type RuntimeBootstrapResponse = {
|
|
|
247
247
|
};
|
|
248
248
|
export type RuntimeMeteringEvent = {
|
|
249
249
|
event_id: string;
|
|
250
|
-
|
|
250
|
+
business_id: string;
|
|
251
251
|
backend_id: string;
|
|
252
252
|
route_id?: string;
|
|
253
253
|
request_id?: string;
|
|
254
|
+
/** Subscription the usage belongs to (billing attribution). Optional and
|
|
255
|
+
* additive: legacy emitters omit it; core then falls back to resolving the
|
|
256
|
+
* served gateway request via `request_id`, and rows that resolve neither
|
|
257
|
+
* way are persisted unbilled + flagged unattributable. */
|
|
258
|
+
subscription_id?: string;
|
|
254
259
|
meter: string;
|
|
255
260
|
qty: number;
|
|
256
261
|
timestamp: string;
|
|
257
262
|
};
|
|
263
|
+
/** Attested, request-bound billing callback sent after a stream completes. */
|
|
264
|
+
export type RuntimePostStreamUsageEvent = {
|
|
265
|
+
requestId: string;
|
|
266
|
+
subscriptionId: string;
|
|
267
|
+
nonce: string;
|
|
268
|
+
meters: Record<string, number>;
|
|
269
|
+
creditUnitsConsumed?: Record<string, number>;
|
|
270
|
+
measureContext?: Record<string, unknown>;
|
|
271
|
+
signature: string;
|
|
272
|
+
};
|
|
258
273
|
export declare const RUNTIME_READINESS_STATES: readonly ["UNKNOWN", "WAITING", "READY", "DEGRADED", "OFFLINE"];
|
|
259
274
|
export type RuntimeReadinessState = (typeof RUNTIME_READINESS_STATES)[number];
|
|
260
275
|
export type RuntimeHealthReport = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RuntimeBootstrapResponse, type RuntimeMeteringEvent } from "../runtime-types.js";
|
|
1
|
+
import { type RuntimeBootstrapResponse, type RuntimeMeteringEvent, type RuntimePostStreamUsageEvent } from "../runtime-types.js";
|
|
2
2
|
import type { DevMode } from "./traceSink.js";
|
|
3
3
|
import type { DevSignerKeys } from "./signers.js";
|
|
4
4
|
export declare const DEV_CORE_URL = "https://dev-gateway.farthershore.local";
|
|
@@ -7,14 +7,16 @@ 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
|
-
|
|
10
|
+
businessId?: string;
|
|
11
11
|
backendId?: string;
|
|
12
|
-
|
|
12
|
+
businessSlug?: string;
|
|
13
13
|
backendSlug?: string;
|
|
14
14
|
/** Extra route ids to expose in bootstrap for route-binding tests. */
|
|
15
15
|
routeIds?: string[];
|
|
16
16
|
/** Called for each captured metering event (at-least-once ACK). */
|
|
17
17
|
onMeterEvent?: (event: RuntimeMeteringEvent) => void;
|
|
18
|
+
/** Called for each captured attested post-stream report. */
|
|
19
|
+
onReportUsage?: (event: RuntimePostStreamUsageEvent) => void;
|
|
18
20
|
};
|
|
19
21
|
export type DevGateway = {
|
|
20
22
|
/** Inject this as `fetchImpl` when constructing the FartherShore runtime. */
|
|
@@ -23,7 +25,9 @@ export type DevGateway = {
|
|
|
23
25
|
bootstrap: RuntimeBootstrapResponse;
|
|
24
26
|
/** Every captured background metering event (a capture == an ACK). */
|
|
25
27
|
meterEvents: RuntimeMeteringEvent[];
|
|
26
|
-
|
|
28
|
+
/** Every captured attested post-stream usage report. */
|
|
29
|
+
reportUsageEvents: RuntimePostStreamUsageEvent[];
|
|
30
|
+
businessId: string;
|
|
27
31
|
backendId: string;
|
|
28
32
|
jwksUrl: string;
|
|
29
33
|
};
|
|
@@ -24,7 +24,7 @@ export type CreateDevRuntimeOptions = {
|
|
|
24
24
|
routes?: string[];
|
|
25
25
|
/** Meter keys (informational — the dev gateway accepts any meter). */
|
|
26
26
|
meters?: string[];
|
|
27
|
-
|
|
27
|
+
businessId?: string;
|
|
28
28
|
backendId?: string;
|
|
29
29
|
/** Optional app transport for persona `.fetch()`; defaults to global fetch. */
|
|
30
30
|
appFetch?: typeof fetch;
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
businessId?: string;
|
|
29
29
|
backendId?: string;
|
|
30
30
|
routeId?: string;
|
|
31
31
|
policyVersion?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RuntimeMeteringEvent } from "../runtime-types.js";
|
|
1
|
+
import type { RuntimeMeteringEvent, RuntimePostStreamUsageEvent } from "../runtime-types.js";
|
|
2
2
|
/** One recorded usage observation. `source` distinguishes the two channels. */
|
|
3
3
|
export type DevUsageEvent = {
|
|
4
4
|
source: "response";
|
|
@@ -16,6 +16,12 @@ export type DevUsageEvent = {
|
|
|
16
16
|
event: RuntimeMeteringEvent;
|
|
17
17
|
requestId?: string;
|
|
18
18
|
at: number;
|
|
19
|
+
} | {
|
|
20
|
+
source: "reportUsage";
|
|
21
|
+
meters: Record<string, number>;
|
|
22
|
+
event: RuntimePostStreamUsageEvent;
|
|
23
|
+
requestId: string;
|
|
24
|
+
at: number;
|
|
19
25
|
};
|
|
20
26
|
/** In-memory, assertable sink for all dev usage. */
|
|
21
27
|
export declare class DevUsageSink {
|
|
@@ -24,6 +30,8 @@ export declare class DevUsageSink {
|
|
|
24
30
|
recordResponse(payload: Record<string, unknown>, requestId?: string): void;
|
|
25
31
|
/** Record a background `fs.meter()` event captured by the dev gateway. */
|
|
26
32
|
recordMeterEvent(event: RuntimeMeteringEvent): void;
|
|
33
|
+
/** Record an attested post-stream report captured by the dev gateway. */
|
|
34
|
+
recordReportUsage(event: RuntimePostStreamUsageEvent): void;
|
|
27
35
|
/** Total quantity per meter key across every recorded event. */
|
|
28
36
|
byMeter(): Record<string, number>;
|
|
29
37
|
/** Clear all recorded usage. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farthershore/backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|