chore/v10-frontmatter-node · closes #3477 · #3461 attempted, reverted, premise disprovedOne fix ships here, with the evidence this milestone's merge bar asks for: a test that failed before it and passes after. The other was attempted, broke a gate in CI, and was reverted; that failure is kept on this page because it disproves a premise written in #3461 itself.
The audit that created this milestone found 103 unit tests passing on a helper that was dead in production, because the fixtures mirrored the bug. So for #3477 the test is not the whole proof: the container was built and the server was actually run.
#3461's own comment says the 7 empty mcpServers: [] declarations are deletable today because
"no test can depend on an empty declaration", and this PR originally acted on that. It is false.
tests/performance/test-mcp-overhead.sh exists precisely to depend on it:
# 1. Every agent .md file has an explicit mcpServers field
const emptyField = /^mcpServers:\s*\[\s*\]/m.test(fm);
const hasField = /^mcpServers:/m.test(fm);
...
echo "FAIL [${rest}]: missing mcpServers field"
The test distinguishes empty from absent and requires the field either way. An explicit
mcpServers: [] is not leftover cruft, it is a deliberate marker meaning "this agent was considered
for MCP and wants none". Deleting it makes an agent indistinguishable from one where somebody simply forgot.
FAIL [demo-producer]: missing mcpServers field
FAIL [emulate-engineer]: missing mcpServers field
FAIL [expect-agent]: missing mcpServers field
FAIL [genui-architect]: missing mcpServers field
FAIL [security-auditor]: missing mcpServers field
FAIL [security-layer-auditor]: missing mcpServers field
FAIL [system-design-reviewer]: missing mcpServers field
FAIL: MCP overhead regression detected. Performance Tests, CI
Reverted in this branch. The consequence for #3461 is larger than one PR: there is no
"free" subset. All 36 declarations, empty ones included, are gated by that test, so the whole issue needs the
owner decision, not just the 29 with real content. Two local suites (agents, skills) were green while this was
broken, because the gate lives in tests/performance/ which was not run locally.
.nvmrc said 24.19.0release artifactrelease.yml builds and pushes this image with
docker buildx build --platform linux/amd64,linux/arm64 --push ., so the divergence shipped. It was
never an outage, which is why #3473 unified 26 of 31 Node call sites and missed this one.
| source of truth | said | status |
|---|---|---|
Dockerfile (2 FROM lines) | node 22 | shipped |
.nvmrc | 24.19.0 | tested by CI |
root package.json engines | >=24 <25 | demanded |
src/mcp-server engines | >=20.0.0 | satisfied either way, which is why it hid |
The in-file comment claimed "Renovate/Dependabot bump it". Measured: zero docker entries in
.github/dependabot.yml, and no renovate.json anywhere in the tree. The pin was frozen by
construction, and the comment told the next reader not to worry about it. Bumping the digest alone would have
recreated the same rot at 24, so the bumper lands in the same change.
$ bash tests/ci/test-dockerfile-node-version.sh .nvmrc major: 24 --- 1: every FROM node:<major> matches .nvmrc --- FAIL: FROM node:22 but .nvmrc says 24 FAIL: FROM node:22 but .nvmrc says 24 --- 2: the major satisfies root package.json engines.node --- PASS: node 24 satisfies '>=24 <25' --- 3: digest pin exists, and something is configured to bump it --- PASS: FROM lines are digest-pinned FAIL: nothing is configured to bump the digest. RESULT: FAIL
exit 1
$ bash tests/ci/test-dockerfile-node-version.sh .nvmrc major: 24 --- 1: every FROM node:<major> matches .nvmrc --- PASS: FROM node:24 PASS: FROM node:24 --- 2: the major satisfies root package.json engines.node --- PASS: node 24 satisfies '>=24 <25' --- 3: digest pin exists, and something is configured to bump it --- PASS: FROM lines are digest-pinned PASS: dependabot.yml declares the docker ecosystem RESULT: PASS
exit 0
$ docker buildx imagetools inspect node:24-alpine Digest: sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43 Platform: linux/amd64 Platform: linux/arm64/v8 # both platforms release.yml pushes $ docker build . # succeeded on the new digest $ docker run --entrypoint node ... --version v24.19.0 # exactly .nvmrc $ MCP stdio: initialize + tools/list {"result":{"serverInfo":{"name":"orchestkit-docs","version":"1.0.0"}}} {"result":{"tools":[{"name":"orchestkit_docs_search", ...
The digest pinned is the multi-arch index, not a single-platform manifest, so one pin still covers amd64 and arm64.
An earlier revision of this page and the PR body claimed the build re-stamped
171 hooks active to 215, and called it a defect that silently undoes #3494. That was
wrong, and the mistake was mine.
What actually happened: the build was running in the background and git diff was read while
setup.mdx was mid-write. A partially-written file was reported as a finding. The counter is
deterministic and correct:
$ bash bin/count-hooks.sh
GLOBAL=150 AGENT=0 SKILL=21 TOTAL=171
$ bash scripts/stamp-counts.sh # in isolation
Stamped counts: ... 171 hooks (150g + 0a + 21s)
files changed: 0
$ bash scripts/build-plugins.sh # full build, clean tree
[5/6] Found 171 hooks (150 global + 21 scoped)
files changed: 0 # idempotent
150 is simply the GLOBAL component, not a competing total. There is one source of
truth, both callers agree, and re-running the build is a no-op. Recorded here rather than quietly deleted, because
a milestone whose thesis is "tests that mirror the bug prove nothing" should not leave a retracted claim
unmarked.
test-agent-skill-validation.sh Test 6 asserts a hard <200ms. Run
three times on pristine origin/main with this branch stashed: 158ms PASS, 710ms FAIL, 312ms
FAIL. A 4.5x spread means it fails for reasons unrelated to correctness, which trains reviewers to ignore red.
Not caused by this branch. Filed as #3522. An earlier revision of this page said "filed separately"
before any issue existed, which was a claim about work not done; the issue now exists and is linked.
chore/v10-frontmatter-node. All output above is captured from real runs, not
reconstructed. Self-contained: no external scripts, styles, or render proxies.