@nifrajs/core 4.0.2 → 5.1.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 (239) hide show
  1. package/README.md +6 -2
  2. package/dist/assurance.d.ts +86 -6
  3. package/dist/assurance.d.ts.map +1 -1
  4. package/dist/assurance.js +186 -95
  5. package/dist/assurance.js.map +1 -1
  6. package/dist/capabilities.d.ts +8 -0
  7. package/dist/capabilities.d.ts.map +1 -1
  8. package/dist/capabilities.js +53 -1
  9. package/dist/capabilities.js.map +1 -1
  10. package/dist/durable-effect.js +1 -1
  11. package/dist/durable-effect.js.map +1 -1
  12. package/dist/durable-saga.d.ts +6 -0
  13. package/dist/durable-saga.d.ts.map +1 -1
  14. package/dist/durable-saga.js +83 -10
  15. package/dist/durable-saga.js.map +1 -1
  16. package/dist/edge-kit.d.ts +8 -3
  17. package/dist/edge-kit.d.ts.map +1 -1
  18. package/dist/edge-kit.js +7 -2
  19. package/dist/edge-kit.js.map +1 -1
  20. package/dist/effect-scope.d.ts.map +1 -1
  21. package/dist/effect-scope.js +16 -5
  22. package/dist/effect-scope.js.map +1 -1
  23. package/dist/errors.d.ts +1 -1
  24. package/dist/errors.d.ts.map +1 -1
  25. package/dist/errors.js.map +1 -1
  26. package/dist/evidence.d.ts +2 -0
  27. package/dist/evidence.d.ts.map +1 -1
  28. package/dist/evidence.js +9 -1
  29. package/dist/evidence.js.map +1 -1
  30. package/dist/index.d.ts +5 -4
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/internal/durable-shared.d.ts +1 -2
  35. package/dist/internal/durable-shared.d.ts.map +1 -1
  36. package/dist/internal/durable-shared.js +25 -44
  37. package/dist/internal/durable-shared.js.map +1 -1
  38. package/dist/internal/durable-types.d.ts +3 -0
  39. package/dist/internal/durable-types.d.ts.map +1 -1
  40. package/dist/internal/hook-origins.d.ts +67 -0
  41. package/dist/internal/hook-origins.d.ts.map +1 -0
  42. package/dist/internal/hook-origins.js +86 -0
  43. package/dist/internal/hook-origins.js.map +1 -0
  44. package/dist/internal/route-assurance.d.ts +6 -3
  45. package/dist/internal/route-assurance.d.ts.map +1 -1
  46. package/dist/internal/route-assurance.js +29 -14
  47. package/dist/internal/route-assurance.js.map +1 -1
  48. package/dist/internal/route-compiler.d.ts +9 -0
  49. package/dist/internal/route-compiler.d.ts.map +1 -1
  50. package/dist/internal/route-compiler.js +95 -13
  51. package/dist/internal/route-compiler.js.map +1 -1
  52. package/dist/internal/route-program.d.ts +1 -1
  53. package/dist/internal/route-program.d.ts.map +1 -1
  54. package/dist/internal/route-program.js +59 -16
  55. package/dist/internal/route-program.js.map +1 -1
  56. package/dist/internal/same-origin.d.ts +11 -1
  57. package/dist/internal/same-origin.d.ts.map +1 -1
  58. package/dist/internal/same-origin.js +8 -0
  59. package/dist/internal/same-origin.js.map +1 -1
  60. package/dist/reconciliation-worker.d.ts.map +1 -1
  61. package/dist/reconciliation-worker.js +12 -2
  62. package/dist/reconciliation-worker.js.map +1 -1
  63. package/dist/reflection.d.ts +53 -2
  64. package/dist/reflection.d.ts.map +1 -1
  65. package/dist/reflection.js +352 -5
  66. package/dist/reflection.js.map +1 -1
  67. package/dist/router/pattern.d.ts +1 -1
  68. package/dist/router/pattern.d.ts.map +1 -1
  69. package/dist/router/pattern.js +6 -3
  70. package/dist/router/pattern.js.map +1 -1
  71. package/dist/security/baseline.d.ts +3 -0
  72. package/dist/security/baseline.d.ts.map +1 -1
  73. package/dist/security/baseline.js +9 -5
  74. package/dist/security/baseline.js.map +1 -1
  75. package/dist/server/auth.d.ts +32 -3
  76. package/dist/server/auth.d.ts.map +1 -1
  77. package/dist/server/auth.js +23 -0
  78. package/dist/server/auth.js.map +1 -1
  79. package/dist/server/body-lane.d.ts +8 -2
  80. package/dist/server/body-lane.d.ts.map +1 -1
  81. package/dist/server/body-lane.js +27 -2
  82. package/dist/server/body-lane.js.map +1 -1
  83. package/dist/server/body.d.ts +7 -0
  84. package/dist/server/body.d.ts.map +1 -1
  85. package/dist/server/body.js +34 -1
  86. package/dist/server/body.js.map +1 -1
  87. package/dist/server/client-ip.d.ts +3 -0
  88. package/dist/server/client-ip.d.ts.map +1 -1
  89. package/dist/server/client-ip.js +6 -5
  90. package/dist/server/client-ip.js.map +1 -1
  91. package/dist/server/context.d.ts +2 -1
  92. package/dist/server/context.d.ts.map +1 -1
  93. package/dist/server/cookies.d.ts.map +1 -1
  94. package/dist/server/cookies.js +13 -1
  95. package/dist/server/cookies.js.map +1 -1
  96. package/dist/server/edge.d.ts.map +1 -1
  97. package/dist/server/edge.js +2 -1
  98. package/dist/server/edge.js.map +1 -1
  99. package/dist/server/http.d.ts.map +1 -1
  100. package/dist/server/http.js +5 -1
  101. package/dist/server/http.js.map +1 -1
  102. package/dist/server/idempotency-lane.d.ts +6 -3
  103. package/dist/server/idempotency-lane.d.ts.map +1 -1
  104. package/dist/server/idempotency-lane.js +38 -17
  105. package/dist/server/idempotency-lane.js.map +1 -1
  106. package/dist/server/install.d.ts +2 -0
  107. package/dist/server/install.d.ts.map +1 -1
  108. package/dist/server/install.js +2 -0
  109. package/dist/server/install.js.map +1 -1
  110. package/dist/server/logger.d.ts.map +1 -1
  111. package/dist/server/logger.js +3 -1
  112. package/dist/server/logger.js.map +1 -1
  113. package/dist/server/methods.d.ts +1 -0
  114. package/dist/server/methods.d.ts.map +1 -1
  115. package/dist/server/methods.js +1 -0
  116. package/dist/server/methods.js.map +1 -1
  117. package/dist/server/node-outcome.d.ts.map +1 -1
  118. package/dist/server/node-outcome.js +29 -11
  119. package/dist/server/node-outcome.js.map +1 -1
  120. package/dist/server/platform-adapters.d.ts.map +1 -1
  121. package/dist/server/platform-adapters.js +3 -2
  122. package/dist/server/platform-adapters.js.map +1 -1
  123. package/dist/server/request-context.d.ts +2 -1
  124. package/dist/server/request-context.d.ts.map +1 -1
  125. package/dist/server/request-context.js +4 -1
  126. package/dist/server/request-context.js.map +1 -1
  127. package/dist/server/respond.d.ts +44 -11
  128. package/dist/server/respond.d.ts.map +1 -1
  129. package/dist/server/respond.js +192 -79
  130. package/dist/server/respond.js.map +1 -1
  131. package/dist/server/response-contract-lane.d.ts +21 -6
  132. package/dist/server/response-contract-lane.d.ts.map +1 -1
  133. package/dist/server/response-contract-lane.js +139 -14
  134. package/dist/server/response-contract-lane.js.map +1 -1
  135. package/dist/server/response-observer-runtime.d.ts.map +1 -1
  136. package/dist/server/response-observer-runtime.js +10 -41
  137. package/dist/server/response-observer-runtime.js.map +1 -1
  138. package/dist/server/runtime-core.d.ts +2 -0
  139. package/dist/server/runtime-core.d.ts.map +1 -1
  140. package/dist/server/runtime-core.js +2 -0
  141. package/dist/server/runtime-core.js.map +1 -1
  142. package/dist/server/server-types.d.ts +45 -10
  143. package/dist/server/server-types.d.ts.map +1 -1
  144. package/dist/server/server.d.ts +67 -32
  145. package/dist/server/server.d.ts.map +1 -1
  146. package/dist/server/server.js +481 -334
  147. package/dist/server/server.js.map +1 -1
  148. package/dist/server/sse.d.ts +11 -1
  149. package/dist/server/sse.d.ts.map +1 -1
  150. package/dist/server/sse.js +18 -3
  151. package/dist/server/sse.js.map +1 -1
  152. package/dist/server/transport-lane.d.ts +7 -1
  153. package/dist/server/transport-lane.d.ts.map +1 -1
  154. package/dist/server/transport-lane.js.map +1 -1
  155. package/dist/server/webhook.d.ts +5 -2
  156. package/dist/server/webhook.d.ts.map +1 -1
  157. package/dist/server/webhook.js +17 -1
  158. package/dist/server/webhook.js.map +1 -1
  159. package/dist/server/websocket.d.ts +33 -14
  160. package/dist/server/websocket.d.ts.map +1 -1
  161. package/dist/server/websocket.js +47 -12
  162. package/dist/server/websocket.js.map +1 -1
  163. package/dist/server/ws-bun.d.ts +7 -1
  164. package/dist/server/ws-bun.d.ts.map +1 -1
  165. package/dist/server/ws-bun.js +42 -2
  166. package/dist/server/ws-bun.js.map +1 -1
  167. package/dist/server/ws-hook.d.ts +15 -3
  168. package/dist/server/ws-hook.d.ts.map +1 -1
  169. package/dist/server.d.ts +4 -3
  170. package/dist/server.d.ts.map +1 -1
  171. package/dist/server.js +3 -2
  172. package/dist/server.js.map +1 -1
  173. package/dist/single-copy.d.ts +6 -0
  174. package/dist/single-copy.d.ts.map +1 -1
  175. package/dist/single-copy.js +55 -6
  176. package/dist/single-copy.js.map +1 -1
  177. package/dist/tool-contract.d.ts.map +1 -1
  178. package/dist/tool-contract.js +71 -40
  179. package/dist/tool-contract.js.map +1 -1
  180. package/dist/wire.d.ts +5 -0
  181. package/dist/wire.d.ts.map +1 -1
  182. package/dist/wire.js +5 -0
  183. package/dist/wire.js.map +1 -1
  184. package/dist/ws.d.ts.map +1 -1
  185. package/dist/ws.js +4 -1
  186. package/dist/ws.js.map +1 -1
  187. package/package.json +1 -1
  188. package/src/assurance.ts +299 -111
  189. package/src/capabilities.ts +62 -1
  190. package/src/durable-effect.ts +1 -1
  191. package/src/durable-saga.ts +94 -10
  192. package/src/edge-kit.ts +8 -3
  193. package/src/effect-scope.ts +15 -5
  194. package/src/errors.ts +1 -0
  195. package/src/evidence.ts +12 -3
  196. package/src/index.ts +7 -2
  197. package/src/internal/durable-shared.ts +25 -37
  198. package/src/internal/durable-types.ts +3 -0
  199. package/src/internal/hook-origins.ts +153 -0
  200. package/src/internal/route-assurance.ts +32 -18
  201. package/src/internal/route-compiler.ts +107 -16
  202. package/src/internal/route-program.ts +98 -44
  203. package/src/internal/same-origin.ts +14 -1
  204. package/src/reconciliation-worker.ts +10 -2
  205. package/src/reflection.ts +455 -4
  206. package/src/router/pattern.ts +6 -3
  207. package/src/security/baseline.ts +10 -5
  208. package/src/server/auth.ts +45 -3
  209. package/src/server/body-lane.ts +36 -1
  210. package/src/server/body.ts +34 -1
  211. package/src/server/client-ip.ts +8 -5
  212. package/src/server/context.ts +2 -1
  213. package/src/server/cookies.ts +12 -1
  214. package/src/server/edge.ts +2 -1
  215. package/src/server/http.ts +7 -1
  216. package/src/server/idempotency-lane.ts +64 -22
  217. package/src/server/install.ts +3 -0
  218. package/src/server/logger.ts +3 -1
  219. package/src/server/methods.ts +1 -0
  220. package/src/server/node-outcome.ts +34 -12
  221. package/src/server/platform-adapters.ts +3 -2
  222. package/src/server/request-context.ts +5 -0
  223. package/src/server/respond.ts +222 -69
  224. package/src/server/response-contract-lane.ts +162 -10
  225. package/src/server/response-observer-runtime.ts +16 -38
  226. package/src/server/runtime-core.ts +2 -0
  227. package/src/server/server-types.ts +45 -10
  228. package/src/server/server.ts +707 -539
  229. package/src/server/sse.ts +84 -55
  230. package/src/server/transport-lane.ts +7 -1
  231. package/src/server/webhook.ts +22 -3
  232. package/src/server/websocket.ts +83 -18
  233. package/src/server/ws-bun.ts +48 -1
  234. package/src/server/ws-hook.ts +14 -2
  235. package/src/server.ts +6 -1
  236. package/src/single-copy.ts +55 -5
  237. package/src/tool-contract.ts +67 -39
  238. package/src/wire.ts +14 -0
  239. package/src/ws.ts +6 -0
package/README.md CHANGED
@@ -120,7 +120,9 @@ route. Split groups before the compiler reports `TS2589`, rather than weakening
120
120
  client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
121
121
  bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
122
122
  deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
123
- subpath keeps rich-wire code out of plain JSON bundles.
123
+ subpath keeps rich-wire code out of plain JSON bundles. The plugin decodes a codec body before
124
+ routing, so a route's `bodyLimit` and its authentication run after the decode; its own `maxBytes`
125
+ is the bound on that work.
124
126
  - **Typed data seam (opt in).** `@nifrajs/core/data` defines token-only operation contracts,
125
127
  `db.read`/`db.write` capability names, an opaque request-local `RlsScope`, typed adapter requests,
126
128
  drift snapshots, and `createDataPort(contract, adapter, { beacon: useCapability })`, which emits the
@@ -158,7 +160,9 @@ contract-first registry.
158
160
 
159
161
  `response` and `errors` are typed by default but are not runtime enforcement by themselves. Install
160
162
  `responseContract("enforce")` before the routes it should cover to validate JSON/status payloads and
161
- prevent extra fields from reaching the wire. `warn` preserves the original payload during migration.
163
+ prevent extra fields from reaching the wire. That covers a `status(...)` or `c.json(...)` reply a
164
+ handler returns or throws, a hook returns, or `onError` answers with. `warn` preserves the original
165
+ payload during migration.
162
166
  Raw `Response`, streams, redirects, and `mountFetch` remain explicit transport escape hatches and
