feat(forge): resolve the spec-to-pr engine from a semver range - #140
Conversation
engine_ref now accepts a caret range (default "^0.6.7"), an exact vX.Y.Z tag, or a 40-char SHA. A range is resolved at run time to the newest matching release tag using npm caret rules (on 0.x the minor is the breaking boundary, so ^0.6.7 means >=0.6.7 <0.7.0), then peeled to a commit SHA that is logged and fetched. Branches are still rejected. Picks up every non-breaking spec-to-pr release without editing the workflow; opting into a new minor/major is a one-line default bump. Existing callers passing a SHA are unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Manifest Files |
Addresses review feedback on #140: - tests/forge-resolver.sh extracts the resolver block from forge.yml verbatim and runs it against a local fixture spec-to-pr, pinning caret semantics (0.x vs >=1 vs 0.0.x), pre-release exclusion, numeric ordering (0.6.10 > 0.6.9), annotated-tag peeling, SHA passthrough and rejection of branches/malformed refs. Wired into lint-actions.yml. - Range path now fails with a clear error if the chosen tag cannot be peeled to a commit, matching the exact-tag path. - Re-wrap the contract test header comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Addressed in dc3ef93: 🟡 1. Floating on movable tags — accepted as-is, no change. The trade-off is intentional and is in the PR description. Following release tags is the point of this PR. Callers who need a fixed version can still pass a SHA in 🟡 2. No automated test for the resolver — fixed. Added
I checked that it fails if the caret filter is broken (e.g. 🟢 3. Missing empty-SHA guard on the range path — fixed. It now uses the same 🟢 4. Comment wrap in the contract test header — fixed. 🤖 Generated with Claude Code |
sparsh-deriv
left a comment
There was a problem hiding this comment.
Checked out dc3ef93 in an isolated worktree and ran bash tests/forge-resolver.sh and bash tests/forge-contract.sh. Both passed. CI is green.
The resolver itself is solid: caret, exact tag, or SHA; npm 0.x caret rules; peel to a commit; fetch that SHA. Rejecting branches is stricter than master, which fetched ENGINE_REF as-is.
Four callers use @master and none pass engine_ref: deriv-blox, derivatives-trader, deriv-prop-trading, deriv-api-v2. Merge moves all of them from 26484fc (v0.6.1) to v0.6.7 (a4f4c23, 114 commits later). After that they follow new v0.6.x tags. deriv-api-v2's old in-repo Forge already resolved release tags, so the direction fits. The PR summary should say this is a live engine bump for those callers.
The input description still talks about a "reviewed release flow". spec-to-pr has branch rulesets only (PR restrictions plus org push protection). There is no v* tag ruleset. You already said that on the PR. The workflow text should match.
Soften the description and name the bump and I will approve.
…engine Addresses sparsh-deriv's review on #140: - engine_ref description no longer claims tags come from a "reviewed release flow": spec-to-pr has branch rulesets only, so anyone with write access can create or move a vX.Y.Z tag. A SHA is how to freeze. - The default's comment names the policy: callers without engine_ref run the newest v0.6.x, which includes feature patches — not "non-breaking". - The fetch step exports engine_tag/engine_sha and the report step adds them to the issue comment, since the claim comment predates resolution. - Contract test: anchor the default check to the whole quoted value, and say that a SHA bypasses the tag list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Model: 🤖 Kimi PR Review Complete🔄 Follow-up Review Summary3 of 4 issues from the previous review have been resolved. The remaining item is the known supply-chain posture point: Recommendation: APPROVE — the one remaining item is a MEDIUM whose substance is an out-of-repo hardening step (tag ruleset on 🔴 Critical Issues (BLOCK MERGE)None. 🟠 High Priority IssuesNone. 🟡 Medium Priority Issues🟡 1. engine_ref default now floats on movable git tags — add tag protection on spec-to-pr —
|
| Severity | File | Lines |
|---|---|---|
| MEDIUM | .github/workflows/forge.yml |
108-121 |
❌ Problematic Code:
A range or tag follows whatever vX.Y.Z tags exist on spec-to-pr at run
time. Tags are not protected there (its rulesets cover branches only),
so anyone with write access to that repo can create or move one, and
the next run executes it with this job's write token in scope. A tag
can be moved, a commit cannot: pass a 40-char SHA to freeze.
...
# Policy: every caller that doesn't pass engine_ref runs the newest
# v0.6.x tag, and each new v0.6.x tag rolls out to them with no PR here.
default: "^0.6.7"📋 Issue: Still present from the previous review. With ^0.6.7 as the default, anyone who can create, move, or delete v0.6.x tags in deriv-com/spec-to-pr changes the code that executes with a write token in every caller repo — git tags are mutable, and git ls-remote resolution picks up a force-moved tag silently on the next run. Progress since the last review: the engine_ref description now explicitly discloses exactly this ("Tags are not protected there (its rulesets cover branches only)… pass a 40-char SHA to freeze"), so the risk is no longer a hidden dependency — that half of the previous suggestion is done, and done honestly. What remains is the substance: the tag-protection ruleset itself, which the new comment confirms does not exist (spec-to-pr's rulesets cover branches only).
Engine: spec-to-pr v0.6.x <sha>) makes the resolved ref visible after the fact, but does not prevent a moved tag from being picked up.
✅ Fix: No code change required in this repo — pair this merge with a tag-protection ruleset on deriv-com/spec-to-pr:
- GitHub repo Settings → Rules → Rulesets → target
refs/tags/v* - Restrict creations and updates to the release role/app only; block deletions and force-pushes
💡 Explanation: The range default is only as immutable as the tags it resolves against. A ruleset that forbids updating/deleting v* tags restores the "a release cannot be re-pointed after the fact" property the SHA pin used to give, while keeping the no-manual-bump workflow this PR wants. The workflow's own description now states the exact gap, so the ruleset is the single remaining step.
🟢 Low Priority Issues
None.
Summary Table
| Priority | Count | Categories |
|---|---|---|
| 🔴 Critical | 0 | — |
| 🟠 High | 0 | — |
| 🟡 Medium | 1 | Supply-chain posture (movable tags) |
| 🟢 Low | 0 | — |
Recommendations
- Before or alongside merging, add a ruleset on
deriv-com/spec-to-prthat restricts creation ofv*tags to the release flow and forbids tag updates/deletions — this closes the one mutability gap the caret default introduces. Theengine_refdescription now documents the gap explicitly, so nothing in-repo is left to do for this item. - Nothing else outstanding. The new engine reporting (
id: engine+engine_tag/engine_shaoutputs appended to the issue comment) was checked for regressions:ENGINE_TAGis assigned in all three resolver branches, the report step's${ENGINE_SHA:-}/${ENGINE_TAG:+$ENGINE_TAG }guards correctly handle both the failed-before-fetch case and the tagless SHA-input case, and the anchored contract-test regex (^ +default: "(\^X.Y.Z|[0-9a-f]{40})"$) matches the current default and is strictly stronger than what it replaced.tests/forge-resolver.shstill extracts and executes the resolver block verbatim — both anchors survived the new commit.
The verified fixes were confirmed against both diffs: the empty-SHA guard remains at .github/workflows/forge.yml:343, the resolver test and its CI wiring are untouched by this increment and intact, and the contract-test header no longer has the dangling wrap.
Auto Fix Claude Reviews
| Action | Open Dashboard |
|---|
sparsh-deriv
left a comment
There was a problem hiding this comment.
ad128de covers the four points from the last review.
The description now says tags are unprotected and a SHA is the freeze. The default comment and the PR body name the live bump for the four @master callers and drop "non-breaking". The fetch step exports engine_tag/engine_sha after checkout, and report appends them to the issue. The contract grep is anchored.
I re-ran bash tests/forge-resolver.sh and bash tests/forge-contract.sh at ad128de. Both passed. CI is green.
Writing the outputs after checkout means the issue names what actually landed. A failed fetch stays silent.
Summary
Forge no longer needs a manual
engine_refbump for every spec-to-pr release.engine_refnow accepts:^0.6.7. At run time this resolves to the newest matchingvX.Y.Zrelease tag, using npm caret rules. While spec-to-pr is on0.xthe minor is the breaking boundary, so^0.6.7means>=0.6.7 <0.7.0(this matches spec-to-pr's README). From 1.x on, it means "same major".v0.6.7.The resolved tag is peeled to its commit SHA. The workflow logs it (
engine_ref ^0.6.7 -> v0.6.7 (a4f4c23…)) and fetches that commit.Security trade-off: Forge's default no longer pins one fixed commit. It follows spec-to-pr's
vX.Y.Ztags, which aren't protected there (spec-to-pr has branch rulesets only), so anyone with write access to that repo can create or move a tag and the next default run executes it. A tag can be moved, a commit cannot: pass a 40-char SHA to freeze. Branches, pre-release tags and anything that isn't a range, tag or SHA are rejected. The resolved tag and SHA are logged and added to the issue comment Forge posts.Opting into a breaking release (e.g. 0.7.0) is a one-line change of the default to
^0.7.0.Changes
.github/workflows/forge.yml: newengine_refsemantics and default; resolver in the "Fetch the spec-to-pr engine" step.tests/forge-contract.sh: theengine_refdefault may now be a caret range or a SHA. Added a check that branch names are rejected.Testing
^0.6.7and^0.6.0→ v0.6.7^0.5.0→ v0.5.0v0.6.1→26484fc(the previous pin)^0.7.0,^1.0.0andmastereach fail with a clear errorbash tests/forge-contract.shpasses.🤖 Generated with Claude Code