create-nifra 5.1.0 → 5.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,559 @@
1
+ # create-nifra
2
+
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - df0fbb3: A project that declares `@nifrajs/cli` now launches its MCP server as `bunx @nifrajs/cli mcp`, with no version pin. `bunx` runs the CLI the project installs, so the server moves with every upgrade and the launch never goes stale. New scaffolds and `nifra init-agents` write this form. `nifra init-agents --sync-mcp` and `nifra upgrade --write` drop an existing pin in such a project, and `nifra doctor` points out a pin that is left. A project that does not declare the CLI keeps the exact-version pin, since an unpinned `bunx` there would run a cached `latest`. A launch in a workspace root's files stays pinned too, because the root need not hold the CLI's bin. `create-nifra/agent-files`: `mcpConfig(pin?)`, `mcpJson(pin?)` and `agentsMcpSection(pin?)` take an optional pin. `MCP_CONFIG` is now the unpinned config a scaffold writes.
8
+
9
+ ### Patch Changes
10
+
11
+ - 9498cf8: The package ships its `CHANGELOG.md`, so the installed package has the notes for every version, as the GitHub release does. Before, the tarball held only the code, `README.md`, `LICENSE` and `package.json`.
12
+ - f2065e2: A new app's `nifra.assurance.ts` now carries a `response-contract` baseline: every route that declares a `response` schema must enforce it, matched with `hasResponse: true`. The backend already installs `responseContract("enforce")`, so a fresh app passes. A route the contract does not cover now fails `nifra assure` and level L1 instead of sending fields its schema never declared.
13
+
14
+ ## 5.1.0
15
+
16
+ ### Patch Changes
17
+
18
+ - 30c2a6e: With both `nifra.config.ts` and `backend/framework.ts`, every command takes `apiPrefix`, `apiStrip`, `mounts`, `csp`, `nonce`, `serverComponents` and `use` from `backend/framework.ts` when `nifra.config.ts` leaves them unset, and validates them there. Before, `nifra dev` loaded only `nifra.config.ts` and the server entry `nifra build` generates imported only the options it set, so an `apiPrefix` set in `backend/framework.ts` alone served the backend nowhere over HTTP. NF-C035 reads the same merged value, reports a missing `apiPrefix` as a warning instead of information, and `nifra fix --code NF-C035` adds the mount to `backend/framework.ts`. A new site scaffold states `export const apiPrefix = ""` in `backend/framework.ts`, so it starts without the warning.
19
+ - c3551d3: `bun create nifra --db <preset> --auth better-auth` now writes the `BETTER_AUTH_URL` its template's dev server actually uses into `.env.example`: `http://localhost:4321` for the site template (`nifra dev`), `http://localhost:8787` for the ISR template (`wrangler dev`) and `http://localhost:3000` for the API and batteries templates. Before, every template got `http://localhost:3000`, so a site copied from `.env.example` had better-auth checking request origins against the wrong port.
20
+
21
+ ## 5.0.0
22
+
23
+ ### Minor Changes
24
+
25
+ - b70b18a: The generated `./+types/<name>` module declares `Route.Loader` and `Route.Action`. Annotate a
26
+ route's loader, `export const loader: Route.Loader = async ({ params, api }) => ...`, and what it
27
+ returns is checked against `loaderOutput` at compile time. A field of the wrong type, a `defer(...)`
28
+ whose value does not match its `t.deferred(...)` field, or any data from a loader with no
29
+ `loaderOutput` is a type error. A redirect, a `Response` and returning nothing stay allowed.
30
+ `Route.Action` does the same against `actionOutput`, `revalidate(paths, data)` included. A field the
31
+ schema does not declare is not a type error, because the output guard drops it.
32
+
33
+ `@nifrajs/client` exports the types behind them: `InputOf<Module, Name>` (what a route module's
34
+ output schema accepts), `RouteLoaderReturn<Module>` and `RouteActionReturn<Module>`.
35
+
36
+ `nifra scaffold` and the `create-nifra` starters write the annotation. `nifra schemas` reports a
37
+ generated schema over an annotated loader or action as unchecked (NF-C033 warns), because the
38
+ annotation, not the body, is then the function's return type.
39
+
40
+ ### Patch Changes
41
+
42
+ - 817492d: fix(cli): `@nifrajs/cli` no longer depends on `create-nifra`. It ships its own copy of the agent-file generators, so installing the CLI pulls in only `@nifrajs/*` packages and a scoped release-age exclusion covers the whole tree.
43
+ - 4c07ca7: The next steps printed after `--db` now match the scripts each preset writes: Prisma and Kysely apps are told to run `bun run db:migrate` only, since they have no `db:generate` step to run first.
44
+ - d192f10: fix(create-nifra): `bun run start` on a new site boots the built app instead of exiting. A `nifra build` output runs as production, where the starter's in-memory rate-limit store refuses to start unless `NIFRA_ALLOW_MEMORY_RATE_LIMIT=true`. The bun, node and deno `start` scripts now set it, as the Cloudflare one already did, because a local run is a single process. A `--docker` scaffold's `deploy` script sets it for the one container it runs. The image itself does not, so a deployed replica set still needs a shared store or an explicit opt-in.
45
+ - c021dc8: A `site` or `isr` scaffold created with `--db` now passes `tsc`. Its database modules import each other as `./schema.ts`, which the generated `tsconfig.json` refused with TS5097. That tsconfig now sets `allowImportingTsExtensions`, as the `api` and `batteries` templates already did.
46
+ - 2aacb5c: The ISR starter no longer ships `REVALIDATE_SECRET = "dev-secret"` as a `[vars]` entry in `wrangler.toml`, which `wrangler deploy` published as the production purge token. The deployed Worker now answers 404 on `/__nifra/revalidate` until `bunx wrangler secret put REVALIDATE_SECRET` sets a real one; `bun run dev` passes the local value with `--var`. An app scaffolded earlier should delete that `[vars]` entry and set the secret.
47
+ - d3e79c4: A layout's `meta` receives its own loader data as `data`, the value its component renders, or `null` when the layout has no loader, and the page's data as `pageData`. A layout that loads the locale sets `<html lang>` from its data, and a client navigation keeps it, since the soft-navigation head resolves each layout's `meta` with the same data the server used. A page's `meta` is unchanged and also receives `pageData`. `resolveChainMeta` from `@nifrajs/web/client` resolves a route's `meta` chain this way. Generated route types add `Route.MetaArgs`, typed with the module's own loader data.
48
+
49
+ Upgrading: a layout `meta` that read the page's data as `data` reads `pageData` now. `nifra check` reports a layout with no loader whose `meta` reads `data` (NF-C040), and the 5.0.0 upgrade notes name the change.
50
+
51
+ - 15a3a66: Root status pages (`_404.tsx`, `_<status>.tsx`) and `_error.tsx` render inside the layouts above them with their loader data, and the server puts a status page's `meta` in the head. For an unmatched URL the root middleware and layout loaders run first; otherwise the page reuses the layout data the request already loaded. When those layouts cannot render it (a loader throws, a gate or the middleware refuses) a root status page renders alone and is not hydrated, and an `_error` renders inside its layouts without their data, with the same status. A page rendered with layout data is `private, no-store`, and a client navigation that lands on one loads the URL as a document. `export const inLayouts = false` renders a page the 4.x way and runs no layout loader for it.
52
+
53
+ `createClientRouter` takes `layoutStatusRoutes`: status route ids whose page renders inside layouts, which a soft navigation loads as a document.
54
+
55
+ `nifra check` reports a root status page that imports the root layout to wrap itself, since it now renders that layout twice (NF-C034, warning). The site templates' `_404` pages no longer wrap the layout.
56
+
57
+ ## 4.0.2
58
+
59
+ ## 4.0.1
60
+
61
+ ## 4.0.0
62
+
63
+ ### Major Changes
64
+
65
+ - 5f1f3d8: feat(create-nifra)!: every template keeps its server code in `backend/`
66
+
67
+ - The `api` and `batteries` templates keep their app in `backend/` (`backend/app.ts`,
68
+ `backend/index.ts`), and `backend/app.ts` exports `backend` and `type Backend`, so `nifra contracts`
69
+ and `nifra sdk` find it without configuration.
70
+ - `--db` writes its data layer to `backend/db/` (the Drizzle config, scripts and `.gitignore` entries
71
+ point there), and `--auth` writes `backend/auth.ts`. Every module a scaffold writes is in a zone,
72
+ so a site's route backends can import them.
73
+ - The `isr` template's Workers entry and local Bun server are `backend/worker.ts` and
74
+ `backend/dev-server.ts`.
75
+
76
+ - 6c978a1: feat(create-nifra)!: a site scaffold picks one deploy target
77
+
78
+ - `--target bun|node|deno|cloudflare|vercel` (default `bun`) chooses where the site deploys.
79
+ `--deploy` is refused with that name.
80
+ - `--docker` adds a Dockerfile and `.dockerignore` for `bun` or `node`.
81
+ - The scaffold carries no hand-written server entry or build script. `nifra build` generates the
82
+ target's entry, and `nifra.config.ts` exports the `target` (`nifra target <t>` switches it).
83
+ - Only the target's own config file is written (`deno.json` for Deno, `wrangler.toml` for
84
+ Cloudflare), plus `start` / `deploy` scripts for it. `--ci` deploys the chosen target.
85
+
86
+ - 214d674: feat(create-nifra)!: site scaffolds use the zoned layout - `backend/app.ts`, `backend/framework.ts`,
87
+ and each route's loader and action in its `x.backend.ts`.
88
+
89
+ ### Minor Changes
90
+
91
+ - c3057ee: feat: AGENTS.md is the one copy of an app's agent guidance
92
+
93
+ A scaffold and `nifra init-agents` write `AGENTS.md` plus a pointer to it for each agent:
94
+ `CLAUDE.md` and `GEMINI.md` import it, `.cursor/rules/nifra.mdc` is an always-applied Cursor rule
95
+ that attaches it, and `.github/copilot-instructions.md` names it. None of them carries guidance of its
96
+ own, so they cannot drift apart. A web app's `AGENTS.md` gains a "Project structure" section on the
97
+ frontend/backend zones the build enforces, and `nifra init-agents` appends it to an app with `routes/`.
98
+
99
+ - a0cfffa: `export const clientIp = "platform"` in `nifra.config.ts` makes a `cloudflare` or `vercel` build read `c.clientIp` from the header that platform's edge overwrites (`cf-connecting-ip`, `x-real-ip`), so per-caller middleware such as `rateLimit` works there. Without it an edge build still has no caller address; Bun, Node and Deno builds use the socket peer either way. `buildTarget` and `generateServerEntry` take the same `clientIp` option. Site scaffolds declare it.
100
+
101
+ ### Patch Changes
102
+
103
+ - a8b33bc: The Solid and Svelte compiler plugins live on their `/plugin` subpaths, and each adapter root exports only the render adapter.
104
+
105
+ - `solidBunPlugin` is imported from `@nifrajs/web-solid/plugin`, matching `@nifrajs/web-svelte/plugin` and `@nifrajs/web-vue/plugin`.
106
+ - `@nifrajs/web-solid` and `@nifrajs/web-svelte` link no build-time or `node:` module, so a server or edge bundle that imports the adapter builds under edge resolve conditions (Cloudflare Workers, Vercel Edge, Deno).
107
+ - Solid site scaffolds import the plugin from the subpath.
108
+
109
+ Breaking: `solidBunPlugin` is no longer exported from `@nifrajs/web-solid`, nor `svelteBunPlugin` from `@nifrajs/web-svelte`. `nifra fix --code NF-C005` rewrites those imports.
110
+
111
+ - a0cfffa: Cloudflare scaffolds (a site with `--target cloudflare`, and the ISR template) declare `compatibility_date = "2025-04-01"`, the first date at which `nodejs_compat` fills `process.env` from the project's variables, so `NIFRA_ALLOW_MEMORY_RATE_LIMIT` reaches the starter's rate limit. Their local-only commands (`bun run start` for the site, `bun run dev` for ISR) set it for the one local process; a deploy still refuses to start until the variable is set.
112
+ - 9868241: `bun create nifra . --force` scaffolds into the current directory on Windows.
113
+ - dd0d5c1: With `--force`, a scaffold that fails reports its own error instead of suggesting `--force`.
114
+ - 6ce7975: The `AGENTS.md` a new app ships with names every route schema slot (`body`, `query`, `params`, `headers`, `cookies`) and shows a `params` schema validating and coercing a path param, where it used to say path params and headers could not be declared in the route schema.
115
+ - 7669f56: A project whose name has capitals, `_`, or `.` (`MyApp`, `my_app`) gets a Docker image tag, a `wrangler.toml` `name`, and Cloudflare Pages and Deno Deploy CI project names in lowercase letters, digits, and `-`, the form those platforms take. `package.json` keeps the name as given.
116
+ - a77b341: The `site` and `isr` templates now ignore `.env` and `.env.*` (keeping `.env.example`), as the `api` and `batteries` templates already did.
117
+ - ce169a2: `create-nifra` refuses a destination that already holds files unless `--force` is given, for every template. Before, the site template scaffolded into such a directory and replaced its `.gitignore`, `AGENTS.md`, `CLAUDE.md` and agent/MCP config files. An existing empty directory is now accepted without `--force`, and `bun create nifra . --force` names the project after the current directory instead of rejecting the name `.`.
118
+ - b83d400: The Vue site starter keeps its global stylesheet in an SFC `<style>` block, and the Preact starter renders its stylesheet unescaped, so both hydrate with no mismatch and the server-rendered page has its fonts before the client loads.
119
+ - 29c6c94: feat(middleware): client-IP default keys and a CSRF form field
120
+
121
+ `rateLimit()` without `key`, `header`, or `trustedProxies` keys each bucket on the caller IP the
122
+ server resolved: the socket peer, or the app's `server({ clientIp })` trust declaration behind a
123
+ proxy. It no longer throws at construction, so the scaffolded backend templates, which pass only
124
+ `store`, `max`, and `windowMs`, start. A request with no resolvable caller IP gets 500
125
+ `rate_limit_key_unavailable`. A custom `key` receives the platform as its second argument.
126
+
127
+ `ipRestriction()` without `clientIp`, `header`, or `trustedProxies` judges the same resolved caller
128
+ IP, and denies a request that has none. A custom `clientIp` receives the platform as its second
129
+ argument.
130
+
131
+ Both read an IPv4-mapped IPv6 address (`::ffff:a.b.c.d`, how Bun and Node report an IPv4 peer on
132
+ their default listener) as the IPv4 address it carries, so IPv4 `allow`/`deny` rules match it; a rule
133
+ may also be written in that form (`::ffff:10.0.0.0/104`). `rateLimit()` counts an IPv6 caller by its
134
+ `/64`, however the key was derived.
135
+
136
+ `csrf({ field })` also accepts the token from a form field in an
137
+ `application/x-www-form-urlencoded` or `multipart/form-data` body, for plain HTML forms that cannot
138
+ set a header. Bodies over `fieldMaxBytes` (default 64 KiB) are not read for the field, file parts
139
+ never count as the token, and the body stays readable by the route. Keep the Origin check on when
140
+ using a field.
141
+
142
+ - 4b8d8de: feat(web)!: everything a route sends to the browser passes a declared output schema
143
+
144
+ A route's backend half declares what its loader and action send to the browser:
145
+ `export const loaderOutput = t.object({ ... })` and `export const actionOutput = ...`. A boundary
146
+ loader declares `boundaryLoaders[name].output`, and a server function `serverFn({ output }, fn)`.
147
+ Before the page renders and before anything is serialized, the data is projected through its schema:
148
+ keys the schema does not declare are dropped, at every depth, even when the schema itself would accept
149
+ them, so the component and the browser see the same value. A declared field of the wrong shape fails
150
+ the request with a 500 whose message names the field's path, never its value. A loader that returns
151
+ data without an output schema fails the request; one that returns nothing, redirects or answers with
152
+ an error status needs none. A loader, action, boundary loader or server function that returns a 2xx
153
+ `Response` is refused, since its body would bypass the schema.
154
+
155
+ A deferred value is declared with `t.deferred(schema)`, and what it resolves to is projected and
156
+ validated by `schema` before it streams. A deferred value the schema does not declare as one is
157
+ refused.
158
+
159
+ An output schema that names a field such as `password`, `passwordHash`, `secret`, `token`, `apiKey`,
160
+ `privateKey` or `ssn` fails the route when it loads, unless the field is wrapped in
161
+ `t.declassified("why it may reach the browser", schema)`. `@nifrajs/web/zones` exports the name test
162
+ as `isSensitiveFieldName`.
163
+
164
+ Projection reads a nifra schema's JSON Schema, or a Standard JSON Schema such as zod's. A schema that
165
+ exposes neither keeps whatever its own `validate` returns.
166
+
167
+ The scaffolded site and ISR routes declare their loader and action output schemas.
168
+
169
+ - d50f73e: feat: generated route types
170
+
171
+ Every route file gets a generated `Route` namespace, imported from `./+types/<name>` by both halves
172
+ of the route:
173
+
174
+ ```ts
175
+ // routes/blog/[slug].backend.ts
176
+ import type { Route } from "./+types/[slug]"
177
+ export const loaderOutput = t.object({ title: t.string() })
178
+ export async function loader({ api, params }: Route.LoaderArgs) { ... }
179
+
180
+ // routes/blog/[slug].tsx
181
+ import type { Route } from "./+types/[slug]"
182
+ export default function Post({ data, params }: Route.ComponentProps) { ... }
183
+ ```
184
+
185
+ `Route.Params` comes from the path (`[id]`, optional `[[lang]]`, catch-all `[...path]`).
186
+ `Route.LoaderData` and `Route.ActionData` are the output types of `loaderOutput` and `actionOutput`,
187
+ so a component is typed with exactly what reaches it. `Route.LoaderArgs` types `api` from the app's
188
+ `backend/app.ts`, which `.nifra/types/register.d.ts` registers once with `@nifrajs/client`'s new
189
+ `Register` interface; no route imports the backend for its types.
190
+
191
+ `nifra types` writes the files to `.nifra/types` (and `--check` fails when one is stale); `nifra dev`,
192
+ `nifra build` and `nifra check` refresh them, and `nifra dev` keeps them current as routes are added
193
+ or removed. A route resolves `./+types/<name>` through `"rootDirs": [".", "./.nifra/types"]` in
194
+ tsconfig, which the scaffolded site and ISR apps now carry; `nifra types` says so when it is missing.
195
+ `@nifrajs/web/route-types` exports the generator.
196
+
197
+ - 4936309: Pin generated CI workflows to the repository's current Bun release.
198
+ - fb7c5b3: Site and ISR scaffolds declare `@types/bun` and load its ambient types. The starter database rules in `nifra.assurance.ts` are optional, so an app without a database passes `nifra assure`; an import that matches a rule still grants its capabilities.
199
+
200
+ The ISR template's client build no longer copies `public/` into its own output directory.
201
+
202
+ - 715186c: `--auth better-auth` scaffolds ship no placeholder secret.
203
+
204
+ - `.env.example` leaves `BETTER_AUTH_SECRET` empty. Locally, better-auth falls back to its development secret; in production it refuses to start until a secret is set.
205
+ - The generated `backend/auth.ts` refuses a `BETTER_AUTH_SECRET` shorter than 32 characters in production, where better-auth itself only logs a warning.
206
+
207
+ - d1e2f50: feat: starters type their routes with the generated `./+types`
208
+
209
+ The site and ISR starters type the landing page as `import type { Route } from "./+types/index"`:
210
+ `props: Route.ComponentProps` in the page, `Route.LoaderArgs` / `Route.ActionArgs` in its backend
211
+ half. `data` is the `loaderOutput` schema's type, which is what reaches the browser. A scaffold
212
+ ships its `.nifra/types`, so the types resolve right after `bun install`, before any nifra command
213
+ has run. The Svelte starter types its `$props()` the same way, and the Vue starter types the props it
214
+ declares from `Route`. Svelte and Vue scaffolds include their `.svelte` / `.vue` files in
215
+ `tsconfig.json`, so svelte-check, vue-tsc and the editor type-check routes. `nifra_frontend`'s
216
+ loader-typing guidance now points at the route types.
217
+
218
+ - 784d772: `testClient` calls now come from `127.0.0.1`, as a socket peer's would, so middleware keyed on the caller's address, such as `rateLimit()`, runs in tests as it does behind a listener. Pass `clientIp` to test another address. `inProcessClient` calls made outside a page render still carry no address.
219
+
220
+ The batteries template declares `@nifrajs/middleware`, and its tests pass on a fresh scaffold.
221
+
222
+ - e32268d: fix(web): portable public-directory serving and required endpoint secrets
223
+
224
+ `publicDir()` now serves files on Node and Deno as well as Bun, sets `content-type` from the file
225
+ extension, sends `x-content-type-options: nosniff`, and never serves dot-prefixed paths other than
226
+ `/.well-known/`.
227
+
228
+ `revalidateEndpoint()` and `previewEndpoint()` throw at construction when `secret` is empty or
229
+ missing. The ISR starter answers 404 on its revalidate route until `REVALIDATE_SECRET` is set.
230
+
231
+ ## 3.5.0
232
+
233
+ ## 3.4.0
234
+
235
+ ## 3.3.0
236
+
237
+ ## 3.2.0
238
+
239
+ ## 3.1.0
240
+
241
+ ## 3.0.0
242
+
243
+ ## 2.14.1
244
+
245
+ ## 2.14.0
246
+
247
+ ## 2.13.0
248
+
249
+ ## 2.12.1
250
+
251
+ ## 2.12.0
252
+
253
+ ### Patch Changes
254
+
255
+ - 7dd3f28: The project name is validated before any file is copied. It is substituted into generated deploy
256
+ scripts that a shell runs (`--name NAME`), so a directory name carrying shell metacharacters, spaces,
257
+ or a leading `-` used to reach those scripts intact. The accepted set is npm's own for an unscoped
258
+ name - letters, digits, `.`, `-`, `_`, starting with a letter or digit, up to 214 characters - so
259
+ ordinary names like `MyApp` still scaffold. Rejecting up front also means a bad name no longer leaves
260
+ a half-written project directory behind.
261
+
262
+ Every action in the generated CI workflows is pinned to a commit SHA rather than a mutable tag, so a
263
+ scaffolded repo starts on the same footing this one uses.
264
+
265
+ ## 2.11.0
266
+
267
+ ## 2.10.0
268
+
269
+ ## 2.9.1
270
+
271
+ ## 2.9.0
272
+
273
+ ## 2.8.2
274
+
275
+ ## 2.8.1
276
+
277
+ ## 2.8.0
278
+
279
+ ## 2.7.1
280
+
281
+ ## 2.7.0
282
+
283
+ ### Minor Changes
284
+
285
+ - 7fd0fc7: The batteries-included backend starter is now `--template batteries` (background jobs, TTL cache, blob storage, cursor pagination on top of the `api` template), and it ships in the published package. `--template fullstack` no longer exists; asking for it explains the split: `site` is the full-stack (frontend + backend) template, `batteries` is the API starter. The README now documents all four templates, and the scaffolded `backend.ts` states its root-path convention: the CLI resolves `backend.ts` (like `routes/`, `framework.ts`, `nifra.config.ts`) from the project root - only that entry file is pinned there; merged feature modules can live anywhere.
286
+
287
+ ## 2.6.1
288
+
289
+ ## 2.6.0
290
+
291
+ ## 2.5.0
292
+
293
+ ## 2.4.0
294
+
295
+ ### Patch Changes
296
+
297
+ - 06f4aaa: Build on `prepack` so the published package always ships its `dist` output - including the `create-nifra/agent-files` entry that `nifra init-agents` imports under Node.
298
+
299
+ ## 2.3.0
300
+
301
+ ### Minor Changes
302
+
303
+ - c8b79d7: `--db` scaffolds the data layer split by access, with a routes module that owns its own reach.
304
+
305
+ `db/read.ts` and `db/write.ts` sit in front of the connection, and `db/read-routes.ts` registers a
306
+ route importing only the read half. Merging it is one line.
307
+
308
+ The shape is not decoration. `nifra check` computes what a route can reach from the module that
309
+ registers it, following its imports; a route may not reach further than it declares, and a GET route
310
+ may not declare a domain write at all. A module holding both halves therefore has GET routes with no
311
+ legal declaration. Splitting reads from writes at the seam, and again at the routes, keeps every
312
+ route's declaration equal to its reach - which is what the `authenticated-write` rule needs in order to
313
+ mean anything.
314
+
315
+ The generated write example is commented rather than live, and says what happens when you uncomment
316
+ it: it fails `nifra assure` until authenticated, because the starter policy requires proof of who asked
317
+ before anything writes business state.
318
+
319
+ - 82b2053: Every template composes its routes from feature modules, and the effect provenance firewall ships ARMED.
320
+
321
+ `provenance.imports` now maps the database drivers and the `--db` seam, so a route that can reach a
322
+ database without declaring it fails `nifra check`. Combined with the `authenticated-write` rule, the
323
+ whole chain holds without anyone remembering anything:
324
+
325
+ - a route that writes and declares nothing fails the check;
326
+ - declare it, and the route fails assurance until it is authenticated;
327
+ - only an authenticated write ships.
328
+
329
+ Arming it required the app root to stop registering routes. Reach is computed from the module that
330
+ REGISTERS a route, following that module's imports, so a root that both composes and registers hands
331
+ every route in it the reach of everything merged there - and a GET route may not declare a domain write
332
+ at all, leaving those routes with no legal declaration and no fix but to move. So `src/app.ts` (api,
333
+ fullstack) and `backend.ts` (site, isr) merge and nothing else; the demo routes moved to `src/routes.ts`,
334
+ `src/notes.ts`, `counter.ts` and `page.ts` beside them. Exports are unchanged - `app`, `backend`, `queue`
335
+ and `wasIndexed` are all still imported from where they were.
336
+
337
+ That is the shape a feature should take anyway: a module owns its store, its adapters and the routes
338
+ over them, and a second feature with a database of its own gets its own file rather than a section.
339
+
340
+ - 26cec7d: A scaffolded app can run the gate it ships with.
341
+
342
+ Every template writes `nifra.assurance.ts` - an armed policy that refuses an unauthenticated write, a
343
+ mutation with no body schema, and a route reaching a database it never declared. None of them had a way
344
+ to run it: no `check` script anywhere, and the two backend templates did not even depend on
345
+ `@nifrajs/cli`, so `nifra check` was not on PATH without a manual install.
346
+
347
+ Every template now has `"check": "nifra check"` and the CLI in devDependencies, and the generated
348
+ GitHub workflow (`--ci github`) runs it before the build. A test asserts the invariant: a template that
349
+ ships an assurance config must ship both.
350
+
351
+ - 7f55876: Every template declares its capabilities, so a scaffolded app can reach L2 of `nifra levels`.
352
+
353
+ `nifra.assurance.ts` now carries a `capabilities` block defining `db.read` and `db.write`, and the
354
+ `authenticated-write` rule matches `{ access: "write", zone: "domain" }` instead of naming `db.write`.
355
+ Any write token added later - `payments.charge`, `orders.write` - is covered by that rule the day it is
356
+ declared, without editing the policy.
357
+
358
+ L2 was previously unreachable from a scaffold: the level requires a capability policy, no template
359
+ shipped one, and writing one from scratch was the only way up. It is now one `nifra capabilities
360
+ snapshot` away.
361
+
362
+ `provenance.imports` ships empty with a worked example and the one caveat that matters, which is that a
363
+ route's reach is computed from the module that REGISTERS it, following its imports. A module that
364
+ registers routes and imports a database gives every route in it database reach, and a GET route is
365
+ refused a domain write outright - so turning the import firewall on wants a root that is pure
366
+ composition, with effects owned by the modules underneath.
367
+
368
+ The `isr` template gains a `nifra.assurance.ts`; it had none, which capped it at L0.
369
+
370
+ ### Patch Changes
371
+
372
+ - cee03d7: The beacon wrapper stops breaking adapters that use `#private` fields, and the `--db` sample no longer
373
+ collides with a template route.
374
+
375
+ A `#` field's brand check is per-instance, so a Proxy that passes itself as the receiver throws
376
+ `Cannot access invalid private field`. Getters broke on both views and methods broke on the unbound
377
+ one - an adapter using `#` worked unwrapped and broke the moment you added beacons. Both proxies now
378
+ read against the target, and the unbound one binds methods to it.
379
+
380
+ The generated `db/read-routes.ts` registered `GET /notes`, which the fullstack template already
381
+ registers. `nifra check` associates modules with routes by matching the registered path across your
382
+ source, so that unmerged sample lent its `db.read` reach to a template route that never touches the
383
+ database - failing the check on a fresh `create-nifra --template fullstack --db …`. The sample uses
384
+ `/db/notes` now, and says why.
385
+
386
+ - 8807004: A scaffold's feature flags declare what they contribute instead of racing to write it.
387
+
388
+ `--db`, `--auth` and `--deploy` each reached into the parsed `package.json` and spread themselves over
389
+ it, in an order fixed by the line their handler sat on. Last writer won, silently. A preset that
390
+ shadowed the scaffold's own `check` script would have removed the assurance gate from every project
391
+ scaffolded with it, and nothing anywhere would have reported that.
392
+
393
+ No shipped preset does that - all six were checked - which is the moment to add the rail rather than
394
+ after someone adds the seventh. Each flag now states its contribution, and an undeclared collision is
395
+ an error naming both sides. Replacing a key stays possible where it is the point: `--deploy` repoints
396
+ the canonical `build` and `deploy` aliases at the chosen target, and says so.
397
+
398
+ - ea0a27f: A scaffolded project's `check` script runs the assurance gate it ships with, and the dev refusals cover
399
+ extensionless modules.
400
+
401
+ Every template ships an assurance config, and every template's `check` script ran `nifra check` only -
402
+ so the policy was shipped, documented, and never executed by the command a project actually runs in CI.
403
+ It now runs `nifra check && nifra assure`.
404
+
405
+ `nifra dev --bun` refuses `.server` and `.fn` modules because Bun's dev bundler takes no plugins and
406
+ would ship them whole. The refusal missed a module with no extension at all, which is the one shape a
407
+ directory import produces.
408
+
409
+ - 35af9fe: A site scaffold is composed from one model instead of copied from five directories.
410
+
411
+ `create-nifra --template site --framework <react|preact|vue|solid|svelte>` produces the same app it
412
+ always did. What changed is where it comes from: thirteen of a site's twenty-six files are identical
413
+ whatever you render with, eight are emitted from a framework model, and five are genuinely the
414
+ framework's own.
415
+
416
+ Five hand-maintained copies had already drifted, which is the argument for this rather than a
417
+ consequence of it. `.vercel` was excluded from four `tsconfig.json` files and not React's, though
418
+ `build-vercel.ts` writes there in all five. React's Vercel entry explains the Build Output API layout
419
+ it emits and the three copies made later had dropped that. Vue's feature-flag defines are explained in
420
+ its Cloudflare entry and nowhere else. Composing restores all of it.
421
+
422
+ The `@nifrajs/*` range a scaffold installs is now one constant. It used to be a regex sweep across
423
+ eight `package.json` files with nothing checking the result, and the release script's own comment
424
+ warned that a missed bump ships templates installing the previous release. A test now fails when that
425
+ constant drifts from the version being published.
426
+
427
+ What is NOT generated is deliberate. `nifra.config.ts` explains why Solid wants a `solid` resolve
428
+ condition and what `@preact/preset-vite` is; the routes are the app a reader opens first. That prose
429
+ stays in files you can read and edit, because moving it into TypeScript string literals would put it
430
+ somewhere strictly worse.
431
+
432
+ ## 2.2.0
433
+
434
+ ### Patch Changes
435
+
436
+ - 5f460db: Fix `nifra init-agents`, and explain rejected route parameters.
437
+
438
+ `nifra init-agents` failed for every installed user with `Cannot find module 'create-nifra/agent-files'`.
439
+ The `./agent-files` subpath resolves through the `bun` condition to `src/agent-files.ts`, which the
440
+ published tarball did not contain - the package shipped `dist` and the templates only. It now ships
441
+ that source file, so the subpath resolves from a real install. Reproduced from a packed 2.1.0 tarball
442
+ before and after.
443
+
444
+ An invalid route parameter now says why. Route grammar is per-segment - a segment is wholly static or
445
+ wholly a parameter - so everything after the colon is the name, and `/v/:id.json` asks for a parameter
446
+ literally called `id.json`. The previous `invalid parameter ":id.json"` read as a typo rather than a
447
+ rule; the message now names the limitation and gives both ways out (`/v/:id/json`, or capture the whole
448
+ segment and split it in the handler). Reserved names, an empty name, and a name that is invalid for
449
+ some other reason each get their own explanation instead of sharing one.
450
+
451
+ Note for anyone who has hit this: a segment that merely _contains_ a colon without starting with one,
452
+ such as `/a/pre-:id`, is a literal static segment and captures nothing. That is deliberate - a colon is
453
+ legal inside a URL path segment (`/v1/things:batchGet`) - and is now covered by a test that documents it.
454
+
455
+ ## 2.1.0
456
+
457
+ ## 2.0.0
458
+
459
+ ### Minor Changes
460
+
461
+ - 202e758: Schema-typed MCP tools, and the default template demonstrates the contract.
462
+
463
+ - `defineMcpTool` accepts `input`: a Standard Schema (nifra's `t`, zod, valibot, arktype, …) that
464
+ validates every call's arguments before the handler runs and types the handler's `args`. Invalid
465
+ arguments return an in-band `isError` result naming each issue, so a calling agent can correct
466
+ and retry. Schemas that carry a JSON Schema (nifra's `t` does) become the advertised
467
+ `inputSchema` automatically; an explicit `inputSchema` still overrides. The raw
468
+ `inputSchema`-only form keeps working unchanged.
469
+ - The `api` template's app now ships a `t`-validated route (body + response schemas) and its tests
470
+ drive the app through `testClient` - the contract-first pitch is visible in the first file a new
471
+ user opens, not just the docs.
472
+
473
+ ### Patch Changes
474
+
475
+ - ade0c7a: Add a curated `@nifrajs/core/server` entry for the common HTTP runtime and dedicated subpaths for
476
+ contracts, classification, cookies, logging, routing, Standard Schema, SEO, SSE, and webhooks. The
477
+ package root remains backwards compatible, while new scaffolds and first-party runtime packages avoid
478
+ eagerly parsing opt-in causality, invariant, manifest, reflection, capability, and assurance tooling.
479
+
480
+ ## 1.13.0
481
+
482
+ ## 1.12.0
483
+
484
+ ## 1.11.0
485
+
486
+ ### Patch Changes
487
+
488
+ - 80ed7b8: Fix fresh scaffolds failing their own `nifra check`, plus two scaffolding tooling defects:
489
+
490
+ - All counter demo templates (site ×5 frameworks, isr): demo loaders now narrow on `res.ok`
491
+ before reading `res.data` - un-narrowed `data` is `{}` under the typed client, so the old
492
+ `res.data?.count` was a compile error on a fresh scaffold.
493
+ - Demo backends now lock output shapes with `response` schemas (`t.object(...)`), per the
494
+ AGENTS.md doctrine the templates themselves ship.
495
+ - `template-isr` now includes `@nifrajs/cli` in devDependencies so a scaffolded app can run
496
+ its own `nifra check` done-gate.
497
+ - `--link` computes `file:` paths from realpaths - a symlinked segment (macOS tmpdir
498
+ `/var/folders` → `/private/var/folders`) previously skewed the relative path and broke
499
+ every linked dependency.
500
+ - New regression suite `test/scaffold-check.test.ts`: static tier always asserts the
501
+ template sources carry both contract fixes; live tier (`SMOKE_SCAFFOLD=1`) scaffolds for
502
+ real, installs published packages, and runs `nifra check`.
503
+
504
+ ## 1.10.0
505
+
506
+ ## 1.9.1
507
+
508
+ ## 1.9.0
509
+
510
+ ## 1.8.0
511
+
512
+ ## 1.7.0
513
+
514
+ ## 1.6.0
515
+
516
+ ## 1.5.0
517
+
518
+ ## 1.4.0
519
+
520
+ ## 1.3.1
521
+
522
+ ## 1.3.0
523
+
524
+ ## 1.2.2
525
+
526
+ ## 1.2.1
527
+
528
+ ## 1.2.0
529
+
530
+ ## 1.1.0
531
+
532
+ ### Minor Changes
533
+
534
+ - 9905f7f: feat(create-nifra): `--template fullstack` - a batteries-included starter
535
+
536
+ `bun create nifra my-app --template fullstack` scaffolds an app that already wires the packages a real
537
+ backend needs on top of core: cursor pagination (`t.pageQuery` / `t.paginated` / `paginate`), background
538
+ jobs (`@nifrajs/jobs`), a single-flight TTL cache (`@nifrajs/cache`), and blob storage (`@nifrajs/storage`)
539
+
540
+ - over a `notes` domain you swap for your DB. Ships with tests exercising each. Complements the existing
541
+ `api`, `site`, and `isr` templates.
542
+
543
+ ## 1.0.0
544
+
545
+ ### Patch Changes
546
+
547
+ - bb31594: Surface `@nifrajs/middleware` where agents look. The `nifra_context` conventions (and a scaffolded app's `AGENTS.md`) now carry a one-line pointer: cross-cutting concerns - rate limiting (`429`), CORS, security headers, body limits, auth, CSRF, IP restriction, caching, compression - are `app.use(...)` plugins in `@nifrajs/middleware`; call `nifra_docs("middleware")` for the full list. So an agent setting up routes finds the built-in middleware (it already shipped) without having to think to search for it.
548
+
549
+ ## 1.0.0-beta.4
550
+
551
+ ### Patch Changes
552
+
553
+ - 5181a35: Pin the generated MCP launch command to an exact `@nifrajs/cli` version (`bunx @nifrajs/cli@<version> mcp`) in `.mcp.json` / `.cursor/mcp.json` / `AGENTS.md`.
554
+
555
+ `bunx` keys its cache on the exact version spec. An unpinned spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every later spawn without re-checking the registry - so an editor that once launched an older `@nifrajs/cli` keeps respawning the stale binary even after a newer one is published, and the MCP server silently runs old code (e.g. without monorepo detection). Pinning the exact version makes the version part of the cache key, so each release fetches fresh. `scripts/version.ts` keeps the pin in lockstep with the published version. Re-run `nifra init-agents` to repin an existing app.
556
+
557
+ ## 1.0.0-beta.3
558
+
559
+ ## 0.1.0-beta.2
@@ -36,17 +36,22 @@ export declare const MCP_SERVER_COMMAND: "bunx";
36
36
  * (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
37
37
  * package's own `package.json`, so the read can't miss at install time.
38
38
  *
39
- * Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
40
- * An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
41
- * later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
42
- * keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
43
- * old code). Pinning the exact version makes the version part of the cache key, so each release fetches
44
- * fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
45
- * at its scaffold-time version until `nifra init-agents --sync-mcp` re-pins it to the nifra the project
46
- * installs - an acceptable, deterministic trade.
39
+ * Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
40
+ * `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
41
+ * (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
42
+ * and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
43
+ * always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
44
+ *
45
+ * A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
46
+ * unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
47
+ * re-checking the registry, so the server would silently run old code. The exact version is part of the
48
+ * cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
49
+ * nifra, or unpins it once the project declares the CLI.
47
50
  */
48
51
  export declare const MCP_CLI_VERSION: string;
49
52
  export declare const MCP_SERVER_ARGS: readonly [`@nifrajs/cli@${string}`, "mcp"];
53
+ /** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
54
+ export declare const MCP_SERVER_ARGS_INSTALLED: readonly ["@nifrajs/cli", "mcp"];
50
55
  /** The server entry registered under the `nifra` key in both Claude Code's and Cursor's MCP config. */
51
56
  export interface McpServerConfig {
52
57
  readonly command: string;
@@ -56,11 +61,14 @@ export interface McpServerConfig {
56
61
  export interface McpConfig {
57
62
  readonly mcpServers: Readonly<Record<string, McpServerConfig>>;
58
63
  }
59
- /** The one canonical MCP config object both registries serialize - the anti-drift seam. */
64
+ /** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
65
+ * `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
66
+ export declare function mcpConfig(pin?: string): McpConfig;
67
+ /** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
60
68
  export declare const MCP_CONFIG: McpConfig;
61
69
  /** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
62
70
  * Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
63
- export declare function mcpJson(): string;
71
+ export declare function mcpJson(pin?: string): string;
64
72
  /** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
65
73
  export declare function claudeMd(): string;
66
74
  /** `GEMINI.md`: Gemini CLI expands `@./AGENTS.md` imports in its context files. */
@@ -74,7 +82,7 @@ export declare function copilotInstructions(): string;
74
82
  * agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
75
83
  * Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
76
84
  */
77
- export declare function agentsMcpSection(): string;
85
+ export declare function agentsMcpSection(pin?: string): string;
78
86
  /**
79
87
  * The "## Project structure" section of a web app's `AGENTS.md`: the zones the build enforces. Shared so
80
88
  * a scaffolded app and one `nifra init-agents` retrofits teach the same rules.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-files.d.ts","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;kFACkF;AAClF,eAAO,MAAM,kBAAkB,EAAG,MAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,EAAE,MAAqB,CAAA;AAenD,eAAO,MAAM,eAAe,4CAAsD,CAAA;AAElF,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAA;CAC/D;AAED,2FAA2F;AAC3F,eAAO,MAAM,UAAU,EAAE,SAIxB,CAAA;AAED;qFACqF;AACrF,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAKD,yEAAyE;AACzE,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,gGAAgG;AAChG,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAczC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA+B/C;AAED,iGAAiG;AACjG,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD;4GAC4G;AAC5G,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;sGACsG;AACtG,eAAO,MAAM,aAAa,cAAc,CAAA;AACxC,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AACtD,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,gBAAgB,4BAA4B,CAAA;AACzD,eAAO,MAAM,yBAAyB,oCAAoC,CAAA;AAC1E,eAAO,MAAM,cAAc,cAAc,CAAA;AAEzC,wFAAwF;AACxF,eAAO,MAAM,cAAc,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,EAM5F,CAAA"}
1
+ {"version":3,"file":"agent-files.d.ts","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;kFACkF;AAClF,eAAO,MAAM,kBAAkB,EAAG,MAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,eAAe,EAAE,MAAqB,CAAA;AAenD,eAAO,MAAM,eAAe,4CAAsD,CAAA;AAElF,wGAAwG;AACxG,eAAO,MAAM,yBAAyB,kCAAmC,CAAA;AAEzE,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAA;CAC/D;AAED;0FAC0F;AAC1F,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAGjD;AAED,gGAAgG;AAChG,eAAO,MAAM,UAAU,EAAE,SAAuB,CAAA;AAEhD;qFACqF;AACrF,wBAAgB,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAKD,yEAAyE;AACzE,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,gGAAgG;AAChG,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAerD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA+B/C;AAED,iGAAiG;AACjG,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD;4GAC4G;AAC5G,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;sGACsG;AACtG,eAAO,MAAM,aAAa,cAAc,CAAA;AACxC,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AACtD,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,gBAAgB,4BAA4B,CAAA;AACzD,eAAO,MAAM,yBAAyB,oCAAoC,CAAA;AAC1E,eAAO,MAAM,cAAc,cAAc,CAAA;AAEzC,wFAAwF;AACxF,eAAO,MAAM,cAAc,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,EAM5F,CAAA"}
@@ -37,14 +37,17 @@ export const MCP_SERVER_COMMAND = "bunx";
37
37
  * (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
38
38
  * package's own `package.json`, so the read can't miss at install time.
39
39
  *
40
- * Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
41
- * An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
42
- * later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
43
- * keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
44
- * old code). Pinning the exact version makes the version part of the cache key, so each release fetches
45
- * fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
46
- * at its scaffold-time version until `nifra init-agents --sync-mcp` re-pins it to the nifra the project
47
- * installs - an acceptable, deterministic trade.
40
+ * Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
41
+ * `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
42
+ * (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
43
+ * and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
44
+ * always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
45
+ *
46
+ * A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
47
+ * unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
48
+ * re-checking the registry, so the server would silently run old code. The exact version is part of the
49
+ * cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
50
+ * nifra, or unpins it once the project declares the CLI.
48
51
  */
49
52
  export const MCP_CLI_VERSION = ownVersion();
50
53
  function ownVersion() {
@@ -57,16 +60,20 @@ function ownVersion() {
57
60
  throw new Error("[nifra] agent-files: this package's package.json has no version");
58
61
  }
59
62
  export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"];
60
- /** The one canonical MCP config object both registries serialize - the anti-drift seam. */
61
- export const MCP_CONFIG = {
62
- mcpServers: {
63
- nifra: { command: MCP_SERVER_COMMAND, args: [...MCP_SERVER_ARGS] },
64
- },
65
- };
63
+ /** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
64
+ export const MCP_SERVER_ARGS_INSTALLED = ["@nifrajs/cli", "mcp"];
65
+ /** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
66
+ * `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
67
+ export function mcpConfig(pin) {
68
+ const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"];
69
+ return { mcpServers: { nifra: { command: MCP_SERVER_COMMAND, args: [...args] } } };
70
+ }
71
+ /** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
72
+ export const MCP_CONFIG = mcpConfig();
66
73
  /** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
67
74
  * Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
68
- export function mcpJson() {
69
- return `${JSON.stringify(MCP_CONFIG, null, 2)}\n`;
75
+ export function mcpJson(pin) {
76
+ return `${JSON.stringify(mcpConfig(pin), null, 2)}\n`;
70
77
  }
71
78
  const POINTER_INTRO = "This project's agent guidance - commands, the project structure, the rules, and the nifra MCP server - lives in AGENTS.md, the single source every agent reads.";
72
79
  /** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
@@ -90,10 +97,11 @@ export function copilotInstructions() {
90
97
  * agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
91
98
  * Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
92
99
  */
93
- export function agentsMcpSection() {
100
+ export function agentsMcpSection(pin) {
101
+ const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"];
94
102
  return `## MCP server
95
103
 
96
- This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`. It's registered for
104
+ This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${args.join(" ")}\`. It's registered for
97
105
  Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
98
106
  at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
99
107
  project + the installed version, so they beat training-data recall:
@@ -1 +1 @@
1
- {"version":3,"file":"agent-files.js","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-files.js","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC;kFACkF;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,UAAU,EAAE,CAAA;AAEnD,SAAS,UAAU;IACjB,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAClC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAClE,CAAA;IACD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,SAAS,IAAI,QAAQ;QACrB,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ;QAEpC,OAAO,QAAQ,CAAC,OAAO,CAAA;IACzB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;AACpF,CAAC;AACD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,gBAAgB,eAAe,EAAE,EAAE,KAAK,CAAU,CAAA;AAElF,wGAAwG;AACxG,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,cAAc,EAAE,KAAK,CAAU,CAAA;AAazE;0FAC0F;AAC1F,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,EAAE,EAAE,KAAK,CAAC,CAAA;IAC3F,OAAO,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAA;AACpF,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,UAAU,GAAc,SAAS,EAAE,CAAA;AAEhD;qFACqF;AACrF,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAA;AACvD,CAAC;AAED,MAAM,aAAa,GACjB,iKAAiK,CAAA;AAEnK,yEAAyE;AACzE,MAAM,UAAU,QAAQ;IACtB,OAAO,kBAAkB,aAAa,kBAAkB,CAAA;AAC1D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,QAAQ;IACtB,OAAO,kBAAkB,aAAa,oBAAoB,CAAA;AAC5D,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,UAAU;IACxB,OAAO,uEAAuE,aAAa,gDAAgD,CAAA;AAC7I,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB;IACjC,OAAO,GAAG,aAAa,+EAA+E,CAAA;AACxG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,EAAE,EAAE,KAAK,CAAC,CAAA;IAC3F,OAAO;;+DAEsD,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;2FAUR,CAAA;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,GAAG,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FA6BkE,CAAA;AAC/F,CAAC;AAED,iGAAiG;AACjG,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAA;AAWvD;sGACsG;AACtG,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAA;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAA;AACtD,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;AACzD,MAAM,CAAC,MAAM,yBAAyB,GAAG,iCAAiC,CAAA;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AAEzC,wFAAwF;AACxF,MAAM,CAAC,MAAM,cAAc,GACzB;IACE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE;IAC3C,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE;IAC3C,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE;IAC/C,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,mBAAmB,EAAE;CAClE,CAAA"}
package/dist/auth.js CHANGED
@@ -34,7 +34,7 @@ export const AUTH_PRESETS = {
34
34
  label: "better-auth",
35
35
  note: "email/password + sessions, mounted at /api/auth/* - add OAuth providers in backend/auth.ts.",
36
36
  // @nifrajs/better-auth mounts it; better-auth is the implementation (peer-installed alongside).
37
- deps: { "@nifrajs/better-auth": "^5.1.0", "better-auth": "^1.2.0" },
37
+ deps: { "@nifrajs/better-auth": "^5.2.0", "better-auth": "^1.2.0" },
38
38
  },
39
39
  };
40
40
  /** The better-auth `database` adapter expression + its import line, for the scaffolded DB's ORM. */
@@ -37,7 +37,7 @@ export interface FrameworkSpec {
37
37
  * ships templates installing the PREVIOUS release. `scaffold-composition.test.ts` now fails when this
38
38
  * drifts from what core is publishing, so the footgun is a red test rather than a silent regression.
39
39
  */
40
- export declare const NIFRA_DEP_RANGE = "^5.1.0";
40
+ export declare const NIFRA_DEP_RANGE = "^5.2.0";
41
41
  /**
42
42
  * React is first because it is the default (`--framework` omitted scaffolds it), and because the
43
43
  * other four are most easily read as deltas from it.
@@ -15,7 +15,7 @@
15
15
  * ships templates installing the PREVIOUS release. `scaffold-composition.test.ts` now fails when this
16
16
  * drifts from what core is publishing, so the footgun is a red test rather than a silent regression.
17
17
  */
18
- export const NIFRA_DEP_RANGE = "^5.1.0";
18
+ export const NIFRA_DEP_RANGE = "^5.2.0";
19
19
  /**
20
20
  * React is first because it is the default (`--framework` omitted scaffolds it), and because the
21
21
  * other four are most easily read as deltas from it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nifra",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Scaffold a new nifra app - `bun create nifra <dir>`.",
5
5
  "keywords": [
6
6
  "bun",
@@ -40,7 +40,8 @@
40
40
  "template-site-solid",
41
41
  "template-site-svelte",
42
42
  "template-isr",
43
- "template-batteries"
43
+ "template-batteries",
44
+ "CHANGELOG.md"
44
45
  ],
45
46
  "sideEffects": false,
46
47
  "scripts": {
@@ -40,14 +40,17 @@ export const MCP_SERVER_COMMAND = "bunx" as const
40
40
  * (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
41
41
  * package's own `package.json`, so the read can't miss at install time.
42
42
  *
43
- * Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
44
- * An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
45
- * later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
46
- * keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
47
- * old code). Pinning the exact version makes the version part of the cache key, so each release fetches
48
- * fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
49
- * at its scaffold-time version until `nifra init-agents --sync-mcp` re-pins it to the nifra the project
50
- * installs - an acceptable, deterministic trade.
43
+ * Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
44
+ * `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
45
+ * (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
46
+ * and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
47
+ * always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
48
+ *
49
+ * A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
50
+ * unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
51
+ * re-checking the registry, so the server would silently run old code. The exact version is part of the
52
+ * cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
53
+ * nifra, or unpins it once the project declares the CLI.
51
54
  */
52
55
  export const MCP_CLI_VERSION: string = ownVersion()
53
56
 
@@ -66,6 +69,9 @@ function ownVersion(): string {
66
69
  }
67
70
  export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"] as const
68
71
 
72
+ /** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
73
+ export const MCP_SERVER_ARGS_INSTALLED = ["@nifrajs/cli", "mcp"] as const
74
+
69
75
  /** The server entry registered under the `nifra` key in both Claude Code's and Cursor's MCP config. */
70
76
  export interface McpServerConfig {
71
77
  readonly command: string
@@ -77,17 +83,20 @@ export interface McpConfig {
77
83
  readonly mcpServers: Readonly<Record<string, McpServerConfig>>
78
84
  }
79
85
 
80
- /** The one canonical MCP config object both registries serialize - the anti-drift seam. */
81
- export const MCP_CONFIG: McpConfig = {
82
- mcpServers: {
83
- nifra: { command: MCP_SERVER_COMMAND, args: [...MCP_SERVER_ARGS] },
84
- },
86
+ /** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
87
+ * `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
88
+ export function mcpConfig(pin?: string): McpConfig {
89
+ const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"]
90
+ return { mcpServers: { nifra: { command: MCP_SERVER_COMMAND, args: [...args] } } }
85
91
  }
86
92
 
93
+ /** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
94
+ export const MCP_CONFIG: McpConfig = mcpConfig()
95
+
87
96
  /** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
88
97
  * Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
89
- export function mcpJson(): string {
90
- return `${JSON.stringify(MCP_CONFIG, null, 2)}\n`
98
+ export function mcpJson(pin?: string): string {
99
+ return `${JSON.stringify(mcpConfig(pin), null, 2)}\n`
91
100
  }
92
101
 
93
102
  const POINTER_INTRO =
@@ -118,10 +127,11 @@ export function copilotInstructions(): string {
118
127
  * agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
119
128
  * Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
120
129
  */
121
- export function agentsMcpSection(): string {
130
+ export function agentsMcpSection(pin?: string): string {
131
+ const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"]
122
132
  return `## MCP server
123
133
 
124
- This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`. It's registered for
134
+ This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${args.join(" ")}\`. It's registered for
125
135
  Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
126
136
  at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
127
137
  project + the installed version, so they beat training-data recall:
@@ -80,5 +80,16 @@ export default defineAssuranceConfig({
80
80
  // an authenticated one rather than weakening the whole class.
81
81
  { name: "read", match: { methods: ["GET"] }, require: [] },
82
82
  ],
83
+ // Checked on every route it matches, whichever rule above owns the route: a route that declares a
84
+ // `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
85
+ // before its routes, so a route the contract does not cover fails here instead of sending fields
86
+ // its schema never declared.
87
+ baseline: [
88
+ {
89
+ name: "response-contract",
90
+ match: { hasResponse: true },
91
+ require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
92
+ },
93
+ ],
83
94
  },
84
95
  })
@@ -11,13 +11,13 @@
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@nifrajs/client": "^5.1.0",
15
- "@nifrajs/core": "^5.1.0",
16
- "@nifrajs/middleware": "^5.1.0",
17
- "@nifrajs/schema": "^5.1.0"
14
+ "@nifrajs/client": "^5.2.0",
15
+ "@nifrajs/core": "^5.2.0",
16
+ "@nifrajs/middleware": "^5.2.0",
17
+ "@nifrajs/schema": "^5.2.0"
18
18
  },
19
19
  "devDependencies": {
20
- "@nifrajs/cli": "^5.1.0",
20
+ "@nifrajs/cli": "^5.2.0",
21
21
  "@types/bun": "latest",
22
22
  "typescript": "^6.0.3"
23
23
  }
@@ -86,5 +86,16 @@ export default defineAssuranceConfig({
86
86
  // an authenticated one rather than weakening the whole class.
87
87
  { name: "read", match: { methods: ["GET"] }, require: [] },
88
88
  ],
89
+ // Checked on every route it matches, whichever rule above owns the route: a route that declares a
90
+ // `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
91
+ // before its routes, so a route the contract does not cover fails here instead of sending fields
92
+ // its schema never declared.
93
+ baseline: [
94
+ {
95
+ name: "response-contract",
96
+ match: { hasResponse: true },
97
+ require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
98
+ },
99
+ ],
89
100
  },
90
101
  })
@@ -11,16 +11,16 @@
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@nifrajs/cache": "^5.1.0",
15
- "@nifrajs/client": "^5.1.0",
16
- "@nifrajs/core": "^5.1.0",
17
- "@nifrajs/jobs": "^5.1.0",
18
- "@nifrajs/middleware": "^5.1.0",
19
- "@nifrajs/schema": "^5.1.0",
20
- "@nifrajs/storage": "^5.1.0"
14
+ "@nifrajs/cache": "^5.2.0",
15
+ "@nifrajs/client": "^5.2.0",
16
+ "@nifrajs/core": "^5.2.0",
17
+ "@nifrajs/jobs": "^5.2.0",
18
+ "@nifrajs/middleware": "^5.2.0",
19
+ "@nifrajs/schema": "^5.2.0",
20
+ "@nifrajs/storage": "^5.2.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@nifrajs/cli": "^5.1.0",
23
+ "@nifrajs/cli": "^5.2.0",
24
24
  "@types/bun": "latest",
25
25
  "typescript": "^6.0.3"
26
26
  }
@@ -81,5 +81,16 @@ export default defineAssuranceConfig({
81
81
  // an authenticated one rather than weakening the whole class.
82
82
  { name: "read", match: { methods: ["GET"] }, require: [] },
83
83
  ],
84
+ // Checked on every route it matches, whichever rule above owns the route: a route that declares a
85
+ // `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
86
+ // before its routes, so a route the contract does not cover fails here instead of sending fields
87
+ // its schema never declared.
88
+ baseline: [
89
+ {
90
+ name: "response-contract",
91
+ match: { hasResponse: true },
92
+ require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
93
+ },
94
+ ],
84
95
  },
85
96
  })
@@ -10,17 +10,17 @@
10
10
  "check": "nifra check && nifra assure"
11
11
  },
12
12
  "dependencies": {
13
- "@nifrajs/client": "^5.1.0",
14
- "@nifrajs/core": "^5.1.0",
15
- "@nifrajs/middleware": "^5.1.0",
16
- "@nifrajs/schema": "^5.1.0",
17
- "@nifrajs/web": "^5.1.0",
18
- "@nifrajs/web-react": "^5.1.0",
13
+ "@nifrajs/client": "^5.2.0",
14
+ "@nifrajs/core": "^5.2.0",
15
+ "@nifrajs/middleware": "^5.2.0",
16
+ "@nifrajs/schema": "^5.2.0",
17
+ "@nifrajs/web": "^5.2.0",
18
+ "@nifrajs/web-react": "^5.2.0",
19
19
  "react": "^19.0.0",
20
20
  "react-dom": "^19.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@nifrajs/cli": "^5.1.0",
23
+ "@nifrajs/cli": "^5.2.0",
24
24
  "@types/bun": "^1.4.2",
25
25
  "@types/react": "^19.0.0",
26
26
  "@types/react-dom": "^19.0.0",
@@ -83,5 +83,16 @@ export default defineAssuranceConfig({
83
83
  // an authenticated one rather than weakening the whole class.
84
84
  { name: "read", match: { methods: ["GET"] }, require: [] },
85
85
  ],
86
+ // Checked on every route it matches, whichever rule above owns the route: a route that declares a
87
+ // `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
88
+ // before its routes, so a route the contract does not cover fails here instead of sending fields
89
+ // its schema never declared.
90
+ baseline: [
91
+ {
92
+ name: "response-contract",
93
+ match: { hasResponse: true },
94
+ require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
95
+ },
96
+ ],
86
97
  },
87
98
  })