feat(semver-validation): rewrite as a composite over semstat - #231
Conversation
|
/vcluster-review |
|
🔍 The agent swarm is reviewing PR #231. I'll post the results here when it's done. Admins can watch the run live |
loft-bot
left a comment
There was a problem hiding this comment.
Panel review: 2 blocking, 5 quality notes, 10 lanes. Both blocking findings were reproduced by running the scripts, so neither is something the nine green checks would have caught.
Blocking concerns
.github/actions/semver-validation/src/report.sh:74— caller-suppliedversion/compare_toreach five plain log echoes unsanitized, so a newline forges workflow commands;::stop-commands::disables annotations for the rest of the job..github/actions/semver-validation/src/report.sh:79— every non-zerosemstat parseexit is reported as an invalid version, so a crashing binary answersis_valid=falsefor a valid tag on a green step.
What was checked
Checked: correctness, security, test-quality, operability, architecture, reuse, dead-code, typos, infra, PR metadata, and cross-PR file conflicts. Skipped: the four e2e rubric lanes, gap-analysis, and entry-point fidelity (no e2e*/ suite, no Go unit tests, no customer-bug link), plus layout coherence (not a restructure-shaped diff). Verified and deliberately not raised: the checksum path fails closed on both download arms; all 12 declared outputs are written on every one of the six exit paths; the semstat_version Renovate annotation matches this repo's custom manager; both scripts are committed 100755; the README's gt exit-code example behaves as documented; is_stable vs release_type == 'stable' never disagree. The reuse lane ran but was limited — this sandbox has no git checkout, so it could not read sibling actions to confirm equivalence.
Quality notes (non-blocking)
- consider
src/install-semstat.sh:88—semstat version 2>&1folds stderr into the compared value, and the exact-equality pins an output shape DEVOPS-1335's command contract never specifies. A stray stderr line rejects a correct binary with a message that reads as a version mismatch; a future release printingv1.2.3breaks every caller the moment Renovate bumps the pin. Capturing stdout only and asserting the version appears keeps the guarantee without pinning the format. - consider
src/report.sh:127— emptycomparison/is_greatercarries three meanings (not requested,compare_tounusable,comparefailed) and only the latter two warn. This path logs nothing, so acompare_toexpression resolving to empty is indistinguishable from a deliberate omission, and the documentedis_greater == 'true'gate quietly stays false on exactly that mis-wiring. - consider
test/install-semstat.bats:172-187— the two platform tests assert only exit 0, andpublish_release()writes byte-identical archives under all four asset names, so any name that resolves passes. Confirmed by mutation: mappingarm64→amd64, and separatelyDarwin→linux, leaves all 12 tests green. This becomes blocking if the OS/arch-to-asset mapping is ever edited, since no test would catch a wrong-asset regression; today the version cross-check still fails such a build loudly. Recording the requested URL in the curl mock and asserting it would close this. - consider cross-PR — open #167 (
chore(deps): update semver-validation) modifiespackage.jsonandpackage-lock.json, both deleted here. It is obsoleted by this change and worth closing rather than merging after; this PR also removes the Renovate npm rule that produces it. - nit
src/report.sh:41— the comment reads "unsanitised" while the function it documents two lines below issanitize, as are all four call sites.
|
@sydorovdmytro ✅ Review finished for #231 |
|
Both blocking findings were real; fixed in 4654776 and b80f224.
Unsanitized log lines. All five plain echoes now fold newlines, same as the warnings did. Test asserts neither a forged On the quality notes:
|
b80f224 to
8b42415
Compare
f7e694a to
07cd10a
Compare
Piotr1215
left a comment
There was a problem hiding this comment.
I think there is one input-trimming compatibility issue left before moving the tag.
One semver implementation now answers for the action and for the shell scripts in this repository, so there is no second engine to disagree with the first. An invalid version stays an answer on a green step. The step fails only when it cannot answer at all, so a crashed binary, a jq that could not read the parse output, or a schema that moved never come back as is_valid=false or as a stable release candidate. The download root is overridable for file:// only, because composite steps inherit the job's environment and checksums.txt is fetched from the same root. semstat is installed once per release and platform per job rather than once per call. The runtime contract changed from a self-contained node action to a composite that needs egress plus curl, tar, jq and a sha256 tool, so callers move to semver-validation/v4. v1 through v3 stay on the node action for callers whose runners have neither.
The bash trim used [[:space:]], which diverges from String.prototype.trim in two ways the node action never had. glibc excludes U+00A0 and U+FEFF from [:space:] while JS trims both, and semstat accepts a version padded with either: a BOM-padded v1.2.3 came back is_valid=true carrying both BOMs in parsed_version.raw, so a caller reusing raw as a tag name got invisible bytes in the tag. Which characters [:space:] matches is also locale-dependent. Under LC_ALL=C it matches none of the non-ASCII ones, so the same input trimmed differently depending on the runner. Spells the set as literal UTF-8 bytes instead: ECMAScript WhiteSpace plus LineTerminator, exact in every locale. U+200B stays untrimmed because JS does not trim it either.
01eee4f to
e9265fc
Compare
The node24 action answered "is this parseable" and nothing else, so classification and ordering were reimplemented in bash in
vcluster-release,promote-releaseandprerelease-setup, none of them agreeing on precedence. This makes semstat the one implementation: the action downloads it, verifies it against the release checksums, and runs it.is_valid,parsed_versionanderror_messagebehave as before, down to the input trimmingcore.getInputdid and the whitespace-only-vs-missing distinction. New:is_stable,release_type, flatmajor/minor/patch/prerelease/build, and an optionalcompare_togivingcomparisonandis_greater. npmsemver,dist/and the node24 runner are gone.The checksum is a transfer check, not a signature check: semstat is ours, so the sigstore bundle the release also publishes is left alone.
This needs a new
semver-validation/v4rather than an advance ofv3, because it adds runner requirements: Linux or macOS with network egress pluscurl,tar,sha256sumandjq.v1throughv3stay on the node action.Closes DEVOPS-1335
Test plan
make test-semver-validation— 63 bats tests over both scripts, including checksum mismatch, an archive with nosemstatmember, a binary reporting the wrong version, arch mapping, dash-leading versions, input trimming, and newline injection into outputs and workflow commands.v4.9.0-rc.2asrc, belowv4.9.0.-1.2.3," v2.1.0 "," "and""against real semstat; outputs match the node action on each.make lintclean, docs regenerated withmake generate-docs.test-semver-validation.yamlgains a job that runs the action end to end and asserts the three old outputs verbatim plus the new ones.