CI has no release-profile Windows smoke — the profile went out with the gnu triple, and the two profiles catch different arms #232
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#232
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by external review of the #231 change, before it merged.
The gap
ci-windows.ymlhad two plugin smoke steps. After #231 it has one:Plugin smoke … (native MSVC build)Plugin smoke … (windows-GNU, the shipped linkage)continue-on-errorPlugin smoke … (MSVC, the SHIPPED CRT)The MSVC leg was always debug — that did not change. What changed is that the only release-profile step in the workflow was deleted along with the gnu target it happened to be attached to. The profile went out with the triple, not because anyone decided the profile was expendable.
The deleted step's own comment argued the point that still stands:
Why this is a real gap and not a duplicate
The two profiles catch different arms of the same step:
WORKER_SERVE_STACK_BYTES, which is the defect that step exists to catch;setjmp/unwind arm differs, and that is the arm the whole #231 episode lived on.Swapping one for the other loses an arm in either direction.
Severity: bounded, and the bound is worth stating
The shipped artifact is covered.
release.yml'swindows-archive-smokeexecutes the real release archive and its verdict gates publication; it has correctly refused to publish three times. This is not a repeat of #231's shape, which was "nothing ever executed the shipped bytes."What is lost is time-to-signal: a release-only trap regression is not caught on the push that introduces it, and surfaces at tag time instead — the expensive place.
The gap is disclosed in
ci-windows.ymlrather than left to be noticed, with the named owner and the named cost.Why the obvious cheap fix does not work
The natural proposal is to scope a release leg to one crate:
on the ground that trap/timeout/stack are plugin-host properties. That fails immediately, and on an anti-vacuity guard rather than on anything real:
release_smokedoes not only exercise the worker — it grades the stock-DLL import property across all four shipped binaries, the check that refusesvcruntime140.dll. A missing binary is a deliberate hard failure, because "I graded three of four and said nothing" is exactly what that check exists to refuse.So a scoped build cannot be used as-is, and a release leg today means a full workspace release build: a second multi-gigabyte target directory on a runner that has already hit 92.8 GB and LNK1201, and which is the queue bottleneck for every Windows job.
What the fix actually needs
A mode on
release_smokethat grades the worker arms (trap, timeout, stack exhaustion) without claiming to have graded the shipped set — so a release leg can be scoped to one crate.And that mode carries its own hazard, which is the real design work: the moment it exists, someone can reach for it in the ARCHIVE smoke and silently stop grading three binaries. So the mode must be refused wherever the shipped set is claimed, not merely documented as "don't use it there". That is a design, not a flag.
Acceptance:
Related: #231.
Two corrections/additions from the reviewer, both worth keeping
1. The scoped-build proposal was not naive — the constraint is NEW in the #231 branch.
At the revision the reviewer had,
release_smoketook$workerand graded the worker. The four-binary stock-DLL check arrived with #231. So "scope the release leg to one crate" was correct against the code in front of them and wrong against the code being written — the ordinary hazard of reviewing a moving branch, recorded here so the next reader does not mistake it for a careless suggestion.There is a neat irony worth preserving: the check that blocks the optimisation is the one that reviewer's own finding caused. The four-binary loop exists to refuse
vcruntime140.dllacross the shipped set — the+crt-staticwork — and that is precisely what makes a one-crate release build impossible. As they put it: a gate that only constrains other people is not a gate.2. The design for the worker-only mode already exists in this tree — copy it, don't invent it.
The hazard named above (a worker-arms mode leaking into the archive smoke and silently grading three of four binaries) is structurally the same problem
pe_linkagesolved in #231, and the solution is the same shape:Concretely: a worker-arms mode and a shipped-set mode are two axes. The archive smoke must ASSERT the shipped set rather than merely not-disable it, so a mode that grades fewer binaries cannot silently satisfy a leg that claims all four. That is the same rule as a CRT verdict cannot be inferred from a toolchain marker —
support::artifactalready refuses when markers are absent on both sides, or present on both, rather than guessing.That pattern already carries mutation coverage in
artifact_linkage.rs(arms A6–A9). Whoever picks this up should model the two smoke modes on it rather than designing a fresh flag.Correction: the gap is narrower than filed — the release path's smoke IS release-profile
The issue says CI has no release-profile Windows smoke and names
release.yml'swindows-archive-smokeas the owner. Both true. But "covered at tag time" understated what that job does, and v0.27.0's run makes it exact. Its own banner:So the late leg is release profile, on the published bytes, re-downloaded and verified against its own sidecar before unpacking — strictly stronger than the
continue-on-errorrelease leg that was deleted, which built its own binary rather than testing the shipped one.What that means for this issue's severity. The gap is early-signal ONLY, and the thing that runs late is stronger than the thing that ran early. A release-only trap regression is still not caught on the push that introduces it — which is the real cost and the reason this stays open — but it cannot reach users, and the artifact that would carry it is executed before publication rather than after.
Restating the acceptance in that light: what a release-profile CI leg buys is time-to-signal, not coverage of the shipped artifact. That should be weighed against the disk cost on the bottleneck runner accordingly — it is a developer-experience improvement, not a safety fix, and it should not be sold as the latter when someone picks it up.
The blocker is gone: the runner volume is back and a full Windows job now completes. Recording the baseline this issue's acceptance asks for ("the disk cost is measured, not estimated, before it lands on the bottleneck runner"), from run 726 on
bd1c4e2— the first Windows run to reach the suite since1d3228e.So, as the DEBUG-only baseline:
target (total)afterThat is the number the release-profile leg has to fit inside. The three lines are still exactly as parked in
ci-windows.yml:What still has to be done properly, and it is not "add it and hope". The leg compiles the plugin-host graph under
WINDOWS_RUSTFLAGS(the stringrelease.yml'sbuild-windowsuses), so it caches a SECOND dependency graph. The honest sequence is: put the leg on a branch, dispatchci-windows.ymlat that ref, read the post-jobDISK REPORTfrom that run, and comparetarget (total)against the 25,5 GB above. The delta is the measured cost. Merge only if it leaves comfortable room above the 40 GB floor — the runner is the queue bottleneck for every Windows job, so an eviction here costs every other job too.A branch run and a master run sit in different
concurrencygroups (ci-windows-${{ github.ref }}), so the measurement does not cancel a master verdict — but they serialise on the one machine.Also worth noting for whoever picks this up: run 726 was RED, on one test, and it was NOT a disk or profile problem —
link_payload_scaling_e2e's per-link ceiling was measuring the temp directory's path length (#252). The disk pre-flight passed with 70 GB of margin.Done in
ee39fd1. All four acceptance criteria met, each verified rather than assumed.The disk cost, measured on a branch before it landed
Run 731 on
measure/232-release-smoke-disk, against runs 726/727/729 on master:target\debugtarget\releasetarget (total)The leg costs 1,0 GB, and that is the from-scratch figure — the restored cache held no
target\releaseat all, so this is the cost of building it rather than an incremental top-up. Scoping to one crate is what bought that;--release --workspacewould have cached a second full dependency graph.The
25,5 → 29,2 GBmove intarget (total)is mostlytarget\debuggrowing25,5 → 28,2across the commits betweenbd1c4e2and here, which this leg did not cause.The comparison is apples to apples: the cache key is
hashFiles('**/Cargo.lock')and the branch does not touchCargo.lock, so run 731 restored the same warmtarget/the baseline runs wrote.Against a threshold fixed before the number arrived — merge only if green and post-job free ≥ 60 GB (the 40 GB floor plus 20 GB of margin, because this runner bottlenecks every Windows job). 90 GB, so 50 GB of margin.
It also costs ~5–6 minutes of wall clock (~12%), which was not in the disk criterion and is stated rather than omitted. That is the price of moving release-profile discovery off the tag.
Acceptance, point by point
SMOKE RESULT: PASS (worker-only) - 6 checks against target\release\code-index-plugin-host.exe, built under the same-C target-feature=+crt-staticstringrelease.yml'sbuild-windowsuses. Stack is covered:check_traps's own doc says "THE RECURSION CASE IS THE ONE THIS STEP EXISTS FOR. Stack exhaustion inside a guest must be an ordinary trap."SMOKE RESULT: PASS (shipped-set) - 7 checks. Debug frames are larger, so it remains the stricterWORKER_SERVE_STACK_BYTEStest.scope_refusalruns before anything is graded, andthe_weaker_scope_is_refused_where_the_shipped_set_is_presentpasses. Unchanged by this commit.The gate that forbade this had to be rewritten, not relaxed
artifact_linkage::every_ci_plugin_smoke_asserts_the_shipped_setasserted no leg may declareworker-onlyat all, and its failure message named the release condition: "worker-onlyexists for a leg scoped to ONE crate, which no leg here is yet — see #232."Simply permitting
worker-onlywould have let any leg drop to the weaker claim, which is the drift the ban existed to prevent. It now grades the justification: a leg may claimworker-onlyonly where its own step builds-p code-index-plugin-hostand does not build the workspace. Structural, so a sixth leg tomorrow is graded by the same clause.MUTATIONS RUN:
--workspace --binsand keepworker-only→ RED, "claimsworker-onlybut its step builds the workspace, so all four shipped binaries are present and the strongershipped-setclaim is available to it". The new rule constrains the leg it was written for.worker-only→ RED, same clause — the case the old blanket ban caught is still caught.md5 verified changed then restored on each; control green. Anti-vacuity floor moved 4 → 5 with the population.
The honest residual:
worker-onlyprintsSMOKE stock-imports: NOT GRADED — … That claim belongs to ashipped-setrun, so the release leg says plainly what it did not check rather than letting a pass read as a statement about all four binaries.