mjolnir-qa 1.0.4 → 1.0.8

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,474 @@ 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
+ ## [1.0.8] — 2026-09-11
13
+
14
+ ### Changes since 1.0.7
15
+
16
+ - chore: sync smithery.yaml in the release cut step (Merged PR #76)
17
+
18
+ ## [1.0.7] — 2026-09-11
19
+
20
+ ### Changes since 1.0.6
21
+
22
+ - docs: 1.0.6 CHANGELOG section lead-ins (Merged PR #75)
23
+
24
+ ## [1.0.6] — 2026-09-11
25
+
26
+ ### R10 2.0 preparation: breaking-set inventory + boundary-law guards (remediation/remote-first WI-25)
27
+
28
+ Preparation-only increment: the 2.0 breaking-set proposal sheet, its migration draft, and the boundary-law guard tests — nothing breaking ships in this release.
29
+
30
+ ### Added
31
+
32
+ - **2.0 breaking-set inventory** (`docs/2.0-BREAKING-SET.md`, WI-25): the
33
+ proposal sheet per the strategic blueprint's §28/§18 — two justified
34
+ breaking candidates (BS-1 default suppression expiry with the explicit
35
+ never-expire opt-out; BS-2 retirement completion into `RETIRED_RULE_IDS`),
36
+ each carrying its benefit>cost justification, its migration pointer, and a
37
+ PROPOSED decision line awaiting owner ratification, plus the locked
38
+ NOT-breaking list (`schemaVersion 1` additive extension, exit codes,
39
+ additive verbs, Node matrix, frozen surfaces). **Nothing is implemented in
40
+ this release** — nothing enters 2.0 "because large", and no frozen surface
41
+ breaks without evidence that `schemaVersion 1` cannot represent the
42
+ behavior.
43
+ - **Migration guide draft** (`docs/MIGRATION-2.0-DRAFT.md`): the working
44
+ draft of the 2.0.0 guide (publication law: CHANGELOG + site with the
45
+ release itself) covering BS-1 (init config check → explicit `expires` /
46
+ `expires: false`; no silent retroactive expiry) and BS-2 (retired-rule
47
+ list, suppression cleanup, §15-lifecycle-honest disappearance causes).
48
+ - **Boundary-law + non-goal guards** (`tests/contract/boundary-law.spec.ts`,
49
+ blueprint §9.1/§24/§36): the canonical layers (engine, forensics,
50
+ adapters, rules) never import upward into commands/ or the transports;
51
+ the MCP transport imports no detection machinery and rides the canonical
52
+ machine contract (pipeline → contract → runtime evidence → agent
53
+ transport is never reversed); the zero-network contract holds; the
54
+ dependency list carries no telemetry/cloud/hosted-backend package; src/
55
+ reads no telemetry configuration; and the breaking-set discipline is
56
+ drift-locked (every entry carries a Decision, nothing is implemented).
57
+
58
+ ### Changed
59
+
60
+ - **Final capability matrix update** (`src/capabilities.ts`,
61
+ `docs/PLAYWRIGHT-CAPABILITIES.md`): the `to agents` column now carries the
62
+ foundational Agent Skill's evidence pointer (`src/commands/install-agents.ts`,
63
+ shipped with R8/WI-22) on every row — a `no → yes` flip in the same change
64
+ set that shipped its evidence, per the claim law.
65
+
66
+ ### Fixed
67
+
68
+ - **MCP stdio bundle no longer prints the terminal Trust Report onto the
69
+ JSON-RPC stream** (`src/mcp/server.ts`): the standalone entry
70
+ (`node dist/mcp/stdio.mjs`, `npm run mcp`) dragged the CLI module in via
71
+ `import { runScan, CLI_VERSION } from "../cli.js"`, and cli.ts's entry
72
+ tail fired inside the bundle (`import.meta.url === argv[1]`), emitting the
73
+ full terminal Trust Report before/between JSON-RPC frames — a fatal
74
+ protocol violation for any MCP client. The transport now imports the
75
+ canonical homes directly (`engine/scan-pipeline.js`, `engine/version.js`),
76
+ the bundle contains no CLI entry tail, and the boundary-law guard bans the
77
+ `../cli.js` import from the MCP layer permanently. Found by the R10
78
+ bug-hunt smoke against the real stdio transport.
79
+ - **Stale dist can no longer mask new code in spawned-binary tests**
80
+ (`tests/e2e/global-setup.ts`): the EXISTS-ONLY guard skipped the build
81
+ whenever a bundle was present, so the spawned stdio binary kept answering
82
+ from a pre-R8 catalog ("unknown tool: triage") while the suite stayed
83
+ green. The setup now rebuilds whenever any `src/**/*.ts` is newer than the
84
+ bundle (the same freshness discipline as the generated-docs drift gates).
85
+ - **Freshness diagnosis names each drift class** (`checkArtifactFreshness`):
86
+ a revision bump (`rule@old -> new`), a rule retired since the render
87
+ (`rule@rev (retired)`), and a rule added since the render
88
+ (`rule@rev (new)`) are distinct remediations — a flat list hid which one
89
+ happened.
90
+ - **`trust-report --from` reports the complete artifact set** it writes
91
+ (md + html + json), not just the MD path.
92
+ - **The `provenance = bound` system-invariant item is now WIRED** (plan
93
+ §5.2 activation; `src/commands/release-trust.ts`): the release-trust
94
+ invariant previously hardcoded `provenance: UNSUPPORTED` even after the
95
+ machinery it waited for shipped. It is now PROVEN exactly when the
96
+ machine-anchored identity chain is proven — scope-integrity (runIdentity +
97
+ evidence graph, R4c) AND artifact-integrity (artifact scanId binding, R9)
98
+ both PASS — and stays UNSUPPORTED (recorded, non-blocking) otherwise. The
99
+ contract doc's activation sentence and the drift-lock are updated
100
+ accordingly; the shipped verdict is unchanged (PASS 12/12, provenance
101
+ bound).
102
+
103
+ ### R9 Trust Artifact integrity + HTML completion (remediation/remote-first WI-23+24)
104
+
105
+ Trust Artifacts gain machine-anchored identity and a deterministic HTML surface; stale, wrong-run, revision-drifted, and unbound artifacts are now detectable.
106
+
107
+ ### Added
108
+
109
+ - **Artifact integrity binding** (`src/commands/trust-report.ts`, R9): every
110
+ Trust Artifact (md · json · html) now embeds its IDENTITY — the machine
111
+ anchor (`scanId` from runIdentity), the bound commit when resolvable
112
+ (offline git read; null, never fabricated), the fired rule(rev) inventory
113
+ (deduped, sorted, undeclared revisions omitted — never a fabricated rev),
114
+ and the evidence inventory (totals, runtime-corroborated count, per-level
115
+ counts). Consumers detect **stale artifacts** (a scanId from another run),
116
+ **mismatched revisions** (rule-set drift, named per rule), and **unbound
117
+ artifacts** (pre-R9 producers) via `checkArtifactFreshness` — an unbound or
118
+ stale artifact is RECORDED, never assumed current.
119
+ - **HTML Trust Artifact** (WI-23 completion, §18): deterministic,
120
+ self-contained `mjolnir-trust-report.html` — inline CSS only, zero external
121
+ resources, hostile interpolations escaped, byte-identical regen (same
122
+ ScanResult + label + commit → same bytes), the same five-question structure
123
+ as the MD. The command writes all three formats; `--from` gains an optional
124
+ `--commit <sha>` so the Action binds the artifact to the executing run's
125
+ HEAD.
126
+ - **Artifact Integrity dimension wired** (`check:artifact-integrity` in
127
+ `src/commands/release-trust.ts`, R9 surface): the structural evaluation
128
+ asserts the identity binding, the three-format output, the freshness
129
+ detection, and the byte-regen/hostile-safety contract locks. The
130
+ release-trust contract's documented-unwired list is now EMPTY — all 12
131
+ canonical dimensions are wired and machine-evaluated.
132
+
133
+ ### R8 MCP runtime-evidence tools + Agent Safety (remediation/remote-first WI-21+22)
134
+
135
+ The MCP transport learns the runtime-evidence tools, and every installed agent surface inherits the safety contract.
136
+
137
+ ### Added
138
+
139
+ - **MCP runtime-evidence tools** (`src/mcp/server.ts`, WI-21): `forensics`,
140
+ `triage`, `pw-report` join the tool catalog as 1:1 mappings onto the SAME
141
+ engine functions the CLI verbs call — no MCP-only semantics. Parity is
142
+ drift-locked table-driven (`tests/mcp/parity.spec.ts`): for every new tool ×
143
+ every fixture class (Playwright JSON · JUnit XML · hostile corrupt report ·
144
+ no-reports directory) the MCP result deep-equals the canonical CLI
145
+ derivation, hostile inputs degrade to zero records on BOTH surfaces, and the
146
+ hostile parameter matrix (missing / empty / non-string / nonexistent path)
147
+ yields INVALID_PARAMS naming the target, never a crash. A crashing tool
148
+ never kills the server (`tests/mcp/crash-containment.spec.ts`): the failure
149
+ lands in the transport's existing catch as a structured INTERNAL error and
150
+ the server keeps answering. One scan in flight; zero network; the plugin
151
+ gate applies unchanged.
152
+ - **Agent Safety dimension wired** (`check:agent-safety` in
153
+ `src/commands/release-trust.ts`, R8 surface): the structural evaluation
154
+ asserts the §17 safety wording on every installed skill surface, that the
155
+ MCP tool surface never opens the plugin trust gate, and that the agent edge
156
+ case (`fg-agent-unsafe-action`) stays registered in the False-Green Attack
157
+ Corpus. The release-trust contract's documented-unwired list shrinks to
158
+ artifact-integrity only (ships R9).
159
+
160
+ ### Changed
161
+
162
+ - **Agent brief inherits the Constitution** (`src/commands/install-agents.ts`,
163
+ WI-22): every installed instruction surface (.claude/, .cursor/, .kilo/,
164
+ AGENTS.md) now carries the non-negotiable agent-safety contract — NEVER
165
+ declare trustworthiness without evidence · AGENT CLAIM ≠ VERIFICATION ·
166
+ NEVER manufacture, edit, or synthesize evidence · NEVER convert INCONCLUSIVE
167
+ to pass · NEVER suppress findings or weaken rules to get green — plus the
168
+ loop preconditions (FIX requires a proven actionable defect; RESCAN requires
169
+ changed-scope identification; PROOF requires fresh post-fix execution
170
+ evidence). Drift-locked by `tests/contract/agent-skill-surface.spec.ts`
171
+ (frozen surfaces only; safety wording asserted).
172
+
173
+ ### R7 Playwright capability matrix (remediation/remote-first WI-20)
174
+
175
+ The Playwright capability matrix becomes a product surface with its own drift lock.
176
+
177
+ ### Added
178
+
179
+ - **Playwright Capability Matrix** (`docs/PLAYWRIGHT-CAPABILITIES.md`,
180
+ `src/capabilities.ts`, WI-20): the product-depth surface — 12 Playwright
181
+ capabilities × 8 depth columns (detect · explain · produce evidence ·
182
+ correlate runtime · trust verdict · CLI · MCP · agents), every cell
183
+ explicitly classed (zero UNCLASSIFIED), every `yes` backed by a resolvable
184
+ evidence pointer (registered rule ID or in-repo artifact) with FAIL-CLOSED
185
+ validation: the generator refuses to render a claim on a dangling pointer.
186
+ Generated (`npm run docs:capabilities-playwright`) and drift-locked
187
+ (tests/contract/playwright-capabilities.spec.ts). The `to agents` column is
188
+ uniformly **no** until R8 ships the Agent Skill — stated, not implied.
189
+ Claims never exceed proven capability; rule counts stay out of the claim
190
+ surface entirely.
191
+
192
+ ### R6 forensic taxonomy + Selector Health v2 (remediation/remote-first WI-18+19)
193
+
194
+ Forensic verdicts gain the semantic taxonomy, and Selector Health v2 replaces the locator heuristic.
195
+
196
+ ### Added
197
+
198
+ - **Forensic verdict taxonomy** (`src/forensics/classify.ts`, WI-18): the
199
+ canonical §6 verdict set (likely-real-defect · environmental-failure ·
200
+ infrastructure-failure · flaky · retry-dependent · unstable-construction ·
201
+ **inconclusive default**) applied by a deterministic minimum-signal table —
202
+ a single weak signal can never classify confidently; conflicting signal
203
+ families force INCONCLUSIVE with an explicit `contradictory` evidence-state.
204
+ Every `TestVerdict` now carries a machine-visible `forensic` classification
205
+ (attempts + captured error text; sources without error text mark
206
+ `unsupported`, never a guess). Contradiction reconciliation
207
+ (`corroborates | contradicts | insufficient`) implements Contract H: the
208
+ runtime can corroborate but never silently weakens a static claim — a
209
+ contradiction renders the PAIR inconclusive while the claim stands.
210
+ - **Selector Health v2** (`correlateSelectorHealth`, WI-19): runtime
211
+ correlation + concrete safe next actions; **no correlation ⇒ no claim** —
212
+ absent or merely-green runtime evidence yields no health claim in either
213
+ direction; the v1 static score is secondary and never altered here.
214
+
215
+ ### Changed
216
+
217
+ - `TestRecord` gains an optional `errors` text surface (the trace ingester
218
+ populates it); `TestVerdict` gains the additive `forensic` field.
219
+
220
+ ### R5 trace ingester (remediation/remote-first WI-17)
221
+
222
+ Trace forensics: bounded ingestion of Playwright trace.zip artifacts into the evidence core.
223
+
224
+ ### Added
225
+
226
+ - **Playwright trace ingester** (`src/forensics/trace.ts`, WI-17): deterministic,
227
+ offline, bounded, version-aware ingestion of per-test traces — `trace.zip`
228
+ (a bounded, dependency-free ZIP reader: EOCD scan, central-directory
229
+ enumeration, stored/deflate members via `node:zlib` with a decompressed-output
230
+ cap) or raw `.trace`/`.ndjson` NDJSON streams. Action pairs become
231
+ Evidence-Core `TestRecord`s (start/end pairing, durations, per-action
232
+ errors; timeout errors render `timedOut`). `runForensics` recognizes trace
233
+ artifacts in both file and directory modes; the report source union gains
234
+ `playwright-trace` additively (`contractVersion 1` unchanged).
235
+ - False-Green corpus cases for the trace surface: corrupt stream, truncated
236
+ stream, event-count overflow, unsupported version marker, zip without
237
+ `trace.trace` — all degrade to the zero-record exit-2 state, never a green
238
+ empty suite; plus positive controls (real stored zip + valid stream ingest
239
+ with paired durations) proving the rejections are precision, not blindness.
240
+
241
+ ### Changed
242
+
243
+ - `ForensicsReport.source` + `RuntimeCorroboration.source` widened additively
244
+ with `"playwright-trace"`.
245
+
246
+ ### R4c Evidence Graph + Scope Integrity + Exit-Code proofs (remediation/remote-first)
247
+
248
+ Every verdict now carries a machine-anchored evidence graph, scope-integrity accounting, and exit-code proofs.
249
+
250
+ ### Added
251
+
252
+ - **Run Identity** (`src/engine/run-identity.ts`): the deterministic anchor —
253
+ `scanId = sha256(input snapshot fingerprint + rulesDigest + config
254
+ fingerprint + engine version)`; set-identity semantics (input order does not
255
+ matter); every scan report carries `runIdentity` + `evidenceGraph` — the
256
+ chain-law links VERDICT ← EVIDENCE ← EXECUTION ← SCOPE ← SOURCE ← RULE(rev)
257
+ ← FIXTURE ← REPRODUCTION, each `ref` present only when its identity input
258
+ exists (no fabrication). The engine-version literal moved to the leaf module
259
+ `src/engine/version.ts` (cli.ts re-exports it as CLI_VERSION;
260
+ sync-sarif-version.cjs + version-consistency spec follow).
261
+ - **Scope Integrity** (`ScanResult.scopeIntegrity`, additive): discovered /
262
+ analyzed / ignored / unrecognized / parseFailed / truncated counts +
263
+ `scopeVerdict` — PROVEN only when analyzed ≡ claimed scope; else PARTIAL
264
+ with named reasons. The terminal reporter renders the scope block and the
265
+ "repository verified" phrasing is forbidden output unless PROVEN. Walk-level
266
+ accounting: matcher exclusions (`onIgnored`) and unclaimed files
267
+ (`onUnrecognized`) are counted at the shared walk; parse failures are
268
+ counted at the rule stage.
269
+ - **Exit-code decision proofs** (tests/blast-radius/scope-and-exit.spec.ts):
270
+ the frozen decision points exercised in both directions — trigger present →
271
+ frozen code, trigger absent → a different code — plus the closed frozen set
272
+ {0,1,2,10,20}.
273
+ - Machine-contract doc regenerated with the three additive blocks
274
+ (`contractVersion 1` unchanged — additive within the schema).
275
+
276
+ ### Changed
277
+
278
+ - Discovery accounting: the shared walk counts matcher-excluded files and
279
+ unclaimed files (ScanContext gains optional `onIgnored`/`onUnrecognized`;
280
+ all shared-walk adapters pass them through).
281
+
282
+ ### R4b False-Green Attack Corpus (remediation/remote-first)
283
+
284
+ The False-Green Attack Corpus: hostile failure classes with mutation-based detection proofs.
285
+
286
+ ### Added
287
+
288
+ - **tests/false-green/** — the adversarial corpus (plan §6, P0): 20 cases
289
+ across the plan's seven hostile classes (execution · parser · adapter ·
290
+ evidence · rule · mcp · agent failures), each declaring the seven
291
+ owner-required fields (INPUT / EXPECTED EXECUTION / EVIDENCE / VERDICT /
292
+ EXIT CODE / REPORT FIELDS / RELEASE IMPACT) and executed against real
293
+ surfaces with specific field bindings:
294
+ - execution: empty suite (score null + no-tests-found recorded), deadline
295
+ truncation, and the partial+findings never-blocks invariant (audit C5);
296
+ - parsers (through the real `runForensics` entry): corrupt JSON, truncated
297
+ Playwright report, malformed JUnit, unsupported schema → zero records →
298
+ exit-2 state — PARSER FAILURE ≠ CLEAN; duplicate retry-storm records stay
299
+ visible;
300
+ - adapters: scalar-jobs workflow fabricates nothing; broken YAML is SKIPPED
301
+ with accounting;
302
+ - rules: a throwing local plugin rule (QA-ACME-666) → `rulesCrashed ≥ 1`
303
+ with the scan completing — RULE CRASH ≠ CLEAN;
304
+ - evidence: missing/corrupt baseline → hasBaseline=false (exit 2); stale
305
+ baseline resolutions stay scoped to their capture; the foreign
306
+ baselineCommit is recorded (binding gate ships R4c);
307
+ - MCP: unknown tool / invalid params answer JSON-RPC errors, never success;
308
+ - agent: codegen and generated-header provenance classification — AGENT
309
+ CLAIM ≠ VERIFICATION.
310
+ - **Mutation / assertion-strength protocol** (tests/false-green/mutation-
311
+ protocol.spec.ts): for every wired case and every report-field binding, the
312
+ false-green twin of the honest report (failure→success, partial→complete,
313
+ unknown→clean, crashed-rule→clean…) is injected and the case's assertion
314
+ must FAIL on it — a decorative assertion fails CI. Parser input twins flip
315
+ the hostile input to its benign form and require the observed verdict to
316
+ flip with it.
317
+ - **Generated, drift-locked index** (npm run false-green:index + index.spec.ts):
318
+ one row per case with all seven declarations, the mutation inventory, and
319
+ the UNSURFACED rows (MCP transport internals / agent-action policy → R8;
320
+ artifact binding → R9) — recorded per Constitution §5, never silently
321
+ dropped. All seven plan classes present.
322
+
323
+ ### R4a Trust Constitution + Release Trust Verdict (remediation/remote-first)
324
+
325
+ The Trust Constitution and the two-layer release-trust verdict algebra.
326
+
327
+ ### Added
328
+
329
+ - **docs/TRUST-CONSTITUTION.md** — canonical law: CERTIFICATION-POLICY A1–A4
330
+ adopted as §1; the 18 PASS-forbidden conditions (verbatim); the closed status
331
+ algebra (PROVEN evidence-state → PASS/FAILED derivation, terminality rule,
332
+ record shape); the core law (`PASS = conclusion backed by sufficient
333
+ evidence`); per-dimension applicability (UNSUPPORTED surfaces are recorded,
334
+ non-blocking, and drift-locked); publication honesty.
335
+ - **docs/RELEASE-TRUST-CONTRACT.md** — the canonical 12 dimensions (fixed set,
336
+ fixed order, governance-locked): Engine/Evidence/Rule Integrity, Failure
337
+ Containment, Corpus Integrity, Contract Compatibility, Determinism, Scope
338
+ Integrity (ships R4c), Reproducibility, Zero-Network Compliance, Agent Safety
339
+ (R8), Artifact Integrity (R9).
340
+ - New verb **`mjolnir release-trust`** emitting `mjolnir.release-trust@1` —
341
+ byte-deterministic (frozen key order, no timestamps, zero absolute paths),
342
+ per-dimension `evidence` + `determination` via the status algebra, verdict =
343
+ contract satisfaction (never a PROVEN count) with the binding system
344
+ invariant. Exit contract: 0 PASS · 1 non-PASS · 2 blocked context · 10 usage ·
345
+ 20 internal. Drift-locked by tests/contract/release-trust-contract.spec.ts
346
+ (canonical set/order, binding resolution, derivation table + terminality,
347
+ byte-stability, path-freedom).
348
+
349
+ ### Changed
350
+
351
+ - **release.yml**: the Release Trust Verdict gate is wired RELEASE-BLOCKING
352
+ pre-publish (Tests → Certification → CHANGELOG Gate → … → release-trust gate
353
+ → publish), running the BUILT binary; the verdict block + machine contract
354
+ ship with the GitHub Release (publication honesty — a missing proof renders
355
+ UNPROVEN, never omitted). No waiver path.
356
+
357
+ ### R4 blast radius audit (remediation/remote-first R4)
358
+
359
+ The blast-radius audit: a machine-verified surface manifest with its own drift lock.
360
+
361
+ ### Added
362
+
363
+ - **docs/BLAST-RADIUS-AUDIT.md** — the machine-verified surface manifest
364
+ (`npm run docs:blast-radius`): src inventory with per-area LOC, the internal
365
+ import fan-in ranking (change-blast candidates), the external dependency
366
+ allowlist, and the shipped surface (adapters, rules census, CLI flags, report
367
+ formats, frozen exit codes).
368
+ - **tests/contract/blast-radius.spec.ts** — the machine-TESTABLE boundary
369
+ contract: the committed manifest must equal a fresh render; every external
370
+ import in src/ must belong to the allowlist (`yaml`, `ts-morph`,
371
+ `web-tree-sitter`, `tree-sitter-wasms`; node builtins are platform
372
+ contracts); every CLI flag parsed must appear in the manifest; every
373
+ `process.exit(N)` in src/ must be inside the frozen set (0/1/2/10/20).
374
+
375
+ ### P6 quarantine remediation (remediation/remote-first R3)
376
+
377
+ Quarantine remediation: measured verdicts recorded, the quarantine ledger reconciled, and three rules restored to the live set.
378
+
379
+ ### Added
380
+
381
+ - **docs/QUARANTINE-REMEDIATION.md** — the ledger-first quarantine view, generated
382
+ from the live registry (`npm run docs:quarantine-ledger`) and drift-locked
383
+ (tests/contract/quarantine-ledger.spec.ts): one row per live quarantine rule
384
+ with failure-mode class, disposition, and re-measure gate; historical section
385
+ records the governed retirements.
386
+ - Python tree-sitter parse stage: `parsePythonAst` wired into the python
387
+ adapter's async `parseAst` hook (the §10 parse-or-fallback contract), with
388
+ `src/engine/python-ast.ts` structural queries — the first real python AST
389
+ substrate (the Sprint-8 "unwired" caveat is closed and re-pinned honestly).
390
+
391
+ ### Changed
392
+
393
+ - **QA-PY-007** (detectorRevision 4, AST rework): fires only on ≥2-statement
394
+ with-blocks or broad root exception types — the adjudicated FP core
395
+ (single-statement/specific-type) suppressed. Corpus: pytest-dev 167 → 11,
396
+ pallets-click 16 → 1 live findings.
397
+ - **QA-TQUAL-009** (detectorRevision 2, AST rework): skips Cypress command
398
+ chains (`cy.`-rooted — the driver awaits them) and deliberate `void`
399
+ discards. Corpus: cypress-realworld-app 10 → 0.
400
+ - **QA-PW-147** (detectorRevision 2, final attempt): AST arm fires only on real
401
+ test/it declarations — code-as-data (`test('test')` inside lint-rule test
402
+ strings) can never fire. Corpus: eslint-plugin repo 32 → 0.
403
+ - **QA-ENV-001** (detectorRevision 4, final attempt): OS-path sub-pattern
404
+ dropped (20/20 adjudicated FP — deliberate path fixtures, same undecidability
405
+ as the wave-2 host drop); locale/local-time families kept. Corpus: grafana
406
+ 7 → 4.
407
+ - Measurement: orphaned verdicts (findings the reworks suppressed) archived to
408
+ `tests/corpus/verdicts/archive/` per the established prune flow; the three
409
+ fully-reworked rules fall below the n ≥ 10 threshold and ship UNMEASURED
410
+ until owner re-adjudication (measured census 77 → 74 of 79; the
411
+ certification floor test documents the P6 invalidations).
412
+
413
+ ### P3c Jenkins (remediation/remote-first R2)
414
+
415
+ Jenkins support: a bounded Jenkinsfile scanner and the QA-CI Jenkins arms (retry masking, catchError rescue, silent swallow).
416
+
417
+ ### Added
418
+
419
+ - Jenkinsfile detection: the root `Jenkinsfile` (declarative and scripted
420
+ pipelines) is now discovered and scanned as a TEXT-target kind — a bounded,
421
+ string-aware Groovy block scanner (`sh` segments, `catchError` blocks,
422
+ `try`/`catch` pairs); no new language grammar (master-plan P3c wording).
423
+ - New rule **QA-CI-014** "try/catch swallows a verification-stage failure" —
424
+ a `try` running a gate whose `catch` neither rethrows, calls `error(...)`,
425
+ marks `currentBuild.result`, nor downgrades via `unstable()`. BORN
426
+ QUARANTINE (§15.5): opt-in via `--strict` until corpus-measured.
427
+
428
+ ### Changed
429
+
430
+ - **QA-CI-002** (detectorRevision 4): Jenkinsfile routing — the lexical
431
+ `|| true` scan now reaches `sh` strings.
432
+ - **QA-CI-008** (detectorRevision 4): Jenkinsfile arms —
433
+ `catchError(buildResult: 'SUCCESS')` wrapping a gate, and `unstable()` used
434
+ as a rescue for a failed verification stage (master-plan P3c shapes;
435
+ `buildResult: 'UNSTABLE'` is a visible downgrade and never fires).
436
+ - **QA-CI-009** (detectorRevision 3): Jenkinsfile arm — `sh` running a
437
+ verification gate with `returnStatus: true` discards the exit code.
438
+ - Measurement: sidecar + `MEASURED_FP` re-recorded for QA-CI-002/008/009
439
+ (corpus re-run: no corpus repo carries a root Jenkinsfile, so the
440
+ classified verdict evidence carries over unchanged).
441
+
442
+ ### P3b Azure DevOps (remediation/remote-first R1)
443
+
444
+ Azure DevOps support: guarded azure-pipelines.yml parsing, the QA-CI Azure arms, and the adapter's honest accounting.
445
+
446
+ ### Added
447
+
448
+ - Azure DevOps pipeline detection: `azure-pipelines.yml` at the repo root is now
449
+ discovered and scanned (`azure-pipelines` adapter, safe-YAML machinery shared
450
+ with the GitHub Actions parser — alias-bomb guard, depth cap, prototype-safe
451
+ keys; docs/AZURE-DEVOPS.md).
452
+ - New rule **QA-CI-013** "Verification gate conditioned so it can never fail the
453
+ pipeline" — `condition: failed()` rescue, `condition: false`, `enabled: false`
454
+ on Azure verification gates. BORN QUARANTINE (§15.5): opt-in via `--strict`
455
+ until corpus-measured; never silent-core.
456
+ - docs/AZURE-DEVOPS.md — platform recipe with the frozen exit-code contract.
457
+
458
+ ### Changed
459
+
460
+ - **QA-CI-001** (detectorRevision 3): Azure DevOps arm — `continueOnError: true`
461
+ on a verification step or a gate-bearing job (same mechanism, framework-tagged
462
+ `azure-pipelines`).
463
+ - **QA-CI-002** (detectorRevision 3): Azure routing — the lexical `|| true` scan
464
+ now reaches `bash:`/`pwsh:` script blocks in azure-pipelines.yml.
465
+ - **QA-CI-007** (detectorRevision 3): Azure DevOps arm — `retryCountOnTaskFailure`
466
+ on verification tasks.
467
+ - **QA-CI-008** (detectorRevision 3): Azure DevOps arm — verification gate jobs
468
+ conditioned `always()` / `succeededOrFailed()` (master-plan P3b shape).
469
+ - Measurement: sidecar + `MEASURED_FP` re-recorded at detectorRevision 3 for
470
+ QA-CI-001/002/007/008; corpus re-run showed zero QA-CI count drift (no corpus
471
+ repo carries a discoverable azure-pipelines.yml), so the existing classified
472
+ verdicts remain the measurement evidence.
473
+
474
+ ## [1.0.5] — 2026-09-10
475
+
476
+ ### Changes since 1
477
+
478
+ - chore: resync managed surface stamp to v1.0.4
479
+
12
480
  ## [1.0.4] — 2026-09-10
13
481
 
14
482
  ### Changes since 1
package/README.md CHANGED
@@ -146,7 +146,7 @@ QA impact: False-green risk (FALSE-GREEN)
146
146
  Measured FP: 11% (19 hand-classified corpus verdicts)
147
147
  FP risk: low (author estimate)
148
148
  Languages: yaml
149
- Frameworks: github-actions
149
+ Frameworks: github-actions, azure-pipelines
150
150
 
151
151
  WHAT WAS FOUND (real detector output, not a mockup)
152
152
  Job `security-scan` runs a verification gate under `continue-on-error: true`.
@@ -262,7 +262,7 @@ have no such requirement.)
262
262
 
263
263
  ## What Mjölnir finds
264
264
 
265
- **<!-- census:total-rules -->77 rules<!-- /census:total-rules -->** in four families — **test hygiene**, **test quality**,
265
+ **<!-- census:total-rules -->79 rules<!-- /census:total-rules -->** in four families — **test hygiene**, **test quality**,
266
266
  **Playwright**, **CI integrity** — over TypeScript/JavaScript, Python,
267
267
  Java, C# and GitHub Actions YAML, covering Playwright in all four bindings
268
268
  plus pytest, JUnit, TestNG, NUnit, xUnit, MSTest, Jest, Vitest and Mocha,
@@ -442,9 +442,9 @@ Rung by rung: [docs/TERMINOLOGY.md](docs/TERMINOLOGY.md).
442
442
 
443
443
  ### How much of this is measured
444
444
 
445
- **<!-- census:measured-of-total -->77 of 77<!-- /census:measured-of-total --> rules carry a false-positive rate measured against real OSS code**
445
+ **<!-- census:measured-of-total -->74 of 79<!-- /census:measured-of-total --> rules carry a false-positive rate measured against real OSS code**
446
446
  (≥ 10 hand-classified findings each — [docs/FP-AUDIT.md](docs/FP-AUDIT.md)).
447
- The other <!-- census:unmeasured -->0<!-- /census:unmeasured --> ship on the author's estimate and say so, per rule, in
447
+ The other <!-- census:unmeasured -->5<!-- /census:unmeasured --> ship on the author's estimate and say so, per rule, in
448
448
  `mjolnir explain`; `mjolnir rules --unmeasured` lists them, and every scan
449
449
  footer reports how many of the rules that actually _fired_ are measured.
450
450
 
@@ -685,7 +685,7 @@ artifacts.
685
685
  product does what the requirement asked for.
686
686
  - **A 100 is not proof of a good suite.** Whether your suite covers your
687
687
  actual risk is a different question, and this tool does not answer it.
688
- - **<!-- census:unmeasured-of-total -->0 of 77<!-- /census:unmeasured-of-total --> rules ship on an estimate**, not a measured rate — disclosed
688
+ - **<!-- census:unmeasured-of-total -->5 of 79<!-- /census:unmeasured-of-total --> rules ship on an estimate**, not a measured rate — disclosed
689
689
  per rule, not buried here.
690
690
  - **E1 is not E2.** Heuristic findings are worth reading, not worth
691
691
  applying blindly.
package/dist/cli.d.mts CHANGED
@@ -80,7 +80,7 @@ interface RuntimeCorroboration {
80
80
  * constrains TRUE-FLAKE derivation, which lives in the analysis, not
81
81
  * in the provenance label.
82
82
  */
83
- source: "playwright-json" | "junit-xml" | "jest-json" | "vitest-json";
83
+ source: "playwright-json" | "junit-xml" | "jest-json" | "vitest-json" | "playwright-trace";
84
84
  /** Number of tests executed in the finding's file (any level). */
85
85
  testsExecuted: number;
86
86
  /**
@@ -304,6 +304,62 @@ interface ScanResult {
304
304
  */
305
305
  rulesCrashed?: number;
306
306
  };
307
+ /**
308
+ * Scope Integrity block (product-gap master plan §7, R4c): the
309
+ * claimed-vs-analyzed accounting. `scopeVerdict` is PROVEN only when
310
+ * every discovered file was analyzed — no matcher exclusions, no
311
+ * unrecognized files, no parse failures, no truncation. Additive
312
+ * within schemaVersion 1.
313
+ */
314
+ scopeIntegrity?: {
315
+ /** Files discovery claimed for adapters. */
316
+ discovered: number;
317
+ /** Files that reached (and survived) the rule stage. */
318
+ analyzed: number;
319
+ /** Files excluded by the ignore matcher (counted at the walk). */
320
+ ignored: number;
321
+ /** Files the walk saw but no adapter claims. */
322
+ unrecognized: number;
323
+ /** Discovered files whose parse/analysis threw (counted, never fatal). */
324
+ parseFailed: number;
325
+ /** Named truncation events (deadline, file caps). */
326
+ truncated: number;
327
+ /** PROVEN only when analyzed ≡ claimed scope; else PARTIAL + reasons. */
328
+ scopeVerdict: "PROVEN" | "PARTIAL";
329
+ /** The named scope reasons, present only when PARTIAL. */
330
+ reasons?: string[];
331
+ };
332
+ /**
333
+ * Run Identity (R4c): the deterministic anchor binding verdict ←
334
+ * evidence ← execution ← scope ← source ← rule(rev). Present when the
335
+ * execution was machine-anchored; never fabricated.
336
+ */
337
+ runIdentity?: {
338
+ scanId: string;
339
+ inputFingerprint: string;
340
+ rulesDigest: string;
341
+ configFingerprint: string;
342
+ engineVersion: string;
343
+ };
344
+ /**
345
+ * Evidence Graph (R4c): the chain-law links (VERDICT ← EVIDENCE ←
346
+ * EXECUTION ← SCOPE ← SOURCE ← RULE(rev) ← FIXTURE ← REPRODUCTION).
347
+ * A link's `ref` is present only when its identity input exists — the
348
+ * CHAIN is always emitted so unbound links stay visible.
349
+ */
350
+ evidenceGraph?: {
351
+ chain: Array<{
352
+ link: string;
353
+ ref?: string;
354
+ }>;
355
+ runId?: {
356
+ scanId: string;
357
+ inputFingerprint: string;
358
+ rulesDigest: string;
359
+ configFingerprint: string;
360
+ engineVersion: string;
361
+ };
362
+ };
307
363
  /**
308
364
  * Local incremental cache report (Beta-to-Stable plan, M5.2). Present
309
365
  * only when the scan ran with `--cache`; additive within
@@ -848,6 +904,17 @@ declare function isValidFindingRecord$1(f: unknown): f is Omit<Finding, "ruleId"
848
904
  */
849
905
  declare function runScan$1(args: CliArgs, hooks?: ScanHooks): Promise<ScanResult>;
850
906
  //#endregion
907
+ //#region src/engine/version.d.ts
908
+ /**
909
+ * Engine version, as a leaf module: `runIdentity` (R4c) needs the engine
910
+ * version inside scan-pipeline WITHOUT importing cli.ts (a cycle — cli
911
+ * imports the pipeline). The literal follows the same discipline as
912
+ * CLI_VERSION and SARIF's driver.version: kept in sync on release by
913
+ * scripts/sync-sarif-version.cjs and guarded by the version-consistency
914
+ * spec. cli.ts re-exports this as CLI_VERSION.
915
+ */
916
+ declare const ENGINE_VERSION = "1.0.8";
917
+ //#endregion
851
918
  //#region src/cli-io.d.ts
852
919
  /**
853
920
  * Shared process IO sinks (certification-audit Phase 5, G6): the variadic
@@ -870,17 +937,6 @@ declare function runDoctorCommand(argv: string[], io?: {
870
937
  //#endregion
871
938
  //#region src/cli.d.ts
872
939
  declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniversalRules$1, fallbackWorkspace: typeof fallbackWorkspace$1, pathMatchesGlob: typeof pathMatchesGlob$1, isValidFindingRecord: typeof isValidFindingRecord$1, discoverRuntimeReport: typeof discoverRuntimeReport$1, KNOWN_RULE_IDS: ReadonlySet<string>, OVERLAP_META_BY_RULE_ID: ReadonlyMap<string, OverlapMeta>, EVIDENCE_OVERRIDES: ReadonlyMap<string, string>, SUITE_INVALIDATING_RULE_IDS: ReadonlySet<string>;
873
- /**
874
- * Tool version for `mjolnir --version`.
875
- *
876
- * A literal, not a package.json read: the shipped artifact is a single
877
- * bundled `dist/cli.mjs`, so resolving package.json at runtime depends on
878
- * where the file happens to sit after install. This follows the same
879
- * discipline as SARIF's `driver.version` — kept in sync by
880
- * `scripts/sync-sarif-version.cjs` on release and guarded by
881
- * `tests/version-consistency.spec.ts` locally.
882
- */
883
- declare const CLI_VERSION = "1.0.4";
884
940
  /** A usage-error detail: the offending token, when one exists. */
885
941
  interface UsageErrorDetail {
886
942
  /** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
@@ -1050,4 +1106,4 @@ declare function runHelpCommand(argv: string[], io?: {
1050
1106
  }): number;
1051
1107
  declare function isEntryPoint(): boolean;
1052
1108
  //#endregion
1053
- export { CLI_VERSION, type CliArgs, EVIDENCE_OVERRIDES, KNOWN_RULE_IDS, OVERLAP_META_BY_RULE_ID, type Output, SUITE_INVALIDATING_RULE_IDS, type ScanHooks, UsageErrorDetail, buildUniversalRules, discoverRuntimeReport, err, exitForFindings, fallbackWorkspace, internalErrorMessage, isEntryPoint, isValidFindingRecord, levenshtein, main, nearestFlags, out, parseArgs, pathMatchesGlob, runBadgeCommand, runBaselineCommand, runCiInstall, runCreateRuleCommand, runDebtCommand, runDiffCommand, runDoctorCommand, runDoctorPlaywright, runExplainCommand, runFixCommand, runForensicsCommand, runHandoverCommand, runHelpCommand, runImpactCommand, runInitCommand, runMutationCommand, runPrCommentCommand, runPwReportCommand, runRulesCommand, runScan, runScanCommand, runStatsCommand, runSuppressions, runTriageCommand, runVerifyCommand, usageErrorMessage };
1109
+ export { ENGINE_VERSION as CLI_VERSION, type CliArgs, EVIDENCE_OVERRIDES, KNOWN_RULE_IDS, OVERLAP_META_BY_RULE_ID, type Output, SUITE_INVALIDATING_RULE_IDS, type ScanHooks, UsageErrorDetail, buildUniversalRules, discoverRuntimeReport, err, exitForFindings, fallbackWorkspace, internalErrorMessage, isEntryPoint, isValidFindingRecord, levenshtein, main, nearestFlags, out, parseArgs, pathMatchesGlob, runBadgeCommand, runBaselineCommand, runCiInstall, runCreateRuleCommand, runDebtCommand, runDiffCommand, runDoctorCommand, runDoctorPlaywright, runExplainCommand, runFixCommand, runForensicsCommand, runHandoverCommand, runHelpCommand, runImpactCommand, runInitCommand, runMutationCommand, runPrCommentCommand, runPwReportCommand, runRulesCommand, runScan, runScanCommand, runStatsCommand, runSuppressions, runTriageCommand, runVerifyCommand, usageErrorMessage };