Skip to content

feat(executor): auto-capture ADO build variables in ExecutionContext#378

Merged
jamesadevine merged 2 commits into
mainfrom
copilot/auto-capture-azure-devops-build-variables
May 1, 2026
Merged

feat(executor): auto-capture ADO build variables in ExecutionContext#378
jamesadevine merged 2 commits into
mainfrom
copilot/auto-capture-azure-devops-build-variables

Conversation

Copilot AI commented May 1, 2026

Copy link
Copy Markdown
Contributor

Stage 3 executors and logs lacked structured access to ADO build vars — particularly the BUILD_TRIGGEREDBY_* set surfaced by the pipeline-completion trigger IR — so add_build_tag was reaching into std::env::var directly to fill the gap.

Summary

  • ExecutionContext (src/safeoutputs/result.rs) — adds 14 Option<…> fields covering build identity (build_id: Option<u64>, build_number, build_reason, definition_name, source_branch, source_branch_name, source_version), ResourceTrigger upstream context (triggered_by_build_id, triggered_by_definition_name, triggered_by_build_number, triggered_by_project_id), and PR context (pull_request_id, pull_request_source_branch, pull_request_target_branch).
  • from_env_lookup<F> constructorDefault::default() now delegates to a generic env lookup so tests drive population from a HashMap-backed closure rather than mutating process-global env (which is unsafe in Rust 2024 and the repo avoids it).
  • add_build_tag.rs — replaces the inline std::env::var("BUILD_BUILDID") with ctx.build_id, eliminating the last executor that read build vars directly from env.
  • log_execution_context (src/execute.rs) — emits build_id, build_reason, and triggered_by_definition_name so trigger source is visible in Stage 3 logs.
  • Test ergonomics — converts existing ExecutionContext { ... } literals across the executor tests to ..Default::default() so future field additions don't ripple through unrelated test files.

Fields not yet read by production code carry #[allow(dead_code)]; they're populated for executor consumers added in follow-ups. No template change is required — ADO auto-exposes all BUILD_*/SYSTEM_* predefined variables to bash steps; only SYSTEM_ACCESSTOKEN (already wired via executor_ado_env) needs explicit mapping. CLI overrides and Stage 1 exposure are intentionally deferred per the plan.

// New testing pattern — no env mutation required
let ctx = ExecutionContext::from_env_lookup(env_from(&[
    ("BUILD_REASON", "ResourceTrigger"),
    ("BUILD_TRIGGEREDBY_DEFINITIONNAME", "Upstream Build"),
]));
assert_eq!(ctx.triggered_by_definition_name.as_deref(), Some("Upstream Build"));

Test plan

  • cargo build, cargo clippy --all-targets --all-features — clean (no new warnings).
  • cargo test — 1057 passing, including 8 new tests in safeoutputs::result covering field population, build_id numeric/non-numeric/absent parsing, and absent triggered_by_*/pull_request_* fields.

Copilot AI and others added 2 commits May 1, 2026 19:22
@jamesadevine

Copy link
Copy Markdown
Collaborator

/rust-review

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Rust PR Reviewer completed successfully!

@jamesadevine jamesadevine marked this pull request as ready for review May 1, 2026 19:37
@jamesadevine jamesadevine merged commit 7575218 into main May 1, 2026
1 check passed
@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Looks good — clean refactor with solid test coverage. Two minor observations worth eyeballing before merge.

Findings

⚠️ Suggestions

  • add_build_tag.rs:140-142i32::try_from(id).ok() on a u64 silently returns None for any build_id > i32::MAX (~2.1 billion), which causes the scope check to be skipped entirely, allowing any build to be tagged. This matches the old BUILD_BUILDID.parse::<i32>().ok() behavior so it's not a regression, but ADO build IDs are monotonically increasing per project and will eventually exceed i32::MAX in active orgs. Consider storing build_id as i64 on the struct (or comparing self.build_id as u64 vs the u64 context value) to avoid the silent bypass.

  • result.rs:88triggered_by_build_id: Option<String> is typed differently from build_id: Option<u64>. If a future executor needs to compare the upstream ID numerically (e.g. a scope check similar to add_build_tag), it will silently parse-fail on the same overflow edge. Might be worth a doc comment explaining why this one stays as String (e.g. "kept as String for flexibility; parse at use site"), so the asymmetry is intentional rather than accidental.

✅ What Looks Good

  • from_env_lookup<F> pattern is exactly right for Rust 2024 — avoids the unsafe set_var trap in tests while keeping Default::default() working in production.
  • ..Default::default() across all test fixtures is the correct forward-compatibility pattern; good that it's applied consistently.
  • New fields are well-grouped by concern (build identity / ResourceTrigger / PR) with ADO variable names in the doc comments — very easy to audit.
  • Test coverage is thorough: numeric parsing, non-numeric, absent, all three variable groups.

Generated by Rust PR Reviewer for issue #378 · ● 240.4K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants