Skip to content

ci: fix Node-version drift, add composite-action schema validation#7408

Merged
JSONbored merged 1 commit into
mainfrom
claude/ci-hygiene-followups
Jul 20, 2026
Merged

ci: fix Node-version drift, add composite-action schema validation#7408
JSONbored merged 1 commit into
mainfrom
claude/ci-hygiene-followups

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Two independent hygiene fixes from the follow-up audit, bundled since both are small and neither conflicts with the other.

Node-version drift. 13 release/publish-adjacent workflows hardcoded node-version: 24.18.0 while .nvmrc pins 22 — every PR-facing workflow (ci.yml, ui-preview.yml, ui-deploy.yml, ui-sentry-release.yml, mcp-release-candidate.yml) already correctly used node-version-file: .nvmrc. Investigated before touching anything: none of the published packages' own engines fields (@loopover/engine, mcp, ui-kit all >=22.0.0; miner >=22.13.0) require Node 24, no comment anywhere explains the choice, and git history shows each was just whatever version was current when that particular publish workflow was first scaffolded, never reconciled against .nvmrc. Switched all 13 files (18 occurrences) to node-version-file: .nvmrc. Left the two files already pinned to 22.23.1 alone (gittensor-impact.yml, visual-capture-fallback.yml) — same major version as .nvmrc, not actually inconsistent, lower value to touch than the real 24-vs-22 mismatch.