163
167
  must be governed by assurance/static review.
164
168
 
@@ -15,6 +15,68 @@ export type { AssuranceAttachment, AssuranceDeclaration, AssuranceEvidence, Assu
15
15
  export { withRouteAssurance };
16
16
  /** Isolated request executor used by adversarial contract verification. */
17
17
  export type InvariantExecutor = (request: Request) => Response | Promise<Response>;
18
+ export type ContractTarget = "headers" | "cookies" | "params" | "body" | "query" | "response";
19
+ export type ContractCaseKind = "input-rejection" | "response-conformance";
20
+ export type ContractAuthContext = "authenticated" | "unauthenticated";
21
+ export interface ContractExpectedResponse {
22
+ readonly status: number;
23
+ readonly contentType?: "json" | "text";
24
+ readonly headers?: Readonly<Record<string, string>>;
25
+ /** When present, the decoded body is compared byte-structure-wise to this value. */
26
+ readonly body?: unknown;
27
+ }
28
+ /** A known-good request. Missing body/query values are synthesized from inspectable JSON Schema. */
29
+ export interface ContractWitness {
30
+ readonly params?: Readonly<Record<string, string>>;
31
+ readonly query?: unknown;
32
+ readonly body?: unknown;
33
+ readonly headers?: ConstructorParameters<typeof Headers>[0];
34
+ /** Explicit auth state for the prepareRequest hook; the harness never invents credentials. */
35
+ readonly auth?: ContractAuthContext;
36
+ /** Expected response for a guard/error witness. Omit for the normal success witness. */
37
+ readonly expected?: ContractExpectedResponse;
38
+ }
39
+ /** Stable context passed to request/rejection hooks. It contains no request payloads or secrets. */
40
+ export interface ContractCaseContext {
41
+ readonly seed: number;
42
+ readonly caseId: string;
43
+ readonly route: ReflectedRoute;
44
+ readonly runtime: string;
45
+ readonly kind: ContractCaseKind;
46
+ readonly target: ContractTarget;
47
+ readonly auth?: ContractAuthContext;
48
+ readonly mutation?: string;
49
+ }
50
+ /** What `invariants.setup` provisions for one L4 run. */
51
+ export interface InvariantSetupResult {
52
+ /** The isolated target for this run. Replaces `invariants.executor`. */
53
+ readonly executor?: InvariantExecutor;
54
+ /** Called once the run ends, after a failure too. */
55
+ readonly teardown?: () => void | Promise<void>;
56
+ }
57
+ /** Provisions a throwaway L4 target. Called once, before the first L4 request - never on import. */
58
+ export type InvariantSetup = () => Promise<InvariantSetupResult | undefined | void>;
59
+ /** Fixture options handed to the L4 contract runner (`runAdversarialContract` in `@nifrajs/testing`). */
60
+ export interface InvariantRunOptions {
61
+ /** Known-good request values keyed by `"METHOD /path"`. Required for opaque Standard Schemas. */
62
+ readonly witnesses?: Readonly<Record<string, ContractWitness>>;
63
+ /** Attach a session, real row ids or signed tokens. Return a new Request. */
64
+ readonly prepareRequest?: (request: Request, context: ContractCaseContext) => Request | Promise<Request>;
65
+ /** Override rejection semantics, for an app with a custom validation response. */
66
+ readonly isRejected?: (response: Response, context: ContractCaseContext) => boolean | Promise<boolean>;
67
+ /** Run L4 on a subset of the reflected routes. */
68
+ readonly include?: (route: ReflectedRoute) => boolean;
69
+ /** Maximum validator-proven mutations per body/query schema. Default 32. */
70
+ readonly maxMutationsPerInput?: number;
71
+ }
72
+ /** L4 configuration: an isolated `executor`, a `setup` that provisions one, or both. */
73
+ export type InvariantsConfig = InvariantRunOptions & ({
74
+ readonly executor: InvariantExecutor;
75
+ readonly setup?: InvariantSetup;
76
+ } | {
77
+ readonly executor?: InvariantExecutor;
78
+ readonly setup: InvariantSetup;
79
+ });
18
80
  /** Canonical evidence ids emitted by Nifra's official middleware modules. */
19
81
  export declare const NIFRA_ASSURANCE: Readonly<{
20
82
  readonly AUTHENTICATED: "nifra.authenticated";
@@ -46,8 +108,14 @@ export declare const NIFRA_ASSURANCE: Readonly<{
46
108
  */
47
109
  export declare function assure(app: unknown, evidence: AssuranceAttachment | readonly AssuranceAttachment[]): void;
48
110
  export interface AssuranceRouteSelector {
49
- /** Omit for every method. */
50
- readonly methods?: readonly Method[];
111
+ /** Omit for every method. `"WS"` selects WebSocket routes. */
112
+ readonly methods?: readonly (Method | "WS")[];
113
+ /**
114
+ * Skip routes under these methods; `"WS"` skips WebSocket routes. Every other method matches, a
115
+ * custom one such as `PURGE` included, so `["GET", "HEAD", "OPTIONS", "WS"]` selects every route
116
+ * that can change state.
117
+ */
118
+ readonly exceptMethods?: readonly (Method | "WS")[];
51
119
  /** Absolute route globs. `*` matches one segment; final `**` matches zero or more. */
52
120
  readonly paths?: readonly string[];
53
121
  /** Restrict the rule to MCP tool routes (`true`) or non-tool routes (`false`). */
@@ -90,6 +158,12 @@ export interface AssuranceRouteSelector {
90
158
  * already carries `schema.body`, so this reads what the route declared, not where it lives.
91
159
  */
92
160
  readonly hasBody?: boolean;
161
+ /**
162
+ * Match routes by whether they declare a success `response` schema - the routes a response contract
163
+ * can hold to anything. Pair it with `require: ["nifra.response-contract"]` to say "every route
164
+ * that declares a response enforces it" without listing the routes that declare none.
165
+ */
166
+ readonly hasResponse?: boolean;
93
167
  /**
94
168
  * Match routes by their effective transport body policy. `"unlimited"` is the explicit
95
169
  * streaming/upload exemption; `"bounded"` is any finite cap (an explicit number or the inherited
@@ -137,6 +211,13 @@ export interface AssuranceRule {
137
211
  export interface AssurancePolicy {
138
212
  /** First matching rule owns a route. Put exceptions before broad defaults. */
139
213
  readonly rules: readonly AssuranceRule[];
214
+ /**
215
+ * Rules checked on EVERY route they match, in addition to the first-match rule that owns it - for a
216
+ * requirement that holds across classes ("every route declaring a response enforces it"), which
217
+ * first-match-wins would otherwise make you repeat in each rule. A baseline rule does not classify a
218
+ * route: one no `rules` entry matches is still unclassified. Names share one namespace with `rules`.
219
+ */
220
+ readonly baseline?: readonly AssuranceRule[];
140
221
  /** Default `error`: an unclassified route fails closed. */
141
222
  readonly unmatched?: "error" | "ignore";
142
223
  /** Default false: reject an empty reflected source so a wrong import cannot pass CI silently. */
@@ -202,9 +283,7 @@ export interface AssuranceConfig {
202
283
  readonly signer?: (keyRef: string) => NifraManifestSigner | Promise<NifraManifestSigner>;
203
284
  };
204
285
  /** Dynamic invariant execution is opt-in and must use a disposable/sandboxed target. */
205
- readonly invariants?: {
206
- readonly executor: InvariantExecutor;
207
- };
286
+ readonly invariants?: InvariantsConfig;
208
287
  /** Optional application-supplied rule packs appended after built-in verification rules. */
209
288
  readonly rulePacks?: readonly AssuranceRulePack[];
210
289
  /** Optional sink for an assurance bundle. The CLI validates the callable shape at runtime. */
@@ -219,6 +298,7 @@ export declare function defineAssurancePolicy(policy: AssurancePolicy): Assuranc
219
298
  /** Identity helper for a `nifra.assurance.ts` default export. */
220
299
  export declare function defineAssuranceConfig(config: AssuranceConfig): AssuranceConfig;
221
300
  export declare function matchesAssuranceSelector(route: Pick<ReflectedRoute, "method" | "path" | "tool" | "capabilities" | "classification" | "schema">, selector: AssuranceRouteSelector, definitions?: ReadonlyMap<string, CapabilityDefinition>): boolean;
222
- /** Evaluate reflected route evidence against the first matching policy rule. */
301
+ /** Evaluate reflected route evidence against the first matching policy rule, plus every matching
302
+ * baseline rule. */
223
303
  export declare function evaluateRouteAssurance(source: unknown, policyInput: AssurancePolicy, options?: AssuranceEvaluationOptions): AssuranceReport;
224
304
  //# sourceMappingURL=assurance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"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"}
package/dist/assurance.js CHANGED
@@ -55,12 +55,27 @@ const CLASS_SELECTOR_KEYS = ["access", "zone"];
55
55
  const usesClassSelector = (selector) => selector.access !== undefined || selector.zone !== undefined;
56
56
  /** The rules whose selector needs capability definitions to mean anything. */
57
57
  function rulesNeedingDefinitions(policy) {
58
- return policy.rules.filter((rule) => usesClassSelector(rule.match)).map((rule) => rule.name);
58
+ return [...policy.rules, ...(policy.baseline ?? [])]
59
+ .filter((rule) => usesClassSelector(rule.match))
60
+ .map((rule) => rule.name);
59
61
  }
60
62
  function definitionMap(definitions) {
61
63
  return new Map((definitions ?? []).map((definition) => [definition.id, definition]));
62
64
  }
63
65
  const nonEmpty = (value) => value.trim() !== "";
66
+ /** A selector's method list, upper-cased. Standard methods and `"WS"` only, so a typo throws rather
67
+ * than leaving a rule that never matches. */
68
+ function selectorMethods(values) {
69
+ if (values === undefined)
70
+ return undefined;
71
+ return Object.freeze(values.map((value) => {
72
+ const method = value.toUpperCase();
73
+ if (method !== "WS" && !validMethod(method)) {
74
+ throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`);
75
+ }
76
+ return method;
77
+ }));
78
+ }
64
79
  function normalizeEvidenceIds(values, label) {
65
80
  const out = [];
66
81
  const seen = new Set();
@@ -89,8 +104,11 @@ export function defineAssurancePolicy(policy) {
89
104
  typeof policy.flagClassifiedWithoutEvidence !== "boolean") {
90
105
  throw new Error(`route assurance: flagClassifiedWithoutEvidence must be boolean`);
91
106
  }
107
+ if (policy.baseline !== undefined && !Array.isArray(policy.baseline)) {
108
+ throw new Error("route assurance: baseline must be an array of rules");
109
+ }
92
110
  const names = new Set();
93
- const rules = policy.rules.map((rule) => {
111
+ const normalize = (rule) => {
94
112
  if (typeof rule.name !== "string" || !nonEmpty(rule.name)) {
95
113
  throw new Error("route assurance: rule name must be non-empty");
96
114
  }
@@ -103,12 +121,14 @@ export function defineAssurancePolicy(policy) {
103
121
  // swallow everything after it. A misspelled key is a policy hole, so it is refused here.
104
122
  const unknown = Object.keys(rule.match).filter((key) => ![
105
123
  "methods",
124
+ "exceptMethods",
106
125
  "paths",
107
126
  "tools",
108
127
  "capabilities",
109
128
  ...CLASS_SELECTOR_KEYS,
110
129
  "classificationAtLeast",
111
130
  "hasBody",
131
+ "hasResponse",
112
132
  "bodyLimit",
113
133
  ].includes(key));
114
134
  if (unknown.length > 0) {
@@ -119,8 +139,10 @@ export function defineAssurancePolicy(policy) {
119
139
  if (rule.match.tools !== undefined && typeof rule.match.tools !== "boolean") {
120
140
  throw new Error(`route assurance: rule ${JSON.stringify(name)} tools selector must be boolean`);
121
141
  }
122
- if (rule.match.hasBody !== undefined && typeof rule.match.hasBody !== "boolean") {
123
- throw new Error(`route assurance: rule ${JSON.stringify(name)} hasBody selector must be boolean`);
142
+ for (const key of ["hasBody", "hasResponse"]) {
143
+ if (rule.match[key] !== undefined && typeof rule.match[key] !== "boolean") {
144
+ throw new Error(`route assurance: rule ${JSON.stringify(name)} ${key} selector must be boolean`);
145
+ }
124
146
  }
125
147
  if (rule.match.bodyLimit !== undefined &&
126
148
  rule.match.bodyLimit !== "bounded" &&
@@ -130,18 +152,14 @@ export function defineAssurancePolicy(policy) {
130
152
  }
131
153
  // An empty list matches nothing, so the rule is inert and its routes fall through to whatever
132
154
  // laxer rule comes next - the same hole as a dropped key, the other way round.
133
- for (const key of ["methods", "paths"]) {
155
+ for (const key of ["methods", "exceptMethods", "paths"]) {
134
156
  const list = rule.match[key];
135
157
  if (list !== undefined && (!Array.isArray(list) || list.length === 0)) {
136
158
  throw new Error(`route assurance: rule ${JSON.stringify(name)} ${key} selector must be a non-empty array`);
137
159
  }
138
160
  }
139
- const methods = rule.match.methods?.map((method) => method.toUpperCase());
140
- for (const method of methods ?? []) {
141
- if (!validMethod(method)) {
142
- throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`);
143
- }
144
- }
161
+ const methods = selectorMethods(rule.match.methods);
162
+ const exceptMethods = selectorMethods(rule.match.exceptMethods);
145
163
  const paths = rule.match.paths?.map((path) => {
146
164
  routeGlob(path);
147
165
  return path;
@@ -189,21 +207,33 @@ export function defineAssurancePolicy(policy) {
189
207
  if (overlap !== undefined) {
190
208
  throw new Error(`route assurance: rule ${JSON.stringify(name)} both requires and forbids ${overlap}`);
191
209
  }
210
+ const match = {};
211
+ if (methods !== undefined)
212
+ match.methods = methods;
213
+ if (exceptMethods !== undefined)
214
+ match.exceptMethods = exceptMethods;
215
+ if (paths !== undefined)
216
+ match.paths = Object.freeze(paths);
217
+ if (rule.match.tools !== undefined)
218
+ match.tools = rule.match.tools;
219
+ if (capabilities !== undefined)
220
+ match.capabilities = Object.freeze([...capabilities]);
221
+ if (access !== undefined)
222
+ match.access = access;
223
+ if (zone !== undefined)
224
+ match.zone = zone;
225
+ if (rule.match.classificationAtLeast !== undefined) {
226
+ match.classificationAtLeast = rule.match.classificationAtLeast;
227
+ }
228
+ if (rule.match.hasBody !== undefined)
229
+ match.hasBody = rule.match.hasBody;
230
+ if (rule.match.hasResponse !== undefined)
231
+ match.hasResponse = rule.match.hasResponse;
232
+ if (rule.match.bodyLimit !== undefined)
233
+ match.bodyLimit = rule.match.bodyLimit;
192
234
  return Object.freeze({
193
235
  name,
194
- match: Object.freeze({
195
- ...(methods !== undefined ? { methods: Object.freeze(methods) } : {}),
196
- ...(paths !== undefined ? { paths: Object.freeze(paths) } : {}),
197
- ...(rule.match.tools !== undefined ? { tools: rule.match.tools } : {}),
198
- ...(capabilities !== undefined ? { capabilities: Object.freeze([...capabilities]) } : {}),
199
- ...(access !== undefined ? { access } : {}),
200
- ...(zone !== undefined ? { zone } : {}),
201
- ...(rule.match.classificationAtLeast !== undefined
202
- ? { classificationAtLeast: rule.match.classificationAtLeast }
203
- : {}),
204
- ...(rule.match.hasBody !== undefined ? { hasBody: rule.match.hasBody } : {}),
205
- ...(rule.match.bodyLimit !== undefined ? { bodyLimit: rule.match.bodyLimit } : {}),
206
- }),
236
+ match: Object.freeze(match),
207
237
  require: required,
208
238
  forbid: forbidden,
209
239
  ...(rule.requireProvenance !== undefined
@@ -211,14 +241,61 @@ export function defineAssurancePolicy(policy) {
211
241
  : {}),
212
242
  ...(rule.requireCsrfWithAuthenticated === true ? { requireCsrfWithAuthenticated: true } : {}),
213
243
  });
214
- });
244
+ };
245
+ const rules = policy.rules.map(normalize);
246
+ const baseline = (policy.baseline ?? []).map(normalize);
215
247
  return Object.freeze({
216
248
  rules: Object.freeze(rules),
249
+ ...(baseline.length > 0 ? { baseline: Object.freeze(baseline) } : {}),
217
250
  unmatched: policy.unmatched ?? "error",
218
251
  allowEmpty: policy.allowEmpty ?? false,
219
252
  flagClassifiedWithoutEvidence: policy.flagClassifiedWithoutEvidence ?? false,
220
253
  });
221
254
  }
255
+ const INVARIANT_FUNCTIONS = [
256
+ "executor",
257
+ "setup",
258
+ "prepareRequest",
259
+ "isRejected",
260
+ "include",
261
+ ];
262
+ function assertInvariantsConfig(invariants) {
263
+ if (invariants === null || typeof invariants !== "object") {
264
+ throw new TypeError("route assurance: invariants must be an object");
265
+ }
266
+ for (const name of INVARIANT_FUNCTIONS) {
267
+ const value = invariants[name];
268
+ if (value !== undefined && typeof value !== "function") {
269
+ throw new TypeError(name === "executor"
270
+ ? "route assurance: invariant executor must be a function"
271
+ : `route assurance: invariants.${name} must be a function`);
272
+ }
273
+ }
274
+ if (invariants.executor === undefined && invariants.setup === undefined) {
275
+ throw new TypeError("route assurance: invariants needs an executor or a setup");
276
+ }
277
+ const witnesses = invariants.witnesses;
278
+ if (witnesses !== undefined) {
279
+ if (witnesses === null || typeof witnesses !== "object" || Array.isArray(witnesses)) {
280
+ throw new TypeError('route assurance: invariants.witnesses must map "METHOD /path" to a witness');
281
+ }
282
+ for (const [key, witness] of Object.entries(witnesses)) {
283
+ if (witness === null || typeof witness !== "object" || Array.isArray(witness)) {
284
+ throw new TypeError(`route assurance: invariants.witnesses[${JSON.stringify(key)}] must be an object`);
285
+ }
286
+ }
287
+ }
288
+ const max = invariants.maxMutationsPerInput;
289
+ if (max !== undefined && (!Number.isSafeInteger(max) || max < 1)) {
290
+ throw new RangeError("route assurance: invariants.maxMutationsPerInput must be a positive integer");
291
+ }
292
+ }
293
+ function frozenInvariantsConfig(invariants) {
294
+ const witnesses = invariants.witnesses;
295
+ return Object.freeze(witnesses === undefined
296
+ ? { ...invariants }
297
+ : { ...invariants, witnesses: Object.freeze({ ...witnesses }) });
298
+ }
222
299
  /** Identity helper for a `nifra.assurance.ts` default export. */
223
300
  export function defineAssuranceConfig(config) {
224
301
  if (config.manifest?.path !== undefined && config.manifest.path.trim() === "") {
@@ -227,9 +304,8 @@ export function defineAssuranceConfig(config) {
227
304
  if (config.manifest?.signer !== undefined && typeof config.manifest.signer !== "function") {
228
305
  throw new Error("route assurance: manifest signer must be a function");
229
306
  }
230
- if (config.invariants !== undefined && typeof config.invariants.executor !== "function") {
231
- throw new Error("route assurance: invariant executor must be a function");
232
- }
307
+ if (config.invariants !== undefined)
308
+ assertInvariantsConfig(config.invariants);
233
309
  if (config.rulePacks !== undefined) {
234
310
  if (!Array.isArray(config.rulePacks))
235
311
  throw new TypeError("route assurance: rulePacks must be an array");
@@ -286,7 +362,7 @@ export function defineAssuranceConfig(config) {
286
362
  // that passes capability assurance, so the rule would match nothing.
287
363
  if (capabilities !== undefined) {
288
364
  const defined = new Set(capabilities.definitions.map((definition) => definition.id));
289
- for (const rule of policy.rules) {
365
+ for (const rule of [...policy.rules, ...(policy.baseline ?? [])]) {
290
366
  const undefinedToken = rule.match.capabilities?.find((token) => !defined.has(token));
291
367
  if (undefinedToken !== undefined) {
292
368
  throw new Error(`route assurance: rule ${JSON.stringify(rule.name)} selects capability ${JSON.stringify(undefinedToken)}, which the capability policy does not define`);
@@ -312,7 +388,7 @@ export function defineAssuranceConfig(config) {
312
388
  }
313
389
  : {}),
314
390
  ...(config.invariants !== undefined
315
- ? { invariants: Object.freeze({ executor: config.invariants.executor }) }
391
+ ? { invariants: frozenInvariantsConfig(config.invariants) }
316
392
  : {}),
317
393
  ...(config.rulePacks !== undefined ? { rulePacks: Object.freeze([...config.rulePacks]) } : {}),
318
394
  ...(config.assureSink !== undefined ? { assureSink: config.assureSink } : {}),
@@ -345,15 +421,19 @@ function effectiveBodyLimit(schema) {
345
421
  }
346
422
  export function matchesAssuranceSelector(route, selector, definitions) {
347
423
  const { methods, paths, tools, capabilities, access, zone, classificationAtLeast } = selector;
348
- const { hasBody, bodyLimit } = selector;
424
+ const { exceptMethods, hasBody, hasResponse, bodyLimit } = selector;
349
425
  if (methods !== undefined && !methods.includes(route.method))
350
426
  return false;
427
+ if (exceptMethods?.some((method) => method === route.method) === true)
428
+ return false;
351
429
  if (paths !== undefined && !paths.some((pattern) => routeGlob(pattern).test(route.path)))
352
430
  return false;
353
431
  if (tools !== undefined && (route.tool !== undefined) !== tools)
354
432
  return false;
355
433
  if (hasBody !== undefined && (route.schema?.body !== undefined) !== hasBody)
356
434
  return false;
435
+ if (hasResponse !== undefined && (route.schema?.response !== undefined) !== hasResponse)
436
+ return false;
357
437
  if (bodyLimit !== undefined && effectiveBodyLimit(route.schema) !== bodyLimit)
358
438
  return false;
359
439
  if (classificationAtLeast !== undefined &&
@@ -381,7 +461,49 @@ export function matchesAssuranceSelector(route, selector, definitions) {
381
461
  }
382
462
  return true;
383
463
  }
384
- /** Evaluate reflected route evidence against the first matching policy rule. */
464
+ /** One rule's missing and forbidden evidence on one route, each also reported as a finding. */
465
+ function checkAssuranceRule(rule, route, evidence, evidenceIds, findings) {
466
+ const requiredProvenance = rule.requireProvenance ?? "any";
467
+ const missing = (rule.require ?? []).filter((id) => {
468
+ if (!evidenceIds.has(id))
469
+ return true;
470
+ if (requiredProvenance === "any")
471
+ return false;
472
+ return !evidence.some((item) => item.id === id && evidenceProvenance(item) === requiredProvenance);
473
+ });
474
+ const forbidden = (rule.forbid ?? []).filter((id) => evidenceIds.has(id));
475
+ if (rule.requireCsrfWithAuthenticated === true &&
476
+ evidenceIds.has(NIFRA_ASSURANCE_IDS.AUTHENTICATED) &&
477
+ !evidence.some((item) => item.id === NIFRA_ASSURANCE_IDS.CSRF && evidenceProvenance(item) === "runtime") &&
478
+ !missing.includes(NIFRA_ASSURANCE_IDS.CSRF)) {
479
+ missing.push(NIFRA_ASSURANCE_IDS.CSRF);
480
+ }
481
+ for (const id of missing) {
482
+ findings.push({
483
+ code: "missing-evidence",
484
+ method: route.method,
485
+ path: route.path,
486
+ rule: rule.name,
487
+ evidence: id,
488
+ message: requiredProvenance === "any"
489
+ ? `${route.method} ${route.path} (${rule.name}) is missing ${id}`
490
+ : `${route.method} ${route.path} (${rule.name}) is missing ${requiredProvenance} evidence for ${id}`,
491
+ });
492
+ }
493
+ for (const id of forbidden) {
494
+ findings.push({
495
+ code: "forbidden-evidence",
496
+ method: route.method,
497
+ path: route.path,
498
+ rule: rule.name,
499
+ evidence: id,
500
+ message: `${route.method} ${route.path} (${rule.name}) carries forbidden ${id}`,
501
+ });
502
+ }
503
+ return { missing, forbidden };
504
+ }
505
+ /** Evaluate reflected route evidence against the first matching policy rule, plus every matching
506
+ * baseline rule. */
385
507
  export function evaluateRouteAssurance(source, policyInput, options) {
386
508
  const policy = defineAssurancePolicy(policyInput);
387
509
  const definitions = definitionMap(options?.definitions);
@@ -408,6 +530,17 @@ export function evaluateRouteAssurance(source, policyInput, options) {
408
530
  const rule = policy.rules.find((candidate) => matchesAssuranceSelector(route, candidate.match, definitions));
409
531
  const evidence = route.assurance ?? [];
410
532
  const evidenceIds = new Set(evidence.map((item) => item.id));
533
+ const missing = [];
534
+ const forbidden = [];
535
+ const hold = (candidate) => {
536
+ const outcome = checkAssuranceRule(candidate, route, evidence, evidenceIds, findings);
537
+ for (const id of outcome.missing)
538
+ if (!missing.includes(id))
539
+ missing.push(id);
540
+ for (const id of outcome.forbidden)
541
+ if (!forbidden.includes(id))
542
+ forbidden.push(id);
543
+ };
411
544
  if (rule === undefined) {
412
545
  if (policy.unmatched !== "ignore") {
413
546
  findings.push({
@@ -417,75 +550,33 @@ export function evaluateRouteAssurance(source, policyInput, options) {
417
550
  message: `${route.method} ${route.path} is not classified by an assurance rule`,
418
551
  });
419
552
  }
420
- routes.push({
421
- method: route.method,
422
- path: route.path,
423
- evidence,
424
- missing: [],
425
- forbidden: [],
426
- ...(route.schema?.response !== undefined || route.schema?.errors !== undefined
427
- ? { responseContract: route.responseContract ?? "unchecked" }
428
- : {}),
429
- });
430
- continue;
431
- }
432
- const requiredProvenance = rule.requireProvenance ?? "any";
433
- let missing = (rule.require ?? []).filter((id) => {
434
- if (!evidenceIds.has(id))
435
- return true;
436
- if (requiredProvenance === "any")
437
- return false;
438
- return !evidence.some((item) => item.id === id && evidenceProvenance(item) === requiredProvenance);
439
- });
440
- const forbidden = (rule.forbid ?? []).filter((id) => evidenceIds.has(id));
441
- if (rule.requireCsrfWithAuthenticated === true &&
442
- evidenceIds.has(NIFRA_ASSURANCE_IDS.AUTHENTICATED) &&
443
- !evidence.some((item) => item.id === NIFRA_ASSURANCE_IDS.CSRF && evidenceProvenance(item) === "runtime")) {
444
- const id = NIFRA_ASSURANCE_IDS.CSRF;
445
- if (!missing.includes(id))
446
- missing = [...missing, id];
447
- }
448
- for (const id of missing) {
449
- findings.push({
450
- code: "missing-evidence",
451
- method: route.method,
452
- path: route.path,
453
- rule: rule.name,
454
- evidence: id,
455
- message: requiredProvenance === "any"
456
- ? `${route.method} ${route.path} (${rule.name}) is missing ${id}`
457
- : `${route.method} ${route.path} (${rule.name}) is missing ${requiredProvenance} evidence for ${id}`,
458
- });
459
553
  }
460
- for (const id of forbidden) {
461
- findings.push({
462
- code: "forbidden-evidence",
463
- method: route.method,
464
- path: route.path,
465
- rule: rule.name,
466
- evidence: id,
467
- message: `${route.method} ${route.path} (${rule.name}) carries forbidden ${id}`,
468
- });
554
+ else {
555
+ hold(rule);
556
+ // Opt-in visibility: a pure-classification rule (no require + no forbid) matching a route with
557
+ // zero evidence is a "label without proof". Surface it so the gap isn't silently green. A rule
558
+ // that expects or forbids evidence already speaks for itself, so it's excluded here.
559
+ if (policy.flagClassifiedWithoutEvidence === true &&
560
+ evidence.length === 0 &&
561
+ (rule.require ?? []).length === 0 &&
562
+ (rule.forbid ?? []).length === 0) {
563
+ findings.push({
564
+ code: "classified-no-evidence",
565
+ method: route.method,
566
+ path: route.path,
567
+ rule: rule.name,
568
+ message: `${route.method} ${route.path} (${rule.name}) is classified but carries no enforcement evidence`,
569
+ });
570
+ }
469
571
  }
470
- // Opt-in visibility: a pure-classification rule (no require + no forbid) matching a route with zero
471
- // evidence is a "label without proof". Surface it so the gap isn't silently green. A rule that expects
472
- // or forbids evidence already speaks for itself via the loops above, so it's excluded here.
473
- if (policy.flagClassifiedWithoutEvidence === true &&
474
- evidence.length === 0 &&
475
- (rule.require ?? []).length === 0 &&
476
- (rule.forbid ?? []).length === 0) {
477
- findings.push({
478
- code: "classified-no-evidence",
479
- method: route.method,
480
- path: route.path,
481
- rule: rule.name,
482
- message: `${route.method} ${route.path} (${rule.name}) is classified but carries no enforcement evidence`,
483
- });
572
+ for (const candidate of policy.baseline ?? []) {
573
+ if (matchesAssuranceSelector(route, candidate.match, definitions))
574
+ hold(candidate);
484
575
  }
485
576
  routes.push({
486
577
  method: route.method,
487
578
  path: route.path,
488
- rule: rule.name,
579
+ ...(rule === undefined ? {} : { rule: rule.name }),
489
580
  evidence,
490
581
  missing: Object.freeze(missing),
491
582
  forbidden: Object.freeze(forbidden),