@nifrajs/core 2.11.0 → 2.12.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 (180) hide show
  1. package/dist/assurance.d.ts +61 -3
  2. package/dist/assurance.d.ts.map +1 -1
  3. package/dist/assurance.js +152 -5
  4. package/dist/assurance.js.map +1 -1
  5. package/dist/capabilities.d.ts +9 -1
  6. package/dist/capabilities.d.ts.map +1 -1
  7. package/dist/capabilities.js +5 -1
  8. package/dist/capabilities.js.map +1 -1
  9. package/dist/diff.d.ts +2 -1
  10. package/dist/diff.d.ts.map +1 -1
  11. package/dist/diff.js +3 -1
  12. package/dist/diff.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/evidence.d.ts +89 -0
  17. package/dist/evidence.d.ts.map +1 -0
  18. package/dist/evidence.js +170 -0
  19. package/dist/evidence.js.map +1 -0
  20. package/dist/execution-policy.d.ts +23 -0
  21. package/dist/execution-policy.d.ts.map +1 -0
  22. package/dist/execution-policy.js +28 -0
  23. package/dist/execution-policy.js.map +1 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/internal/route-assurance.d.ts +19 -0
  27. package/dist/internal/route-assurance.d.ts.map +1 -1
  28. package/dist/internal/route-assurance.js +38 -5
  29. package/dist/internal/route-assurance.js.map +1 -1
  30. package/dist/internal/route-execution.d.ts +18 -0
  31. package/dist/internal/route-execution.d.ts.map +1 -1
  32. package/dist/internal/route-execution.js +67 -1
  33. package/dist/internal/route-execution.js.map +1 -1
  34. package/dist/manifest.d.ts +5 -1
  35. package/dist/manifest.d.ts.map +1 -1
  36. package/dist/manifest.js +22 -4
  37. package/dist/manifest.js.map +1 -1
  38. package/dist/reflection.d.ts +5 -0
  39. package/dist/reflection.d.ts.map +1 -1
  40. package/dist/reflection.js +13 -4
  41. package/dist/reflection.js.map +1 -1
  42. package/dist/replay.d.ts +26 -0
  43. package/dist/replay.d.ts.map +1 -0
  44. package/dist/replay.js +63 -0
  45. package/dist/replay.js.map +1 -0
  46. package/dist/schema/standard.d.ts +2 -0
  47. package/dist/schema/standard.d.ts.map +1 -1
  48. package/dist/schema/standard.js +13 -0
  49. package/dist/schema/standard.js.map +1 -1
  50. package/dist/server/body.d.ts +49 -6
  51. package/dist/server/body.d.ts.map +1 -1
  52. package/dist/server/body.js +226 -8
  53. package/dist/server/body.js.map +1 -1
  54. package/dist/server/context.d.ts +20 -3
  55. package/dist/server/context.d.ts.map +1 -1
  56. package/dist/server/contract.d.ts +15 -3
  57. package/dist/server/contract.d.ts.map +1 -1
  58. package/dist/server/contract.js +22 -0
  59. package/dist/server/contract.js.map +1 -1
  60. package/dist/server/cookies.d.ts +26 -5
  61. package/dist/server/cookies.d.ts.map +1 -1
  62. package/dist/server/cookies.js +76 -9
  63. package/dist/server/cookies.js.map +1 -1
  64. package/dist/server/edge.d.ts.map +1 -1
  65. package/dist/server/edge.js +11 -0
  66. package/dist/server/edge.js.map +1 -1
  67. package/dist/server/header-case.d.ts +9 -0
  68. package/dist/server/header-case.d.ts.map +1 -0
  69. package/dist/server/header-case.js +54 -0
  70. package/dist/server/header-case.js.map +1 -0
  71. package/dist/server/headers.d.ts +8 -0
  72. package/dist/server/headers.d.ts.map +1 -0
  73. package/dist/server/headers.js +13 -0
  74. package/dist/server/headers.js.map +1 -0
  75. package/dist/server/node-outcome-hook.d.ts.map +1 -1
  76. package/dist/server/node-outcome-hook.js +7 -11
  77. package/dist/server/node-outcome-hook.js.map +1 -1
  78. package/dist/server/node-outcome.d.ts +8 -1
  79. package/dist/server/node-outcome.d.ts.map +1 -1
  80. package/dist/server/node-outcome.js +10 -3
  81. package/dist/server/node-outcome.js.map +1 -1
  82. package/dist/server/plugin.d.ts +66 -7
  83. package/dist/server/plugin.d.ts.map +1 -1
  84. package/dist/server/plugin.js +14 -10
  85. package/dist/server/plugin.js.map +1 -1
  86. package/dist/server/proto-guard.d.ts +30 -0
  87. package/dist/server/proto-guard.d.ts.map +1 -0
  88. package/dist/server/proto-guard.js +116 -0
  89. package/dist/server/proto-guard.js.map +1 -0
  90. package/dist/server/registry.d.ts +6 -2
  91. package/dist/server/registry.d.ts.map +1 -1
  92. package/dist/server/request-context.d.ts +24 -6
  93. package/dist/server/request-context.d.ts.map +1 -1
  94. package/dist/server/request-context.js +157 -30
  95. package/dist/server/request-context.js.map +1 -1
  96. package/dist/server/respond.d.ts +2 -2
  97. package/dist/server/respond.d.ts.map +1 -1
  98. package/dist/server/respond.js +34 -8
  99. package/dist/server/respond.js.map +1 -1
  100. package/dist/server/response-contract-lane.d.ts +1 -1
  101. package/dist/server/response-contract-lane.d.ts.map +1 -1
  102. package/dist/server/response-contract-lane.js +45 -13
  103. package/dist/server/response-contract-lane.js.map +1 -1
  104. package/dist/server/response-hooks.d.ts +10 -0
  105. package/dist/server/response-hooks.d.ts.map +1 -0
  106. package/dist/server/response-hooks.js +52 -0
  107. package/dist/server/response-hooks.js.map +1 -0
  108. package/dist/server/runtime-core.d.ts +12 -0
  109. package/dist/server/runtime-core.d.ts.map +1 -1
  110. package/dist/server/runtime-core.js +8 -0
  111. package/dist/server/runtime-core.js.map +1 -1
  112. package/dist/server/server-types.d.ts +60 -6
  113. package/dist/server/server-types.d.ts.map +1 -1
  114. package/dist/server/server.d.ts +80 -23
  115. package/dist/server/server.d.ts.map +1 -1
  116. package/dist/server/server.js +424 -213
  117. package/dist/server/server.js.map +1 -1
  118. package/dist/server/static-headers.d.ts +14 -3
  119. package/dist/server/static-headers.d.ts.map +1 -1
  120. package/dist/server/static-headers.js +28 -6
  121. package/dist/server/static-headers.js.map +1 -1
  122. package/dist/server/transport-lane.d.ts +8 -0
  123. package/dist/server/transport-lane.d.ts.map +1 -1
  124. package/dist/server/transport-lane.js +12 -8
  125. package/dist/server/transport-lane.js.map +1 -1
  126. package/dist/server/websocket.d.ts +3 -0
  127. package/dist/server/websocket.d.ts.map +1 -1
  128. package/dist/server/websocket.js +14 -0
  129. package/dist/server/websocket.js.map +1 -1
  130. package/dist/server.d.ts +2 -2
  131. package/dist/server.d.ts.map +1 -1
  132. package/dist/server.js +2 -2
  133. package/dist/server.js.map +1 -1
  134. package/dist/tool-catalog.d.ts +21 -0
  135. package/dist/tool-catalog.d.ts.map +1 -0
  136. package/dist/tool-catalog.js +16 -0
  137. package/dist/tool-catalog.js.map +1 -0
  138. package/dist/tool-contract.d.ts +238 -0
  139. package/dist/tool-contract.d.ts.map +1 -0
  140. package/dist/tool-contract.js +569 -0
  141. package/dist/tool-contract.js.map +1 -0
  142. package/package.json +26 -1
  143. package/src/assurance.ts +236 -6
  144. package/src/capabilities.ts +14 -1
  145. package/src/diff.ts +5 -2
  146. package/src/errors.ts +1 -0
  147. package/src/evidence.ts +319 -0
  148. package/src/execution-policy.ts +57 -0
  149. package/src/index.ts +1 -1
  150. package/src/internal/route-assurance.ts +67 -4
  151. package/src/internal/route-execution.ts +276 -1
  152. package/src/manifest.ts +27 -5
  153. package/src/reflection.ts +24 -4
  154. package/src/replay.ts +96 -0
  155. package/src/schema/standard.ts +16 -0
  156. package/src/server/body.ts +275 -7
  157. package/src/server/context.ts +23 -3
  158. package/src/server/contract.ts +43 -3
  159. package/src/server/cookies.ts +80 -10
  160. package/src/server/edge.ts +10 -0
  161. package/src/server/header-case.ts +54 -0
  162. package/src/server/headers.ts +11 -0
  163. package/src/server/node-outcome-hook.ts +6 -11
  164. package/src/server/node-outcome.ts +10 -2
  165. package/src/server/plugin.ts +87 -8
  166. package/src/server/proto-guard.ts +118 -0
  167. package/src/server/registry.ts +6 -2
  168. package/src/server/request-context.ts +242 -26
  169. package/src/server/respond.ts +35 -4
  170. package/src/server/response-contract-lane.ts +6 -17
  171. package/src/server/response-hooks.ts +51 -0
  172. package/src/server/runtime-core.ts +14 -0
  173. package/src/server/server-types.ts +66 -6
  174. package/src/server/server.ts +667 -287
  175. package/src/server/static-headers.ts +27 -3
  176. package/src/server/transport-lane.ts +19 -8
  177. package/src/server/websocket.ts +18 -2
  178. package/src/server.ts +7 -0
  179. package/src/tool-catalog.ts +40 -0
  180. package/src/tool-contract.ts +896 -0
@@ -17,11 +17,14 @@
17
17
  * The `Server` import is type-only and erased, so there is no runtime cycle back into the kernel.
18
18
  */
19
19
  import type { RequestBudget } from "../budget.ts"
20
+ import type { RequestLedger } from "../ledger.ts"
20
21
  import type { StandardSchemaV1 } from "../schema/standard.ts"
21
22
  import type { Platform, RouteSchema } from "../server/context.ts"
22
23
  import type { ResolvedIdempotency } from "../server/idempotency-lane.ts"
23
- import type { ResolvedEffectLedger } from "../server/ledger-lane.ts"
24
+ import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger-lane.ts"
25
+ import type { ProtoPoisoning } from "../server/proto-guard.ts"
24
26
  import type { Registry } from "../server/registry.ts"
27
+ import { RequestContext } from "../server/request-context.ts"
25
28
  import type { ResponseContractRuntime } from "../server/response-contract-lane.ts"
26
29
  import type { HandlerResult } from "../server/runtime-core.ts"
27
30
  import type { CtxSet, MaybePromise, RawContext, RequestSource, Server } from "../server/server.ts"
@@ -58,6 +61,276 @@ export type ContextRouteRunner = <T, R extends Registry, Ctx>(
58
61
  wrapResponse: (response: Response) => T,
59
62
  ) => MaybePromise<T>
60
63
 
64
+ /** The kernel's private execution surface, mirrored structurally. The runner methods are private on
65
+ * `Server` by design; this compiler lives outside the class, so it reaches them through a type-level
66
+ * mirror of their exact signatures. The `as unknown as` at each use is erased by compilation - the
67
+ * request path calls the kernel methods directly, with no adapter object and no extra hop. The mirror
68
+ * is not exported: it is this module's view of the kernel, not vocabulary anyone else may bind to. */
69
+ interface RouteExecutionRuntime {
70
+ runContextlessBare<T>(
71
+ entry: RouteEntry,
72
+ source: RequestSource,
73
+ params: Record<string, string>,
74
+ search: string | undefined,
75
+ signal: AbortSignal,
76
+ budget: RequestBudget,
77
+ platform: Platform | undefined,
78
+ finalize: (result: unknown, set: CtxSet) => T,
79
+ wrapResponse: (response: Response) => T,
80
+ ): MaybePromise<T>
81
+ runBare<T>(
82
+ entry: RouteEntry,
83
+ ctx: RawContext,
84
+ finalize: (result: unknown, set: CtxSet) => T,
85
+ wrapResponse: (response: Response) => T,
86
+ ): MaybePromise<T>
87
+ runBodyOnly<T>(
88
+ entry: RouteEntry,
89
+ source: RequestSource,
90
+ ctx: RawContext,
91
+ finalize: (result: unknown, set: CtxSet) => T,
92
+ wrapResponse: (response: Response) => T,
93
+ ): MaybePromise<T>
94
+ runQueryOnly<T>(
95
+ entry: RouteEntry,
96
+ ctx: RawContext,
97
+ finalize: (result: unknown, set: CtxSet) => T,
98
+ wrapResponse: (response: Response) => T,
99
+ ): MaybePromise<T>
100
+ runLifecycleHooks<T>(
101
+ entry: RouteEntry,
102
+ ctx: RawContext,
103
+ finalize: (result: unknown, set: CtxSet) => T,
104
+ wrapResponse: (response: Response) => T,
105
+ ): MaybePromise<T>
106
+ runLifecycleQuery<T>(
107
+ entry: RouteEntry,
108
+ ctx: RawContext,
109
+ finalize: (result: unknown, set: CtxSet) => T,
110
+ wrapResponse: (response: Response) => T,
111
+ ): MaybePromise<T>
112
+ runLifecycleBody<T>(
113
+ entry: RouteEntry,
114
+ source: RequestSource,
115
+ ctx: RawContext,
116
+ finalize: (result: unknown, set: CtxSet) => T,
117
+ wrapResponse: (response: Response) => T,
118
+ ): MaybePromise<T>
119
+ runLifecycleBodyQuery<T>(
120
+ entry: RouteEntry,
121
+ source: RequestSource,
122
+ ctx: RawContext,
123
+ finalize: (result: unknown, set: CtxSet) => T,
124
+ wrapResponse: (response: Response) => T,
125
+ ): MaybePromise<T>
126
+ runLifecycle<T>(
127
+ entry: RouteEntry,
128
+ source: RequestSource,
129
+ ctx: RawContext,
130
+ finalize: (result: unknown, set: CtxSet) => T,
131
+ wrapResponse: (response: Response) => T,
132
+ ): MaybePromise<T>
133
+ runWithAround<T>(
134
+ entry: RouteEntry,
135
+ ctx: RawContext,
136
+ run: () => MaybePromise<T>,
137
+ finalize: (result: unknown, set: CtxSet) => T,
138
+ wrapResponse: (response: Response) => T,
139
+ ): MaybePromise<T>
140
+ readonly maxBodyBytes: number
141
+ readonly protoPoisoning: ProtoPoisoning
142
+ readonly effectLedgerRuntime: EffectLedgerRuntime | undefined
143
+ readonly logger: {
144
+ error(message: string, fields: { method: string; path: string; name: string }): void
145
+ }
146
+ }
147
+
148
+ export type RouteExecutionLane = "bare" | "body" | "query" | "lifecycle"
149
+ export type LifecycleExecutionLane = "hooks" | "query" | "body" | "body-query" | undefined
150
+
151
+ /** Compile the one immutable route decision at registration time. The request path only invokes the
152
+ * selected closure; it never repeats this eligibility ladder. The closures reach the kernel's private
153
+ * runners through the {@link RouteExecutionRuntime} mirror - a compile-time cast, not a dispatch
154
+ * object, so the emitted request path is a direct method call on the server. */
155
+ export function compileRouteExecutionPlan(options: {
156
+ readonly lane: RouteExecutionLane
157
+ readonly contextless: boolean
158
+ readonly hasAround: boolean
159
+ readonly hasLedger: boolean
160
+ readonly lifecycleLane: LifecycleExecutionLane
161
+ readonly fusedWeb: FusedWebRunner | undefined
162
+ readonly fusedBody: FusedBodyRunner | undefined
163
+ readonly fusedLane: "bare" | "body" | "query" | undefined
164
+ }): RouteExecutionPlan {
165
+ const { lane, contextless, hasAround, hasLedger, lifecycleLane, fusedWeb, fusedBody, fusedLane } =
166
+ options
167
+
168
+ if (contextless) {
169
+ const run: RouteExecutionRunner = (
170
+ runtime,
171
+ entry,
172
+ source,
173
+ params,
174
+ search,
175
+ signal,
176
+ budget,
177
+ platform,
178
+ _nativeContext,
179
+ finalize,
180
+ wrapResponse,
181
+ ) =>
182
+ (runtime as unknown as RouteExecutionRuntime).runContextlessBare(
183
+ entry,
184
+ source,
185
+ params,
186
+ search,
187
+ signal,
188
+ budget,
189
+ platform,
190
+ finalize,
191
+ wrapResponse,
192
+ )
193
+ return Object.freeze({ run, fusedWeb, fusedBody, fusedLane })
194
+ }
195
+
196
+ let inner: ContextRouteRunner
197
+ switch (lane) {
198
+ case "bare":
199
+ inner = (runtime, entry, _source, ctx, finalize, wrapResponse) =>
200
+ (runtime as unknown as RouteExecutionRuntime).runBare(entry, ctx, finalize, wrapResponse)
201
+ break
202
+ case "body":
203
+ inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
204
+ (runtime as unknown as RouteExecutionRuntime).runBodyOnly(
205
+ entry,
206
+ source,
207
+ ctx,
208
+ finalize,
209
+ wrapResponse,
210
+ )
211
+ break
212
+ case "query":
213
+ inner = (runtime, entry, _source, ctx, finalize, wrapResponse) =>
214
+ (runtime as unknown as RouteExecutionRuntime).runQueryOnly(
215
+ entry,
216
+ ctx,
217
+ finalize,
218
+ wrapResponse,
219
+ )
220
+ break
221
+ default:
222
+ switch (lifecycleLane) {
223
+ case "hooks":
224
+ inner = (runtime, entry, _source, ctx, finalize, wrapResponse) =>
225
+ (runtime as unknown as RouteExecutionRuntime).runLifecycleHooks(
226
+ entry,
227
+ ctx,
228
+ finalize,
229
+ wrapResponse,
230
+ )
231
+ break
232
+ case "query":
233
+ inner = (runtime, entry, _source, ctx, finalize, wrapResponse) =>
234
+ (runtime as unknown as RouteExecutionRuntime).runLifecycleQuery(
235
+ entry,
236
+ ctx,
237
+ finalize,
238
+ wrapResponse,
239
+ )
240
+ break
241
+ case "body":
242
+ inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
243
+ (runtime as unknown as RouteExecutionRuntime).runLifecycleBody(
244
+ entry,
245
+ source,
246
+ ctx,
247
+ finalize,
248
+ wrapResponse,
249
+ )
250
+ break
251
+ case "body-query":
252
+ inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
253
+ (runtime as unknown as RouteExecutionRuntime).runLifecycleBodyQuery(
254
+ entry,
255
+ source,
256
+ ctx,
257
+ finalize,
258
+ wrapResponse,
259
+ )
260
+ break
261
+ default:
262
+ inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
263
+ (runtime as unknown as RouteExecutionRuntime).runLifecycle(
264
+ entry,
265
+ source,
266
+ ctx,
267
+ finalize,
268
+ wrapResponse,
269
+ )
270
+ }
271
+ }
272
+
273
+ const execute: ContextRouteRunner = hasAround
274
+ ? (runtime, entry, source, ctx, finalize, wrapResponse) =>
275
+ (runtime as unknown as RouteExecutionRuntime).runWithAround(
276
+ entry,
277
+ ctx,
278
+ () => inner(runtime, entry, source, ctx, finalize, wrapResponse),
279
+ finalize,
280
+ wrapResponse,
281
+ )
282
+ : inner
283
+
284
+ const run: RouteExecutionRunner = (
285
+ runtime,
286
+ entry,
287
+ source,
288
+ params,
289
+ search,
290
+ signal,
291
+ budget,
292
+ platform,
293
+ nativeContext,
294
+ finalize,
295
+ wrapResponse,
296
+ ) => {
297
+ const server = runtime as unknown as RouteExecutionRuntime
298
+ const maxBodyBytes = entry.bodyLimit ?? server.maxBodyBytes
299
+ const ctx = nativeContext
300
+ ? RequestContext.native(source, params, search, maxBodyBytes, platform, server.protoPoisoning)
301
+ : new RequestContext(
302
+ source,
303
+ params,
304
+ search,
305
+ signal,
306
+ budget,
307
+ platform,
308
+ maxBodyBytes,
309
+ server.protoPoisoning,
310
+ )
311
+ let ledger: RequestLedger | undefined
312
+ // The runtime is always present when a route resolved a ledger (enforced at registration).
313
+ const ledgerRuntime = server.effectLedgerRuntime
314
+ if (hasLedger && ledgerRuntime !== undefined) {
315
+ const resolved = entry.ledgered as ResolvedEffectLedger
316
+ ledger = ledgerRuntime.create(resolved)
317
+ ledgerRuntime.attach(ctx, ledger)
318
+ }
319
+ let outcome = execute(runtime, entry, source, ctx, finalize, wrapResponse)
320
+ if (ledger !== undefined && ledgerRuntime !== undefined) {
321
+ const active = ledger
322
+ const resolved = entry.ledgered as ResolvedEffectLedger
323
+ outcome = (outcome instanceof Promise ? outcome : Promise.resolve(outcome)).then((value) =>
324
+ ledgerRuntime.settle(active, resolved, value, (fields) =>
325
+ server.logger.error("effect ledger sink failed", fields),
326
+ ),
327
+ )
328
+ }
329
+ return outcome
330
+ }
331
+ return Object.freeze({ run, fusedWeb, fusedBody, fusedLane })
332
+ }
333
+
61
334
  /** Registration-compiled route behavior. Every adapter invokes the same runner; the optional fused
62
335
  * renderer is only a response-format specialization of that same selected route semantics. */
