@nifrajs/core 1.10.0 → 1.11.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.
Files changed (63) hide show
  1. package/dist/assurance.d.ts +13 -0
  2. package/dist/assurance.d.ts.map +1 -1
  3. package/dist/assurance.js +20 -0
  4. package/dist/assurance.js.map +1 -1
  5. package/dist/capabilities.d.ts +30 -1
  6. package/dist/capabilities.d.ts.map +1 -1
  7. package/dist/capabilities.js +32 -6
  8. package/dist/capabilities.js.map +1 -1
  9. package/dist/classification.d.ts +20 -0
  10. package/dist/classification.d.ts.map +1 -1
  11. package/dist/classification.js +120 -0
  12. package/dist/classification.js.map +1 -1
  13. package/dist/errors.d.ts +1 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js.map +1 -1
  16. package/dist/idempotency.d.ts +59 -12
  17. package/dist/idempotency.d.ts.map +1 -1
  18. package/dist/idempotency.js +191 -20
  19. package/dist/idempotency.js.map +1 -1
  20. package/dist/index.d.ts +5 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/invariants.d.ts +66 -0
  25. package/dist/invariants.d.ts.map +1 -0
  26. package/dist/invariants.js +483 -0
  27. package/dist/invariants.js.map +1 -0
  28. package/dist/ledger.d.ts +154 -0
  29. package/dist/ledger.d.ts.map +1 -0
  30. package/dist/ledger.js +271 -0
  31. package/dist/ledger.js.map +1 -0
  32. package/dist/manifest.d.ts +81 -0
  33. package/dist/manifest.d.ts.map +1 -0
  34. package/dist/manifest.js +394 -0
  35. package/dist/manifest.js.map +1 -0
  36. package/dist/mount.d.ts +18 -0
  37. package/dist/mount.d.ts.map +1 -0
  38. package/dist/mount.js +11 -0
  39. package/dist/mount.js.map +1 -0
  40. package/dist/reflection.d.ts +3 -3
  41. package/dist/reflection.d.ts.map +1 -1
  42. package/dist/reflection.js +5 -5
  43. package/dist/reflection.js.map +1 -1
  44. package/dist/server/context.d.ts +17 -1
  45. package/dist/server/context.d.ts.map +1 -1
  46. package/dist/server/server.d.ts +23 -2
  47. package/dist/server/server.d.ts.map +1 -1
  48. package/dist/server/server.js +179 -26
  49. package/dist/server/server.js.map +1 -1
  50. package/package.json +26 -1
  51. package/src/assurance.ts +33 -0
  52. package/src/capabilities.ts +60 -4
  53. package/src/classification.ts +145 -0
  54. package/src/errors.ts +1 -0
  55. package/src/idempotency.ts +245 -30
  56. package/src/index.ts +5 -1
  57. package/src/invariants.ts +600 -0
  58. package/src/ledger.ts +430 -0
  59. package/src/manifest.ts +543 -0
  60. package/src/mount.ts +24 -0
  61. package/src/reflection.ts +11 -7
  62. package/src/server/context.ts +20 -1
  63. package/src/server/server.ts +254 -30
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Effect ledger: a per-request, append-only, ordered record of side-effect intents and outcomes.
3
+ *
4
+ * Every owned effect seam (a repository write, an external call, an email send) records one entry via
5
+ * the `useCapability` beacon. The entry type is deliberately **token-only**: capability ids, phase,
6
+ * an adapter/resource token, dimensionless cost counters, an optional keyed digest, and an error code.
7
+ * There is no free-form payload field — request bodies, rows, and values cannot enter the ledger, so
8
+ * redaction holds by construction rather than by filtering. The sealed ledger carries the **route
9
+ * pattern** (`/users/:id`), never the concrete URL, so path parameters cannot leak either.
10
+ *
11
+ * The ledger is the substrate that audit, replay, reconciliation, and cost accounting read.
12
+ * This module ships the primitive: the entry contract, a bounded per-request ledger with an optional
13
+ * tamper-evident hash chain, a sink seam, an in-memory sink, and a keyed digest helper. Durable,
14
+ * encrypted, or retention-managed sinks implement the same {@link LedgerSink} seam externally.
15
+ */
16
+ /** Lifecycle phase of one effect. `intent` precedes execution; the rest describe its outcome. */
17
+ export type EffectPhase = "intent" | "committed" | "failed" | "compensated";
18
+ /**
19
+ * Dimensionless resource counters (`{ ms: 12, calls: 1, bytes: 512 }`). Counters carry *how much
20
+ * resource* an effect consumed; mapping counters to money/pricing is deliberately out of scope here.
21
+ */
22
+ export type EffectCost = Readonly<Record<string, number>>;
23
+ /** Caller-supplied fields for one entry. Everything else (`seq`, `at`) is assigned by the ledger. */
24
+ export interface EffectEntryInput {
25
+ /** Capability token (`db.write`, `payments.charge`). Must be a valid capability id. */
26
+ readonly capability: string;
27
+ /** Default `"intent"`. Record `committed`/`failed`/`compensated` only after the outcome is known. */
28
+ readonly phase?: EffectPhase;
29
+ /** Adapter/resource token (`repo:orders`, `provider:payments`). A token — never a value or a row. */
30
+ readonly target?: string;
31
+ /** Dimensionless counters; see {@link EffectCost}. At most {@link MAX_COST_AXES} axes. */
32
+ readonly cost?: EffectCost;
33
+ /** Optional keyed-HMAC digest of the effect payload (see {@link computeEffectDigest}); hex, 64 chars. */
34
+ readonly digest?: string;
35
+ /** Outcome error as a bounded token code — never a message, never a stack. */
36
+ readonly error?: {
37
+ readonly code: string;
38
+ };
39
+ }
40
+ /** One recorded effect. Frozen; token-only by construction (no payload field exists). */
41
+ export interface EffectEntry {
42
+ /** Monotonic position within the request (0-based). The replay/simulation order. */
43
+ readonly seq: number;
44
+ /** Milliseconds from the ledger's (injectable) clock at append time. */
45
+ readonly at: number;
46
+ readonly capability: string;
47
+ readonly phase: EffectPhase;
48
+ readonly target?: string;
49
+ readonly cost?: EffectCost;
50
+ readonly digest?: string;
51
+ readonly error?: {
52
+ readonly code: string;
53
+ };
54
+ }
55
+ /** Tamper-evidence over the route identity, declarations, and sealed entries. */
56
+ export interface EffectChain {
57
+ /** `hashes[i]` = SHA-256(hex) over (`hashes[i-1]` + canonical entry `i`). */
58
+ readonly hashes: readonly string[];
59
+ /** Last entry hash, or the route-header hash for an empty ledger. Anchor/sign this externally. */
60
+ readonly head: string;
61
+ }
62
+ /** The immutable result of sealing a request's ledger. Token-only; safe to hand to any sink. */
63
+ export interface SealedEffectLedger {
64
+ readonly method: string;
65
+ /** The registered route pattern (`/users/:id`) — never the concrete request URL. */
66
+ readonly path: string;
67
+ readonly entries: readonly EffectEntry[];
68
+ /** The route's declared capability tokens — the runtime-enforcement view: recorded ⊆ declared is
69
+ * guaranteed by the beacon, and `declared` minus the recorded ids is the unused declaration set. */
70
+ readonly declared: readonly string[];
71
+ /** Present when the ledger was created with `chain: true`. */
72
+ readonly chain?: EffectChain;
73
+ }
74
+ /**
75
+ * Receives each sealed ledger once per request (only when it has entries). Implementations must not
76
+ * assume a payload: the ledger is token-only. A durable/tenant-scoped sink lives behind this seam.
77
+ */
78
+ export type LedgerSink = (ledger: SealedEffectLedger) => void | Promise<void>;
79
+ /** Thrown by `append` when the per-request entry bound is exceeded. Fails the request closed. */
80
+ export declare class EffectLedgerOverflowError extends Error {
81
+ readonly maxEntries: number;
82
+ constructor(maxEntries: number);
83
+ }
84
+ /** Thrown by `append` after `seal()` — e.g. an effect attempted while streaming a response body. */
85
+ export declare class EffectLedgerSealedError extends Error {
86
+ constructor();
87
+ }
88
+ /** Per-request ledger. `append` is synchronous (hot-path safe); `seal` is idempotent and async. */
89
+ export interface RequestLedger {
90
+ /** Validate, freeze, and record one entry. Throws on invalid input, overflow, or after seal. */
91
+ append(input: EffectEntryInput): EffectEntry;
92
+ /** Entries recorded so far (frozen snapshot view). */
93
+ entries(): readonly EffectEntry[];
94
+ readonly size: number;
95
+ readonly sealed: boolean;
96
+ /** Finalize the ledger (computing the chain when enabled). Idempotent — always the same result. */
97
+ seal(): Promise<SealedEffectLedger>;
98
+ }
99
+ export interface CreateRequestLedgerOptions {
100
+ /** HTTP method of the matched route. */
101
+ readonly method: string;
102
+ /** The registered route pattern — callers must never pass the concrete request URL. */
103
+ readonly path: string;
104
+ /** The route's declared capability tokens, surfaced verbatim on the sealed ledger. Default `[]`. */
105
+ readonly declared?: readonly string[];
106
+ /** Entry bound; exceeding it throws {@link EffectLedgerOverflowError}. Default {@link DEFAULT_MAX_ENTRIES}. */
107
+ readonly maxEntries?: number;
108
+ /** Compute the tamper-evident hash chain at seal. Default false. */
109
+ readonly chain?: boolean;
110
+ /** Injectable monotonic clock (ms) for deterministic tests. Default `performance.now`. */
111
+ readonly clock?: () => number;
112
+ }
113
+ /** Per-request entry bound. Generous for real handlers, small enough to stop a runaway loop. */
114
+ export declare const DEFAULT_MAX_ENTRIES = 1000;
115
+ /** Most cost axes one entry may carry. */
116
+ export declare const MAX_COST_AXES = 8;
117
+ /** Create a bounded per-request ledger. The server wires one per capability-declaring route. */
118
+ export declare function createRequestLedger(options: CreateRequestLedgerOptions): RequestLedger;
119
+ export interface MemoryLedgerSinkOptions {
120
+ /** Retain at most this many sealed ledgers (oldest evicted first). Default 1000. */
121
+ readonly maxLedgers?: number;
122
+ }
123
+ export interface MemoryLedgerSink {
124
+ readonly sink: LedgerSink;
125
+ /** Sealed ledgers received so far, oldest first. */
126
+ readonly ledgers: readonly SealedEffectLedger[];
127
+ clear(): void;
128
+ }
129
+ /** Bounded in-memory sink for tests and local development. Token-only, like every sink. */
130
+ export declare function createMemoryLedgerSink(options?: MemoryLedgerSinkOptions): MemoryLedgerSink;
131
+ /** Minimum digest key material. A short key would make the keyed digest brute-forceable. */
132
+ export declare const MIN_DIGEST_KEY_BYTES = 16;
133
+ /** Fresh random digest key (32 bytes). Per-process by default — persist one externally to correlate across restarts. */
134
+ export declare function randomEffectDigestKey(): Uint8Array;
135
+ /**
136
+ * Keyed HMAC-SHA-256 digest (hex) of an effect payload, for replay/reconciliation matching without
137
+ * storing the payload. Keyed on purpose: a bare hash of low-entropy data (an email, a flag) is
138
+ * brute-forceable and would itself leak. Digest the **whole** effect payload, never a single field.
139
+ */
140
+ export declare function computeEffectDigest(key: Uint8Array | CryptoKey, payload: Uint8Array): Promise<string>;
141
+ /** Framework wiring: attach a per-request ledger to a handler context. Not for application code. */
142
+ export declare function attachEffectLedger(context: object, ledger: RequestLedger): void;
143
+ /** The request's effect ledger, when the server enabled one for this route. Read-only access. */
144
+ export declare function effectLedgerOf(context: object): RequestLedger | undefined;
145
+ /** Server-level effect ledger configuration (see `server({ effectLedger })`). */
146
+ export interface EffectLedgerOptions {
147
+ /** Receives each request's sealed ledger (only when it recorded entries). */
148
+ readonly sink: LedgerSink;
149
+ /** Per-request entry bound. Default {@link DEFAULT_MAX_ENTRIES}. */
150
+ readonly maxEntries?: number;
151
+ /** Compute the tamper-evident hash chain at seal. Default false. */
152
+ readonly chain?: boolean;
153
+ }
154
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"}
package/dist/ledger.js ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Effect ledger: a per-request, append-only, ordered record of side-effect intents and outcomes.
3
+ *
4
+ * Every owned effect seam (a repository write, an external call, an email send) records one entry via
5
+ * the `useCapability` beacon. The entry type is deliberately **token-only**: capability ids, phase,
6
+ * an adapter/resource token, dimensionless cost counters, an optional keyed digest, and an error code.
7
+ * There is no free-form payload field — request bodies, rows, and values cannot enter the ledger, so
8
+ * redaction holds by construction rather than by filtering. The sealed ledger carries the **route
9
+ * pattern** (`/users/:id`), never the concrete URL, so path parameters cannot leak either.
10
+ *
11
+ * The ledger is the substrate that audit, replay, reconciliation, and cost accounting read.
12
+ * This module ships the primitive: the entry contract, a bounded per-request ledger with an optional
13
+ * tamper-evident hash chain, a sink seam, an in-memory sink, and a keyed digest helper. Durable,
14
+ * encrypted, or retention-managed sinks implement the same {@link LedgerSink} seam externally.
15
+ */
16
+ import { validCapabilityId } from "./internal/capability-runtime.js";
17
+ const EFFECT_PHASES = new Set(["intent", "committed", "failed", "compensated"]);
18
+ /** Thrown by `append` when the per-request entry bound is exceeded. Fails the request closed. */
19
+ export class EffectLedgerOverflowError extends Error {
20
+ maxEntries;
21
+ constructor(maxEntries) {
22
+ super(`effect ledger overflow: more than ${maxEntries} entries in one request`);
23
+ this.maxEntries = maxEntries;
24
+ this.name = "EffectLedgerOverflowError";
25
+ }
26
+ }
27
+ /** Thrown by `append` after `seal()` — e.g. an effect attempted while streaming a response body. */
28
+ export class EffectLedgerSealedError extends Error {
29
+ constructor() {
30
+ super("effect ledger is sealed: effects cannot be recorded after the response settles");
31
+ this.name = "EffectLedgerSealedError";
32
+ }
33
+ }
34
+ /** Per-request entry bound. Generous for real handlers, small enough to stop a runaway loop. */
35
+ export const DEFAULT_MAX_ENTRIES = 1_000;
36
+ /** Most cost axes one entry may carry. */
37
+ export const MAX_COST_AXES = 8;
38
+ const TARGET_MAX_LENGTH = 128;
39
+ const ERROR_CODE = /^[a-z][a-z0-9_.-]{0,63}$/;
40
+ const DIGEST_HEX = /^[0-9a-f]{64}$/;
41
+ function printableToken(value, maxLength) {
42
+ if (value.length === 0 || value.length > maxLength)
43
+ return false;
44
+ for (let index = 0; index < value.length; index++) {
45
+ const code = value.charCodeAt(index);
46
+ if (code < 33 || code > 126)
47
+ return false;
48
+ }
49
+ return true;
50
+ }
51
+ function validateCost(cost) {
52
+ const keys = Object.keys(cost);
53
+ if (keys.length > MAX_COST_AXES) {
54
+ throw new TypeError(`effect ledger: cost carries more than ${MAX_COST_AXES} axes`);
55
+ }
56
+ for (const key of keys) {
57
+ if (!validCapabilityId(key)) {
58
+ throw new TypeError(`effect ledger: invalid cost axis ${JSON.stringify(key)}`);
59
+ }
60
+ const value = cost[key];
61
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
62
+ throw new TypeError(`effect ledger: cost axis ${key} must be a finite non-negative number`);
63
+ }
64
+ }
65
+ return Object.freeze({ ...cost });
66
+ }
67
+ const defaultClock = () => performance.now();
68
+ class BoundedRequestLedger {
69
+ items = [];
70
+ method;
71
+ path;
72
+ declared;
73
+ maxEntries;
74
+ chain;
75
+ clock;
76
+ sealResult;
77
+ constructor(options) {
78
+ if (!printableToken(options.method, 16) || !options.path.startsWith("/")) {
79
+ throw new TypeError("effect ledger: method/path must be a route pattern");
80
+ }
81
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
82
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
83
+ throw new TypeError("effect ledger: maxEntries must be a positive safe integer");
84
+ }
85
+ const declared = options.declared ?? [];
86
+ for (const id of declared) {
87
+ if (!validCapabilityId(id)) {
88
+ throw new TypeError(`effect ledger: invalid declared capability ${JSON.stringify(id)}`);
89
+ }
90
+ }
91
+ this.method = options.method.toUpperCase();
92
+ this.path = options.path;
93
+ this.declared = Object.freeze([...declared]);
94
+ this.maxEntries = maxEntries;
95
+ this.chain = options.chain ?? false;
96
+ this.clock = options.clock ?? defaultClock;
97
+ }
98
+ get size() {
99
+ return this.items.length;
100
+ }
101
+ get sealed() {
102
+ return this.sealResult !== undefined;
103
+ }
104
+ append(input) {
105
+ if (this.sealResult !== undefined)
106
+ throw new EffectLedgerSealedError();
107
+ if (this.items.length >= this.maxEntries)
108
+ throw new EffectLedgerOverflowError(this.maxEntries);
109
+ if (!validCapabilityId(input.capability)) {
110
+ throw new TypeError(`effect ledger: invalid capability ${JSON.stringify(input.capability)}`);
111
+ }
112
+ const phase = input.phase ?? "intent";
113
+ if (!EFFECT_PHASES.has(phase)) {
114
+ throw new TypeError(`effect ledger: invalid phase ${JSON.stringify(phase)}`);
115
+ }
116
+ if (input.target !== undefined && !printableToken(input.target, TARGET_MAX_LENGTH)) {
117
+ throw new TypeError("effect ledger: target must be a bounded printable token");
118
+ }
119
+ if (input.digest !== undefined && !DIGEST_HEX.test(input.digest)) {
120
+ throw new TypeError("effect ledger: digest must be 64 lowercase hex characters");
121
+ }
122
+ if (input.error !== undefined && !ERROR_CODE.test(input.error.code)) {
123
+ throw new TypeError("effect ledger: error code must be a bounded lowercase token");
124
+ }
125
+ // Only the known token fields are copied — a stray `payload`-like property never survives append.
126
+ const at = this.clock();
127
+ if (!Number.isFinite(at) || at < 0) {
128
+ throw new TypeError("effect ledger: clock must return a finite non-negative number");
129
+ }
130
+ const entry = Object.freeze({
131
+ seq: this.items.length,
132
+ at,
133
+ capability: input.capability,
134
+ phase,
135
+ ...(input.target !== undefined ? { target: input.target } : {}),
136
+ ...(input.cost !== undefined ? { cost: validateCost(input.cost) } : {}),
137
+ ...(input.digest !== undefined ? { digest: input.digest } : {}),
138
+ ...(input.error !== undefined ? { error: Object.freeze({ code: input.error.code }) } : {}),
139
+ });
140
+ this.items.push(entry);
141
+ return entry;
142
+ }
143
+ entries() {
144
+ return Object.freeze([...this.items]);
145
+ }
146
+ seal() {
147
+ this.sealResult ??= this.sealOnce();
148
+ return this.sealResult;
149
+ }
150
+ async sealOnce() {
151
+ const entries = Object.freeze([...this.items]);
152
+ const base = {
153
+ method: this.method,
154
+ path: this.path,
155
+ entries,
156
+ declared: this.declared,
157
+ };
158
+ if (!this.chain)
159
+ return Object.freeze(base);
160
+ const hashes = [];
161
+ // Seed the chain with the route identity and declared authority. Otherwise an anchored entry
162
+ // chain could be copied onto a different route/declaration without changing its head.
163
+ let previous = await sha256Hex(JSON.stringify({ method: this.method, path: this.path, declared: [...this.declared].sort() }));
164
+ for (const entry of entries) {
165
+ previous = await sha256Hex(`${previous}${canonicalEntry(entry)}`);
166
+ hashes.push(previous);
167
+ }
168
+ return Object.freeze({
169
+ ...base,
170
+ chain: Object.freeze({ hashes: Object.freeze(hashes), head: previous }),
171
+ });
172
+ }
173
+ }
174
+ /** Create a bounded per-request ledger. The server wires one per capability-declaring route. */
175
+ export function createRequestLedger(options) {
176
+ return new BoundedRequestLedger(options);
177
+ }
178
+ /** Canonical, key-ordered serialization of one entry — the hash-chain input. */
179
+ function canonicalEntry(entry) {
180
+ const cost = entry.cost === undefined
181
+ ? ""
182
+ : `,"cost":{${Object.keys(entry.cost)
183
+ .sort()
184
+ .map((key) => `${JSON.stringify(key)}:${JSON.stringify(entry.cost[key])}`)
185
+ .join(",")}}`;
186
+ return (`{"seq":${entry.seq},"at":${JSON.stringify(entry.at)},"capability":${JSON.stringify(entry.capability)},"phase":${JSON.stringify(entry.phase)}` +
187
+ `${entry.target !== undefined ? `,"target":${JSON.stringify(entry.target)}` : ""}` +
188
+ cost +
189
+ `${entry.digest !== undefined ? `,"digest":${JSON.stringify(entry.digest)}` : ""}` +
190
+ `${entry.error !== undefined ? `,"error":${JSON.stringify(entry.error.code)}` : ""}}`);
191
+ }
192
+ const encoder = new TextEncoder();
193
+ async function sha256Hex(value) {
194
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
195
+ let hex = "";
196
+ for (let index = 0; index < digest.length; index++) {
197
+ hex += digest[index].toString(16).padStart(2, "0");
198
+ }
199
+ return hex;
200
+ }
201
+ /** Bounded in-memory sink for tests and local development. Token-only, like every sink. */
202
+ export function createMemoryLedgerSink(options = {}) {
203
+ const maxLedgers = options.maxLedgers ?? 1_000;
204
+ if (!Number.isSafeInteger(maxLedgers) || maxLedgers < 1) {
205
+ throw new TypeError("effect ledger: maxLedgers must be a positive safe integer");
206
+ }
207
+ const ledgers = [];
208
+ return {
209
+ sink: (ledger) => {
210
+ ledgers.push(ledger);
211
+ if (ledgers.length > maxLedgers)
212
+ ledgers.shift();
213
+ },
214
+ get ledgers() {
215
+ return Object.freeze([...ledgers]);
216
+ },
217
+ clear: () => {
218
+ ledgers.length = 0;
219
+ },
220
+ };
221
+ }
222
+ /** Minimum digest key material. A short key would make the keyed digest brute-forceable. */
223
+ export const MIN_DIGEST_KEY_BYTES = 16;
224
+ /** Fresh random digest key (32 bytes). Per-process by default — persist one externally to correlate across restarts. */
225
+ export function randomEffectDigestKey() {
226
+ const bytes = new Uint8Array(32);
227
+ crypto.getRandomValues(bytes);
228
+ return bytes;
229
+ }
230
+ /**
231
+ * Keyed HMAC-SHA-256 digest (hex) of an effect payload, for replay/reconciliation matching without
232
+ * storing the payload. Keyed on purpose: a bare hash of low-entropy data (an email, a flag) is
233
+ * brute-forceable and would itself leak. Digest the **whole** effect payload, never a single field.
234
+ */
235
+ export async function computeEffectDigest(key, payload) {
236
+ let cryptoKey;
237
+ if (key instanceof Uint8Array) {
238
+ if (key.byteLength < MIN_DIGEST_KEY_BYTES) {
239
+ throw new TypeError(`effect ledger: digest key must be at least ${MIN_DIGEST_KEY_BYTES} bytes`);
240
+ }
241
+ cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
242
+ }
243
+ else {
244
+ const algorithm = key.algorithm;
245
+ if (algorithm.name !== "HMAC" ||
246
+ algorithm.hash?.name !== "SHA-256" ||
247
+ typeof algorithm.length !== "number" ||
248
+ algorithm.length < MIN_DIGEST_KEY_BYTES * 8 ||
249
+ !key.usages.includes("sign")) {
250
+ throw new TypeError("effect ledger: CryptoKey must be an HMAC-SHA-256 signing key of at least 128 bits");
251
+ }
252
+ cryptoKey = key;
253
+ }
254
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, payload));
255
+ let hex = "";
256
+ for (let index = 0; index < mac.length; index++) {
257
+ hex += mac[index].toString(16).padStart(2, "0");
258
+ }
259
+ return hex;
260
+ }
261
+ const EFFECT_LEDGER = Symbol("nifra.effect.ledger");
262
+ /** Framework wiring: attach a per-request ledger to a handler context. Not for application code. */
263
+ export function attachEffectLedger(context, ledger) {
264
+ ;
265
+ context[EFFECT_LEDGER] = ledger;
266
+ }
267
+ /** The request's effect ledger, when the server enabled one for this route. Read-only access. */
268
+ export function effectLedgerOf(context) {
269
+ return context[EFFECT_LEDGER];
270
+ }
271
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /** Deterministic, signed route manifest: contract × assurance × capabilities × classification. */
2
+ import type { AssuranceReport } from "./assurance.js";
3
+ import type { CapabilityAssuranceReport } from "./capabilities.js";
4
+ import { type ResponseClassification } from "./classification.js";
5
+ import { type DiffSeverity, type RouteChange, type RouteSnapshotSchema } from "./diff.js";
6
+ export interface NifraManifestAssurance {
7
+ readonly rule?: string;
8
+ readonly evidence: readonly {
9
+ readonly id: string;
10
+ readonly source: string;
11
+ }[];
12
+ }
13
+ export interface NifraManifestCapabilities {
14
+ readonly declared: readonly string[];
15
+ readonly evidenced: readonly string[];
16
+ readonly unproven: readonly string[];
17
+ readonly covered: boolean;
18
+ }
19
+ export interface NifraManifestRoute {
20
+ readonly method: string;
21
+ readonly path: string;
22
+ readonly schema?: RouteSnapshotSchema;
23
+ readonly assurance?: NifraManifestAssurance;
24
+ readonly capabilities?: NifraManifestCapabilities;
25
+ readonly classification?: ResponseClassification;
26
+ }
27
+ export interface NifraManifest {
28
+ readonly manifestVersion: 1;
29
+ readonly routes: readonly NifraManifestRoute[];
30
+ /** SHA-256 hex of the canonical manifest body (`manifestVersion` + `routes`). */
31
+ readonly contentHash: string;
32
+ }
33
+ export interface NifraManifestSigner {
34
+ readonly algorithm: "Ed25519";
35
+ readonly keyId: string;
36
+ sign(payload: Uint8Array): ArrayBuffer | Uint8Array | Promise<ArrayBuffer | Uint8Array>;
37
+ }
38
+ export interface NifraManifestSignature {
39
+ readonly nifraManifestSignature: 1;
40
+ readonly algorithm: "Ed25519";
41
+ readonly keyId: string;
42
+ readonly contentHash: string;
43
+ /** Base64url Ed25519 signature over the canonical manifest body. */
44
+ readonly signature: string;
45
+ }
46
+ export interface BuildNifraManifestInput {
47
+ readonly source: unknown;
48
+ readonly assurance?: AssuranceReport;
49
+ readonly capabilities?: CapabilityAssuranceReport;
50
+ }
51
+ /** Canonical bytes are stable across runtime, object-key order, and route registration order. */
52
+ export declare function canonicalManifest(manifest: Pick<NifraManifest, "manifestVersion" | "routes">): string;
53
+ /** Byte-stable artifact serialization (including `contentHash`). */
54
+ export declare function serializeNifraManifest(manifest: NifraManifest): string;
55
+ /** Byte-stable serialization for the detached signature sidecar. */
56
+ export declare function serializeNifraManifestSignature(signature: NifraManifestSignature): string;
57
+ /** Parse the detached sidecar before selecting its operator-controlled public key. */
58
+ export declare function parseNifraManifestSignature(content: string, source?: string): NifraManifestSignature;
59
+ /** Build one fail-closed, deterministic manifest from already-evaluated assurance reports. */
60
+ export declare function buildNifraManifest(input: BuildNifraManifestInput): Promise<NifraManifest>;
61
+ /** Sign without handling private keys: the operator-supplied signer may call KMS/HSM/local WebCrypto. */
62
+ export declare function signNifraManifest(manifest: NifraManifest, signer: NifraManifestSigner): Promise<NifraManifestSignature>;
63
+ /** Verify the hash first, then the detached Ed25519 signature. Malformed/tampered input returns false. */
64
+ export declare function verifyNifraManifestSignature(manifest: NifraManifest, signature: NifraManifestSignature, publicKey: CryptoKey): Promise<boolean>;
65
+ /** Parse and hash-verify an emitted manifest before it is trusted by diff/codegen tooling. */
66
+ export declare function parseNifraManifest(content: string, source?: string): Promise<NifraManifest>;
67
+ export interface NifraManifestChange {
68
+ readonly severity: DiffSeverity;
69
+ readonly method: string;
70
+ readonly path: string;
71
+ readonly section: RouteChange["section"] | "assurance" | "capabilities" | "classification";
72
+ readonly field?: string;
73
+ readonly message: string;
74
+ }
75
+ export interface NifraManifestDiff {
76
+ readonly changes: readonly NifraManifestChange[];
77
+ readonly hasBreaking: boolean;
78
+ }
79
+ /** Contract changes reuse the route-diff engine; governance changes fail closed on expanded risk. */
80
+ export declare function diffNifraManifests(before: NifraManifest, after: NifraManifest): NifraManifestDiff;
81
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}