Repository navigation
feat(safe-outputs): split Azure DevOps PR tools with safe migration - #2222
jamesadevine with Copilot wants to merge 56 commits into
Conversation
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: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
|
✅ PR Security Reviewer completed the security review.
|
|
✅ Compiler Contract Reviewer completed the compiler contract review.
|
|
✅ Test Quality Sentinel completed the test quality analysis.
|
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
There was a problem hiding this comment.
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 callsqueue_sanitized_output— it never touches the write-capable token. The actual PATCH/POST happens inexecute.rs/abandon_pull_request.rs'sExecutor::execute_impl, which runs in Stage 3 withctx.access_token(the write-capable token the agent never sees), consistent withupdate-prandsubmit-pr-review. - Sanitization: the agent-supplied
bodyis passed throughsanitize_textinsanitize_content_fieldsbefore being reused as the abandonment comment, andrepositorythroughsanitize_config— matching the existing pattern for other PR-comment safe outputs. - Repository resolution: uses the same
resolve_repo_name+allowed-repositoriesallowlist gate already used byupdate-pr,submit-pr-review, andresolve-pr-thread. Repository/project segments are percent-encoded (utf8_percent_encodewithPATH_SEGMENT) before being placed in the URL, so no path/URL injection viarepository. - State check before mutation: the PR is fetched and its
statusvalidated (activerequired,abandonedshort-circuited) and title/label filters applied before thePATCHthat 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 noado-aw-debuggate 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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
- Likely dead validation code (comment on
src/compile/common.rs): the new"abandon-pull-request"match arm lives insidevalidate_github_issue_outputs_config, which only iteratesGITHUB_ISSUE_SAFE_OUTPUT_TOOLS— a listabandon-pull-requestis not part of. If that reading is correct,validate_abandon_pull_request_configis never invoked at compile time and an invalid config (e.g. an emptyrequired-labelsentry) will silently compile. No test exercises this path, which is exactly why it slipped through. - Untested control-flow branches in
abandon_pull_request.rs: theallowed-repositoriesrejection, therequired-title-prefixmismatch, 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
There was a problem hiding this comment.
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-criticsub-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
anyhowcontext consistently, nounwrap/expecton user-input paths, repository/target resolution mirrorsresolve_repo_name/resolve_repository_write_targetconventions used elsewhere. - One clippy
derivable_implshint on the manualDefault for AbandonPullRequestTargetimpl — already caught bycargo 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
# Conflicts: # src/execute.rs # src/mcp.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
This comment has been minimized.
This comment has been minimized.
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
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
This comment has been minimized.
This comment has been minimized.
Skip observed membership without state-changing writes and add missing identities through the collection endpoint. Document the accepted concurrent required-status race and report partial creation follow-ups. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
ADO forbids a PR creator declining review. Keep the optional/flagged fixture valid and retain no-write declined-state coverage in deterministic tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
This comment has been minimized.
This comment has been minimized.
Close the review coverage gap for no-op abandonment results without changing production behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
|
/review |
|
There was a problem hiding this comment.
Compiler Contract Review
Checked for the usual drift classes: ado-script bundle/codegen drift (none — no filter_ir.rs/Fact changes), compiled-workflow drift (.lock.yml files move alongside their .md sources, pr-sous-chef/review-rust/review-typescript all consistent), tests/safe-outputs/ smoke sources (markdown-only, correctly registered via tests/smoke/cases.json, no stray lock files), front-matter grammar additions (Option/defaulted, codemods 0009–0011 properly registered in CODEMODS), and safe-output identifier typing (PrLabelName newtype correctly added to src/secure.rs and used across add_pr_labels.rs/remove_pull_request_labels.rs/replace_pull_request_label.rs; push_to_pull_request_branch.rs correctly uses GitRefName/CommitSha).
Two documentation-sync gaps found (both non-blocking):
AGENTS.md'ssrc/safe_outputs/tree is missing ~10 of this PR's new files (mark_pull_request_as_ready_for_review.rs,pr_comments.rs,pr_http.rs,pr_inline.rs,pr_labels.rs,pr_patch.rs,push_to_pull_request_branch.rs,remove_pull_request_labels.rs,replace_pull_request_label.rs,update_pull_request_comment.rs) — only 6 of the ~16 new modules were added to the tree.safe-outputs.budget-groupsis a new author-writable front-matter key (validated inpr_migration.rs::validate_budget_groups, constructed directly in front matter bytests/codemod_tests.rs) but is documented only as a migration side effect, not as a standalone feature authors can use.
Everything else checked (codegen, schemas, registries, extension registration, generated-shell patterns) is consistent with the existing contracts.
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 137.6 AIC · ⌖ 1.98 AIC · ⊞ 12.5K
Comment /review to run again
There was a problem hiding this comment.
Security review — no regressions found
This diff is a large, security-conscious refactor of the Azure DevOps PR safe-output tools (new pr_patch, pr_http, pr_mutations, pr_common modules; new abandon-pull-request / push-to-pull-request-branch tools). I focused on the threat model for this repo: injection into generated pipelines, weakened validation, path traversal, token scope/projection, network boundary, and safe-output integrity.
Findings: none that weaken the existing security posture. Highlights that strengthen it:
- Bounded HTTP everywhere —
pr_http.rsintroduces a shared 8 MiB response bound and continuation-token/partial-page rejection;create_pull_request.rs's old rawresp.json()call was migrated tobounded_json(). - New validated newtype —
PrLabelNameinsrc/secure.rsrunsreject_pipeline_injectionplus length/control-char checks at deserialization time, consistently used acrossadd_pr_labels.rs,remove_pull_request_labels.rs,replace_pull_request_label.rsinstead of rawString. push-to-pull-request-branchenforces an exact expected-head guard, rejects fork-backed PRs, non-branch refs, completed/abandoned PRs, and verifies the ADO push response's ref/commit/parent before declaring success — a solid anti-TOCTOU design including a re-check for the source moving during patch preparation.- Patch parsing (
pr_patch/parse.rs) is a from-scratch, bounds-checked unified-diff/binary-delta parser (size caps, symlink rejection, path validation viaRelativeSafePath, conflicting-destination/removal detection) rather than shelling out to an unboundedgit apply. - Git subprocess hygiene —
git_command()usesenv_clear()with an explicit allow-list andGIT_TERMINAL_PROMPT=0/GIT_NO_LAZY_FETCH=1, and the PR-diff-prefetch GitHub Actions step scopes its credential header to the exactGITHUB_SERVER_URLhost withhttp.followRedirects=falsebefore fetching pinned base/head SHAs. - Token/target integrity —
PrMutationPolicy/resolve_pr_policy_targetvalidate every policy string throughreject_pipeline_injection, andsource_state()cross-checks repository/project/PR identity against the authorized target before any write.
I did not find any new unsanitized value reaching a bash:/##vso[...]/template-expression sink, any newtype-to-String downgrade, or any token reaching a Stage 1 (Agent) step beyond the existing SC_READ_TOKEN/System.AccessToken pattern already used elsewhere in the compiler. Nothing here is security-neutral filler either — several of the above are genuine hardening over the previous implementation (e.g., replacing the old format-patch/synthetic-commit flow in mcp.rs with an isolated private-index capture).
No blocking issues from a security standpoint.
🔒 Security review by PR Security Reviewer · auto · 125.2 AIC · ⌖ 3.27 AIC · ⊞ 11.4K
Comment /review to run again
There was a problem hiding this comment.
Reviewed the PR-safe-outputs test suite (the Rust src/safe_outputs/pr_* family, pr_patch/parse.rs, and related). Overall the test quality here is strong: ownership/provenance in pr_comments.rs, label-transition policy in pr_labels.rs, and inline-review supersession in submit_pr_review.rs all have thorough, behavior-focused test coverage with real adversarial cases (self-approval, concurrent edits, forged ownership markers, head-moved races).
Two gaps worth closing before this consolidates further:
unquote()in the newpr_patch/parse.rs— this git-style octal/backslash path-escape decoder for untrusted patch content has no direct unit test; only one happy-path octal sequence is exercised indirectly throughinspected_paths. Malformed-escape edge cases (unterminated quote, truncated octal, invalid escape char) aren't covered.update-pull-request-comment— this new 168-line tool has zero tests of its own. The only coverage is a shared cross-tool smoke test that checks the generic required-labels gate, not this tool's ownership verification,comment-keybounds, or ID validation.
Both findings are posted inline. Nothing else in the diff showed weakened assertions — files like update_pr.rs and the inline-comment helpers in add_pr_comment.rs had code removed, but the corresponding behavior and assertions moved to new modules (pr_inline.rs, abandon_pull_request.rs, etc.) with equivalent or stronger coverage, which is a legitimate refactor rather than a regression.
🧪 Test quality analysis by Test Quality Sentinel · auto · 212.5 AIC · ⌖ 1.83 AIC · ⊞ 11K
Comment /review to run again
There was a problem hiding this comment.
TypeScript review (scripts/ado-script/)
Reviewed the production TS diff (28 files, test/generated files excluded per workspace convention). The ts-critic sub-agent ran in the background and returned one finding after a brief retry (first turn had no diff attached; it answered once I pointed it at the on-disk file).
Overall: solid. The new ADO REST helpers (ado-rest.ts boundary-PR/ref APIs), ado-remote.ts identity parsing, and approval-summary PR-target rendering are thoroughly defensive — strict shape validation on every REST response, AdoHttpError with typed retry classification, force-with-lease SHA-pinned ref deletion with reconciliation on failure, bounded integer parsing (positivePrId, preserveLargeIntegers for u64 IDs), and injection-safe marker sanitization ($(, ##vso[, {{ rejected in trusted identity fields). No unhandled rejections, no any/unchecked casts on external data, no secret leakage into logs or errors.
One minor finding (inline, test-harness scope): a readback fetch() in pr-comments.ts is missing the AbortSignal.timeout its sibling mutation call uses.
Not merge-blocking.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 376.4 AIC · ⌖ 2.67 AIC · ⊞ 11.4K
Comment /review to run again
Close documentation and behavior-test gaps, bound inline readback, and keep dispatched review comments and summaries scoped to trusted PR context and revision. Reserve time for Rust review submission without broadening permissions or runtime limits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
There was a problem hiding this comment.
Compiler contract review — no blocking findings
Checked the areas in my mandate against this PR:
- Codegen/lock drift: no
Fact/filter_irchanges, sofact-catalog.gen.jsoncorrectly wasn't touched. No.github/workflows/*.mdchanged without its matching.lock.yml.tests/safe-outputs/smoke sources remain lock-free, consistent with the lane model. - Breaking front-matter changes: the
update-pr→ focused PR tools split and the abbreviated-name rename are both covered by new codemods0009_split_update_pr,0010_pull_request_tool_names, and0011_explicit_pr_policy(registered incodemods/mod.rs), including idempotency/version-gating tests. - Safe-output identifier typing: new PR tools consistently use
src/secure.rsnewtypes where expected —PrLabelNamefor label fields (add-pull-request-labels,remove-pull-request-labels,replace-pull-request-label), andRelativeSafePath/CommitSha/StrictRelativePathinpush-to-pull-request-branch. No raw-Stringidentifier fields found where a newtype was warranted. - Generated shell: the new
PREPARE_PR_PUSH_SOURCEscript inagentic_pipeline.rsis correctly registered viashell_script!with declaredbindings/externals, and theSYSTEM_ACCESSTOKENcredential goes through.with_env(..., EnvValue::secret(...))rather than a binding. - Public IR/graph contracts: no
PipelineSummary/GraphSummaryfield renames; new dependency wiring (e.g.budgetGroupsinresolved_execution_config_json) doesn't touch graph cycle detection. - Docs sync:
AGENTS.md'ssafe_outputs/module tree is updated for every new/renamed file (abandon_pull_request.rs,pr_labels.rs,pr_patch/, etc.), anddocs/safe-outputs.mddocuments the new tools, shared budget groups, and label policies in depth. - Binary patch decompression (
pr_patch/parse.rs) boundsflate2output with.take(MAX_SOURCE_BYTES + 1)beforeread_to_end, avoiding a zip-bomb amplification path.
Given the PR description's extensive prior review-resolution history, this pass found nothing new to add on top of the already-resolved threads.
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 96.4 AIC · ⌖ 3.15 AIC · ⊞ 12.8K
Comment /review to run again
There was a problem hiding this comment.
Security review — no merge-blocking findings
Reviewed this PR's diff against main for erosion of the Agent/Detection/SafeOutputs trust boundary: token projection (ado_bundle.rs-adjacent flows), path/ref validation on the new push-to-pull-request-branch + shared pr_patch module, repository allow-listing in pr_common.rs::resolve_pr_policy_target, and the new AW_PR_TRIGGERING_IDENTITY propagation from the Agent/Setup job into SafeOutputs.
Findings: none merge-blocking. The diff is security-neutral-to-positive:
push-to-pull-request-branch'sprepare-pr-pushstep only ever receives a read token (SC_READ_TOKENorSystem.AccessToken), asserted by its own compiler test (neverSC_WRITE_TOKEN) — the write-capable token projection boundary is preserved.- The new
src/safe_outputs/pr_patch/patch parser validates every path throughRelativeSafePath::parse, rejects control characters and literal backslashes, bounds binary-frame/compressed sizes againstMAX_SOURCE_BYTES, and explicitly rejects newly-introduced symlinks (mode120000) in both the push and create-PR code paths. secure.rsadds a new validatedPrLabelNamenewtype (routed throughreject_pipeline_injection) rather than loosening an existing one.pr_http.rsadds a bounded-response wrapper (8 MiB cap, continuation-token rejection, explicit timeouts) used uniformly by the new PR mutation paths — this is new defense-in-depth, not a removal.resolve_pr_policy_target/resolve_pr_targetenforceallowed-repositoriesmembership and cross-check temporary-ID-resolved repositories against the caller's requested selector before any mutation.- The new
AW_PR_TRIGGERING_IDENTITYvalue is sourced only from trusted ADO predefined variables (Build.Repository.*,System.CollectionUri) in Setup/native builds or from the compiler-owned synthetic-PR step, validated with a strict GUID/control-character/pipeline-injection regex on the TypeScript side (parseTriggeringPrIdentity), and is never derived from agent-authored content. - A new executor-e2e test (
pr-boundary/ado-rest tests) explicitly asserts a fake access token never leaks into the executor's report output.
No added network-allowlist domains, no sanitize.rs/validate.rs weakening (neither file is touched), and no new raw-String identifier fields replacing a secure.rs newtype were found. I did not flag anything already covered in the four existing review threads from github-actions[bot].
This review is scoped to security regressions only; correctness/test-coverage questions are owned by the other reviewers.
🔒 Security review by PR Security Reviewer · auto · 136.3 AIC · ⌖ 1.85 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
TypeScript Code Quality Review — scripts/ado-script/
Reviewed the diff scoped to scripts/ado-script/src/** (tests excluded from scoring per contract). The ts-critic sub-agent ran in parallel and returned successfully; its findings were all low-severity maintainability nits in test-harness code (executor-e2e/, compiler-smoke-e2e/), triaged below.
Production bundle code (approval-summary/, exec-context-pr-synth/, shared/ado-remote.ts) is solid:
- No unhandled promise rejections or forEach-with-async patterns found.
- External data (NDJSON proposals, env vars, REST responses) is consistently shape-validated before use —
parseTriggeringPrIdentity,parsePrPolicies,positivePrIdall reject malformed input rather than trusting it. as Foocasts are limited to already-validatedRecord<string, unknown>narrowing, not blind trust of external shapes.- No tokens/secrets reach log lines or thrown
Errormessages. - The one real
catch { return undefined }(inreadTriggeringPrIdentity) is a safe fail-closed fallback on malformed trusted JSON, not an error swallow.
Test-harness code (executor-e2e/, compiler-smoke-e2e/) is new E2E infrastructure, not shipped in ado-script.zip. It's unusually defensive for test code (exact-match REST response validation, conditional ref deletion with lease semantics, bounded retry/cancellation). ts-critic's findings here were all low-severity:
- A hardcoded branch-prefix literal in
verifyBoundaryPushinstead of reusingCANDIDATE_BRANCH_PREFIX— posted inline (the one finding I kept, since it's a real maintainability trap on an ownership-proving function). - A
lane definition 0sentinel-collision in a log message, a reviewer-value guard relying on an implicit upstream check, and a missing test branch for one artifact-verification failure path — all triaged as DROP: cosmetic, test-only, or redundant with existing coverage, not worth a review comment budget slot.
No merge-blocking defects found.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 262.9 AIC · ⌖ 3.27 AIC · ⊞ 11.7K
Comment /review to run again
Comments that could not be inline-anchored
scripts/ado-script/src/compiler-smoke-e2e/ado-rest.ts:2190
verifyBoundaryPush hardcodes the literal "refs/heads/ado-aw-smoke-candidate/" instead of reusing CANDIDATE_BRANCH_PREFIX from ./config.js, which every other owned-ref check in this file (and git.ts) derives from.
<details><summary>💡 Why this matters</summary>
If CANDIDATE_BRANCH_PREFIX is ever renamed, this ownership guard silently stops matching real candidate refs (or starts matching the wrong ones) while every sibling check (parseCandidateRef, createBoundaryTarget, `listCa…
There was a problem hiding this comment.
Test Quality Sentinel — no blocking findings
Reviewed the test diffs in this PR (new abandon_pull_request.rs, pr_migration.rs, pr_policy.rs, pr_patch.rs/parse.rs, codemods 0009-0011, and the exec-context-pr-synth / ado-remote.ts identity plumbing) against the production changes they cover.
This diff is unusually well-tested for its size:
- Every new module ships a dedicated #[cfg(test)]/tests suite that exercises error paths explicitly (e.g. rejects_title_prefix_before_patch, rejects_disallowed_repository_before_network, handles_already_abandoned_and_rejects_completed, fixed_target_rejects_mismatched_request_id in abandon_pull_request.rs).
- Migration/codemod logic (pr_migration.rs, pr_policy.rs, codemods 0009-0011) is tested for idempotency (a second migrate call returns false and leaves state unchanged) and for conflicting/ambiguous states that must fail atomically without rewriting source files — directly protecting against the riskiest bug class in a migration tool: partial rewrites.
- One assertion-shape change that looked suspicious at first glance (Condition::And(...) to Condition::Succeeded in src/compile/extensions/ado_script.rs tests) is a legitimate simplification tied to the matching production condition change, not a weakened regression check, and a new AW_PR_TRIGGERING_IDENTITY assertion was added alongside it.
- nativeTriggeringPrIdentity/parseTriggeringPrIdentity in ado-remote.ts lack a dedicated direct unit test for every branch, but are exercised end-to-end through approval-summary and exec-context-pr-synth consumer tests covering both the synthetic (ADO_AW_TRIGGERING_PR_IDENTITY) and native (ADO_AW_TRIGGER_*) paths, including malformed and foreign-org rejection — acceptable indirect coverage given the thin, well-typed surface of that parser.
I did not find an untested new behavior, a weakened or deleted assertion, or a reintroduced tests/safe-outputs/*.lock.yml / per-case ADO definition in this diff that would warrant a blocking comment. No inline comments posted.
🧪 Test quality analysis by Test Quality Sentinel · auto · 184.3 AIC · ⌖ 1.73 AIC · ⊞ 11.3K
Comment /review to run again
There was a problem hiding this comment.
Rust review — d9b25ef0
Reviewed the Rust-specific diff: src/safe_outputs/abandon_pull_request.rs (new), src/safe_outputs/pr_patch/parse.rs (new native patch parser), src/safe_outputs/pr_mutations.rs (new, shared PR mutation ops: set-auto-complete, add-reviewers, add-labels, update-description), plus a lighter pass over pr_common.rs/pr_http.rs for the shared target-resolution and HTTP helpers these depend on.
Overall this is careful, well-tested code: every fallible HTTP path returns a structured ExecutionResult rather than panicking, checked_add/checked_sub guard the patch-parsing arithmetic throughout pr_patch/parse.rs, reviewer-identity resolution re-checks membership after a POST rather than trusting the write blindly, and the AbandonPullRequestTarget serde visitor correctly rejects zero/negative IDs on every input variant (u64, i64, string).
Two inline findings, both low/medium and non-blocking:
- An
abandon_prtransport failure loses the PR-ID context that every sibling error path in the same function preserves (inconsistency, not a crash). - The VSSPS endpoint derivation in
pr_mutations.rsis a literal substring match rather than a structural URL check — not reachable today given howorganization_urlis constructed, but fragile for future callers.
rust-critic ran in the background and returned 5 candidate findings; I incorporated its two highest-value ones above (the abandon_pr context loss, adjudicated as HARDEN with added detail, and the VSSPS substring fragility). Its other three (an unwrap_or_else swallowing the original read error, a theoretical raw.len() - 1 underflow on a single-byte hunk line) were triaged DROP: the unwrap_or_else pattern is intentional and repeated 20× across this module family as the established error-formatting convention, and the raw.len() - 1 lines are only reached after lines are confirmed non-empty via the preceding split_inclusive(b'\n') producing at least a \n-terminated byte, so raw.len() >= 1 always holds there.
Scope not covered: the Rust diff also touches create_pull_request.rs, push_to_pull_request_branch.rs, pr_patch.rs/pr_patch/tests.rs, pr_labels.rs, submit_pr_review.rs, update_pull_request.rs, and several codemods — these were out of this run's 3-file bounded scope and were not inspected.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 340.2 AIC · ⌖ 1.97 AIC · ⊞ 12.1K
Comment /review to run again
| "assembled abandonment comment exceeds {MAX_COMMENT_LEN} UTF-16 units" | ||
| ))); | ||
| } | ||
| if let Err(result) = abandon_pr(&client, &pr_url, pull_request_id, token, ctx).await? { |
There was a problem hiding this comment.
A transport failure in abandon_pr (e.g. network timeout, DNS failure) propagates via the outer ? as a generic anyhow::Error, losing the pull_request_id/repository context that every sibling failure path in this function preserves through ExecutionResult.
💡 Why this matters
Every other failure branch in execute_impl — fetch, label, filter, and the comment-post error paths just below — returns Ok(ExecutionResult::failure(...)) or abandoned_comment_warning(...) carrying pull_request_id/repository in structured data. When abandon_pr's HTTP send itself fails (not just a non-2xx response), the ? instead surfaces as a bare anyhow::Error to the Stage 3 caller, which wraps it as "Failed to execute entry: {e}" with no PR ID — audit tooling and operators then can't tell which PR abandonment attempt failed from transport-level errors, unlike every adjacent branch here.
Consider mapping the transport error the same way fetch_pr/post_comment do internally, but surfacing it as an ExecutionResult::failure with the pull_request_id embedded, e.g.:
let abandon_result = abandon_pr(&client, &pr_url, pull_request_id, token, ctx)
.await
.map_err(|error| anyhow::anyhow!("Failed to abandon PR #{pull_request_id}: {error}"))?;
if let Err(result) = abandon_result {
return Ok(result);
}This at least keeps the PR ID in the error text reaching the operator/audit log.
| // Derive VSSPS base URL once, before the loop. | ||
| let trimmed_org = operation_ctx.target.organization_url.trim_end_matches('/'); | ||
| let vssps_base = trimmed_org.replace("://dev.azure.com/", "://vssps.dev.azure.com/"); | ||
| if vssps_base == trimmed_org { |
There was a problem hiding this comment.
VSSPS base derivation only matches the exact substring "://dev.azure.com/", so a valid org URL without a trailing path segment (just https://dev.azure.com) or any case variant silently fails add-reviewers with "Cannot derive VSSPS identity endpoint" even though the org is fully supported.
💡 Why this matters
organization_url is stored as e.g. "https://dev.azure.com/org" per the test fixtures in result.rs/pr_common.rs, so trimmed_org.replace("://dev.azure.com/", ...) works for the common shape. But the match is a literal string substitution, not a URL-structural check: a value without the trailing slash before the org segment, or with mixed case (Dev.Azure.com), produces vssps_base == trimmed_org and trips the "Legacy *.visualstudio.com organizations are not currently supported" error message — which is misleading for a dev.azure.com URL that simply didn't match the exact substring.
A more robust approach parses the authority and swaps the host rather than doing substring replacement, e.g.:
let vssps_base = if let Some(rest) = trimmed_org.strip_prefix("https://dev.azure.com") {
format!("https://vssps.dev.azure.com{rest}")
} else { ... };This isn't reachable with current construction paths (the URL is always /-suffixed with the org), so it's low severity today, but it's an easy correctness trap for future callers who build organization_url differently.
Integrate the legacy discovery URL fix from a7d6582 while retaining strict HTTPS collection/PR identity validation. Add regressions separating discovery normalization from trusted identity and align the imported HTTP test with the stricter branch contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da8711de-7251-47cd-a006-6e4ece913b49
Prompt evaluationNote This is an advisory static review. Only Prompt Contracts is merge-blocking. Suites selected: Diff summary: Both changed prompts add the same new guidance block: how to choose among Azure DevOps PR safe-output tools ( None of the 6 synthetic cases in the
Potential regressionsNone found. The added PR-tool-selection guidance is net-additive (new deterministic intent boundaries for review vs. comment vs. vote/reset vs. push tools) and was not observed to remove, weaken, or contradict any existing instruction relevant to the evaluated cases. Per-case scores
Evidence for the recurring
These gaps are pre-existing and orthogonal to this PR's change (new PR comment/review tool-selection guidance); recommend tracking separately rather than blocking this PR.
|
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.
Review resolution highlights (October 6, 2026)
Current branch tip:
c66b8cdb, a normal merge ofmainata7d6582ainto the independently reviewed feature tip
4b439842. No history rewriteor PR-to-main merge was performed.
The single conflict was in
scripts/ado-script/src/shared/ado-remote.ts.Resolution retains main's legacy discovery-URL normalization and exported
segment decoder while preserving this PR's strict HTTPS/collection validation
and triggering-PR identity checks. Added cross-consumer regressions prevent
discovery URLs being mistaken for collection identity. The auto-merged HTTP
test now asserts the branch's deliberate HTTPS-only contract.
Integration validation: 1,464 TypeScript tests passed, typecheck passed,
and all four affected bundles rebuilt. GitHub reports MERGEABLE, and fresh
integration checks on
c66b8cdbhave completed successfully: Rust build/tests,Linux TypeScript/build/drift, Windows executor harness, prompt and SafeOutputs
contracts, ADO script E2E 645955, and executor E2E 645956. The current PR
check rollup has no unfinished or failed checks. Skipped reviewer jobs are not
counted as new independent review coverage.
The preceding
d9b25ef0added the requested abandonment result assertions;4b439842addresses the documentation, test, harness and review-deliveryfollow-ups below. Their review/live evidence remains pinned to those revisions.
The four previously open GitHub review threads have evidence-backed replies
and are resolved, based on current code and regression tests rather than their
outdated-diff markers:
abandon_pull_requestbeforeadd_build_tag; fixed inba005bd2. Replyrejects_disallowed_repository_before_networkexplicitly denies the default/self destination withallowed-repositories: ["other"]and asserts zero HTTP requests. Replyd9b25ef0adds the requested explicitalready_abandoned/abandoned/comment-result assertions. ReplyThe later holistic findings are also addressed: reviewer assignment skips
observed existing membership without writing (
749d2fa2, with the explicitlyaccepted concurrent required-status race); native Git space-header delimiters
are handled; and staged executable-mode intent is preserved while
unrepresentable REST changes fail (
94b5a633).Revalidated on
d9b25ef0:cargo test --quiet --bin ado-aw abandon_pull_request— 19 passed.The prior 63/63 executor run 645720 and successful real-agent smoke
645885 remain evidence for
2cda595f, not newly claimed runs on the test-onlytip. Current-tip validation is tracked separately;
closing old threads is not a maintainer approval or a claim that no new
findings can be raised.
Fresh review outcomes on
d9b25ef0Four reviewers completed and their summary reviews are confirmed published:
AGENTS.mdarchitecture map, and no standalonebudget-groupsreference/example.update-pull-request-comment. The claim of no behavioral coverage is too broad: shared ownership tests and successful live update/manual-edit-refusal scenarios already exist.fetchinscenarios/pr-comments.tshas no timeout.Review publication also has a confirmed workflow gap: inline comment proposals
were skipped under the centralized
workflow_dispatchcommand because thehandler's
target: triggeringdid not recognize a PR context. Summary submissionfound PR #2222 and succeeded, but direct review-comment API reads confirm zero
inline comments on those four reviews. Consequently, zero open threads must
not be interpreted as zero new findings.
Resolutions in
4b439842pr_patchparser/tests toAGENTS.md.budget-groups, itsmax/toolsschema, a minimal example, zero/failed/no-op counting, member restrictions and matching approval/staged lanes.execute_safe_outputtests for successful owned updates, 100/101-byte key boundary, ID validation, wrong actor/pipeline/key, missing ownership, manual edits, replies and mismatched IDs; denied cases assert no writes.Local validation: 3,913 Rust tests passed (2 ignored), 1,452 TypeScript
tests passed, strict Clippy/typecheck, harness build and strict five-workflow
compilation passed. Eleven prompt/lock contract tests verify the actual
generated activation guards, fixed targets and read-only agent permissions.
A one-off test of the pinned upstream inline handler reproduced the old dispatch
skip and verified that fixed PR 2222 overrides an agent-supplied PR 9999; invalid
target zero performs no API read or buffering.
All five repaired reviewer runs completed successfully on exact
4b439842,and one published COMMENT review per run has been verified through REST:
The Rust safe-output log confirms
target: 2222, both handlers pinned to4b439842, two buffered findings, and successful review submission. This isactual inline-publication evidence, not just a green setup/evaluation job.
Its free-text heading mistakenly names
d9b25ef0; checkout, prefetch, outputconfiguration and GitHub's review
commit_idall identify4b439842.Review limits and new advisory notes remain explicit:
lighter target/HTTP pass. It explicitly did not cover every Rust module; this
is not an exhaustive all-files sign-off.
uncertain abandonment writes; the suggested snippet does not itself restore
structured result data, and adjacent paths do not universally preserve data.
ADO organization URL, but mixed-case hosts can reach the raw URL path through
CLI/environment overrides. Structural host handling merits a targeted check.
follow-up, not a demonstrated production failure.
The two Rust threads and the TypeScript summary note have not been
auto-resolved or silently changed. All verdicts are COMMENT, not maintainer
approval. CI blocker resolved: the earlier
ACTION_REQUIREDchecks werecaused by branch conflicts, not human approval gates. Merge commit
c66b8cdbclears that conflict state; GitHub reports MERGEABLE and fresh integration
checks have passed. This does not automatically resolve the advisory notes or
constitute maintainer approval.
Implemented capabilities
appear to work while being ignored.
label/title filters across PR mutations. Triggering means the complete trusted
collection/project/repository/PR identity, not just a numeric ID.
without granting extra authority. Imports preserve consumer precedence,
custom-job ownership and source/cache bytes. Old runtime names are rejected.
commentreviews preserve existing votes; explicitresetclearsthe authenticated actor's vote. This intentional new behavior has no legacy
toggle; prompts are warned about, not rewritten.
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, exactone-field mutation, persisted read-back and repeat no-op.
update-pull-request-comment: verified same-pipeline/same-actor root-commentupdates with immutable ownership and a checked content hash. Unmarked history,
manual edits and conversations with replies are protected.
and close older eligible threads only after replacement succeeds. No deletion,
automatic vote reset or review dismissal.
ADO diff metadata, not local file presence, determines anchors.
max-commentsdefaults to 0; the whole batch is preflighted, comments precedevotes, 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-indexMCP capture, only the agent delta, patch/path/file/binary bounds, source-ref
allowlists, optimistic
oldObjectIdguard 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.
compiler targets, approval/staged constraints and migration documentation.
Validation infrastructure repairs
cleanup reporting and diagnostic failure-issue suppression.
terminal-state cleanup proof.
explicit skip evidence, while missing records alone are not.
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.
now invokes the compiler already staged at
/tmp/awf-tools/ado-aw.The corrected full pipeline passed on the published head.
Holistic review follow-up (October 5, 2026)
Production fix:
749d2fa2; production-validation candidate2cda595fadditionally corrects the optional-reviewer fixture. The later review tip
d9b25ef0changes only test assertions. The three later findings are addressed:addition and make existing identities no-write no-ops. Missing identities use
an ID-only collection POST followed by membership read-back. The focused tool
and creation's configured-reviewer follow-up share this behavior. Results
distinguish
added,already_present, andfailed; creation retains its PRidentity and emits a warning for unconfirmed reviewer follow-ups.
94b5a633separates Git's terminal tab delimiter fromold/new header filenames. Real Git-generated spaced-path and rename-with-edit
fixtures cover MCP capture and Stage 3 without weakening path validation.
94b5a633preserves staged mode intent in private-indexcapture and rejects modes that content-only REST cannot represent before
remote writes. Existing executable-content edits remain supported and are
checked using tree modes, not just blob IDs. No new publication transport.
Explicitly accepted residual risk: membership read and addition are not
atomic. If another actor adds the same reviewer and marks them required between
those requests, the collection POST can clear required status. The October 5
decision accepts this narrow race rather than disabling new assignments.
There is no stale-state restoration or blind write retry.
The API investigation did not prove atomic preservation: ID-only collection
POST preserved the vote but cleared required status; individual PUT, including
If-None-Match: *, reset both and returned HTTP 200. Those failed capabilityhypotheses remain recorded and locally tested, but are not registered as routine
production-success scenarios.
Local integration: 3,907 Rust tests passed (2 ignored); 1,450 TypeScript
tests passed; strict Clippy/typecheck and both harness builds passed.
Mode/path run 645444 passed all 16 required cases, with no remaining refs.
Required executor 645706 finished 62/63 passing, no skips, including both
required-reviewer vote cases and configured creation. The only failure was
fixture setup: ADO forbids a PR creator declining their own review.
2cda595fuses supported optional/flagged creator state and adds a setupregression; all state-preservation assertions remain. Already-declined reviewer
preservation is covered deterministically, not claimed as live coverage.
The corrected run 645720 passed all 63 required cases, with no skips, on
2cda595f26a9e617b95ff8e5b7c10f5771125652. Its downloaded result artifactconfirms the exact selected cases and candidate revision. No run-owned refs
remain from either 645706 or 645720. This includes existing negative/positive
required reviewers, optional/flagged membership, new assignments and configured
creation/follow-up no-ops, as well as the full path/mode and earlier PR matrix.
Actions recovered (October 6, 2026): all seven outage-affected workflows
passed on attempt 2 at unchanged
2cda595f: Rust, Linux TypeScript/build/drift,prompt contracts, prompt evaluation, title lint, PR prefetch and the TypeScript
reviewer. Annotations confirmed that the original cancelled jobs never acquired
hosted runners. Only those jobs and their dependents were retried; previously
successful checks were left untouched. Those checks passed on
2cda595f;fresh checks on the later test-only review tip are tracked separately.
Actual-agent authentication recovered after token rotation: following the
user's token refresh, smoke 645885 passed on unchanged
2cda595f:canary 645887 and actual-agent PR push 645886 both succeeded.
Agent, Detection and SafeOutputs succeeded; all three proposed outputs were
confirmed. Persisted Git read-back verifies commit
83d5c20dc8d804fdbadfae2cc07deb27ddfc2c3b, its exact prepared parent03cdf57da8a6ac6601843621419de848382d5d09, and exact proof-file content.The expected build tag and safe-output artifact exist. Disposable PR 43841
is abandoned and all three run-owned source/target refs are absent.
The authentication validation hold is closed; no source changes were needed.
Historical authentication failures: smoke 645707 failed:
children 645714/645715 stopped before producing proposals
with GitHub 401 Bad credentials. This is not proven to be caused by the
Actions incident. No credentials/permissions were changed and no agentic smoke
rerun was queued as part of Actions recovery.
The explicitly requested retry 645873 on unchanged
2cda595falso failedon October 6. Canary 645875 failed and PR-push 645874 was canceled;
both agent logs show "Authentication token found but could not be validated"
and GitHub 401 Bad credentials before any proposals were produced.
Credentials were not changed for that attempt. These failed runs are not counted
as passing code-validation evidence; the post-rotation run above supplies it.
Cleanup for the retry is complete: disposable PR 43840 is confirmed
abandoned, and all three refs in its source/target namespaces are absent.
It introduced no additional retained refs. Existing historical retained refs
remain separately tracked.
The smoke's retained PR 43686 is now confirmed abandoned after its source
child became terminal. Its two unchanged refs remain: expected-SHA deletion
returned
forcePushRequired. The exact refs/SHAs are recorded separately fromthe 24 historical refs below; none are represented as successful cleanup.
Review remediation (October 1, 2026)
All ten divided-review findings now have production-path fixes and regression
coverage. That milestone was
873a05d4, following the shared patch milestonec5dac7b5and filtered-preimage preservation fixc9f9881e.CI and required live evidence for that milestone passed. Historical failed-run
ref cleanup remains permission-blocked, as recorded below.
3172f1f6: normalize legacytruelike null at the source-migration boundary; canonical runtime schemas remain strict.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.c5dac7b5: one application-glob selection; omit/report whole copy or rename operations and reject retained dependencies. Commit preimages handle rename swaps/chains correctly.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.d890eb8a: separate target namespace, corroborated legacy ownership, all-definition source-child terminal proof, PR-before-ref cleanup and SHA leases. Ambiguity retains resources.6b07d65b: only the auto-complete scenario accepts confirmed completion; uncertain abandonment gets one read-back, never blind replay.6b07d65b: independently resolve every selected local/cross-org reviewer prerequisite before any setup writes.b915f392: shared 30-second request / 8 MiB streamed-response bounds across the ADO PR family, including policy reads; incomplete metadata fails closed.2f97a8a4: port valuable assertions to production paths and remove retired request/result and inline-builder substitutes.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-sizedefaults 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-versionclaim. 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.
873a05d4correctsall 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
c5dac7b5passed and cleaned both ref namespaces.The final-head repeat 644659 on
873a05d4also 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 exactproof-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 confirmedabandoned. The exact retained refs/SHAs were recorded; conditional deletion
returned
forcePushRequiredfor the local identity. No permissions werechanged 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.
643035ond4f7f948: 20 passed, no failures/skips643206on7957fa6a: passed; canary643210, automatic643211, expected rejection643212; exact PR state, tags/artifacts, reviewed skip and five-ref cleanup verified643209onc8645de8: 4 passed, no skips; these establish platform prerequisites, not substitute executor coverage643219on34c307d5: 10 passed, no skips643228ona9be3c30: 6 passed, no skips, including forbidden-batch no-write and repeat-publication no-op643257on2726a751: 10 passed, no skips. The earlier run exposed immutable thread properties and deleted-fileitem.path=null; both corrected and re-proven643919onef74722c: 6 passed, no skips; exact source update, stale head, forbidden branch, protected file, hash mismatch and empty patch643927onc4aa0506: passed. Push child643932and canary643933succeeded; 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. Earlier643920exposed the PATH bug and was cleaned up; it is not counted as a passc4aa0506: Rust, Linux TypeScript/build/bundle/drift, Windows process harness and reviewer prefetch passed. The prefetch fix was also verified on86e552b4with a complete 28,672-line filtered diffDiagnostic 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.