@nifrajs/cli 5.0.0 → 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 (202) hide show
  1. package/README.md +7 -2
  2. package/dist/assure-hydration.d.ts.map +1 -1
  3. package/dist/assure-hydration.js +4 -2
  4. package/dist/assure-hydration.js.map +1 -1
  5. package/dist/assure.d.ts +12 -2
  6. package/dist/assure.d.ts.map +1 -1
  7. package/dist/assure.js +59 -8
  8. package/dist/assure.js.map +1 -1
  9. package/dist/capabilities-tool.d.ts.map +1 -1
  10. package/dist/capabilities-tool.js +24 -7
  11. package/dist/capabilities-tool.js.map +1 -1
  12. package/dist/check-diagnostics.d.ts +2 -0
  13. package/dist/check-diagnostics.d.ts.map +1 -1
  14. package/dist/check-diagnostics.js.map +1 -1
  15. package/dist/check-scan.d.ts +8 -2
  16. package/dist/check-scan.d.ts.map +1 -1
  17. package/dist/check-scan.js +43 -10
  18. package/dist/check-scan.js.map +1 -1
  19. package/dist/check.d.ts.map +1 -1
  20. package/dist/check.js +39 -33
  21. package/dist/check.js.map +1 -1
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.d.ts.map +1 -1
  24. package/dist/cli.js +106 -48
  25. package/dist/cli.js.map +1 -1
  26. package/dist/client-module.d.ts +5 -3
  27. package/dist/client-module.d.ts.map +1 -1
  28. package/dist/client-module.js +34 -10
  29. package/dist/client-module.js.map +1 -1
  30. package/dist/command-catalog.d.ts +9 -1
  31. package/dist/command-catalog.d.ts.map +1 -1
  32. package/dist/command-catalog.js +67 -22
  33. package/dist/command-catalog.js.map +1 -1
  34. package/dist/dev-feed-tool.d.ts +18 -1
  35. package/dist/dev-feed-tool.d.ts.map +1 -1
  36. package/dist/dev-feed-tool.js +93 -0
  37. package/dist/dev-feed-tool.js.map +1 -1
  38. package/dist/fix-recipes.d.ts +2 -0
  39. package/dist/fix-recipes.d.ts.map +1 -1
  40. package/dist/fix-recipes.js +93 -12
  41. package/dist/fix-recipes.js.map +1 -1
  42. package/dist/init-agents.d.ts +16 -0
  43. package/dist/init-agents.d.ts.map +1 -1
  44. package/dist/init-agents.js +3 -2
  45. package/dist/init-agents.js.map +1 -1
  46. package/dist/internal/config-paths.d.ts +16 -0
  47. package/dist/internal/config-paths.d.ts.map +1 -0
  48. package/dist/internal/config-paths.js +124 -0
  49. package/dist/internal/config-paths.js.map +1 -0
  50. package/dist/internal/installed-package.d.ts +4 -0
  51. package/dist/internal/installed-package.d.ts.map +1 -0
  52. package/dist/internal/installed-package.js +17 -0
  53. package/dist/internal/installed-package.js.map +1 -0
  54. package/dist/internal/project-specifiers.d.ts +14 -1
  55. package/dist/internal/project-specifiers.d.ts.map +1 -1
  56. package/dist/internal/project-specifiers.js +54 -15
  57. package/dist/internal/project-specifiers.js.map +1 -1
  58. package/dist/internal/source-facts.d.ts +6 -1
  59. package/dist/internal/source-facts.d.ts.map +1 -1
  60. package/dist/internal/source-facts.js +11 -5
  61. package/dist/internal/source-facts.js.map +1 -1
  62. package/dist/internal/typescript-import.d.ts.map +1 -1
  63. package/dist/internal/typescript-import.js +5 -3
  64. package/dist/internal/typescript-import.js.map +1 -1
  65. package/dist/introspect.d.ts +9 -0
  66. package/dist/introspect.d.ts.map +1 -1
  67. package/dist/introspect.js +32 -1
  68. package/dist/introspect.js.map +1 -1
  69. package/dist/levels-tool.d.ts.map +1 -1
  70. package/dist/levels-tool.js +5 -8
  71. package/dist/levels-tool.js.map +1 -1
  72. package/dist/load.d.ts +2 -2
  73. package/dist/load.d.ts.map +1 -1
  74. package/dist/load.js +23 -3
  75. package/dist/load.js.map +1 -1
  76. package/dist/mcp-context.d.ts.map +1 -1
  77. package/dist/mcp-context.js +6 -21
  78. package/dist/mcp-context.js.map +1 -1
  79. package/dist/mcp-delegate.d.ts +2 -2
  80. package/dist/mcp-delegate.d.ts.map +1 -1
  81. package/dist/mcp-delegate.js +19 -7
  82. package/dist/mcp-delegate.js.map +1 -1
  83. package/dist/mcp-exec.d.ts +1 -0
  84. package/dist/mcp-exec.d.ts.map +1 -1
  85. package/dist/mcp-exec.js +8 -3
  86. package/dist/mcp-exec.js.map +1 -1
  87. package/dist/mcp-http.js +1 -1
  88. package/dist/mcp-io.d.ts +13 -0
  89. package/dist/mcp-io.d.ts.map +1 -1
  90. package/dist/mcp-io.js +23 -0
  91. package/dist/mcp-io.js.map +1 -1
  92. package/dist/mcp-path.d.ts +2 -1
  93. package/dist/mcp-path.d.ts.map +1 -1
  94. package/dist/mcp-path.js +9 -24
  95. package/dist/mcp-path.js.map +1 -1
  96. package/dist/mcp.d.ts.map +1 -1
  97. package/dist/mcp.js +40 -17
  98. package/dist/mcp.js.map +1 -1
  99. package/dist/migrate-layout.d.ts +40 -1
  100. package/dist/migrate-layout.d.ts.map +1 -1
  101. package/dist/migrate-layout.js +1316 -228
  102. package/dist/migrate-layout.js.map +1 -1
  103. package/dist/output-schemas.d.ts +34 -0
  104. package/dist/output-schemas.d.ts.map +1 -1
  105. package/dist/output-schemas.js +132 -21
  106. package/dist/output-schemas.js.map +1 -1
  107. package/dist/project-path.d.ts +6 -0
  108. package/dist/project-path.d.ts.map +1 -1
  109. package/dist/project-path.js +48 -14
  110. package/dist/project-path.js.map +1 -1
  111. package/dist/release-verification.d.ts +5 -0
  112. package/dist/release-verification.d.ts.map +1 -1
  113. package/dist/release-verification.js +14 -0
  114. package/dist/release-verification.js.map +1 -1
  115. package/dist/review.d.ts.map +1 -1
  116. package/dist/review.js +5 -0
  117. package/dist/review.js.map +1 -1
  118. package/dist/rules/codes.d.ts +3 -1
  119. package/dist/rules/codes.d.ts.map +1 -1
  120. package/dist/rules/codes.js +3 -1
  121. package/dist/rules/codes.js.map +1 -1
  122. package/dist/rules/data-guard.d.ts +6 -0
  123. package/dist/rules/data-guard.d.ts.map +1 -1
  124. package/dist/rules/data-guard.js +37 -0
  125. package/dist/rules/data-guard.js.map +1 -1
  126. package/dist/rules/legacy.d.ts.map +1 -1
  127. package/dist/rules/legacy.js +20 -1
  128. package/dist/rules/legacy.js.map +1 -1
  129. package/dist/rules/pages.d.ts +29 -3
  130. package/dist/rules/pages.d.ts.map +1 -1
  131. package/dist/rules/pages.js +236 -22
  132. package/dist/rules/pages.js.map +1 -1
  133. package/dist/rules/security.d.ts +3 -2
  134. package/dist/rules/security.d.ts.map +1 -1
  135. package/dist/rules/security.js +42 -11
  136. package/dist/rules/security.js.map +1 -1
  137. package/dist/rules/zones.d.ts.map +1 -1
  138. package/dist/rules/zones.js +31 -9
  139. package/dist/rules/zones.js.map +1 -1
  140. package/dist/stylex-migrate.d.ts.map +1 -1
  141. package/dist/stylex-migrate.js +127 -17
  142. package/dist/stylex-migrate.js.map +1 -1
  143. package/dist/test-tool.d.ts.map +1 -1
  144. package/dist/test-tool.js +3 -9
  145. package/dist/test-tool.js.map +1 -1
  146. package/dist/upgrade.d.ts +20 -5
  147. package/dist/upgrade.d.ts.map +1 -1
  148. package/dist/upgrade.js +86 -9
  149. package/dist/upgrade.js.map +1 -1
  150. package/dist/verification-plan.d.ts.map +1 -1
  151. package/dist/verification-plan.js +5 -2
  152. package/dist/verification-plan.js.map +1 -1
  153. package/dist/version-drift.d.ts.map +1 -1
  154. package/dist/version-drift.js +55 -3
  155. package/dist/version-drift.js.map +1 -1
  156. package/docs/examples.json +2 -2
  157. package/docs/llms-full.txt +481 -61
  158. package/docs/types.json +147 -70
  159. package/package.json +11 -11
  160. package/src/assure-hydration.ts +4 -1
  161. package/src/assure.ts +88 -10
  162. package/src/capabilities-tool.ts +33 -7
  163. package/src/check-diagnostics.ts +2 -0
  164. package/src/check-scan.ts +56 -9
  165. package/src/check.ts +55 -39
  166. package/src/cli.ts +104 -35
  167. package/src/client-module.ts +45 -9
  168. package/src/command-catalog.ts +77 -21
  169. package/src/dev-feed-tool.ts +112 -1
  170. package/src/fix-recipes.ts +101 -10
  171. package/src/init-agents.ts +4 -3
  172. package/src/internal/config-paths.ts +154 -0
  173. package/src/internal/installed-package.ts +15 -0
  174. package/src/internal/project-specifiers.ts +69 -16
  175. package/src/internal/source-facts.ts +23 -5
  176. package/src/internal/typescript-import.ts +5 -3
  177. package/src/introspect.ts +39 -1
  178. package/src/levels-tool.ts +7 -9
  179. package/src/load.ts +29 -3
  180. package/src/mcp-context.ts +5 -18
  181. package/src/mcp-delegate.ts +22 -10
  182. package/src/mcp-exec.ts +9 -2
  183. package/src/mcp-http.ts +1 -1
  184. package/src/mcp-io.ts +33 -0
  185. package/src/mcp-path.ts +8 -22
  186. package/src/mcp.ts +48 -19
  187. package/src/migrate-layout.ts +1496 -237
  188. package/src/output-schemas.ts +179 -23
  189. package/src/project-path.ts +44 -13
  190. package/src/release-verification.ts +20 -0
  191. package/src/review.ts +5 -0
  192. package/src/rules/codes.ts +3 -1
  193. package/src/rules/data-guard.ts +40 -0
  194. package/src/rules/legacy.ts +22 -1
  195. package/src/rules/pages.ts +266 -24
  196. package/src/rules/security.ts +49 -11
  197. package/src/rules/zones.ts +30 -7
  198. package/src/stylex-migrate.ts +117 -17
  199. package/src/test-tool.ts +3 -9
  200. package/src/upgrade.ts +96 -8
  201. package/src/verification-plan.ts +9 -2
  202. package/src/version-drift.ts +53 -2
@@ -1404,7 +1404,9 @@ that is not JSON, says so in a comment, and marks what it wrote with a
1404
1404
  under the line was edited (reformatting it is not an edit); delete the line to own the
1405
1405
  schema by hand. A hand-written schema is never rewritten: the report lists the
1406
1406
  returned fields it drops, by path. Nothing is derived at request time - the schema in the
1407
- file is the one the guard enforces.
1407
+ file is the one the guard enforces. `nifra fix --code NF-C030` writes the same
1408
+ draft for one flagged route, and `nifra migrate layout` drafts one for each
1409
+ route it splits whose loader or action returns data without a schema.
1408
1410
 
1409
1411
  A field named like a credential (`password`, `token`,
1410
1412
  `apiKey`) fails the route when it loads unless its schema is wrapped in
@@ -1448,6 +1450,18 @@ export default function NewUser() {
1448
1450
  }
1449
1451
  ```
1450
1452
 
1453
+ A form can post to another site and the browser sends that site's cookies with it, so an action
1454
+ refuses a POST whose `Origin` is another site's with 403, before middleware, gates or
1455
+ the action run. A request without an `Origin`, such as one from a server, passes, and
1456
+ so does a form on the app's own page sent under `Referrer-Policy: no-referrer`, which
1457
+ arrives as `Origin: null` with `Sec-Fetch-Site: same-origin`.
1458
+ Same-origin is judged from the request URL's host, so a proxy in front must forward the
1459
+ `Host` header. A route that takes posts from elsewhere, such as an identity
1460
+ provider's `form_post` callback, exports `allowedOrigins` from its backend
1461
+ file: an array of exact origins, or a function that receives the origin (`null` when
1462
+ absent). It checks where the browser says the request comes from, not who sent it, so keep the
1463
+ action's own authentication.
1464
+
1451
1465
  ### Layout loaders on navigation
1452
1466
 
1453
1467
  A `_layout.backend.ts` can have a `loader` too; its output reaches the
@@ -1455,7 +1469,11 @@ layout component as `data`. On a client navigation that keeps the layout on scre
1455
1469
  browser already holds that data, so the loader runs again only when a param the layout owns
1456
1470
  changes (`org` for `orgs/[org]/_layout.tsx`) or the query changes. After
1457
1471
  an action, every loader runs. A layout that exports `gate = true` runs on every
1458
- request; a layout loader without it is not an authorization boundary. A
1472
+ request; a layout loader without it is not an authorization boundary: a client navigation can
1473
+ skip it, and an action below it runs without it. Put a check that refuses access in a gate or
1474
+ in middleware. In development, nifra warns the first time a layout loader without
1475
+ `gate` refuses a request with a redirect, a `Response`, or a 401 or 403,
1476
+ and `nifra check` reports one whose loader refuses in its own body (NF-C042). A
1459
1477
  `_layout.backend.ts` that exports `middleware` guards a directory, with
1460
1478
  or without a layout component - see [Route middleware](/docs/routing#middleware).
1461
1479
 
@@ -1561,8 +1579,9 @@ host - defence in depth behind the JSON requirement, at the cost of one comparis
1561
1579
  Serialising closed-over variables to the browser and back is a class of problem worth not
1562
1580
  having rather than encrypting.
1563
1581
 
1564
- Both content-type rules were measured rather than assumed. A body schema alone still accepts a
1565
- cross-origin urlencoded form - 200, attacker-controlled fields - and a bounded JSON read alone
1582
+ Both content-type rules were measured rather than assumed. A body schema alone still reads a
1583
+ urlencoded form that carries no `Origin` or one from a site in
1584
+ `formOrigins` - 200, attacker-controlled fields - and a bounded JSON read alone
1566
1585
  accepts the `text/plain` trick where a form's `name=value` is crafted to
1567
1586
  parse as valid JSON. Neither is sufficient by itself.
1568
1587
 
@@ -2952,6 +2971,20 @@ Svelte resolve deferred data on the **client**, so JS-off users see the fallback
2952
2971
  deferred boundary. Put critical page data in the route `loader`; use
2953
2972
  `defer()` for slower, non-critical data.
2954
2973
 
2974
+ ### Markup outside the page
2975
+
2976
+ Svelte&apos;s `<svelte:head>` renders into the document&apos;s
2977
+ `<head>`, after the tags from the route&apos;s `meta`. The document
2978
+ always has a `<title>` of its own and a browser uses the first one, so set the
2979
+ title in `meta`.
2980
+
2981
+ A Vue `<Teleport>` is server-rendered where Vue hydrates it from, the first
2982
+ children of its target. `to="body"` content comes first in
2983
+ `<body>`. An `#id` target the app renders empty receives it, and one
2984
+ the app does not render gets a container before the app root. Any other selector, a target that
2985
+ has children of its own, and every teleport on a page that uses `defer()` render on
2986
+ the client, with a warning in development.
2987
+
2955
2988
  ### Dev HMR support
2956
2989
 
2957
2990
  Dev HMR works for all five adapters through `createViteDevServer` and the
@@ -2997,6 +3030,47 @@ app
2997
3030
  app.use((a) => a.decorate("db", db).derive((c) => ({ now: Date.now() })))
2998
3031
  ```
2999
3032
 
3033
+ ### What a parent&rsquo;s use() covers
3034
+
3035
+ A `group()` scope inherits the parent&rsquo;s chain as it stands at the call. A
3036
+ `merge()`d server was compiled on its own, so its routes keep _their_ chain. Only
3037
+ what the parent installed **before** the `group()` or `merge()` counts.
3038
+
3039
+ Installed on the parent
3040
+
3041
+ `group()` route
3042
+
3043
+ `merge()`d route
3044
+
3045
+ `derive`, `decorate`, `authenticate`,
3046
+ `beforeHandle`, `afterHandle`, `onError`,
3047
+ `around`, route-scoped assurance
3048
+
3049
+ yes
3050
+ no - wire it on the merged server
3051
+
3052
+ `onRequest`, `onResponse`, `responseHeaders`
3053
+
3054
+ yes
3055
+ yes (app-wide)
3056
+
3057
+ `responseContract()`
3058
+
3059
+ yes
3060
+ yes, when the route declares a response and its own server installed no contract
3061
+
3062
+ `effectLedger()`
3063
+
3064
+ yes
3065
+ yes, when the route declares capabilities and has no ledger of its own
3066
+
3067
+ `idempotency()`
3068
+
3069
+ yes
3070
+
3071
+ the merged server&rsquo;s own - a route declaring `idempotency` without one is
3072
+ refused at registration
3073
+
3000
3074
  ### Route/hook plugins: keep types with defineRouterPlugin
3001
3075
 
3002
3076
  A plugin that registers routes or hooks but adds **no context type** - e.g. mounting an auth
@@ -3285,7 +3359,10 @@ const app = server()
3285
3359
  `healthcheck()` adds liveness (`/health`) and readiness (`/ready`)
3286
3360
  endpoints - apply it **before** any auth guard so they stay public. `openapi()`
3287
3361
  serves an OpenAPI 3.1 document at `/openapi.json`, generated from your routes - add
3288
- `ui: true` for a Scalar API-reference page at `/reference`. Paths,
3362
+ `ui: true` for a Scalar API-reference page at `/reference`. The page
3363
+ loads an exact Scalar version from jsDelivr under a Subresource Integrity hash; serve your own
3364
+ copy with `ui: { cdn, integrity }` to keep a third-party origin out of your
3365
+ Content-Security-Policy. Paths,
3289
3366
  methods, and params are introspected; `servers`, `tags`,
3290
3367
  `security`, and `securitySchemes` are document options, and
3291
3368
  `operations` (keyed by `"GET /users/:id"`) supplies per-route bodies/security
@@ -3330,7 +3407,10 @@ app.use(idempotency({ store: new MemoryIdempotencyStore() }))
3330
3407
  enforced by default, and claims (`iss`/`aud`/`nbf`) are checked.
3331
3408
  Read the typed claims off the returned plugin - `auth.requireClaims(c.req)` (throws
3332
3409
  `401`) or `auth.claims(c.req)` (nullable). For rotating keys, pass
3333
- `key: jwks({ url })` (HTTPS-only, cached, size/time-bounded). `basicAuth`
3410
+ `key: jwks({ url })` (HTTPS-only, cached, size/time-bounded). A fetch that fails
3411
+ or returns no keys is remembered for `failureCooldownMs` (default 5 s), so while the
3412
+ identity provider is down a request fails at once instead of waiting on a fetch of its own.
3413
+ `basicAuth`
3334
3414
  compares static credentials in **constant time** (or takes a `verify` callback).
3335
3415
 
3336
3416
  ```ts
@@ -3473,7 +3553,7 @@ const PublicUser = t.object({ id: t.string(), name: t.string() })
3473
3553
 
3474
3554
  // "warn" logs and changes nothing; "enforce" makes the contract the upper bound too.
3475
3555
  // Not installing the plugin is "off" - and keeps the lane out of your bundle entirely.
3476
- export const app = server().use(responseContract("enforce")).get(
3556
+ export const app = server().use(responseContract("enforce", { extra: "strip" })).get(
3477
3557
  "/me",
3478
3558
  { response: PublicUser },
3479
3559
  async () => {
@@ -3483,8 +3563,10 @@ export const app = server().use(responseContract("enforce")).get(
3483
3563
  },
3484
3564
  )
3485
3565
 
3486
- // off -> {"id":"u1","name":"Ada","email":"a@b.c","passwordHash":"..."}
3487
- // enforce -> {"id":"u1","name":"Ada"}
3566
+ // off -> 200 {"id":"u1","name":"Ada","email":"a@b.c","passwordHash":"..."}
3567
+ // enforce -> 500 {"ok":false,"error":"internal_error"}
3568
+ // (t.object is closed; the log names email and passwordHash)
3569
+ // enforce, { extra: "strip" } -> 200 {"id":"u1","name":"Ada"}
3488
3570
  ```
3489
3571
 
3490
3572
  Not installing the plugin is &ldquo;off&rdquo;, which is exactly today&rsquo;s behaviour and
@@ -3499,12 +3581,19 @@ returns it early, or `onError` answers with it; only a hand-built
3499
3581
  should cover: like
3500
3582
  `idempotency()`, the decision is made per route at registration.
3501
3583
 
3502
- Enforcement follows your schema&rsquo;s own semantics, because Standard Schema exposes
3503
- `validate` and no way to enumerate declared keys. A _stripping_ schema (Zod,
3504
- Valibot) yields a cleaned value, so the extra fields are dropped. A _strict_ one
3584
+ By default, enforcement follows your schema&rsquo;s own semantics, because Standard Schema
3585
+ exposes `validate` and no way to enumerate declared keys. A _stripping_ schema
3586
+ (Zod, Valibot) yields a cleaned value, so the extra fields are dropped. A _strict_ one
3505
3587
  (`@nifrajs/schema`&rsquo;s `t.object`) reports them as issues, so the
3506
- response becomes a 500 and the detail goes to the logger, never to the caller. Both are what you
3507
- already declared about extra fields.
3588
+ response becomes a 500 and the detail goes to the logger, never to the caller.
3589
+ `{ extra: "strip" }` changes that for `t` schemas: they carry their
3590
+ JSON Schema, which does list the declared keys, so the payload is projected onto them - nested
3591
+ objects, arrays and records included - before it is validated. In `"warn"`
3592
+ mode the same option makes the log name the fields `"enforce"` would drop.
3593
+
3594
+ A `merge()`d server&rsquo;s routes are covered by a contract the parent installed
3595
+ before the merge, as long as the route declares a response and its own server installed none.
3596
+ See [what a parent&rsquo;s use() covers](/docs/plugins#parent-use).
3508
3597
 
3509
3598
  The check itself is essentially free: with a compiled validator it measures in the
3510
3599
  ~100ns-per-response range, and a realistic middleware-carrying route benchmarks within noise
@@ -3727,8 +3816,12 @@ one the bytes proved.
3727
3816
 
3728
3817
  -
3729
3818
  **A form post is a simple cross-origin request.** A browser sends
3730
- `multipart/form-data` with cookies and no preflight, so a cookie-authenticated upload
3731
- route needs `csrf()` like any other state change.
3819
+ `multipart/form-data`, urlencoded and `text/plain` with cookies and no
3820
+ preflight. A body-schema route refuses one whose `Origin` is another site&rsquo;s
3821
+ with `403` before reading it; list a site that must post forms to the app in the
3822
+ server&rsquo;s `formOrigins`. A request without an `Origin` passes, so
3823
+ a cookie-authenticated upload route still needs `csrf()` like any other state
3824
+ change.
3732
3825
 
3733
3826
  -
3734
3827
  **The form is held in memory.** Budget roughly twice `bodyLimit` per request in
@@ -3909,7 +4002,12 @@ accept either during a key rotation.
3909
4002
 
3910
4003
  A dropped connection or an impatient double-tap shouldn't double-charge a card. With an
3911
4004
  `Idempotency-Key` header, a retried unsafe request replays the first response instead of
3912
- re-running the side effect. It short-circuits in `onRequest`, _before_ the handler.
4005
+ re-running the side effect. The key is claimed in `onRequest`, so a concurrent retry gets
4006
+ a `409`; the stored response is replayed from `beforeHandle`, _after_ the
4007
+ route's authentication accepts the retry, so a credential revoked since the first request gets the
4008
+ route's current answer. Declare routes after `.use(idempotency())`:
4009
+ `beforeHandle` is order-scoped, and a route declared earlier replays from
4010
+ `onRequest`.
3913
4011
 
3914
4012
  ```ts
3915
4013
  // doc-check: skip - fragment: `app`, `chargeCard`, and `id` are your application's.
@@ -4659,6 +4757,15 @@ a `SessionStore`; in **cookie mode** (no store) the data is signed into the cook
4659
4757
  Stores mirror the ISR discipline: `MemorySessionStore` is prod-guarded;
4660
4758
  `KVSessionStore` is the durable, shared production store.
4661
4759
 
4760
+ The signature covers the cookie name, so two managers that share a secret never read each
4761
+ other's cookies; still, give sessions a secret no other signed value uses. A cookie-mode
4762
+ session lives at most `maxAge`, whatever expiry its payload names. Upgrading from
4763
+ 4.x, whose cookies were signed without the name, signs everyone out unless you pass
4764
+ `acceptLegacyCookies: true`: keep it for one `maxAge` after the upgrade
4765
+ (each such session is re-signed on its next commit), then remove it, since while it is on any
4766
+ session-shaped value the app signs with the same secret through `signValue` reads
4767
+ as a session.
4768
+
4662
4769
  ```ts
4663
4770
  // backend/auth.ts - one session manager, created on first use so the secret is read at request time.
4664
4771
  // Store mode keeps data server-side; cookie mode (no store) is stateless.
@@ -5770,7 +5877,9 @@ bridge an external fan-out (Redis pub/sub, NATS, a queue) to broadcast across al
5770
5877
  **Cloudflare Workers**, a stateless isolate can’t broadcast across connections,
5771
5878
  so Nifra ships a Durable Object hub: `createWebSocketHub(app)` holds the
5772
5879
  connections, and `toFetchHandler(app, { webSocketHub })` routes upgrades to it
5773
- - then `ws.subscribe` / `app.publish` behave exactly as on Bun.
5880
+ - then `ws.subscribe` / `app.publish` behave exactly as on Bun. An
5881
+ upgrade request for a path with no WebSocket route still gets that path's HTTP answer, as on
5882
+ every other runtime.
5774
5883
 
5775
5884
  ```ts
5776
5885
  // doc-check: skip - Workers entry: a Durable Object hub holds the connections.
@@ -5823,6 +5932,14 @@ it). Install it when you use `app.ws()`; without it a WS upgrade gets a clean
5823
5932
  npm i ws
5824
5933
  ```
5825
5934
 
5935
+ `server({ wsMaxPayloadBytes })` caps one inbound message (default
5936
+ `maxBodyBytes`, 1 MB): a socket that sends more is closed with `1009`
5937
+ and the message never reaches your handler. Bun and Node refuse it while it arrives. Deno's
5938
+ `Deno.upgradeWebSocket` takes no size limit, so Deno receives each message whole
5939
+ (up to its own limit, 64 MiB on Deno 2.9) before nifra can check it: the cap still keeps the
5940
+ message from your handler, but it does not bound the memory Deno spent receiving it. Where that
5941
+ matters, cap message size in a proxy in front of a Deno app.
5942
+
5826
5943
  ### In a composed app
5827
5944
 
5828
5945
  `mount({ path, app })` carries WebSocket upgrades as well as HTTP: when the
@@ -5834,6 +5951,11 @@ implement it too, building the outcome from `@nifrajs/core/ws`:
5834
5951
  owns its pub/sub. On Bun, install `websocket()` on the parent or on a mounted nifra
5835
5952
  child, or `listen()` refuses to start with `INVALID_WS_RUNTIME`.
5836
5953
 
5954
+ Each mounted nifra app keeps its own `wsMaxPayloadBytes`: a message over the
5955
+ child's cap closes that socket with `1009`, whether the cap is below or above the
5956
+ parent's. On Bun one listener serves them all, so its frame limit is the largest cap among the
5957
+ parent and the apps mounted on it, and a socket whose app declares less checks its own.
5958
+
5837
5959
  `mountFetch(path, handler)` and a plain fetch handler carry HTTP only. An upgrade
5838
5960
  request under such a path reaches the handler as an ordinary request and never becomes a
5839
5961
  socket. Serve that path's WebSockets from a mounted child, or keep that server beside nifra.
@@ -6281,10 +6403,13 @@ when the rollback is intended); a target newer than the running CLI prints the c
6281
6403
  that release&apos;s CLI. It edits files under the workspace root only: a symlinked package
6282
6404
  directory or `package.json` is never followed, and a group package installed by a
6283
6405
  link from outside the root is listed under "Left alone" for you to upgrade in its
6284
- own repository. So is another version pinned outside `package.json`, with its
6285
- line: an MCP config such as `.mcp.json`, a Dockerfile or compose file, a CI
6286
- workflow, and a lockfile other than the workspace&apos;s own (an image build&apos;s frozen
6287
- lockfile, say), which you regenerate after installing. With `--write` it also
6406
+ own repository. The MCP launch pin in `.mcp.json`, `.cursor/mcp.json`,
6407
+ `CLAUDE.md` and `AGENTS.md` moves to the target, the edit
6408
+ `nifra init-agents --sync-mcp` makes. Another version pinned outside
6409
+ `package.json` is listed with its line: the rest of an agent file, an MCP config
6410
+ elsewhere, a Dockerfile or compose file, a CI workflow, and a lockfile other than the
6411
+ workspace&apos;s own (an image build&apos;s frozen lockfile, say), which you regenerate after
6412
+ installing. With `--write` it also
6288
6413
  prints how hook scope changed across the
6289
6414
  upgrade (`--no-hooks` skips that). `nifra doctor` and
6290
6415
  `nifra check` (NF-D002) point here when a workspace carries more than one version;
@@ -6869,7 +6994,10 @@ emit `approval.required` with an opaque approval id, action, capability, and bou
6869
6994
  coordinates. Resolve it through the host or RPC with `approval.resolve`; the backend
6870
6995
  emits `approval.resolved` and only then executes the tool. Denial, cancellation,
6871
6996
  timeout, and closed sessions all fail closed, and `approvalTimeoutMs` bounds how long
6872
- a pending decision can remain open.
6997
+ a pending decision can remain open: 5 minutes by default, at most 24 hours, or
6998
+ `"never"` to wait until it is answered, cancelled, its turn ends, or the session
6999
+ closes. The host's broker takes the same values as `approvals.timeoutMs` on
7000
+ `CodingAgentHost` and `CodingAgentRpcServer`.
6873
7001
 
6874
7002
  ### Expose the same agent to other clients
6875
7003
 
@@ -6967,12 +7095,13 @@ fail-open OpenTelemetry run and step spans. Providers, credentials, durable stor
6967
7095
  and tenant policy remain application-owned ports.
6968
7096
 
6969
7097
  A step that suspends for approval resumes with the decision the caller sends in
6970
- `resume.approval`. Over A2A, or on an AG-UI or `mountAgent` route that
6971
- anyone can reach, that caller may be the party asking for the tool. Return
7098
+ `resume.approval`. On an AG-UI or `mountAgent` route that anyone can
7099
+ reach, that caller may be the party asking for the tool. Return
6972
7100
  `resumeApproval: "port"` from `ports` to keep the configured approval
6973
7101
  port authoritative: it is asked again on resume, receives the caller's decision as
6974
7102
  `callerDecision`, and its verdict stands. Without a port, such a resume suspends
6975
- again.
7103
+ again. A2A does this by default, since the resuming peer is the agent that asked for the
7104
+ tool; return `resumeApproval: "caller"` there only when the peer is the approver.
6976
7105
 
6977
7106
  ### Where your code and secrets run
6978
7107
 
@@ -7000,9 +7129,11 @@ values (`nifra mcp --env-file .env.secrets`), reach all of them.
7000
7129
  -
7001
7130
  **No `.env` in the server.** When Bun loaded `.env`
7002
7131
  values into the server at startup, the server hands the session to a copy of itself that
7003
- loads none, keeping the values from the environment and `--env-file`. The process
7004
- the client started stays to forward signals and the exit code: it still holds the values
7005
- Bun loaded, but runs no project code and reads no messages.
7132
+ loads none, keeping the values from the environment and `--env-file`. A project
7133
+ that installs another release of `@nifrajs/cli` gets the session handed to that
7134
+ CLI the same way, with no `.env` loaded. The process the client started stays to
7135
+ forward signals and the exit code: it still holds the values Bun loaded, but runs no project
7136
+ code and reads no messages.
7006
7137
 
7007
7138
  -
7008
7139
  **A process per call has a cost.** A call that loads the app pays one process
@@ -8091,6 +8222,13 @@ written down: `nifra fix --code NF-C035` adds
8091
8222
  `export const apiPrefix = "/api"`, which is what 4.x mounted by default, and
8092
8223
  `""` keeps the backend loader-only.
8093
8224
 
8225
+ With both files, `nifra dev` and `nifra build` take
8226
+ `apiPrefix`, `apiStrip`, `mounts`, `csp`,
8227
+ `nonce`, `serverComponents` and `use` from
8228
+ `backend/framework.ts` when `nifra.config.ts` does not set them, so the
8229
+ server entry and the dev server read one value without a re-export. A hand-written
8230
+ `createWebApp` call passes `apiPrefix` itself.
8231
+
8094
8232
  Because the backend answers first and its 404 is final, a page file under the prefix (
8095
8233
  `routes/api/report.tsx`) could never render. Nifra refuses it instead of serving a
8096
8234
  silent 404: `createWebApp` throws at startup naming the file, `nifra build`
@@ -8807,7 +8945,9 @@ pass through, and every `<link>` attribute with a normal name -
8807
8945
 
8808
8946
  A route's `meta` emits the full social / structured-data head with three public
8809
8947
  helpers - `canonical()`, `openGraph()`, and `jsonLd()` - and
8810
- builds **absolute** URLs from the `origin` argument. Because
8948
+ builds **absolute** URLs from the `origin` argument. Import them from
8949
+ `@nifrajs/web/seo`: the entry imports nothing at runtime, so a page's browser bundle
8950
+ gets the three helpers and none of the server modules the package root also exports. Because
8811
8951
  `meta()` runs in **both** SSR and client navigation it has
8812
8952
  **no server env**: never read `process.env` or the request inside it.
8813
8953
  The framework injects `origin` (the site's scheme + host, e.g.
@@ -8819,9 +8959,11 @@ base, a CDN host) belongs in the `loader`, not `meta()`.
8819
8959
 
8820
8960
  ```ts
8821
8961
  // routes/articles/[slug].tsx - a complete SEO head: canonical + Open Graph + Twitter + JSON-LD,
8822
- // with ABSOLUTE URLs built from `origin`. The three helpers (canonical/openGraph/jsonLd) are public
8823
- // exports of @nifrajs/web; `origin` is a MetaArgs field beside data, pageData and params.
8824
- import { canonical, jsonLd, openGraph, type MetaArgs } from "@nifrajs/web"
8962
+ // with ABSOLUTE URLs built from `origin`. The three helpers (canonical/openGraph/jsonLd) come from
8963
+ // @nifrajs/web/seo, which imports nothing at runtime, so the page's browser bundle takes only them;
8964
+ // `origin` is a MetaArgs field beside data, pageData and params.
8965
+ import type { MetaArgs } from "@nifrajs/web"
8966
+ import { canonical, jsonLd, openGraph } from "@nifrajs/web/seo"
8825
8967
 
8826
8968
  // CAVEAT: meta() runs in BOTH SSR and client navigation, so it has NO server env - never read
8827
8969
  // `process.env` or the request here. The framework injects `origin` (the site's scheme+host, e.g.
@@ -9916,7 +10058,8 @@ Schema that describes exactly the fields the page needs. `nifra check` flags rou
9916
10058
  without one (NF-C030): an error when the loader's own body returns a literal (an object,
9917
10059
  array, string, number or boolean), since every request that reaches that return fails, and a
9918
10060
  warning when only running it shows what it returns. `nifra schemas --write` drafts
9919
- a schema from the loader's return type for review.
10061
+ a schema from the loader's return type for review, and
10062
+ `nifra fix --code NF-C030` writes that draft for each flagged route.
9920
10063
 
9921
10064
  ### Rendering and data
9922
10065
 
@@ -9995,6 +10138,10 @@ as information, with the comment and its reason as evidence. Some codes also ans
9995
10138
  `@nifra-gate-reviewed` silences nothing; a finding under it names the comment to
9996
10139
  write instead.
9997
10140
 
10141
+ A fail-open gate (NF-S001) or a non-constant-time comparison (NF-S002) in code that only
10142
+ ever runs in a browser is a warning rather than an error: the server's own gate decides
10143
+ there.
10144
+
9998
10145
  ## Nifra - Failure laboratory
9999
10146
 
10000
10147
  > Replayable durable-failure schedules on virtual time: crash after commit, duplicate delivery, reordering, budget expiry, and checkpoint contention.
@@ -10514,6 +10661,146 @@ If the project has `nifra.assurance.ts`, run `nifra assure --json`.
10514
10661
  Exercise every redirect path - loader, action, and layout gate - and, if the app proxies,
10515
10662
  each deploy adapter it uses.
10516
10663
 
10664
+ ## Nifra 5.0 migration guide - upgrade from 3.x or 4.x
10665
+
10666
+ > Upgrade a Nifra 3.x or 4.x application to 5.0: the chained upgrade command, the frontend/backend layout move and its follow-ups, drafted output schemas, the backend's HTTP mount, the 5.0 defaults that changed, and the release gates.
10667
+
10668
+ Nifra 4.0 split every app into a frontend and a backend half, and 5.0 enforces the split and
10669
+ changes a handful of defaults. The upgrade command pins the packages and moves imports; the
10670
+ layout migration moves files; this guide covers what each leaves to you and how to tell a
10671
+ finished migration from one that needs a decision.
10672
+
10673
+ ### 1. Run the executable upgrade
10674
+
10675
+ ```ts
10676
+
10677
+ ```
10678
+
10679
+ Run the command from the release you are moving to. It chains every release recipe between
10680
+ the installed version and 5.0, so a 3.x app takes the 4.0 steps (the retired
10681
+ `@nifrajs/web/server-only` import moves to `@nifrajs/web/backend-only`)
10682
+ and the 5.0 ones in one pass, and prints each release's notes. It lists every
10683
+ `@nifra-gate-reviewed` comment, which silences nothing in 5.0: after review, write
10684
+ `// nifra-expect : ` with the code `nifra check`
10685
+ reports there. The upgrade never writes a reason for you.
10686
+
10687
+ ### 2. Move onto the frontend/backend layout (from 3.x)
10688
+
10689
+ ```ts
10690
+
10691
+ ```
10692
+
10693
+ Each route file splits into `x.tsx`, the page, and `x.backend.ts`, which
10694
+ holds `loader`, `action`, `middleware` and the other server
10695
+ exports. The root `backend.ts` and `framework.ts` move to
10696
+ `backend/app.ts` and `backend/framework.ts`, and every other module goes
10697
+ to `frontend/`, `backend/` or `shared/` by who imports it at
10698
+ runtime. A folder whose imported files all land in one zone moves whole, so a component nothing
10699
+ imports yet travels with its folder; a folder split between zones is reported instead.
10700
+
10701
+ ```ts
10702
+
10703
+ ```
10704
+
10705
+ To keep a folder where it is, declare it a zone before migrating. The migration leaves zoned
10706
+ folders in place, and builds, `nifra dev` and `nifra check` enforce them
10707
+ like the default folders.
10708
+
10709
+ ```ts
10710
+
10711
+ ```
10712
+
10713
+ `nifra migrate layout --zones` writes those entries for you: every module the
10714
+ layout would move stays where it is, and `package.json` lists the widest folder
10715
+ whose files all belong to one side, or the file itself where its folder holds both. Route
10716
+ files are still split and the root entries still move. A path nested in another's folder
10717
+ overrides it, so a backend file can sit in a frontend folder without moving.
10718
+
10719
+ The command exits `0` when nothing needs a decision. A step with a known command,
10720
+ such as drafting an output schema for a backend half it did not write, is listed under
10721
+ `next` and in `followUps` with `--json`, and does not fail it.
10722
+ An issue (an import it could not place, a frontend that still uses a moved name at runtime)
10723
+ exits `1`.
10724
+
10725
+ 5.0 refuses a route file that still exports a server name: `nifra build` and the
10726
+ server stop on it, and `nifra check` reports it as NF-C041.
10727
+ `nifra fix --code NF-C041` moves the export and the code only it uses into the
10728
+ route's backend half.
10729
+
10730
+ ### 3. Draft and review output schemas
10731
+
10732
+ A loader or action that returns data needs `loaderOutput` or
10733
+ `actionOutput`; the data is projected through it, so undeclared keys never reach
10734
+ the page. `nifra schemas --write` drafts each missing one from the function's return
10735
+ type, leaves out credential-like fields and marks it `nifra:generated` so later runs
10736
+ keep it current. A value typed `any` or `unknown` drafts as
10737
+ `t.json()`, which admits anything: `nifra schemas` lists those paths and
10738
+ `nifra check` warns on them (NF-C030). Give the value a type and draft again.
10739
+ `nifra schemas --check` fails on a missing, stale or open schema, for CI.
10740
+
10741
+ ### 4. Choose where the backend is served
10742
+
10743
+ 3.x and 4.x served `backend/app.ts` at `/api` by default. In 5.0
10744
+ `createWebApp` serves it over HTTP only at an `apiPrefix` the app sets;
10745
+ without one the backend is loader-only, reached in-process through `ctx.api`, and a
10746
+ browser call to `/api` reaches page routing. `nifra migrate layout` adds
10747
+ `apiPrefix = "/api"` when it moves a root `backend.ts`, and
10748
+ `nifra check` reports a framework config that sets none (NF-C035).
10749
+
10750
+ ```ts
10751
+
10752
+ ```
10753
+
10754
+ With both `nifra.config.ts` and `backend/framework.ts`, every command
10755
+ takes `apiPrefix`, `apiStrip`, `mounts`, `csp`,
10756
+ `nonce`, `serverComponents` and `use` from
10757
+ `backend/framework.ts` when `nifra.config.ts` leaves them unset. The
10758
+ backend sees each request's full path unless `apiStrip` is on, so its routes are
10759
+ registered under the prefix; `nifra routes` marks one outside it as in-process
10760
+ only, and `nifra dev` and `nifra build` warn about it.
10761
+
10762
+ ### 5. Review the 5.0 defaults that changed
10763
+
10764
+ -
10765
+ Root status pages (`_404.tsx`, `_<status>.tsx`) and
10766
+ `_error.tsx` render inside the layouts above them. One that imports the root
10767
+ layout to wrap itself renders it twice (NF-C034); remove the wrap, or export
10768
+ `inLayouts = false`.
10769
+
10770
+ -
10771
+ A layout's `meta` receives its own loader data as `data` and the page's
10772
+ as `pageData` (NF-C040).
10773
+
10774
+ -
10775
+ A WebSocket handshake runs the route chain an HTTP `GET` at the same point runs, so
10776
+ guards added before `app.ws()` apply to it.
10777
+
10778
+ -
10779
+ A `Response` returned to more than one request is no longer shared; build one per
10780
+ request.
10781
+
10782
+ -
10783
+ `nifra check` fails a secret comparison (NF-S002) outside browser code of a web
10784
+ app.
10785
+
10786
+ `nifra upgrade` prints the full list with the finding or the one-line change that
10787
+ keeps the 4.x behavior for each.
10788
+
10789
+ ### 6. Run the release gates
10790
+
10791
+ -
10792
+ Run `nifra check --json` and fix every error. `nifra doctor` lists any
10793
+ package still on another release.
10794
+
10795
+ -
10796
+ If the project has `nifra.assurance.ts`, run `nifra assure --json`.
10797
+
10798
+ - Run the application test suite and a production build.
10799
+
10800
+ -
10801
+ Exercise the pages that load or post data, and every browser call to the backend, under
10802
+ `nifra dev` and against the production build.
10803
+
10517
10804
  ## Nifra - Migrating from Express, Hono, Fastify & Elysia
10518
10805
 
10519
10806
  > Move a backend to Nifra: routing, middleware, validation, and body parsing map directly from Express, Hono, Fastify, and Elysia - and you gain a typed client, multi-runtime deploy, and optional SSR.
@@ -11434,6 +11721,34 @@ in its `package.json`:
11434
11721
  `{'"nifra": { "environment": "frontend" | "backend" | "shared" | "library" }'}`.
11435
11722
  A file in no zone is a build error, so nothing is ever classified by accident.
11436
11723
 
11724
+ A package whose entries run on different sides gives each export subpath its own:
11725
+
11726
+ ```ts
11727
+ {
11728
+ "name": "@shop/payments",
11729
+ "exports": {
11730
+ ".": "./src/index.ts",
11731
+ "./react": "./src/react.tsx",
11732
+ "./schema": "./src/schema.ts"
11733
+ },
11734
+ "nifra": {
11735
+ "environment": {
11736
+ ".": "backend",
11737
+ "./react": "frontend",
11738
+ "./schema": "library"
11739
+ }
11740
+ }
11741
+ }
11742
+ ```
11743
+
11744
+ An import is judged by the entry it names: the exact subpath first, then the most specific
11745
+ `*` pattern. Here a page may import `@shop/payments/react` but not
11746
+ `@shop/payments`, and backend code may not import the React entry, even when two
11747
+ entries are backed by one file. Importing an entry the map leaves out is an error. A file
11748
+ reached by path rather than by entry, through a relative or aliased deep import, takes the
11749
+ side of every mapped entry whose own imports reach it, and a file no mapped entry reaches is
11750
+ an error.
11751
+
11437
11752
  The app's own `package.json` can set sides too, so a folder that has to stay
11438
11753
  where it is, or a family of workspace packages, needs no edit of its own:
11439
11754
 
@@ -11443,7 +11758,8 @@ where it is, or a family of workspace packages, needs no edit of its own:
11443
11758
  "nifra": {
11444
11759
  "zones": {
11445
11760
  "packages": { "@shop/*": "library", "@shop/db": "backend" },
11446
- "backend": ["migrations"]
11761
+ "backend": ["migrations", "src/ui/mailer.ts"],
11762
+ "frontend": ["src/ui"]
11447
11763
  }
11448
11764
  }
11449
11765
  }
@@ -11451,9 +11767,11 @@ where it is, or a family of workspace packages, needs no edit of its own:
11451
11767
 
11452
11768
  `packages` gives a side to a package that does not declare one, by exact name or
11453
11769
  by a prefix ending in `*`; a package's own declaration always wins.
11454
- `backend`, `frontend` and `shared` list app folders that
11455
- count as that zone folder. Builds, dev servers, `nifra check` and the editor
11456
- plugin all read this setting.
11770
+ `backend`, `frontend` and `shared` list app folders, or
11771
+ single files, that count as that zone. A path inside another's folder overrides it, so the
11772
+ most specific entry decides: above, `src/ui/mailer.ts` is backend code in a
11773
+ frontend folder. One path listed under two sides is refused. Builds, dev servers,
11774
+ `nifra check` and the editor plugin all read this setting.
11457
11775
 
11458
11776
  A project that serves no pages has no browser, so the zones do not apply to it. Declare that
11459
11777
  in its `package.json` with `{'"nifra": { "kind": "api" }'}`:
@@ -11569,24 +11887,37 @@ import fails the build with the chain that led to it:
11569
11887
  -
11570
11888
  **Browser code reads only public environment variables** - `NODE_ENV` and
11571
11889
  names with the `PUBLIC_` prefix. Any other `process.env` read in a
11572
- page, `frontend/` or `shared/` fails the build.
11890
+ page, `frontend/` or `shared/` fails the build, optional access (
11891
+ `process.env?.["SECRET"]`) included.
11573
11892
 
11574
11893
  -
11575
11894
  **The build scans what it emits.** A client bundle, a public file or a prerendered page
11576
- that contains what looks like a credential fails the build.
11895
+ that contains what looks like a credential fails the build. A label in a message catalog (
11896
+ `{'{ password: "Password" }'}`) and a documented placeholder (
11897
+ `your-api-key`) are text, not credentials.
11577
11898
 
11578
11899
  The same rules run in `nifra dev` (a refused module is answered with a 403 before
11579
11900
  its source is sent), in both bundlers, for every framework, and in `nifra check`,
11580
11901
  which follows the imports the bundler follows: relative paths, tsconfig `paths`
11581
11902
  and `baseUrl`, `package.json` `imports`, and the
11582
- `clientModule` the browser entry loads. An app on the older layout moves with `nifra migrate layout`: a dry run that
11583
- reports every move, then `--write`. It follows the same imports, rewrites them
11584
- and test-runner mocks (an alias through the same alias), moves the config's
11585
- `clientModule` and component pipeline entries with their modules, adds the zone
11586
- folders to tsconfig `include` and `.nifra/` to
11587
- `.gitignore`, and leaves data files and folders zoned in
11588
- `package.json` where they are. It reports what still needs a hand, such as an
11589
- alias with no target that covers a moved file, or a specifier that names nothing installed.
11903
+ `clientModule` the browser entry loads. An app on the older layout moves with
11904
+ `nifra migrate layout`: a dry run that reports every move, then
11905
+ `--write`. It follows the same imports, rewrites them and test-runner mocks (an
11906
+ alias through the same alias), and moves the config's `clientModule`, any module
11907
+ path the config builds from its own folder, and component pipeline entries with their
11908
+ modules. A test moves with the file beside it, snapshots included. Each
11909
+ `tsconfig*.json` keeps every file in, or out of, its program as before. It adds
11910
+ `.nifra/` to `.gitignore` and leaves data files and folders zoned in
11911
+ `package.json` where they are.
11912
+
11913
+ A route file that holds server code splits into the page and its
11914
+ `x.backend.ts`. Data both halves read moves to `x.shared.ts` beside
11915
+ them; a value that cannot move is copied into each half and reported. A loader or action
11916
+ that returns data without an output schema gets a draft one, typed from what it returns:
11917
+ review that the browser may see every field it declares. The migration reports what still
11918
+ needs a hand, such as an alias with no target that covers a moved file, a specifier that
11919
+ names nothing installed, or a sibling workspace package that names a moved file. It never
11920
+ edits files outside the app.
11590
11921
 
11591
11922
  An API-only app, one that declares `{'"nifra": { "kind": "api" }'}` or has no web
11592
11923
  config, no `routes/` and a `backend.ts` that exports
@@ -12132,6 +12463,43 @@ export default defineAssuranceConfig({
12132
12463
  })
12133
12464
  ```
12134
12465
 
12466
+ ### Response contracts
12467
+
12468
+ Next to the ladder, `nifra levels` counts the routes that declare a
12469
+ `response` or `errors` schema and how each is held at runtime:
12470
+ _enforced_, _warn-only_ (checked, original payload still sent) or _typed-only_
12471
+ (no `responseContract()` covers it). A typed-only route is usually registered on a
12472
+ server other than the one the plugin is installed on, or before it; the report names the routes
12473
+ and says which.
12474
+
12475
+ The count does not fail a level on its own. To make L1 require enforcement, add a
12476
+ `baseline` rule selecting `hasResponse: true`: unlike
12477
+ `rules`, where the first match owns a route, a baseline rule is checked on every
12478
+ route it matches, so one entry covers every class. `nifra assure --strict` also fails
12479
+ while any declared route is typed-only or warn-only.
12480
+
12481
+ ```ts
12482
+ // nifra.assurance.ts
12483
+ // doc-check: skip - the backend is app-specific
12484
+ import { defineAssuranceConfig, NIFRA_ASSURANCE } from "@nifrajs/core/assurance"
12485
+ import { backend } from "./backend"
12486
+
12487
+ export default defineAssuranceConfig({
12488
+ source: backend,
12489
+ policy: {
12490
+ rules: [/* first match owns each route: classify by path, method, capability ... */],
12491
+ // Checked on every route it matches, whichever rule owns the route.
12492
+ baseline: [
12493
+ {
12494
+ name: "response-contract",
12495
+ match: { hasResponse: true },
12496
+ require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
12497
+ },
12498
+ ],
12499
+ },
12500
+ })
12501
+ ```
12502
+
12135
12503
  ### For coding agents
12136
12504
 
12137
12505
  The `nifra_levels` MCP tool returns the same
@@ -12303,9 +12671,11 @@ A suspended task's status message carries the pending continuation in `metadata`
12303
12671
  }
12304
12672
  ```
12305
12673
 
12674
+ The resuming peer is usually the agent that asked for the tool, so it does not approve its own call: the run asks `ports.approval` again and hands it the peer's `approval` as `callerDecision`. With no approval port the task stays `TASK_STATE_INPUT_REQUIRED`. Return `resumeApproval: "caller"` from `ports` only when the peer is the approver, such as a human operator's client.
12675
+
12306
12676
  Cross-request registries (cancellation, subscription fan-out, push notifications) are deliberately out of scope for the stateless seam; those spec methods answer `UnsupportedOperationError` (-32004).
12307
12677
 
12308
- A reply larger than `maxOutputBytes` answers the request with a JSON-RPC internal error (-32603, `output_limit`), the same error a stream sends when it runs out of budget. A request whose ports or store fail answers with -32603 `internal_error`, keeping the request's `id`.
12678
+ A reply larger than `maxOutputBytes` answers the request with a JSON-RPC internal error (-32603, `output_limit`), the same error a stream sends when it runs out of budget. A stream that runs out also aborts the run's signal, as a client disconnect does, so the run stops calling the model. A request whose ports or store fail answers with -32603 `internal_error`, keeping the request's `id`.
12309
12679
 
12310
12680
  The seam performs no authentication or authorization - wrap it with your app's route guards and scope every port in `ports(c)` to the caller.
12311
12681
 
@@ -12509,7 +12879,8 @@ adapter implementing the same `AgentEvidenceLog` interface.
12509
12879
  When more than one caller shares the route, also pass `evidenceOwner: (c) => <the caller's user or
12510
12880
  tenant id>`: turns are recorded and replayed under that owner, so another caller's reconnect or
12511
12881
  reused `runId` finds none of them. Without it a turn id (by default the client's `runId`) works as a
12512
- bearer capability. The owner function runs before any replay; throwing from it refuses the request.
12882
+ bearer capability. The owner function runs before any replay; throwing from it refuses the request with `403`, and a
12883
+ thrown `Response` answers the request as built, as one thrown from `ports` does.
12513
12884
 
12514
12885
  The seam performs no authentication or authorization - wrap it with your app's route guards and scope every port in `ports(c)` to the caller.
12515
12886
 
@@ -12603,7 +12974,9 @@ on from where the response stopped; without one the run is cancelled.
12603
12974
  When more than one caller shares the route, also pass `evidenceOwner: (c) => <the caller's user or
12604
12975
  tenant id>`: turns are recorded and replayed under that owner, so another caller's reconnect or
12605
12976
  reused `turnId` finds none of them. Without it a turn id works as a bearer capability. The owner
12606
- function runs before any replay; throwing from it refuses the request.
12977
+ function runs before any replay; throwing from it refuses the request with `403`, and a thrown
12978
+ `Response` answers the request as built, as one thrown from `ports` does. A JSON-mode run that
12979
+ throws answers `500 run_failed`, or `409 resume_unavailable` when the POST resumed a turn.
12607
12980
 
12608
12981
  Execution policies can be required by a tool contract. `createLocalProcessAdapter` applies cwd and
12609
12982
  environment filtering, timeouts, and cancellation to child processes. On POSIX each run leads its
@@ -12975,6 +13348,7 @@ How it holds the line:
12975
13348
 
12976
13349
  - **Post-decode body limits.** The body is decoded (base64 or UTF-8) and its real `byteLength` checked against `maxBodyBytes` (default 1,000,000 - match your app's setting if you changed it) *before* a `Request` is constructed. The event's own length claims are never trusted; over the limit answers a flat `413`.
12977
13350
  - **One header merge site.** Request headers come from the v2 `headers` map plus the separate `cookies` array in exactly one function; the array is canonical, so a `cookie` key smuggled into `headers` never reaches the app. Response `Set-Cookie` values travel in the result's `cookies` array, one entry each - never comma-joined.
13351
+ - **Body-less HEAD.** A `HEAD` answer keeps the GET route's status and headers but carries no body, on both handlers. A streaming body is cancelled rather than read, so its producer stops.
12978
13352
  - **Base64 honesty.** Response `isBase64Encoded` is decided by a strict UTF-8 decode of the actual bytes, never by content-type guessing. Binary round-trips exactly; text ships as text.
12979
13353
  - **Client IP.** `event.requestContext.http.sourceIp` feeds core's client-IP seam; `X-Forwarded-For` trust stays where it belongs, in the app's `clientIp` server option.
12980
13354
  - **Flat errors.** Uncaught failures collapse to the same `{"ok":false,"error":"internal_error"}` 500 as every other nifra runtime - the event is never echoed.
@@ -13226,6 +13600,9 @@ nifra sdk --lang <python|go> [--strict]
13226
13600
  nifra doctor [--json] [--auto-fix] Catch undeclared imports and duplicate Nifra/React installs.
13227
13601
  ```
13228
13602
 
13603
+ `nifra dev`, `build` and `start` take a flag's value as `--target vercel` or `--target=vercel`, and refuse
13604
+ a flag or argument they do not know instead of ignoring it.
13605
+
13229
13606
  ## `nifra doctor` - dependency health
13230
13607
 
13231
13608
  Doctor diffs source imports against declared dependencies, then resolves identity-sensitive
@@ -13266,7 +13643,8 @@ is the feedback plan and reports which release gates it omits. CI and release au
13266
13643
  `nifra verify --release` (or `bun run check:release`), which includes build, coverage, corpus,
13267
13644
  consumer, publish, and cross-runtime gates. Each gate runs a `package.json` script (`bun run lint`,
13268
13645
  `bun run check:docs`, ...); a gate whose script the project does not declare is reported as
13269
- `undeclared` and does not fail the run. Run `nifra port --ci --target <target>` for an app's
13646
+ `undeclared` and does not fail the run. The nifra repository's own `bun run verify` and
13647
+ `bun run check:release` fail that gate instead, so a renamed script cannot drop it. Run `nifra port --ci --target <target>` for an app's
13270
13648
  resolved deployment target; CI never guesses a target when none is configured.
13271
13649
 
13272
13650
  ## `nifra check` - the drift gate (run as "done")
@@ -13277,7 +13655,8 @@ remember them. It does three things and exits non-zero if any fails:
13277
13655
  1. **Typecheck** (`tsc --noEmit`, using the project's own TypeScript) - the frontend↔backend contract is
13278
13656
  compiler-enforced. `client<typeof app>` derives request inputs **and** `res.data` from your routes, so
13279
13657
  a screen that expects the wrong shape is a type error. Running the typecheck is what turns that latent
13280
- guarantee into an enforced one.
13658
+ guarantee into an enforced one. TypeScript sources with no `tsconfig.json` beside them fail this step,
13659
+ as a missing `typescript` install does: nothing typechecked them. A JavaScript-only project skips it.
13281
13660
  2. **Typed-client lint** - flags any hand-rolled `fetch()` to your own API (a relative URL like
13282
13661
  `fetch("/users")`), which bypasses the typed client so the compiler can't see the drift. External
13283
13662
  absolute-URL fetches are not flagged.
@@ -13511,6 +13890,14 @@ default 8 MiB) answers `409 resync_required`, so read `session.events` instead;
13511
13890
  `turn.send` while one runs answers `409 turn_running`; and a turn no client has followed for
13512
13891
  `detachedTimeoutMs` (default 2 minutes) is cancelled.
13513
13892
 
13893
+ An approval, or a coding tool's question to the person, is denied once it has waited 5 minutes.
13894
+ Pass `approvals: { timeoutMs }` to `CodingAgentHost` or `CodingAgentRpcServer` for up to 24 h, or
13895
+ `timeoutMs: "never"` to keep it until it is answered, cancelled, its turn ends, or the session
13896
+ closes; `maxPending` (default 32) still bounds how many can wait. `NifraBackend` has its own
13897
+ `approvalTimeoutMs`, taking the same values, and the host denies in the backend whatever its broker
13898
+ denies, so the shorter of the two applies. A never-expiring approval reports `expiresAt` as
13899
+ `APPROVAL_NEVER_EXPIRES_AT`, which survives JSON and stays later than any `Date.now()`.
13900
+
13514
13901
  `nifra-agent` loads `.nifra/extensions/**` from `--cwd` only when started with
13515
13902
  `--extensions`. An extension module's top-level code runs as soon as it is
13516
13903
  imported, before its declared `capabilities` are checked, so pass the flag only
@@ -13819,8 +14206,11 @@ cron.stop() // graceful - in-flight handlers finish
13819
14206
 
13820
14207
  Macros: `@hourly` `@daily` (`@midnight`) `@weekly` `@monthly` `@yearly` (`@annually`).
13821
14208
 
14209
+ Day-of-week runs 0-6 from Sunday, and 7 is Sunday too.
14210
+
13822
14211
  Day-of-month + day-of-week follow the **standard OR rule**: when both are restricted, a match on
13823
- either fires the job.
14212
+ either fires the job. A field that starts with `*` is not restricted, a step like `*/2` included,
14213
+ so `0 0 */2 * 1` fires on an odd day that is also a Monday, as in Vixie cron.
13824
14214
 
13825
14215
  ## Guarantees
13826
14216
 
@@ -13890,6 +14280,14 @@ cap live inside `app.fetch`, so they apply through this adapter automatically.
13890
14280
 
13891
14281
  Works with any `{ fetch(req): Promise<Response> }` handler, not just nifra. ESM/TS. MIT.
13892
14282
 
14283
+ ## WebSocket message size
14284
+
14285
+ `app.ws()` routes upgrade through `Deno.upgradeWebSocket`, which takes no size limit: Deno receives
14286
+ each inbound message whole (up to its own limit, 64 MiB on Deno 2.9) before the app sees it.
14287
+ `server({ wsMaxPayloadBytes })` then closes the socket with `1009` and keeps the message from your
14288
+ handler, but it cannot bound the memory Deno spent receiving it. Where that matters, cap message
14289
+ size in a proxy in front of the app. On Bun and Node the cap is enforced while the message arrives.
14290
+
13893
14291
  ## For AI agents
13894
14292
 
13895
14293
  Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
@@ -14109,8 +14507,11 @@ pass the request's bindings: `defineEnv(shape, { source: env })`.
14109
14507
  ## Secret safety
14110
14508
 
14111
14509
  Error messages name the offending variable and the reason - **never its value** (it may be a
14112
- secret). A BYO validator such as valibot may quote the value in its message; `defineEnv` replaces
14113
- it with `<redacted>`. `defineEnv` returns a frozen object, so a validated config can't be mutated downstream.
14510
+ secret). A BYO validator such as valibot or zod may quote the value in its message, escaped or
14511
+ cut short, so when it refuses a set value `defineEnv` reports the variable as invalid without the
14512
+ validator's message. For an unset variable the message is shown, since there is no value to quote.
14513
+ Use the `env.*` helpers when you want the reason as well. `defineEnv` returns a frozen object, so a
14514
+ validated config can't be mutated downstream.
14114
14515
 
14115
14516
  ## For AI agents
14116
14517
 
@@ -14250,6 +14651,7 @@ With `subscriptions: true`, a socket is admitted only when `authorize` accepts i
14250
14651
  | `allowedOrigins` | reflect `*` | CORS origin allowlist. |
14251
14652
  | `authorize` | - | Per-request guard; return `false` for 401. |
14252
14653
  | `enableGet` | `true` | Serve `GET /graphql` (queries only). |
14654
+ | `introspection` | `true` | `false` refuses `__schema` and `__type` as a validation error (400 over HTTP, an `error` frame over WS); `__typename` still resolves. Subscriptions inherit it unless their own options set it. |
14253
14655
  | `subscriptions` | `false` | `true` (reuse schema, context, `authorize` and limits) or explicit WS options. |
14254
14656
  | `legacyJsonResponse` | `false` | Old always-200 `application/json` shape. |
14255
14657
 
@@ -14569,6 +14971,8 @@ fails at the call site, not three retries later.
14569
14971
 
14570
14972
  Per-job: `retries: number` (attempts) or `{ attempts, backoff }`. A handler that throws is routed to
14571
14973
  `onError`, then retried with the backoff delay; after the last attempt it's **dead-lettered**, not lost.
14974
+ An attempt count (and `createQueue({ defaultAttempts })`) must be a whole number; `NaN`, `Infinity` or
14975
+ a fraction throws a `RangeError`, and a count below 1 means one attempt.
14572
14976
 
14573
14977
  ```ts
14574
14978
  import { exponentialBackoff, fixedBackoff } from "@nifrajs/jobs"
@@ -14590,10 +14994,18 @@ const q = createQueue({ store: new RedisJobStore(redis) })
14590
14994
  Leasing is at-least-once: a leased job is hidden for `leaseMs`; a worker that dies mid-job releases it
14591
14995
  back automatically. Make handlers **idempotent**.
14592
14996
 
14997
+ While a handler runs, the worker renews its lease about three times per `leaseMs` through the store's
14998
+ optional `renew(id, until, lease)`, so a job that runs longer than `leaseMs` is not handed to another
14999
+ worker. A store without `renew` cannot do that: such a job may then run twice, on two workers, though
15000
+ never twice on the same queue. Each `lease` may also return a `lease` token on the job; the queue
15001
+ passes it back to `complete` / `retry` / `deadLetter` / `renew`, so a store can ignore a worker whose
15002
+ lease another worker has since taken. `MemoryJobStore` does both.
15003
+
14593
15004
  A job's `timeoutMs` bounds each attempt: one still running after that many ms fails with a
14594
15005
  `JobTimeoutError` (then retries or dead-letters), frees its worker slot, and aborts `ctx.signal`. Pass the
14595
- signal on to `fetch` or your driver, since a handler that ignores it keeps running unobserved. Keep
14596
- `timeoutMs` below `leaseMs`, after which the job can be leased again.
15006
+ signal on to `fetch` or your driver, since a handler that ignores it keeps running unobserved.
15007
+ `timeoutMs` must be below the worker's `leaseMs`: `start()` (and a later `define()`) throws a
15008
+ `RangeError` otherwise.
14597
15009
 
14598
15010
  ```ts
14599
15011
  q.define("render", { timeoutMs: 10_000, async handler(id, ctx) { await fetch(url(id), { signal: ctx.signal }) } })
@@ -16054,6 +16466,14 @@ Framework-agnostic SSR core for nifra - the render seam + HTML document orchestr
16054
16466
 
16055
16467
  Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
16056
16468
 
16469
+ ## Guarding pages
16470
+
16471
+ A `_layout.backend.ts` loader is data, not a guard: a client navigation can skip it while the browser
16472
+ keeps the layout's data, and an action below it runs without it. Refuse access in that file's
16473
+ `middleware`, or export `gate = true` so its loader runs on every request and before every action,
16474
+ ahead of the page loader. In development, nifra warns when a layout loader without `gate` refuses a
16475
+ request, and `nifra check` reports one whose refusal it can read (NF-C042). See [Layout loaders on navigation](https://nifra.dev/docs/data).
16476
+
16057
16477
  ## Install
16058
16478
 
16059
16479
  ```sh
@@ -16545,7 +16965,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
16545
16965
 
16546
16966
  ## @nifrajs/coding-agent
16547
16967
 
16548
- `AGENT_CAPABILITY_MANIFEST_VERSION`, `AGENT_PRESETS`, `AgentCapability`, `AgentCapabilityManifest`, `AgentPlan`, `AgentPreset`, `AgentPresetName`, `ApprovalDecision`, `ApprovalManager`, `ApprovalManagerOptions`, `ApprovalMatchRejection`, `ApprovalMatchResult`, `ApprovalRequest`, `BoundedSubagentRunner`, `BoundedText`, `CapabilityDescriptor`, `CiDeploymentAdapter`, `CodingAgentExtension`, `CodingAgentHost`, `CodingAgentHostOptions`, `CodingAgentRpcServer`, `CodingAgentRpcServerHandle`, `CodingAgentRpcServerOptions`, `CompactionReport`, `ContextRecord`, `ContextWindow`, `ContextWindowOptions`, `DEPLOYMENT_REFERENCE_PROFILES`, `ExtensionCommand`, `ExtensionContext`, `ExtensionDescriptorOptions`, `ExtensionDescriptorSource`, `ExtensionEventHandler`, `ExtensionHost`, `ExtensionHostOptions`, `ExtensionProvider`, `ExtensionReloadResult`, `ExtensionSubagent`, `ExtensionTool`, `FileSessionStore`, `FileSessionStoreOptions`, `HandoffCoordinator`, `HandoffCoordinatorOptions`, `HandoffDecision`, `HandoffError`, `HandoffRejection`, `HandoffView`, `HealingEvent`, `HealingOptions`, `HealingResult`, `IsolatedExtensionSnapshot`, `IsolatedExtensionTool`, `IsolatedExtensionWorker`, `IsolatedExtensionWorkerOptions`, `LocalProcessDeploymentAdapter`, `MigrateLegacySessionOptions`, `NIFRA_AGENT_INSTRUCTIONS`, `NativeApprovalPort`, `NativeGatewayModelPortOptions`, `NativeMessage`, `NativeModelChunk`, `NativeModelPort`, `NativeModelRequest`, `NativeModelResponse`, `NativeTool`, `NifraAgentTool`, `NifraBackend`, `NifraBackendOptions`, `NifraContextOptions`, `NifraContextResult`, `OpenHandoffInput`, `PiBackend`, `PiBackendOptions`, `PlanEvent`, `PlanPhase`, `PlanResult`, `PlanRunner`, `PlanRunnerOptions`, `ProjectDiffOptions`, `ProjectDiffResult`, `RepairProposal`, `ReplayBackend`, `ReplayBackendOptions`, `ReplayDeploymentAdapter`, `ReviewExecutionErrorCode`, `ReviewExecutionResult`, `ReviewOptions`, `SESSION_EVIDENCE_VERSION`, `SelfHealingController`, `SessionEvidenceRecord`, `SessionLogEntry`, `SessionMigrationError`, `SessionMigrationReport`, `SessionStore`, `SubagentAbandonment`, `SubagentAbandonmentLedger`, `SubagentExecutor`, `SubagentResult`, `SubagentRunnerOptions`, `SubagentSpec`, `UiExtensionHost`, `UiExtensionHostOptions`, `UiExtensionManifest`, `UiExtensionSlot`, `UiReloadResult`, `UiStatusWidget`, `UiThemeDescriptor`, `VerificationOptions`, `VerificationRepairTask`, `VerificationResult`, `WorkflowContext`, `WorkflowEvent`, `WorkflowResult`, `WorkflowRunner`, `WorkflowRunnerOptions`, `WorkflowStep`, `approvalRequestFromEvent`, `approvalResolvedEvent`, `createCapabilityManifest`, `createCiDeploymentAdapter`, `createLocalProcessDeploymentAdapter`, `createNativeGatewayModelPort`, `createNifraTools`, `createPresetSpec`, `createReplayDeploymentAdapter`, `createVerificationRepairTask`, `deniedCapabilities`, `discoverExtensions`, `extensionDescriptor`, `extensionDescriptors`, `getAgentPreset`, `isSafeReviewDiff`, `isVerificationAvailable`, `migrateLegacySession`, `parseCapabilityManifest`, `parseSessionEvidenceRecord`, `readBoundedText`, `readProjectDiff`, `readReplayEvents`, `runNifraContext`, `runNifraReview`, `runNifraVerification`, `stableSessionEventCode`, `validateExtensionModule`
16968
+ `AGENT_CAPABILITY_MANIFEST_VERSION`, `AGENT_PRESETS`, `APPROVAL_NEVER_EXPIRES_AT`, `AgentCapability`, `AgentCapabilityManifest`, `AgentPlan`, `AgentPreset`, `AgentPresetName`, `ApprovalDecision`, `ApprovalManager`, `ApprovalManagerOptions`, `ApprovalMatchRejection`, `ApprovalMatchResult`, `ApprovalRequest`, `ApprovalTimeout`, `BoundedSubagentRunner`, `BoundedText`, `CapabilityDescriptor`, `CiDeploymentAdapter`, `CodingAgentExtension`, `CodingAgentHost`, `CodingAgentHostOptions`, `CodingAgentRpcServer`, `CodingAgentRpcServerHandle`, `CodingAgentRpcServerOptions`, `CompactionReport`, `ContextRecord`, `ContextWindow`, `ContextWindowOptions`, `DEPLOYMENT_REFERENCE_PROFILES`, `ExtensionCommand`, `ExtensionContext`, `ExtensionDescriptorOptions`, `ExtensionDescriptorSource`, `ExtensionEventHandler`, `ExtensionHost`, `ExtensionHostOptions`, `ExtensionProvider`, `ExtensionReloadResult`, `ExtensionSubagent`, `ExtensionTool`, `FileSessionStore`, `FileSessionStoreOptions`, `HandoffCoordinator`, `HandoffCoordinatorOptions`, `HandoffDecision`, `HandoffError`, `HandoffRejection`, `HandoffView`, `HealingEvent`, `HealingOptions`, `HealingResult`, `IsolatedExtensionSnapshot`, `IsolatedExtensionTool`, `IsolatedExtensionWorker`, `IsolatedExtensionWorkerOptions`, `LocalProcessDeploymentAdapter`, `MigrateLegacySessionOptions`, `NIFRA_AGENT_INSTRUCTIONS`, `NativeApprovalPort`, `NativeGatewayModelPortOptions`, `NativeMessage`, `NativeModelChunk`, `NativeModelPort`, `NativeModelRequest`, `NativeModelResponse`, `NativeTool`, `NifraAgentTool`, `NifraBackend`, `NifraBackendOptions`, `NifraContextOptions`, `NifraContextResult`, `OpenHandoffInput`, `PiBackend`, `PiBackendOptions`, `PlanEvent`, `PlanPhase`, `PlanResult`, `PlanRunner`, `PlanRunnerOptions`, `ProjectDiffOptions`, `ProjectDiffResult`, `RepairProposal`, `ReplayBackend`, `ReplayBackendOptions`, `ReplayDeploymentAdapter`, `ReviewExecutionErrorCode`, `ReviewExecutionResult`, `ReviewOptions`, `SESSION_EVIDENCE_VERSION`, `SelfHealingController`, `SessionEvidenceRecord`, `SessionLogEntry`, `SessionMigrationError`, `SessionMigrationReport`, `SessionStore`, `SubagentAbandonment`, `SubagentAbandonmentLedger`, `SubagentExecutor`, `SubagentResult`, `SubagentRunnerOptions`, `SubagentSpec`, `UiExtensionHost`, `UiExtensionHostOptions`, `UiExtensionManifest`, `UiExtensionSlot`, `UiReloadResult`, `UiStatusWidget`, `UiThemeDescriptor`, `VerificationOptions`, `VerificationRepairTask`, `VerificationResult`, `WorkflowContext`, `WorkflowEvent`, `WorkflowResult`, `WorkflowRunner`, `WorkflowRunnerOptions`, `WorkflowStep`, `approvalRequestFromEvent`, `approvalResolvedEvent`, `createCapabilityManifest`, `createCiDeploymentAdapter`, `createLocalProcessDeploymentAdapter`, `createNativeGatewayModelPort`, `createNifraTools`, `createPresetSpec`, `createReplayDeploymentAdapter`, `createVerificationRepairTask`, `deniedCapabilities`, `discoverExtensions`, `extensionDescriptor`, `extensionDescriptors`, `getAgentPreset`, `isSafeReviewDiff`, `isVerificationAvailable`, `migrateLegacySession`, `parseCapabilityManifest`, `parseSessionEvidenceRecord`, `readBoundedText`, `readProjectDiff`, `readReplayEvents`, `runNifraContext`, `runNifraReview`, `runNifraVerification`, `stableSessionEventCode`, `validateExtensionModule`
16549
16969
 
16550
16970
  ## @nifrajs/content
16551
16971
 
@@ -16553,7 +16973,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
16553
16973
 
16554
16974
  ## @nifrajs/core
16555
16975
 
16556
- `AdmissionController`, `AdmissionDecision`, `AnyServer`, `AuthHeaders`, `AuthMaybePromise`, `AuthenticationFailure`, `AuthenticationFailureReason`, `AuthenticationInput`, `AuthenticationResult`, `AuthenticationStage`, `AuthenticationSuccess`, `Context`, `ContextPlugin`, `CookieOptions`, `CookieSecret`, `DeclaredMountRoute`, `DefinePluginResult`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FetchApp`, `FrameworkError`, `FrameworkName`, `Handler`, `IdentityPlugin`, `InferInput`, `InferOutput`, `JoinRoutePath`, `LambdaEvent`, `LambdaHandler`, `LambdaResponse`, `LambdaV1Event`, `LambdaV2Event`, `ListenTlsOptions`, `LogFields`, `Logger`, `METHODS`, `McpPromptDescriptor`, `McpResourceDescriptor`, `Method`, `Middleware`, `MountFetchOptions`, `MountOptions`, `MountableApp`, `NetlifyEvent`, `NetlifyHandler`, `NifraFeatureVersion`, `NifraPlugin`, `NifraWebSocket`, `NodeRequestContext`, `NodeRequestHook`, `NodeResponseBodyHook`, `NodeResponseContext`, `NodeResponseHook`, `NodeServeOutcome`, `OnRequestResult`, `OpaqueMountDeclaration`, `Params`, `PlainRender`, `Platform`, `PlatformResponse`, `PluginTypeCollapsed`, `PrefixRegistry`, `Prettify`, `PromptArgument`, `PromptMessage`, `ROUTE_PATTERN_OVERLAP_MAX_STATES`, `RedactOptions`, `Registry`, `RequestPath`, `ResponseBodyHook`, `ResponseBodyReplacement`, `ResponseControls`, `ResponseFinalization`, `ResponseHeadersHook`, `ResponseHeadersView`, `ResponseMapFor`, `ResponseResult`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `RouteInfoFor`, `RouteMethod`, `RoutePatternOverlapLimitError`, `RouteSchema`, `Router`, `RouterMatch`, `RunningServer`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `ScheduledController`, `ScheduledHandler`, `Server`, `ServerOptions`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `StatusResponse`, `ToolAnnotations`, `TypedSSEStream`, `UrlParts`, `VERSION`, `ValidationOutcome`, `VercelHandler`, `Version`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`, `authenticated`, `commonSecretPatterns`, `cookieNamePrefix`, `defineContextPlugin`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `hasDuplicateCookie`, `isAuthenticatedRequest`, `isSameOriginPath`, `isSameOriginRequest`, `jsonLogger`, `markAuthenticatedRequest`, `parseCookies`, `pathnameOf`, `redactLogFields`, `rejected`, `replacedRequestOf`, `routePatternOverlap`, `serializeCookie`, `server`, `signValue`, `silentLogger`, `status`, `toFetchHandler`, `toLambdaHandler`, `toNetlifyHandler`, `toVercelHandler`, `unsignValue`, `urlPartsOf`, `writableResponse`
16976
+ `AdmissionController`, `AdmissionDecision`, `AnyServer`, `AuthHeaders`, `AuthMaybePromise`, `AuthenticationFailure`, `AuthenticationFailureReason`, `AuthenticationInput`, `AuthenticationResult`, `AuthenticationStage`, `AuthenticationSuccess`, `Context`, `ContextPlugin`, `CookieOptions`, `CookieSecret`, `DeclaredMountRoute`, `DefinePluginResult`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FetchApp`, `FrameworkError`, `FrameworkName`, `Handler`, `IdentityPlugin`, `InferInput`, `InferOutput`, `JoinRoutePath`, `LambdaEvent`, `LambdaHandler`, `LambdaResponse`, `LambdaV1Event`, `LambdaV2Event`, `ListenTlsOptions`, `LogFields`, `Logger`, `METHODS`, `McpPromptDescriptor`, `McpResourceDescriptor`, `Method`, `Middleware`, `MountFetchOptions`, `MountOptions`, `MountableApp`, `NetlifyEvent`, `NetlifyHandler`, `NifraFeatureVersion`, `NifraPlugin`, `NifraWebSocket`, `NodeRequestContext`, `NodeRequestHook`, `NodeResponseBodyHook`, `NodeResponseContext`, `NodeResponseHook`, `NodeServeOutcome`, `OnRequestResult`, `OpaqueMountDeclaration`, `Params`, `PlainRender`, `Platform`, `PlatformResponse`, `PluginTypeCollapsed`, `PrefixRegistry`, `Prettify`, `PromptArgument`, `PromptMessage`, `ROUTE_PATTERN_OVERLAP_MAX_STATES`, `RedactOptions`, `Registry`, `RequestPath`, `ResponseBodyHook`, `ResponseBodyReplacement`, `ResponseControls`, `ResponseFinalization`, `ResponseHeadersHook`, `ResponseHeadersView`, `ResponseMapFor`, `ResponseResult`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `RouteInfoFor`, `RouteMethod`, `RoutePatternOverlapLimitError`, `RouteSchema`, `Router`, `RouterMatch`, `RunningServer`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `ScheduledController`, `ScheduledHandler`, `Server`, `ServerOptions`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `StatusResponse`, `ToolAnnotations`, `TypedSSEStream`, `UrlParts`, `VERSION`, `ValidationOutcome`, `VercelHandler`, `Version`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`, `authenticated`, `commonSecretPatterns`, `cookieNamePrefix`, `defineContextPlugin`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `hasDuplicateCookie`, `isAuthenticatedRequest`, `isSameOriginForm`, `isSameOriginPath`, `isSameOriginRequest`, `jsonLogger`, `markAuthenticatedRequest`, `parseCookies`, `pathnameOf`, `redactLogFields`, `rejected`, `replacedRequestOf`, `routePatternOverlap`, `serializeCookie`, `server`, `signValue`, `silentLogger`, `status`, `toFetchHandler`, `toLambdaHandler`, `toNetlifyHandler`, `toVercelHandler`, `unsignValue`, `urlPartsOf`, `writableResponse`
16557
16977
 
16558
16978
  ## @nifrajs/cron
16559
16979
 
@@ -16665,7 +17085,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
16665
17085
 
16666
17086
  ## @nifrajs/web
16667
17087
 
16668
- `ACTION_GLOBAL`, `Action`, `BOUNDARY_GLOBAL`, `BackendOnly`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `Boundary`, `BoundaryDescriptor`, `BoundaryError`, `BoundaryMode`, `BoundaryRegistration`, `BoundaryRequestCtx`, `BoundaryState`, `BoundaryStates`, `BoundaryStatus`, `BrowserNavigate`, `CacheStore`, `CachedResponse`, `CdnPurgeOutcome`, `CdnPurgeTarget`, `ClientAction`, `ClientActionArgs`, `ClientActionResult`, `ClientLoader`, `ClientLoaderArgs`, `ClientRequestBody`, `ClientRouteHooks`, `ClientRouter`, `ClientRouterOptions`, `CreateCspPolicyOptions`, `CreateNonceResolverOptions`, `CreateWebAppOptions`, `CspHeaderContext`, `CspPolicy`, `CssLoadingMode`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_CSS_LOADING`, `DEFAULT_DEV_PORT`, `DRAFT_COOKIE`, `Deferred`, `DehydratedState`, `DraftCookieControls`, `DynamicBoundary`, `DynamicBoundaryBatch`, `EnableDraftOptions`, `FetchRouteData`, `Fetcher`, `FetcherState`, `FontDisplay`, `FontFace`, `FontPreloadInput`, `FontSource`, `GenerateClientEntryOptions`, `GenerateRouteSearchTypesOptions`, `GenerateServerManifestOptions`, `GetStaticPaths`, `HANDOVER_ID`, `HtmlSanitizer`, `IDLE_BLOCKER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `ISRQuery`, `ISRStoreOp`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `InterceptBoundary`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `LoaderResponseControls`, `LoadingEntry`, `Manifest`, `MatchChain`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `MemoryStaticBoundaryCache`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MiddlewareOutcome`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `NonceContext`, `NonceGenerator`, `NonceHeader`, `NonceHeaderContext`, `NonceResolver`, `NotFoundEntry`, `NotFoundScope`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `PendingBoundary`, `PrefetchMode`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `REDIRECT_HEADER`, `RETAIN_HEADER`, `REVALIDATE_HEADER`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderPageInput`, `RenderPageOptions`, `RenderProps`, `RenderStreamOptions`, `RenderedPage`, `RevalidateEndpointOptions`, `RevalidateResult`, `RevalidateTags`, `RevalidateTagsInput`, `RouteEntry`, `RouteMatch`, `RouteMiddleware`, `RouteModule`, `RoutePattern`, `RouteSearch`, `RouterState`, `SERVER_FN_MODULE`, `STATUS_HEADER`, `SanitizedHtml`, `ScriptDescriptor`, `SearchOf`, `ServePublicDirOptions`, `ShouldRevalidate`, `ShouldRevalidateArgs`, `SsrModuleLoader`, `StaticBoundary`, `StaticBoundaryCache`, `StaticBoundaryImportEdge`, `StaticBoundaryRoot`, `StaticCtx`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `StatusPageOptions`, `Submission`, `SubmitOptions`, `TrustedHtml`, `UIMatch`, `UnsafeScriptDescriptor`, `assertCssLoadingCompatible`, `assertRenderAdapterConformance`, `assertStaticBoundaryImports`, `boundaryDescriptors`, `boundaryModeKey`, `buildManifest`, `canonical`, `createClientRouter`, `createCspPolicy`, `createMatcher`, `createMutation`, `createNonceResolver`, `createQueryClient`, `createWebApp`, `defer`, `disableDraft`, `enableDraft`, `enumerateStaticRoutes`, `filePathToPattern`, `filePathToPatterns`, `fontFace`, `fontPreload`, `generateClientEntry`, `generateRouteSearchTypes`, `generateServerManifest`, `getBrowserNavigate`, `gone`, `hashQueryKey`, `isCacheablePage`, `isDraftEnabled`, `jsonLd`, `mergeHeads`, `nifraScriptHashes`, `normalizeCssLoading`, `notFound`, `openGraph`, `previewEndpoint`, `redirect`, `registerBlocker`, `renderPage`, `renderPageResult`, `resolveChainMeta`, `resolveDynamicBoundaries`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `resolveStaticBoundaries`, `revalidate`, `revalidateEndpoint`, `sanitizeHtml`, `sanitizedHtml`, `searchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `startDynamicBoundaries`, `statusPage`, `trustHtml`, `unsafeInlineScript`, `webProjectEvidence`, `withISR`
17088
+ `ACTION_GLOBAL`, `Action`, `BOUNDARY_GLOBAL`, `BackendOnly`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `Boundary`, `BoundaryDescriptor`, `BoundaryError`, `BoundaryMode`, `BoundaryRegistration`, `BoundaryRequestCtx`, `BoundaryState`, `BoundaryStates`, `BoundaryStatus`, `BrowserNavigate`, `CacheStore`, `CachedResponse`, `CdnPurgeOutcome`, `CdnPurgeTarget`, `ClientAction`, `ClientActionArgs`, `ClientActionResult`, `ClientLoader`, `ClientLoaderArgs`, `ClientRequestBody`, `ClientRouteHooks`, `ClientRouter`, `ClientRouterOptions`, `CreateCspPolicyOptions`, `CreateNonceResolverOptions`, `CreateWebAppOptions`, `CspHeaderContext`, `CspPolicy`, `CssLoadingMode`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_CSS_LOADING`, `DEFAULT_DEV_PORT`, `DRAFT_COOKIE`, `Deferred`, `DehydratedState`, `DocumentParts`, `DraftCookieControls`, `DynamicBoundary`, `DynamicBoundaryBatch`, `EnableDraftOptions`, `FetchRouteData`, `Fetcher`, `FetcherState`, `FontDisplay`, `FontFace`, `FontPreloadInput`, `FontSource`, `GenerateClientEntryOptions`, `GenerateRouteSearchTypesOptions`, `GenerateServerManifestOptions`, `GetStaticPaths`, `HANDOVER_ID`, `HtmlSanitizer`, `IDLE_BLOCKER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `ISRQuery`, `ISRStoreOp`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `InterceptBoundary`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `LoaderResponseControls`, `LoadingEntry`, `Manifest`, `MatchChain`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `MemoryStaticBoundaryCache`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MiddlewareOutcome`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `NonceContext`, `NonceGenerator`, `NonceHeader`, `NonceHeaderContext`, `NonceResolver`, `NotFoundEntry`, `NotFoundScope`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `PendingBoundary`, `PrefetchMode`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `REDIRECT_HEADER`, `RETAIN_HEADER`, `REVALIDATE_HEADER`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderPageInput`, `RenderPageOptions`, `RenderProps`, `RenderStreamOptions`, `RenderedPage`, `RevalidateEndpointOptions`, `RevalidateResult`, `RevalidateTags`, `RevalidateTagsInput`, `RouteEntry`, `RouteMatch`, `RouteMiddleware`, `RouteModule`, `RoutePattern`, `RouteSearch`, `RouterState`, `SERVER_FN_MODULE`, `STATUS_HEADER`, `SanitizedHtml`, `ScriptDescriptor`, `SearchOf`, `ServePublicDirOptions`, `ShouldRevalidate`, `ShouldRevalidateArgs`, `SsrModuleLoader`, `StaticBoundary`, `StaticBoundaryCache`, `StaticBoundaryImportEdge`, `StaticBoundaryRoot`, `StaticCtx`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `StatusPageOptions`, `Submission`, `SubmitOptions`, `TrustedHtml`, `UIMatch`, `UnsafeScriptDescriptor`, `assertCssLoadingCompatible`, `assertRenderAdapterConformance`, `assertStaticBoundaryImports`, `boundaryDescriptors`, `boundaryModeKey`, `buildManifest`, `canonical`, `createClientRouter`, `createCspPolicy`, `createMatcher`, `createMutation`, `createNonceResolver`, `createQueryClient`, `createWebApp`, `defer`, `disableDraft`, `enableDraft`, `enumerateStaticRoutes`, `filePathToPattern`, `filePathToPatterns`, `fontFace`, `fontPreload`, `generateClientEntry`, `generateRouteSearchTypes`, `generateServerManifest`, `getBrowserNavigate`, `gone`, `hashQueryKey`, `isCacheablePage`, `isDraftEnabled`, `jsonLd`, `mergeHeads`, `nifraScriptHashes`, `normalizeCssLoading`, `notFound`, `openGraph`, `previewEndpoint`, `redirect`, `registerBlocker`, `renderPage`, `renderPageResult`, `resolveChainMeta`, `resolveDynamicBoundaries`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `resolveStaticBoundaries`, `revalidate`, `revalidateEndpoint`, `sanitizeHtml`, `sanitizedHtml`, `searchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `startDynamicBoundaries`, `statusPage`, `trustHtml`, `unsafeInlineScript`, `webProjectEvidence`, `withISR`
16669
17089
 
16670
17090
  ## @nifrajs/web-preact
16671
17091