usertrust-server 1.4.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 ADDED
@@ -0,0 +1,74 @@
1
+ # usertrust-server
2
+
3
+ Self-hostable HTTP control plane for the [usertrust](https://github.com/usertools/usertrust)
4
+ governance kernel. Wraps the headless Governor's two-phase lifecycle
5
+ (authorize → settle/abort) with per-tenant isolation, bearer-key auth, and SSE telemetry.
6
+
7
+ ## Quickstart
8
+
9
+ ```bash
10
+ # Generate a tenant key (high-entropy secret) and its SHA-256 hash for the config:
11
+ KEY=$(openssl rand -hex 32)
12
+ node -e "console.log(require('node:crypto').createHash('sha256').update(process.argv[1]).digest('hex'))" "$KEY"
13
+ ```
14
+
15
+ `usertrust-server.config.json`:
16
+
17
+ ```json
18
+ {
19
+ "port": 4519,
20
+ "stateDir": ".usertrust-server",
21
+ "tenants": [{ "id": "acme", "keyHash": "<sha256 hex of the key>", "budget": 50000 }]
22
+ }
23
+ ```
24
+
25
+ ```bash
26
+ usertrust-server --config usertrust-server.config.json
27
+
28
+ curl -s localhost:4519/v1/authorize -H "Authorization: Bearer $KEY" \
29
+ -H "content-type: application/json" \
30
+ -d '{"model":"claude-sonnet-4-6","estimatedInputTokens":200,"maxOutputTokens":100}'
31
+ curl -s localhost:4519/v1/settle -H "Authorization: Bearer $KEY" \
32
+ -H "content-type: application/json" \
33
+ -d '{"transferId":"<from authorize>","inputTokens":200,"outputTokens":40}'
34
+ ```
35
+
36
+ ## Endpoints
37
+
38
+ | Method | Path | Auth | Purpose |
39
+ | ------ | --------------- | ------ | --------------------------------------------------- |
40
+ | POST | `/v1/authorize` | Bearer | Phase 1: policy gate + PENDING budget hold |
41
+ | POST | `/v1/settle` | Bearer | Phase 2a: post actual usage, returns a TrustReceipt |
42
+ | POST | `/v1/abort` | Bearer | Phase 2b: void the hold for a failed call |
43
+ | GET | `/v1/budget` | Bearer | Remaining tenant budget |
44
+ | GET | `/v1/events` | Bearer | SSE stream of tenant governance events |
45
+ | GET | `/v1/health` | none | Liveness |
46
+
47
+ Errors: `403 policy_denied`, `402 budget_exceeded`, `429 anomaly`, `401 unauthorized`,
48
+ `404 not_found` (unknown/already-settled transferId), `413 too_large` (1 MiB body cap).
49
+ Pending holds not settled within `pendingTtlMs` (default 5 min) are swept and aborted.
50
+
51
+ ## Keys
52
+
53
+ Tenant keys are generated high-entropy secrets (`openssl rand -hex 32`), never passwords.
54
+ The config stores only the SHA-256 hash of each key — this is the standard API-key model
55
+ (hash-at-rest for a random 256-bit secret), not a password store, so no slow hash
56
+ (scrypt/argon2) is needed. Lookup is timing-safe. The server never logs request bodies,
57
+ messages, params, or Authorization headers.
58
+
59
+ ## Shadow mode (`"enforcement": "evaluate_only"`)
60
+
61
+ In `evaluate_only` mode a denial is converted into a shadow allow: the client receives
62
+ `200 { shadow: true, shadowId: "shadow_…", decision: "would_deny", reason }` and a
63
+ `denied` event with `shadow: true` is emitted. No reservation is created — a `shadowId`
64
+ is not a `transferId` and cannot be settled or aborted (those routes 404). This is the
65
+ server-layer semantic; other usertrust layers define their own evaluate-only behavior.
66
+
67
+ ## Audit authority
68
+
69
+ Only Governor-produced receipts and audit-chain records are authoritative. The server
70
+ invents no audit records of its own; every receipt returned by `/v1/settle` comes from
71
+ the tenant's Governor (per-tenant `stateDir/<tenant>` vault: separate audit chain and
72
+ spend ledger). The SSE stream is best-effort operational telemetry, NOT an audit
73
+ source — dropped subscribers lose events. Shadow denials produce no receipt and are
74
+ not auditable or verifiable.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Copyright 2026 Usertools, Inc.
4
+ import { loadServerConfig } from "../config.js";
5
+ import { createUsertrustServer } from "../server.js";
6
+ function argValue(flag) {
7
+ const index = process.argv.indexOf(flag);
8
+ return index >= 0 ? process.argv[index + 1] : undefined;
9
+ }
10
+ async function main() {
11
+ const configPath = argValue("--config") ?? "usertrust-server.config.json";
12
+ const config = await loadServerConfig(configPath);
13
+ const portOverride = argValue("--port");
14
+ if (portOverride)
15
+ config.port = Number.parseInt(portOverride, 10);
16
+ const server = createUsertrustServer({ config });
17
+ const { port } = await server.listen();
18
+ process.stderr.write(`usertrust-server listening on ${config.host}:${port}\n`);
19
+ const shutdown = async () => {
20
+ process.stderr.write("usertrust-server shutting down — voiding pending holds\n");
21
+ await server.close();
22
+ process.exit(0);
23
+ };
24
+ process.on("SIGINT", () => void shutdown());
25
+ process.on("SIGTERM", () => void shutdown());
26
+ }
27
+ main().catch((err) => {
28
+ process.stderr.write(`usertrust-server failed to start: ${err instanceof Error ? err.message : String(err)}\n`);
29
+ process.exit(1);
30
+ });
31
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AACA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,SAAS,QAAQ,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED,KAAK,UAAU,IAAI;IAClB,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,8BAA8B,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,YAAY;QAAE,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QACjF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,qCAAqC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACzF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ declare const TenantSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ /** SHA-256 hex of the tenant's bearer key. Keys are never stored in plaintext. */
5
+ keyHash: z.ZodString;
6
+ budget: z.ZodOptional<z.ZodNumber>;
7
+ tier: z.ZodOptional<z.ZodString>;
8
+ configPath: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ id: string;
11
+ keyHash: string;
12
+ budget?: number | undefined;
13
+ tier?: string | undefined;
14
+ configPath?: string | undefined;
15
+ }, {
16
+ id: string;
17
+ keyHash: string;
18
+ budget?: number | undefined;
19
+ tier?: string | undefined;
20
+ configPath?: string | undefined;
21
+ }>;
22
+ declare const ServerConfigSchema: z.ZodObject<{
23
+ host: z.ZodDefault<z.ZodString>;
24
+ port: z.ZodDefault<z.ZodNumber>;
25
+ stateDir: z.ZodDefault<z.ZodString>;
26
+ enforcement: z.ZodDefault<z.ZodEnum<["enforce", "evaluate_only"]>>;
27
+ pendingTtlMs: z.ZodDefault<z.ZodNumber>;
28
+ dryRun: z.ZodDefault<z.ZodBoolean>;
29
+ tenants: z.ZodArray<z.ZodObject<{
30
+ id: z.ZodString;
31
+ /** SHA-256 hex of the tenant's bearer key. Keys are never stored in plaintext. */
32
+ keyHash: z.ZodString;
33
+ budget: z.ZodOptional<z.ZodNumber>;
34
+ tier: z.ZodOptional<z.ZodString>;
35
+ configPath: z.ZodOptional<z.ZodString>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ id: string;
38
+ keyHash: string;
39
+ budget?: number | undefined;
40
+ tier?: string | undefined;
41
+ configPath?: string | undefined;
42
+ }, {
43
+ id: string;
44
+ keyHash: string;
45
+ budget?: number | undefined;
46
+ tier?: string | undefined;
47
+ configPath?: string | undefined;
48
+ }>, "many">;
49
+ }, "strip", z.ZodTypeAny, {
50
+ host: string;
51
+ port: number;
52
+ stateDir: string;
53
+ enforcement: "enforce" | "evaluate_only";
54
+ pendingTtlMs: number;
55
+ dryRun: boolean;
56
+ tenants: {
57
+ id: string;
58
+ keyHash: string;
59
+ budget?: number | undefined;
60
+ tier?: string | undefined;
61
+ configPath?: string | undefined;
62
+ }[];
63
+ }, {
64
+ tenants: {
65
+ id: string;
66
+ keyHash: string;
67
+ budget?: number | undefined;
68
+ tier?: string | undefined;
69
+ configPath?: string | undefined;
70
+ }[];
71
+ host?: string | undefined;
72
+ port?: number | undefined;
73
+ stateDir?: string | undefined;
74
+ enforcement?: "enforce" | "evaluate_only" | undefined;
75
+ pendingTtlMs?: number | undefined;
76
+ dryRun?: boolean | undefined;
77
+ }>;
78
+ export type TenantConfig = z.infer<typeof TenantSchema>;
79
+ export type ServerConfig = z.infer<typeof ServerConfigSchema>;
80
+ export declare function hashKey(key: string): string;
81
+ export declare function loadServerConfig(path: string): Promise<ServerConfig>;
82
+ /** Constant-time key lookup: hash the presented key, compare against every tenant. */
83
+ export declare function resolveTenant(config: ServerConfig, bearerKey: string): TenantConfig | null;
84
+ export {};
85
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,YAAY;;IAIjB,kFAAkF;;;;;;;;;;;;;;;;;EAKjF,CAAC;AAEH,QAAA,MAAM,kBAAkB;;;;;;;;;QAPvB,kFAAkF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAejF,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkB1E;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAU1F"}
package/dist/config.js ADDED
@@ -0,0 +1,59 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ import { createHash, timingSafeEqual } from "node:crypto";
4
+ import { readFile } from "node:fs/promises";
5
+ import { z } from "zod";
6
+ const TenantSchema = z.object({
7
+ // Interpolated into a filesystem path (pool.ts vaultBase), so it must not be
8
+ // able to escape stateDir — constrain to a safe, traversal-proof charset.
9
+ id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
10
+ /** SHA-256 hex of the tenant's bearer key. Keys are never stored in plaintext. */
11
+ keyHash: z.string().regex(/^[0-9a-f]{64}$/),
12
+ budget: z.number().int().positive().optional(),
13
+ tier: z.string().optional(),
14
+ configPath: z.string().optional(),
15
+ });
16
+ const ServerConfigSchema = z.object({
17
+ host: z.string().default("127.0.0.1"),
18
+ port: z.number().int().min(1).max(65535).default(4519),
19
+ stateDir: z.string().default(".usertrust-server"),
20
+ enforcement: z.enum(["enforce", "evaluate_only"]).default("enforce"),
21
+ pendingTtlMs: z.number().int().positive().default(300_000),
22
+ dryRun: z.boolean().default(false),
23
+ tenants: z.array(TenantSchema).min(1),
24
+ });
25
+ export function hashKey(key) {
26
+ return createHash("sha256").update(key, "utf-8").digest("hex");
27
+ }
28
+ export async function loadServerConfig(path) {
29
+ const raw = await readFile(path, "utf-8");
30
+ const config = ServerConfigSchema.parse(JSON.parse(raw));
31
+ const ids = new Set();
32
+ const keyHashes = new Set();
33
+ for (const tenant of config.tenants) {
34
+ if (ids.has(tenant.id)) {
35
+ throw new Error(`duplicate tenant id: ${tenant.id}`);
36
+ }
37
+ ids.add(tenant.id);
38
+ // A duplicated keyHash would silently resolve to the last matching tenant
39
+ // (resolveTenant keeps the last match), misrouting auth. Reject it.
40
+ if (keyHashes.has(tenant.keyHash)) {
41
+ throw new Error(`duplicate tenant keyHash: ${tenant.id}`);
42
+ }
43
+ keyHashes.add(tenant.keyHash);
44
+ }
45
+ return config;
46
+ }
47
+ /** Constant-time key lookup: hash the presented key, compare against every tenant. */
48
+ export function resolveTenant(config, bearerKey) {
49
+ const presented = Buffer.from(hashKey(bearerKey), "hex");
50
+ let match = null;
51
+ for (const tenant of config.tenants) {
52
+ const expected = Buffer.from(tenant.keyHash, "hex");
53
+ if (expected.length === presented.length && timingSafeEqual(expected, presented)) {
54
+ match = tenant;
55
+ }
56
+ }
57
+ return match;
58
+ }
59
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ export type ServerEvent = {
2
+ type: "authorized";
3
+ transferId: string;
4
+ model: string;
5
+ estimatedCost: number;
6
+ at: string;
7
+ } | {
8
+ type: "settled";
9
+ transferId: string;
10
+ cost: number;
11
+ budgetRemaining: number;
12
+ at: string;
13
+ } | {
14
+ type: "aborted";
15
+ transferId: string;
16
+ reason: string;
17
+ at: string;
18
+ } | {
19
+ type: "denied";
20
+ error: string;
21
+ reason: string;
22
+ shadow: boolean;
23
+ at: string;
24
+ } | {
25
+ type: "pending_expired";
26
+ transferId: string;
27
+ at: string;
28
+ };
29
+ type Listener = (event: ServerEvent) => void;
30
+ export declare class EventBus {
31
+ private readonly listeners;
32
+ subscribe(tenantId: string, listener: Listener): () => void;
33
+ publish(tenantId: string, event: ServerEvent): void;
34
+ }
35
+ export {};
36
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9E;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAE7C,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAE9D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;IAe3D,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;CAWnD"}
package/dist/events.js ADDED
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ export class EventBus {
4
+ listeners = new Map();
5
+ subscribe(tenantId, listener) {
6
+ let set = this.listeners.get(tenantId);
7
+ if (!set) {
8
+ set = new Set();
9
+ this.listeners.set(tenantId, set);
10
+ }
11
+ set.add(listener);
12
+ return () => {
13
+ set.delete(listener);
14
+ if (set.size === 0) {
15
+ this.listeners.delete(tenantId);
16
+ }
17
+ };
18
+ }
19
+ publish(tenantId, event) {
20
+ const set = this.listeners.get(tenantId);
21
+ if (!set)
22
+ return;
23
+ for (const listener of set) {
24
+ try {
25
+ listener(event);
26
+ }
27
+ catch {
28
+ // A broken subscriber must never break governance event delivery.
29
+ }
30
+ }
31
+ }
32
+ }
33
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAWjC,MAAM,OAAO,QAAQ;IACH,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE9D,SAAS,CAAC,QAAgB,EAAE,QAAkB;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG,EAAE,CAAC;YACV,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,GAAG,EAAE;YACX,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrB,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;QACF,CAAC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,QAAgB,EAAE,KAAkB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,KAAK,MAAM,QAAQ,IAAI,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACJ,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACR,kEAAkE;YACnE,CAAC;QACF,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,11 @@
1
+ export { hashKey, loadServerConfig, resolveTenant } from "./config.js";
2
+ export type { ServerConfig, TenantConfig } from "./config.js";
3
+ export { EventBus } from "./events.js";
4
+ export type { ServerEvent } from "./events.js";
5
+ export { GovernorPool } from "./pool.js";
6
+ export type { GovernorFactory } from "./pool.js";
7
+ export { createUsertrustServer } from "./server.js";
8
+ export type { UsertrustServer } from "./server.js";
9
+ export { AbortRequestSchema, AuthorizeRequestSchema, SettleRequestSchema, toHttpError, } from "./wire.js";
10
+ export type { AuthorizeRequest, AuthorizeResponse, SettleRequest, ShadowResponse } from "./wire.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EACN,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,WAAW,GACX,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ export { hashKey, loadServerConfig, resolveTenant } from "./config.js";
4
+ export { EventBus } from "./events.js";
5
+ export { GovernorPool } from "./pool.js";
6
+ export { createUsertrustServer } from "./server.js";
7
+ export { AbortRequestSchema, AuthorizeRequestSchema, SettleRequestSchema, toHttpError, } from "./wire.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACN,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,WAAW,GACX,MAAM,WAAW,CAAC"}
package/dist/pool.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { Governor, TrustOpts } from "usertrust";
2
+ import type { ServerConfig, TenantConfig } from "./config.js";
3
+ export type GovernorFactory = (opts: TrustOpts) => Promise<Governor>;
4
+ /**
5
+ * Lazy per-tenant Governor instances. Tenant isolation comes from per-tenant
6
+ * vaultBase directories (separate audit chains + spend ledgers) and per-tenant
7
+ * budget/tier overrides. The factory is injectable for tests.
8
+ */
9
+ export declare class GovernorPool {
10
+ private readonly config;
11
+ private readonly factory;
12
+ private readonly governors;
13
+ constructor(config: ServerConfig, factory?: GovernorFactory);
14
+ get(tenant: TenantConfig): Promise<Governor>;
15
+ destroyAll(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAErE;;;;GAIG;AACH,qBAAa,YAAY;IAIvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;gBAGhD,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,eAAgD;IAG3E,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC;IAmBtC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAKjC"}
package/dist/pool.js ADDED
@@ -0,0 +1,46 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ import { join } from "node:path";
4
+ import { createGovernor } from "usertrust";
5
+ /**
6
+ * Lazy per-tenant Governor instances. Tenant isolation comes from per-tenant
7
+ * vaultBase directories (separate audit chains + spend ledgers) and per-tenant
8
+ * budget/tier overrides. The factory is injectable for tests.
9
+ */
10
+ export class GovernorPool {
11
+ config;
12
+ factory;
13
+ governors = new Map();
14
+ constructor(config, factory = (opts) => createGovernor(opts)) {
15
+ this.config = config;
16
+ this.factory = factory;
17
+ }
18
+ get(tenant) {
19
+ const existing = this.governors.get(tenant.id);
20
+ if (existing)
21
+ return existing;
22
+ const opts = {
23
+ vaultBase: join(this.config.stateDir, tenant.id),
24
+ dryRun: this.config.dryRun,
25
+ };
26
+ if (tenant.budget !== undefined)
27
+ opts.budget = tenant.budget;
28
+ if (tenant.tier !== undefined)
29
+ opts.tier = tenant.tier;
30
+ if (tenant.configPath !== undefined)
31
+ opts.configPath = tenant.configPath;
32
+ const created = this.factory(opts).catch((err) => {
33
+ // Failed creation must not poison the cache.
34
+ this.governors.delete(tenant.id);
35
+ throw err;
36
+ });
37
+ this.governors.set(tenant.id, created);
38
+ return created;
39
+ }
40
+ async destroyAll() {
41
+ const all = [...this.governors.values()];
42
+ this.governors.clear();
43
+ await Promise.allSettled(all.map(async (p) => (await p).destroy()));
44
+ }
45
+ }
46
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAK3C;;;;GAIG;AACH,MAAM,OAAO,YAAY;IAIN;IACA;IAJD,SAAS,GAAG,IAAI,GAAG,EAA6B,CAAC;IAElE,YACkB,MAAoB,EACpB,UAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC;QADzD,WAAM,GAAN,MAAM,CAAc;QACpB,YAAO,GAAP,OAAO,CAAkD;IACxE,CAAC;IAEJ,GAAG,CAAC,MAAoB;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,IAAI,GAAc;YACvB,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;YAChD,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;SAC1B,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7D,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACvD,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACzE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACzD,6CAA6C;YAC7C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACjC,MAAM,GAAG,CAAC;QACX,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAAU;QACf,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;CACD"}
@@ -0,0 +1,19 @@
1
+ import type { ServerConfig } from "./config.js";
2
+ import { EventBus } from "./events.js";
3
+ import type { GovernorFactory } from "./pool.js";
4
+ import { GovernorPool } from "./pool.js";
5
+ export interface UsertrustServer {
6
+ listen(): Promise<{
7
+ port: number;
8
+ }>;
9
+ close(): Promise<void>;
10
+ readonly bus: EventBus;
11
+ readonly pool: GovernorPool;
12
+ pendingCount(): number;
13
+ sweepExpired(now?: number): Promise<number>;
14
+ }
15
+ export declare function createUsertrustServer(opts: {
16
+ config: ServerConfig;
17
+ factory?: GovernorFactory;
18
+ }): UsertrustServer;
19
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAuBzC,MAAM,WAAW,eAAe;IAC/B,MAAM,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,YAAY,IAAI,MAAM,CAAC;IACvB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC1B,GAAG,eAAe,CA0WlB"}
package/dist/server.js ADDED
@@ -0,0 +1,370 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ import { randomUUID } from "node:crypto";
4
+ import { createServer } from "node:http";
5
+ import { resolveTenant } from "./config.js";
6
+ import { EventBus } from "./events.js";
7
+ import { GovernorPool } from "./pool.js";
8
+ import { AbortRequestSchema, AuthorizeRequestSchema, SettleRequestSchema, toHttpError, } from "./wire.js";
9
+ const MAX_BODY_BYTES = 1024 * 1024;
10
+ const SERVER_NAME = "usertrust-server";
11
+ const SERVER_VERSION = "1.3.0";
12
+ const SWEEP_INTERVAL_MS = 30_000;
13
+ /** Max concurrent SSE streams a single tenant may hold open at once. */
14
+ const MAX_SSE_PER_TENANT = 8;
15
+ /** Drop an SSE subscriber whose kernel send buffer backs up past this. */
16
+ const MAX_SSE_BUFFER_BYTES = 1024 * 1024;
17
+ export function createUsertrustServer(opts) {
18
+ const { config } = opts;
19
+ const bus = new EventBus();
20
+ const pool = opts.factory ? new GovernorPool(config, opts.factory) : new GovernorPool(config);
21
+ const pending = new Map();
22
+ // Live SSE stream count per tenant id, enforcing MAX_SSE_PER_TENANT.
23
+ const sseCounts = new Map();
24
+ let httpServer;
25
+ let sweeper;
26
+ function sendJson(res, status, body) {
27
+ const payload = JSON.stringify(body);
28
+ res.writeHead(status, {
29
+ "content-type": "application/json",
30
+ "content-length": Buffer.byteLength(payload),
31
+ });
32
+ res.end(payload);
33
+ }
34
+ function readBody(req) {
35
+ return new Promise((resolve, reject) => {
36
+ const chunks = [];
37
+ let size = 0;
38
+ req.on("data", (chunk) => {
39
+ size += chunk.length;
40
+ if (size > MAX_BODY_BYTES) {
41
+ // Stop buffering and let the caller answer 413; destroying the
42
+ // socket here would reset the connection before the response
43
+ // can be flushed to the client.
44
+ req.removeAllListeners("data");
45
+ req.removeAllListeners("end");
46
+ resolve(null);
47
+ return;
48
+ }
49
+ chunks.push(chunk);
50
+ });
51
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
52
+ req.on("error", reject);
53
+ });
54
+ }
55
+ /** Extract the bearer key. Empty or whitespace-only tokens are rejected before hashing. */
56
+ function bearerKey(req) {
57
+ const header = req.headers.authorization;
58
+ if (!header?.startsWith("Bearer "))
59
+ return null;
60
+ const token = header.slice("Bearer ".length);
61
+ if (token.trim() === "")
62
+ return null;
63
+ return token;
64
+ }
65
+ async function handleAuthorize(tenant, body, res) {
66
+ const parsed = AuthorizeRequestSchema.safeParse(body);
67
+ if (!parsed.success) {
68
+ sendJson(res, 400, {
69
+ error: "bad_request",
70
+ reason: parsed.error.issues[0]?.message ?? "invalid",
71
+ });
72
+ return;
73
+ }
74
+ const governor = await pool.get(tenant);
75
+ try {
76
+ const auth = await governor.authorize(parsed.data);
77
+ pending.set(auth.transferId, { auth, tenantId: tenant.id, createdAt: Date.now() });
78
+ bus.publish(tenant.id, {
79
+ type: "authorized",
80
+ transferId: auth.transferId,
81
+ model: auth.model,
82
+ estimatedCost: auth.estimatedCost,
83
+ at: new Date().toISOString(),
84
+ });
85
+ sendJson(res, 200, {
86
+ transferId: auth.transferId,
87
+ estimatedCost: auth.estimatedCost,
88
+ model: auth.model,
89
+ createdAt: auth.createdAt,
90
+ });
91
+ }
92
+ catch (err) {
93
+ const mapped = toHttpError(err);
94
+ const shadow = config.enforcement === "evaluate_only" && mapped.status !== 500;
95
+ bus.publish(tenant.id, {
96
+ type: "denied",
97
+ error: mapped.body.error,
98
+ reason: mapped.body.reason,
99
+ shadow,
100
+ at: new Date().toISOString(),
101
+ });
102
+ if (shadow) {
103
+ // Shadow ids are NOT transferIds: no reservation exists, so they can
104
+ // never be settled or aborted (those routes 404 on unknown ids).
105
+ sendJson(res, 200, {
106
+ shadow: true,
107
+ shadowId: `shadow_${randomUUID()}`,
108
+ decision: "would_deny",
109
+ reason: mapped.body.reason,
110
+ });
111
+ return;
112
+ }
113
+ sendJson(res, mapped.status, mapped.body);
114
+ }
115
+ }
116
+ async function handleSettle(tenant, body, res) {
117
+ const parsed = SettleRequestSchema.safeParse(body);
118
+ if (!parsed.success) {
119
+ sendJson(res, 400, { error: "bad_request", reason: "invalid settle request" });
120
+ return;
121
+ }
122
+ const { transferId, ...usage } = parsed.data;
123
+ const entry = pending.get(transferId);
124
+ if (!entry || entry.tenantId !== tenant.id) {
125
+ sendJson(res, 404, { error: "not_found", reason: "unknown transferId" });
126
+ return;
127
+ }
128
+ // Atomic claim: first concurrent caller wins; a governor failure re-inserts
129
+ // the entry so a transient settle error stays retryable.
130
+ pending.delete(transferId);
131
+ try {
132
+ const governor = await pool.get(tenant);
133
+ const receipt = await governor.settle(entry.auth, usage);
134
+ bus.publish(tenant.id, {
135
+ type: "settled",
136
+ transferId,
137
+ cost: receipt.cost,
138
+ budgetRemaining: receipt.budgetRemaining,
139
+ at: new Date().toISOString(),
140
+ });
141
+ sendJson(res, 200, receipt);
142
+ }
143
+ catch (err) {
144
+ pending.set(transferId, entry);
145
+ const mapped = toHttpError(err);
146
+ sendJson(res, mapped.status, mapped.body);
147
+ }
148
+ }
149
+ async function handleAbort(tenant, body, res) {
150
+ const parsed = AbortRequestSchema.safeParse(body);
151
+ if (!parsed.success) {
152
+ sendJson(res, 400, { error: "bad_request", reason: "invalid abort request" });
153
+ return;
154
+ }
155
+ const { transferId } = parsed.data;
156
+ const entry = pending.get(transferId);
157
+ if (!entry || entry.tenantId !== tenant.id) {
158
+ sendJson(res, 404, { error: "not_found", reason: "unknown transferId" });
159
+ return;
160
+ }
161
+ // Atomic claim with re-insert on failure (same contract as settle).
162
+ pending.delete(transferId);
163
+ try {
164
+ const governor = await pool.get(tenant);
165
+ await governor.abort(entry.auth, parsed.data.error);
166
+ }
167
+ catch (err) {
168
+ pending.set(transferId, entry);
169
+ const mapped = toHttpError(err);
170
+ sendJson(res, mapped.status, mapped.body);
171
+ return;
172
+ }
173
+ bus.publish(tenant.id, {
174
+ type: "aborted",
175
+ transferId,
176
+ reason: parsed.data.error ?? "aborted",
177
+ at: new Date().toISOString(),
178
+ });
179
+ sendJson(res, 200, { aborted: true, transferId });
180
+ }
181
+ function handleEvents(tenant, req, res) {
182
+ // Per-tenant fan-out cap: a tenant opening unbounded SSE streams would pin
183
+ // memory and file descriptors. Reject past the cap; the slot is released
184
+ // when the connection closes (below).
185
+ const active = sseCounts.get(tenant.id) ?? 0;
186
+ if (active >= MAX_SSE_PER_TENANT) {
187
+ sendJson(res, 429, { error: "too_many_streams", reason: "SSE subscriber limit reached" });
188
+ return;
189
+ }
190
+ sseCounts.set(tenant.id, active + 1);
191
+ let released = false;
192
+ const release = () => {
193
+ if (released)
194
+ return;
195
+ released = true;
196
+ const remaining = (sseCounts.get(tenant.id) ?? 1) - 1;
197
+ if (remaining <= 0)
198
+ sseCounts.delete(tenant.id);
199
+ else
200
+ sseCounts.set(tenant.id, remaining);
201
+ };
202
+ res.writeHead(200, {
203
+ "content-type": "text/event-stream",
204
+ "cache-control": "no-cache",
205
+ connection: "keep-alive",
206
+ });
207
+ res.write(": connected\n\n");
208
+ // SSE is best-effort operational telemetry — a broken pipe drops the
209
+ // subscriber, it never breaks governance processing.
210
+ const unsubscribe = bus.subscribe(tenant.id, (event) => {
211
+ try {
212
+ const ok = res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
213
+ // Backpressure guard: a subscriber that cannot keep up (kernel send
214
+ // buffer backed up past 1 MiB) is dropped rather than allowed to grow
215
+ // unbounded. destroy() fires the close handler → unsubscribe + release.
216
+ if (!ok && res.writableLength > MAX_SSE_BUFFER_BYTES)
217
+ res.destroy();
218
+ }
219
+ catch {
220
+ unsubscribe();
221
+ }
222
+ });
223
+ const heartbeat = setInterval(() => {
224
+ try {
225
+ res.write(": heartbeat\n\n");
226
+ }
227
+ catch {
228
+ clearInterval(heartbeat);
229
+ unsubscribe();
230
+ }
231
+ }, 15_000);
232
+ heartbeat.unref();
233
+ req.on("close", () => {
234
+ clearInterval(heartbeat);
235
+ unsubscribe();
236
+ release();
237
+ });
238
+ }
239
+ async function route(req, res) {
240
+ const url = req.url ?? "/";
241
+ if (req.method === "GET" && url === "/v1/health") {
242
+ sendJson(res, 200, { ok: true, name: SERVER_NAME, version: SERVER_VERSION });
243
+ return;
244
+ }
245
+ const key = bearerKey(req);
246
+ const tenant = key ? resolveTenant(config, key) : null;
247
+ if (!tenant) {
248
+ sendJson(res, 401, { error: "unauthorized", reason: "missing or invalid bearer key" });
249
+ return;
250
+ }
251
+ if (req.method === "GET" && url === "/v1/budget") {
252
+ const governor = await pool.get(tenant);
253
+ sendJson(res, 200, { remaining: governor.budgetRemaining() });
254
+ return;
255
+ }
256
+ if (req.method === "GET" && url === "/v1/events") {
257
+ handleEvents(tenant, req, res);
258
+ return;
259
+ }
260
+ if (req.method === "POST" &&
261
+ (url === "/v1/authorize" || url === "/v1/settle" || url === "/v1/abort")) {
262
+ const raw = await readBody(req);
263
+ if (raw === null) {
264
+ // The rest of the oversized body is never read — close the
265
+ // connection after the response so the socket cannot be reused.
266
+ res.setHeader("connection", "close");
267
+ sendJson(res, 413, { error: "too_large", reason: "body exceeds 1 MiB" });
268
+ return;
269
+ }
270
+ let body;
271
+ try {
272
+ body = JSON.parse(raw === "" ? "{}" : raw);
273
+ }
274
+ catch {
275
+ sendJson(res, 400, { error: "bad_request", reason: "invalid JSON" });
276
+ return;
277
+ }
278
+ if (url === "/v1/authorize")
279
+ await handleAuthorize(tenant, body, res);
280
+ else if (url === "/v1/settle")
281
+ await handleSettle(tenant, body, res);
282
+ else
283
+ await handleAbort(tenant, body, res);
284
+ return;
285
+ }
286
+ sendJson(res, 404, { error: "not_found", reason: "unknown route" });
287
+ }
288
+ async function abortEntry(transferId, entry, reason) {
289
+ const tenant = config.tenants.find((t) => t.id === entry.tenantId);
290
+ if (tenant) {
291
+ try {
292
+ const governor = await pool.get(tenant);
293
+ await governor.abort(entry.auth, reason);
294
+ }
295
+ catch {
296
+ // Best-effort — the Governor's own destroy()/reconciliation voids
297
+ // anything the control plane fails to abort here.
298
+ }
299
+ }
300
+ }
301
+ async function sweepExpired(now = Date.now()) {
302
+ let swept = 0;
303
+ for (const [transferId, entry] of pending) {
304
+ if (now - entry.createdAt < config.pendingTtlMs)
305
+ continue;
306
+ pending.delete(transferId);
307
+ swept += 1;
308
+ await abortEntry(transferId, entry, "pending TTL expired");
309
+ bus.publish(entry.tenantId, {
310
+ type: "pending_expired",
311
+ transferId,
312
+ at: new Date().toISOString(),
313
+ });
314
+ }
315
+ return swept;
316
+ }
317
+ return {
318
+ bus,
319
+ pool,
320
+ pendingCount: () => pending.size,
321
+ sweepExpired,
322
+ listen() {
323
+ return new Promise((resolve, reject) => {
324
+ httpServer = createServer((req, res) => {
325
+ route(req, res).catch((err) => {
326
+ const mapped = toHttpError(err);
327
+ if (!res.headersSent)
328
+ sendJson(res, mapped.status, mapped.body);
329
+ });
330
+ });
331
+ httpServer.on("error", reject);
332
+ httpServer.listen(config.port, config.host, () => {
333
+ sweeper = setInterval(() => {
334
+ void sweepExpired();
335
+ }, SWEEP_INTERVAL_MS);
336
+ sweeper.unref();
337
+ const address = httpServer?.address();
338
+ const port = typeof address === "object" && address !== null ? address.port : config.port;
339
+ resolve({ port });
340
+ });
341
+ });
342
+ },
343
+ async close() {
344
+ if (sweeper)
345
+ clearInterval(sweeper);
346
+ // Abort every remaining pending hold (best-effort) so the control plane
347
+ // and the ledger stay consistent; Governor.destroy() voids at the ledger
348
+ // layer as the backstop.
349
+ const remaining = [...pending.entries()];
350
+ pending.clear();
351
+ for (const [transferId, entry] of remaining) {
352
+ await abortEntry(transferId, entry, "server shutdown");
353
+ bus.publish(entry.tenantId, {
354
+ type: "aborted",
355
+ transferId,
356
+ reason: "server shutdown",
357
+ at: new Date().toISOString(),
358
+ });
359
+ }
360
+ await new Promise((resolve) => {
361
+ if (!httpServer)
362
+ return resolve();
363
+ httpServer.closeAllConnections();
364
+ httpServer.close(() => resolve());
365
+ });
366
+ await pool.destroyAll();
367
+ },
368
+ };
369
+ }
370
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/wire.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ export declare const AuthorizeRequestSchema: z.ZodObject<{
3
+ model: z.ZodString;
4
+ estimatedInputTokens: z.ZodOptional<z.ZodNumber>;
5
+ maxOutputTokens: z.ZodOptional<z.ZodNumber>;
6
+ messages: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
7
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
+ actor: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ model: string;
11
+ params?: Record<string, unknown> | undefined;
12
+ estimatedInputTokens?: number | undefined;
13
+ maxOutputTokens?: number | undefined;
14
+ messages?: unknown[] | undefined;
15
+ actor?: string | undefined;
16
+ }, {
17
+ model: string;
18
+ params?: Record<string, unknown> | undefined;
19
+ estimatedInputTokens?: number | undefined;
20
+ maxOutputTokens?: number | undefined;
21
+ messages?: unknown[] | undefined;
22
+ actor?: string | undefined;
23
+ }>;
24
+ export declare const SettleRequestSchema: z.ZodObject<{
25
+ transferId: z.ZodString;
26
+ inputTokens: z.ZodOptional<z.ZodNumber>;
27
+ outputTokens: z.ZodOptional<z.ZodNumber>;
28
+ chunksDelivered: z.ZodOptional<z.ZodNumber>;
29
+ usageSource: z.ZodOptional<z.ZodEnum<["provider", "estimated"]>>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ transferId: string;
32
+ inputTokens?: number | undefined;
33
+ outputTokens?: number | undefined;
34
+ chunksDelivered?: number | undefined;
35
+ usageSource?: "provider" | "estimated" | undefined;
36
+ }, {
37
+ transferId: string;
38
+ inputTokens?: number | undefined;
39
+ outputTokens?: number | undefined;
40
+ chunksDelivered?: number | undefined;
41
+ usageSource?: "provider" | "estimated" | undefined;
42
+ }>;
43
+ export declare const AbortRequestSchema: z.ZodObject<{
44
+ transferId: z.ZodString;
45
+ error: z.ZodOptional<z.ZodString>;
46
+ }, "strip", z.ZodTypeAny, {
47
+ transferId: string;
48
+ error?: string | undefined;
49
+ }, {
50
+ transferId: string;
51
+ error?: string | undefined;
52
+ }>;
53
+ export type AuthorizeRequest = z.infer<typeof AuthorizeRequestSchema>;
54
+ export type SettleRequest = z.infer<typeof SettleRequestSchema>;
55
+ export type AbortRequest = z.infer<typeof AbortRequestSchema>;
56
+ export interface AuthorizeResponse {
57
+ transferId: string;
58
+ estimatedCost: number;
59
+ model: string;
60
+ createdAt: number;
61
+ }
62
+ /**
63
+ * Shadow (evaluate_only) response. Carries a `shadowId` — deliberately NOT a
64
+ * `transferId` — because no reservation exists: shadow ids cannot be settled
65
+ * or aborted, and hitting those routes with one 404s naturally.
66
+ */
67
+ export interface ShadowResponse {
68
+ shadow: true;
69
+ shadowId: string;
70
+ decision: "would_deny";
71
+ reason: string;
72
+ }
73
+ /**
74
+ * Map governance errors to HTTP responses. Unknown errors return an opaque
75
+ * 500 — internal messages (which may embed key material or file paths) are
76
+ * never forwarded to clients.
77
+ */
78
+ export declare function toHttpError(err: unknown): {
79
+ status: number;
80
+ body: {
81
+ error: string;
82
+ reason: string;
83
+ };
84
+ };
85
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;EAOjC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;EAM9B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;EAG7B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC,CAiBA"}
package/dist/wire.js ADDED
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Usertools, Inc.
3
+ import { AnomalyError, InsufficientBalanceError, PolicyDeniedError } from "usertrust";
4
+ import { z } from "zod";
5
+ export const AuthorizeRequestSchema = z.object({
6
+ model: z.string().min(1),
7
+ estimatedInputTokens: z.number().int().nonnegative().optional(),
8
+ maxOutputTokens: z.number().int().positive().optional(),
9
+ messages: z.array(z.unknown()).optional(),
10
+ params: z.record(z.unknown()).optional(),
11
+ actor: z.string().optional(),
12
+ });
13
+ export const SettleRequestSchema = z.object({
14
+ transferId: z.string().min(1),
15
+ inputTokens: z.number().int().nonnegative().optional(),
16
+ outputTokens: z.number().int().nonnegative().optional(),
17
+ chunksDelivered: z.number().int().nonnegative().optional(),
18
+ usageSource: z.enum(["provider", "estimated"]).optional(),
19
+ });
20
+ export const AbortRequestSchema = z.object({
21
+ transferId: z.string().min(1),
22
+ error: z.string().optional(),
23
+ });
24
+ /**
25
+ * Map governance errors to HTTP responses. Unknown errors return an opaque
26
+ * 500 — internal messages (which may embed key material or file paths) are
27
+ * never forwarded to clients.
28
+ */
29
+ export function toHttpError(err) {
30
+ if (err instanceof PolicyDeniedError) {
31
+ return { status: 403, body: { error: "policy_denied", reason: err.reason } };
32
+ }
33
+ if (err instanceof InsufficientBalanceError) {
34
+ return {
35
+ status: 402,
36
+ body: {
37
+ error: "budget_exceeded",
38
+ reason: `need ${err.required}, have ${err.available}`,
39
+ },
40
+ };
41
+ }
42
+ if (err instanceof AnomalyError) {
43
+ return { status: 429, body: { error: "anomaly", reason: err.message } };
44
+ }
45
+ return { status: 500, body: { error: "internal", reason: "internal error" } };
46
+ }
47
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACtF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAC/D,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACvD,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACtD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACvD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAC1D,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;CACzD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAyBH;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IAIvC,IAAI,GAAG,YAAY,iBAAiB,EAAE,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IAC9E,CAAC;IACD,IAAI,GAAG,YAAY,wBAAwB,EAAE,CAAC;QAC7C,OAAO;YACN,MAAM,EAAE,GAAG;YACX,IAAI,EAAE;gBACL,KAAK,EAAE,iBAAiB;gBACxB,MAAM,EAAE,QAAQ,GAAG,CAAC,QAAQ,UAAU,GAAG,CAAC,SAAS,EAAE;aACrD;SACD,CAAC;IACH,CAAC;IACD,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,CAAC;AAC/E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "usertrust-server",
3
+ "version": "1.4.0",
4
+ "description": "Self-hostable HTTP control plane for the usertrust governance kernel",
5
+ "license": "Apache-2.0",
6
+ "author": "Usertools, Inc. <hello@usertools.ai> (https://usertrust.ai)",
7
+ "homepage": "https://usertrust.ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/usertools-ai/usertrust.git",
11
+ "directory": "packages/server"
12
+ },
13
+ "bugs": "https://github.com/usertools-ai/usertrust/issues",
14
+ "keywords": ["ai", "governance", "llm", "agent", "server", "self-hosted", "control-plane"],
15
+ "type": "module",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "import": "./dist/index.js",
21
+ "types": "./dist/index.d.ts"
22
+ }
23
+ },
24
+ "bin": {
25
+ "usertrust-server": "./dist/cli/main.js"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -b",
29
+ "prepublishOnly": "tsc -b"
30
+ },
31
+ "files": ["dist"],
32
+ "dependencies": {
33
+ "usertrust": "*",
34
+ "zod": "^3.23.0"
35
+ }
36
+ }