Skip to content

feat(safe-outputs): split Azure DevOps PR tools with safe migration - #2222

Open
jamesadevine with Copilot wants to merge 46 commits into
mainfrom
copilot/find-safe-output-gh-aw
Open

jamesadevine with Copilot wants to merge 46 commits into
mainfrom
copilot/find-safe-output-gh-aw

Conversation

Copilot AI commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Consolidates #2221 into #2222 and provides focused, ADO-native PR safe outputs
with safe source migration. Public names use pull-request, and public
labels terminology is retained. This is not a drop-in gh-aw schema adapter.

Implemented

  • Closed PR configuration/proposal schemas; unsupported policy fields no longer
    appear to work while being ignored.
  • Common triggering/fixed/wildcard targets, default repository, allowlists and
    label/title filters across PR mutations. Triggering means the complete trusted
    collection/project/repository/PR identity, not just a numeric ID.
  • Proven legacy explicit-ID scope and original aggregate budgets migrate
    without granting extra authority. Imports preserve consumer precedence,
    custom-job ownership and source/cache bytes. Old runtime names are rejected.
  • Content updates retain final UTF-16 limits and strict managed-section rules.
  • Non-voting comment reviews preserve existing votes; explicit reset clears
    the authenticated actor's vote. This intentional new behavior has no legacy
    toggle; prompts are warned about, not rewritten.
  • Bounded label add/remove/replace policies: allow/block controls, default
    10-label batch cap, authoritative label identities, non-atomic add/verify/remove
    replacement and truthful partial results.
  • mark-pull-request-as-ready-for-review: active draft publication, exact
    one-field mutation, persisted read-back and repeat no-op.
  • update-pull-request-comment: verified same-pipeline/same-actor root-comment
    updates with immutable ownership and a checked content hash. Unmarked history,
    manual edits and conversations with replies are protected.
  • Opt-in non-destructive comment supersession: preserve text, mark superseded
    and close older eligible threads only after replacement succeeds. No deletion,
    automatic vote reset or review dismissal.
  • Exact-source-head left/right inline positioning, including deleted files.
    ADO diff metadata, not local file presence, determines anchors.
  • One self-contained review proposal with summary and inline findings.
    max-comments defaults to 0; the whole batch is preflighted, comments precede
    votes, stale heads stop further writes, and partial/uncertain outcomes persist.
    Standalone comments are independent: no hidden buffering or duplicate posting.
  • push-to-pull-request-branch: exact source snapshot preparation, isolated-index
    MCP capture, only the agent delta, patch/path/file/binary bounds, source-ref
    allowlists, optimistic oldObjectId guard and persisted-head verification.
    No forks, force-push, rebase-on-race, fallback PR, policy bypass or immediate merge.
    Failed/unconfirmed pushes block same-PR publication/review/auto-complete follow-ups.
  • Updated authoring guidance, previews, catalog, audit records, registries,
    compiler targets, approval/staged constraints and migration documentation.

Validation infrastructure repairs

  • JavaScript process fixtures run through Node on Linux and Windows.
  • PR label read-back uses the dedicated endpoint; cleanup is idempotent.
  • Exact required selections, prerequisite preflight, JSON result artifacts,
    cleanup reporting and diagnostic failure-issue suppression.
  • Bounded transient status-read recovery without write replay or weakened
    terminal-state cleanup proof.
  • Actual ADO timeline handling: a skipped Phase without an allocated Job is
    explicit skip evidence, while missing records alone are not.
  • Large-PR reviewer prefetch falls back from GitHub's 20,000-line diff API limit
    to pinned bare Git objects with identical exclusions and no checkout of PR code.
    All five reviewer locks were regenerated with the pinned gh-aw compiler.
  • Real-agent validation caught a pre-agent PATH assumption; source preparation
    now invokes the compiler already staged at /tmp/awf-tools/ado-aw.
    The corrected full pipeline passed on the published head.

Review remediation (October 1, 2026)

All ten divided-review findings now have production-path fixes and regression
coverage. Current head is 873a05d4, following the shared patch milestone
c5dac7b5 and filtered-preimage preservation fix c9f9881e.
Final-head CI and required live evidence have passed. Historical failed-run
ref cleanup remains permission-blocked, as recorded below.

Finding Response
Legacy boolean shorthand 3172f1f6: normalize legacy true like null at the source-migration boundary; canonical runtime schemas remain strict.
Native copy expansion c5dac7b5: parse native operations, inspect source/intermediate objects and bound expansion before Git application; covers the 99-copy amplification case even at the maximum configured limit.
Exclusion mismatch c5dac7b5: one application-glob selection; omit/report whole copy or rename operations and reject retained dependencies. Commit preimages handle rename swaps/chains correctly.
Checkout byte conversion c5dac7b5: isolated push index and exact bounded Git-blob serialization shared with creation. Creation applies and parents at the same verified captured base; original index/worktree/refs remain unchanged.
Stale boundary cleanup d890eb8a: separate target namespace, corroborated legacy ownership, all-definition source-child terminal proof, PR-before-ref cleanup and SHA leases. Ambiguity retains resources.
Auto-complete race 6b07d65b: only the auto-complete scenario accepts confirmed completion; uncertain abandonment gets one read-back, never blind replay.
Mixed-org preflight 6b07d65b: independently resolve every selected local/cross-org reviewer prerequisite before any setup writes.
Unbounded PR transport b915f392: shared 30-second request / 8 MiB streamed-response bounds across the ADO PR family, including policy reads; incomplete metadata fails closed.
Obsolete test algorithms 2f97a8a4: port valuable assertions to production paths and remove retired request/result and inline-builder substitutes.
Repeated inline reads b915f392: fetch/scan each immutable (commit,path) once, preserve proposal order, explicit comment authority and later head checks.

Intentional size tightening: per-tool max-patch-size defaults to 4096 KiB
(previously 5 MiB), valid integer range 1–10240. Creation gains expanded-content
and full encoded-payload bounds. Both retain separate 10 MiB source-processing
and encoded REST ceilings. There is no automatic larger-limit migration or
agent override.

The remaining gh-aw comparison is documented against v0.89.21 /
856e7fa3ca4f1597f9adbd519eec415ce92320e2, not an unqualified latest-version
claim. Native inputs and size defaults align; ADO-native schemas, max-files,
REST full-blob transport, exact-head guards and independent resource bounds
remain intentional differences.

Local integration: 3,893 Rust tests passed (2 ignored),
1,429 TypeScript tests passed
, strict Clippy, typecheck, 59 registry shell
tests and 2 emitted-shell tests passed; both live harnesses built. The final
creation corrections also passed the complete Rust suite/strict Clippy and
82 directly affected TypeScript tests plus harness rebuild.

Required executor run 644640 finished 40/45 passing, no skips. All
guarded-push cases and the broader comment/review/label/publication matrix
passed. Five creation cases exposed a prefix-ref collision check, ADO's
one-operation-per-path restriction, and a fixture whose intended retained
content was also detected as a copy of the excluded source. 873a05d4 corrects
all three: exact complete ref matching, final-tree add/edit/delete serialization,
and independent fixture content. 644654 passed all 45 required cases, with
zero failures/skips
, on 873a05d45640bc24483a4434339989c1e69658e1.
The downloaded result artifact confirms the exact revision and selected IDs;
no run-owned refs remain.

Smoke run 644641 on c5dac7b5 passed and cleaned both ref namespaces.
The final-head repeat 644659 on 873a05d4 also passed all four cases:
canary 644660, automatic boundary 644661, expected timeout rejection
644662, and actual-agent push 644663. The rejected child's one-minute
Manual Review failure is the expected result, not a suite failure: ungated
SafeOutputs ran, while reviewed writes/artifacts were withheld.

Persisted read-back verifies push commit
65a34c4487f76f3e41e9aff2a50e6a1e6ed794ba, its exact prepared parent, and exact
proof-file bytes. All three disposable PRs 43213–43215 are abandoned,
required tags/artifacts exist, and all seven source/target refs are absent.
Final PR checks are green, including Linux Rust/TypeScript/drift, Windows
executor harness, prompt contracts and ADO integration checks.
No active run was replaced or counted as a pass. Failure-issue filing remained
disabled; no gate was auto-approved.

Historical cleanup blocker: the failed manual run 644640 and automatic
validation runs 644639/644644 left 24 refs (eight per run, under
refs/heads/ado-aw-det-<buildId>-). All nine associated owned PRs are confirmed
abandoned. The exact retained refs/SHAs were recorded; conditional deletion
returned forcePushRequired for the local identity. No permissions were
changed and no unconditional retry was made. These retained refs are not
counted as successful cleanup; current-candidate cleanup is verified separately.

Test plan

Evidence is tied to immutable revisions. Mocks, registered cases, cancellations,
and skipped prerequisites are not counted as live-service passes.

Coverage Evidence
Final required remediation executor matrix 644654 on 873a05d: 45/45 passed, no skips; native copy/rename, whole-operation exclusions, CRLF/binary fidelity, pre-application expansion rejection, broad PR operations and current-run cleanup verified
Final full-pipeline boundaries and real-agent push 644659 on 873a05d: 4/4 passed; children 644660–644663, expected manual-review timeout, exact push parent/content, tags/artifacts, three abandoned PRs and seven-ref cleanup verified
Final local and CI validation 3,893 Rust passes (2 ignored), 1,429 TypeScript passes, strict Clippy/typecheck and enforced shell checks; final affected TS rerun 82 passed. All checks green on 873a05d
Original required PR executor matrix 643035 on d4f7f948: 20 passed, no failures/skips
Synthetic automatic and timeout-rejected boundaries 643206 on 7957fa6a: passed; canary 643210, automatic 643211, expected rejection 643212; exact PR state, tags/artifacts, reviewed skip and five-ref cleanup verified
ADO lifecycle API prerequisite probes 643209 on c8645de8: 4 passed, no skips; these establish platform prerequisites, not substitute executor coverage
Non-voting review/reset and exact-target regressions 643219 on 34c307d5: 10 passed, no skips
Labels and draft publication 643228 on a9be3c30: 6 passed, no skips, including forbidden-batch no-write and repeat-publication no-op
Owned comments, left/right anchors and review batches 643257 on 2726a751: 10 passed, no skips. The earlier run exposed immutable thread properties and deleted-file item.path=null; both corrected and re-proven
Deterministic guarded PR pushes 643919 on ef74722c: 6 passed, no skips; exact source update, stale head, forbidden branch, protected file, hash mismatch and empty patch
Real-agent source preparation / MCP capture / push 643927 on c4aa0506: passed. Push child 643932 and canary 643933 succeeded; source preparation, Agent, Detection and SafeOutputs all succeeded. Observer verified direct-parent source commit, exact proof-file content, PR update, tags/artifacts and deletion of all three owned refs. Earlier 643920 exposed the PATH bug and was cleaned up; it is not counted as a pass
Full local regression 3,542 Rust unit passes, 1 ignored, plus integration suites; 1,342 TypeScript tests before the four additional prefetch regressions; strict Clippy/typecheck and shell registry/emission guards passed
Published-head CI c4aa0506: Rust, Linux TypeScript/build/bundle/drift, Windows process harness and reviewer prefetch passed. The prefetch fix was also verified on 86e552b4 with a complete 28,672-line filtered diff
Additional-project/organization live coverage Blocked: approved destination inputs absent; no resources/grants provisioned
Native PR full-pipeline boundary Blocked: existing mirror has no native build-validation policy
Human-approved reviewed path On demand only: never auto-approved

Diagnostic runs explicitly disable GitHub failure-issue filing and report cleanup
outcomes. No line/branch coverage percentage is claimed. No main merge or release
is part of this work.

Copilot AI and others added 11 commits September 22, 2026 11:06
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@jamesadevine
jamesadevine marked this pull request as ready for review September 22, 2026 12:38
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Security Reviewer completed the security review.

🔒 Security review by PR Security Reviewer

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

✅ Compiler Contract Reviewer completed the compiler contract review.

🏗️ Compiler contract review by Compiler Contract Reviewer

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed the test quality analysis.

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

✅ Rust Code Quality Reviewer completed the Rust code quality review.

🦀 Rust code quality review by Rust Code Quality Reviewer

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review: no regressions found

Reviewed the new abandon-pull-request Azure DevOps safe output end-to-end:

  • Boundary respected: the Stage 1 MCP tool (mcp.rs) only calls queue_sanitized_output — it never touches the write-capable token. The actual PATCH/POST happens in execute.rs/abandon_pull_request.rs's Executor::execute_impl, which runs in Stage 3 with ctx.access_token (the write-capable token the agent never sees), consistent with update-pr and submit-pr-review.
  • Sanitization: the agent-supplied body is passed through sanitize_text in sanitize_content_fields before being reused as the abandonment comment, and repository through sanitize_config — matching the existing pattern for other PR-comment safe outputs.
  • Repository resolution: uses the same resolve_repo_name + allowed-repositories allowlist gate already used by update-pr, submit-pr-review, and resolve-pr-thread. Repository/project segments are percent-encoded (utf8_percent_encode with PATH_SEGMENT) before being placed in the URL, so no path/URL injection via repository.
  • State check before mutation: the PR is fetched and its status validated (active required, abandoned short-circuited) and title/label filters applied before the PATCH that abandons it — no TOCTOU-style bypass of the configured guards.
  • No validated newtype was downgraded to a raw String, no new domain was added to the AWF allowlist, and no ado-aw-debug gate was touched.

This diff does not weaken the compiler's trust boundary or the Stage 1/Stage 3 credential isolation. No inline findings to post.

🔒 Security review by PR Security Reviewer · auto · 56.7 AIC · ⌖ 1.8 AIC · ⊞ 10.2K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler contract review — abandon-pull-request

New safe output is wired consistently with the existing ADO-repository-write
contracts (typed_safe_output_config, ALL_KNOWN_SAFE_OUTPUTS, mcp.rs tool
registration, dispatch_pr_tools, resolve_repo_name same-org-only resolution
matching update_pr.rs's precedent). No codegen, gate/fact IR, or lock-file
drift here.

Documentation sync — missing (docs half of the contract)

AGENTS.md's src/safe_outputs/ architecture tree (around line 212) lists
every safe-output module file but does not include the new
abandon_pull_request.rs. Per docs/extending.md, a new safe-output tool
needs an entry there so future agents can find it by scanning the tree. There's
no diff line to attach this to since the omission is what's missing — fix is a
one-line addition to the tree, alphabetically between assign_work_item.rs
and close_github_issue.rs.

Minor: module ordering nit

Flagged inline on src/safe_outputs/mod.rs — new mod/pub use entries
landed out of the file's alphabetical convention.

Everything else — Option<String> for repository/target_repo (an alias,
not a path/ref/sha, so the src/secure.rs newtype rule doesn't apply),
sanitization via SanitizeContent, and cross-org write gating being
out-of-scope for a same-org-only tool (matches update_pr.rs) — looks correct.

🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 79.4 AIC · ⌖ 2.67 AIC · ⊞ 11.3K
Comment /review to run again

Comment thread src/safe_outputs/mod.rs Outdated
mod assign_github_issue_to_user;
mod assign_work_item;
mod close_github_issue;
mod abandon_pull_request;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mod abandon_pull_request; is out of alphabetical order (lands between assign_work_item/close_github_issue, and assign_work_item itself got reordered around it). ado-aw's safe_outputs module list is otherwise kept alphabetical — worth a quick fixup so future diffs on this list stay minimal.

💡 Fix

Move both the mod declaration and the matching pub use line so abandon_pull_request sorts before add_build_tag.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Quality review — abandon-pull-request safe output

Good coverage of the happy path (with/without comment, triggering-vs-explicit target) and the missing-label rejection. Two things need attention before merge:

  1. Likely dead validation code (comment on src/compile/common.rs): the new "abandon-pull-request" match arm lives inside validate_github_issue_outputs_config, which only iterates GITHUB_ISSUE_SAFE_OUTPUT_TOOLS — a list abandon-pull-request is not part of. If that reading is correct, validate_abandon_pull_request_config is never invoked at compile time and an invalid config (e.g. an empty required-labels entry) will silently compile. No test exercises this path, which is exactly why it slipped through.
  2. Untested control-flow branches in abandon_pull_request.rs: the allowed-repositories rejection, the required-title-prefix mismatch, the already-abandoned short-circuit, the non-active status rejection, and the comment-post-failure warning are all real branches with distinct user-facing messages, but only the missing-label case has a test. These are the core guardrails for a destructive action (abandoning a PR) — a regression in any of them should not be able to ship silently.

Requesting changes mainly for (1), since it means a documented, seemingly-validated config option may not actually be validated.

🧪 Test quality analysis by Test Quality Sentinel · auto · 110.2 AIC · ⌖ 1.86 AIC · ⊞ 9.8K
Comment /review to run again

.as_ref()
.ok_or_else(|| anyhow::anyhow!("SYSTEM_TEAMPROJECT not set"))?;
let token = ctx.access_token.as_ref().ok_or_else(|| {
anyhow::anyhow!(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowed-repositories rejection path is untested

No test configures allowed-repositories with a repository selector that is not in the list. The three async tests all use the implicit "self" repository selector with an empty allowed_repositories, so resolve_repository's deny branch never executes.

💡 Why this matters

This is a security-relevant guard — it exists specifically to stop an agent-controlled repository param from targeting an unlisted repo. A regression here (e.g. an accidental == → != flip, or the emptiness check being inverted) would silently allow writes to any repository and no test would catch it.

Suggested case: configure allowed-repositories: ["other"], pass repository: "self" (or omit it so it defaults to "self"), and assert the execution fails with the not in the allowed-repositories list message before any HTTP mock is hit (mount GET/PATCH with .expect(0)).

Ok(repo_name) => repo_name,
Err(result) => return Ok(result),
};
let client = reqwest::Client::new();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required-title-prefix mismatch and the already-abandoned short-circuit are both untested

missing_label_rejects_before_patch covers a missing label, but no test exercises a title-prefix mismatch (this branch, lines 461-467), and none exercises the PR-already-abandoned short-circuit (around line 670) or the not-active/non-abandoned status rejection (around line 683). The comment-post-failure warning path (around line 706) is also unexercised.

💡 Why this matters

Each of these is a distinct control-flow branch with its own user-facing message, and together they are most of the business logic guarding this destructive action (aside from label filtering, which is covered). A regression in any of them — e.g. the already-abandoned branch accidentally falling through to call PATCH again, or the status check being inverted so a completed PR gets abandoned — would ship untested.

At minimum, add: (1) a title-prefix-mismatch case mirroring missing_label_rejects_before_patch; (2) a case where pr("abandoned") is returned by the GET mock and PATCH is asserted with .expect(0), verifying the success message and already_abandoned: true; (3) a case with pr("completed") asserting failure.

Comment thread src/compile/common.rs Outdated
crate::safe_outputs::validate_close_github_issue_config(&config)?;
}
}
"abandon-pull-request" => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This match arm is unreachable — no test would catch that validate_abandon_pull_request_config is never invoked here

validate_github_issue_outputs_config only iterates front_matter.github_issue_tool_names(), which filters GITHUB_ISSUE_SAFE_OUTPUT_TOOLS (src/compile/types.rs:710). "abandon-pull-request" is not in that list, so this match arm can never be reached for any front matter — validate_abandon_pull_request_config is dead code from this call site, and a workflow with an invalid abandon-pull-request config (e.g. an empty required-labels entry) will compile successfully instead of failing validation.

💡 Why this matters

There is no compiler test that asserts an invalid abandon-pull-request config is rejected at compile time (the existing mod tests in abandon_pull_request.rs only unit-tests the config parsing and the executor in isolation, never through validate_github_issue_outputs_config or the compile pipeline). Had one existed — e.g. validate_github_issue_outputs_config(&fm_with_bad_abandon_config).is_err() — it would have caught that this branch is unreachable.

Likely fix: call validate_abandon_pull_request_config from wherever abandon-pull-request is actually compiled/validated (it is not a GitHub-issue-style tool), and add a fixture/unit test that exercises compile-time rejection of an invalid config (e.g. an empty string in required-labels).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust review: no merge-blocking issues

Solid implementation, consistent with existing safe-output patterns (update_pr.rs, close_github_issue.rs). Builds cleanly; all 6 new tests pass.

Notes
  • The rust-critic sub-agent failed with a model-access error (400, gpt-5.4-mini not accessible); its output was discarded per contract and this review is my own manual pass.
  • Error handling uses anyhow context consistently, no unwrap/expect on user-input paths, repository/target resolution mirrors resolve_repo_name/resolve_repository_write_target conventions used elsewhere.
  • One clippy derivable_impls hint on the manual Default for AbandonPullRequestTarget impl — already caught by cargo clippy, so not posted as a separate comment per review-signal guidance.
  • Tests cover target-form parsing, filter rejection, triggering-context resolution, and the abandon+comment happy path against a mock ADO server.

🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 151.4 AIC · ⌖ 16.5 AIC · ⊞ 10.1K
Comment /review to run again

jamesadevine and others added 3 commits September 24, 2026 10:05
# Conflicts:
#	src/execute.rs
#	src/mcp.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesadevine jamesadevine changed the title fix(safe-outputs): add Azure DevOps abandon pull request feat(safe-outputs): add Azure DevOps pull request mutations Sep 24, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust code quality review

Note: the rust-critic sub-agent failed to start (model access error, not a timeout), so this review reflects only my own manual pass over the Rust diff (src/**, tests/**, Cargo.toml).

The new PR-mutation safe-output modules (pr_common.rs, pr_mutations.rs, push_to_pull_request_branch.rs, pr_inline.rs, add_pr_labels.rs, etc.) are carefully guarded: exact-head checks, bounded pagination, byte-size limits, and typed newtypes for paths/SHAs/GUIDs are used consistently, and error handling uses anyhow::Context/ensure! throughout with no user-input-reachable unwrap()/expect() found. Casts I checked (i32::MAX as u32, MAX_PATCH as u64, value as u64 after an i64 <= 0 guard) are all safe.

One concurrency concern found and posted inline: Worktree's Drop fallback in push_to_pull_request_branch.rs runs a blocking std::process::Command on the async runtime thread if an early ? return skips the explicit async cleanup().

Themes considered
  • Error handling / anyhow context: clean, no issues found.
  • Unwrap/expect on user-input paths: none found outside #[cfg(test)] blocks and compiler-owned constant .expect() calls.
  • Lossy casts: checked all as u32/u64/usize conversions in the diff; all guarded.
  • HashMap iteration affecting generated YAML determinism: new HashMap usage is confined to execution-time budget-group counters and test fixtures, not codegen output ordering.
  • Async/blocking: one finding (see inline comment on push_to_pull_request_branch.rs).
  • Existing bot comments already cover the mod.rs alphabetical ordering, abandon_pull_request.rs untested rejection paths, and the unreachable common.rs match arm — not duplicated here.

🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 92.8 AIC · ⌖ 1.78 AIC · ⊞ 10.1K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review

This is a large, security-focused PR (new push-to-pull-request-branch safe-output tool, PR-source-identity capture/projection, prefetch workflow token handling, label identity handling, ADO remote-URL parsing hardening). I reviewed it end-to-end against the ado-aw threat model (Stage 1/2/3 boundary, token scoping via ado_bundle.rs, sandbox network isolation, path/ref validation via secure.rs newtypes) and found no security regressions:

  • Token scoping: The new PR-push source-preparation step correctly reuses the existing SC_READ_TOKEN vs System.AccessToken selection pattern (agentic_pipeline.rs), and the actual write (the ADO pushes REST call) happens only in push_to_pull_request_branch.rs::execute_impl, which runs in Stage 3 with the write-capable token — the agent never sees it, matching the three-stage model.
  • Path/ref validation: New fields (repository, expected_head_sha, patch_file) use the existing validated newtypes (RelativeSafePath, CommitSha, StrictRelativePath) rather than raw strings, and the new PrLabelName newtype in secure.rs runs reject_pipeline_injection at deserialization time and is applied consistently across the label tools.
  • Patch application hardening: push_to_pull_request_branch.rs disables git smudge/clean/process filters during patch application (git_without_filters), which reduces attack surface (blocks LFS/custom-filter code execution) rather than weakening it; it also rejects fork-backed PRs, merge/synthetic-merge history, non-branch refs, mismatched source/target, protected files, symlink/submodule changes, and enforces exact-head optimistic-concurrency checks before and after the push, with size/aggregate bounds (5 MB/file, 10 MB aggregate).
  • Credential handling in git fetch fallback: Both the ADO source-commit fetch (ensure_source_commit) and the GitHub Actions PR-diff fallback (pr-data-prefetch.yml / pr-diff-data-fetch.md) pass bearer tokens via GIT_CONFIG_KEY/VALUE env vars (never argv), scrub them from the config after building the header, and validate the origin's scheme/host/path shape before use.
  • ado-remote.ts hardening: The diff tightens URL parsing (rejects control characters, path separators, embedded credentials, ports, unexpected path-segment counts) rather than loosening it.

No instances of a validated newtype being downgraded to a raw String, no new network-allowlist entries, no secret logging, and no write bypassing Stage 3 were found. Nothing here is merge-blocking from a security perspective.

🔒 Security review by PR Security Reviewer · auto · 112.7 AIC · ⌖ 1.8 AIC · ⊞ 10.3K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Quality Sentinel 🧪

This is a very large PR (100 files, ~20k additions) that overall has strong test discipline: shared PR-target resolution (resolve_configured_pr_target, PrMutationPolicy) is exercised centrally in pr_common.rs across all 14 mutation tools, race/fail-closed paths in push_to_pull_request_branch.rs and pr_labels.rs are covered with wiremock, and the previously-flagged gaps in abandon_pull_request.rs (allowed-repositories rejection, title-prefix mismatch, already-abandoned short-circuit) and the unreachable validate_abandon_pull_request_config call in compile/common.rs have since been fixed and tested — this looks like a later iteration of a PR already under review.

One coverage gap found (see inline comment): the new has_current_pr_policy / PR_POLICY_HEADER recompile-provenance marker in src/compile/common.rs, which gates whether the explicit_pr_policy codemod re-pins PR target defaults on every future recompile, has no test anywhere in the diff or repo. A regression there would silently change PR-mutation authority scope on recompiles without any test catching it.

No weakened or deleted assertions found; the one large assertion change (CONFIGURED_ONLY_TOOLS.len() 14→24, plus new negative assertions for update-pr/abbreviated routes) is a legitimate strengthening, not a weakening.

🧪 Test quality analysis by Test Quality Sentinel · auto · 198.2 AIC · ⌖ 1.77 AIC · ⊞ 9.8K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript Code Quality Review 🔵

Reviewed the scripts/ado-script/** portion of this diff (approval-summary, shared/ado-remote, executor-e2e, compiler-smoke-e2e, exec-context-pr-synth/pr-checks). The ts-critic sub-agent failed at startup (400 model "gpt-5.4-mini" is not accessible via the /chat/completions endpoint), so this reflects my own manual pass only, not an adjudicated combination.

Findings posted inline (2):

  1. preserveLargeIntegers in render.ts silently coerces decimal/exponent numeric tokens (e.g. 3.5) into JSON strings, not just full-u64 integers, contrary to its stated purpose — a real correctness bug on the proposal-rendering path.
  2. A minor defense-in-depth note on unsanitized string interpolation in renderPrTarget's triggering-destination message; the value is subsequently escaped as a whole by sanitizeInline when rendered, so it isn't currently exploitable, but it's worth tightening for future-proofing.

What else I checked and found solid:

  • New validation in shared/ado-remote.ts (parseAdoRepoUrl, parseTriggeringPrIdentity, positivePrId) rejects malformed hosts, ports, credentials, control characters, and injection markers ($(, ##vso[, {{) before trusting any triggering-PR identity — good hardening.
  • compiler-smoke-e2e's new retry/cancel state machine in runner.ts bounds transient read failures (max 3) against the existing deadline/cancel-grace-period logic without introducing unbounded retries or losing the original fail-closed abort semantics.
  • AdoRest REST helpers added in this PR (listPullRequestLabels, boundaryTimeline, boundaryArtifacts, verifyBoundaryPush) validate response shape before use and throw descriptive errors rather than silently returning undefined/empty arrays.
  • No unhandled-rejection patterns (async forEach, floating promises) introduced in the reviewed files; Promise.all usages are properly awaited.
  • No secrets/tokens observed reaching a log line or thrown Error message in the reviewed diff.

🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 157 AIC · ⌖ 2.12 AIC · ⊞ 10.1K
Comment /review to run again

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

@jamesadevine

Copy link
Copy Markdown
Collaborator

/review

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

✅ Rust Code Quality Reviewer completed the Rust code quality review.

🦀 Rust code quality review by Rust Code Quality Reviewer

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review

Reviewed this PR's diff (~28.7k lines, 124 files) for security regressions against the code it replaces. Focused on the highest-risk new surface: the new push-to-pull-request-branch safe output (src/safe_outputs/push_to_pull_request_branch.rs), PR target/identity resolution (pr_common.rs, pr_mutations.rs), the triggering-PR identity plumbing (ado-remote.ts, agentic_pipeline.rs), and the PR-policy codemods.

No security regression found. Notable positives:

  • The new patch-apply write path is heavily bounded: exact-head verification before and after push, 5 MB/10 MB size caps, max-files, symlink/submodule/LFS/file-mode-change rejection, protected-file checks, and a worktree-isolated git apply --check dry run before the real apply.
  • New identifier fields (repository, expected_head_sha, file paths) use validated secure.rs newtypes (RelativeSafePath, CommitSha, StrictRelativePath) rather than raw String.
  • The write-capable token still only flows through the existing authenticate_ado_request/UpdatePrContext chokepoint; no new token widened to Stage 1.
  • ado-remote.ts URL/identity parsing was tightened, not loosened (rejects path separators, control characters, ##vso[/$(/{{ injection markers, embedded credentials/ports/query/fragment).
  • No changes to src/allowed_hosts.rs / src/ecosystem_domains.rs — no new AWF network allowlist entries.
  • Sanitization (reject_pipeline_injection, SanitizeContent, neutralize_pipeline_commands) is consistently applied to new agent-controlled fields (reviewer names, labels, repository selectors, PR descriptions).

This PR is security-neutral relative to main; no blocking findings to request changes on.

This review is scoped to the diff only, per the narrow security-regression mandate — a full vulnerability sweep is out of scope here.

🔒 Security review by PR Security Reviewer · auto · 94.3 AIC · ⌖ 2.24 AIC · ⊞ 10.3K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler contract review

Reviewed this PR for ado-aw-specific compiler contracts: front-matter grammar changes, safe-output tool registrations, codemod coverage, typed IR, generated shell, and documentation sync.

No contract violations found. Specifically verified:

  • All three new codemods (0009_split_update_pr, 0010_pull_request_tool_names, 0011_explicit_pr_policy) are registered in CODEMODS and documented in docs/codemods.md with detailed migration semantics.
  • New/renamed safe-output tools (abandon-pull-request, add-pull-request-labels, add-pull-request-reviewers, update-pull-request, set-pull-request-auto-complete, etc.) are wired into ALL_KNOWN_SAFE_OUTPUTS/CONFIGURED_ONLY_TOOLS in src/safe_outputs/mod.rs, and docs/safe-outputs.md was updated with matching sections and renamed examples throughout.
  • Identifier fields follow the src/secure.rs newtype convention where new fields were introduced (e.g. new PrLabelName newtype used by add_pr_labels.rs; CommitSha/GitRefName reused in push_to_pull_request_branch.rs). Existing repository/allowed-repositories String/Vec<String> fields follow the pre-existing repo-wide convention rather than introducing a new gap.
  • AGENTS.md's architecture tree and module list were updated to include every new src/safe_outputs/*.rs file.
  • No Fact/gate IR or PipelineSummary changes in this PR, so no codegen-drift risk there; scripts/ado-script/src/approval-summary/render.ts was updated consistently with the renamed tool keys.
  • No stray .lock.yml added under tests/safe-outputs/, and the workflow .md files that changed (review-rust.md, review-typescript.md, pr-sous-chef.md) all have matching .lock.yml updates.
  • Generated shell in push_to_pull_request_branch.rs/pr_mutations.rs is Stage 3 runtime tokio::process::Command/HTTP code, not compiler-emitted ShellScript pipeline YAML, so the ShellScript/Binding contract doesn't apply there; credentials are passed via Command::env, not interpolated into any generated script body.

This is a large, well-scoped migration with strong test and documentation coverage. Deferring to the other specialist reviewers for general Rust/TypeScript/test-quality/security concerns.

🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 117.3 AIC · ⌖ 1.89 AIC · ⊞ 11.3K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript reviewer (scripts/ado-script/ only)

Reviewed the ado-script diff directly plus a ts-critic sub-agent pass (its single finding is duplicated below; agreed and posted).

The production shared/ado-remote.ts, approval-summary/ and exec-context-pr-synth/ changes are solid: inputs from JSON/env are validated with explicit shape/format checks before use (positivePrId, GUID regex, injection-marker rejection on project/repository_name), errors from JSON.parse are caught, and the new retry/timeout logic in compiler-smoke-e2e/ado-rest.ts and runner.ts is well-reasoned (bounded consecutive-failure tolerance, explicit transient-vs-permanent classification).

Only one advisory finding survived triage — everything else in the diff either has adequate error handling/tests already, or is test-harness-only code exercised against a real ADO project where the existing patterns (thrown errors, AbortSignal.timeout where present) are consistent with the rest of the codebase.

Findings

  1. Missing fetch timeout (low) — executor-e2e/scenarios/pr-comments.ts:191: inconsistent with sibling fetch() calls added in this PR.

Nothing here blocks merge.

🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 228.3 AIC · ⌖ 2.7 AIC · ⊞ 10.2K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Test quality analysis by Test Quality Sentinel · auto · 167.8 AIC · ⌖ 1.71 AIC · ⊞ 9.8K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust code quality review — advisory

Reviewed the Rust portion of the diff (src/**, tests/**, Cargo.toml) directly, plus a rust-critic background pass; its 4 candidates were triaged and 3 were kept as inline comments (1 dropped as a defensive-only, already-unreachable panic surface not worth the noise).

Themes
  • src/execute.rs: the executed-manifest now persists result.data unconditionally (was gated to succeeded/warning) — likely intentional given the matching test rename, but worth confirming every failure_with_data call site in the new PR tools only attaches data safe to retain on failure.
  • src/safe_outputs/push_to_pull_request_branch.rs: Worktree's Drop fallback runs a blocking subprocess call from async execution paths.
  • src/compile/agentic_pipeline.rs: unguarded serde_json::Value index access on resolved_execution_config_json would silently fall back to null rather than failing the compile if the JSON shape ever drifts.

The large volume of new .unwrap()/.expect() in the diff (abandon_pull_request.rs, pr_mutations.rs, pr_common.rs, push_to_pull_request_branch.rs, etc.) was checked line-by-line against each file's #[cfg(test)] boundary — all of it is confined to test code or compiler-owned static/constant values consistent with the project's documented .expect("...") pattern for values that cannot fail. No unwraps on untrusted/external input were found.

🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 664.5 AIC · ⌖ 2.65 AIC · ⊞ 10.2K
Comment /review to run again

…aces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Prompt evaluation

Note

This is an advisory static review. Only Prompt Contracts is merge-blocking.

Suites selected: create (prompts/create-ado-agentic-workflow.md changed), update (prompts/update-ado-agentic-workflow.md changed). debug not selected (unchanged, and no shared-contract/fixture/workflow file in the diff triggers a full re-run).

Prompt Cases Improved Unchanged Regressed Inconclusive
create 3 0 3 0 0
update 3 0 3 0 0

Observation (not a regression)

Both candidate prompts add new PR comment/review/push-branch tool-selection
guidance (add-pull-request-comment vs submit-pull-request-review,
comment/reset vote semantics, update-pull-request vs
update-pull-request-comment, push-to-pull-request-branch constraints,
authority-change caution for target/label limits/approval lanes). This text
is explicitly scoped ("For Azure DevOps PR work...", "For PR comment/review
changes...") so it does not affect the three create or three update cases in
the current suite — none of them exercises a PR comment/review safe output
(they cover a read-only TODO summary, a scheduled work-item comment report,
and an on.pr trigger-filter-mode change). Net effect for the existing
fixtures is neutral; the addition is currently untested by any synthetic
case. Recommend a future case under update (e.g. converting an ad hoc PR
comment into a submit-pull-request-review call, or vice versa) to actually
exercise this new guidance.

Potential regressions

None found.

Per-case scores
Case Prompt Base Candidate Result
create-minimal-manual — task_completion create 2 2 unchanged
create-minimal-manual — grounding create 2 2 unchanged
create-minimal-manual — safety_and_consent create 2 2 unchanged
create-minimal-manual — clarity_and_done_criteria create 2 2 unchanged
create-minimal-manual — create_workflow_coherence create 2 2 unchanged
create-minimal-manual — create_trigger_scope create 2 2 unchanged
create-minimal-manual — create_tools_outputs_permissions create 2 2 unchanged
create-minimal-manual — create_no_action create 2 2 unchanged
create-needs-clarification — task_completion create 2 2 unchanged
create-needs-clarification — grounding create 2 2 unchanged
create-needs-clarification — safety_and_consent create 2 2 unchanged
create-needs-clarification — clarity_and_done_criteria create 2 2 unchanged
create-needs-clarification — create_workflow_coherence create 2 2 unchanged
create-needs-clarification — create_trigger_scope create 2 2 unchanged
create-needs-clarification — create_tools_outputs_permissions create 2 2 unchanged
create-needs-clarification — create_no_action create 2 2 unchanged
create-scheduled-workitem-report — task_completion create 2 2 unchanged
create-scheduled-workitem-report — grounding create 2 2 unchanged
create-scheduled-workitem-report — safety_and_consent create 2 2 unchanged
create-scheduled-workitem-report — clarity_and_done_criteria create 2 2 unchanged
create-scheduled-workitem-report — create_workflow_coherence create 2 2 unchanged
create-scheduled-workitem-report — create_trigger_scope create 2 2 unchanged
create-scheduled-workitem-report — create_tools_outputs_permissions create 2 2 unchanged
create-scheduled-workitem-report — create_no_action create 2 2 unchanged
update-body-only — task_completion update 2 2 unchanged
update-body-only — grounding update 2 2 unchanged
update-body-only — safety_and_consent update 2 2 unchanged
update-body-only — clarity_and_done_criteria update 2 2 unchanged
update-body-only — update_requested_delta update 2 2 unchanged
update-body-only — update_preservation update 2 2 unchanged
update-body-only — update_privilege_discipline update 2 2 unchanged
update-body-only — update_recompile_guidance update 2 2 unchanged
update-pr-filter-mode — task_completion update 2 2 unchanged
update-pr-filter-mode — grounding update 2 2 unchanged
update-pr-filter-mode — safety_and_consent update 2 2 unchanged
update-pr-filter-mode — clarity_and_done_criteria update 2 2 unchanged
update-pr-filter-mode — update_requested_delta update 2 2 unchanged
update-pr-filter-mode — update_preservation update 2 2 unchanged
update-pr-filter-mode — update_privilege_discipline update 2 2 unchanged
update-pr-filter-mode — update_recompile_guidance update 2 2 unchanged
update-safe-output — task_completion update 2 2 unchanged
update-safe-output — grounding update 2 2 unchanged
update-safe-output — safety_and_consent update 2 2 unchanged
update-safe-output — clarity_and_done_criteria update 2 2 unchanged
update-safe-output — update_requested_delta update 2 2 unchanged
update-safe-output — update_preservation update 2 2 unchanged
update-safe-output — update_privilege_discipline update 2 2 unchanged
update-safe-output — update_recompile_guidance update 2 2 unchanged

Generated by Prompt Evaluator for #2222 · auto · 51.9 AIC · ⌖ 1.75 AIC · ⊞ 9.7K · ◷

This branch has not been deployed

No deployments
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