@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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @farthershore/backend
|
|
2
2
|
|
|
3
|
-
The runtime SDK for your own backend. When you run a software
|
|
3
|
+
The runtime SDK for your own backend. When you run a software business on Farther
|
|
4
4
|
Shore with a bring-your-own-backend, the platform's edge gateway sits in front of
|
|
5
5
|
your service. This package lets your backend **trust the gateway** (verify that
|
|
6
6
|
each request really came from it) and **report usage** back for metering and
|
|
@@ -8,11 +8,11 @@ billing — from a single token, `FS_RUNTIME_TOKEN`.
|
|
|
8
8
|
|
|
9
9
|
Install one package, set one environment variable, and you get fail-closed
|
|
10
10
|
gateway-to-upstream request verification, response-bound usage reporting, and
|
|
11
|
-
graceful lifecycle (health + shutdown). Everything else — your
|
|
11
|
+
graceful lifecycle (health + shutdown). Everything else — your business, backend,
|
|
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.
|
|
15
|
+
> **Status: `0.15.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
|
|
|
@@ -76,7 +76,7 @@ process.on("SIGTERM", () => void fs.shutdown());
|
|
|
76
76
|
## What `initFromEnv()` derives
|
|
77
77
|
|
|
78
78
|
You configure exactly one thing: `FS_RUNTIME_TOKEN` (mint it for your backend
|
|
79
|
-
with the Farther Shore CLI or dashboard). Everything else —
|
|
79
|
+
with the Farther Shore CLI or dashboard). Everything else — business / backend /
|
|
80
80
|
environment ids, the JWKS url used to verify signatures, the metering endpoint
|
|
81
81
|
and credential, and verification settings — is fetched from the platform at
|
|
82
82
|
startup and cached in memory. The token is validated eagerly, so a
|
|
@@ -126,11 +126,11 @@ export async function POST(request: Request) {
|
|
|
126
126
|
|
|
127
127
|
- `measureContext` is free-form pricing/analytics context persisted with the
|
|
128
128
|
usage event.
|
|
129
|
-
- `creditUnitsConsumed` is a numeric map for credit-wallet style
|
|
129
|
+
- `creditUnitsConsumed` is a numeric map for credit-wallet style businesses; keys
|
|
130
130
|
and values are validated locally before signing.
|
|
131
131
|
|
|
132
132
|
The meter keys you report (e.g. `tokens_used`) must match meters declared in your
|
|
133
|
-
|
|
133
|
+
business. Request-count style limits are enforced by the gateway and need no
|
|
134
134
|
backend code.
|
|
135
135
|
|
|
136
136
|
## Async / background usage
|
|
@@ -142,6 +142,40 @@ Delivery is at-least-once; the event idempotency key keeps ingestion safe.
|
|
|
142
142
|
Background usage is tallied and billed after the cycle, not enforced in
|
|
143
143
|
real time.
|
|
144
144
|
|
|
145
|
+
## Post-stream usage reporting
|
|
146
|
+
|
|
147
|
+
Use `fs.reportUsage()` when a gateway request streams its response and the
|
|
148
|
+
billable total is known only after the stream completes. Declare that route
|
|
149
|
+
with `postStreamBilling: true`, then report from the request-scoped verified
|
|
150
|
+
context so the SDK retains the attested subscription subject:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const context = await fs.verifyRequest({ method, path, query, headers, body });
|
|
154
|
+
|
|
155
|
+
await streamResponse(context);
|
|
156
|
+
await context.reportUsage?.({
|
|
157
|
+
meters: { output_tokens: 1280 },
|
|
158
|
+
measureContext: { model: "apsu-1" },
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The callback is HMAC-attested and requires a subscription subject. Core binds it
|
|
163
|
+
to the immutable gateway request row and writes one billable `UsageEvent` with
|
|
164
|
+
the gateway-known request units merged with reported actual units, the served
|
|
165
|
+
plan, served route, and served request time. The gateway evidence row is
|
|
166
|
+
unbilled and omits only response-derived dimensions. This is a billing-only channel: it
|
|
167
|
+
does not settle or mutate Durable Object enforcement windows, so units that are
|
|
168
|
+
unknown at admission cannot be hard-enforced. Knowable request dimensions still
|
|
169
|
+
follow the normal admission path and are billed once on the merged callback.
|
|
170
|
+
|
|
171
|
+
Gateway evidence is published asynchronously. If the callback arrives first,
|
|
172
|
+
Core parks the verified payload and the SDK retries only
|
|
173
|
+
`post_stream_request_not_found` with bounded backoff, reusing the exact signed
|
|
174
|
+
payload and nonce. A maintenance pass binds any remaining parked callback once
|
|
175
|
+
evidence lands and durably alerts if it expires. The SDK method is best-effort and resolves
|
|
176
|
+
`{ ok: false, reason }` instead of rejecting, so handle or log a failed report
|
|
177
|
+
according to your service's delivery policy.
|
|
178
|
+
|
|
145
179
|
## Lifecycle
|
|
146
180
|
|
|
147
181
|
- `fs.health()` returns the current local health report (token present, bootstrap
|
|
@@ -211,6 +245,7 @@ See `templates/3-simulated-authz.test.ts` for the full fail-closed + usage flow.
|
|
|
211
245
|
| `withUsage()` / `createUsage()` | Response-bound usage reporting (no network call). |
|
|
212
246
|
| `computeMeteringHeaders()` | Metering headers as a plain map — never throws. |
|
|
213
247
|
| `fs.meter(meter, qty, opts)` | Async/background usage event. |
|
|
248
|
+
| `fs.reportUsage(input)` | Attested post-stream usage callback. |
|
|
214
249
|
| `fs.health()` / `fs.shutdown()` | Health report and graceful shutdown. |
|
|
215
250
|
| `FartherShoreError`, `MeteringError` | Typed errors. |
|
|
216
251
|
| `@farthershore/backend/testing` | Dev-mode + persona test harness (dev/test only). |
|
|
@@ -220,12 +255,17 @@ types directly if you prefer to wire the middleware yourself.
|
|
|
220
255
|
|
|
221
256
|
## Metering channels
|
|
222
257
|
|
|
223
|
-
|
|
258
|
+
Three usage channels exist and are **not** interchangeable:
|
|
224
259
|
|
|
225
260
|
- **Response-bound** (`withUsage` / `createUsage` / `computeMeteringHeaders`) is
|
|
226
261
|
the attested, request-bound settlement channel: the gateway verifies the HMAC
|
|
227
262
|
and settles the reported units against the request's lease in the same
|
|
228
263
|
lifecycle. Wire recipe (any language): [`docs/response-metering-wire.md`](docs/response-metering-wire.md).
|
|
264
|
+
- **Post-stream** (`fs.reportUsage` or the request-scoped
|
|
265
|
+
`context.reportUsage`) is attested and request-bound for streaming routes
|
|
266
|
+
declared with `postStreamBilling: true`. It writes the sole billable row for
|
|
267
|
+
gateway-known plus reported actual units and never mutates real-time
|
|
268
|
+
enforcement windows.
|
|
229
269
|
- **Background** (`fs.meter`) is a billing-only, unattested, post-cycle tally for
|
|
230
270
|
usage not tied to a gateway response. It never settles a lease.
|
|
231
271
|
|
|
@@ -83,7 +83,7 @@ var RUNTIME_SIGNING_CONTRACT = {
|
|
|
83
83
|
"body-hash",
|
|
84
84
|
"request-id",
|
|
85
85
|
"timestamp",
|
|
86
|
-
"
|
|
86
|
+
"business-id",
|
|
87
87
|
"backend-id",
|
|
88
88
|
"route-id",
|
|
89
89
|
"policy-version"
|
|
@@ -95,7 +95,7 @@ var RUNTIME_SIGNING_CONTRACT = {
|
|
|
95
95
|
"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'.",
|
|
96
96
|
"request-id": "Opaque unique request id minted by the gateway (also the replay-cache nonce).",
|
|
97
97
|
timestamp: "Integer Unix epoch seconds (UTC) at signing time, as a base-10 string with no padding.",
|
|
98
|
-
"
|
|
98
|
+
"business-id": "Business id the request is routed to.",
|
|
99
99
|
"backend-id": "Backend id the route binds to.",
|
|
100
100
|
"route-id": "Resolved route id; empty string if the route is unresolved.",
|
|
101
101
|
"policy-version": "Tenant artifact / policy version the gateway signed under."
|
|
@@ -109,7 +109,7 @@ var RUNTIME_CANONICAL_FIELDS = [
|
|
|
109
109
|
"body-hash",
|
|
110
110
|
"request-id",
|
|
111
111
|
"timestamp",
|
|
112
|
-
"
|
|
112
|
+
"business-id",
|
|
113
113
|
"backend-id",
|
|
114
114
|
"route-id",
|
|
115
115
|
"policy-version"
|
|
@@ -133,7 +133,7 @@ var RUNTIME_HEADERS = {
|
|
|
133
133
|
keyId: "x-fs-key-id",
|
|
134
134
|
requestId: "x-fs-request-id",
|
|
135
135
|
timestamp: "x-fs-timestamp",
|
|
136
|
-
|
|
136
|
+
businessId: "x-fs-business-id",
|
|
137
137
|
backendId: "x-fs-backend-id",
|
|
138
138
|
routeId: "x-fs-route-id",
|
|
139
139
|
policyVersion: "x-fs-policy-version",
|
|
@@ -169,18 +169,33 @@ var RUNTIME_METERING_CONTRACT = {
|
|
|
169
169
|
credential: "reusable-bearer",
|
|
170
170
|
event: {
|
|
171
171
|
event_id: "string",
|
|
172
|
-
|
|
172
|
+
business_id: "string",
|
|
173
173
|
backend_id: "string",
|
|
174
174
|
route_id: "string?",
|
|
175
175
|
request_id: "string?",
|
|
176
|
+
requestId: "string?",
|
|
177
|
+
subscriptionId: "string",
|
|
178
|
+
nonce: "string?",
|
|
176
179
|
meter: "string",
|
|
177
180
|
qty: "number",
|
|
178
181
|
timestamp: "string"
|
|
179
182
|
},
|
|
183
|
+
postStreamEvent: {
|
|
184
|
+
requestId: "string",
|
|
185
|
+
subscriptionId: "string?",
|
|
186
|
+
nonce: "string",
|
|
187
|
+
meters: "Record<string, number>",
|
|
188
|
+
creditUnitsConsumed: "Record<string, number>?",
|
|
189
|
+
measureContext: "Record<string, unknown>?",
|
|
190
|
+
signature: "string"
|
|
191
|
+
},
|
|
180
192
|
idempotencyKey: "event_id",
|
|
181
193
|
delivery: "at-least-once",
|
|
182
194
|
billingOnly: true,
|
|
183
195
|
realtimeEnforced: false,
|
|
196
|
+
postStreamBillingOnly: true,
|
|
197
|
+
postStreamRealtimeEnforced: false,
|
|
198
|
+
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.",
|
|
184
199
|
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."
|
|
185
200
|
};
|
|
186
201
|
var RUNTIME_RESPONSE_METERING_CONTRACT = {
|