feat(safe-outputs): allow model-selected PR reviewers - #2099
Conversation
|
Azure Pipelines: 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Use generated temporary PR references so update-pr can safely apply model-selected reviewers and other follow-up operations after PR creation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Exercise ordered create-pull-request and update-pr NDJSON execution through the shared temporary PR registry and mocked Azure DevOps APIs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Update existing create-pull-request fixtures for the required temporary ID and add a deterministic create-then-update handoff scenario in one executor process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
✅ Compiler Contract Reviewer completed the compiler contract review.
|
|
✅ TypeScript Code Quality Reviewer completed the TypeScript code quality review.
|
|
✅ Test Quality Sentinel completed the test quality analysis.
|
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
|
✅ PR Security Reviewer completed the security review.
|
There was a problem hiding this comment.
TypeScript review — scripts/ado-script/
Reviewed the new create-pull-request-temporary-id-handoff executor-e2e scenario (the only change under scripts/ado-script/ in this diff).
No blocking issues found:
executedRecordForToolthrows with a clear, contextual error (no executed record found for prior tool ...) rather than silently returningundefined.- ID/type validation goes through the existing
numResult/strResulthelpers, which already guard againstnull/""/falsecoercing to a false-positive0and reject non-string values — noanyor unchecked casts were introduced. Teardown.run()aggregates step failures with per-step context and always attempts every step, consistent with existing usage elsewhere in this file.- The new scenario is registered in the test registry (
index.test.ts), so it has coverage for the new branch.
Note: the ts-critic sub-agent was started in the background as required, but returned no parseable findings after two prompts and the full wait budget; I proceeded with my own pass only.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 35.7 AIC · ⌖ 2.81 AIC · ⊞ 10.6K
Comment /review to run again
There was a problem hiding this comment.
Test Quality Sentinel review
The new temporary-PR-reference machinery is well covered for the happy path (id parsing, reviewer allowlist/dedup/limit, identity-lookup exact-match, warning-vs-failure structuring, and an end-to-end create→update NDJSON test in execute.rs). Three gaps in error-path coverage for genuinely security-relevant new logic:
- Reviewer input validation (max 100 entries, length cap, pipeline-injection rejection in
UpdatePrParams::validate) has no test for itsErrarms. resolve_update_pr_target's temporary-reference failure branches (unresolved ID, repository mismatch, disallowed repository) are untested — only the success path has a unit test.- The duplicate-
temporary_idguard increate_pull_request.rs(rejecting reuse of a temporary ID within the same run) is never exercised by any test.
None of these are merge-blocking on their own — the happy-path and structural tests are solid — but each is a new fail-closed safety check whose regression would silently ship broken security behavior.
🧪 Test quality analysis by Test Quality Sentinel · auto · 105.3 AIC · ⌖ 1.98 AIC · ⊞ 10.3K
Comment /review to run again
There was a problem hiding this comment.
Compiler Contract Review
Reviewed the temporary PR-reference handoff (create-pull-request → update-pr). This mirrors the existing WorkItemTemporaryId/GithubTemporaryId contract exactly: same validated_string! macro in src/secure.rs, same impl_temporary_reference_deserialize! macro reuse, same registration/resolution pattern in ExecutionContext (resolved_pull_requests alongside resolved_work_items/resolved_github_issues), and the deny_unknown_fields + schema-exclusion tests match the create-work-item precedent.
No drift found:
- No codegen/gate IR touched, so
types.gen.ts/fact-catalog.gen.jsonare correctly untouched. - No
.github/workflows/*.mdchanged, so no lock-file drift. docs/safe-outputs.mdwas updated with the newupdate-prconfig keys (allowed-reviewers,max-reviewers), the temporary-ID handoff semantics, and an example call sequence.AGENTS.mdarchitecture tree needs no changes (no new module/file added, only existing files modified).- The new
PullRequestTemporaryIdcorrectly reuses thesrc/secure.rsvalidated-newtype pattern rather than a rawString, satisfying the safe-output identifier-typing contract.
No compiler-contract violations found. cargo build and the targeted update_pr/create_pull_request test suites pass.
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 79 AIC · ⌖ 14.2 AIC · ⊞ 11.8K
Comment /review to run again
There was a problem hiding this comment.
Rust review: PASS with one advisory nitpick
Well-structured change: anyhow context is preserved throughout, u64 used consistently for PR IDs (fixing a prior i32/lossy-cast risk), the temporary-ID registry uses a poison-safe Mutex with duplicate-registration rejection, reviewer resolution requires exact case-insensitive matches with ambiguity rejection, and safe-output execution is sequential/ordered so the create→update temporary-ID handoff has no concurrency hazard. Test coverage is thorough (unit + wiremock + TS executor-e2e scenario).
One non-blocking nitpick posted inline: the ad-hoc GUID-shape sniff in lookup_reviewer_id duplicates existing UUID validation elsewhere in the codebase instead of reusing it.
Note: the rust-critic sub-agent did not return usable output after two attempts (idle turns with an empty response), so this review reflects my own pass only.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 108.3 AIC · ⌖ 15.9 AIC · ⊞ 10.5K
Comment /review to run again
There was a problem hiding this comment.
🔒 Security review by PR Security Reviewer · auto · 182.7 AIC · ⌖ 2.59 AIC · ⊞ 10.7K
Comment /review to run again
Keep temporary PR producers and consumers in one approval lane, align reviewer allowlists with gh-aw, encode identity queries safely, and make executor E2E cleanup reclaim created PRs after consumer failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Create a real PR, resolve its temporary ID through update-pr add-reviewers, verify the configured Azure DevOps identity is present, and clean up safely on failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
|
/review |
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
There was a problem hiding this comment.
TypeScript review — scripts/ado-script/ (this PR's TS diff)
Scope: scripts/ado-script/src/executor-e2e/** only (test-harness module, explicitly documented as "not shipped in ado-script.zip") — no other scripts/ado-script/ files changed in this PR.
Sub-agent note: the ts-critic sub-agent was started on the diff but remained idle with no output after two blocking waits (~130s total); its findings are unavailable for this review and are not reflected below.
My own pass:
ado-rest.ts::resolveIdentityId— GUID passthrough, single-match, and ambiguous-match branches are all covered by the newado-rest.test.tscases.authedFetchcentralizes auth header injection and already applies a per-requestAbortSignal.timeout, so the new identity-lookup call inherits timeout/retry-adjacent safety without extra work.runner.tscorrectly threadsexecutedRecordsintocleanup()even on execute-phase failure, and the new runner test exercises exactly that path (prior success + primary failure).create-pull-request.ts's newresolveExecutorE2eReviewerguards against an unexpanded ADO macro literal ($(Build.RequestedForEmail)) reaching the harness, with matching tests.- No unhandled promise rejections, swallowed errors,
anyleaks, or token/secret log exposure found in the changed lines. Case-insensitive reviewer/added-list comparisons and error propagation all look correct.
No merge-blocking defects found in this file set. Deeper identity-verification and GUID-bypass concerns for the production update-pr Rust implementation are already covered by other reviewers' existing comments on src/safe_outputs/update_pr.rs (out of this reviewer's TypeScript-only scope).
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 58.2 AIC · ⌖ 11 AIC · ⊞ 10.5K
Comment /review to run again
There was a problem hiding this comment.
Security review
Reviewed this diff against main for regressions in the trust boundaries this repo cares about (Stage 1 read-only token isolation, Stage 3 write-token scoping, injection into generated shell/YAML, validated newtypes, path traversal, network allowlist, safe-output integrity).
No new security regressions found. This PR is security-neutral to net-positive:
CreatePrParamsnow has#[serde(deny_unknown_fields)]and drops the inlinereviewersfield, so the agent can no longer smuggle reviewers (or a self-chosentemporary_id) past theupdate-pr.allowed-reviewers/max-reviewerspolicy checks — reviewer selection is forced through the policy-checkedupdate-pr add-reviewerspath.- The new
PullRequestReference::Temporaryhandoff is scoped to one in-processExecutionContext(an in-memoryHashMapkeyed bytemporary_id.canonical()), and the compiler (common.rs::validate_pull_request_outputs_config) now requirescreate-pull-requestandupdate-prto share the same effectiverequire-approvallane, so a temporary ID can never cross the Stage-3-automatic / Stage-3-reviewed job boundary. - Repository re-validation is re-applied on the resolved target for temporary references (
resolved.target.aliasis checked against both the caller'srepositoryselector andallowed-repositories), so a temporary PR reference can't be used to write into an unintended or cross-org repository. - Reviewer strings continue to go through
reject_pipeline_injectionbefore being embedded in ADO log/error output, and the identity lookup moved from manual string interpolation (filterValue={encoded}) toreqwest's.query()builder, a hardening rather than a regression. - Token handling is unchanged:
SYSTEM_ACCESSTOKEN/AZURE_DEVOPS_EXT_PATstill flows only through the existingauthenticate_ado_requesthelper into Stage 3 REST calls; no new token projection into Stage 1 or into logs.
The one pre-existing weakness touched by this diff — the ad-hoc 36-char/4-hyphen/hex "looks like a GUID" heuristic in lookup_reviewer_id/resolveIdentityId that skips identity verification for any GUID-shaped reviewer string — is already flagged by existing review comments on this PR (low severity, medium confidence), so it is not repeated here.
No blocking findings; nothing further to add beyond what is already tracked in existing comments.
🔒 Security review by PR Security Reviewer · auto · 111 AIC · ⌖ 2.9 AIC · ⊞ 10.7K
Comment /review to run again
There was a problem hiding this comment.
Compiler Contract Review — no blocking findings
Reviewed the new PR temporary-ID handoff (create-pull-request → update-pr) against the ado-aw compiler contracts:
- Newtype pattern:
PullRequestTemporaryId(src/secure.rs) andPullRequestReference(src/safe_outputs/update_pr.rs) correctly follow the existingWorkItemTemporaryId/impl_temporary_reference_deserialize!convention used by create-work-item/update-work-item. - Schema boundary:
CreatePrParamsgained#[serde(deny_unknown_fields)], correctly blocking the internaltemporary_idand the removed inlinereviewersfield from agent input (verified bytest_create_pr_schema_excludes_internal_and_inline_reviewer_fieldsand the params-level test). - Approval-lane contract:
validate_pull_request_outputs_configinsrc/compile/common.rsreusesrequire_same_approval_laneto forcecreate-pull-requestandupdate-printo the same Stage 3 job when temporary IDs are shared — consistent with the work-item precedent, and wired intoagentic_pipeline.rs::validate_pipeline_front_matter. - Registry:
ResolvedPullRequest/resolved_pull_requestsinsrc/safe_outputs/result.rsmirrors the existingresolved_work_itemsmap (lock-poison handling, duplicate-registration rejection,Defaultwiring). - Docs:
docs/safe-outputs.mdwas updated in the same commit with the temporary-ID description,allowed-reviewers/max-reviewerssemantics, and the same-approval-lane requirement. - No drift:
pr-meta.json's file list shows no.lock.yml,*.gen.ts/*.gen.json, orscripts/ado-script/*.jsbundle changes needed — nothing in this diff touches the gate/fact IR or ado-script bundle sources, so no codegen/bundle drift applies here.
The five pre-existing review comments (missing-test coverage for the reject arm, temporary-reference failure branches, duplicate-temporary_id guard, and the GUID-sniff reviewer bypass) already cover the test-completeness and identity-verification concerns I would otherwise raise, and are outside compiler-contract scope proper — no new findings to add on top of those.
No inline comments posted (no new contract-drift findings on changed lines).
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 110.3 AIC · ⌖ 2.86 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
Test Quality Review
Overall the new test suite for the temporary-ID handoff is thorough: reviewer policy (allowlist/wildcard/dedup/limit), identity exact-match/ambiguity/injection-safe query encoding, the create→update handoff end-to-end (both Rust unit test and TS executor-e2e scenarios), and cleanup-on-failure recovery are all covered.
Two untested error-path gaps I found beyond what prior review passes already flagged (reviewer count/length/injection validation, resolve_update_pr_target's temporary-reference failure branches, and the GUID-sniff reviewer bypass are already covered by existing comments on this PR):
PullRequestReference's deserializer negative-number and quoted-out-of-range arms are untested (inline comment onupdate_pr.rs).CreatePrResult::execute'sregister_resolved_pull_requestfailure branch — the one path that can leave a real ADO PR created but itstemporary_idunregistered — has no test proving thefailure_with_datapayload is correct (inline comment oncreate_pull_request.rs).
Neither is merge-blocking; both are advisory gaps in otherwise strong coverage.
🧪 Test quality analysis by Test Quality Sentinel · auto · 151.8 AIC · ⌖ 9.28 AIC · ⊞ 10.3K
Comment /review to run again
There was a problem hiding this comment.
🟡 Changes recommended
Four unresolved moderate findings remain in validation, GUID handling, and persisted warning data.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds same-run temporary PR references so update-pr can operate on PRs created earlier in the SafeOutputs job, including dynamic reviewer selection.
Changes:
- Adds temporary PR ID generation, persistence, routing, and approval-lane validation.
- Adds reviewer allowlists, limits, exact identity matching, and structured outcomes.
- Expands Rust, TypeScript, E2E, and documentation coverage.
Review findings:
src/compile/common.rs:2987— Moderate, 3 votes: validatemax-reviewersas a positiveusizeduring compilation.src/safe_outputs/update_pr.rs:509— Moderate, 1 vote: reuse full reviewer validation for execution records.src/safe_outputs/update_pr.rs:1056— Moderate, 1 vote: use canonical GUID validation.src/safe_outputs/update_pr.rs:487— Moderate, 1 vote: persist structured warning data.
File summaries
| File | Reviewed change |
|---|---|
tests/executor-e2e/README.md |
Documents reviewer scenario configuration. |
tests/executor-e2e/azure-pipelines.yml |
Passes reviewer configuration to E2E runs. |
tests/compiler_tests.rs |
Tests approval-lane validation. |
src/secure.rs |
Adds validated temporary PR IDs. |
src/safe_outputs/upload_build_attachment.rs |
Updates execution-context fixtures. |
src/safe_outputs/update_pr.rs |
Supports temporary references and reviewer operations. |
src/safe_outputs/result.rs |
Stores PR registry and warning results. |
src/safe_outputs/mod.rs |
Exposes repository target types. |
src/safe_outputs/create_pull_request.rs |
Registers temporary PR references. |
src/mcp.rs |
Generates and returns temporary PR IDs. |
src/execute.rs |
Tests ordered create/update execution. |
src/compile/common.rs |
Validates PR configuration and reviewer limits. |
src/compile/agentic_pipeline.rs |
Runs PR configuration validation. |
scripts/ado-script/src/executor-e2e/scenarios/create-pull-request.ts |
Adds temporary-ID and reviewer scenarios. |
scripts/ado-script/src/executor-e2e/scenario.ts |
Extends scenario cleanup contracts. |
scripts/ado-script/src/executor-e2e/runner.ts |
Propagates cleanup records. |
scripts/ado-script/src/executor-e2e/ado-rest.ts |
Adds exact identity lookup. |
scripts/ado-script/src/executor-e2e/__tests__/runner.test.ts |
Tests cleanup record propagation. |
scripts/ado-script/src/executor-e2e/__tests__/index.test.ts |
Tests scenario registration. |
scripts/ado-script/src/executor-e2e/__tests__/create-pull-request-scenarios.test.ts |
Tests reviewer handoff scenarios. |
scripts/ado-script/src/executor-e2e/__tests__/ado-rest.test.ts |
Tests identity lookup behavior. |
docs/safe-outputs.md |
Documents temporary references and reviewer policies. |
Review details
Suppressed comments (3)
src/safe_outputs/update_pr.rs:516
- The MCP-side
UpdatePrParams::validateenforces the 100-entry, non-empty, 256-character, and pipeline-injection checks, but Stage 3 deserializesUpdatePrResultdirectly and this execution-side validator only applies the allowlist/deduplication/max checks. An untrusted execution record can therefore bypass those new bounds and drive an excessive number of identity/API requests or oversized query values. Reuse the same reviewer validation before the write loop.
let mut normalized = Vec::new();
for reviewer in reviewers {
let reviewer = reviewer.trim();
if !allow_any
&& !config
.allowed_reviewers
.iter()
.any(|allowed| allowed.eq_ignore_ascii_case(reviewer))
src/safe_outputs/update_pr.rs:1060
- This predicate only counts four hyphens anywhere in a 36-character hex string, so malformed GUID-shaped reviewer values can bypass identity lookup and be sent directly as the reviewer ID. That violates the exact identity-resolution contract and turns a valid display name/email into a failed PUT in this edge case; use the shared canonical 8-4-4-4-12 GUID validator instead.
if reviewer.len() == 36
&& reviewer
.chars()
.filter(|character| *character == '-')
.count()
src/safe_outputs/update_pr.rs:490
- Although this builds the required
addedandfailedarrays,append_execution_recordcurrently writesresult: Nonefor every warning status, sowarning_with_datais discarded fromsafe-outputs-executed.ndjson. The structured reviewer outcome is therefore unavailable to executor-E2E/audit consumers; preserve warning data in the execution record (or expose it through an equivalent persisted path) while keeping the warning status.
if has_failures {
ExecutionResult::warning_with_data(message, data)
} else {
ExecutionResult::success_with_data(message, data)
- Files reviewed: 22/22 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Verify GUID reviewer identities, reject invalid reviewer limits at compile time, and add regression coverage for reviewer validation and temporary PR reference failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Verify GUID reviewers through the documented identity API, enforce consistent staged behavior for temporary-ID workflows, preserve structured warning results, and exercise both reviewer lookup paths end to end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Keep the general reviewer E2E temporary reference within the validated 12-character suffix limit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
Exercise generated PR proposals through offline dry-run deserialization, cover malformed payloads, and run the contract in CI for Rust schema changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d15887-5c0e-4975-98a2-82ca84d35d5f
|
/review |
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
There was a problem hiding this comment.
Compiler-contract review
Checked drift (ado-script bundles/codegen/lock files), front-matter grammar, safe-output newtype usage, typed IR, and docs sync for this PR.
PullRequestTemporaryIdfollows the existingGithubTemporaryId/WorkItemTemporaryIdpattern correctly (validated newtype insrc/secure.rs,impl_temporary_reference_deserialize!macro reuse).- New
update-pr.max-reviewers/allowed-reviewersfields areOption/defaulted, so existing workflows still parse — no codemod needed. update-pr.max-reviewersis validated at compile time as a positiveusize(rejects negative/fractional/overflow) insrc/compile/common.rs, matching Stage 3 deserialization.- GUID reviewer identities are verified against VSSPS via a
Guid-typed lookup instead of being trusted on shape alone;src/ado/mod.rs::is_uuid_likenow delegates to the same sharedcrate::validate::is_valid_guid. require_same_approval_laneand the newrequire_same_staged_lanecorrectly gatecreate-pull-request/update-prpairing so temporary PR IDs cannot cross a SafeOutputs-job boundary.docs/safe-outputs.mddocuments the new fields, the temporary-ID handoff, and the staged/approval-lane requirements.- No
.lock.yml/ codegen / bundle drift: no.github/workflows/*.mdchanged, andtypes.gen.ts/fact-catalog.gen.jsonare untouched (consistent with the filter IR being unchanged in this PR).
All findings raised by earlier automated reviews on this PR (missing tests for validation error arms, duplicate-temporary-ID guard, GUID reviewer bypass) were addressed in commit 4dbcee4. No new compiler-contract issues found.
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 69.8 AIC · ⌖ 1.84 AIC · ⊞ 11.3K
Comment /review to run again
There was a problem hiding this comment.
Security review
This diff (temporary PR-reference handoff between create-pull-request and update-pr, plus reviewer allowlisting) is security-neutral to positive:
- New
PullRequestReference/PullRequestTemporaryIdhandling is exact and validated (Guid::parse,reject_pipeline_injectionon every reviewer string,##vso[/$(/{{all rejected). - Temporary-ID resolution enforces repository-target and allowed-repositories checks even on the temporary path, and rejects duplicate registration before any PR is created (tested).
- Reviewer resolution now requires an unambiguous case-insensitive exact match on email/account/display-name (rejecting fuzzy/multi-match results), and GUID-shaped input is verified against a real VSSPS identity before being trusted — closing the gap flagged in earlier review rounds.
execute.rs/update_pr.rsnow route every ADO call through the sharedauthenticate_ado_requestchokepoint instead of ad-hocbasic_auth, so token-scheme selection (Bearervs Basic) stays centralized — a hardening, not a regression.max-reviewersdeserializes strictly to a positiveusizeat compile time (rejecting negative/fractional/quoted/overflow values), addressing the prior Copilot finding on that field.- No new domains, no widened token scope, no bypass of Stage 3 write-token isolation —
update-pr/create-pull-requestwrites remain confined to Stage 3, and staged/require-approval lane-matching is extended consistently to the new temporary-ID family.
No exploitable regression found in this diff; all previously-flagged items in review comments appear addressed. No blocking findings.
🔒 Security review by PR Security Reviewer · auto · 87.2 AIC · ⌖ 9.39 AIC · ⊞ 10.2K
Comment /review to run again
There was a problem hiding this comment.
This PR's test additions are notably thorough. Reviewing beyond the prior feedback thread (all of which has since been addressed in 4dbcee44):
- Error-path coverage is strong:
resolve_update_pr_targethas direct unit tests for all threeTemporaryfailure branches (unresolved ID, repository mismatch, allowlist exclusion), reviewer validation covers length/count/injection limits, and the GUID identity lookup has five distinct rejection-path tests (missing, duplicate, mismatched, HTTP error, malformed JSON) plus an exact-match ambiguity test. - No assertions were weakened or removed — the two
-assertlines in the diff are pure reformatting (multi-line wrap), not semantic changes. - No
tests/safe-outputs/*.lock.ymldrift and no per-case ADO definition additions in the smoke suite — both invariants this repo cares about are respected. - TS and Rust coverage are symmetric: the executor-e2e scenario tests exercise the same create→update temporary-ID handoff, duplicate-ID rejection, and reviewer resolution edge cases as the Rust unit tests, including a rejected-live-ADO-state case.
Minor, non-blocking observation: the five reviewer_guid_lookup_rejects_* tests (update_pr.rs) are structurally identical (mock a GET /_apis/identities response shape, assert None) and could be collapsed into one table-driven test over the five response bodies, which would make it easier to add a sixth case later without copy-pasting ~30 lines of wiremock boilerplate each time. Not a blocker.
No new test-quality issues found.
🧪 Test quality analysis by Test Quality Sentinel · auto · 147.2 AIC · ⌖ 1.78 AIC · ⊞ 10.9K
Comment /review to run again
There was a problem hiding this comment.
Verdict: COMMENT (no merge-blocking defects found)
This is a well-tested, carefully implemented feature. Temporary-PR-ID plumbing (lock scoping, collision retry, duplicate-registration guard) mirrors the existing create-work-item pattern correctly, the u64 PR-ID conversion and Guid-based reviewer verification are sound, and the approval/staged-lane cross-validation is thorough. I did not find any correctness, panic-risk, or error-handling regressions in the changed lines worth a merge-blocking comment.
Note: the rust-critic sub-agent failed to start (400 model "gpt-5.4-mini" is not accessible), so this review reflects only my own manual pass over the Rust diff (src/ado, src/audit, src/compile/{agentic_pipeline,common}, src/execute, src/mcp, src/safe_outputs/{create_pull_request,mod,result,update_pr,upload_build_attachment}, src/secure, tests/compiler_tests).
Themes checked
- Error handling: anyhow context preserved throughout; no new unwrap/expect on request-reachable paths (pr_id, reviewer JSON, etc. all use safe fallbacks with explicit zero/none handling).
- Correctness: pullRequestId now read via
as_u64(wasas_i64().unwrap_or(0)), consistent with the newu64PR-ID type end-to-end; GUID reviewer path now round-trips through the sharedGuidvalidated type and a real identity lookup rather than a shape-only check. - Concurrency:
create_pr_proposal_lockcorrectly serializes temp-ID allocation + NDJSON append, matching the pre-existingcreate_work_item_proposal_lockpattern; no.awaitheld across astd::sync::Mutexguard (theresolved_pull_requestsmap usesstd::sync::Mutexonly for synchronous read/insert). - Maintainability:
UpdatePrContext/PrContextparam-object refactors reduce per-function argument counts; new public items (ResolvedPullRequest,PullRequestReference, reviewer validation helpers) are documented. - Sequential execution of the NDJSON queue in
execute_safe_outputs(confirmed viasrc/execute.rs) guarantees acreate-pull-requestproposal is fully resolved before a same-jobupdate-prconsumer can reference its temporary ID, so the new approval/staged-lane validation is not just defense-in-depth but load-bearing.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 189.4 AIC · ⌖ 2.34 AIC · ⊞ 10.1K
Comment /review to run again
Summary
Model-selected reviewers need the PR created by the same run, but Azure DevOps assigns its numeric ID only during SafeOutputs execution. Rather than making reviewer selection a special inline field on
create-pull-request, this change adds composable temporary PR references:create-pull-requestreturns and persists an MCP-generated#aw_...temporary ID; it is internal execution metadata, not model-supplied input.update-properation accepts either a numeric PR ID or a same-job temporary ID.update-pr add-reviewersoperation. Omittedallowed-reviewerspermits any valid reviewer, matching gh-aw; a non-empty list restricts selection, andmax-reviewersprovides a separate bound.addedandfailedarrays, while policy and reference failures remain hard failures.create-pull-request.reviewersremain unchanged.Temporary IDs resolve in NDJSON proposal order and only within one SafeOutputs job. Automatic and manually reviewed outputs run in separate jobs and therefore cannot share temporary references; compilation requires
create-pull-requestandupdate-prto use the same effectiverequire-approvalsetting.Test plan
update-prrequest.cargo test --all-targets.cargo clippy --all-targets.Review follow-ups
The branch review identified four actionable gaps, all addressed:
create-pull-requestandupdate-prconfigurations that resolve to different approval lanes, because temporary references are process-local to one SafeOutputs job.allowed-reviewerslist is restrictive. Exact and unambiguous Azure DevOps identity resolution remains fail-closed.update-properation fails.Latest feedback addressed (
4dbcee44)Guidtype and verified GUID identities through VSSPS before reviewer assignment.max-reviewersnow must deserialize as a positiveusizeduring compilation; negative, fractional, quoted, zero, and overflowing values are rejected.Cross-version replay of historical post-MCP NDJSON remains intentionally unsupported. Supported
audit,trace, MCP-author audit, and approval-summary debugging paths parse proposal records generically and are unaffected by the required internaltemporary_id.Validation
cargo check --all-targetscargo test --all-targetscargo clippy --all-targets4dbcee44(cargo test, clippy with warnings denied, andcargo check)639050passed, includingcreatePullRequestTemporaryIdHandoffcreate-pull-request→update-pr add-reviewerstemporary-ID scenario that resolves the configured reviewer, verifies real PR membership by identity ID, and cleans up on failure639292rancreate-pull-request-add-reviewersagainst a real PR, addeddevinejames@microsoft.com, verified reviewer membership by identity ID, and completed cleanupFinal integration review fixes (
4d98c8d0)identityIdsquery and require exactly one matching real identity.stagedsettings across pull requests, work items, and GitHub issues, in addition to matching approval lanes.resultdata while retaining the human-readable warning inerror.update-pr add-reviewers.#aw_prreviewgen(220878c4).641376passed both live reviewer paths: GUIDidentityIdson Azure DevOps scratch PR ID 41799 and raw email/namesearchFilteron Azure DevOps scratch PR ID 41800; both verified membership and completed cleanup.Final local validation: 3,670 Rust tests and 1,205 TypeScript tests passed, with clean clippy,
cargo check, TypeScript typecheck, executor bundle build, and diff checks.