fix(vale): gate publishing on the pinned Vale version, not the manifest file - #92
Conversation
…st file The publish path fires on a push to main touching vale-manifest.json, and a `paths:` filter cannot see WHY the file changed. A reworded comment, a reformat, or a digest correction is indistinguishable from a version bump, and each one published six packages at a fresh <valeVersion>-<timestamp>. Nothing downstream absorbs that: every stamp is novel by construction, so there was no second line of defense. Add a credential-free `gate` job ahead of `prepare` that asks whether the pinned Vale version is already published, and skips when it is. It runs before prepare downloads ~60 MB, so a skip is cheap. design.md D5 argued an already-published check cannot work here. That is true of the STAMPED version and false of the BASE version, and the difference is the whole design: "is 3.17.1-20260810000724 published?" is always no, while "has anything been published for Vale 3.17.1?" is answered by a published 3.17.1 or any 3.17.1-* stamp. D5 is amended to draw that line, so the gate does not read as contradicting it. Two properties kept deliberately: - An explicit workflow_dispatch passes --force and is never suppressed. A human asking for a publish gets one. - A skip requires ALL six packages to carry the pinned version. Checking one would silently skip a half-published set, so the gate doubles as partial-release repair. Blocks #91: that PR archives this change, so its design.md must carry the amended D5 before it lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3
|
@claude /review |
|
Claude finished @thecodedrift's task in 4m 24s —— View job Review complete
Overall assessmentThis is a well-reasoned, well-tested fix. The Two non-blocking items posted as inline comments:
Nothing else stood out — the |
There was a problem hiding this comment.
Pull request overview
This PR prevents redundant Vale platform-package publishes that were previously triggered by any edit to .github/scripts/vale-manifest.json (including non-version changes). It introduces a credential-free “gate” that checks whether the pinned base Vale version already exists on npm (across all six platform packages) and only proceeds to prepare → publish when publishing is actually needed (or when explicitly forced via workflow_dispatch).
Changes:
- Add a new
gatejob ahead ofpreparein the Vale binaries workflow and conditionprepareongate’sshould_publishoutput. - Introduce
.github/scripts/vale-gate.cjsto decide “publish vs skip” based on npm registry state, with a--forceoverride for manual dispatch. - Amend the design doc’s D5 rationale to clarify why base-version publish checks are valid while stamped-version checks are not.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| openspec/changes/add-vale-binary-packages/design.md | Updates D5 to explicitly distinguish stamped vs base version checks and justify gating publish on the base version. |
| .github/workflows/vale-binaries.yml | Adds a credential-free gate job and gates prepare (and thus publish) on should_publish. |
| .github/scripts/vale-gate.cjs | Implements the npm-registry-based decision logic and writes should_publish to $GITHUB_OUTPUT. |
| .github/scripts/vale-gate.test.cjs | Adds unit tests covering matching behavior, pre-bootstrap, partial/complete publish sets, and forced dispatch. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The PR that added the gate amended design.md D5 to distinguish a stamped-version check (impossible) from a base-version check (the gate), specifically so nobody would later delete the gate for disagreeing with the design — and left the near-identical paragraph in the workflow's own header, a few dozen lines above the job it describes, still asserting that such a check "could never suppress anything". Fix the header to draw the same distinction, and list `gate` in the TWO PHASES summary alongside prepare and publish. Also run the six registry lookups concurrently. They are independent, and the job's whole justification is deciding cheaply before prepare downloads ~60 MB; sequential awaits made the gate six round trips deep for no reason. Order is unaffected — `missing` is built by filtering manifest.platforms, not by completion order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3
Both items addressed in 1 — the workflow header. You caught a real miss, and it is the exact failure this PR exists to prevent. I amended 2 — sequential lookups. Now Verified after both: 113/113 script tests, — AI Coding Agent |
The problem
The publish path fires on a push to
maintouching.github/scripts/vale-manifest.json:A
paths:filter cannot see why the file changed. Rewording a comment, reformatting it, or correcting a digest is indistinguishable from a version bump — and each one publishes six packages at a fresh<valeVersion>-<yyyymmddhhmmss>.There was no second line of defense, because publishing here is not idempotent: every stamp is novel by construction, so nothing downstream could recognize the run as redundant.
To be clear about what was not wrong: the weekly schedule only reaches
detect, which holds no npm credential and publishes nothing. The exposure was the manifest-file push trigger alone.The fix
A credential-free
gatejob ahead ofprepare:It asks whether the pinned Vale version is already published and skips when it is. It runs before
preparedownloads ~60 MB of upstream archives, so a skip is cheap.Two properties are deliberate:
workflow_dispatchpasses--forceand is never suppressed. A human asking for a publish gets one. Only the automatic push path is gated.A package that 404s reads as "nothing published", not as an error. That is the ordinary pre-bootstrap state, and failing there would wedge the gate closed precisely when the packages most need publishing.
Why this doesn't contradict D5
design.mdD5 argued that an already-published check cannot bound this workflow. That is true of the stamped version and false of the base version, and the distinction is the whole design:3.17.1-20260810000724published?3.17.1?3.17.1or any3.17.1-*stampD5 is amended to draw that line explicitly. Without it, the next reader removes this gate as contradicting the design.
Verification
node --test .github/scripts/*.test.cjs→ 113/113 pass (9 new)-separator (so pinned3.1is not satisfied by3.1.1), pre-bootstrap, fully published, partial set, forced dispatch, andshould_publishoutput in both directionsMISSINGfor 3.17.1 → would publish, which is correct todaypnpm lint,pnpm typecheck, prettier all cleanBlocking
Blocks #91. That PR archives this change, so its
design.mdneeds the amended D5 before it lands — it will need a merge frommainafter this one.Refs #91