Composite-action schema validation. This repo's actionlint — both the npm wrapper and the raw upstream binary, tested directly with no wrapper — does not support .github/actions/**/action.yml files at all. Confirmed a genuine, long-standing limitation (rhysd/actionlint#46 and #401, open since 2021), not a configuration gap: every file passed to actionlint is parsed as a workflow regardless of shape, so it errors on runs/inputs/outputs as unexpected top-level keys rather than validating the composite action. scripts/lint-composite-actions.mjs is the closest available substitute — validates against GitHub's own official action-metadata JSON Schema (vendored locally, not fetched live, so this doesn't add a network dependency to CI), plus a dedicated check for the one thing the schema alone doesn't catch: every run: step in a composite action needs an explicit shell:, unlike a top-level workflow job which defaults to bash. Verified it actually catches real problems, not just that it exists — fed it a deliberately broken action file (missing shell:, an invalid uses: type) and confirmed both surface clearly.

Wired into test:ci and a new "Lint composite actions" step in ci.yml, alongside "Lint workflows". Also added .github/actions/** to the backend path filter — it was missing entirely (only .github/workflows/** was covered), so a PR editing only a composite action file would previously trigger neither the new check nor the existing actionlint step at all.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused — two independent CI/tooling hygiene fixes, no application code.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves — N/A, maintainer-initiated build-tooling work.

Validation

  • git diff --check
  • npm run actionlint
  • npm run lint:composite-actions (the new check itself, run against the real repo — clean)
  • npm run typecheck
  • Investigated the Node-version question with real evidence before changing anything: checked every published package's engines field, searched for any explanatory comment, and checked git blame/history for the original introducing commit — found nothing justifying 24 over 22.
  • Confirmed actionlint's composite-action gap directly against the real upstream binary (not just the npm wrapper), and searched for prior art before concluding there's no fix — this is a documented, unresolved upstream limitation.
  • Proved the new validator actually works as a safety net, not just that it runs: fed it a deliberately broken action.yml (missing shell:, invalid uses: type) in a throwaway fixture and confirmed both error classes surface with clear messages.
  • All 13 touched workflow files re-parsed as valid YAML after the version-pin edits.
  • Ran the full targeted CI-meta-test sweep (19 files covering test:ci composition, the backend filter, the composite-action setup, observability, codecov policy, predicted-gate) — 181/181 passing, confirming the test:ci script insertion and filter addition didn't break any exact-match assertions.
  • npm run test:coverage — not run in full; this PR touches no src/** file.
  • npm audit --audit-level=moderateajv added as a new devDependency (already present transitively at two different versions; added directly and explicitly rather than relying on an unpinned transitive resolution). No known advisories for the version installed.

If any required check was skipped, explain why: this PR is CI/build-tooling configuration — the skipped commands aren't applicable to source-only checks, and every actually-relevant command (including proving the new validator catches real bugs, not just that it exists) is listed above.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • New devDependency (ajv) is a well-established, widely-used JSON Schema validator already present transitively in this repo's own dependency tree at two different versions — added explicitly for a pinned, predictable resolution rather than relying on whatever a future npm install happens to resolve transitively.

Notes

Two independent hygiene fixes from the follow-up audit, bundled since both
are small and neither conflicts with the other.

Node-version drift: 13 release/publish-adjacent workflows hardcoded
node-version: 24.18.0 while .nvmrc pins 22 -- every PR-facing workflow
(ci.yml, ui-preview.yml, ui-deploy.yml, ui-sentry-release.yml,
mcp-release-candidate.yml) already correctly used node-version-file:
.nvmrc. Investigated before touching anything: none of the published
packages' own engines fields (@loopover/engine, mcp, ui-kit all >=22.0.0;
miner >=22.13.0) require Node 24, no comment anywhere explains the choice,
and git history shows each was just whatever version was current when
that particular publish workflow was first scaffolded, never reconciled
against .nvmrc. Switched all 13 (18 occurrences) to node-version-file:
.nvmrc for consistency. Left the two files already pinned to 22.23.1
alone (gittensor-impact.yml, visual-capture-fallback.yml) -- same major
version as .nvmrc, not actually inconsistent, lower value/higher risk to
touch than the real Node-24-vs-22 mismatch.

Composite-action schema validation: this repo's actionlint (both the npm
wrapper and the raw upstream binary, tested directly) does not support
.github/actions/**/action.yml files at all -- confirmed a genuine,
long-standing limitation (rhysd/actionlint#46 and #401, open since 2021),
not a configuration gap. Every file passed to actionlint is parsed as a
workflow regardless of shape, so it errors on runs/inputs/outputs as
unexpected top-level keys rather than actually validating the composite
action. scripts/lint-composite-actions.mjs is the closest available
substitute: validates against GitHub's own official action-metadata JSON
Schema (vendored locally at scripts/schemas/github-action.schema.json,
not fetched live, so this check doesn't depend on network access in CI),
plus a dedicated check for one thing the schema alone doesn't catch --
every run: step in a composite action needs an explicit shell:, unlike a
top-level workflow job which defaults to bash. Verified it actually
catches real problems, not just that it exists: fed it a deliberately
broken action file (missing shell:, an invalid uses: type) and confirmed
both classes of error surface clearly.

Wired into test:ci (npm run lint:composite-actions) and a new "Lint
composite actions" step in ci.yml, alongside the existing "Lint workflows"
step. Also added .github/actions/** to the backend path filter -- it was
missing entirely (only .github/workflows/** was covered), so a PR editing
ONLY a composite action file would previously trigger neither this new
check nor the existing actionlint step at all.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui cc55d8c Commit Preview URL

Branch Preview URL
Jul 20 2026, 07:19 AM

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.21%. Comparing base (cd52bed) to head (cc55d8c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7408   +/-   ##
=======================================
  Coverage   91.21%   91.21%           
=======================================
  Files         716      716           
  Lines       72789    72789           
  Branches    20859    20859           
=======================================
+ Hits        66392    66393    +1     
+ Misses       5355     5354    -1     
  Partials     1042     1042           
Flag Coverage Δ
rees 88.56% <ø> (ø)
shard-1 35.90% <ø> (ø)
shard-2 41.35% <ø> (+0.02%) ⬆️
shard-3 38.95% <ø> (ø)
shard-4 39.92% <ø> (ø)
shard-5 27.14% <ø> (ø)
shard-6 31.24% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 1 file with indirect coverage changes

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 20, 2026
@JSONbored JSONbored self-assigned this Jul 20, 2026
@JSONbored
JSONbored merged commit 8bd60f0 into main Jul 20, 2026
20 checks passed
@JSONbored
JSONbored deleted the claude/ci-hygiene-followups branch July 20, 2026 07:27
@github-actions github-actions Bot mentioned this pull request Jul 20, 2026
12 tasks
JSONbored added a commit that referenced this pull request Jul 21, 2026
… .nvmrc

node-version-file: .nvmrc (introduced repo-wide by 8bd60f0 / #7408) broke
this one job: it deliberately has no actions/checkout step (see the file
header -- it never runs fork/PR code), so .nvmrc doesn't exist on the
runner and Setup Node has failed on every run since. That failure trips
the "Record FAILED deployment for Reviewbot" step, which writes a
permanent failure GitHub Deployment status -- so ORB's preview-URL
discovery (preview-url.ts's getLatestDeploymentStatus) short-circuits on
that failure and never even checks the separately-successful Cloudflare
Workers Builds deployment, breaking the "after" screenshot/scroll-preview
for every visual PR repo-wide, including this one on PR #7581.

Hardcodes node-version instead, matching the same already-established
exception in gittensor-impact.yml and visual-capture-fallback.yml.
JSONbored added a commit that referenced this pull request Jul 21, 2026
… report, contributor trust profiles (#7581)

* fix(review): repair the public decision-accuracy metric's reversal detection

The reversal query derived "mistake" status from a PR's own state after a
close/merge, which can never detect a merge undone by a separate revert PR
(a merged PR's state never returns to open on GitHub) and was scoped only
to a frozen own-ledger snapshot. Reuse the existing reversal_reopened/
reversal_reverted events already recorded elsewhere, and blend in live
fleet-wide accuracy across registered self-hosted instances so the public
number reflects the current fleet, not a historical snapshot. Also surfaces
the fleet's anti-gaming detection count (#2350) on the same payload.

Refs #7567

* feat(site): add a public fairness report page

Adds /fairness, linked from the homepage's Decision accuracy tile: the
full 8-week accuracy trend, per-repo breakdown, and anti-gaming-flags-
caught count, plus a short methodology note. Reuses the existing
/v1/public/stats payload rather than adding a new endpoint -- aggregate
counts only, no PR content or contributor identities.

Refs #7567

* feat(review): add internal contributor trust profiles

Per-login, per-repo gate-decision accuracy (mirrors computeGateEval,
keyed by contributor_gate_history instead of the actor-free review_audit)
and fairness-outlier detection that flags deviation from a project's
median in either direction, never asserting fault. Composes with per-repo
submission counts and moderation-violation history into one trust profile
per contributor, plus a backfill mechanism reconstructing historical gate
history from review_audit + pull_requests.author_login for PRs that
predate contributor_gate_history.

Internal, bearer-gated routes only -- never exposed publicly. Config-as-
code throughout: a global LOOPOVER_FAIRNESS_ANALYTICS flag overridable via
the self-repo's .loopover.yml (mirroring publicStats/ops), plus a
per-repo fairnessAnalyticsMode so any installed repo can opt its own gate
decisions and moderation history out of the aggregation via its own
private .loopover.yml.

Refs #7567

* fix(site): guard against a fleetAccuracy-less /v1/public/stats response

Discovered via manual verification: the homepage stats band and the new
fairness report page both read data.fleetAccuracy without a null guard,
which crashes (and is caught by the route error boundary) against any
currently-live API response, since that field doesn't exist until the
backend change ships. Add defensive optional chaining so both surfaces
degrade to the own-ledger accuracy number instead, matching the intended
fallback behavior once the fields genuinely diverge only during a
frontend-ahead-of-backend deployment window.

Refs #7567

* chore(openapi): regenerate spec after rebase onto main

The rebase conflict resolution took main's openapi.json wholesale for
one commit; regenerate from the final merged schemas so both this PR's
and #7521's additions are present.

* fix(review): sync fairnessAnalytics config docs into loopover.full.yml

The per-repo fairnessAnalyticsMode setting and fleet-wide fairnessAnalytics
block were only documented in .loopover.yml.example, drifting from
config/examples/loopover.full.yml which must stay byte-identical from the
canonical marker onward (test/unit/config-templates.test.ts).

* fix(review): remove stray null byte from contributor-gate-eval.ts

A literal NUL byte had been typed into the (login, project) map-key
template literal instead of a separator character, making git and other
tooling treat the whole file as binary (no diff, no line-level coverage
attribution). Replaced with ':', matching the composite-key separator
convention used elsewhere (rag.ts, inline-comments-select.ts).

* fix(ci): pin Node version in ui-preview-deploy.yml instead of reading .nvmrc

node-version-file: .nvmrc (introduced repo-wide by 8bd60f0 / #7408) broke
this one job: it deliberately has no actions/checkout step (see the file
header -- it never runs fork/PR code), so .nvmrc doesn't exist on the
runner and Setup Node has failed on every run since. That failure trips
the "Record FAILED deployment for Reviewbot" step, which writes a
permanent failure GitHub Deployment status -- so ORB's preview-URL
discovery (preview-url.ts's getLatestDeploymentStatus) short-circuits on
that failure and never even checks the separately-successful Cloudflare
Workers Builds deployment, breaking the "after" screenshot/scroll-preview
for every visual PR repo-wide, including this one on PR #7581.

Hardcodes node-version instead, matching the same already-established
exception in gittensor-impact.yml and visual-capture-fallback.yml.

* fix(review): fall back to a placeholder for a missing after-GIF

The scroll-preview "after" slot resolved to undefined (rendered as a bare
"—") whenever there was no live preview page to capture, unlike the
screenshot slot which already substitutes a loading/failed placeholder
(afterPlaceholder) in the same situation. That made "GIF capture still
building/failed" indistinguishable from "GIF capture isn't configured for
this repo at all". actions_fallback never produces a GIF (only static
PNGs), so there's no cached fallback artifact to look up here the way
resolveFallbackAfterShot does for the screenshot slot -- the placeholder
substitution is the whole fix.

* test(review): cover the missing-limit branch on the backfill route

Closes the last codecov/patch gap on PR #7581 (routes.ts's typeof
body?.limit === "number" ternary only had its truthy arm exercised).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant