@farthershore/backend 0.12.0 → 0.14.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 +106 -1
- package/dist/generated/runtime-contract.js +15 -0
- package/dist/index.js +1072 -124
- package/dist/testing/index.js +2906 -0
- package/dist/types/core/post-stream-usage.d.ts +45 -0
- package/dist/types/core/runtime.d.ts +4 -0
- package/dist/types/core/verifyRequest.d.ts +3 -0
- package/dist/types/generated/runtime-contract.d.ts +15 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/response-metering.d.ts +45 -0
- package/dist/types/runtime-types.d.ts +10 -0
- package/dist/types/testing/devGateway.d.ts +40 -0
- package/dist/types/testing/devRuntime.d.ts +68 -0
- package/dist/types/testing/index.d.ts +8 -0
- package/dist/types/testing/keysFile.d.ts +30 -0
- package/dist/types/testing/personas.d.ts +104 -0
- package/dist/types/testing/prodGuard.d.ts +12 -0
- package/dist/types/testing/signers.d.ts +89 -0
- package/dist/types/testing/traceSink.d.ts +67 -0
- package/dist/types/testing/usageSink.d.ts +39 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ graceful lifecycle (health + shutdown). Everything else — your product, backen
|
|
|
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.14.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
|
|
|
@@ -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
|
|
@@ -149,6 +183,58 @@ real time.
|
|
|
149
183
|
- `fs.shutdown()` flushes any buffered metering and sends a `stopping` heartbeat.
|
|
150
184
|
Call it on `SIGTERM` / `SIGINT` for graceful shutdown.
|
|
151
185
|
|
|
186
|
+
## Local development & testing — the mode ladder
|
|
187
|
+
|
|
188
|
+
You do not need the platform to test a backend that runs behind it. Pick the
|
|
189
|
+
lowest tier that answers your question — each is a superset of the one below.
|
|
190
|
+
|
|
191
|
+
**Tier 0 — off (needs NOTHING from the SDK).** Unit-test your business logic
|
|
192
|
+
directly. The gateway sits in front of you in production; your pure handlers
|
|
193
|
+
don't import Farther Shore to be tested. There is no SDK step at this tier — see
|
|
194
|
+
`templates/1-unit.test.ts`.
|
|
195
|
+
|
|
196
|
+
**Tier 1 — passthrough.** Run your real app over HTTP with `fs.middleware()`
|
|
197
|
+
mounted but verification OFF, matching the pre-keystone deploy order: the
|
|
198
|
+
middleware passes requests through without attaching a context, so your routes
|
|
199
|
+
run normally. Activate with `FS_DEV_MODE=passthrough`. See
|
|
200
|
+
`templates/2-passthrough-http.test.ts`.
|
|
201
|
+
|
|
202
|
+
**Tier 2 — simulated.** A real runtime wired to an in-process gateway with
|
|
203
|
+
fail-closed verification ON and SIGNED personas driving requests. Assert the
|
|
204
|
+
fail-closed boundary (a persona without a permission gets 403) and that usage is
|
|
205
|
+
metered. Activate with `FS_DEV_MODE=simulated`, or construct explicitly:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { createDevRuntime, definePersona } from "@farthershore/backend/testing";
|
|
209
|
+
|
|
210
|
+
const rt = createDevRuntime({
|
|
211
|
+
mode: "simulated",
|
|
212
|
+
personas: {
|
|
213
|
+
creator: definePersona({
|
|
214
|
+
name: "creator",
|
|
215
|
+
permissions: ["widgets:create"],
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Sign a request as a persona and drive your app (supertest, fetch, or inject):
|
|
221
|
+
const headers = await rt.asPersona("creator").headers({ path: "/v1/widgets" });
|
|
222
|
+
// rt.usage.byMeter() → assert reported usage
|
|
223
|
+
// rt.trace.forRequest(id) → why a request verified / was denied
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`@farthershore/backend/testing` gives you signed personas (owner / admin /
|
|
227
|
+
member / anonymous, plus your own), an in-process gateway fixture, and
|
|
228
|
+
assertable `usage` + per-request `trace` side channels. It is dev/test tooling
|
|
229
|
+
only — **it throws when `NODE_ENV=production`**, and `FS_DEV_MODE` is never a
|
|
230
|
+
required-at-boot variable. When `FS_DEV_MODE` is set, `initFromEnv()`
|
|
231
|
+
self-constructs the simulator (ephemeral keys, a loud banner, JSONL usage/trace
|
|
232
|
+
logs under `.farthershore/`, and a mode-600 `.farthershore/dev-keys.json` so a
|
|
233
|
+
separate test-runner process can sign against a running service via
|
|
234
|
+
`personaClientFromKeysFile`).
|
|
235
|
+
|
|
236
|
+
See `templates/3-simulated-authz.test.ts` for the full fail-closed + usage flow.
|
|
237
|
+
|
|
152
238
|
## Key exports
|
|
153
239
|
|
|
154
240
|
| Export | Purpose |
|
|
@@ -157,13 +243,32 @@ real time.
|
|
|
157
243
|
| `fs.middleware()` | Express fail-closed verify → `req.fartherShore`. |
|
|
158
244
|
| `fs.verifyRequest({...})` | Framework-neutral request verification. |
|
|
159
245
|
| `withUsage()` / `createUsage()` | Response-bound usage reporting (no network call). |
|
|
246
|
+
| `computeMeteringHeaders()` | Metering headers as a plain map — never throws. |
|
|
160
247
|
| `fs.meter(meter, qty, opts)` | Async/background usage event. |
|
|
248
|
+
| `fs.reportUsage(input)` | Attested post-stream usage callback. |
|
|
161
249
|
| `fs.health()` / `fs.shutdown()` | Health report and graceful shutdown. |
|
|
162
250
|
| `FartherShoreError`, `MeteringError` | Typed errors. |
|
|
251
|
+
| `@farthershore/backend/testing` | Dev-mode + persona test harness (dev/test only). |
|
|
163
252
|
|
|
164
253
|
A subpath export, `@farthershore/backend/express`, exposes the Express adapter
|
|
165
254
|
types directly if you prefer to wire the middleware yourself.
|
|
166
255
|
|
|
256
|
+
## Metering channels
|
|
257
|
+
|
|
258
|
+
Three usage channels exist and are **not** interchangeable:
|
|
259
|
+
|
|
260
|
+
- **Response-bound** (`withUsage` / `createUsage` / `computeMeteringHeaders`) is
|
|
261
|
+
the attested, request-bound settlement channel: the gateway verifies the HMAC
|
|
262
|
+
and settles the reported units against the request's lease in the same
|
|
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.
|
|
269
|
+
- **Background** (`fs.meter`) is a billing-only, unattested, post-cycle tally for
|
|
270
|
+
usage not tied to a gateway response. It never settles a lease.
|
|
271
|
+
|
|
167
272
|
## Learn more
|
|
168
273
|
|
|
169
274
|
- Platform documentation: https://docs.farthershore.com
|
|
@@ -173,14 +173,29 @@ var RUNTIME_METERING_CONTRACT = {
|
|
|
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 = {
|