From 12b60e3eca455b0ed54f1a7d643dca69259ffae4 Mon Sep 17 00:00:00 2001 From: bodurkalukasz Date: Thu, 3 Sep 2026 22:58:55 +0200 Subject: [PATCH 1/2] fix(stack-contract,adoption-path): record the provenance commit, stop counting a monorepo's test config as drift Closes #5, both halves, one of them by declining the fix the issue proposed. `stack.manifest.json` gains `provenanceCommit`, shipping `null`, written into the adopting repository's copy by the run that applies or updates this layer. The core's manifest has carried the equivalent since standard ADR-052, and the asymmetry showed the first time a real repository updated: the core half of the delta is an exact diff between two commits, the stack half had a version string. Most entries here are `merge` class and carry no hash, so that half was not coarse - it was unenumerated, and a release's additions could go unapplied with the drift number still reading zero. The field records a commit of THIS repository: the layers are linked by a registry pointer rather than a version range, so neither commit follows from the other. `vitest.config.ts` becomes optional. What DECISIONS#9 asks for is the tier split, not a path. A workspace whose packages each configure the tiers has them where the tests run, and a root projects config on top of those runs every suite twice - so for that shape the root file is wrong rather than missing. Required, the entry manufactured drift for a repository that had done nothing wrong, and the exception recording it sat beside the exceptions that mean something. What the schema cannot say - this path, or one per workspace package - is the core's expressiveness to extend, and `altPaths` takes literal paths rather than globs; filed as SELF-11 instead of worked around here. `biome.json` stays required, against what the issue asked for. The adaptation path prescribes a two-step Biome migration, so a repository following it correctly has no `biome.json` between the steps - and that is a real, unfinished deviation from DECISIONS#3 which should read as one. Making the entry optional would let a repository that will never migrate look finished. `ADAPTING.md` now gives the exception to record, with the migration step in its reason, and says it comes out when step 2 lands. That is the difference between the two entries: one decision is satisfied elsewhere, the other is not yet taken. `SHIPPED.md` says what "required" means for a repository mid-migration, since that is where the word was being read as a demand rather than as what the verifier counts. The manifest's own `$about` cites the core's records as `standard ADR-NNN`, the spelling core 1.0.11 adopted for shipped files after issue #133 - this file ships into repositories that have their own index. --- ADAPTING.md | 12 ++++++++++-- CHANGELOG.md | 27 +++++++++++++++++++++++++++ SHIPPED.md | 7 ++++++- backlog.md | 1 + specs/adoption-path/spec.md | 6 ++++++ specs/stack-contract/spec.md | 23 +++++++++++++++++++++++ stack.manifest.json | 7 ++++--- 7 files changed, 77 insertions(+), 6 deletions(-) diff --git a/ADAPTING.md b/ADAPTING.md index 9693460..59ecfde 100644 --- a/ADAPTING.md +++ b/ADAPTING.md @@ -47,12 +47,20 @@ a declared key). A guard script itself cannot be excepted by presence - that wou the check rather than waive it; a `kind: "content"` exception on a guard is allowed, since the guard still runs and must pass. +**Where your copy came from:** when the run finishes, write this repository's commit SHA +into your `stack.manifest.json` as `provenanceCommit`. `version` says which release you took; +the commit says which tree, and that is what the next update diffs against. Without it the +stack half of an update has nothing to compare but a version string, and every `merge` entry +- which is most of them - goes unenumerated. The core manifest carries the same field for its +own layer and the two move independently: updating one stack never implies the core or +another stack. + | Entry | The repo probably has | The move | |---|---|---| -| `biome.json` | ESLint + Prettier | Two-step, never big-bang: install Biome alongside, port rule intents (`biome migrate eslint --write` gets most), run both until the diff stabilizes, then remove ESLint/Prettier in their own PR. Keep Prettier only if SCSS stays (Biome does not format SCSS - that is the pick's own escape hatch). | +| `biome.json` | ESLint + Prettier | Two-step, never big-bang: install Biome alongside, port rule intents (`biome migrate eslint --write` gets most), run both until the diff stabilizes, then remove ESLint/Prettier in their own PR. Keep Prettier only if SCSS stays (Biome does not format SCSS - that is the pick's own escape hatch). The entry is required and the migration is deliberately not: while you are between the two steps you have no `biome.json`, so record the exception with the step in its reason - `"reason": "ESLint + Prettier; Biome migration step 1 of 2, see ADAPTING.md"` - and delete it when step 2 lands. The exception is how the unfinished migration stays visible; making the entry optional would hide it. | | `pnpm-workspace.yaml` | npm, yarn, bun, or Deno, maybe no workspace | `pnpm import` converts the existing lockfile where that source tool is actually supported - see the by-source-tool breakdown below, it is not all four. Workspaces map 1:1. The policy block (release-age cooldown, allowed build scripts) is the point of the entry - merge it even if the workspace globs differ. Without a migration path, or without migration appetite: record the exception (see above); the cooldown has no equivalent outside pnpm's own config, name that trade-off. | | `tsconfig.base.json` | a looser tsconfig | Stage strictness: `strict` first, then `noUncheckedIndexedAccess` and friends one flag per PR - each flag surfaces real findings; a hundred errors at once teaches nothing. Extend, do not replace: their paths/aliases stay theirs. | -| `vitest.config.ts` | Jest, or tests mixed in one pile | Vitest reads most Jest suites as-is (`vitest run` first, fix the stragglers). The entry's real content is the tier split - unit vs integration as separate projects; introduce the naming convention (`*.test.ts` / `*.integration.test.ts`) before touching any test body. | +| `vitest.config.ts` | Jest, or tests mixed in one pile | Vitest reads most Jest suites as-is (`vitest run` first, fix the stragglers). The entry's real content is the tier split - unit vs integration as separate projects; introduce the naming convention (`*.test.ts` / `*.integration.test.ts`) before touching any test body. A workspace whose packages each carry a config already has the tiers where they run - configure them there and leave the root without the file, rather than adding a projects config that runs every suite a second time. That is why this entry is optional: for that shape the root file is wrong, not missing. | | `playwright.config.ts` | Cypress, or nothing | Nothing: start with the starter's two journeys (auth, home) adapted to their routes. Cypress: do not port wholesale - write the 3-5 journeys that matter in Playwright, retire Cypress when they cover the old suite's intent. | | `docker-compose.test.yml` | mocks, or a shared dev database | The move is philosophical (DECISIONS: real dependencies, not mocks) - land it with the integration tier, non-default ports so it never collides with their dev stack. | | `perf/` + `scripts/perf-budget.sh` | nothing, or another perf tool | Run it once against the built app before changing a number: the first run writes the baseline, and a baseline nobody argued about is worth more than a target somebody guessed. It is deliberately not a CI gate - a budget that blocks a merge on day one gets disabled by the end of the week. Adapt the script's seed/build/start hooks to their stack, keep the committed history so a regression is a diff rather than an opinion. If a perf tool already exists, keep one, not both. | diff --git a/CHANGELOG.md b/CHANGELOG.md index 605cc1a..95b2eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,33 @@ exists to make unnecessary. ## Unreleased +### An adopter's copy of this manifest says which tree it came from (2026-09-04) + +`stack.manifest.json` gains `provenanceCommit`, shipping `null`, written into the adopting +repository's copy by the run that applies or updates this layer. The core's manifest has +carried the equivalent since standard ADR-052, and the difference showed the first time a real +repository updated: the core half of the delta is an exact diff between two commits, while the +stack half had a version string and nothing else to compare. Most entries here are `merge` +class and carry no hash, so that half was not merely coarse - it was unenumerated, and a +release's additions could go unapplied with the drift number still reading zero. The two +layers advance on separate clocks, so the field records a commit of this repository and never +one of the core's. + +`vitest.config.ts` becomes optional. The requirement behind it is the tier split, not the +path: a workspace whose packages each configure the tiers satisfies DECISIONS#9 where the +tests actually run, and a root projects config on top of those runs every suite twice. Marked +required, the entry manufactured drift for a repository that had done nothing wrong, and the +exception recording it sat beside the exceptions that mean something. What the schema cannot +yet say - this path, or one per workspace package - is the core's to fix, and it is filed as +SELF-11 rather than papered over here. + +`biome.json` deliberately stays required. The adaptation path prescribes a two-step Biome +migration, so a repository following it correctly has no `biome.json` between the steps - +which is a real, unfinished deviation from DECISIONS#3 and should read as one. `ADAPTING.md` +now gives the exception to record, with the migration step in its reason, and says it comes +out when step 2 lands. That is the difference between the two entries: one is a decision +satisfied elsewhere, the other is a decision not yet taken. + ### The stack layer became an adopter of the standard it belongs to (2026-08-06) This repository carried a `stack.manifest.json` and nothing else the core standard asks for. diff --git a/SHIPPED.md b/SHIPPED.md index 8824c95..3d7cccd 100644 --- a/SHIPPED.md +++ b/SHIPPED.md @@ -9,13 +9,18 @@ decision; changing it is deviating, and deviating is fine if you record it. **Me yours** means the substance is a block inside the file, not the whole file: your globs, your job names, your paths stay yours. +**Required** means the verifier counts its absence as drift - not that you must take it +today. A repository part-way through one of the migrations in [adapting](ADAPTING.md) +records the entry as an exception naming the step it is on, and deletes the exception when +the step lands; that is the migration's visible state, not a way around the check. + | Path | What it is | How to take it | |---|---|---| | `.nvmrc` | the Node version pin - one runtime, no drift between machines | copy as-is · required | | `biome.json` | lint + format, one tool - the reference copy encodes the strictness the picks assume | merge into yours · required | | `tsconfig.base.json` | strict TypeScript base every package extends | merge into yours · required | | `pnpm-workspace.yaml` | workspace globs + the supply-chain policy (release-age cooldown, allowed build scripts, save-exact) - the policy block is the point | merge into yours · required | -| `vitest.config.ts` | unit and integration tiers as separate Vitest projects | merge into yours · required | +| `vitest.config.ts` | unit and integration tiers as separate Vitest projects - a workspace may configure them per package instead | merge into yours · optional | | `playwright.config.ts` | the e2e tier - real browser journeys against the running app | merge into yours · optional | | `docker-compose.test.yml` | ephemeral real dependencies for integration/e2e - not mocks | copy as-is · optional | | `perf/` | local Lighthouse perf-budget runner - report-only, committed baseline + dated history, no external upload; edit config.mjs routes | copy as-is · optional | diff --git a/backlog.md b/backlog.md index e9619e1..d074854 100644 --- a/backlog.md +++ b/backlog.md @@ -19,6 +19,7 @@ Technology picks do not belong here. A pick that should change is a change to | SELF-8 | Decide this repository's manifest profile - `core` or `scale` | The elicitation ledger's `green.stack` point asked which profile applies. The first answer given was `scale`; raised back was the actual cost - `self-verify --profile scale` checks roughly nine more entries than `core`, unverified whether this repository already satisfies them - and whether to switch now, expanding the update-to-1.0.6 PR, or defer. No reconfirmation came either way, so `standard.manifest.json` keeps `"profile": "core"` unchanged rather than silently picking a side | a deliberate answer, reached on its own rather than folded into an unrelated PR: either the profile moves to `scale` with the new entries measured and satisfied, or `core` is confirmed as right for this repository's actual reach and the reasoning is recorded | todo | | SELF-9 | Confirm backlog row attribution style | The elicitation ledger's `adopt.backlog` point asks whether rows should name a role, a person, or nothing. The question that got answered instead was a different one - where the backlog physically lives, not how a row is attributed - so this repository's rows keep the pattern already in use (no owner/role column, matching every row here) as a default, not a confirmed choice | either the current pattern is confirmed as intentional, or rows start naming a role per the standard's own recommended default | todo | | SELF-10 | Reconfirm scope and acceptance criteria for `specs/stack-contract/spec.md` with a human | The elicitation ledger's `spec.scope` and `spec.acceptance` points were reached when this update added the spec's missing Interface contracts section - both sections' existing content was carried forward unchanged, not re-decided, so the provenance is recorded as `provisional` rather than `human` | a human either confirms the existing Scope and Acceptance criteria sections still hold, or revises them, and the ledger rows move to `human` | todo | +| SELF-11 | Give the manifest a way to say "this path, or one per workspace package" | `vitest.config.ts` is now optional because the schema cannot express where a monorepo really configures the test tiers: `required: true` manufactured drift for a repository that satisfies DECISIONS#9 in nineteen places instead of one, and `altPaths` takes literal paths, not globs. Optional is the honest reading of a check the schema cannot make, but it also stops asking - a repository with no tiers anywhere now passes the same way. The expressiveness is the core's (`scripts/self-verify.mjs` and the manifest schema), so this is a request to make there, not a change to make here | the core's manifest can express a per-package satisfying path, or records that it will not; if it can, this entry goes back to required with that spelling | todo | ## Alignment scope diff --git a/specs/adoption-path/spec.md b/specs/adoption-path/spec.md index e8dcb17..62889ac 100644 --- a/specs/adoption-path/spec.md +++ b/specs/adoption-path/spec.md @@ -54,6 +54,12 @@ brownfield, and what happens after each. and brownfield walks. - Package-manager migration covers the routes a real repository arrives on, and says plainly where a route does not exist rather than implying one does. +- The run ends by recording where the copy came from: this repository's commit, written into + the adopting repository's copy of the stack manifest. A path that is walked but not recorded + cannot be walked again from where it stopped. +- An entry the repository is part-way to is recorded as an exception naming the step, and the + note says when the exception comes out. A migration with two steps has a state between them, + and that state is the exception rather than a silent absence. ## Open questions diff --git a/specs/stack-contract/spec.md b/specs/stack-contract/spec.md index ad9be98..90a2f58 100644 --- a/specs/stack-contract/spec.md +++ b/specs/stack-contract/spec.md @@ -34,6 +34,14 @@ and what each check actually needs to run. - **`requiredKeys`** - what a `merge`-class entry must still contain after an adopter merges it into their own file. Presence is worthless for an entry whose whole point is a block inside a file the adopter also edits. +- **Provenance is per layer** - the adopter's copy of this manifest records which commit of + this repository it came from, and the core's manifest records the same for the core. A stack + is linked by a registry pointer rather than a version range, so the two advance on separate + clocks and neither commit can be derived from the other. +- **Required is about the check, not the calendar** - an entry marked required counts as drift + while it is absent, which is what makes a half-finished migration visible; an entry marked + optional is one the decision behind it can be satisfied without, at a path this schema cannot + name. - **The guard** - a live command whose verdict is part of the drift number, as distinct from a declarative entry describing layout. @@ -45,6 +53,7 @@ and what each check actually needs to run. |---|---|---| | `technology` | string | the single technology this stack covers; one stack per technology is policy | | `version` | string | this stack's own version, independent of the core's | +| `provenanceCommit` | string \| null | the commit of THIS repository an adopter's copy last aligned to; ships `null` and is written into the adopter's copy by the run that applies or updates the layer. What an update's delta is computed from - `version` names the release, this names the tree | | `registry` | string | back-pointer to the core's `stacks.json`, which is what confers official status | | `files` | array | entries in the core manifest's file-entry schema (`path`, `adapt`, `required`, `purpose`, and `requiredKeys` where the entry is a merge) | | `guards` | array | entries in the core manifest's guard schema (`id`, `run`) | @@ -74,6 +83,14 @@ behind the one-drift-number promise in Success metric above. - A guard's failure is drift. It cannot be waived by a manifest exception - waiving a live check removes it rather than recording a deviation from it. - The manifest carries no version range against the core. +- `provenanceCommit` ships `null` and is never a commit of the core repository. A release + that changes shipped content is what makes a recorded commit stale; nothing in this + repository rewrites an adopter's copy. +- An entry is `required` only where its absence is itself the deviation. Where a decision can + be satisfied at a path the schema cannot express - one config per workspace package rather + than one at the root - the entry is optional, because a required entry there manufactures + drift for a repository that did nothing wrong, and an exception recording it buries the + cases that mean something. ## Acceptance criteria @@ -84,6 +101,12 @@ behind the one-drift-number promise in Success metric above. passing on the file's existence. - Given a manifest exception targeting a guard, when the verifier runs, then the exception is rejected as drift rather than silently honoured. +- Given an adopting repository whose copy of this manifest records a `provenanceCommit`, when + the next update of this layer runs, then the delta is computed from that commit and every + entry is enumerated, rather than from the version string with the `merge` entries unlisted. +- Given a workspace that configures the test tiers in each package and carries no root + `vitest.config.ts`, when the verifier runs, then that entry is reported as an unmet optional + rather than as drift, and the repository records no exception for it. ## Open questions diff --git a/stack.manifest.json b/stack.manifest.json index 0be1684..7855f7f 100644 --- a/stack.manifest.json +++ b/stack.manifest.json @@ -1,7 +1,8 @@ { - "$about": "The stack contract and manifest in one (ADR-016 in the core repo): the same schema (files/guards, adapt classes, per-entry rule anchors - here DECISIONS#N instead of R#) the core's standard.manifest.json uses, so the align engine and self-verify read it whole. Paths are the adopted repo's; the reference copy of every merge-class file lives in starter/. An adopting repo carries this file beside standard.manifest.json; self-verify then counts one drift across both layers. The registry pointer is the linkage to the core - discovery metadata, never a version requirement (core ADR-022).", + "$about": "The stack contract and manifest in one (ADR-016 in the core repo): the same schema (files/guards, adapt classes, per-entry rule anchors - here DECISIONS#N instead of R#) the core's standard.manifest.json uses, so the align engine and self-verify read it whole. Paths are the adopted repo's; the reference copy of every merge-class file lives in starter/. An adopting repo carries this file beside standard.manifest.json; self-verify then counts one drift across both layers. The registry pointer is the linkage to the core - discovery metadata, never a version requirement (standard ADR-022). `provenanceCommit` is the full commit SHA of THIS repository that an adopter's copy last aligned to: it ships `null`, and the run that applies or updates this layer writes it into the adopter's copy. It is what the stack half of an update's delta is computed from, the way the core manifest's own field is (standard ADR-052); `version` stays the human-facing bookmark. The two layers are independent - a stack rides its own clock (standard ADR-022) - so this field records a commit here, never one in the core repository.", "technology": "node", "version": "0.3.1", + "provenanceCommit": null, "description": "Node/TypeScript best practices - Next.js + Fastify through one proxy, Better Auth, tiered testing; distilled from production repos and boot-verified", "registry": "repository-standards/core", "files": [ @@ -44,9 +45,9 @@ }, { "path": "vitest.config.ts", - "purpose": "unit and integration tiers as separate Vitest projects", + "purpose": "unit and integration tiers as separate Vitest projects - the tiers are the requirement, not the path: a workspace whose packages each configure them satisfies it there, and a root projects config on top of those runs every suite twice, so this entry is optional rather than drift for that shape", "adapt": "merge", - "required": true, + "required": false, "profile": "core", "rule": "DECISIONS#9" }, From e16b632a4b9cddf93b4be6d86b8aa3214491550d Mon Sep 17 00:00:00 2001 From: bodurkalukasz Date: Thu, 3 Sep 2026 23:09:21 +0200 Subject: [PATCH 2/2] fix(stack-contract): drop the exception the optional entry made stale, and keep the count measured Self-review of the previous commit, in the three places it left behind. This repository's own `standard.manifest.json` waived `vitest.config.ts` with a `kind: file` exception, because the entry was required and this repository publishes the stack rather than consuming it. The entry is optional now, so the exception waives nothing: it cannot be the difference between drift and no drift, and it sits in the list that is supposed to mean something. That is the exact cost the previous commit argued about, so it goes. The `stack-contract` spec's open question said four entries fail and named `vitest.config.ts` among them. Three do. A measured claim that quietly stops being measured is worse than no number, and this one is the evidence behind an open question somebody has to decide. The manifest's `$about` cited three core records in two spellings after the previous commit - `ADR-016 in the core repo` beside `standard ADR-022`. One paragraph, one spelling: all three now read `standard ADR-NNN`, which is what core 1.0.11 settled on for files that ship into a repository with its own index. --- CHANGELOG.md | 5 +++++ specs/stack-contract/spec.md | 6 +++--- stack.manifest.json | 2 +- standard.manifest.json | 5 ----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b2eca..47e133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ exception recording it sat beside the exceptions that mean something. What the s yet say - this path, or one per workspace package - is the core's to fix, and it is filed as SELF-11 rather than papered over here. +One consequence lands in this repository's own adoption: it carried a `kind: file` exception +for `vitest.config.ts`, waiving an entry that can no longer fail. A stale exception is the +thing this change is against, so it is deleted, and the `stack-contract` spec's open question +now says three entries rather than four - a measured claim, kept measured. + `biome.json` deliberately stays required. The adaptation path prescribes a two-step Biome migration, so a repository following it correctly has no `biome.json` between the steps - which is a real, unfinished deviation from DECISIONS#3 and should read as one. `ADAPTING.md` diff --git a/specs/stack-contract/spec.md b/specs/stack-contract/spec.md index 90a2f58..8e0b8dc 100644 --- a/specs/stack-contract/spec.md +++ b/specs/stack-contract/spec.md @@ -112,9 +112,9 @@ behind the one-drift-number promise in Success metric above. - **This repository cannot satisfy its own stack manifest, and that is not yet decided either way.** The entries describe a repository that *consumes* the stack - `biome.json`, - `tsconfig.base.json`, `pnpm-workspace.yaml` and `vitest.config.ts` at the repository root - - while this repository *publishes* it and ships those files inside - [`starter/`](../../starter). Measured: with both manifests present, the four entries fail + `tsconfig.base.json` and `pnpm-workspace.yaml` at the repository root - while this + repository *publishes* it and ships those files inside + [`starter/`](../../starter). Measured: with both manifests present, the three entries fail and the `stack-check-all` guard fails with `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND`, because the guard runs `pnpm check:all` at a root that has no `package.json`. The manifest schema has no notion of publisher versus consumer, and the guard cannot be excepted by design. The diff --git a/stack.manifest.json b/stack.manifest.json index 7855f7f..f25af67 100644 --- a/stack.manifest.json +++ b/stack.manifest.json @@ -1,5 +1,5 @@ { - "$about": "The stack contract and manifest in one (ADR-016 in the core repo): the same schema (files/guards, adapt classes, per-entry rule anchors - here DECISIONS#N instead of R#) the core's standard.manifest.json uses, so the align engine and self-verify read it whole. Paths are the adopted repo's; the reference copy of every merge-class file lives in starter/. An adopting repo carries this file beside standard.manifest.json; self-verify then counts one drift across both layers. The registry pointer is the linkage to the core - discovery metadata, never a version requirement (standard ADR-022). `provenanceCommit` is the full commit SHA of THIS repository that an adopter's copy last aligned to: it ships `null`, and the run that applies or updates this layer writes it into the adopter's copy. It is what the stack half of an update's delta is computed from, the way the core manifest's own field is (standard ADR-052); `version` stays the human-facing bookmark. The two layers are independent - a stack rides its own clock (standard ADR-022) - so this field records a commit here, never one in the core repository.", + "$about": "The stack contract and manifest in one (standard ADR-016): the same schema (files/guards, adapt classes, per-entry rule anchors - here DECISIONS#N instead of R#) the core's standard.manifest.json uses, so the align engine and self-verify read it whole. Paths are the adopted repo's; the reference copy of every merge-class file lives in starter/. An adopting repo carries this file beside standard.manifest.json; self-verify then counts one drift across both layers. The registry pointer is the linkage to the core - discovery metadata, never a version requirement (standard ADR-022). `provenanceCommit` is the full commit SHA of THIS repository that an adopter's copy last aligned to: it ships `null`, and the run that applies or updates this layer writes it into the adopter's copy. It is what the stack half of an update's delta is computed from, the way the core manifest's own field is (standard ADR-052); `version` stays the human-facing bookmark. The two layers are independent - a stack rides its own clock (standard ADR-022) - so this field records a commit here, never one in the core repository.", "technology": "node", "version": "0.3.1", "provenanceCommit": null, diff --git a/standard.manifest.json b/standard.manifest.json index bf339dc..aeb5c38 100644 --- a/standard.manifest.json +++ b/standard.manifest.json @@ -1045,11 +1045,6 @@ "kind": "file", "match": "pnpm-workspace.yaml", "reason": "this repo publishes the node stack rather than consuming it; the real copy ships from starter/pnpm-workspace.yaml (SELF-1). Declared here, not in stack.manifest.json, for the same reason as biome.json above" - }, - { - "kind": "file", - "match": "vitest.config.ts", - "reason": "this repo publishes the node stack rather than consuming it; the real copy ships from starter/vitest.config.ts (SELF-1). Declared here, not in stack.manifest.json, for the same reason as biome.json above" } ] }