feat(cargo-anvil)!: identify the ADO stage on every job wrapper invocation - #154
feat(cargo-anvil)!: identify the ADO stage on every job wrapper invocation#154Evgenii (Vaiz) wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The stage contract expansion is consistently applied across templates and is backed by a targeted regression test plus synchronized docs/snapshots.
Pull request overview
This PR extends the Azure DevOps job wrapper contract in cargo-anvil so every steps/job.yml invocation is told which ADO stage it belongs to, enabling downstream-owned wrappers (e.g., 1ESPT/SubstratePT/M365PT) to apply stage-specific behavior despite per-OS job name reuse.
Changes:
- Add an optional
stageparameter totemplates/ado/steps/job.yml(default'') and pass it from all PR and scheduled stage job wrapper invocations. - Add a regression test that derives the expected ADO stage identifiers from
GROUPS(plus the literalimpact) and asserts everysteps/job.ymlinvocation passesstage. - Update the ADO design doc and regenerate the
ado_backendsnapshot to reflect the expanded wrapper contract and new stage threading.
File summaries
| File | Description |
|---|---|
| crates/cargo-anvil/templates/ado/steps/job.yml | Expands the job wrapper parameter contract with optional stage (default '') and documents its purpose for owned wrappers. |
| crates/cargo-anvil/templates/ado/pr-stages.yml | Threads stage: <stage_id> through every PR-tier steps/job.yml invocation (including impact). |
| crates/cargo-anvil/templates/ado/scheduled-stages.yml | Threads stage: <stage_id> through every scheduled-tier steps/job.yml invocation. |
| crates/cargo-anvil/src/anvil/artifacts/ado.rs | Updates contract-pinning test and adds a new test ensuring every job wrapper invocation passes stage, derived from GROUPS. |
| crates/cargo-anvil/docs/design/ado.md | Updates design documentation to reflect the stage parameter and the adopter implication of contract growth. |
| crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap | Regenerates the backend snapshot to include the new stage parameter usage and updated wrapper contract text. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report✅ All modified and coverable lines are covered by tests. ❌ Your project status has failed because the head coverage (97.5%) is below the target coverage (100.0%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #154 +/- ##
=====================================
Coverage 97.5% 97.5%
=====================================
Files 299 299
Lines 67766 67766
=====================================
+ Hits 66125 66126 +1
+ Misses 1641 1640 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ation `steps/job.yml` is the extensibility point adopters take ownership of, but it cannot tell which stage it is rendering: per-OS job names repeat across stages, and `templateContext:` is consumed at template-expansion time, so a runtime condition on `System.StageName` is unavailable there. Add an optional `stage` parameter to the `steps/job.yml` contract and pass it from all 20 invocations across both stages templates. The value is the ADO stage identifier exactly as written after `stage:`, matching `System.StageName`. The default wrapper ignores it; it exists so an owned wrapper can discriminate. It defaults to `''`, so a caller that passes no stage still renders. BREAKING CHANGE: `steps/job.yml` now accepts a `stage` parameter and all generated stage templates pass it. ADO rejects a parameter the target template does not declare, so a repository that owns `steps/job.yml` must take the wrapper update in the same change as the regenerated `pr.yml` / `scheduled.yml`, or its pipeline fails to compile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
69a24bd to
b0d6db4
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The wrapper contract change is consistently propagated through templates, enforced by targeted tests, and reflected in the design documentation and regenerated snapshot.
Review details
- Files reviewed: 6/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.
Replaces #85, which sat 37 commits behind
mainin exactly the files it edited (pr-stages.yml+80/-78,ado.rs+115/-32,ado.md+242/-130) and would have failed its own test on rebase, because a newpr_msrvstage landed meanwhile. Re-derived from currentmain, with martin-kolinek's four review threads from that PR folded in rather than retrofitted.Problem
steps/job.ymlis the extensibility point adopters on 1ESPT / SubstratePT / M365PT take ownership of, but it cannot tell which stage it is rendering. Two escape hatches do not work:linux/windowsin everypr-*andscheduled-*stage,compute_linux/compute_windowsin impact — sonamedoes not identify the stage;templateContext:is consumed at template-expansion time, so a runtime condition onSystem.StageNameis not available there either.The motivating case is Component Governance. A
pr_mutantsjob compiles inside cargo-mutants' scratch copy of the tree, so its cargo SBOM dies with that copy and CG falls back to parsingCargo.lock, reporting optional backends the build never selects. A wrapper that setsfailOnAlert: truehas to exempt that one stage, and today has no way to name it. The impact jobs are the same non-compiling shape.Change
Add an optional
stageparameter to thesteps/job.ymlcontract and pass it from all 20 invocations:The value is the ADO stage identifier exactly as written after
stage:, which also matchesSystem.StageName. The default wrapper ignores it; it exists purely so an owned wrapper can discriminate. It defaults to'', so a caller that passes no stage still renders.Named
stage, notgroup(thread r3841475638):groupalready means "anvil group recipe name" across this repo —anvil-run-group,anvil-setup,steps/setup.yml— andimpactis not an anvil check group. The stage identifier is what the value actually is, so the collision disappears instead of needing a disclaimer.Effects
steps/job.ymlacceptsstage; the default wrapper's rendered output is otherwise unchanged.steps/job.ymlinvocations across both stages templates pass a stage — 10 stages × 2 per-OS jobs.stages_identify_the_stage_for_every_jobderives the expected stages fromGROUPSrather than restating them (thread r3841475966). A group added toGROUPSbut never threaded into a stages template now fails the test — the regression the old hardcoded list could not catch. Verified by temporarily dropping onestage:line: the test fails naming the exact stage and template.job_wrapper_declares_expected_contractalso pinsname: stage.name/pool/steps/artifactscontract are reconciled (thread r3841475483), including the §4.2 sample, which previously calledsteps/job.ymlwith no stage while the new test requires one. The "stages update without merging" promise is now qualified: it holds for the wrapper body, not for a contract that grows.squash_merge_commit_messageisCOMMIT_MESSAGES, so theBREAKING CHANGE:trailer reaches the changelog;CHANGELOG.mdis written at bump time and is deliberately not hand-edited here.ado_backendsnapshot is regenerated, never hand-edited.Adopter action required. ADO rejects a parameter the target template does not declare. A repository that already owns
steps/job.ymlmust take the proposed wrapper update in the same change as the regeneratedpr.yml/scheduled.yml, or its pipeline fails to compile.Validation
cargo test -p cargo-anvil(386 unit + all integration suites),cargo clippy -p cargo-anvil --all-targets -D warnings,cargo fmt, andjust anvil-spellcheck— all green.