mjolnir-qa 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,503 @@ Rule behavior changes (new rules, FP-rate changes against the corpus,
9
9
  severity changes) are first-class entries here — rule IDs are immutable
10
10
  once shipped, so this file is the record of what changed between versions.
11
11
 
12
+ ## [Unreleased]
13
+
14
+ ## [6.1.0] — 2026-10-28
15
+
16
+ A minor release from the second half of the maintenance review. Most of it
17
+ fixes behaviour that was wrong without saying so: report text that rendered as
18
+ escaped markup, warnings that printed twice or not at all, reads that failed
19
+ and were counted as successes, and release workflows that could publish when
20
+ told not to. No verb, flag, Action input or MCP tool was added or removed.
21
+
22
+ ### Upgrade notes
23
+
24
+ Two config checks are now strict and exit 10 with the entry named. Both
25
+ configurations used to load and then silently do nothing, so a config that
26
+ fails here was already not doing what it said.
27
+
28
+ - `ignore[].expires` must be a calendar date, `YYYY-MM-DD`. A date-time
29
+ (`2026-09-01T00:00:00Z`) or an unpadded date (`2026-9-1`) left the
30
+ suppression inactive; write the date alone.
31
+ - `plugins` must be an array of package names or `{ "package": "…" }`
32
+ objects. An object such as `{ "acme": {} }` loaded no plugin; write
33
+ `["acme"]`.
34
+
35
+ ### Fixed: the scheduled OSV scan runs again
36
+
37
+ - **OSV-Scanner 2.7.0 removed `--skip-git`**, so the push and scheduled scans
38
+ failed before scanning. The flag is gone; pull-request scans never passed it.
39
+
40
+ ### Fixed: flag suggestions name only flags that exist
41
+
42
+ - **`mjolnir scan` suggests only flags it accepts.** `--dry-run` and
43
+ `--version` were offered as "Did you mean" right after being rejected, and
44
+ ten accepted flags (`--staged`, `--blocking`, `--enable-plugins`, …) were
45
+ never suggested. A spec now reads the parser to hold the two lists together.
46
+ - **Dead code removed:** the `ci install` line that shadowed the `ci`
47
+ subcommand table, an unreachable `ci --help` arm, an `existsSync` given a
48
+ glob it cannot expand, and two copies of the scan time-budget constants,
49
+ which the CLI and the MCP server now import from the pipeline that applies
50
+ them.
51
+
52
+ ### Fixed: CI and release workflows
53
+
54
+ - **A stable release publishes to `latest` directly** (`release.yml`). It
55
+ published to `next` and relied on a token-based repair to move `latest`.
56
+ - **A stable-release resume honours `dry_run` and requires
57
+ `STABLE_RELEASE_AUTHORIZED`.** It ignored both, so a resume dispatched with
58
+ the default `dry_run: true` published, including a dry run's artifact.
59
+ - **The npm propagation wait polls `latest` as well as the version, for up to
60
+ ten minutes, and fails by name.** 6.0.1 took over five minutes to appear,
61
+ and the job timed out.
62
+ - **The Action major-tag workflow moves `vN` to the newest stable `vN.x.y`.**
63
+ A manual dispatch tagged the head of the dispatching branch, and an older
64
+ patch tag could move `vN` backwards.
65
+ - **The dogfood PR comment posts only on same-repository pull requests.** On
66
+ a fork or a manual dispatch the job failed with 403.
67
+ - `github.base_ref` reaches the shell through `env:` rather than script
68
+ interpolation. The dead badge step and a duplicate `check-version` call are
69
+ gone, and the nightly stress run no longer starts at the same minute as
70
+ fuzz.
71
+ - **`ci install` treats a Dependabot action-pin bump as not a
72
+ customisation.** Only the Mjölnir Action's SHA was ignored, so each bump of
73
+ `actions/*` made every workflow the previous release generated read as
74
+ hand-edited. The template's pins follow `mjolnir.yml` again.
75
+ - Action pins bumped: `setup-node` 7.1.0, `upload-artifact` 7.0.2,
76
+ `download-artifact` 8.0.2, CodeQL 4.39.1, OSV-Scanner 2.7.0, Scorecard
77
+ 2.5.0. The `web-tree-sitter` 0.25.10 bump is not taken: the
78
+ `package-smoke` spec holds its exact 0.25.6 pin.
79
+
80
+ ### Fixed: report rendering, config validation and swallowed errors
81
+
82
+ - **Paths and code-like fixes in the PR comment, step summary and handoff are
83
+ real code spans.** Backslash escapes are literal inside a code span, so
84
+ `__snapshots__` rendered as `\_\_snapshots\_\_`, and a backtick in a path
85
+ closed the span early. The fence is now longer than any backtick run inside.
86
+ - **The INCOMPLETE banner on a complete scan with no runtime evidence says
87
+ so**, and the quarantine coverage note is written out in words. The banner
88
+ used to name "an unreported reason".
89
+ - **`ignore[].expires` must be a calendar date, `YYYY-MM-DD` (exit 10
90
+ otherwise).** A date-time or `2026-9-1` loaded, then left the suppression
91
+ silently inactive with no expired warning, and `2026-02-30` quietly meant
92
+ March 2. The expired warning now uses the same check as
93
+ `mjolnir scan --suppressions`.
94
+ - **`plugins` must be an array of package names or `{ "package": … }`
95
+ objects (exit 10 otherwise).** An object, or an entry of another shape,
96
+ loaded cleanly and the loader ran nothing.
97
+ - **Config warnings print once.** `scan` printed each one twice, and `fix`,
98
+ `ci verify` and `handover` dropped them. Those three now also send the
99
+ plugin gate notice to their own stderr.
100
+ - **MCP `explain` finds the rule's fixture from a checkout**, defaulting to
101
+ `<cwd>/tests/fixtures` as `mjolnir explain` does. It never returned the
102
+ example finding the CLI prints.
103
+ - **Runtime corroboration matches Windows report paths whose case differs
104
+ from the scan root** (`c:\repo` and `C:\Repo`). Each verdict missed its
105
+ finding.
106
+ - **`--changed` marks an unreadable untracked file as fully changed** instead
107
+ of building a million-entry line set for it, which also dropped any finding
108
+ past line 1,000,000.
109
+ - **Swallowed reads are counted:** an unreadable `pom.xml`, `build.gradle` or
110
+ `.csproj` (`java-manifest-unreadable`, `csharp-project-unreadable`) and an
111
+ unreadable rule source file in the detector fingerprint
112
+ (`rules-tree-file-unreadable`). The evidence store's audit fails on an
113
+ unreadable records directory instead of reporting an empty, intact store,
114
+ and `forensics` says why `FLAKY.md` could not be written.
115
+ - **Census version majors sort numerically without the ambient locale**, and
116
+ the deterministic-ordering contract now also catches a `localeCompare`
117
+ whose locale argument is `undefined`.
118
+
119
+ ### Fixed: the stable release publishes the artifact it verified
120
+
121
+ - **The first 6.0.1 publish run pushed `v6.0.1` and moved `v6`, then failed
122
+ before `npm publish`.** The verified artifact holds `release-artifact/` and
123
+ `release-assets/`, so `upload-artifact` keeps both directories, and the
124
+ publish job downloaded it into `release-artifact`, one level too deep:
125
+ `sha256sum: release-artifact/mjolnir-qa-6.0.1.tgz: No such file or
126
+ directory`. The GitHub-release job and both resume jobs read the same wrong
127
+ layout. All four now download into the workspace root, and a spec reads the
128
+ upload paths and holds every consumer to them.
129
+ - **The resumed GitHub release attaches the release-trust record**, which the
130
+ first-attempt release already required.
131
+ - **Both publish jobs read no `.npmrc`**, as `release.yml` does, so the
132
+ placeholder token `setup-node` writes there cannot stand in for OIDC trusted
133
+ publishing.
134
+ - **The SBOM checksum records a relative path**, like the tarball's. It
135
+ recorded the runner's absolute workspace path, and checked out only because
136
+ every job ran on the same path.
137
+
138
+ ## [6.0.1] — 2026-10-28
139
+
140
+ A patch release. 6.0.0 shipped a GitHub Action whose report step calls a
141
+ command the same release removed, so every job that used the Action failed,
142
+ whatever the scan found. This release repairs that, along with the findings of
143
+ the maintenance review that followed. Each change fixes behaviour that did not
144
+ match its documentation or its contract. No verb, flag or Action input was
145
+ added or removed.
146
+
147
+ **Upgrading from 6.0.0:**
148
+
149
+ - **The Action.** Pin `Sergey-Bar/Mjolnir@v6.0.1`, or `@v6`, which the stable
150
+ release workflow now moves to the newest 6.x release. The `version` input
151
+ defaults to `6.0.1`.
152
+ - **`mjolnir ci install`.** Run `npx mjolnir-qa@6.0.1 ci install` again with
153
+ the same flags. It recognises the workflow 6.0.0 wrote and replaces its
154
+ broken steps; a workflow you edited still needs `--force`.
155
+ - **Exit codes now match the contract.** `fix` and `explain --plan` return `2`
156
+ on a partial scan, `explain --evidence` returns `2` rather than `1` on an
157
+ incomplete report, an invalid config returns `10` rather than `20` on every
158
+ verb, and `ci verify` now honours `--blocking` and the E0 exemption. A
159
+ pipeline that keyed on the old codes can change colour.
160
+
161
+ ### Fixed: the stable release moves the Action's major tag
162
+
163
+ - **`stable-release.yml` moved `v3` and nothing else.** The step that follows a
164
+ stable tag with the `vN` Action tag exited early for every major but 3, and a
165
+ spec required it to. Its tag push uses the job token, which starts no other
166
+ workflow, so `action-tags.yml` never ran either. That is why
167
+ `Sergey-Bar/Mjolnir@v6`, the pin the documentation recommends, does not
168
+ resolve. The step now derives the major from the tag, and leaves the major
169
+ where it is when a newer release on that line already exists.
170
+
171
+ ### Fixed: the release record
172
+
173
+ - **5.1.0 was never published.** Its changelog section was cut and its stable
174
+ run reached the tag approval, which nobody gave, and 6.0.0 shipped first.
175
+ There is no `v5.1.0` tag and no 5.1.0 on npm. `docs/PUBLISHING.md` listed it
176
+ as published with links to a registry page and a GitHub release that do not
177
+ exist, and the roadmap called it shipped. Both now say what happened, and the
178
+ 5.1.0 section of this file says so too.
179
+ - **The run still waiting at that approval was cancelled.** If it had been
180
+ approved, it would have tagged 5.1.0 and published it as npm `latest`, over
181
+ 6.0.0.
182
+ - **Install commands that were left behind.** The site's guided proof and its
183
+ agent guide pinned 5.0.0, where the guided proof's `ci verify` does not
184
+ exist. `docs/guide/PERSONAS.md` pinned 6.0.0-rc.1, which was never
185
+ published, and sent readers to `stats` for per-rule FP rates; `stats` is the
186
+ fix-tracking ledger and the rates are `explain --list`. None of these files
187
+ is covered by `version:surface:sync`, so they now name 6.0.1 by hand.
188
+
189
+ ### Fixed: the `ci install` Action template pins an Action that works
190
+
191
+ - **It pinned `4a588bc`, a pull-request branch commit that `main` never
192
+ contained.** That commit's `action.yml` installed `mjolnir-qa@latest`, called
193
+ the retired `trust-report` verb and wrote `mjolnir-trust-report.md`. The
194
+ template's publish job reads `mjolnir-comment.md`, so no workflow it
195
+ generated ever published a comment. It now pins the merge commit of the fix
196
+ above, on `main`.
197
+ - **The template passes `version:`**, the published version the generating
198
+ package resolves, which is the npx template's pin. Omitting it ran the pinned
199
+ Action's own default: the stable published when that commit was made, which
200
+ can predate the formats its steps call.
201
+ - **`ci install` printed the pin as a separately typed SHA with 41 hex
202
+ digits**, which names no commit. It now prints the template's own value.
203
+ - Recognition ignores the `version:` value and still recognises 6.0.0's
204
+ template, which had no such line.
205
+
206
+ ### Fixed: the Action, `ci install` and the PR workflow render their reports again
207
+
208
+ The v6 carve retired `summary` and `pr-comment` onto `scan --format
209
+ github-summary` and `scan --format pr-comment`, and shipped only the half of
210
+ each that scans. Every CI consumer renders the `--json` report its scan step
211
+ saved. All of them kept calling the retired verbs, which exit 10:
212
+
213
+ - **The Action.** Its report step runs by default, so v6.0.0 failed every
214
+ consumer's job, on a clean scan as well as a dirty one. Its summary step
215
+ failed too.
216
+ - **`ci install --no-action`.** The generated summary and comment steps are
217
+ `continue-on-error`, so they failed without a sound: no annotations and no
218
+ PR comment, on any run.
219
+ - **This repository's PR workflow**, which has posted "Mjölnir report
220
+ unavailable" on every pull request since 6.0.0.
221
+
222
+ What changed:
223
+
224
+ - **`scan --format github-summary <report.json>` and `scan --format pr-comment
225
+ <report.json>` render a saved report**, as `site/reference/cli.md` already
226
+ said they did. They delegate to the retired verbs' own code, so the
227
+ contracts are the verbs': annotations on stdout inside GitHub Actions, the
228
+ summary written to `$GITHUB_STEP_SUMMARY`, and a render that never gates.
229
+ No verb or flag was added. A directory target still scans.
230
+ - **The comment's commit is checked, not passed in.** It states the commit the
231
+ scan recorded only while the checkout is at that commit, and `unknown`
232
+ otherwise. The Action used to hand `--commit "$GITHUB_SHA"` to a verb that
233
+ printed whatever it was given. The live `--format pr-comment` render, which
234
+ always said `unknown`, now states the commit it scanned.
235
+ - **The Action's summary step no longer pipes stdout into the summary**, which
236
+ copied every annotation command into it as text. Its `strict` input passes
237
+ `--include-warn` instead of the deprecated `--strict`, which printed a
238
+ deprecation notice into every consumer's log.
239
+ - **`ci install` recognises the workflows 6.0.0 wrote**, so re-running it
240
+ replaces their broken steps without `--force`. Recognition required byte
241
+ equality with the current template, so the pinned version alone made every
242
+ earlier release's file read as hand-customised. Line endings, the pinned
243
+ version and the Action SHA are now ignored, and any other edit still refuses.
244
+ - **The PR workflow's render step fails the job when the render fails**,
245
+ instead of `exit 0` under `continue-on-error`. Its fallback report for a
246
+ failed analysis now validates: it carried `"reason":"analysis-failed"`, which
247
+ the report validator rejects, so the one report written for that case could
248
+ never be rendered.
249
+ - **A `--include-warn` scan's report loads again.** With the quarantine tier
250
+ running, nothing is withheld, and `rulesWithheld` said 0 while
251
+ `withheldRuleIds` still named the whole tier. The loader rejects a report
252
+ that contradicts itself, so the Action's `strict: true` would have failed
253
+ every render even with the verbs fixed, and no saved-report command could
254
+ read such a report. Lifecycle resolution also read those rules as withheld,
255
+ so a quarantine-tier finding that a strict scan had fixed resolved as
256
+ `INCONCLUSIVE(rule-withheld)`.
257
+
258
+ The same defect in three more places, found by the widened gate below:
259
+
260
+ - `release.yml` ran `release-trust`, retired onto `ci release-trust`, so that
261
+ step exits 10 when it runs. `stable-release.yml` already used `ci`.
262
+ - `site/guide/forensics.md` told readers to run `npx mjolnir-qa@6.0.0
263
+ forensics`, and the version-surface check required that line. Both now say
264
+ `explain --evidence`.
265
+ - `docs/AI-DISCOVERY.md` pinned 5.0.0 and named the retired `baseline` and
266
+ `verify`. It now pins 6.0.0 with `--save-baseline` and `ci verify`, and
267
+ `site/public/llms.txt` says 6.0.0 too.
268
+
269
+ Why nothing caught it:
270
+
271
+ - **`check-cli-contract` matched only `mjolnir <verb>`.** CI steps launch the
272
+ CLI as `npx "$MJ_PACKAGE"`, `npx …/mjolnir-qa-<version>.tgz`, `npx
273
+ mjolnir-qa@<version>` or `node dist/cli.mjs`, and it now reads all of them.
274
+ `docs/MIGRATION-3.0.0.md` joins the frozen-report list: its commands pin
275
+ 3.0.0, where those verbs exist.
276
+ - **Four specs asserted the command text**, and text is satisfied by a command
277
+ that cannot run. `tests/integrations/ci-render-commands.spec.ts` takes each
278
+ consumer's command from the file that ships it and runs it against the CLI,
279
+ and checks every scan argument the Action can pass.
280
+
281
+ Still open:
282
+
283
+ - The Action runs the published package, so Action users get this with the
284
+ next release, which moves the `version` default to it.
285
+ - The floating `v6` Action tag was never created, because the tag job for
286
+ v6.0.0 was cancelled. The docs recommend `Sergey-Bar/Mjolnir@v6`, which does
287
+ not resolve yet.
288
+
289
+ ### Changed: every devDependency is an exact pin, and current
290
+
291
+ `npm outdated` now lists only the two blocked upgrades below. Runtime
292
+ `dependencies` and `peerDependencies` keep their ranges on purpose: they
293
+ resolve inside every user's install, and an exact pin there would force
294
+ duplicate copies and stop transitive security patches from reaching users.
295
+
296
+ - **Pinned and updated.** Every devDependency in the root, the
297
+ `packages/playwright-reporter` workspace and `site/` is now an exact
298
+ version, taken to its latest release inside the existing major. This
299
+ covers Playwright 1.64.1 (and `playwright-core`, which two scripts import
300
+ directly, at the same version), Sentry 11.9.0, Vitest 5.0.5, ESLint
301
+ 10.13.0, typescript-eslint 8.72.1, Prettier 3.9.10, tsdown 0.23.3 and
302
+ Lighthouse 13.6.0.
303
+ - **commitlint 19 → 21.** The two majors break nothing here. 20 makes the
304
+ body line-length rule skip URL lines, and 21 drops Node 18 and 20. 21's
305
+ parser does read a body line that opens with `word: ` as a footer, which
306
+ `footer-leading-blank` then rejects. Across the last 150 commits on `main`
307
+ that rejected 2 that 19 accepted, and `.commitlintrc.cjs` now says how to
308
+ avoid it.
309
+ - **vitepress 2.0.0-alpha.19 → alpha.21.** There is no stable 2.x, and 1.6.4
310
+ would be a downgrade. The site still renders 99 pages, its 44 tests and
311
+ doctor pass, and `home.data.ts` no longer trips the warning Vite prints for
312
+ its planned native config loader.
313
+ - **The reporter's TypeScript 7 is gone.** Its build resolves the root
314
+ compiler, and building with and without the nested 7.0.2 gives
315
+ byte-identical output. Removing it ends what read as a split toolchain and
316
+ drops the compiler's 20 native binaries.
317
+ - **Lockfile 589 → 461 entries.** Everything that left had no dependent in
318
+ the new tree, and the file still has the linux, darwin and win32 variants
319
+ every native package needs.
320
+ - **`scripts/complete-lockfile.mjs` no longer resurrects removed packages.** It
321
+ restored every key the known-good lockfile had, so `siginfo` and
322
+ `stackback` sat in the committed lockfile with nothing depending on them.
323
+ It now restores only platform entries the tree still wants, and has tests.
324
+ - **Dependabot.** Minor and patch updates come back as weekly grouped PRs:
325
+ runtime and tooling separately, plus the site, which had no entry and
326
+ received only security updates. 5.0.0 ignored them to cut noise, but with
327
+ every version locked an ignored update is never taken, which is how the
328
+ tree fell behind. Each blocked upgrade states when it was re-verified and a
329
+ review-by date.
330
+
331
+ Still blocked, re-verified 2026-10-28, review by 2027-01-26:
332
+
333
+ - `web-tree-sitter` 0.27.1 cannot load the Java or C# grammars of
334
+ `tree-sitter-wasms` 0.1.13, still its latest release, and 0.25.6 can.
335
+ - TypeScript 7 has no JS API for ts-morph, and typescript-eslint 8.72.1 still
336
+ requires TypeScript below 6.1.
337
+
338
+ Decided against, on inspection:
339
+
340
+ - **Moving Lighthouse into `site/`.** The root deliberately holds Lighthouse,
341
+ axe-core and Playwright for `site:lighthouse` and `site:audit`, so the site
342
+ build that CI and Pages run installs only vitepress. Lighthouse's dependency
343
+ closure is 137 lockfile entries, which would be added to every site build.
344
+ - **Dropping `axe-core`.** It looked unused only because `site:audit` loads
345
+ `axe.min.js` by path.
346
+
347
+ ### Removed: scripts and npm aliases that nothing ran
348
+
349
+ Files under `scripts/`, 169 → 138. Tracked npm scripts, 147 → 140.
350
+ `git show 05af655:<path>` recovers any deleted file.
351
+
352
+ - **`scripts/archive/` (24 files).** One-shot fixture authors and
353
+ adjudicators from 2026-06 to 6.0. Their output is committed and none of
354
+ them ran; they were kept only as `HISTORY` entries in the orphan-script
355
+ list, and that list and the archive lint block are gone with them.
356
+ - **Orphans:**
357
+ - `scripts/lib/omitted-measured.ts` and `scripts/lib/test001-probe.ts`:
358
+ scratch probes with zero references.
359
+ - `scripts/lib/coverage-exemption-ledger.ts`: a typed wrapper nothing
360
+ imported.
361
+ - `scripts/lib/candidate-decision.ts`: reachable only through an
362
+ extensionless import that resolved to the `.mjs` anyway. Its spec now
363
+ imports the `.mjs`, typed by the existing `.d.mts`.
364
+ - `scripts/compute-release.mjs` and its spec: release-bump logic whose
365
+ orphan entry said "reachable by nobody".
366
+ - `scripts/verify-remediation-isolation.mjs` (`verify:isolation`): a drill
367
+ hard-wired to a branch that no longer exists.
368
+ - `scripts/retired-external-evidence.ts` (`docs:external-evidence`): a
369
+ tombstone that exited 0 and checked nothing. The three documents that
370
+ named it now call it "the `docs:external-evidence` script (since
371
+ removed)".
372
+ - **Aliases for suites `npm test` already runs:** `frontier:contracts`,
373
+ `test:integration`, `test:mcp:parity`, `test:release` and
374
+ `artifact:deterministic`. Three of their `docs/MANUAL-SCRIPTS.md` rows were
375
+ false: `test:mcp:parity` did not "need a live MCP client", `test:release`
376
+ needed no credentials, and `test:integration` was never "kept out of the
377
+ certify chain". All three ran in every `npm test`. The agent skills now name
378
+ the commands the aliases stood for.
379
+ - **`docs/CI-STANDARD.md`** credited the compatibility lanes with
380
+ `npm run test:integration`. They run `npm test`, and the row now says so.
381
+
382
+ Not done, on inspection: `check-promotion-throughput.mjs` keeps running under
383
+ `tsx`, because it imports `src/commands/doctor.ts` and plain `node` cannot load
384
+ it.
385
+
386
+ ### Removed: tracked files with no reader
387
+
388
+ Six paths were tracked and read by nothing. Git history keeps all of them,
389
+ and `repo-hygiene.spec.ts` now fails if any returns:
390
+
391
+ - `enterprise-output/`: a 4.0.0 copy of `capability-manifest.json` with no reference.
392
+ - `QA/`: one 2026-09-06 report that only linked to itself.
393
+ - `archive/`: five exported stash patches. Their own README records each as
394
+ superseded, and they apply only at their old base commits.
395
+ - `assets/readme/homepage-linkedin-aurora.gif`: 4.85 MB, referenced nowhere.
396
+ - `.github/workflows/jekyll-gh-pages.yml`: a workflow whose only step was an
397
+ `echo`, plus its tier exemption.
398
+ - `.claude/launch.json`: hard-coded another machine's worktree path; now gitignored.
399
+
400
+ Also fixed:
401
+
402
+ - **CHANGELOG.** A second `## [Unreleased]` block (about 1,000 lines of
403
+ 6.0.0 working notes) sat below `[6.0.0]`, where the release script would
404
+ have swept it into the next version. It is now titled as the 6.0.0
405
+ development log, and a test asserts exactly one `[Unreleased]`, first.
406
+ - **Stale references.** Removed a `package.json` `allowScripts` entry for
407
+ `@vitest/coverage-istanbul@5.0.1`, which the lockfile does not install.
408
+ Fixed the commit-msg hook's `.commitlintrc.json` reference (the file is
409
+ `.cjs`), the `.prettierignore` entries for retired M26 files, two Sonar
410
+ paths that do not exist, and the CodeRabbit workflow count.
411
+
412
+ Kept on purpose: `PRODUCT-ENHANCEMENT-ANALYSIS.md` is a pinned redirect to
413
+ its archive copy (`GAP-V6-007`, two specs), and `kilo.json` is the
414
+ documented shared skills wiring (`docs/EXTERNAL-CONFIG.md`).
415
+
416
+ ### Fixed: the P0/P1 findings of the maintenance review (#732)
417
+
418
+ Each has a regression test that failed before its commit.
419
+
420
+ - **SARIF results pointed one directory too high.** `SRCROOT` had no trailing
421
+ `/` (SARIF 2.1.0 §3.14.14), so RFC 3986 resolution of `src/a.ts` against
422
+ `file:///repo` landed on `file:///src/a.ts` in every Code Scanning upload.
423
+ - **Failing .NET tests disappeared from TRX runs.** A `UnitTestResult` body
424
+ over 4,000 characters (normal with StdOut and a stack trace) did not match,
425
+ so the failure was dropped and the run read greener than it was.
426
+ - **Quadratic parsing of untrusted input.** JUnit `<failure>` tags without a
427
+ closer took 63 s for 2.2 MB, and `countTestDeclarations` took 152 s for
428
+ 300,000 blank lines (it runs on every scanned file). Both are linear now.
429
+ - **Exit codes.** `--save-baseline` no longer corrupts `--json` output, never
430
+ saves a partial scan, and reports a failed save as `2` (not `1`) after the
431
+ report. `fix` and `explain --plan` exit `2` on a partial scan. An invalid
432
+ config exits `10` on every verb, not `20`. `ci verify` gates new findings
433
+ through the same matrix as `scan`, so `--blocking` and the E0 exemption
434
+ apply. `explain --evidence` exits `2`, not `1`, on an incomplete report.
435
+ - **MCP workspace root.** With `MJOLNIR_WORKSPACE_ROOT` set, a symlink or
436
+ junction inside the root that pointed outside it was accepted, and a child
437
+ named `..cache` was rejected. Both sides are now real paths. Without the
438
+ variable the read-only tools stay open, as before; the server header no
439
+ longer claims otherwise.
440
+ - **Evidence store lock.** A live lock was bypassed after ~0.4 s: the write
441
+ ran unlocked and deleted the owner's lock. It now waits until the lock
442
+ could be stale and otherwise times out without running.
443
+ - **Half-applied runtime corroboration.** A throw part-way through left some
444
+ findings at a runtime trust level (up to L5). Findings are restored and the
445
+ new `runtime-corroboration-failed` degradation is recorded.
446
+ - **Action output.** `sarif_file` was nested under `findings:` in `action.yml`
447
+ and was always empty for consumers.
448
+
449
+ Deferred, recorded on #732: the same `^\s*` pattern in three detectors also
450
+ reports the wrong line (the blank line above the declaration); fixing it
451
+ changes line attribution, so it ships with a `detectorRevision` bump and a
452
+ corpus re-measure.
453
+
454
+ ### Fixed: `main` was red on every CI run since 2026-10-06
455
+
456
+ - **Lockfile.** `package-lock.json` had been regenerated on one platform and
457
+ lost the linux/darwin `lightningcss` binaries, so `npm ci` failed on the
458
+ Node 24 job and on any fresh install. Restored with
459
+ `scripts/complete-lockfile.mjs`; `npm run lockfile:platforms` passes.
460
+ - **README contracts.** The 6.0.0 README rewrite dropped the release-status
461
+ section, the `Status` heading, the documentation table, the roadmap link,
462
+ the census sentinels, the rule table, the WORTHINESS alt text and the
463
+ "a CI step fails" wording on exit `2` — every one pinned by a contract spec,
464
+ 13 failures in all. They are back in the short layout. Two claims in the
465
+ rewrite were wrong and are corrected: `QA-CI-008` is "always-success step
466
+ masks failures", not "mutable Action refs", and the YAML example named
467
+ detections no rule makes (`--maxfail=0`, `--workers=1`, `@main`).
468
+ - **README specs restored, not weakened.** The same rewrite had cut three
469
+ contract specs to match it: `readme-doctest` (which runs every README
470
+ command and checks the `explain QA-CI-001` transcript) became
471
+ `expect(true).toBe(true)`, `readme-samples` asserted the samples were
472
+ _absent_, and `readme-commands` grew a ten-verb exemption list. Those specs
473
+ were the only tests reaching several command paths, so function coverage
474
+ fell to 98.58 % against a 98.67 % ratchet floor. The specs are restored
475
+ verbatim from 6.0.0, and the command table, the explain transcript and the
476
+ two terminal samples come back as collapsed `<details>` blocks in a
477
+ Reference section, which keeps the page short. Functions are at 98.76 %;
478
+ the suite runs 87 more tests.
479
+ - **Version surface.** `checkVersionSurfaceEnvelope` read an inline-code
480
+ install command (`` `npx mjolnir-qa@X` ``) as `` X` ``, so an unpublished
481
+ version written in backticks was never reported. The terminator set now
482
+ includes the backtick, matching the sync routine.
483
+ - **Weekly SARIF scan.** `sarif-code-scanning.yml` scans `.` and had failed
484
+ every run since 2026-09-28 with exit `2`: `mjolnir.config.json` excluded
485
+ `tests/golden/**`, which covers the real spec `tests/golden/golden.spec.ts`,
486
+ so the scan honestly reported PARTIAL. The exclude is narrowed to the
487
+ fixture repo, `tests/golden/repo/**`, and `repo-hygiene.spec.ts` now asserts
488
+ the whole-repo scan is complete.
489
+ - **Promotion ratchet.** `docs/RULE-PROMOTION-LEDGER.json` still bounded
490
+ `core` at `0` after 6.0.0 promoted `QA-PW-117` and `QA-JV-101`. The ratchet
491
+ passed only because a stale `[Unreleased]` block below the 6.0.0 heading
492
+ still carried that release's anti-creep exception marker; a new entry at the
493
+ top turned it red. The growth is ABSORBED (bound `0 → 2`, the same convention
494
+ as `docs/ANTI-CREEP-BASELINE.json`), and the licensed-growth arm, which the
495
+ committed tree no longer reaches, is now driven by a fixture pair. (This
496
+ entry spells the marker in lower case on purpose: the gate matches the
497
+ upper-case token anywhere in the top entry, so quoting it would license
498
+ growth that nobody declared.)
499
+ - **Dependency advisories.** With `npm ci` failing, `npm run audit:ci` had
500
+ never been reached; it now ran and failed on `zod` (high, GHSA-mrhx-c7xc-m3w5,
501
+ via lighthouse → puppeteer-core → chromium-bidi) and `fast-uri` (moderate).
502
+ The site lockfile carried a `vite` advisory (high, GHSA-vfpm-58rq-9qcg). All
503
+ three are lockfile-only `npm audit fix` updates inside the declared ranges,
504
+ in dev tooling only — nothing in the published package changes.
505
+ `lockfile:platforms` passes for both lockfiles.
506
+ - Regenerated: `docs/BLAST-RADIUS-AUDIT.md`, `docs/CARVE-MANIFEST.json`,
507
+ `candidate-trust-manifest.json`.
508
+
12
509
  ## [6.0.0] — 2026-10-04
13
510
 
14
511
  <!-- ANTI-CREEP-EXCEPTION: The launch set grew from 0 to 2 — QA-PW-117 and QA-JV-101 both earned core at n=35 with a 9.89% Wilson upper bound against the 10% ceiling, every row adjudicated by hand against a pinned corpus commit. Law 1 requires an equal-size removal or a recorded exception. Nothing was removed, deliberately: the quarantine population is what keeps unmeasured rules out of default scans, and promoting two rules by demoting two measured ones would have traded measured accuracy for a smaller number. The growth is arithmetic, not preference. -->
@@ -82,7 +579,12 @@ The check RAN and was RECORDED as unsatisfied. It was not treated as blocking
82
579
  because `vars.STABLE_RELEASE_AUTHORIZED` is `true`: a decision someone made and
83
580
  wrote down, not a check that passed.
84
581
 
85
- ## [Unreleased]
582
+ ### Development log: 6.0.0-rc.1 → 6.0.0
583
+
584
+ The working notes written between the release candidate and the cut. They
585
+ sat under a second `## [Unreleased]` heading below this release, which
586
+ `scripts/release-changelog.mjs` would have swept into the next version as if
587
+ it were new work. They are history of 6.0.0 and are kept verbatim below.
86
588
 
87
589
  ### Two core rules, measured: QA-PW-117 and QA-JV-101 - `ANTI-CREEP-EXCEPTION`
88
590
 
@@ -1362,6 +1864,11 @@ and the replacement itself never warns.
1362
1864
 
1363
1865
  ## [5.1.0] — 2026-10-02
1364
1866
 
1867
+ > **Never published.** This section was cut on its release branch, but the
1868
+ > stable release run waited at the tag approval and was not approved, and 6.0.0
1869
+ > shipped first, carrying everything below. There is no `v5.1.0` tag and no
1870
+ > 5.1.0 on npm.
1871
+
1365
1872
  ### Two subtractions declined, because the surface they would remove says something the replacement does not
1366
1873
 
1367
1874
  **`mjolnir capability`: delete the M0–M5 ladder, render the tier instead.** The