63
336
  export interface RouteExecutionPlan {
@@ -73,6 +346,8 @@ export interface RouteExecutionPlan {
73
346
  export interface RouteEntry {
74
347
  readonly handler: InternalHandler
75
348
  readonly schema: RouteSchema | undefined
349
+ /** Effective transport body cap; `undefined` is an explicit streaming/upload exemption. */
350
+ readonly bodyLimit: number | undefined
76
351
  /** Resolved idempotency config; `undefined` = off (the dedupe lane is never entered). */
77
352
  readonly idempotent: ResolvedIdempotency | undefined
78
353
  /** Resolved effect-ledger wiring; `undefined` = off (no per-request ledger, no settle step). */
package/src/manifest.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  type RouteSnapshotSchema,
15
15
  snapshotRoutes,
16
16
  } from "./diff.ts"
17
+ import type { ProjectEvidenceSnapshot } from "./evidence.ts"
17
18
  import { reflectRoutes } from "./reflection.ts"
18
19
 
19
20
  export interface NifraManifestAssurance {
@@ -60,7 +61,10 @@ export interface NifraManifestSignature {
60
61
  }
61
62
 
62
63
  export interface BuildNifraManifestInput {
63
- readonly source: unknown
64
+ /** Runtime route source, retained for compatibility with callers that have not built a snapshot. */
65
+ readonly source?: unknown
66
+ /** Canonical offline evidence; when supplied, no second route reflection is performed. */
67
+ readonly evidence?: ProjectEvidenceSnapshot
64
68
  readonly assurance?: AssuranceReport
65
69
  readonly capabilities?: CapabilityAssuranceReport
66
70
  }
@@ -171,22 +175,40 @@ const sortedStrings = (values: Iterable<string>): readonly string[] =>
171
175
 
172
176
  /** Build one fail-closed, deterministic manifest from already-evaluated assurance reports. */
173
177
  export async function buildNifraManifest(input: BuildNifraManifestInput): Promise<NifraManifest> {
178
+ if (input.evidence === undefined && input.source === undefined) {
179
+ throw new TypeError("nifra manifest: source or canonical evidence is required")
180
+ }
174
181
  if (input.assurance !== undefined && !input.assurance.ok) {
175
182
  throw new Error("nifra manifest: refusing to emit failing route assurance")
176
183
  }
184
+ if (input.evidence?.assurance !== undefined && !input.evidence.assurance.ok) {
185
+ throw new Error("nifra manifest: refusing to emit failing route assurance")
186
+ }
177
187
  if (input.capabilities !== undefined && !input.capabilities.ok) {
178
188
  throw new Error("nifra manifest: refusing to emit failing capability assurance")
179
189
  }
190
+ if (input.evidence?.capabilities !== undefined && !input.evidence.capabilities.ok) {
191
+ throw new Error("nifra manifest: refusing to emit failing capability assurance")
192
+ }
180
193
  const reflected = new Map(
181
- reflectRoutes(input.source).map((route) => [routeKey(route.method, route.path), route]),
194
+ (input.evidence?.routes ?? reflectRoutes(input.source)).map((route) => [
195
+ routeKey(route.method, route.path),
196
+ route,
197
+ ]),
182
198
  )
183
199
  const assured = new Map(
184
- (input.assurance?.routes ?? []).map((route) => [routeKey(route.method, route.path), route]),
200
+ (input.evidence?.assurance?.routes ?? input.assurance?.routes ?? []).map((route) => [
201
+ routeKey(route.method, route.path),
202
+ route,
203
+ ]),
185
204
  )
186
205
  const capable = new Map(
187
- (input.capabilities?.routes ?? []).map((route) => [routeKey(route.method, route.path), route]),
206
+ (input.evidence?.capabilities?.routes ?? input.capabilities?.routes ?? []).map((route) => [
207
+ routeKey(route.method, route.path),
208
+ route,
209
+ ]),
188
210
  )
189
- const routes = snapshotRoutes(input.source)
211
+ const routes = (input.evidence?.routes ?? snapshotRoutes(input.source))
190
212
  .map((route): NifraManifestRoute => {
191
213
  const key = routeKey(route.method, route.path)
192
214
  const reflection = reflected.get(key)
package/src/reflection.ts CHANGED
@@ -13,7 +13,11 @@ import {
13
13
  routeClassification,
14
14
  } from "./classification.ts"
15
15
  import { normalizeRouteCapabilities } from "./internal/capability-runtime.ts"
16
- import { type AssuranceEvidence, validEvidence } from "./internal/route-assurance.ts"
16
+ import {
17
+ type AssuranceEvidence,
18
+ evidenceProvenance,
19
+ validEvidence,
20
+ } from "./internal/route-assurance.ts"
17
21
  import type { StandardSchemaV1 } from "./schema/standard.ts"
18
22
  import type { ToolAnnotations } from "./server/server.ts"
19
23
 
@@ -38,6 +42,11 @@ export interface SchemaReflection {
38
42
  }
39
43
 
40
44
  export interface ReflectedRouteSchema {
45
+ /** Effective transport body policy; surfaced so upload/streaming exemptions are auditable. */
46
+ readonly bodyLimit?: number | "unlimited"
47
+ readonly bodyLimitReason?: string
48
+ /** Request-header schema; names are normalized to lower-case at runtime. */
49
+ readonly headers?: SchemaReflection
41
50
  readonly body?: SchemaReflection
42
51
  readonly query?: SchemaReflection
43
52
  /** Path-params schema - constraints (uuid format, integer min/max) declared via `params: t.object(…)`. */
@@ -170,6 +179,13 @@ const reflectedRouteSchema = (value: unknown): ReflectedRouteSchema | undefined
170
179
  }
171
180
  }
172
181
  return {
182
+ ...(schema.bodyLimit === "unlimited" || typeof schema.bodyLimit === "number"
183
+ ? { bodyLimit: schema.bodyLimit }
184
+ : {}),
185
+ ...(typeof schema.bodyLimitReason === "string"
186
+ ? { bodyLimitReason: schema.bodyLimitReason }
187
+ : {}),
188
+ ...(schema.headers !== undefined ? { headers: reflectSchema(schema.headers) } : {}),
173
189
  ...(schema.body !== undefined ? { body: reflectSchema(schema.body) } : {}),
174
190
  ...(schema.query !== undefined ? { query: reflectSchema(schema.query) } : {}),
175
191
  ...(schema.params !== undefined ? { params: reflectSchema(schema.params) } : {}),
@@ -193,9 +209,13 @@ const reflectedTool = (value: unknown): ReflectedRoute["tool"] => {
193
209
 
194
210
  const reflectedAssurance = (value: unknown): readonly AssuranceEvidence[] | undefined => {
195
211
  if (!Array.isArray(value)) return undefined
196
- const evidence = value
197
- .filter(validEvidence)
198
- .map((item) => Object.freeze({ id: item.id, source: item.source.trim() }))
212
+ const evidence = value.filter(validEvidence).map((item) =>
213
+ Object.freeze({
214
+ id: item.id,
215
+ source: item.source.trim(),
216
+ provenance: evidenceProvenance(item),
217
+ }),
218
+ )
199
219
  return evidence.length > 0 ? Object.freeze(evidence) : undefined
200
220
  }
201
221
 
package/src/replay.ts ADDED
@@ -0,0 +1,96 @@
1
+ /** Versioned, token-only replay metadata shared by verification tools. */
2
+
3
+ export interface ReplayFile {
4
+ readonly version: 1
5
+ readonly gate: string
6
+ readonly case: string
7
+ readonly seed: string
8
+ readonly inputsDigest: string
9
+ readonly meta: Readonly<Record<string, unknown>>
10
+ }
11
+
12
+ /** Existing adversarial contract replay metadata. Kept as a compatibility envelope. */
13
+ export interface LegacyContractReplayFile {
14
+ readonly seed: number
15
+ readonly caseId: string
16
+ readonly runtime: string
17
+ }
18
+
19
+ /** Existing deterministic failure-lab replay metadata. Kept as a compatibility envelope. */
20
+ export interface LegacyFailureReplayFile {
21
+ readonly seed: number
22
+ readonly schedule: readonly Record<string, unknown>[]
23
+ }
24
+
25
+ export type CompatibleReplayFile = ReplayFile | LegacyContractReplayFile | LegacyFailureReplayFile
26
+
27
+ export function defineReplayFile(input: Omit<ReplayFile, "version">): ReplayFile {
28
+ if (input.gate.trim() === "" || input.case.trim() === "" || input.seed.trim() === "")
29
+ throw new TypeError("replay metadata requires gate, case, and seed")
30
+ if (!/^[a-f0-9]{64}$/.test(input.inputsDigest))
31
+ throw new TypeError("replay inputsDigest must be a SHA-256 hex digest")
32
+ return Object.freeze({ version: 1, ...input, meta: Object.freeze({ ...input.meta }) })
33
+ }
34
+
35
+ export function parseReplayFile(value: unknown): ReplayFile {
36
+ const record =
37
+ typeof value === "object" && value !== null && !Array.isArray(value)
38
+ ? Object.fromEntries(Object.entries(value))
39
+ : undefined
40
+ if (record === undefined) throw new TypeError("invalid replay file")
41
+ const meta =
42
+ typeof record.meta === "object" && record.meta !== null && !Array.isArray(record.meta)
43
+ ? Object.fromEntries(Object.entries(record.meta))
44
+ : undefined
45
+ const gate = typeof record.gate === "string" ? record.gate : undefined
46
+ const caseId = typeof record.case === "string" ? record.case : undefined
47
+ const seed = typeof record.seed === "string" ? record.seed : undefined
48
+ const inputsDigest = typeof record.inputsDigest === "string" ? record.inputsDigest : undefined
49
+ if (
50
+ record.version !== 1 ||
51
+ gate === undefined ||
52
+ caseId === undefined ||
53
+ seed === undefined ||
54
+ inputsDigest === undefined ||
55
+ meta === undefined
56
+ ) {
57
+ throw new TypeError("invalid replay file: expected version 1 metadata")
58
+ }
59
+ return defineReplayFile({
60
+ gate,
61
+ case: caseId,
62
+ seed,
63
+ inputsDigest,
64
+ meta,
65
+ })
66
+ }
67
+
68
+ /** Parse the unified format while preserving the two pre-existing token-only replay shapes. */
69
+ export function parseCompatibleReplayFile(value: unknown): CompatibleReplayFile {
70
+ try {
71
+ return parseReplayFile(value)
72
+ } catch {
73
+ const record =
74
+ typeof value === "object" && value !== null && !Array.isArray(value)
75
+ ? Object.fromEntries(Object.entries(value))
76
+ : undefined
77
+ if (
78
+ typeof record?.seed === "number" &&
79
+ Number.isSafeInteger(record.seed) &&
80
+ typeof record.caseId === "string" &&
81
+ record.caseId.trim() !== "" &&
82
+ typeof record.runtime === "string" &&
83
+ record.runtime.trim() !== ""
84
+ ) {
85
+ return Object.freeze({ seed: record.seed, caseId: record.caseId, runtime: record.runtime })
86
+ }
87
+ if (
88
+ typeof record?.seed === "number" &&
89
+ Number.isSafeInteger(record.seed) &&
90
+ Array.isArray(record.schedule)
91
+ ) {
92
+ return Object.freeze({ seed: record.seed, schedule: Object.freeze([...record.schedule]) })
93
+ }
94
+ throw new TypeError("invalid replay file: expected unified or legacy token-only metadata")
95
+ }
96
+ }
@@ -53,6 +53,22 @@ export type ValidationOutcome<Output> =
53
53
  | { readonly ok: true; readonly value: Output }
54
54
  | { readonly ok: false; readonly issues: ReadonlyArray<StandardIssue> }
55
55
 
56
+ /** Format Standard Schema issues consistently across server and client contract diagnostics. */
57
+ export function formatStandardIssues(issues: ReadonlyArray<StandardIssue>): string {
58
+ return issues
59
+ .map((issue) => {
60
+ const path = Array.isArray(issue.path)
61
+ ? issue.path
62
+ .map((segment) =>
63
+ String(typeof segment === "object" && segment !== null ? segment.key : segment),
64
+ )
65
+ .join(".")
66
+ : ""
67
+ return path === "" ? issue.message : `${path}: ${issue.message}`
68
+ })
69
+ .join("; ")
70
+ }
71
+
56
72
  function normalizeStandardResult<Output>(
57
73
  result: StandardResult<Output>,
58
74
  ): ValidationOutcome<Output> {