@nifrajs/core 2.11.0 → 2.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assurance.d.ts +61 -3
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +152 -5
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +5 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/diff.d.ts +2 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +3 -1
- package/dist/diff.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +89 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +170 -0
- package/dist/evidence.js.map +1 -0
- package/dist/execution-policy.d.ts +23 -0
- package/dist/execution-policy.d.ts.map +1 -0
- package/dist/execution-policy.js +28 -0
- package/dist/execution-policy.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/route-assurance.d.ts +19 -0
- package/dist/internal/route-assurance.d.ts.map +1 -1
- package/dist/internal/route-assurance.js +38 -5
- package/dist/internal/route-assurance.js.map +1 -1
- package/dist/internal/route-execution.d.ts +18 -0
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js +67 -1
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/manifest.d.ts +5 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +22 -4
- package/dist/manifest.js.map +1 -1
- package/dist/reflection.d.ts +5 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +13 -4
- package/dist/reflection.js.map +1 -1
- package/dist/replay.d.ts +26 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +63 -0
- package/dist/replay.js.map +1 -0
- package/dist/schema/standard.d.ts +2 -0
- package/dist/schema/standard.d.ts.map +1 -1
- package/dist/schema/standard.js +13 -0
- package/dist/schema/standard.js.map +1 -1
- package/dist/server/body.d.ts +49 -6
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +226 -8
- package/dist/server/body.js.map +1 -1
- package/dist/server/context.d.ts +20 -3
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract.d.ts +15 -3
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +22 -0
- package/dist/server/contract.js.map +1 -1
- package/dist/server/cookies.d.ts +26 -5
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +76 -9
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +11 -0
- package/dist/server/edge.js.map +1 -1
- package/dist/server/header-case.d.ts +9 -0
- package/dist/server/header-case.d.ts.map +1 -0
- package/dist/server/header-case.js +54 -0
- package/dist/server/header-case.js.map +1 -0
- package/dist/server/headers.d.ts +8 -0
- package/dist/server/headers.d.ts.map +1 -0
- package/dist/server/headers.js +13 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/node-outcome-hook.d.ts.map +1 -1
- package/dist/server/node-outcome-hook.js +7 -11
- package/dist/server/node-outcome-hook.js.map +1 -1
- package/dist/server/node-outcome.d.ts +8 -1
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +10 -3
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/plugin.d.ts +66 -7
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/plugin.js +14 -10
- package/dist/server/plugin.js.map +1 -1
- package/dist/server/proto-guard.d.ts +30 -0
- package/dist/server/proto-guard.d.ts.map +1 -0
- package/dist/server/proto-guard.js +116 -0
- package/dist/server/proto-guard.js.map +1 -0
- package/dist/server/registry.d.ts +6 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +24 -6
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +157 -30
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +2 -2
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +34 -8
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +1 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +45 -13
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-hooks.d.ts +10 -0
- package/dist/server/response-hooks.d.ts.map +1 -0
- package/dist/server/response-hooks.js +52 -0
- package/dist/server/response-hooks.js.map +1 -0
- package/dist/server/runtime-core.d.ts +12 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +8 -0
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +60 -6
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +80 -23
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +429 -213
- package/dist/server/server.js.map +1 -1
- package/dist/server/static-headers.d.ts +14 -3
- package/dist/server/static-headers.d.ts.map +1 -1
- package/dist/server/static-headers.js +28 -6
- package/dist/server/static-headers.js.map +1 -1
- package/dist/server/transport-lane.d.ts +8 -0
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +12 -8
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/websocket.d.ts +3 -0
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +14 -0
- package/dist/server/websocket.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/tool-catalog.d.ts +21 -0
- package/dist/tool-catalog.d.ts.map +1 -0
- package/dist/tool-catalog.js +16 -0
- package/dist/tool-catalog.js.map +1 -0
- package/dist/tool-contract.d.ts +238 -0
- package/dist/tool-contract.d.ts.map +1 -0
- package/dist/tool-contract.js +569 -0
- package/dist/tool-contract.js.map +1 -0
- package/package.json +26 -1
- package/src/assurance.ts +236 -6
- package/src/capabilities.ts +14 -1
- package/src/diff.ts +5 -2
- package/src/errors.ts +1 -0
- package/src/evidence.ts +319 -0
- package/src/execution-policy.ts +57 -0
- package/src/index.ts +1 -1
- package/src/internal/route-assurance.ts +67 -4
- package/src/internal/route-execution.ts +276 -1
- package/src/manifest.ts +27 -5
- package/src/reflection.ts +24 -4
- package/src/replay.ts +96 -0
- package/src/schema/standard.ts +16 -0
- package/src/server/body.ts +275 -7
- package/src/server/context.ts +23 -3
- package/src/server/contract.ts +43 -3
- package/src/server/cookies.ts +80 -10
- package/src/server/edge.ts +10 -0
- package/src/server/header-case.ts +54 -0
- package/src/server/headers.ts +11 -0
- package/src/server/node-outcome-hook.ts +6 -11
- package/src/server/node-outcome.ts +10 -2
- package/src/server/plugin.ts +87 -8
- package/src/server/proto-guard.ts +118 -0
- package/src/server/registry.ts +6 -2
- package/src/server/request-context.ts +242 -26
- package/src/server/respond.ts +35 -4
- package/src/server/response-contract-lane.ts +6 -17
- package/src/server/response-hooks.ts +51 -0
- package/src/server/runtime-core.ts +14 -0
- package/src/server/server-types.ts +66 -6
- package/src/server/server.ts +672 -287
- package/src/server/static-headers.ts +27 -3
- package/src/server/transport-lane.ts +19 -8
- package/src/server/websocket.ts +18 -2
- package/src/server.ts +7 -0
- package/src/tool-catalog.ts +40 -0
- 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
|
-
|
|
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) => [
|
|
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) => [
|
|
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) => [
|
|
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 {
|
|
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
|
-
.
|
|
198
|
-
|
|
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
|
+
}
|
package/src/schema/standard.ts
CHANGED
|
@@ -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> {
|