feat(safeoutputs): add GitHub issue outputs - #1670
Conversation
|
Azure Pipelines: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
🔍 Rust PR ReviewSummary: looks good — solid promotion from debug-only to public safe output, well-structured; one correctness concern and two suggestions worth addressing. Findings🐛 Bugs / Logic Issues
|
🔍 Rust PR ReviewSummary: Solid promotion of GitHub issue outputs from debug-only to production; a few items worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
|
@copilot This PR has a failing check: Please investigate the failure and push a fix.
|
|
@copilot resolve the merge conflicts in this pull request |
fc90d6f to
3ba50d1
Compare
|
/review |
|
✅ PR Security Reviewer completed the security review. |
|
/review |
|
✅ Rust Code Quality Reviewer completed the Rust code quality review. No review performed: pre-fetched PR data files were empty (pr-meta.json = {}, pr-diff.patch = 0 lines) for PR #1670, so there were no Rust changes to analyze. |
|
/review |
|
✅ Test Quality Sentinel completed the test quality analysis. Test Quality Sentinel: pre-fetched PR diff/metadata files (pr-diff.patch, pr-meta.json, pr-review-comments.json) for PR #1670 were empty/blank when this review ran, so no test-quality review could be performed. Likely a cache-warming/prefetch step failure or timing issue in the pr-data-prefetch workflow — no findings to report. |
|
/review |
|
✅ Test Quality Sentinel completed the test quality analysis. |
There was a problem hiding this comment.
Reviewed the scripts/ado-script/ portion of this PR (mainly github-app-token/index.ts's new parsePermissions helper and the ADO_AW_DEBUG_GITHUB_TOKEN → ADO_AW_GITHUB_TOKEN env-var rename across the e2e harnesses).
The new permission-parsing code is solid: prototype-pollution guards (__proto__/prototype/constructor), a strict name regex, validated read/write levels, and matching unit tests for both the happy path and each rejection case. mintInstallationToken's optional permissions param defaults safely and is only added to the request body when non-empty. main()'s try/catch already wraps the new parsePermissions call so a bad flag fails the pipeline step cleanly rather than crashing unhandled.
One minor advisory comment posted inline about giving JSON-syntax parse errors more context. Nothing merge-blocking.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · aut00 · 37.3 AIC · ⌖ 3.85 AIC · ⊞ 10.9K
Comment /review to run again
There was a problem hiding this comment.
Compiler contract review — no drift found
Checked the areas that matter most for this PR's scope:
- Bundle drift:
scripts/ado-script/src/approval-summary/render.tsandscripts/ado-script/src/github-app-token/index.tschanged; both bundle directories map to gitignored.jsoutputs that are rebuilt by CI (npm run build), not committed — no drift to flag here. - Tool registration:
CreateGithubIssueResultandSetGithubIssueTypeResultare correctly wired intoALL_KNOWN_SAFE_OUTPUTS, the newCONFIGURED_ONLY_TOOLSregistry, andsrc/mcp.rs's gating logic (DEBUG_ONLY_TOOLS.contains(...) || CONFIGURED_ONLY_TOOLS.contains(...)). - Codemod:
0006_promote_debug_create_github_issueis registered in both themod m0006_...declaration and theCODEMODSstatic array insrc/compile/codemods/mod.rs, matches the numbering convention, and has idempotency + conflict-detection tests. - Typed identifiers: the new
temporary_id/issue_numberfields correctly use the validatedGithubTemporaryIdnewtype fromsrc/secure.rsrather than a rawString. - Docs:
docs/safe-outputs.mdhas a full "GitHub issue safe outputs" section (auth, target-repo resolution, temporary IDs, approval-gating parity requirement) and both tools are documented individually.AGENTS.md's architecture tree reflects the renamedcreate_github_issue.rs, newset_github_issue_type.rs, and the new codemod file.
No merge-blocking contract gaps found in this diff.
🏗️ Compiler contract review by Compiler Contract Reviewer · aut00 · 95.3 AIC · ⌖ 3.83 AIC · ⊞ 11.9K
Comment /review to run again
There was a problem hiding this comment.
Reviewed test coverage for the GitHub issue safe-output promotion. The new set_github_issue_type.rs and the codemod (0006_promote_debug_create_github_issue.rs) are well-tested, including idempotency, no-mutation-on-error, and a full wiremock-backed temporary-ID resolution flow. tests/compiler_tests.rs::test_compile_github_issue_app_fixture_scopes_tokens_by_stage is a strong integration test for the stage-isolated-credential claim, and its fixture (tests/fixtures/github-issue-app-agent.md) genuinely exercises the scenario it asserts on.
However, github_safe_outputs_auth() in src/compile/types.rs has several bail! branches with no matching tests — specifically the "github-app.permissions not supported for issue outputs" rejection, the write-permission rejection for an inherited engine.github-app-token, and both "github-api-url applies only to PAT auth" conflict checks (explicit github-app and inherited engine.github-app-token). These are exactly the kind of security-relevant auth-resolution branches this PR's description is built around, so leaving them untested is a real regression risk. resolve_target_repo's GitHub-Enterprise-without-api-url branch in create_github_issue.rs is similarly untested (only github/tfsgit providers are covered). Finally, create_github_issue.rs's executor tests are all rejection-path only — no test exercises the actual HTTP success path or the "filed but failed to register temporary_id" partial-failure branch, unlike its sibling set_github_issue_type.rs which does mock the real request/response cycle.
None of these are merge-blocking on their own (the existing coverage is otherwise solid), but I'd like to see at least the auth-resolution branches covered before merge given their security relevance.
🧪 Test quality analysis by Test Quality Sentinel · aut00 · 238.4 AIC · ⌖ 4.35 AIC · ⊞ 10.7K
Comment /review to run again
|
/review |
|
✅ Compiler Contract Reviewer completed the compiler contract review. Compiler-contract review of PR #1670 complete. Checked: bundle drift (approval-summary.js, github-app-token.js are release-built artifacts, not committed to git — confirmed via .gitignore and .github/workflows/ado-script.yml; no drift possible), codegen drift (types.gen.ts/fact-catalog.gen.json correctly untouched since no Fact/gate IR changed), compiled workflow drift (no .github/workflows/*.md changed), release-owned fixtures (smoke-failure-reporter.lock.yml untouched, correct), front-matter grammar (new github-token/github-api-url/github-app fields are optional with proper validation), safe-output tools (create-github-issue and set-github-issue-type properly registered in catalog.rs, docs/safe-outputs.md, custom_tools reserved keys), codemod 0006 correctly registered in CODEMODS registry, AGENTS.md tree fully updated with both new safe-output files and the codemod. No merge-blocking contract violations found — this is a well-executed, thoroughly cross-referenced change. No inline comments needed. |
There was a problem hiding this comment.
TypeScript review — scripts/ado-script/
Reviewed the scripts/ado-script/ changes: parsePermissions/mintInstallationToken additions in github-app-token/index.ts, the approval-summary TOOL_SPECS rename/addition, and the ADO_AW_DEBUG_GITHUB_TOKEN → ADO_AW_GITHUB_TOKEN env rename in the executor/trigger e2e harnesses.
No defects found:
parsePermissionsvalidates permission names against an allowlist regex and explicitly rejects__proto__/prototype/constructorbefore use (the map is also built withObject.create(null), so prototype pollution isn't reachable regardless).JSON.parseand all new network/error paths are covered bymain()'s existing outer try/catch, which logserr.messageand returns a non-zero exit rather than throwing unhandled.- No secrets are logged — the new
logInfocall only reports the owner, installation id, and repo count, not permissions or the token. - New behavior (permission parsing, minting with permissions) has matching unit tests in
github-app-token/__tests__/index.test.ts.
No inline comments needed.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · aut00 · 54.1 AIC · ⌖ 6.33 AIC · ⊞ 10.9K
Comment /review to run again
There was a problem hiding this comment.
Security review — no findings
Reviewed the diff for token-scope and injection regressions in the new create-github-issue / set-github-issue-type safe outputs and the GitHub App permission-scoping work.
- Token isolation is preserved:
ADO_AW_GITHUB_TOKEN/ minted App tokens are wired only into the Stage 3 SafeOutputs job env (generate_executor_ado_env), never into the Agent or Detection stage.safe-outputs.github-tokenexplicitly rejects referencing the Agent-visibleGITHUB_TOKENor Copilot engine token variable, closing a plausible confusion vector. - App token minting is now permission-scoped (
--permissions-json) rather than inheriting full installation scope, and repository scoping (scope_github_app_to_issue_targets) enforces owner match and rejects mixing implicit/explicit targets, preventing the token from being minted broader than the configured target repo(s). - GHES
api-urlis validated (https-only, no query/fragment) in both the GitHub App config path and the newgithub-api-urlfield, andresolve_target_reporequires an explicitgithub-api-url/Appapi-urlfor enterprise sources rather than silently falling back to api.github.com. - Agent-supplied content (
title,body,labels,assignees,issue_type) passes throughreject_pipeline_injectionandsanitize_text/neutralize_pipeline_commandsbefore being embedded in requests, logs, or failure messages; label allowlisting is default-deny with an explicit"*"opt-out. - HTTP calls use
reqwestwith.bearer_auth(token)(never string-interpolated into a header or URL) andowner/repopath segments are percent-encoded before building the request URL. - MCP-layer gating (
CONFIGURED_ONLY_TOOLS) keepscreate-github-issue/set-github-issue-typeunreachable unless the operator declares thesafe-outputskey, consistent with the existingDEBUG_ONLY_TOOLSpattern, and is covered by tests for both the default-stripped and explicitly-enabled cases.
No exploitable regression found in this diff relative to main. Nice work isolating the PAT/App credential to Stage 3 and adding the permission-subset minting.
🔒 Security review by PR Security Reviewer · aut00 · 64.2 AIC · ⌖ 3.89 AIC · ⊞ 11.2K
Comment /review to run again
There was a problem hiding this comment.
Reviewed the Rust portions of the diff (src/compile/agentic_pipeline.rs, src/compile/codemods/0006_promote_debug_create_github_issue.rs, src/compile/common.rs, src/compile/custom_tools.rs, src/compile/extensions/ado_script.rs, src/compile/imports/merge.rs, src/compile/mod.rs, src/compile/types.rs) — note the diff was truncated at 3000 lines so types.rs and later files (src/execute.rs, src/mcp.rs, src/safe_outputs/*, src/secure.rs) were not fully visible.
One defect found: a silent permission-key collision after -→_ normalization in the GitHub App token mint step (inline comment). Error handling, anyhow context usage, and overall structure otherwise look solid — the new GithubSafeOutputsAuth enum, codemod, and validation paths are well-tested and free of panics on user-input paths. rust-critic sub-agent returned no output after one retry attempt per protocol, so this review reflects my own pass only.
🦀 Rust code quality review by Rust Code Quality Reviewer · aut00 · 92.6 AIC · ⌖ 5.58 AIC · ⊞ 11K
Comment /review to run again
There was a problem hiding this comment.
Solid test coverage overall for the new create-github-issue/set-github-issue-type tools and the App-token permission scoping (positive/negative cases for token vs. App auth, mixed implicit/explicit targets, empty-permissions rejection, reserved permission keys). Two gaps worth closing before merge: an untested default-allow behavior in set-github-issue-type when allowed: is empty (asymmetric with the sibling tool's default-deny for labels), and no test for the write-scoped engine App-token rejection branch — both are security-relevant gates that currently have no regression protection. Neither is merge-blocking on its own but the second in particular protects the PR's core "isolate credentials to Stage 3" invariant.
🧪 Test quality analysis by Test Quality Sentinel · aut00 · 119.6 AIC · ⌖ 4 AIC · ⊞ 10.7K
Comment /review to run again
|
/review |
There was a problem hiding this comment.
The test additions visible in the fetched diff are solid: github-app-token gains focused positive/negative unit tests for parsePermissions (valid read/write JSON, rejected permission names, rejected levels, reserved __proto__/constructor keys) and an integration-style assertion that mintInstallationToken actually sends the scoped permissions body — this directly protects the write-scope-leak concern called out in the PR description. The new codemod (0007_promote_debug_create_github_issue.rs) has good behavioral coverage: happy path, empty-debug-section cleanup, existing-auth preservation, the conflict-error branch (with a snapshot equality check confirming no partial mutation on error), and an explicit idempotency test.
No weakened or removed assertions, and no untested new behavior, in the portion of the diff I could inspect.
Scope note: the fetched pr-diff.patch is capped at 3000 lines and truncates partway through src/compile/types.rs, before it reaches several test-relevant additions mentioned in this PR (the new src/safe_outputs/set_github_issue_type.rs, and the modified tests/codemod_tests.rs, tests/compiler_tests.rs, src/safe_outputs/create_github_issue.rs tests). I could not assess test quality for that code in this pass and have no diff lines to anchor inline comments to for it — flagging so a follow-up pass (or a reviewer with the full diff) covers that portion, particularly the two review-requested security-gate tests the PR body says were added (engine.github-app-token.permissions write-rejection, set-github-issue-type.allowed default-allow).
🧪 Test quality analysis by Test Quality Sentinel · aut00 · 40.9 AIC · ⌖ 3.82 AIC · ⊞ 10.7K
Comment /review to run again
There was a problem hiding this comment.
Rust engineering review
No merge-blocking defects found in the reviewed portion.
Reviewed the Rust-relevant hunks in src/compile/agentic_pipeline.rs, src/compile/codemods/0007_promote_debug_create_github_issue.rs, src/compile/codemods/mod.rs, src/compile/common.rs, src/compile/custom_tools.rs, src/compile/extensions/ado_script.rs, src/compile/imports/merge.rs, src/compile/mod.rs, and the start of src/compile/types.rs. The pre-fetched diff is capped at 3000 lines and cuts off mid-types.rs, so src/execute.rs, src/mcp.rs, src/safe_outputs/create_github_issue.rs, src/safe_outputs/set_github_issue_type.rs, src/safe_outputs/result.rs, src/secure.rs, and the remainder of types.rs were not visible to this pass.
💡 What was checked
- Error handling: new validation paths (
validate_github_issue_outputs_config,GithubAppTokenConfig::validate_for,parse_safe_outputs_github_token/_api_url) consistently useanyhow::bail!/Contextwith actionable messages, no bareunwrap/expecton user-reachable input. - Determinism: the new
permissionsmap usesBTreeMap(notHashMap), so--permissions-jsonserialization is stable for generated YAML. - The permission-key normalization collision check (
-vs_) invalidate_foris a nice defensive addition catching a real silent-collapse bug before it reaches the mint step. - Removed
executor_ado_envfield fromStandaloneCtx/tests and its recomputation per-variant inbuild_safeoutputs_jobis consistent with the new per-job GitHub auth requirement. - Codemod (
0007_promote_debug_create_github_issue.rs) mutation is guarded (existing-key conflict bails without mutating), and idempotency is explicitly tested.
The rust-critic sub-agent invocation in this run did not receive diff content due to a tooling issue on my side, so its findings could not be incorporated; this review reflects only my own pass.
Note: given the truncated diff, a full pass over the untruncated src/safe_outputs/create_github_issue.rs, set_github_issue_type.rs, and src/mcp.rs changes (largest additions in this PR) would need a follow-up review once the full diff is available.
🦀 Rust code quality review by Rust Code Quality Reviewer · aut00 · 56.7 AIC · ⌖ 4.24 AIC · ⊞ 11K
Comment /review to run again
There was a problem hiding this comment.
Security review — no regressions found
Reviewed the visible portion of the diff (capped at 3000 lines; several newer/larger files — src/mcp.rs, src/secure.rs, src/safe_outputs/create_github_issue.rs, src/safe_outputs/set_github_issue_type.rs, src/safe_outputs/result.rs — fell outside the cap and were not inspected here).
What was reviewable looks like a genuine hardening pass rather than a weakening:
- Token isolation preserved:
generate_executor_ado_envstill routesSYSTEM_ACCESSTOKENthrough the sharedtoken_source_for()chokepoint, and the newADO_AW_GITHUB_TOKEN/ADO_AW_GITHUB_API_URLenv is only emitted for the SafeOutputs (Stage 3) job — Agent/Detection are untouched. - App-token scoping:
scope_github_app_to_issue_targetsrejects mixing implicit (current-repo) and explicittarget-repovalues across the two tools, and cross-checks the App'sowneragainst the resolved target, closing an ambiguous-scope path. - Permission-collision defect fixed: the
-→_normalization collision inGithubAppTokenConfig::validate_for(which could silently let a declaredreadbe overridden bywrite) is now rejected at compile time, with a mutation-checked test, matching what the PR description states. - Injection guards extended:
validate_github_issue_outputs_configrunsreject_pipeline_injectionovertarget-repo,title-prefix,labels,allowed-labels, andassigneesfor both new tools; API URLs are validated ashttps://, host-present, no query/fragment. safe-outputs.github-tokenguard: explicitly rejects referencingGITHUB_TOKENor the engine App-token variable, so a Stage-3-only credential can't be redirected to double as the Agent/Detection credential.
The declined target-repo newtype (documented in the PR body) is a defensible call given get_tool_config's .ok().unwrap_or_default() fail-open behavior — converting to a hard deserialization failure there would swap a loud error for a silent redirect. Compile-time + Stage-3 re-validation already cover it, and the tradeoff is explained and tested.
No exploitable findings in the reviewable portion of the diff. Not requesting changes.
🔒 Security review by PR Security Reviewer · aut00 · 52.8 AIC · ⌖ 3.91 AIC · ⊞ 11.2K
Comment /review to run again
There was a problem hiding this comment.
Compiler-contract review — no findings
Checked the areas this reviewer owns for PR #1670 (promoting create-issue → create-github-issue, adding set-github-issue-type):
- Codemod registration:
0007_promote_debug_create_github_issue.rsis correctly registered incodemods/mod.rs'sCODEMODSarray — the breaking front-matter rename (ado-aw-debug.create-issue→safe-outputs.create-github-issue) has a migration path. - Bundle drift:
scripts/ado-script/src/approval-summary/render.tsandsrc/github-app-token/index.tschanged, but their compiled.jsoutputs are.gitignored (confirmed viascripts/ado-script/.gitignoreanddocs/ado-script.md), not committed artifacts — so there is no drift to detect here, unlike a typical committed-bundle scenario. - Codegen drift: no changes touched
filter_ir.rsor theFactenum, sotypes.gen.ts/fact-catalog.gen.jsonare correctly untouched. - Front-matter grammar: new
GithubAppTokenConfig.permissions,safe-outputs.github-token/github-api-url/github-appare all#[serde(default...)]/Option, non-breaking for existing pipelines. - New safe-output tools:
create-github-issue/set-github-issue-typeare registered inALL_KNOWN_SAFE_OUTPUTS, flow through the genericall_safe_output_tool_names()tool-config plumbing (the old special-caseddebug_create_issue/ado_aw_debug.create_issueinsertion logic insrc/main.rswas correctly deleted rather than left as dead code), and are documented indocs/safe-outputs.md,docs/front-matter.md,docs/ado-script.md,AGENTS.md, andsrc/inspect/catalog.rs. - Identifiers:
target-repostays aStringinCreateGithubIssueConfig/SetGithubIssueTypeConfig, but it is validated at compile time viavalidate_target_repo(owner/repo regex) and again at Stage 3 — not an unvalidated raw string reaching an untrusted boundary. - New
GithubTemporaryIdinsrc/secure.rscorrectly follows thevalidated_string!newtype convention for the#aw_...temporary-ID format.
No bundle/codegen/lock drift, no unregistered extensions, no missing documentation found. Nice, thorough PR description covering the deliberate allowed/allowed-labels asymmetry and the known executor-e2e gap.
🏗️ Compiler contract review by Compiler Contract Reviewer · aut00 · 122 AIC · ⌖ 3.93 AIC · ⊞ 12K
Comment /review to run again
…defer the infra lane Follows #1670, which promotes GitHub issue filing from \�do-aw-debug\ to the public \create-github-issue\ safe output and renames the secret. Same token value, same scope, same single case - the debug lane is a credential boundary, so a rename does not move the boundary. Also records that only \�gentic\ and \debug\ need registering at cutover. \loadCases\ resolves a definition id per lane in play for the mode being run, and no case targets \infra\ yet, so registering it now would create a credentialed definition nothing queues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
The debug lane isolated ADO_AW_GITHUB_TOKEN for a single case, back when GitHub issue filing was a debug-only capability behind \�do-aw-debug:\. #1670 promotes it to the public create-github-issue safe output, so a lane per credential would fragment as more cases adopt it - each new issue-filing case would either need its own lane or quietly widen this one, which is the failure mode the split existed to prevent. The isolation that mattered is now enforced where it cannot drift. The compiler projects the token into the Stage 3 executor only, and assertAdoTokenIsolation fails the run on freshly compiled YAML if it appears in Agent or Detection. That prevents the leak rather than bounding its blast radius, which is all a separate definition bought. infra stays: it holds no credentials at all, so it remains a real boundary for the AWF and ado-proxy smokes. The manifest test that pinned the debug split now asserts infra carries no cases, so the boundary cannot be dissolved by quietly provisioning a secret onto it. Cutover now needs ONE lane definition registered, not three. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
…thub-issue-safe-outputs
|
/review |
|
✅ Test Quality Sentinel completed the test quality analysis. |
There was a problem hiding this comment.
Compiler contract review
Good contract hygiene overall: the breaking ado-aw-debug.create-issue -> safe-outputs.create-github-issue rename is covered by a properly-registered codemod (0007_promote_debug_create_github_issue), and the new temporary_id field uses a validated GithubTemporaryId newtype from src/secure.rs rather than a raw String.
Bundle drift (blocking): two scripts/ado-script/src/** sources change runtime-relevant behavior but their committed bundles are absent from this PR's file list:
src/github-app-token/index.tsaddsparsePermissions/--permissions-jsonscoping (security-relevant — this is the very mechanism isolating write-capable App credentials to Stage 3), butgithub-app-token.jsis not rebuilt.src/approval-summary/render.tsrenamescreate-issue->create-github-issueand addsset-github-issue-typetoTOOL_SPECS, butapproval-summary.jsis not rebuilt.
Both need npm --prefix scripts/ado-script run build with the regenerated .js files committed — otherwise the shipped bundles silently keep running the old, unscoped/mislabeled behavior even though the source and tests say otherwise. Inline comments added on each source file.
🏗️ Compiler contract review by Compiler Contract Reviewer · aut00 · 37.7 AIC · ⌖ 6.5 AIC · ⊞ 11.9K
Comment /review to run again
There was a problem hiding this comment.
TypeScript review — scripts/ado-script/
Reviewed the github-app-token permission-scoping addition and the ADO_AW_DEBUG_GITHUB_TOKEN to ADO_AW_GITHUB_TOKEN rename across executor-e2e/trigger-e2e.
No blocking findings. parsePermissions validates permission names against an allowlist regex, explicitly rejects proto/prototype/constructor (prototype-pollution guard), validates levels are read/write, and is called inside main's existing try/catch so failures are logged via logError and return a non-zero exit code without leaking the raw JSON or secret material. Test coverage for both valid and invalid permission JSON is present. No unhandled promises, any leakage, or unsafe casts introduced in the diff.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · aut00 · 52.7 AIC · ⌖ 3.75 AIC · ⊞ 10.9K
Comment /review to run again
There was a problem hiding this comment.
This diff is a well-scoped credential-isolation refactor and I did not find a security regression compared to main.
Threat-model check (Stage 1/2 read-only vs Stage 3 write-capable GitHub credential):
ADO_AW_GITHUB_TOKEN/App token is only injected viagenerate_executor_ado_env()into the SafeOutputs (Stage 3) job env;agentic_pipeline.rsnever wiresgithub_authinto the Agent or Detection job builders. A new regression test (test_compile_github_issue_app_fixture_scopes_tokens_by_stage) explicitly assertsADO_AW_GITHUB_TOKENis absent from the Agent/Detection blocks — good coverage for exactly the leak a reviewer would worry about.github_safe_outputs_auth()intypes.rsrejects anywritepermission when GitHub issue outputs would inherit the sharedengine.github-app-token(which is also used in Stage 1/2), forcing a dedicated Stage-3-only credential whenever write access is needed.parse_safe_outputs_github_token()rejects reusingGITHUB_TOKENor the engine App-token variable name as the safe-outputs token source, so an author cannot accidentally point Stage 3 write config back at a Stage 1 read-only var.- Both new Stage 3 executors (
create_github_issue.rs,set_github_issue_type.rs) independently gate onctx.tool_configs.contains_key(...), so a forged NDJSON entry cannot bypass the MCP layer'sCONFIGURED_ONLY_TOOLSdefault-deny even if that filter were somehow circumvented. set-github-issue-type's temporary-ID resolution validates the resolved issue's repository against the configuredtarget-repowhen both are set, preventing a temp-ID mixup from redirecting a type change to an unintended repo.- GitHub API error bodies are passed through
neutralize_pipeline_commands()before being embedded inExecutionResultmessages, preventing##vso[...]injection via a reflected/compromised API response. GithubTemporaryId(new validated newtype insecure.rs) uses a closed-form regex, consistent with the rest of thesecure.rspattern.
No injection sinks, weakened validation, path-traversal issues, or network-allowlist widening were found in the reviewed portions (compiler front-matter validation, IR/pipeline wiring, MCP tool gating, Stage 3 executors, the TS App-token minting script, and the migration codemod). Nice defense-in-depth design overall.
🔒 Security review by PR Security Reviewer · aut00 · 137.1 AIC · ⌖ 3.91 AIC · ⊞ 11.2K
Comment /review to run again
There was a problem hiding this comment.
Rust Code Quality Review — no merge-blocking findings
Reviewed the Rust changes (src/safe_outputs/create_github_issue.rs, set_github_issue_type.rs, result.rs, secure.rs, compile/types.rs, compile/extensions/ado_script.rs, compile/agentic_pipeline.rs, execute.rs, mcp.rs, main.rs) plus accompanying tests, cross-referenced with a rust-critic sub-agent pass.
Verdict: no defects found. Both passes came back empty.
💡 What was checked
- Error handling: all fallible executor paths return
anyhow::Result/ExecutionResult::failure, no reachableunwrap()/expect()outside tests (the one non-testexpect()incommon.rs::generate_executor_ado_envis onserde_json::to_stringof a&str, which cannot fail). - Credential isolation:
github_token/github_api_urlare correctly confined to the Stage 3ExecutionContext, never wired into Agent/Detection env. - Concurrency:
resolved_github_issues: Arc<Mutex<HashMap<...>>>inresult.rsuses poison-safe.lock().map_err(...)and a proper check-then-insert under a single lock inregister_resolved_github_issue, avoiding a TOCTOU race on the mutation path. - Determinism: the new
permissions: BTreeMap<String, GithubAppPermissionLevel>inGithubAppTokenConfigplus thevalidate_forcollision guard (rejecting-/_spelling collisions before they silently collapse in--permissions-json) is correctly ordered and covered bygithub_app_permissions_reject_separator_only_key_collision. - Fallback safety:
resolve_target_repo's current-repo fallback is validated end-to-end (malformed_target_repo_does_not_redirect_to_current_repository), confirming no silent redirect to the wrong repository whentarget-repois malformed. - Cross-file consistency: renames (
create_issue.rs->create_github_issue.rs,CreateIssue*->CreateGithubIssue*,ADO_AW_DEBUG_GITHUB_TOKEN->ADO_AW_GITHUB_TOKEN) are complete and consistent acrossmcp.rs,execute.rs,mod.rs,main.rs, and tests.
Nothing to add beyond what's already covered by the compiler-contract and test-quality reviewers.
🦀 Rust code quality review by Rust Code Quality Reviewer · aut00 · 138.5 AIC · ⌖ 3.9 AIC · ⊞ 11K
Comment /review to run again
There was a problem hiding this comment.
Test Quality Sentinel review
This is a very well-tested PR overall — the author's own PR description calls out mutation-checked coverage for the two most security-relevant gates (write-scope rejection for engine App tokens, and the set-github-issue-type default-allow asymmetry), and both are indeed exercised by focused positive/negative test pairs (github_issue_outputs_reject_write_scoped_engine_app_permissions / _accept_read_only_engine_app_permissions, empty_allowed_list_permits_any_issue_type / non_empty_allowed_list_rejects_unlisted_type_before_http). The rename/promotion of create-issue → create-github-issue is mechanically mirrored across the Rust and TypeScript test suites, and the new set_github_issue_type.rs module has solid wiremock-backed coverage of its create→resolve→PATCH flow, unresolved temporary IDs, and case-insensitive type matching.
One gap: the new GithubTemporaryId newtype in src/secure.rs breaks the file's established pattern of pinning every validated-string type's accept/reject boundary with a dedicated unit test (see inline comment). Everything else checked out — no weakened or deleted assertions were found, and the removed test_execute_rejects_when_debug_tool_not_authorized test is legitimate since the underlying debug-gate mechanism it protected was intentionally removed as part of the promotion to a public safe output.
🧪 Test quality analysis by Test Quality Sentinel · aut00 · 142.7 AIC · ⌖ 4.01 AIC · ⊞ 10.7K
Comment /review to run again
incorrect prompt - built bundles are not committed
…d-mode version skew The rotation script still named only the retired per-case definitions, so running it would have rotated secrets onto definitions being deleted while leaving the new lane with none - the lane cannot run at all until GITHUB_TOKEN is present. Adds 2567, and sets the issues PAT under BOTH names during the cutover: ADO_AW_DEBUG_GITHUB_TOKEN for the committed release-owned locks that 2549/2558 still run, and ADO_AW_GITHUB_TOKEN for the lane once a release ships #1670. Both are dropped with those definitions at the end of the cutover. Also documents a constraint released mode inherits from dropping the committed locks. Those were regenerated by a bot after each release, so lock and binary always agreed; released mode instead compiles HEAD sources with the last released binary, so a source adopting unreleased front matter fails to compile - ado-aw rejects unknown safe-output keys outright. Verified against the real v0.48.0 asset: smoke-failure-reporter now fails ('unrecognised tool name: create-github-issue') because #1670 is merged but unreleased. canary, azure-cli, noop-target and janitor all still compile, so the blast radius is one case until the next release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
The reporter resolved its targets by exact ADO definition NAME, which the lane model abolishes for cases: a case is a ref queued against a shared lane, not a definition, so there is no 'canary definition' to look up. Two of the three names it watched - 'Daily safe-output smoke canary' (2545) and 'Daily smoke az CLI access' (2546) - are deleted at cutover anyway. It could not be repaired by editing names. Deleting it also removes the only released-mode case that could not compile. Verified against the real v0.48.0 asset: all four remaining released cases now compile, where smoke-failure-reporter failed with 'unrecognised tool name: create-github-issue' because #1670 is merged but unreleased. Released mode is green today rather than after a release. Knock-on simplification: no smoke case files GitHub issues any more, so the lane needs no GitHub PAT beyond Copilot CLI auth. ADO_AW_GITHUB_TOKEN is provisioned nowhere. Its intent - turn a failed scheduled run into a GitHub issue, because nobody watches ADO - is worth keeping and is filed as a follow-up. It belongs in the orchestrator as a deterministic step reusing executor-e2e/github-issue.ts, which already does exactly this job with title-based dedupe and no agent in the loop. Removes test_smoke_failure_reporter_uses_registered_ado_names_and_staging_repo, whose assertions were entirely about the deleted fixture's contents. The general contracts it touched keep coverage elsewhere: ADO_MCP_AUTH_TOKEN at five other sites, assert_job_execution_env_excludes_ado_credentials at six other call sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
…defer the infra lane Follows #1670, which promotes GitHub issue filing from \�do-aw-debug\ to the public \create-github-issue\ safe output and renames the secret. Same token value, same scope, same single case - the debug lane is a credential boundary, so a rename does not move the boundary. Also records that only \�gentic\ and \debug\ need registering at cutover. \loadCases\ resolves a definition id per lane in play for the mode being run, and no case targets \infra\ yet, so registering it now would create a credentialed definition nothing queues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
The debug lane isolated ADO_AW_GITHUB_TOKEN for a single case, back when GitHub issue filing was a debug-only capability behind \�do-aw-debug:\. #1670 promotes it to the public create-github-issue safe output, so a lane per credential would fragment as more cases adopt it - each new issue-filing case would either need its own lane or quietly widen this one, which is the failure mode the split existed to prevent. The isolation that mattered is now enforced where it cannot drift. The compiler projects the token into the Stage 3 executor only, and assertAdoTokenIsolation fails the run on freshly compiled YAML if it appears in Agent or Detection. That prevents the leak rather than bounding its blast radius, which is all a separate definition bought. infra stays: it holds no credentials at all, so it remains a real boundary for the AWF and ado-proxy smokes. The manifest test that pinned the debug split now asserts infra carries no cases, so the boundary cannot be dissolved by quietly provisioning a secret onto it. Cutover now needs ONE lane definition registered, not three. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
…d-mode version skew The rotation script still named only the retired per-case definitions, so running it would have rotated secrets onto definitions being deleted while leaving the new lane with none - the lane cannot run at all until GITHUB_TOKEN is present. Adds 2567, and sets the issues PAT under BOTH names during the cutover: ADO_AW_DEBUG_GITHUB_TOKEN for the committed release-owned locks that 2549/2558 still run, and ADO_AW_GITHUB_TOKEN for the lane once a release ships #1670. Both are dropped with those definitions at the end of the cutover. Also documents a constraint released mode inherits from dropping the committed locks. Those were regenerated by a bot after each release, so lock and binary always agreed; released mode instead compiles HEAD sources with the last released binary, so a source adopting unreleased front matter fails to compile - ado-aw rejects unknown safe-output keys outright. Verified against the real v0.48.0 asset: smoke-failure-reporter now fails ('unrecognised tool name: create-github-issue') because #1670 is merged but unreleased. canary, azure-cli, noop-target and janitor all still compile, so the blast radius is one case until the next release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
The reporter resolved its targets by exact ADO definition NAME, which the lane model abolishes for cases: a case is a ref queued against a shared lane, not a definition, so there is no 'canary definition' to look up. Two of the three names it watched - 'Daily safe-output smoke canary' (2545) and 'Daily smoke az CLI access' (2546) - are deleted at cutover anyway. It could not be repaired by editing names. Deleting it also removes the only released-mode case that could not compile. Verified against the real v0.48.0 asset: all four remaining released cases now compile, where smoke-failure-reporter failed with 'unrecognised tool name: create-github-issue' because #1670 is merged but unreleased. Released mode is green today rather than after a release. Knock-on simplification: no smoke case files GitHub issues any more, so the lane needs no GitHub PAT beyond Copilot CLI auth. ADO_AW_GITHUB_TOKEN is provisioned nowhere. Its intent - turn a failed scheduled run into a GitHub issue, because nobody watches ADO - is worth keeping and is filed as a follow-up. It belongs in the orchestrator as a deterministic step reusing executor-e2e/github-issue.ts, which already does exactly this job with title-based dedupe and no agent in the loop. Removes test_smoke_failure_reporter_uses_registered_ado_names_and_staging_repo, whose assertions were entirely about the deleted fixture's contents. The general contracts it touched keep coverage elsewhere: ADO_MCP_AUTH_TOKEN at five other sites, assert_job_execution_env_excludes_ado_credentials at six other call sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
…file removal Rebasing onto #1670 surfaced that this branch had dropped the 'Adding a new safe output' section along with the lock-file machinery it sat next to. The guidance itself was never lock-specific - it routes a new tool to executor-e2e (ADO write path) or signals.ts (signal-only) - so it is restored rather than lost. Two corrections while restoring it. The old point 5 said debug-only tools 'currently only create-github-issue' are excluded from both suites and exercised by smoke-failure-reporter.md; #1670 made that tool a public safe output and this branch deleted that case, so both halves were wrong. It now points at #1798, which tracks the executor-e2e gap. The 'Running locally' section referenced ado-aw check against committed locks that no longer exist, and a manual handoff runbook that now lives in tests/smoke/REGISTERED.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
…1791) * feat(smoke): replace per-case pipelines with lane-based smoke suite Adding a smoke cost a manual ADO definition registration, secret provisioning, service-connection authorization, fork-hardening, an orchestrator variable, a placeholder commit, a committed lock file and a TypeScript change - ten definitions and five locks in total. An ADO definition binds (repo, yamlFilename) but the ref is supplied per queue, so every case now compiles to the same .smoke/pipeline.yml path and is pushed to its own per-case ref. The ref carries the test case; the definition carries only the credentials. - Cases are declared in tests/smoke/cases.json and loaded by cases.ts with strict fail-closed validation (case ids become git ref segments). - Three lane definitions replace ten per-case ones, cut by credential class: agentic, debug (ADO_AW_DEBUG_GITHUB_TOKEN), infra (reserved for AWF and the ado-proxy sidecar, and ready for kind: raw cases). - Two modes share one steps template: candidate (compiler built from the commit) and released (latest release asset, release URLs required). The latter replaces the five committed *.lock.yml files and the bot workflow that kept them fresh. - Ref cleanup is now per case: one unproven build no longer strands every other case's ref. Adding a smoke is now a markdown file plus one manifest entry. Two dependencies that would otherwise have broken silently: executor-e2e's queue-build scenario targeted the noop-target definition, so it gets a dedicated static queue-target.yml; and the weekly janitor becomes a released-mode case (daily, its 30-day prune window is idempotent). Trigger hardening is two steps rather than three. Since on: became the complete declaration of when a pipeline runs, stripping it makes the compiler emit an explicit trigger: none / pr: none, so the harness no longer patches those keys into the staged copy. assertNoTriggers still verifies the staged bytes before push: ADO reads a MISSING trigger: as "CI on every branch", so a compiler that regressed to omitting it would let a ref push queue the shared lane on top of the API-queued run. The staged copy is now byte-identical to the pristine lock committed beside it. kind: raw sources have no compiler in the loop and so must declare both keys themselves. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): correct the load-time enforcement claim in the case-authoring guide The list conflated checks the harness actually performs with conventions an author must follow. arget: standalone and the credential-subset rule are neither parsed nor asserted anywhere, so a case declaring a safe output whose token its lane does not carry compiles, stages and queues cleanly, then fails in Stage 3. That distinction gets load-bearing as GitHub issue filing becomes a public configured-only safe output rather than an \�do-aw-debug:\ one: the obvious fix for such a Stage 3 failure is to add the token to the \�gentic\ lane, which would dissolve the isolation the lanes exist to provide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): rename the debug lane secret to ADO_AW_GITHUB_TOKEN and defer the infra lane Follows #1670, which promotes GitHub issue filing from \�do-aw-debug\ to the public \create-github-issue\ safe output and renames the secret. Same token value, same scope, same single case - the debug lane is a credential boundary, so a rename does not move the boundary. Also records that only \�gentic\ and \debug\ need registering at cutover. \loadCases\ resolves a definition id per lane in play for the mode being run, and no case targets \infra\ yet, so registering it now would create a credentialed definition nothing queues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): delete the retired definitions at cutover instead of disabling them The disable-first plan bought a rollback nobody wants: re-enabling ten definitions still leaves them without the secrets, service-connection authorizations and fork hardening that a working smoke needs. Deleting makes the tracked ids matter for exactly one reason, now stated where it can be acted on. The trigger-policy audit fetches every id in scheduled_only_definition_ids with curl --fail-with-body, so a deleted definition 404s, exhausts its three retries and aborts the run with 'Unable to audit scheduled-only definition <id>'. It fails closed rather than passing silently, but it fails every smoke run until the policy file is corrected - so 2545-2549 must leave that file in the same commit that deletes them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * test(smoke): forbid ADO_AW_GITHUB_TOKEN in Agent and Detection assertAdoTokenIsolation covered four ADO credentials but not the GitHub PAT, so nothing in the smoke suite would have caught a regression that projected it into Stage 1. It is the one credential here that grants write access outside the AgentPlayground project - Issues write on an external GitHub repo - so a leak into the agent is a reach-outside-ADO escape rather than a widening within a sandbox already scoped to the project. The compiler confines it to the Stage 3 executor env today (generate_executor_ado_env), but that is a per-workflow compile-time property with no regression guard. This asserts it on freshly compiled YAML, before push, so a regression fails the run rather than being contained by the lane split. GITHUB_TOKEN stays permitted: it is Copilot CLI auth and the agent legitimately receives it. Pinned by its own test so a later tightening cannot conflate the two. Mutation-checked: dropping the entry fails both new cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * refactor(smoke): merge the debug lane into agentic The debug lane isolated ADO_AW_GITHUB_TOKEN for a single case, back when GitHub issue filing was a debug-only capability behind \�do-aw-debug:\. #1670 promotes it to the public create-github-issue safe output, so a lane per credential would fragment as more cases adopt it - each new issue-filing case would either need its own lane or quietly widen this one, which is the failure mode the split existed to prevent. The isolation that mattered is now enforced where it cannot drift. The compiler projects the token into the Stage 3 executor only, and assertAdoTokenIsolation fails the run on freshly compiled YAML if it appears in Agent or Detection. That prevents the leak rather than bounding its blast radius, which is all a separate definition bought. infra stays: it holds no credentials at all, so it remains a real boundary for the AWF and ado-proxy smokes. The manifest test that pinned the debug split now asserts infra carries no cases, so the boundary cannot be dissolved by quietly provisioning a secret onto it. Cutover now needs ONE lane definition registered, not three. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): record the provisioned agentic lane and the 2559 repoint Provisioned in AgentPlayground and recorded here: - .smoke/pipeline.yml added to refs/heads/ado-aw-smoke-candidate-base (commit 1d173bc), carrying inert-child.yml - lane definition 'ado-aw smoke lane - agentic' registered as 2567, no triggers, default branch = the inert base ref - agent-playground-read/write authorized on 2567 - SMOKE_LANE_AGENTIC_DEFINITION_ID=2567 set on orchestrator 2559 - 2567 added to scheduled_only_definition_ids so the policy audit covers it The legacy tests/**/*.lock.yml paths were deliberately LEFT on the base ref: the ten retired definitions still point at them, so removing them before cutover would break the smokes that are currently running. They go with the definitions at the end. Also records a break this runbook had missed. Definition 2559 points at /tests/compiler-smoke-e2e/azure-pipelines.yml, which this change deletes, so the candidate orchestrator breaks on its next run unless it is repointed at /tests/smoke/azure-pipelines-candidate.yml. It cannot be repointed in advance because the new path does not exist on main until merge, so it is now an explicit merge-time step rather than an assumption. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): distinguish orchestrators from the per-case definitions the lanes replace 'Candidate compiler smoke' named two different things: the orchestrator 2559, and the six per-case children 2554-2565. Only the children are replaced by the lane model. An orchestrator builds or downloads the compiler, publishes the candidate artifact, stages each case to its own ref and queues the lane - work that cannot live in a lane, because a lane runs a staged pipeline from the mirror while an orchestrator runs from GitHub. Also pairs two edits that must land together at the repoint. The old orchestrator YAML reads the six COMPILER_SMOKE_*_DEFINITION_ID variables on 2559 and the new one never does, so removing them before the repoint breaks the running smoke, and leaving them afterwards keeps live pointers to deleted definitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * fix(smoke): include lane 2567 in secret rotation and document released-mode version skew The rotation script still named only the retired per-case definitions, so running it would have rotated secrets onto definitions being deleted while leaving the new lane with none - the lane cannot run at all until GITHUB_TOKEN is present. Adds 2567, and sets the issues PAT under BOTH names during the cutover: ADO_AW_DEBUG_GITHUB_TOKEN for the committed release-owned locks that 2549/2558 still run, and ADO_AW_GITHUB_TOKEN for the lane once a release ships #1670. Both are dropped with those definitions at the end of the cutover. Also documents a constraint released mode inherits from dropping the committed locks. Those were regenerated by a bot after each release, so lock and binary always agreed; released mode instead compiles HEAD sources with the last released binary, so a source adopting unreleased front matter fails to compile - ado-aw rejects unknown safe-output keys outright. Verified against the real v0.48.0 asset: smoke-failure-reporter now fails ('unrecognised tool name: create-github-issue') because #1670 is merged but unreleased. canary, azure-cli, noop-target and janitor all still compile, so the blast radius is one case until the next release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * refactor(smoke): remove the smoke-failure-reporter case The reporter resolved its targets by exact ADO definition NAME, which the lane model abolishes for cases: a case is a ref queued against a shared lane, not a definition, so there is no 'canary definition' to look up. Two of the three names it watched - 'Daily safe-output smoke canary' (2545) and 'Daily smoke az CLI access' (2546) - are deleted at cutover anyway. It could not be repaired by editing names. Deleting it also removes the only released-mode case that could not compile. Verified against the real v0.48.0 asset: all four remaining released cases now compile, where smoke-failure-reporter failed with 'unrecognised tool name: create-github-issue' because #1670 is merged but unreleased. Released mode is green today rather than after a release. Knock-on simplification: no smoke case files GitHub issues any more, so the lane needs no GitHub PAT beyond Copilot CLI auth. ADO_AW_GITHUB_TOKEN is provisioned nowhere. Its intent - turn a failed scheduled run into a GitHub issue, because nobody watches ADO - is worth keeping and is filed as a follow-up. It belongs in the orchestrator as a deterministic step reusing executor-e2e/github-issue.ts, which already does exactly this job with title-based dedupe and no agent in the loop. Removes test_smoke_failure_reporter_uses_registered_ado_names_and_staging_repo, whose assertions were entirely about the deleted fixture's contents. The general contracts it touched keep coverage elsewhere: ADO_MCP_AUTH_TOKEN at five other sites, assert_job_execution_env_excludes_ado_credentials at six other call sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): record the verified lane and the agent-pool authorization step Confirms the secrets are provisioned on 2567 and adds a step the runbook was missing. A new lane definition needs the agent POOL authorized in addition to the service connections, and its absence does not surface as an error: the build queues, sits at status notStarted indefinitely, and the timeline shows Checkpoint.Authorization inProgress. No failure, no timeout - it simply never starts. Found by queueing the lane and watching it hang for seven minutes; every pre-existing definition was already on the pool's explicit allowlist, so this only bites on newly registered ones. Also records a live verification. Build 629504 queued 2567 against the base ref and failed at 'Reject inert candidate-smoke base' with 'Candidate compiler smoke must be queued with an explicit generated ref.' That failure is the pass condition: it proves checkout, pool, YAML path and the inert guard all work, and that a lane cannot run without an explicitly supplied case ref. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd * docs(smoke): restore safe-output authoring guidance lost in the lock-file removal Rebasing onto #1670 surfaced that this branch had dropped the 'Adding a new safe output' section along with the lock-file machinery it sat next to. The guidance itself was never lock-specific - it routes a new tool to executor-e2e (ADO write path) or signals.ts (signal-only) - so it is restored rather than lost. Two corrections while restoring it. The old point 5 said debug-only tools 'currently only create-github-issue' are excluded from both suites and exercised by smoke-failure-reporter.md; #1670 made that tool a public safe output and this branch deleted that case, so both halves were wrong. It now points at #1798, which tracks the executor-e2e gap. The 'Running locally' section referenced ado-aw check against committed locks that no longer exist, and a manual handoff runbook that now lives in tests/smoke/REGISTERED.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 323b70ff-d193-4c6e-b4c7-9ec6c3dc6ebd
Summary
ado-aw-debugto a configured-only public safe output namedcreate-github-issueset-github-issue-typewith same-run temporary issue IDscreate-work-itemsurface at runtimepromote_debug_create_github_issuecodemod (ado-aw-debug.create-issue->safe-outputs.create-github-issue) and update documentation and dogfood workflowsCloses #1621
Test plan
cargo test(2,774 tests)cargo clippy --all-targetscargo test --test compiler_testscargo test --test codemod_testsnpm test -- --maxWorkers=1(789 tests, 75 files)npm run typechecknpm run build:github-app-tokenAdded review-requested coverage
Two security-relevant gates flagged in review as having no regression protection:
engine.github-app-token.permissionswrite rejection — GitHub issue safe outputs may inherit the engine App credentials, and that token is also handed to Agent/Detection, so awritescope there would leak write-capable GitHub credentials into Stage 1. The sibling empty-permissions branch was already tested; the write branch was not. Adds the negative test plus a read-only positive counterpart.set-github-issue-type.alloweddefault-allow — verified intentional, not a bug: issue types are a closed set defined by the repository owner, so an empty list is already bounded by configuration the agent cannot influence, whereascreate-github-issue.allowed-labelsis default-deny because labels are free-form strings the agent can invent. Adds three tests pinning the documented behaviour, plus a note indocs/safe-outputs.mdso the asymmetry is not "fixed" for consistency later.Both gates were mutation-checked: disabling the write guard, and flipping the empty-allowed branch to default-deny, each fail the corresponding new test.
Known gap: no executor E2E coverage (follow-up)
Neither new tool is exercised end to end against real GitHub. Deferred deliberately — the smoke suite is being reworked separately, so adding a scenario now would collide with that work.
executor-e2eexcludes it by design today —tests/executor-e2e/README.md: "Excluded (out of scope or GitHub-only): the GitHub-onlycreate-github-issue." There is no github-issue scenario inscenarios/index.ts. (Note the confusable:executor-e2e/github-issue.tsis the harness's own failure reporter, not a scenario under test.)smoke-failure-reporter(def 2549) is the only pipeline that calls the tool at runtime, but its release-owned lock still runs the legacy path ("create-issue",ADO_AW_DEBUG_GITHUB_TOKEN). It only switches tocreate-github-issueonce a release regenerates the lock — at which pointADO_AW_GITHUB_TOKENmust be provisioned on that definition (the rotation script is updated in this PR to match).set-github-issue-typehas no runtime coverage at all; its only non-src/appearance is a compile fixture.Residual risk while this is open: the same-run temporary-ID handoff (
create-github-issue->set-github-issue-type) is proven only by a wiremock unit test, so a wrong REST shape or App-token scoping failure would not be caught until the reworked smokes land.Feasibility note for whoever picks this up: definition 2550 already has
EXECUTOR_E2E_GITHUB_TOKENprovisioned, so ascenarios/github-issue.tscovering create -> set-type -> clear-type needs no new secret provisioning.Addressed review feedback (2026-08-03)
The three reviewers each reported findings with "see inline comment", but no inline comments were actually posted (0 review threads exist on this PR via both REST and GraphQL) — the
create-pull-request-review-commentsafe outputs were dropped, most likely because the target lines fell outside the pre-fetched diff, which the Rust reviewer noted was truncated at 3000 lines. Findings were therefore taken from the review bodies and are resolved here rather than on threads.-→_normalization in the App-token mint steptarget-reposhould be asrc/secure.rsnewtype1. Permission-key collision (defect)
ado_script.rsnormalizes-→_before serializing--permissions-json(the GitHub API spells permissions with underscores) and collects into aBTreeMap, sopull-requestsandpull_requestscollapse onto one entry. The source map is also aBTreeMap, so_sorts after-and deterministically wins — an author declaring both spellings could have an intendedreadsilently replaced bywrite.GithubAppTokenConfig::validate_fornow rejects two keys that normalize to the same identifier, failing at compile time with a clear message. A single dashed spelling stays valid, so existing front matter is unaffected.2. Untested reachable error paths
Both were in
resolve_target_repo: a GitHub Enterprise pipeline source still pointing atapi.github.com, and a GitHub-backed build where ADO did not surfaceBUILD_REPOSITORY_NAME. Added both negative tests plus a GHE positive counterpart.3.
target-reponewtype — declinedApplying this would introduce a fail-open.
ExecutionContext::get_tool_configdeserializes with.ok().unwrap_or_default(), so a value rejected at deserialization time collapses the entire config toDefault(target_repo: None) — whichresolve_target_repothen resolves to the current repository. The newtype would convert today's loud failure into a silent redirect that files issues against the wrong repo.target-repois already validated at compile time byvalidate_github_issue_outputs_config(validate_target_repo+reject_pipeline_injection) and re-validated at every Stage 3 call site. Addedmalformed_target_repo_does_not_redirect_to_current_repository, which pins the no-silent-redirect behaviour with a usable current repository deliberately present in the context, and records the rationale on the test so this is not "fixed" later.