Skip to content

[quality] All 8 surviving mutants in validate-architecture-assets.mjs are equivalent — annotate them so the gate can reach 100% #1806

Closed

Description

@hivecommons-hive

Finding

scripts/lib/validate-architecture-assets.mjs is the callable core of the asset
security gate (extracted in #1787). A StrykerJS run over it scores 97.44%,
with 8 surviving mutants. I analysed all 8 individually: none of them is
killable by a behaviour test.
They are equivalent mutants and dead data, so
the mutation backlog cleared in #1788 and #1790 cannot be driven to zero here,
and every weekly scheduled run re-reports the same 8 for someone to
re-investigate.

This is not a coverage gap. The file is at 100% line and region coverage
under the --require-source-files / --check-source-file-regions 100 gate; the
mutants survive because the mutated expressions have no observable effect, not
because no test runs them.

Run / provenance

# clone of cncf/endusers at main 5511207
npm run test:mutation -- --mutate 'scripts/lib/validate-architecture-assets.mjs,scripts/lib/duplicate-check.mjs'

Local run 2026-10-22, 9m32s: 312 killed, 2 timeout, 8 survived, 0 no-coverage.
duplicate-check.mjs scored 100.00%. For contrast I also ran
--mutate 'src/components/shared/*.js,src/components/hooks/*.js' at the same
revision: 152 mutants, 100.00%, zero survivors — the rest of the tree really
is clean, which is what makes these 8 stand out.

Why each survivor is unkillable

Line Mutant Why no test can kill it
107 reason: 'holds only font files…' → "" Dead data. Nothing reads .reason; exemptTopLevelDirs is consulted only through .get(), and only .extensions is used.
119 'web app manifest; JSON data…' → "" Dead data. exemptTopLevelFiles is consulted only through .has(); the Map value is never read.
120 'crawler directives; plain text…' → "" Same as line 119.
152 validateAsset(file, false, …) → true Unreachable by design. quality is consumed only by validateSvg(), and the only exemption's FONT_EXTENSIONS (.ttf/.woff/.woff2) contains no .svg, so validateSvg() is never called on this path.
268 writeFileSync(path, source, 'utf8') → "" Truly equivalent. Node treats '' as the default encoding: fs.writeFileSync(p, 'hé', '') round-trips byte-identically to 'utf8' (verified on node 22/26).
348 same as 268 Same as 268.
417 if (!stats) return 'missing' → "" Equivalent. Callers compare assetRootKind() only against 'symlink' (line 394) and 'directory' (line 402); every other value takes the same branch.
419 ? 'directory' : 'other' → ? 'directory' : "" Same as 417 — the 'other' arm is only ever "not 'directory'".

Recommendation

Annotate the eight sites so Stryker stops reporting them, which makes the file's
score reflect reality and leaves runtime behaviour untouched. This is the
smallest additive change; it needs no test edits and no API change.

Apply in scripts/lib/validate-architecture-assets.mjs:

@@ -104,8 +104,9 @@
       'fonts',
       {
+        // Stryker disable next-line StringLiteral: reviewer documentation; no code reads .reason
         reason:
           'holds only font files (.ttf/.woff/.woff2); never SVG or markup',
         extensions: FONT_EXTENSIONS,
       },
@@ -117,4 +118,5 @@
   const exemptTopLevelFiles = new Map([
+    // Stryker disable next-line StringLiteral: reviewer documentation; only .has() reads this Map
     ['manifest.json', 'web app manifest; JSON data, never parsed as markup'],
+    // Stryker disable next-line StringLiteral: reviewer documentation; only .has() reads this Map
     ['robots.txt', 'crawler directives; plain text, never parsed as markup'],
   ]);
@@ -150,3 +152,4 @@
           for (const file of walk(path)) {
+            // Stryker disable next-line BooleanLiteral: quality is read only for .svg, which FONT_EXTENSIONS excludes
             validateAsset(file, false, exemption.extensions);
           }
@@ -266,3 +269,4 @@
     if (!quality) {
       if (shouldFix && source !== original) {
+        // Stryker disable next-line StringLiteral: node treats '' as the default utf8 encoding
         writeFileSync(path, source, 'utf8');
       }
@@ -347,2 +351,3 @@
       }
+      // Stryker disable next-line StringLiteral: node treats '' as the default utf8 encoding
       writeFileSync(path, source, 'utf8');
     }
@@ -415,5 +420,7 @@
   function assetRootKind(dir) {
     const stats = lstatSync(dir, { throwIfNoEntry: false });
+    // Stryker disable next-line StringLiteral: callers only compare against 'symlink' and 'directory'
     if (!stats) return 'missing';
     if (stats.isSymbolicLink()) return 'symlink';
+    // Stryker disable next-line StringLiteral: callers only compare against 'symlink' and 'directory'
     return stats.isDirectory() ? 'directory' : 'other';
   }

After this the file should report 100.00% with 0 survivors; re-run the command
above to confirm. The break threshold in stryker.config.mjs is unaffected —
it is a floor, and this only removes noise from above it.

Decision taken by hive

Two shapes were possible for lines 107/119/120: annotate the dead reason text,
or delete it and move the wording into the surrounding comments (turning
exemptTopLevelFiles into a Set). I chose annotation because it is additive,
keeps the reviewer-facing reason adjacent to the entry it justifies — which is
the stated purpose of those Maps — and changes no runtime behaviour. Deleting
the field would change the data structure of a security gate for a cosmetic
score, which is the worse trade.

Why this is an issue and not a hold-gated PR

The entire change is to scripts/lib/validate-architecture-assets.mjs, which is
source, not test code. The quality lane is restricted to test-only changes, so
it cannot open the PR for this one; it needs a human or a lane that may touch
source. Nothing here lives in .github/workflows/, so there is no token
restriction — only a lane-scope one. The diff above is mechanical and complete.

Priority

  • Impact: medium — no behaviour is wrong today; the cost is a permanently
    sub-100% score on a security gate and recurring re-investigation of 8
    known-benign mutants on every weekly cron.
  • Effort: low — eight comment lines, verified by re-running one command.

🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 5511207

— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88

Activity

  1. added
    testingApproved by a Hive merger/owner for auto-merge on green CI
    agent/qualityApproved by a Hive merger/owner for auto-merge on green CI
    stage/010-actionableTriaged; eligible for the hive to pick up
    on Oct 22, 2026
  2. mrbobbytables commented on Oct 22, 2026

    @mrbobbytables
    Member

    Agreed on all eight; the same analysis was reached independently in the Group F pass. #1808 (in the merge queue) annotates exactly these sites in scripts/lib/validate-architecture-assets.mjs (lines 107, 119, 120, 152, 268, 348, 417, 419 at 5511207) with Stryker disable directives carrying the reason, and the scoped run reports them as Ignored with 0 survivors. #1807 documents the directive convention in CONTRIBUTING, including that an Ignored mutant does not need an issue. Closing in favour of #1808.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions