Skip to content

refactor: expose awards validation core for mutation attribution - #1757

Merged
mrbobbytables merged 2 commits into
cncf:mainfrom
mrbobbytables:copilot/awards-validator-core
Oct 21, 2026
Merged

mrbobbytables merged 2 commits into
cncf:mainfrom
mrbobbytables:copilot/awards-validator-core

Conversation

@mrbobbytables

Copy link
Copy Markdown
Member

Summary

Implement the maintainer-approved, one-validator callable-core pilot for
#1752.

  • Extract existing awards rules into
    scripts/lib/validate-awards.mjs::validateAwards(data, staticRoot), returning
    structured findings with invocation-local state and an explicit assets root.
  • Keep scripts/validate-awards.mjs as the input/reporting adapter. JSON loading,
    input locations, exit behavior, diagnostic formatting and success output remain
    unchanged.
  • Exercise the existing 30 awards-rule cases directly, retaining real temporary
    files for asset checks. Keep four actual subprocess contracts for CLI success,
    validation diagnostics, missing input and malformed JSON.
  • Assert exact metadata findings where diagnostic-contract mutants exposed
    imprecise assertions, including avoiding cascading entry errors for a
    non-array awards collection.

The extracted core is covered by the existing scripts/lib/**/*.mjs mutation
target. No Stryker internals, child-output bridge, generic fixture-helper change,
other validator conversion, workflow edit, mutation-glob change or threshold
change is included.

Measured mutation evidence

Using the installed Stryker/TAP runner 10.0.0 on local Node v26.11.1:

npm run test:mutation -- \
  --mutate scripts/lib/validate-awards.mjs --concurrency 2

Final result: 101 killed, 0 survived, 0 NoCoverage, 0 timeouts, 0 errors;
100% mutation score
, with the existing break threshold of 83 unchanged.
Known inclusive-year-boundary and same-year-ordering mutants are killed.

The first direct-core run was 95 killed / 6 survived / 0 NoCoverage. Those six
changed metadata diagnostics or added misleading per-entry errors for an invalid
collection; strengthening the existing diagnostic-contract assertions killed
them without changing validation logic.

This is a scoped core result, not a full-tree baseline or a claim about the
remaining CLI validators. Its 101 mutants are not the same target set as the
historical 104-mutant top-level-script pilot.

Verification

  • node --test tests/validate-awards.test.mjs: 34 passed.
  • npm run test:unit:coverage:check: 3,026 passed; source 100% lines/regions,
    harness 100% lines / 99.77% regions; all enforced per-file floors retained.
  • All six repository lint gates, the dependency-audit allowlist gate and
    npm run build:production passed.
  • AST comparison confirmed the extracted validation statements and host policy
    match the original script. The checked-in awards file still validates through
    the real CLI.

Based on main 342b7f8; implementation commit
f47cef0a650bba3c4d7a0ceb24831b340d4db7ac.

Related to #1752, deliberately not an automatic closing reference.
The maintainer approved this bounded pilot; broader rollout and final issue
closure remain separate decisions. Leave unmerged for review.

Keep the awards CLI as the input/reporting adapter and exercise its extracted rules directly in the existing mutation scope. Preserve CLI diagnostics and filesystem behavior with real subprocess contracts; no runner hooks, workflow paths or thresholds change.

Signed-off-by: mrbobbytables <mrbobbytables@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@netlify

netlify Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit 3a3ca3d
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6ad924177205580e933c4d10
😎 Deploy Preview https://deploy-preview-1757--endusers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Architecture review (hold-gated, non-merging)

The extraction is clean and faithful: scripts/lib/validate-awards.mjs is a pure, side-effect-free validateAwards(data, staticRoot) function, and scripts/validate-awards.mjs is reduced to the I/O adapter (read file, call core, report/exit). Diffed the moved block against main line-for-line — rule logic, field list, URL host allow-lists and the logo path-containment check are unchanged, only lifted out of the CLI script. This matches the maintainer-approved #1752 pilot shape and the same core/adapter split already used for validate-utils.mjs/url-safety.mjs. No concerns with the structural change itself.

On the one red check: the CodeQL "Incomplete regular expression for hostnames" alert on tests/validate-awards.test.mjs:246 is not new behavior introduced by this PR — that exact fixture string ('https://www.cncf.io.evil.example/announcements/x') already exists verbatim on main (currently at line 224 of the same file); this PR only reorganizes the file around it. The underlying validator (url-safety.mjs) doesn't build a regex from the host at all — it does a string-suffix check via allowedHostSuffixes, not RegExp, so there's no live hostname-regex vulnerability in this codepath. CodeQL appears to be re-attributing a pre-existing (likely dismissible) alert as "new in this PR" because of the line shift from the refactor, not flagging a regression this diff caused. Worth a maintainer dismiss-with-reason on the alert rather than blocking this PR on it; if the string keeps tripping CodeQL across future diffs in this file, a separate issue to suppress/annotate that specific alert would be the right fix, not something to fold into this core-extraction PR.

Not pushing/merging — fork PR, hold-gated review only.


🐝 Hive Agent: architect | Instance: hosted-available-lke648397-260827-5n31 | SHA: unknown

— hive: agent=architect backend=copilot model=claude-opus-4-6 copilot=1.0.88

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against the tree at f47cef0: the extraction is faithful and the tests are equivalent-or-stronger. One CI-blocking item: the new CodeQL alert on a test fixture.

What I checked:

  • Extraction fidelity — the block removed from scripts/validate-awards.mjs and the body of scripts/lib/validate-awards.mjs::validateAwards are token-identical; the only changes are invocation-local errors, parameterized staticRoot/awardsLogoRoot, and the comment's relative path to validate-utils.mjs. CLI adapter behavior (input location, reportAndExit, success output, data.awards.length after exit-on-error) is unchanged.
  • Mutation target claim — stryker.config.mjs:62 mutates scripts/lib/**/*.mjs, so the new core is inside the existing PR-lane target as the body states; the break: 83 threshold is untouched, and the "Mutation testing" check is green.
  • Tests — the 30 ported cases assert the same or stricter contracts (several moved from assert.match on stderr to exact deepEqual on structured findings), and four subprocess tests retain the real CLI contracts (success output, diagnostics/exit 1, ENOENT, SyntaxError). The ..-escape and non-string-logo edge cases survive the port (tests/validate-awards.test.mjs:316-343).

The blocking item — the CodeQL check fails with one new high-severity alert: "This string, which is used as a regular expression, has an unescaped '.' before 'cncf.io'", anchored at tests/validate-awards.test.mjs:246 (the deliberate https://www.cncf.io.evil.example/… lookalike fixture). The sink is the dynamically built regex in the allow-list test (new RegExp(​${field} must be on ​) at ~line 252): CodeQL's taint tracking connects the fixture string to a RegExp construction and applies js/incomplete-hostname-regexp. No production code builds a regex from these values — checkUrl in scripts/lib/url-safety.mjs:99-170 uses new URL plus exact/endsWith('.' + domain) host matching — so the alert is a false positive in effect, but it keeps the PR UNSTABLE and the automerge/queue tooling will not pass it.

Two clean resolutions, either of which the author can do without touching production code:

  1. Replace the dynamic regex with a plain string check, e.g. errors.some((e) => e.severity === 'error' && e.message.startsWith(​${field} must be on ​)) — removing the RegExp sink kills the alert outright; or
  2. A maintainer dismisses the alert as a test-fixture false positive.

Option 1 is preferable since it needs no maintainer action. Everything else looks correct to me; the Refs #1752 non-closing citation is justified in the body (bounded one-validator pilot, rollout decision deliberately left open). This is a fork branch, so the change has to come from the author.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Quality lane — red CodeQL check triaged (no merge, no push)

The required CodeQL check is failing on f47cef0. It is PR-local, not a shared CI incident, and the cause is one line the PR introduced.

Evidence

Check run 119421593381 reports "1 new alert including 1 high severity security vulnerability". Its single annotation:

tests/validate-awards.test.mjs:246 — Incomplete regular expression for hostnames: This string, which is used as a regular expression, has an unescaped . before cncf.io, so it might match more hosts than expected.

Line 246 is the hostile negative-test fixture 'https://www.cncf.io.evil.example/announcements/x'.

Why it is PR-local

On main (342b7f8) the same test asserted with result.stderr.includes(field) — no regular expression was built. This PR replaces that with a dynamic construction inside the for…of:

for (const [field, url] of [
  ['announcementUrl', 'https://www.cncf.io.evil.example/announcements/x'],
  ...
]) {
  assertError(..., new RegExp(`${field} must be on `));
}

field and url are destructured from the same tuple literals, so CodeQL's element inference cannot keep them apart and concludes the hostile host string reaches new RegExp. Substantively this is a false positive — url is only ever fixture input, never a pattern — but js/incomplete-hostname-regexp is a high-severity rule and the check stays red regardless.

Suggested replacement

Carry the expected pattern as a literal RegExp in the tuple. No string reaches a regex position, so the rule has nothing to analyse, and the pattern text is byte-identical to what the PR asserts today — no assertion strength is traded away.

test('rejects a link field whose host is outside the allow-list', () => {
  for (const [field, url, message] of [
    [
      'announcementUrl',
      'https://www.cncf.io.evil.example/announcements/x',
      /announcementUrl must be on /,
    ],
    [
      'caseStudyUrl',
      'https://evil.example/case-studies/acme',
      /caseStudyUrl must be on /,
    ],
    ['talkUrl', 'https://vimeo.example/9', /talkUrl must be on /],
  ]) {
    assertError(
      validateFixture(awardsFixture([{ ...validEntry, [field]: url }])),
      message,
    );
  }
});

Verification of the replacement

Applied to this PR's head (f47cef0) locally, Node v26.11.1, installed Stryker/TAP runner 10.0.0:

  • node --test tests/validate-awards.test.mjs → 34 passed, 0 failed
  • npx prettier --check tests/validate-awards.test.mjs and npx eslint tests/validate-awards.test.mjs → clean
  • npm run test:mutation -- --mutate scripts/lib/validate-awards.mjs --concurrency 2 → 101 killed, 0 survived, 0 NoCoverage, 0 timeouts, 0 errors; 100.00%, against the unchanged break threshold of 83

That is the same 101/0/100% this PR reports, so the change costs the pilot nothing — it only removes the dynamic-regex construction CodeQL objects to.

I have not verified this against CodeQL itself; only a fresh run on the updated head can confirm the alert clears.

Why no push from me

This PR's head is mrbobbytables/endusers:copilot/awards-validator-core, a fork I cannot push to, so this is a comment rather than a commit. The one-test edit above is the whole change. Everything else on the PR is green, including Mutation testing, End-to-end coverage and all six lint gates.

Preserve the host-rejection assertions without dynamic RegExp construction that confuses CodeQL tuple-value inference. Keep the hostile hostname fixtures and production validation unchanged.

Signed-off-by: mrbobbytables <mrbobbytables@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mrbobbytables

Copy link
Copy Markdown
Member Author

Fixed the failed CodeQL check in 3a3ca3d. The host-rejection test now carries literal expected regex patterns in its fixture tuples instead of constructing RegExp from a tuple-derived string. Hostile hostname inputs and assertion meaning are unchanged; production code is untouched. Fresh CodeQL check 119475296321 is SUCCESS on the repaired commit, with no suppression or alert dismissal. All 34 focused tests pass and the normal scoped mutation run remains 101 killed / 0 survived / 0 NoCoverage / 0 timeouts / 0 errors (100%, threshold83 unchanged). Remaining CI continues normally; no merge requested.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct to me. I verified this by reading the tree at the PR head, not just the diff.

What I checked:

  • Faithful extraction. The validation body moved from scripts/validate-awards.mjs into scripts/lib/validate-awards.mjs:30-121 is statement-for-statement identical to the removed code (same lastYear = Math.max(entry.year, 2015) ordering guard, same resolve-then-prefix logo containment check at scripts/lib/validate-awards.mjs:97-105, same host allow-lists). The only changes are invocation-local errors/awardsLogoRoot and the explicit staticRoot parameter.
  • Adapter unchanged. scripts/validate-awards.mjs keeps JSON loading, reportAndExit, and the success line; package.json:52 (validate:awards) still points at it, and running it against the checked-in data/awards.json at this head prints Validated 15 award entries with exit 0.
  • Tests. node --test tests/validate-awards.test.mjs passes 34/34 locally at head 3a3ca3d. The converted cases now assert exact finding objects (e.g. the non-array-awards case at tests/validate-awards.test.mjs:43-50 pins a single error with no cascading per-entry noise), and four real subprocess contracts remain (tests/validate-awards.test.mjs:369-400) covering CLI success, diagnostics, missing input and malformed JSON.
  • Scope matches the body. 3 files changed, exactly as described; no workflow, Stryker config, or threshold edits. The new core falls under the existing scripts/lib/** mutation target, and the Mutation testing check is green on this head.

The PR body says Related to #1752, deliberately not an automatic closing reference and "Leave unmerged for review" — consistent with the bounded one-validator pilot it describes, so the non-closing reference looks intentional, not an oversight. No findings from me; merge timing is the maintainer's call as the author requested.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant