diff --git a/.copilot/AGENT_GUIDE.md b/.copilot/AGENT_GUIDE.md index a963ae107..a9d7c453e 100644 --- a/.copilot/AGENT_GUIDE.md +++ b/.copilot/AGENT_GUIDE.md @@ -51,4 +51,6 @@ The setup runner profile selects placement; it does not install the project's la Declare reviewed version prefixes in `.copilot/verification-prerequisites.json` (schemaVersion 1, runtimes such as java 17, node 24, pnpm 10). Copilot checks declared versions in the credential-free verification environment before each configured verification command; a missing or incompatible runtime blocks publication. Without this optional declaration, existing verification behavior remains and no language is inferred. Java provisioning may use `actions/setup-java`; `JAVA_HOME` and `PATH` survive isolation, while agent credentials and tool-option injection variables do not. Do not add an unreviewed bootstrap or weaken verification to work around a missing runtime. Read [verification prerequisites and same-job setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites). +The optional repository-owned composite action prepares each candidate before verification. Review `.github/actions/copilot-verification-setup/action.yml` and its assets onto trusted main, update generated workflows, then enable `VERIFICATION_SETUP=true` (Action input `verification-setup`, disabled by default). GitHub Actions executes the trusted snapshot before each command with a fresh private HOME; candidate changes cannot replace it. Native post steps finish before profile cleanup. Setup failure blocks verification and commit/push. The local CLI cannot execute composite actions and fails closed when opted in. Read [repository-owned setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup). + For the full contributor flow, read [repository collaboration](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-collaboration.mdx). For drift and recovery, read [guidance troubleshooting](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-guidance-troubleshooting.mdx). diff --git a/.copilot/setup-manifest.json b/.copilot/setup-manifest.json index b94212fbe..4b26a2fb2 100644 --- a/.copilot/setup-manifest.json +++ b/.copilot/setup-manifest.json @@ -12,7 +12,7 @@ }, ".copilot/AGENT_GUIDE.md": { "role": "guide", - "sha256": "07ef187d584f4da868a6cebe6b6d7c7654b057af703e0d10ede11f768ba64674" + "sha256": "4c453f9e76287c970d56787bd082fc2d7d8d432b191a96a7132e811657905dbc" }, ".copilot/repository-profile.json": { "role": "profile", diff --git a/.github/actions/copilot-native-cleanup-check/action.yml b/.github/actions/copilot-native-cleanup-check/action.yml new file mode 100644 index 000000000..e23989c47 --- /dev/null +++ b/.github/actions/copilot-native-cleanup-check/action.yml @@ -0,0 +1,6 @@ +name: Verify native setup cleanup +runs: + using: node24 + main: main.cjs + post: post.cjs + post-if: always() diff --git a/.github/actions/copilot-native-cleanup-check/main.cjs b/.github/actions/copilot-native-cleanup-check/main.cjs new file mode 100644 index 000000000..e036784e6 --- /dev/null +++ b/.github/actions/copilot-native-cleanup-check/main.cjs @@ -0,0 +1,2 @@ +const fs = require('node:fs'); +fs.appendFileSync(process.env.GITHUB_STATE, `workspace=${fs.realpathSync(process.cwd())}\n`); diff --git a/.github/actions/copilot-native-cleanup-check/post.cjs b/.github/actions/copilot-native-cleanup-check/post.cjs new file mode 100644 index 000000000..5e7aec929 --- /dev/null +++ b/.github/actions/copilot-native-cleanup-check/post.cjs @@ -0,0 +1,10 @@ +const fs = require('node:fs'); +const path = require('node:path'); +const workspace = process.env.STATE_workspace; +// Keep the poisoned candidate bundle through every deferred post step, then remove +// only this job's ignored generated fixture output so subsequent runs start clean. +fs.rmSync(path.join(workspace, '.github/actions/copilot-native-fixture/dist'), { recursive: true, force: true }); +for (const file of [path.join(workspace, '.git/copilot-verification-setup'), path.join(path.dirname(workspace), '.copilot-native-fixture-runtime')]) { + if (fs.existsSync(file)) throw new Error('Native acceptance left owned staging or runtime snapshots after post cleanup.'); +} +console.log('Native acceptance cleanup passed after all deferred post steps.'); diff --git a/.github/actions/copilot-native-fixture/action.yml b/.github/actions/copilot-native-fixture/action.yml new file mode 100644 index 000000000..165ffd61b --- /dev/null +++ b/.github/actions/copilot-native-fixture/action.yml @@ -0,0 +1,10 @@ +name: Copilot native verification acceptance +inputs: + verification-setup: + default: 'true' + description: Exercise the native verification bridge +runs: + using: node24 + main: dist/index.js + post: dist/index.js + post-if: always() && env.COPILOT_NATIVE_PHASE != '' diff --git a/.github/workflows/copilot_commit.yml b/.github/workflows/copilot_commit.yml index f1a263b89..dbdfdd087 100644 --- a/.github/workflows/copilot_commit.yml +++ b/.github/workflows/copilot_commit.yml @@ -51,6 +51,7 @@ jobs: issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} debug: ${{ vars.DEBUG }} bugbot-severity: ${{ vars.BUGBOT_SEVERITY || 'low' }} bugbot-comment-limit: ${{ vars.BUGBOT_COMMENT_LIMIT || '20' }} diff --git a/.github/workflows/copilot_issue.yml b/.github/workflows/copilot_issue.yml index 789458199..beef7c2a3 100644 --- a/.github/workflows/copilot_issue.yml +++ b/.github/workflows/copilot_issue.yml @@ -26,6 +26,7 @@ jobs: issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} ai-ignore-files: build/*,specs/CATALOG.md bugbot-dry-run: ${{ vars.BUGBOT_DRY_RUN || 'false' }} bugbot-effort: ${{ vars.BUGBOT_EFFORT || 'smart' }} diff --git a/.github/workflows/copilot_issue_comment.yml b/.github/workflows/copilot_issue_comment.yml index 8fde2a2dc..b8a567d2d 100644 --- a/.github/workflows/copilot_issue_comment.yml +++ b/.github/workflows/copilot_issue_comment.yml @@ -42,14 +42,21 @@ jobs: shell: bash run: pnpm install --frozen-lockfile --ignore-scripts - - uses: ./ - with: + - name: Snapshot the Copilot runtime before candidate changes + id: prepare_native_runtime + run: node scripts/prepare-native-source-action.cjs + shell: bash + + - uses: ./../.copilot-native-runtime + id: copilot + with: &copilot-inputs repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }} issues-locale: ${{ vars.ISSUES_LOCALE || '' }} pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }} issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} ai-ignore-files: build/*,specs/CATALOG.md debug: ${{ vars.DEBUG }} agent-provider: ${{ vars.AGENT_PROVIDER || 'codex' }} @@ -94,6 +101,8 @@ jobs: bugbot-fail-on-unresolved: ${{ vars.BUGBOT_FAIL_ON_UNRESOLVED || 'false' }} bugbot-organization-rules: ${{ vars.BUGBOT_ORGANIZATION_RULES }} env: + COPILOT_NATIVE_PHASE: prepare + COPILOT_NATIVE_RUNTIME_CACHE: ${{ steps.prepare_native_runtime.outputs.path }} AGENT_PROVIDER: ${{ vars.AGENT_PROVIDER || 'codex' }} AGENT_MODEL_PROVIDER: ${{ vars.AGENT_MODEL_PROVIDER || 'openai' }} AGENT_MODEL: ${{ vars.AGENT_MODEL }} @@ -109,3 +118,577 @@ jobs: CURSOR_API_KEY: ${{ (vars.AGENT_PROVIDER == 'cursor' || vars.FINDINGS_PROVIDER == 'cursor' || vars.FIXER_PROVIDER == 'cursor' || vars.PLANNER_PROVIDER == 'cursor' || vars.REVIEWER_PROVIDER == 'cursor' || vars.TESTER_PROVIDER == 'cursor') && secrets.CURSOR_API_KEY || '' }} CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} COPILOT_EVIDENCE_TOKEN: ${{ github.token }} + + # copilot:native-verification:start + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot.outcome == 'success' && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot.outputs.verification-localappdata }} + PATH: ${{ steps.copilot.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot.outputs.verification-java_home }} + TMP: ${{ steps.copilot.outputs.verification-tmp }} + TEMP: ${{ steps.copilot.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot.outputs.verification-ticket != '' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + # copilot:native-verification:end diff --git a/.github/workflows/copilot_pull_request.yml b/.github/workflows/copilot_pull_request.yml index d63827d47..928453a51 100644 --- a/.github/workflows/copilot_pull_request.yml +++ b/.github/workflows/copilot_pull_request.yml @@ -48,6 +48,7 @@ jobs: issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} ai-ignore-files: build/*,specs/CATALOG.md bugbot-severity: ${{ vars.BUGBOT_SEVERITY || 'low' }} bugbot-comment-limit: ${{ vars.BUGBOT_COMMENT_LIMIT || '20' }} diff --git a/.github/workflows/copilot_pull_request_comment.yml b/.github/workflows/copilot_pull_request_comment.yml index 0b2fdd06d..b3e5f1f38 100644 --- a/.github/workflows/copilot_pull_request_comment.yml +++ b/.github/workflows/copilot_pull_request_comment.yml @@ -42,14 +42,21 @@ jobs: shell: bash run: pnpm install --frozen-lockfile --ignore-scripts - - uses: ./ - with: + - name: Snapshot the Copilot runtime before candidate changes + id: prepare_native_runtime + run: node scripts/prepare-native-source-action.cjs + shell: bash + + - uses: ./../.copilot-native-runtime + id: copilot + with: &copilot-inputs repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }} issues-locale: ${{ vars.ISSUES_LOCALE || '' }} pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }} issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} ai-ignore-files: build/*,specs/CATALOG.md debug: ${{ vars.DEBUG }} agent-provider: ${{ vars.AGENT_PROVIDER || 'codex' }} @@ -94,6 +101,8 @@ jobs: bugbot-fail-on-unresolved: ${{ vars.BUGBOT_FAIL_ON_UNRESOLVED || 'false' }} bugbot-organization-rules: ${{ vars.BUGBOT_ORGANIZATION_RULES }} env: + COPILOT_NATIVE_PHASE: prepare + COPILOT_NATIVE_RUNTIME_CACHE: ${{ steps.prepare_native_runtime.outputs.path }} AGENT_PROVIDER: ${{ vars.AGENT_PROVIDER || 'codex' }} AGENT_MODEL_PROVIDER: ${{ vars.AGENT_MODEL_PROVIDER || 'openai' }} AGENT_MODEL: ${{ vars.AGENT_MODEL }} @@ -109,3 +118,577 @@ jobs: CURSOR_API_KEY: ${{ (vars.AGENT_PROVIDER == 'cursor' || vars.FINDINGS_PROVIDER == 'cursor' || vars.FIXER_PROVIDER == 'cursor' || vars.PLANNER_PROVIDER == 'cursor' || vars.REVIEWER_PROVIDER == 'cursor' || vars.TESTER_PROVIDER == 'cursor') && secrets.CURSOR_API_KEY || '' }} CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} COPILOT_EVIDENCE_TOKEN: ${{ github.token }} + + # copilot:native-verification:start + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot.outcome == 'success' && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot.outputs.verification-localappdata }} + PATH: ${{ steps.copilot.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot.outputs.verification-java_home }} + TMP: ${{ steps.copilot.outputs.verification-tmp }} + TEMP: ${{ steps.copilot.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot.outputs.verification-ticket != '' }} + uses: ./../.copilot-native-runtime + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + # copilot:native-verification:end diff --git a/.github/workflows/copilot_pull_request_review_state.yml b/.github/workflows/copilot_pull_request_review_state.yml index 4d77d0bda..eb5879069 100644 --- a/.github/workflows/copilot_pull_request_review_state.yml +++ b/.github/workflows/copilot_pull_request_review_state.yml @@ -33,6 +33,7 @@ jobs: issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} issue-managed-branches: true pre-branch-sdd: true + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} ai-ignore-files: build/*,specs/CATALOG.md bugbot-severity: ${{ vars.BUGBOT_SEVERITY || 'low' }} bugbot-comment-limit: ${{ vars.BUGBOT_COMMENT_LIMIT || '20' }} diff --git a/.github/workflows/setup_platform_smoke.yml b/.github/workflows/setup_platform_smoke.yml index 251118abb..81a8fec6c 100644 --- a/.github/workflows/setup_platform_smoke.yml +++ b/.github/workflows/setup_platform_smoke.yml @@ -63,6 +63,592 @@ jobs: if: runner.os == 'Windows' run: node scripts/verify-windows-job-npm.cjs - run: pnpm install --frozen-lockfile + - name: Register native post cleanup acceptance + uses: ./.github/actions/copilot-native-cleanup-check + - name: Build native verification acceptance fixture + run: pnpm exec ncc build src/tooling/__tests__/fixtures/verification_native_fixture.ts -o .github/actions/copilot-native-fixture/dist + - name: Snapshot native fixture runtime + id: prepare_native_fixture_runtime + run: node scripts/prepare-native-source-action.cjs --fixture + - name: Prepare native verification acceptance + id: copilot_native_fixture + uses: ./../.copilot-native-fixture-runtime + with: &native-fixture-inputs + verification-setup: 'true' + env: + COPILOT_NATIVE_PHASE: prepare + COPILOT_NATIVE_RUNTIME_CACHE: ${{ steps.prepare_native_fixture_runtime.outputs.path }} + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot_native_fixture.outcome == 'success' && steps.copilot_native_fixture.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot_native_fixture.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot_native_fixture.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot_native_fixture.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot_native_fixture.outputs.verification-localappdata }} + PATH: ${{ steps.copilot_native_fixture.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot_native_fixture.outputs.verification-java_home }} + TMP: ${{ steps.copilot_native_fixture.outputs.verification-tmp }} + TEMP: ${{ steps.copilot_native_fixture.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot_native_fixture.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot_native_fixture.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot_native_fixture.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot_native_fixture.outputs.verification-ticket != '' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} - run: pnpm run build # Checked-in ncc bundles are generated on Unix; Windows output differs. - if: runner.os != 'Windows' @@ -157,6 +743,592 @@ jobs: run: | git ls-files --eol -- .copilot/repository-profile.json .copilot/AGENT_GUIDE.md .agents/skills/copilot-repository-workflow/SKILL.md pnpm run validate:agent-docs + - name: Register native post cleanup acceptance + uses: ./.github/actions/copilot-native-cleanup-check + - name: Build native verification acceptance fixture + run: pnpm exec ncc build src/tooling/__tests__/fixtures/verification_native_fixture.ts -o .github/actions/copilot-native-fixture/dist + - name: Snapshot native fixture runtime + id: prepare_native_fixture_runtime + run: node scripts/prepare-native-source-action.cjs --fixture + - name: Prepare native verification acceptance + id: copilot_native_fixture + uses: ./../.copilot-native-fixture-runtime + with: &native-fixture-inputs + verification-setup: 'true' + env: + COPILOT_NATIVE_PHASE: prepare + COPILOT_NATIVE_RUNTIME_CACHE: ${{ steps.prepare_native_fixture_runtime.outputs.path }} + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot_native_fixture.outcome == 'success' && steps.copilot_native_fixture.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot_native_fixture.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot_native_fixture.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot_native_fixture.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot_native_fixture.outputs.verification-localappdata }} + PATH: ${{ steps.copilot_native_fixture.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot_native_fixture.outputs.verification-java_home }} + TMP: ${{ steps.copilot_native_fixture.outputs.verification-tmp }} + TEMP: ${{ steps.copilot_native_fixture.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot_native_fixture.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot_native_fixture.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot_native_fixture.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot_native_fixture.outputs.verification-ticket != '' }} + uses: ./../.copilot-native-fixture-runtime + with: *native-fixture-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot_native_fixture.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot_native_fixture.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot_native_fixture.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot_native_fixture.outputs.verification-ticket }} - run: pnpm run build - run: pnpm run validate:build - run: pnpm run typecheck diff --git a/.gitignore b/.gitignore index eb924bdb6..80f4da40e 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,6 @@ opencode.json # Python analysis tooling __pycache__/ + +# Native Actions acceptance bundle is built only by the platform smoke job. +.github/actions/copilot-native-fixture/dist/ diff --git a/action.yml b/action.yml index 8c30c4230..9772d06bf 100644 --- a/action.yml +++ b/action.yml @@ -53,6 +53,9 @@ inputs: pre-branch-sdd: description: "Require a validated SDD first commit for started feature and contract-change issues. Requires issue-managed-branches." default: "false" + verification-setup: + description: "Run the trusted repository composite action before repository verification in generated workflows. Disabled by default." + default: "false" issue-workflow-profile: description: "JSON profile of enabled issue workflows." default: "" @@ -558,6 +561,8 @@ outputs: runs: using: "node24" main: "build/github_action/index.js" + post: "build/github_action/index.js" + post-if: "always() && env.COPILOT_NATIVE_PHASE != ''" branding: icon: "cpu" color: "blue" diff --git a/build/cli/index.js b/build/cli/index.js index 1138be24d..c4b0d3a60 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -37179,6 +37179,7 @@ function readLocalWorkflowConfiguration(additionalParams, actionInputs) { commitPrefixBuilder: read(input_keys_1.INPUT_KEYS.COMMIT_PREFIX_TRANSFORMS) || 'replace-slash', issueManagedBranches: (0, input_boolean_policy_1.parseIssueWorkflowBoolean)(read(input_keys_1.INPUT_KEYS.ISSUE_MANAGED_BRANCHES), input_keys_1.INPUT_KEYS.ISSUE_MANAGED_BRANCHES, true), preBranchSdd: (0, input_boolean_policy_1.parseIssueWorkflowBoolean)(read(input_keys_1.INPUT_KEYS.PRE_BRANCH_SDD), input_keys_1.INPUT_KEYS.PRE_BRANCH_SDD, false), + verificationSetup: (0, input_boolean_policy_1.parseIssueWorkflowBoolean)(read(input_keys_1.INPUT_KEYS.VERIFICATION_SETUP), input_keys_1.INPUT_KEYS.VERIFICATION_SETUP, false), reopenIssueOnPush: (0, input_boolean_policy_1.isEnabledInput)(read(input_keys_1.INPUT_KEYS.REOPEN_ISSUE_ON_PUSH)), issueDesiredAssigneesCount: (0, input_number_policy_1.parseIntegerInput)(read(input_keys_1.INPUT_KEYS.DESIRED_ASSIGNEES_COUNT), 0), pullRequestDesiredAssigneesCount: (0, input_number_policy_1.parseIntegerInput)(read(input_keys_1.INPUT_KEYS.PULL_REQUEST_DESIRED_ASSIGNEES_COUNT), 0), @@ -37212,7 +37213,7 @@ const configuration_builders_1 = __nccwpck_require__(19094); const branches_builder_1 = __nccwpck_require__(30085); const size_threshold_builder_1 = __nccwpck_require__(39757); function buildLocalActionExecution(configuration, additionalParams) { - const { debug, singleAction, singleActionIssue, singleActionVersion, singleActionTitle, singleActionChangelog, singleActionMessage, singleActionCommentId, singleActionCommentMode, singleActionOperationId, inactivityThresholdHours, commitPrefixBuilder, issueManagedBranches, preBranchSdd, reopenIssueOnPush, issueDesiredAssigneesCount, pullRequestDesiredAssigneesCount, pullRequestDesiredReviewersCount, titleEmoji, branchManagementEmoji, token, agentModel, aiPullRequestDescriptionMode, aiMembersOnly, aiIgnoreFiles, aiIncludeReasoning, bugbotSeverity, bugbotCommentLimit, bugbotFixVerifyCommands, bugbotReviewConfiguration, agentTasks, bugLabel, bugfixLabel, hotfixLabel, enhancementLabel, featureLabel, releaseLabel, questionLabel, helpLabel, deployLabel, deployedLabel, docsLabel, documentationLabel, choreLabel, maintenanceLabel, priorityHighLabel, priorityMediumLabel, priorityLowLabel, priorityNoneLabel, sizeXxlLabel, sizeXlLabel, sizeLLabel, sizeMLabel, sizeSLabel, sizeXsLabel, lifecycle, issueTypeTask, issueTypeTaskDescription, issueTypeTaskColor, issueTypeBug, issueTypeBugDescription, issueTypeBugColor, issueTypeFeature, issueTypeFeatureDescription, issueTypeFeatureColor, issueTypeDocumentation, issueTypeDocumentationDescription, issueTypeDocumentationColor, issueTypeMaintenance, issueTypeMaintenanceDescription, issueTypeMaintenanceColor, issueTypeHotfix, issueTypeHotfixDescription, issueTypeHotfixColor, issueTypeRelease, issueTypeReleaseDescription, issueTypeReleaseColor, issueTypeQuestion, issueTypeQuestionDescription, issueTypeQuestionColor, issueTypeHelp, issueTypeHelpDescription, issueTypeHelpColor, repositoryLocale, issueLocale, pullRequestLocale, sizeXxlThresholdLines, sizeXxlThresholdFiles, sizeXxlThresholdCommits, sizeXlThresholdLines, sizeXlThresholdFiles, sizeXlThresholdCommits, sizeLThresholdLines, sizeLThresholdFiles, sizeLThresholdCommits, sizeMThresholdLines, sizeMThresholdFiles, sizeMThresholdCommits, sizeSThresholdLines, sizeSThresholdFiles, sizeSThresholdCommits, sizeXsThresholdLines, sizeXsThresholdFiles, sizeXsThresholdCommits, mainBranch, developmentBranch, featureTree, bugfixTree, hotfixTree, releaseTree, docsTree, choreTree, releaseWorkflow, hotfixWorkflow, projects, projectColumnIssueCreated, projectColumnPullRequestCreated, projectColumnIssueInProgress, projectColumnPullRequestInProgress, welcomeTitle, welcomeMessages, deployment, } = configuration; + const { debug, singleAction, singleActionIssue, singleActionVersion, singleActionTitle, singleActionChangelog, singleActionMessage, singleActionCommentId, singleActionCommentMode, singleActionOperationId, inactivityThresholdHours, commitPrefixBuilder, issueManagedBranches, preBranchSdd, verificationSetup, reopenIssueOnPush, issueDesiredAssigneesCount, pullRequestDesiredAssigneesCount, pullRequestDesiredReviewersCount, titleEmoji, branchManagementEmoji, token, agentModel, aiPullRequestDescriptionMode, aiMembersOnly, aiIgnoreFiles, aiIncludeReasoning, bugbotSeverity, bugbotCommentLimit, bugbotFixVerifyCommands, bugbotReviewConfiguration, agentTasks, bugLabel, bugfixLabel, hotfixLabel, enhancementLabel, featureLabel, releaseLabel, questionLabel, helpLabel, deployLabel, deployedLabel, docsLabel, documentationLabel, choreLabel, maintenanceLabel, priorityHighLabel, priorityMediumLabel, priorityLowLabel, priorityNoneLabel, sizeXxlLabel, sizeXlLabel, sizeLLabel, sizeMLabel, sizeSLabel, sizeXsLabel, lifecycle, issueTypeTask, issueTypeTaskDescription, issueTypeTaskColor, issueTypeBug, issueTypeBugDescription, issueTypeBugColor, issueTypeFeature, issueTypeFeatureDescription, issueTypeFeatureColor, issueTypeDocumentation, issueTypeDocumentationDescription, issueTypeDocumentationColor, issueTypeMaintenance, issueTypeMaintenanceDescription, issueTypeMaintenanceColor, issueTypeHotfix, issueTypeHotfixDescription, issueTypeHotfixColor, issueTypeRelease, issueTypeReleaseDescription, issueTypeReleaseColor, issueTypeQuestion, issueTypeQuestionDescription, issueTypeQuestionColor, issueTypeHelp, issueTypeHelpDescription, issueTypeHelpColor, repositoryLocale, issueLocale, pullRequestLocale, sizeXxlThresholdLines, sizeXxlThresholdFiles, sizeXxlThresholdCommits, sizeXlThresholdLines, sizeXlThresholdFiles, sizeXlThresholdCommits, sizeLThresholdLines, sizeLThresholdFiles, sizeLThresholdCommits, sizeMThresholdLines, sizeMThresholdFiles, sizeMThresholdCommits, sizeSThresholdLines, sizeSThresholdFiles, sizeSThresholdCommits, sizeXsThresholdLines, sizeXsThresholdFiles, sizeXsThresholdCommits, mainBranch, developmentBranch, featureTree, bugfixTree, hotfixTree, releaseTree, docsTree, choreTree, releaseWorkflow, hotfixWorkflow, projects, projectColumnIssueCreated, projectColumnPullRequestCreated, projectColumnIssueInProgress, projectColumnPullRequestInProgress, welcomeTitle, welcomeMessages, deployment, } = configuration; return (0, execution_builder_1.buildExecution)({ debug, inactivityThresholdHours, @@ -37220,6 +37221,7 @@ function buildLocalActionExecution(configuration, additionalParams) { commitPrefixBuilder, issue: (0, configuration_builders_1.buildIssue)(issueManagedBranches, reopenIssueOnPush, issueDesiredAssigneesCount, additionalParams), preBranchSdd, + verificationSetup, pullRequest: (0, configuration_builders_1.buildPullRequest)(pullRequestDesiredAssigneesCount, pullRequestDesiredReviewersCount, additionalParams), emoji: (0, configuration_builders_1.buildEmoji)(titleEmoji, branchManagementEmoji), tokens: (0, configuration_builders_1.buildTokens)(token), @@ -37982,6 +37984,7 @@ exports.INPUT_KEYS = { // Issue ISSUE_MANAGED_BRANCHES: 'issue-managed-branches', PRE_BRANCH_SDD: 'pre-branch-sdd', + VERIFICATION_SETUP: 'verification-setup', REOPEN_ISSUE_ON_PUSH: 'reopen-issue-on-push', DESIRED_ASSIGNEES_COUNT: 'desired-assignees-count', // Pull Request @@ -46259,6 +46262,8 @@ The setup runner profile selects placement; it does not install the project's la Declare reviewed version prefixes in \`.copilot/verification-prerequisites.json\` (schemaVersion 1, runtimes such as java 17, node 24, pnpm 10). Copilot checks declared versions in the credential-free verification environment before each configured verification command; a missing or incompatible runtime blocks publication. Without this optional declaration, existing verification behavior remains and no language is inferred. Java provisioning may use \`actions/setup-java\`; \`JAVA_HOME\` and \`PATH\` survive isolation, while agent credentials and tool-option injection variables do not. Do not add an unreviewed bootstrap or weaken verification to work around a missing runtime. Read [verification prerequisites and same-job setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites). +The optional repository-owned composite action prepares each candidate before verification. Review \`.github/actions/copilot-verification-setup/action.yml\` and its assets onto trusted main, update generated workflows, then enable \`VERIFICATION_SETUP=true\` (Action input \`verification-setup\`, disabled by default). GitHub Actions executes the trusted snapshot before each command with a fresh private HOME; candidate changes cannot replace it. Native post steps finish before profile cleanup. Setup failure blocks verification and commit/push. The local CLI cannot execute composite actions and fails closed when opted in. Read [repository-owned setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup). + For the full contributor flow, read [repository collaboration](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-collaboration.mdx). For drift and recovery, read [guidance troubleshooting](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-guidance-troubleshooting.mdx). `; } @@ -47976,6 +47981,7 @@ function createDefaultSetupConfiguration() { choreTree: 'chore', issueManagedBranches: true, preBranchSdd: false, + verificationSetup: false, reopenIssueOnPush: true, desiredAssigneesCount: 1, desiredReviewersCount: 1, @@ -48243,6 +48249,7 @@ function buildSetupRepositoryVariables(configuration) { add('CHORE_TREE', repository.choreTree); add('ISSUE_MANAGED_BRANCHES', repository.issueManagedBranches); add('PRE_BRANCH_SDD', repository.preBranchSdd); + add('VERIFICATION_SETUP', repository.verificationSetup ?? false); add('REOPEN_ISSUE_ON_PUSH', repository.reopenIssueOnPush); add('DESIRED_ASSIGNEES_COUNT', repository.desiredAssigneesCount); add('DESIRED_REVIEWERS_COUNT', repository.desiredReviewersCount); @@ -48305,6 +48312,7 @@ function buildSetupActionInputs(configuration) { 'chore-tree': repository.choreTree, 'issue-managed-branches': String(repository.issueManagedBranches), 'pre-branch-sdd': String(repository.preBranchSdd), + 'verification-setup': String(repository.verificationSetup ?? false), 'reopen-issue-on-push': String(repository.reopenIssueOnPush), 'desired-assignees-count': String(repository.desiredAssigneesCount), 'desired-reviewers-count': String(repository.desiredReviewersCount), @@ -48750,6 +48758,9 @@ function validateSetupConfiguration(configuration, options = {}) { || typeof configuration.repository.preBranchSdd !== 'boolean') { errors.push('issue-managed-branches and pre-branch-sdd must be boolean values.'); } + if (configuration.repository.verificationSetup !== undefined && typeof configuration.repository.verificationSetup !== 'boolean') { + errors.push('verification-setup must be a boolean value.'); + } if (configuration.repository.preBranchSdd && !configuration.repository.issueManagedBranches) { errors.push('pre-branch-sdd requires issue-managed-branches.'); } @@ -49981,6 +49992,7 @@ const repositoryFields = [ ['HOTFIX_TREE', 'hotfixTree'], ['RELEASE_TREE', 'releaseTree'], ['DOCS_TREE', 'docsTree'], ['CHORE_TREE', 'choreTree'], ['ISSUE_MANAGED_BRANCHES', 'issueManagedBranches'], ['PRE_BRANCH_SDD', 'preBranchSdd'], + ['VERIFICATION_SETUP', 'verificationSetup'], ['REOPEN_ISSUE_ON_PUSH', 'reopenIssueOnPush'], ['DESIRED_ASSIGNEES_COUNT', 'desiredAssigneesCount'], ['DESIRED_REVIEWERS_COUNT', 'desiredReviewersCount'], ['INACTIVITY_THRESHOLD_HOURS', 'inactivityThresholdHours'], ['REPOSITORY_LOCALE', 'repositoryLocale'], ['ISSUES_LOCALE', 'issueLocale'], @@ -53376,6 +53388,58 @@ function verificationRuntimeMatches(required, observed) { } +/***/ }), + +/***/ 65326: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.VerificationSetupError = exports.VERIFICATION_SETUP_HELP = exports.VERIFICATION_SETUP_LIMITS = exports.VERIFICATION_SETUP_PATH = exports.VERIFICATION_SETUP_DIRECTORY = void 0; +exports.parseVerificationSetupResult = parseVerificationSetupResult; +exports.VERIFICATION_SETUP_DIRECTORY = '.github/actions/copilot-verification-setup'; +exports.VERIFICATION_SETUP_PATH = `${exports.VERIFICATION_SETUP_DIRECTORY}/action.yml`; +exports.VERIFICATION_SETUP_LIMITS = Object.freeze({ timeoutMs: 300000, resultBytes: 16384, scriptBytes: 65536, snapshotBytes: 262144, files: 32, requests: 20 }); +exports.VERIFICATION_SETUP_HELP = 'https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup'; +/** Closed facts only. Provider/helper output and raw exception causes never cross this boundary. */ +class VerificationSetupError extends Error { + constructor(reason, sourceSha, exitCode) { + const sha = sourceSha && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sourceSha) ? sourceSha : 'unavailable'; + const exit = Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255 ? ` Exit status: ${exitCode}.` : ''; + const action = reason === 'native-workflow-required' + ? 'Update the generated Copilot workflows and run this operation in GitHub Actions; the local CLI cannot execute composite actions.' + : `Review ${exports.VERIFICATION_SETUP_PATH} on the trusted main branch and rerun.`; + super(`Repository setup blocked verification (${reason}). ${action} Trusted source SHA: ${sha}.${exit} Verification, commit and push are withheld. Help: ${exports.VERIFICATION_SETUP_HELP}`); + this.reason = reason; + this.exitCode = exitCode; + this.name = 'VerificationSetupError'; + } +} +exports.VerificationSetupError = VerificationSetupError; +function record(value) { + return !!value && typeof value === 'object' && !Array.isArray(value); +} +function pathString(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 2048 + && ![...value].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127); +} +/** Filesystem containment is checked by the adapter, after this closed schema is parsed. */ +function parseVerificationSetupResult(value) { + if (!record(value) || value.schemaVersion !== 1 || Object.keys(value).some(key => !['schemaVersion', 'path', 'environment'].includes(key))) { + throw new VerificationSetupError('result-invalid'); + } + const paths = value.path === undefined ? [] : value.path; + const environment = value.environment === undefined ? {} : value.environment; + if (!Array.isArray(paths) || paths.length > 8 || !paths.every(pathString) || new Set(paths).size !== paths.length + || !record(environment) || Object.keys(environment).some(key => key !== 'JAVA_HOME') + || ('JAVA_HOME' in environment && !pathString(environment.JAVA_HOME))) { + throw new VerificationSetupError('result-invalid'); + } + return Object.freeze({ path: Object.freeze([...paths]), ...(environment.JAVA_HOME ? { javaHome: environment.JAVA_HOME } : {}) }); +} + + /***/ }), /***/ 43193: @@ -53588,6 +53652,18 @@ function bindSddEvidenceReadPorts(ports) { } +/***/ }), + +/***/ 26509: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DISABLED_VERIFICATION_SETUP = void 0; +exports.DISABLED_VERIFICATION_SETUP = Object.freeze({ kind: 'disabled' }); + + /***/ }), /***/ 84387: @@ -68392,6 +68468,7 @@ exports.runVerifyCommands = runVerifyCommands; const logging_ports_1 = __nccwpck_require__(6152); const verify_command_policy_1 = __nccwpck_require__(96031); const verification_runtime_policy_1 = __nccwpck_require__(89347); +const verification_setup_policy_1 = __nccwpck_require__(65326); async function runVerifyCommands(commands, execute) { for (const command of commands) { const result = await executeVerifyCommand(command, execute); @@ -68411,7 +68488,7 @@ async function executeVerifyCommand(command, execute) { : { success: false, failedCommand: formatCommandForDiagnostics(parsed) }; } catch (error) { - if (error instanceof verification_runtime_policy_1.VerificationPrerequisiteError) { + if (error instanceof verification_runtime_policy_1.VerificationPrerequisiteError || error instanceof verification_setup_policy_1.VerificationSetupError) { return { success: false, failedCommand: formatCommandForDiagnostics(parsed), error: `${error.message} Verification command: ${formatCommandForDiagnostics(parsed)}.` }; } @@ -75182,7 +75259,7 @@ const REPOSITORY_STRING_KEYS = new Set([ 'orchestrationPresentationMode', 'orchestrationCommentMode', ]); -const REPOSITORY_BOOLEAN_KEYS = new Set(['issueManagedBranches', 'preBranchSdd', 'reopenIssueOnPush', 'orchestrationDiagrams']); +const REPOSITORY_BOOLEAN_KEYS = new Set(['issueManagedBranches', 'preBranchSdd', 'verificationSetup', 'reopenIssueOnPush', 'orchestrationDiagrams']); const REPOSITORY_NUMBER_KEYS = new Set(['desiredAssigneesCount', 'desiredReviewersCount', 'inactivityThresholdHours']); const REPOSITORY_STRUCTURED_KEYS = new Set(['mergeQueueCheckAttestations']); const AI_STRING_KEYS = new Set(['ignoreFiles', 'pullRequestDescriptionMode', 'bugbotSeverity', 'bugbotFixVerifyCommands', 'bugbotEffort', 'bugbotOrganizationRules', 'provisioningMode']); @@ -79156,6 +79233,7 @@ class Execution { this.currentIssueWorkflowAdmission = components.issueWorkflowAdmission; this.currentConfiguration.issueWorkflowProfileDigest = components.issueWorkflowProfileDigest; this.preBranchSdd = components.preBranchSdd ?? false; + this.verificationSetup = components.verificationSetup ?? false; } } exports.Execution = Execution; @@ -95967,12 +96045,14 @@ function createDeploymentOrchestrationUseCase(issueDescriptionQueryPort, publica }); } const copilot_history_port_factory_1 = __nccwpck_require__(12321); -function createIssueCommentUseCaseCompositionRoot(binding) { +const verification_setup_snapshot_1 = __nccwpck_require__(62148); +const verification_setup_ports_1 = __nccwpck_require__(26509); +function createIssueCommentUseCaseCompositionRoot(binding, setup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); const findings = (0, agent_capability_composition_root_1.createFindingsQueryPort)(); const language = (0, agent_capability_composition_root_1.createLanguageQueryPort)(); const fixer = (0, agent_capability_composition_root_1.createFixerQueryPort)(); - const gitCommit = new git_commit_adapter_1.GitCommitAdapter(); + const gitCommit = new git_commit_adapter_1.GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = (0, authenticated_user_composition_root_1.createAuthenticatedUserCompositionRoot)(); const bugbotGit = new bound_bugbot_git_mutation_adapter_1.BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new update_pull_request_description_use_case_1.UpdatePullRequestDescriptionUseCase((0, lifecycle_capability_port_binding_1.bindPullRequestDescription)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding, new pull_request_changes_repository_1.PullRequestChangesRepository((0, github_pull_request_client_factory_1.createPullRequestChangesClient)())), (0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindOrganizationMembers)((0, organization_members_composition_root_1.createOrganizationMembersCompositionRoot)(), binding), (0, agent_capability_composition_root_1.createFindingsQueryPort)()); @@ -95983,12 +96063,12 @@ function createIssueCommentUseCaseCompositionRoot(binding) { catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), (0, pull_request_status_composition_root_1.createPullRequestStatusReadPorts)(binding, bugbot.scm.context), (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, sdd_diff_composition_root_1.createSddDiffQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_2.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } -function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { +function createPullRequestReviewCommentUseCaseCompositionRoot(binding, setup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); const findings = (0, agent_capability_composition_root_1.createFindingsQueryPort)(); const language = (0, agent_capability_composition_root_1.createLanguageQueryPort)(); const fixer = (0, agent_capability_composition_root_1.createFixerQueryPort)(); - const gitCommit = new git_commit_adapter_1.GitCommitAdapter(); + const gitCommit = new git_commit_adapter_1.GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = (0, authenticated_user_composition_root_1.createAuthenticatedUserCompositionRoot)(); const bugbotGit = new bound_bugbot_git_mutation_adapter_1.BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new update_pull_request_description_use_case_1.UpdatePullRequestDescriptionUseCase((0, lifecycle_capability_port_binding_1.bindPullRequestDescription)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding, new pull_request_changes_repository_1.PullRequestChangesRepository((0, github_pull_request_client_factory_1.createPullRequestChangesClient)())), (0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindOrganizationMembers)((0, organization_members_composition_root_1.createOrganizationMembersCompositionRoot)(), binding), (0, agent_capability_composition_root_1.createFindingsQueryPort)()); @@ -96015,7 +96095,7 @@ function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface, set return singleAction.invoke(execution); }, "issue-comment": async (execution) => { - issueComment ?? (issueComment = createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution))); + issueComment ?? (issueComment = createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution), (0, verification_setup_snapshot_1.captureVerificationSetup)(execution.verificationSetup, execution.branches.main))); return issueComment.invoke(execution); }, issue: async (execution) => { @@ -96023,7 +96103,7 @@ function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface, set return issue.invoke(execution); }, "pull-request-review-comment": async (execution) => { - pullRequestReviewComment ?? (pullRequestReviewComment = createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution))); + pullRequestReviewComment ?? (pullRequestReviewComment = createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution), (0, verification_setup_snapshot_1.captureVerificationSetup)(execution.verificationSetup, execution.branches.main))); return pullRequestReviewComment.invoke(execution); }, "pull-request": async (execution) => { @@ -97131,23 +97211,28 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.GitCommitAdapter = void 0; +const verification_executable_1 = __nccwpck_require__(20878); +const native_verification_profiles_1 = __nccwpck_require__(22290); const exec = __importStar(__nccwpck_require__(18538)); const git_authentication_environment_1 = __nccwpck_require__(16535); const untrusted_command_environment_1 = __nccwpck_require__(2304); const git_hook_isolation_1 = __nccwpck_require__(39764); const verification_runtime_preflight_1 = __nccwpck_require__(23100); +const verification_setup_ports_1 = __nccwpck_require__(26509); +const verification_setup_runner_1 = __nccwpck_require__(11709); class GitCommitAdapter { constructor(executeCommand = (program, args, options) => options - ? exec.exec(program, args, { + ? exec.exec(options.resolveFromEnvironment && options.env ? (0, verification_executable_1.verificationExecutable)(program, options.env) : program, args, { // Captured Git reads can contain NUL-delimited records that // hide subsequent lines in GitHub's Actions log viewer. ...(options.stdout ? { silent: true } : {}), ...(options.stdout ? { listeners: { stdout: options.stdout } } : {}), ...(options.env ? { env: options.env } : {}), }) - : exec.exec(program, args), checkPrerequisites = verification_runtime_preflight_1.assertVerificationRuntimePrerequisites) { + : exec.exec(program, args), checkPrerequisites = verification_runtime_preflight_1.assertVerificationRuntimePrerequisites, verificationSetup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { this.executeCommand = executeCommand; this.checkPrerequisites = checkPrerequisites; + this.verificationSetup = verificationSetup; } async execute(program, args, options) { if (options?.verifyRuntimePrerequisites && !options.untrusted) @@ -97158,15 +97243,19 @@ class GitCommitAdapter { throw new Error('Untrusted command execution does not accept a caller-supplied environment.'); const runtime = (0, untrusted_command_environment_1.prepareUntrustedCommandEnvironment)(); try { - if (options.verifyRuntimePrerequisites) + if (options.verifyRuntimePrerequisites) { + await (0, verification_setup_runner_1.prepareRepositoryVerification)(this.verificationSetup, runtime.environment); this.checkPrerequisites(runtime.environment); + } return await this.executeCommand(program, args, { ...(options.stdout ? { stdout: options.stdout } : {}), env: runtime.environment, + ...(options.verifyRuntimePrerequisites && this.verificationSetup.kind === 'ready' ? { resolveFromEnvironment: true } : {}), }); } finally { - runtime.cleanup(); + if (!(0, native_verification_profiles_1.nativeVerificationRetainsProfile)(runtime.environment.HOME)) + runtime.cleanup(); } } async configureAuthor(name, email) { @@ -98154,6 +98243,233 @@ class LoggerWorkflowPollingObserverAdapter { exports.LoggerWorkflowPollingObserverAdapter = LoggerWorkflowPollingObserverAdapter; +/***/ }), + +/***/ 18883: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationCandidateIdentity = verificationCandidateIdentity; +const node_child_process_1 = __nccwpck_require__(17718); +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +/** Bind setup to the exact HEAD, branch, index, tracked edits and publishable untracked files. */ +function verificationCandidateIdentity(workspace, environment) { + const hash = (0, node_crypto_1.createHash)('sha256'); + const git = (...args) => { + const result = (0, node_child_process_1.spawnSync)('git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, shell: false, + windowsHide: true, timeout: 10000, maxBuffer: 16 * 1024 * 1024 }); + if (result.error || result.status !== 0) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update(result.stdout).update('\0'); + return result.stdout; + }; + git('rev-parse', '--verify', 'HEAD'); + git('rev-parse', '--symbolic-full-name', 'HEAD'); + git('ls-files', '--stage', '-z'); + git('diff', '--no-ext-diff', '--no-textconv', '--binary', 'HEAD', '--'); + const paths = git('ls-files', '--others', '--exclude-standard', '-z').toString('utf8').split('\0').filter(Boolean); + let total = 0; + if (paths.length > 256) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + for (const path of paths) { + const file = (0, node_path_1.join)(workspace, path); + const stat = (0, node_fs_1.lstatSync)(file); + total += stat.size; + if (total > 16 * 1024 * 1024) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update(path).update('\0'); + if (stat.isSymbolicLink()) + hash.update('120000\0').update((0, node_fs_1.readlinkSync)(file)); + else if (stat.isFile()) + hash.update(stat.mode & 0o100 ? '100755\0' : '100644\0').update((0, node_fs_1.readFileSync)(file)); + else + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update('\0'); + } + return hash.digest('hex'); +} + + +/***/ }), + +/***/ 22290: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.nativeVerificationProfilePaths = nativeVerificationProfilePaths; +exports.registerNativeVerificationProfile = registerNativeVerificationProfile; +exports.nativeVerificationRetainsProfile = nativeVerificationRetainsProfile; +exports.removeNativeVerificationProfiles = removeNativeVerificationProfiles; +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const native_verification_protocol_1 = __nccwpck_require__(88709); +function nativeVerificationProfilePaths(root) { + const stored = (0, native_verification_protocol_1.readNativeRecord)(root, 'profiles.json', 65536); + const paths = stored === undefined ? [] : stored; + if (!Array.isArray(paths) || paths.length > 20 || new Set(paths).size !== paths.length || paths.some(path => typeof path !== 'string' + || (0, node_path_1.dirname)(path) !== (0, node_path_1.dirname)(root) || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(path)) || path === root)) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + return paths; +} +/** Native actions may have post steps. Preserve dependency-only profiles until those steps finish. */ +function registerNativeVerificationProfile(root, profile) { + const previous = nativeVerificationProfilePaths(root); + if (previous.length >= 20 || previous.includes(profile) || (0, node_path_1.dirname)(profile) !== (0, node_path_1.dirname)(root) + || profile === root || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(profile)) + || (0, node_fs_1.realpathSync)(profile) !== profile || !(0, node_fs_1.lstatSync)(profile).isDirectory() || (0, node_fs_1.lstatSync)(profile).isSymbolicLink()) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.writeFileSync)((0, node_path_1.join)(profile, 'native-session-owner'), (0, node_crypto_1.createHash)('sha256').update(root).digest('hex'), { mode: 0o600, flag: 'wx' }); + (0, native_verification_protocol_1.writeNativeRecord)(root, 'profiles.json', [...previous, profile]); +} +function nativeVerificationRetainsProfile(profile) { + const root = process.env.COPILOT_NATIVE_WORKER_SESSION; + return !!root && nativeVerificationProfilePaths(root).includes(profile); +} +function removeNativeVerificationProfiles(root) { + for (const path of nativeVerificationProfilePaths(root)) { + let stat; + try { + stat = (0, node_fs_1.lstatSync)(path); + } + catch (error) { + if (error.code === 'ENOENT') + continue; + throw error; + } + if (!stat.isDirectory() || stat.isSymbolicLink() || (0, node_fs_1.realpathSync)(path) !== path || (0, node_fs_1.readFileSync)((0, node_path_1.join)(path, 'native-session-owner'), 'utf8') !== (0, node_crypto_1.createHash)('sha256').update(root).digest('hex')) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.rmSync)(path, { recursive: true, force: true }); + } +} + + +/***/ }), + +/***/ 88709: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.nativePause = void 0; +exports.writeNativeRecord = writeNativeRecord; +exports.readNativeRecord = readNativeRecord; +exports.nativeSession = nativeSession; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const node_crypto_1 = __nccwpck_require__(6005); +const verification_setup_policy_1 = __nccwpck_require__(65326); +function writeNativeRecord(root, name, value) { + const temporary = (0, node_path_1.join)(root, `${(0, node_crypto_1.randomUUID)()}.tmp`); + (0, node_fs_1.writeFileSync)(temporary, JSON.stringify(value), { mode: 0o600, flag: 'wx' }); + (0, node_fs_1.renameSync)(temporary, (0, node_path_1.join)(root, name)); +} +function readNativeRecord(root, name, limit = 16384) { + const file = (0, node_path_1.join)(root, name); + let descriptor; + try { + const before = (0, node_fs_1.lstatSync)(file, { bigint: true }); + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(limit)) + throw new Error(); + descriptor = (0, node_fs_1.openSync)(file, node_fs_1.constants.O_RDONLY | (node_fs_1.constants.O_NOFOLLOW ?? 0)); + const after = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || after.mtimeNs !== before.mtimeNs) + throw new Error(); + const data = (0, node_fs_1.readFileSync)(descriptor); + const final = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (data.length > limit || final.size !== after.size || final.mtimeNs !== after.mtimeNs) + throw new Error(); + return JSON.parse(data.toString('utf8')); + } + catch (error) { + if (error.code === 'ENOENT') + return undefined; + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + } + finally { + if (descriptor !== undefined) + (0, node_fs_1.closeSync)(descriptor); + } +} +function nativeSession(root, environment = process.env) { + if (!/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(root)) || (0, node_fs_1.realpathSync)(root) !== root + || !(0, node_fs_1.lstatSync)(root).isDirectory() || (0, node_fs_1.lstatSync)(root).isSymbolicLink()) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + if (process.platform !== 'win32' && ((0, node_fs_1.lstatSync)(root).mode & 0o077) !== 0) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + const session = readNativeRecord(root, 'session.json'); + if (session?.schemaVersion !== 1 || session.workspace !== (0, node_fs_1.realpathSync)(process.cwd()) + || session.run !== (environment.GITHUB_RUN_ID ?? '') || session.attempt !== (environment.GITHUB_RUN_ATTEMPT ?? '') + || session.job !== (environment.GITHUB_JOB ?? '') || !Number.isSafeInteger(session.pid) || session.pid <= 0) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + return session; +} +const nativePause = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds)); +exports.nativePause = nativePause; + + +/***/ }), + +/***/ 63587: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.NATIVE_VERIFICATION_ACTION = void 0; +exports.stageNativeVerificationAction = stageNativeVerificationAction; +exports.cleanupNativeVerificationAction = cleanupNativeVerificationAction; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const native_verification_protocol_1 = __nccwpck_require__(88709); +exports.NATIVE_VERIFICATION_ACTION = '.git/copilot-verification-setup'; +function targetPath(workspace) { + const metadata = (0, node_path_1.join)(workspace, '.git'); + const stat = (0, node_fs_1.lstatSync)(metadata); + if (!stat.isDirectory() || stat.isSymbolicLink() || (0, node_fs_1.realpathSync)(metadata) !== metadata) + throw new verification_setup_policy_1.VerificationSetupError('native-workflow-required'); + return (0, node_path_1.join)(workspace, exports.NATIVE_VERIFICATION_ACTION); +} +/** Native composite metadata must remain available until its deferred post steps have finished. */ +function stageNativeVerificationAction(setup, session) { + const target = targetPath(setup.workspace); + const record = (0, native_verification_protocol_1.readNativeRecord)(session, 'staging.json'); + if (record && record.target !== target || !record && !!(0, node_fs_1.lstatSync)(target, { throwIfNoEntry: false })) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'staging.json', { target }); + (0, node_fs_1.rmSync)(target, { recursive: true, force: true }); + (0, node_fs_1.mkdirSync)(target, { mode: 0o700 }); + for (const file of setup.files) { + const destination = (0, node_path_1.join)(target, file.path); + const remainder = (0, node_path_1.relative)(target, destination); + if (!remainder || remainder.startsWith(`..${node_path_1.sep}`) || remainder === '..') + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(destination), { recursive: true, mode: 0o700 }); + (0, node_fs_1.writeFileSync)(destination, Buffer.from(file.base64, 'base64'), { mode: file.executable ? 0o700 : 0o600, flag: 'wx' }); + } +} +function cleanupNativeVerificationAction(session, workspace) { + const record = (0, native_verification_protocol_1.readNativeRecord)(session, 'staging.json'); + if (!record) + return; + const target = targetPath(workspace); + if (record.target !== target) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.rmSync)(target, { recursive: true, force: true }); + (0, node_fs_1.rmSync)((0, node_path_1.join)(session, 'staging.json'), { force: true }); +} + + /***/ }), /***/ 35849: @@ -102672,7 +102988,7 @@ const ALLOWED_VARIABLES = [ * credentials or paths to local agent authentication stores. */ function prepareUntrustedCommandEnvironment(source = process.env, platform = process.platform) { - const runtimeHome = (0, agent_private_install_root_1.createPrivateVerificationRuntimeRoot)(); + const runtimeHome = (0, node_fs_1.realpathSync)((0, agent_private_install_root_1.createPrivateVerificationRuntimeRoot)()); const environment = { HOME: runtimeHome }; for (const variable of ALLOWED_VARIABLES) { const value = source[variable]; @@ -102700,6 +103016,39 @@ function prepareUntrustedCommandEnvironment(source = process.env, platform = pro } +/***/ }), + +/***/ 20878: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationExecutable = verificationExecutable; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +/** @actions/exec resolves basenames against its own process PATH; native tools belong to the verifier PATH. */ +function verificationExecutable(program, environment, platform = process.platform) { + const explicit = (0, node_path_1.isAbsolute)(program) || /[\\/]/u.test(program); + const candidates = explicit ? [(0, node_path_1.resolve)(program)] : (environment.PATH ?? '').split(node_path_1.delimiter).filter(Boolean).map(directory => (0, node_path_1.resolve)((0, node_path_1.join)(directory, program))); + const windowsExtensions = (environment.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean); + const hasWindowsExtension = windowsExtensions.some(extension => program.toLowerCase().endsWith(extension.toLowerCase())); + const extensions = platform === 'win32' && !hasWindowsExtension ? windowsExtensions : ['']; + for (const candidate of candidates) + for (const extension of extensions) { + const file = candidate + extension; + try { + if ((0, node_fs_1.statSync)(file).isFile()) { + (0, node_fs_1.accessSync)(file, node_fs_1.constants.X_OK); + return file; + } + } + catch { /* Try only the next executable in the isolated PATH. */ } + } + throw new Error('Verification executable is unavailable in the isolated PATH.'); +} + + /***/ }), /***/ 91053: @@ -102826,6 +103175,264 @@ function checkRuntime(requirement, environment, run, platform, job) { } +/***/ }), + +/***/ 23358: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.applyVerificationSetupResult = applyVerificationSetupResult; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +function contained(root, path) { + const remainder = (0, node_path_1.relative)(root, path); + return remainder === '' || (remainder !== '..' && !remainder.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(remainder)); +} +/** Accept only existing contained directories, checking both lexical paths and symlink targets. */ +function approvedDirectory(value, roots) { + if (!(0, node_path_1.isAbsolute)(value) || value.includes(node_path_1.delimiter) || value.split(/[\\/]/u).includes('..') + || !roots.some(root => contained(root, value))) + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + const actual = (0, node_fs_1.realpathSync)(value); + if (!roots.some(root => contained(root, actual)) || !(0, node_fs_1.lstatSync)(actual).isDirectory()) + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + return actual; +} +function applyVerificationSetupResult(file, environment, roots) { + let descriptor; + try { + let before; + try { + before = (0, node_fs_1.lstatSync)(file, { bigint: true }); + } + catch (error) { + if (error.code === 'ENOENT') + return; + throw error; + } + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes)) + throw new Error(); + descriptor = (0, node_fs_1.openSync)(file, node_fs_1.constants.O_RDONLY | (node_fs_1.constants.O_NOFOLLOW ?? 0)); + const opened = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (!opened.isFile() || opened.nlink !== 1n || opened.dev !== before.dev || opened.ino !== before.ino + || opened.size !== before.size || opened.mtimeNs !== before.mtimeNs) + throw new Error(); + const bytes = Buffer.alloc(verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes + 1); + const size = (0, node_fs_1.readSync)(descriptor, bytes, 0, bytes.length, 0); + if (size > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes) + throw new Error(); + const after = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (after.size !== opened.size || after.mtimeNs !== opened.mtimeNs) + throw new Error(); + const result = (0, verification_setup_policy_1.parseVerificationSetupResult)(JSON.parse(bytes.subarray(0, size).toString('utf8'))); + const paths = result.path.map(value => approvedDirectory(value, roots)); + const javaHome = result.javaHome ? approvedDirectory(result.javaHome, roots) : undefined; + if (paths.length) + environment.PATH = [...paths, ...(environment.PATH ? [environment.PATH] : [])].join(node_path_1.delimiter); + if (javaHome) + environment.JAVA_HOME = javaHome; + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + } + finally { + if (descriptor !== undefined) + (0, node_fs_1.closeSync)(descriptor); + } +} + + +/***/ }), + +/***/ 11709: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prepareRepositoryVerification = prepareRepositoryVerification; +const verification_setup_snapshot_1 = __nccwpck_require__(62148); +const native_verification_profiles_1 = __nccwpck_require__(22290); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const node_crypto_1 = __nccwpck_require__(6005); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const verification_setup_result_1 = __nccwpck_require__(23358); +const logging_ports_1 = __nccwpck_require__(6152); +const native_verification_protocol_1 = __nccwpck_require__(88709); +const native_verification_staging_1 = __nccwpck_require__(63587); +const native_verification_candidate_1 = __nccwpck_require__(18883); +/** The runner executes YAML. This adapter pauses the admitted call stack at its verification boundary. */ +async function prepareRepositoryVerification(setup, environment) { + if (setup.kind === 'disabled') + return; + if (setup.kind === 'blocked') + throw new verification_setup_policy_1.VerificationSetupError(setup.reason, setup.sourceSha); + const session = process.env.COPILOT_NATIVE_WORKER_SESSION; + if (!session) + throw new verification_setup_policy_1.VerificationSetupError('native-workflow-required', setup.sourceSha); + try { + (0, native_verification_protocol_1.nativeSession)(session); + const requests = (0, native_verification_protocol_1.readNativeRecord)(session, 'request-count.json')?.count ?? 0; + if (requests >= verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.requests) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'request-count.json', { count: requests + 1 }); + if ((0, node_fs_1.realpathSync)(process.cwd()) !== setup.workspace) + throw new verification_setup_policy_1.VerificationSetupError('workspace-changed'); + (0, native_verification_profiles_1.registerNativeVerificationProfile)(session, environment.HOME); + const identity = (0, native_verification_candidate_1.verificationCandidateIdentity)(setup.workspace, environment); + (0, native_verification_staging_1.stageNativeVerificationAction)(setup, session); + const id = (0, node_crypto_1.randomUUID)(); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'request.json', { id, sourceSha: setup.sourceSha, digest: (0, verification_setup_snapshot_1.verificationSetupDigest)(setup), environment }); + (0, logging_ports_1.logInfo)(`Preparing repository verification (${verification_setup_policy_1.VERIFICATION_SETUP_PATH}; trusted source SHA: ${setup.sourceSha}).`); + const deadline = Date.now() + verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.timeoutMs; + let response; + while (!(response = (0, native_verification_protocol_1.readNativeRecord)(session, `response-${id}.json`))) { + if ((0, native_verification_protocol_1.readNativeRecord)(session, 'cancel.json')) + throw new verification_setup_policy_1.VerificationSetupError('cancelled'); + if (Date.now() >= deadline) + throw new verification_setup_policy_1.VerificationSetupError('timeout'); + await (0, native_verification_protocol_1.nativePause)(100); + } + if (response.id !== id || response.success !== true) + throw new verification_setup_policy_1.VerificationSetupError('process-failed'); + if ((0, native_verification_candidate_1.verificationCandidateIdentity)(setup.workspace, environment) !== identity) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + if (typeof response.path !== 'string' || response.path.length > 16384 || response.javaHome !== undefined && typeof response.javaHome !== 'string') + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + const original = new Set((environment.PATH ?? '').split(node_path_1.delimiter)); + const previousProfiles = (0, native_verification_profiles_1.nativeVerificationProfilePaths)(session).filter(profile => profile !== environment.HOME); + const fromPreviousProfile = (path) => previousProfiles.some(profile => { + const part = (0, node_path_1.relative)(profile, path); + return part === '' || part !== '..' && !part.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(part); + }); + const additions = [...new Set(response.path.split(node_path_1.delimiter).filter(path => path && !original.has(path) && !fromPreviousProfile(path)))]; + const result = (0, node_path_1.join)(environment.HOME, 'native-setup-result.json'); + (0, node_fs_1.writeFileSync)(result, JSON.stringify({ schemaVersion: 1, path: additions, + environment: response.javaHome && response.javaHome !== environment.JAVA_HOME ? { JAVA_HOME: response.javaHome } : {} }), { mode: 0o600, flag: 'wx' }); + (0, verification_setup_result_1.applyVerificationSetupResult)(result, environment, [setup.workspace, environment.HOME, ...(setup.toolRoot ? [setup.toolRoot] : [])]); + (0, logging_ports_1.logInfo)('Repository setup completed; runtime preflight and verification may proceed.'); + } + catch (error) { + throw new verification_setup_policy_1.VerificationSetupError(error instanceof verification_setup_policy_1.VerificationSetupError ? error.reason : 'handoff-invalid', setup.sourceSha); + } + finally { + (0, node_fs_1.rmSync)((0, node_path_1.join)(session, 'request.json'), { force: true }); + } +} + + +/***/ }), + +/***/ 62148: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationSetupDigest = verificationSetupDigest; +exports.captureVerificationSetup = captureVerificationSetup; +exports.captureVerificationSetupAtCommit = captureVerificationSetupAtCommit; +const node_path_1 = __nccwpck_require__(49411); +const node_child_process_1 = __nccwpck_require__(17718); +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const js_yaml_1 = __nccwpck_require__(783); +const verification_setup_ports_1 = __nccwpck_require__(26509); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const untrusted_command_environment_1 = __nccwpck_require__(2304); +/** Only immutable Git objects are read. Errors remain deferred on non-verifying routes. */ +function verificationSetupDigest(setup) { + return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(setup.files)).digest('hex'); +} +function captureVerificationSetup(enabled, mainBranch, root = process.cwd()) { + return capture(enabled, mainBranch, root); +} +/** Post steps use the already pinned commit from runner state, never a mutable branch ref. */ +function captureVerificationSetupAtCommit(commit, root = process.cwd(), gitExecutable) { + return capture(true, '', root, commit, gitExecutable); +} +function capture(enabled, mainBranch, root, pinned, gitExecutable) { + if (!enabled) + return verification_setup_ports_1.DISABLED_VERIFICATION_SETUP; + let sourceSha; + let runtime; + let snapshot; + try { + runtime = (0, untrusted_command_environment_1.prepareUntrustedCommandEnvironment)(); + const environment = runtime.environment; + if (!pinned && (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,200}$/u.test(mainBranch) || mainBranch.includes('..') + || mainBranch.includes('//') || mainBranch.endsWith('/') || mainBranch.endsWith('.lock'))) + throw new Error(); + if (gitExecutable !== undefined && !(0, node_path_1.isAbsolute)(gitExecutable)) + throw new Error(); + const workspace = (0, node_fs_1.realpathSync)(root); + const git = (...args) => { + const result = (0, node_child_process_1.spawnSync)(gitExecutable ?? 'git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, + shell: false, windowsHide: true, timeout: 5000, maxBuffer: verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.snapshotBytes + 4096 }); + if (result.error || result.status !== 0) + throw new Error(); + return result.stdout; + }; + const sha = pinned ?? git('rev-parse', '--verify', `refs/remotes/origin/${mainBranch}^{commit}`).toString('utf8').trim(); + if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sha)) + throw new Error(); + sourceSha = sha; + const entries = git('ls-tree', '-rz', sourceSha, '--', verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY).toString('utf8').split('\0').filter(Boolean); + if (!entries.length || entries.length > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.files) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + let total = 0; + const files = entries.map(entry => { + const match = /^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t(.+)$/u.exec(entry); + if (!match || !match[3].startsWith(`${verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY}/`)) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const path = match[3].slice(verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY.length + 1); + if (!/^[A-Za-z0-9_./-]{1,200}$/u.test(path) || path.split('/').some(part => !part || part === '.' || part === '..' || part.endsWith('.') || /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part))) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const bytes = git('cat-file', 'blob', match[2]); + total += bytes.length; + const digest = (0, node_crypto_1.createHash)(match[2].length === 40 ? 'sha1' : 'sha256').update(`blob ${bytes.length}\0`).update(bytes).digest('hex'); + if (bytes.length > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.scriptBytes || total > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.snapshotBytes || digest !== match[2]) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + return Object.freeze({ path, base64: bytes.toString('base64'), executable: match[1] === '100755' }); + }); + const manifest = files.find(file => file.path === 'action.yml'); + if (!manifest) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const bytes = Buffer.from(manifest.base64, 'base64'); + const text = bytes.toString('utf8'); + if (!bytes.length || !bytes.equals(Buffer.from(text)) || text.includes('\0')) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + let action; + try { + action = (0, js_yaml_1.load)(text); + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + } + if (action?.runs?.using !== 'composite' || !Array.isArray(action.runs.steps) || !action.runs.steps.length || action.runs.steps.length > 32) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + if (action.runs.steps.some(step => !step || typeof step !== 'object' || 'uses' in step && typeof step.uses === 'string' && step.uses.startsWith('./'))) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const toolRoot = environment.RUNNER_TOOL_CACHE ? (0, node_fs_1.realpathSync)(environment.RUNNER_TOOL_CACHE) : undefined; + snapshot = Object.freeze({ kind: 'ready', files: Object.freeze(files), sourceSha, workspace, ...(toolRoot ? { toolRoot } : {}) }); + } + catch (error) { + snapshot = Object.freeze({ kind: 'blocked', reason: error instanceof verification_setup_policy_1.VerificationSetupError ? error.reason : 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); + } + try { + runtime?.cleanup(); + } + catch { + snapshot = Object.freeze({ kind: 'blocked', reason: 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); + } + return snapshot; +} + + /***/ }), /***/ 92540: diff --git a/build/github_action/index.js b/build/github_action/index.js index c11de64b9..5a70afc29 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -36897,6 +36897,7 @@ var __importStar = (this && this.__importStar) || (function () { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.runGitHubAction = runGitHubAction; exports.runGitHubActionEntry = runGitHubActionEntry; +const native_verification_controller_1 = __nccwpck_require__(71046); const issue_completion_composition_root_1 = __nccwpck_require__(63719); const core = __importStar(__nccwpck_require__(75855)); const github = __importStar(__nccwpck_require__(78227)); @@ -37106,7 +37107,7 @@ function earlyGithubActionErrorMessage() { // Only auto-run when executed as the action entry (not when imported by tests) /* istanbul ignore next -- the bundled production entry is covered by the Action smoke path. */ if (typeof process.env.JEST_WORKER_ID === 'undefined') { - void runGitHubActionEntry(); + void runGitHubActionEntry(() => (0, native_verification_controller_1.runNativeVerificationEntry)(runGitHubActionEntry)); } @@ -37531,6 +37532,7 @@ async function buildGithubActionExecution(input) { commitPrefixBuilder: getCommitPrefixBuilder(getInput), issue: (0, configuration_builders_1.buildIssue)((0, input_boolean_policy_1.parseIssueWorkflowBoolean)(getInput(input_keys_1.INPUT_KEYS.ISSUE_MANAGED_BRANCHES), input_keys_1.INPUT_KEYS.ISSUE_MANAGED_BRANCHES, true), (0, input_boolean_policy_1.isEnabledInput)(getInput(input_keys_1.INPUT_KEYS.REOPEN_ISSUE_ON_PUSH)), (0, input_number_policy_1.parseIntegerInput)(getInput(input_keys_1.INPUT_KEYS.DESIRED_ASSIGNEES_COUNT), 0), eventInputs), preBranchSdd: (0, input_boolean_policy_1.parseIssueWorkflowBoolean)(getInput(input_keys_1.INPUT_KEYS.PRE_BRANCH_SDD), input_keys_1.INPUT_KEYS.PRE_BRANCH_SDD, false), + verificationSetup: (0, input_boolean_policy_1.parseIssueWorkflowBoolean)(getInput(input_keys_1.INPUT_KEYS.VERIFICATION_SETUP), input_keys_1.INPUT_KEYS.VERIFICATION_SETUP, false), pullRequest: (0, configuration_builders_1.buildPullRequest)((0, input_number_policy_1.parseIntegerInput)(getInput(input_keys_1.INPUT_KEYS.PULL_REQUEST_DESIRED_ASSIGNEES_COUNT), 0), (0, input_number_policy_1.parseIntegerInput)(getInput(input_keys_1.INPUT_KEYS.PULL_REQUEST_DESIRED_REVIEWERS_COUNT), 0), eventInputs), emoji: (0, configuration_builders_1.buildEmoji)(getInput(input_keys_1.INPUT_KEYS.EMOJI_LABELED_TITLE) === 'true', getInput(input_keys_1.INPUT_KEYS.BRANCH_MANAGEMENT_EMOJI)), tokens: (0, configuration_builders_1.buildTokens)(token), @@ -38746,6 +38748,7 @@ exports.INPUT_KEYS = { // Issue ISSUE_MANAGED_BRANCHES: 'issue-managed-branches', PRE_BRANCH_SDD: 'pre-branch-sdd', + VERIFICATION_SETUP: 'verification-setup', REOPEN_ISSUE_ON_PUSH: 'reopen-issue-on-push', DESIRED_ASSIGNEES_COUNT: 'desired-assignees-count', // Pull Request @@ -47801,6 +47804,8 @@ The setup runner profile selects placement; it does not install the project's la Declare reviewed version prefixes in \`.copilot/verification-prerequisites.json\` (schemaVersion 1, runtimes such as java 17, node 24, pnpm 10). Copilot checks declared versions in the credential-free verification environment before each configured verification command; a missing or incompatible runtime blocks publication. Without this optional declaration, existing verification behavior remains and no language is inferred. Java provisioning may use \`actions/setup-java\`; \`JAVA_HOME\` and \`PATH\` survive isolation, while agent credentials and tool-option injection variables do not. Do not add an unreviewed bootstrap or weaken verification to work around a missing runtime. Read [verification prerequisites and same-job setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites). +The optional repository-owned composite action prepares each candidate before verification. Review \`.github/actions/copilot-verification-setup/action.yml\` and its assets onto trusted main, update generated workflows, then enable \`VERIFICATION_SETUP=true\` (Action input \`verification-setup\`, disabled by default). GitHub Actions executes the trusted snapshot before each command with a fresh private HOME; candidate changes cannot replace it. Native post steps finish before profile cleanup. Setup failure blocks verification and commit/push. The local CLI cannot execute composite actions and fails closed when opted in. Read [repository-owned setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup). + For the full contributor flow, read [repository collaboration](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-collaboration.mdx). For drift and recovery, read [guidance troubleshooting](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-guidance-troubleshooting.mdx). `; } @@ -49216,6 +49221,7 @@ function createDefaultSetupConfiguration() { choreTree: 'chore', issueManagedBranches: true, preBranchSdd: false, + verificationSetup: false, reopenIssueOnPush: true, desiredAssigneesCount: 1, desiredReviewersCount: 1, @@ -49483,6 +49489,7 @@ function buildSetupRepositoryVariables(configuration) { add('CHORE_TREE', repository.choreTree); add('ISSUE_MANAGED_BRANCHES', repository.issueManagedBranches); add('PRE_BRANCH_SDD', repository.preBranchSdd); + add('VERIFICATION_SETUP', repository.verificationSetup ?? false); add('REOPEN_ISSUE_ON_PUSH', repository.reopenIssueOnPush); add('DESIRED_ASSIGNEES_COUNT', repository.desiredAssigneesCount); add('DESIRED_REVIEWERS_COUNT', repository.desiredReviewersCount); @@ -49545,6 +49552,7 @@ function buildSetupActionInputs(configuration) { 'chore-tree': repository.choreTree, 'issue-managed-branches': String(repository.issueManagedBranches), 'pre-branch-sdd': String(repository.preBranchSdd), + 'verification-setup': String(repository.verificationSetup ?? false), 'reopen-issue-on-push': String(repository.reopenIssueOnPush), 'desired-assignees-count': String(repository.desiredAssigneesCount), 'desired-reviewers-count': String(repository.desiredReviewersCount), @@ -49990,6 +49998,9 @@ function validateSetupConfiguration(configuration, options = {}) { || typeof configuration.repository.preBranchSdd !== 'boolean') { errors.push('issue-managed-branches and pre-branch-sdd must be boolean values.'); } + if (configuration.repository.verificationSetup !== undefined && typeof configuration.repository.verificationSetup !== 'boolean') { + errors.push('verification-setup must be a boolean value.'); + } if (configuration.repository.preBranchSdd && !configuration.repository.issueManagedBranches) { errors.push('pre-branch-sdd requires issue-managed-branches.'); } @@ -50392,6 +50403,7 @@ const repositoryFields = [ ['HOTFIX_TREE', 'hotfixTree'], ['RELEASE_TREE', 'releaseTree'], ['DOCS_TREE', 'docsTree'], ['CHORE_TREE', 'choreTree'], ['ISSUE_MANAGED_BRANCHES', 'issueManagedBranches'], ['PRE_BRANCH_SDD', 'preBranchSdd'], + ['VERIFICATION_SETUP', 'verificationSetup'], ['REOPEN_ISSUE_ON_PUSH', 'reopenIssueOnPush'], ['DESIRED_ASSIGNEES_COUNT', 'desiredAssigneesCount'], ['DESIRED_REVIEWERS_COUNT', 'desiredReviewersCount'], ['INACTIVITY_THRESHOLD_HOURS', 'inactivityThresholdHours'], ['REPOSITORY_LOCALE', 'repositoryLocale'], ['ISSUES_LOCALE', 'issueLocale'], @@ -51122,6 +51134,58 @@ function verificationRuntimeMatches(required, observed) { } +/***/ }), + +/***/ 65326: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.VerificationSetupError = exports.VERIFICATION_SETUP_HELP = exports.VERIFICATION_SETUP_LIMITS = exports.VERIFICATION_SETUP_PATH = exports.VERIFICATION_SETUP_DIRECTORY = void 0; +exports.parseVerificationSetupResult = parseVerificationSetupResult; +exports.VERIFICATION_SETUP_DIRECTORY = '.github/actions/copilot-verification-setup'; +exports.VERIFICATION_SETUP_PATH = `${exports.VERIFICATION_SETUP_DIRECTORY}/action.yml`; +exports.VERIFICATION_SETUP_LIMITS = Object.freeze({ timeoutMs: 300000, resultBytes: 16384, scriptBytes: 65536, snapshotBytes: 262144, files: 32, requests: 20 }); +exports.VERIFICATION_SETUP_HELP = 'https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup'; +/** Closed facts only. Provider/helper output and raw exception causes never cross this boundary. */ +class VerificationSetupError extends Error { + constructor(reason, sourceSha, exitCode) { + const sha = sourceSha && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sourceSha) ? sourceSha : 'unavailable'; + const exit = Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255 ? ` Exit status: ${exitCode}.` : ''; + const action = reason === 'native-workflow-required' + ? 'Update the generated Copilot workflows and run this operation in GitHub Actions; the local CLI cannot execute composite actions.' + : `Review ${exports.VERIFICATION_SETUP_PATH} on the trusted main branch and rerun.`; + super(`Repository setup blocked verification (${reason}). ${action} Trusted source SHA: ${sha}.${exit} Verification, commit and push are withheld. Help: ${exports.VERIFICATION_SETUP_HELP}`); + this.reason = reason; + this.exitCode = exitCode; + this.name = 'VerificationSetupError'; + } +} +exports.VerificationSetupError = VerificationSetupError; +function record(value) { + return !!value && typeof value === 'object' && !Array.isArray(value); +} +function pathString(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 2048 + && ![...value].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127); +} +/** Filesystem containment is checked by the adapter, after this closed schema is parsed. */ +function parseVerificationSetupResult(value) { + if (!record(value) || value.schemaVersion !== 1 || Object.keys(value).some(key => !['schemaVersion', 'path', 'environment'].includes(key))) { + throw new VerificationSetupError('result-invalid'); + } + const paths = value.path === undefined ? [] : value.path; + const environment = value.environment === undefined ? {} : value.environment; + if (!Array.isArray(paths) || paths.length > 8 || !paths.every(pathString) || new Set(paths).size !== paths.length + || !record(environment) || Object.keys(environment).some(key => key !== 'JAVA_HOME') + || ('JAVA_HOME' in environment && !pathString(environment.JAVA_HOME))) { + throw new VerificationSetupError('result-invalid'); + } + return Object.freeze({ path: Object.freeze([...paths]), ...(environment.JAVA_HOME ? { javaHome: environment.JAVA_HOME } : {}) }); +} + + /***/ }), /***/ 43193: @@ -51334,6 +51398,18 @@ function bindSddEvidenceReadPorts(ports) { } +/***/ }), + +/***/ 26509: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DISABLED_VERIFICATION_SETUP = void 0; +exports.DISABLED_VERIFICATION_SETUP = Object.freeze({ kind: 'disabled' }); + + /***/ }), /***/ 84387: @@ -63621,6 +63697,7 @@ exports.runVerifyCommands = runVerifyCommands; const logging_ports_1 = __nccwpck_require__(6152); const verify_command_policy_1 = __nccwpck_require__(96031); const verification_runtime_policy_1 = __nccwpck_require__(89347); +const verification_setup_policy_1 = __nccwpck_require__(65326); async function runVerifyCommands(commands, execute) { for (const command of commands) { const result = await executeVerifyCommand(command, execute); @@ -63640,7 +63717,7 @@ async function executeVerifyCommand(command, execute) { : { success: false, failedCommand: formatCommandForDiagnostics(parsed) }; } catch (error) { - if (error instanceof verification_runtime_policy_1.VerificationPrerequisiteError) { + if (error instanceof verification_runtime_policy_1.VerificationPrerequisiteError || error instanceof verification_setup_policy_1.VerificationSetupError) { return { success: false, failedCommand: formatCommandForDiagnostics(parsed), error: `${error.message} Verification command: ${formatCommandForDiagnostics(parsed)}.` }; } @@ -69580,6 +69657,7 @@ class Execution { this.currentIssueWorkflowAdmission = components.issueWorkflowAdmission; this.currentConfiguration.issueWorkflowProfileDigest = components.issueWorkflowProfileDigest; this.preBranchSdd = components.preBranchSdd ?? false; + this.verificationSetup = components.verificationSetup ?? false; } } exports.Execution = Execution; @@ -86527,12 +86605,14 @@ function createDeploymentOrchestrationUseCase(issueDescriptionQueryPort, publica }); } const copilot_history_port_factory_1 = __nccwpck_require__(12321); -function createIssueCommentUseCaseCompositionRoot(binding) { +const verification_setup_snapshot_1 = __nccwpck_require__(62148); +const verification_setup_ports_1 = __nccwpck_require__(26509); +function createIssueCommentUseCaseCompositionRoot(binding, setup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); const findings = (0, agent_capability_composition_root_1.createFindingsQueryPort)(); const language = (0, agent_capability_composition_root_1.createLanguageQueryPort)(); const fixer = (0, agent_capability_composition_root_1.createFixerQueryPort)(); - const gitCommit = new git_commit_adapter_1.GitCommitAdapter(); + const gitCommit = new git_commit_adapter_1.GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = (0, authenticated_user_composition_root_1.createAuthenticatedUserCompositionRoot)(); const bugbotGit = new bound_bugbot_git_mutation_adapter_1.BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new update_pull_request_description_use_case_1.UpdatePullRequestDescriptionUseCase((0, lifecycle_capability_port_binding_1.bindPullRequestDescription)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding, new pull_request_changes_repository_1.PullRequestChangesRepository((0, github_pull_request_client_factory_1.createPullRequestChangesClient)())), (0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindOrganizationMembers)((0, organization_members_composition_root_1.createOrganizationMembersCompositionRoot)(), binding), (0, agent_capability_composition_root_1.createFindingsQueryPort)()); @@ -86543,12 +86623,12 @@ function createIssueCommentUseCaseCompositionRoot(binding) { catalogResolver: new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language), }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry, new resolve_message_catalog_use_case_1.ResolveMessageCatalogUseCase(language)), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync, (0, issue_use_case_composition_root_1.createIssueUseCaseCompositionRoot)(binding), rememberBranchPorts(binding, bugbot.scm.context), (0, pull_request_status_composition_root_1.createPullRequestStatusReadPorts)(binding, bugbot.scm.context), (0, copilot_history_port_factory_1.createCopilotHistoryReadPort)(bugbot.scm.context, process.env.GITHUB_SERVER_URL ?? 'https://github.com'), (0, sdd_evidence_composition_root_1.createSddEvidenceQueryPort)(binding, bugbot.scm.context), (0, sdd_diff_composition_root_1.createSddDiffQueryPort)(binding, bugbot.scm.context), (0, issue_completion_composition_root_2.createNativeStatusReadPort)(binding), (0, github_runtime_doctor_composition_root_1.createGithubRuntimeDoctorPorts)(binding, bugbot.scm.context)); } -function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { +function createPullRequestReviewCommentUseCaseCompositionRoot(binding, setup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { const bugbot = (0, bugbot_composition_root_1.createBugbotCompositionRoot)(binding); const findings = (0, agent_capability_composition_root_1.createFindingsQueryPort)(); const language = (0, agent_capability_composition_root_1.createLanguageQueryPort)(); const fixer = (0, agent_capability_composition_root_1.createFixerQueryPort)(); - const gitCommit = new git_commit_adapter_1.GitCommitAdapter(); + const gitCommit = new git_commit_adapter_1.GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = (0, authenticated_user_composition_root_1.createAuthenticatedUserCompositionRoot)(); const bugbotGit = new bound_bugbot_git_mutation_adapter_1.BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new update_pull_request_description_use_case_1.UpdatePullRequestDescriptionUseCase((0, lifecycle_capability_port_binding_1.bindPullRequestDescription)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding, new pull_request_changes_repository_1.PullRequestChangesRepository((0, github_pull_request_client_factory_1.createPullRequestChangesClient)())), (0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindOrganizationMembers)((0, organization_members_composition_root_1.createOrganizationMembersCompositionRoot)(), binding), (0, agent_capability_composition_root_1.createFindingsQueryPort)()); @@ -86575,7 +86655,7 @@ function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface, set return singleAction.invoke(execution); }, "issue-comment": async (execution) => { - issueComment ?? (issueComment = createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution))); + issueComment ?? (issueComment = createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution), (0, verification_setup_snapshot_1.captureVerificationSetup)(execution.verificationSetup, execution.branches.main))); return issueComment.invoke(execution); }, issue: async (execution) => { @@ -86583,7 +86663,7 @@ function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface, set return issue.invoke(execution); }, "pull-request-review-comment": async (execution) => { - pullRequestReviewComment ?? (pullRequestReviewComment = createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution))); + pullRequestReviewComment ?? (pullRequestReviewComment = createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution), (0, verification_setup_snapshot_1.captureVerificationSetup)(execution.verificationSetup, execution.branches.main))); return pullRequestReviewComment.invoke(execution); }, "pull-request": async (execution) => { @@ -87520,23 +87600,28 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.GitCommitAdapter = void 0; +const verification_executable_1 = __nccwpck_require__(20878); +const native_verification_profiles_1 = __nccwpck_require__(22290); const exec = __importStar(__nccwpck_require__(18538)); const git_authentication_environment_1 = __nccwpck_require__(16535); const untrusted_command_environment_1 = __nccwpck_require__(2304); const git_hook_isolation_1 = __nccwpck_require__(39764); const verification_runtime_preflight_1 = __nccwpck_require__(23100); +const verification_setup_ports_1 = __nccwpck_require__(26509); +const verification_setup_runner_1 = __nccwpck_require__(11709); class GitCommitAdapter { constructor(executeCommand = (program, args, options) => options - ? exec.exec(program, args, { + ? exec.exec(options.resolveFromEnvironment && options.env ? (0, verification_executable_1.verificationExecutable)(program, options.env) : program, args, { // Captured Git reads can contain NUL-delimited records that // hide subsequent lines in GitHub's Actions log viewer. ...(options.stdout ? { silent: true } : {}), ...(options.stdout ? { listeners: { stdout: options.stdout } } : {}), ...(options.env ? { env: options.env } : {}), }) - : exec.exec(program, args), checkPrerequisites = verification_runtime_preflight_1.assertVerificationRuntimePrerequisites) { + : exec.exec(program, args), checkPrerequisites = verification_runtime_preflight_1.assertVerificationRuntimePrerequisites, verificationSetup = verification_setup_ports_1.DISABLED_VERIFICATION_SETUP) { this.executeCommand = executeCommand; this.checkPrerequisites = checkPrerequisites; + this.verificationSetup = verificationSetup; } async execute(program, args, options) { if (options?.verifyRuntimePrerequisites && !options.untrusted) @@ -87547,15 +87632,19 @@ class GitCommitAdapter { throw new Error('Untrusted command execution does not accept a caller-supplied environment.'); const runtime = (0, untrusted_command_environment_1.prepareUntrustedCommandEnvironment)(); try { - if (options.verifyRuntimePrerequisites) + if (options.verifyRuntimePrerequisites) { + await (0, verification_setup_runner_1.prepareRepositoryVerification)(this.verificationSetup, runtime.environment); this.checkPrerequisites(runtime.environment); + } return await this.executeCommand(program, args, { ...(options.stdout ? { stdout: options.stdout } : {}), env: runtime.environment, + ...(options.verifyRuntimePrerequisites && this.verificationSetup.kind === 'ready' ? { resolveFromEnvironment: true } : {}), }); } finally { - runtime.cleanup(); + if (!(0, native_verification_profiles_1.nativeVerificationRetainsProfile)(runtime.environment.HOME)) + runtime.cleanup(); } } async configureAuthor(name, email) { @@ -88251,6 +88340,659 @@ class LoggerWorkflowPollingObserverAdapter { exports.LoggerWorkflowPollingObserverAdapter = LoggerWorkflowPollingObserverAdapter; +/***/ }), + +/***/ 18883: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationCandidateIdentity = verificationCandidateIdentity; +const node_child_process_1 = __nccwpck_require__(17718); +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +/** Bind setup to the exact HEAD, branch, index, tracked edits and publishable untracked files. */ +function verificationCandidateIdentity(workspace, environment) { + const hash = (0, node_crypto_1.createHash)('sha256'); + const git = (...args) => { + const result = (0, node_child_process_1.spawnSync)('git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, shell: false, + windowsHide: true, timeout: 10000, maxBuffer: 16 * 1024 * 1024 }); + if (result.error || result.status !== 0) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update(result.stdout).update('\0'); + return result.stdout; + }; + git('rev-parse', '--verify', 'HEAD'); + git('rev-parse', '--symbolic-full-name', 'HEAD'); + git('ls-files', '--stage', '-z'); + git('diff', '--no-ext-diff', '--no-textconv', '--binary', 'HEAD', '--'); + const paths = git('ls-files', '--others', '--exclude-standard', '-z').toString('utf8').split('\0').filter(Boolean); + let total = 0; + if (paths.length > 256) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + for (const path of paths) { + const file = (0, node_path_1.join)(workspace, path); + const stat = (0, node_fs_1.lstatSync)(file); + total += stat.size; + if (total > 16 * 1024 * 1024) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update(path).update('\0'); + if (stat.isSymbolicLink()) + hash.update('120000\0').update((0, node_fs_1.readlinkSync)(file)); + else if (stat.isFile()) + hash.update(stat.mode & 0o100 ? '100755\0' : '100644\0').update((0, node_fs_1.readFileSync)(file)); + else + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + hash.update('\0'); + } + return hash.digest('hex'); +} + + +/***/ }), + +/***/ 71046: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.drainNativeWorkerLog = drainNativeWorkerLog; +exports.awaitNativeBoundary = awaitNativeBoundary; +exports.continueNativeVerification = continueNativeVerification; +exports.cleanupNativeVerification = cleanupNativeVerification; +exports.runNativeVerificationEntry = runNativeVerificationEntry; +const native_verification_source_runtime_1 = __nccwpck_require__(85290); +const verification_executable_1 = __nccwpck_require__(20878); +const native_verification_redaction_1 = __nccwpck_require__(27632); +const native_verification_profiles_1 = __nccwpck_require__(22290); +const core = __importStar(__nccwpck_require__(75855)); +const node_child_process_1 = __nccwpck_require__(17718); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const agent_private_install_root_1 = __nccwpck_require__(44640); +const agent_trusted_system_tools_1 = __nccwpck_require__(76049); +const github_action_input_1 = __nccwpck_require__(57581); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const native_verification_protocol_1 = __nccwpck_require__(88709); +const native_verification_staging_1 = __nccwpck_require__(63587); +const verification_setup_snapshot_1 = __nccwpck_require__(62148); +function emitWorkerOutput(root) { + const file = (0, node_path_1.join)(root, 'worker-output'); + if (process.env.GITHUB_OUTPUT && (0, node_fs_1.existsSync)(file)) + (0, node_fs_1.appendFileSync)(process.env.GITHUB_OUTPUT, requireRead(file)); + const summary = (0, node_path_1.join)(root, 'worker-summary'); + if (process.env.GITHUB_STEP_SUMMARY && (0, node_fs_1.existsSync)(summary) && !(0, native_verification_protocol_1.readNativeRecord)(root, 'summary-emitted.json')) { + (0, node_fs_1.appendFileSync)(process.env.GITHUB_STEP_SUMMARY, requireRead(summary, 1048576)); + (0, native_verification_protocol_1.writeNativeRecord)(root, 'summary-emitted.json', { emitted: true }); + } +} +function requireRead(file, limit = 262144) { + const fd = (0, node_fs_1.openSync)(file, 'r'); + try { + const size = (0, node_fs_1.statSync)(file).size; + if (size > limit) + throw new verification_setup_policy_1.VerificationSetupError('output-limit'); + const data = Buffer.alloc(size); + (0, node_fs_1.readSync)(fd, data, 0, data.length, 0); + return data; + } + finally { + (0, node_fs_1.closeSync)(fd); + } +} +/** Drain complete lines so runner masking and workflow commands keep their native framing. */ +function drainNativeWorkerLog(root, flush = false) { + const file = (0, node_path_1.join)(root, 'worker-log'); + if (!(0, node_fs_1.existsSync)(file)) + return; + const cursor = (0, native_verification_protocol_1.readNativeRecord)(root, 'log-cursor.json')?.offset ?? 0; + const fd = (0, node_fs_1.openSync)(file, 'r'); + let offset = cursor; + try { + const buffer = Buffer.alloc(65536); + let pending = Buffer.alloc(0); + let size; + while ((size = (0, node_fs_1.readSync)(fd, buffer, 0, buffer.length, offset + pending.length))) { + pending = Buffer.concat([pending, buffer.subarray(0, size)]); + const lastLine = pending.lastIndexOf(10); + if (lastLine >= 0) { + const complete = pending.subarray(0, lastLine + 1); + process.stdout.write(complete); + offset += complete.length; + pending = pending.subarray(lastLine + 1); + } + // Existing action logs may contain long lines; retain framing without unbounded buffering. + if (pending.length > 1048576) + throw new verification_setup_policy_1.VerificationSetupError('output-limit'); + } + if (flush && pending.length) { + process.stdout.write(pending); + offset += pending.length; + } + (0, native_verification_protocol_1.writeNativeRecord)(root, 'log-cursor.json', { offset }); + } + finally { + (0, node_fs_1.closeSync)(fd); + } +} +async function awaitNativeBoundary(root, timeoutMs = 7200000) { + const deadline = Date.now() + timeoutMs; + while (true) { + drainNativeWorkerLog(root); + const done = (0, native_verification_protocol_1.readNativeRecord)(root, 'done.json'); + if (done) { + drainNativeWorkerLog(root, true); + emitWorkerOutput(root); + core.setOutput('verification-setup-pending', 'false'); + if (done.exitCode !== 0) + core.setFailed('Copilot execution failed; inspect the preceding operation result.'); + return; + } + const request = (0, native_verification_protocol_1.readNativeRecord)(root, 'request.json', 32768); + if (request) { + if (!/^[a-f0-9-]{36}$/u.test(request.id) || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(request.sourceSha) + || !/^[a-f0-9]{64}$/u.test(request.digest) || !request.environment || typeof request.environment.HOME !== 'string') + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + core.setOutput('verification-request', request.id); + core.setOutput('verification-source', request.sourceSha); + core.setOutput('verification-digest', request.digest); + core.saveState('native-verification-digest', request.digest); + core.saveState('native-verification-source', request.sourceSha); + for (const key of ['HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PATH', 'JAVA_HOME', 'TMP', 'TEMP', 'TMPDIR']) { + const value = request.environment[key] ?? ''; + if (typeof value !== 'string' || /[\r\n\0]/u.test(value)) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + core.setOutput(`verification-${key.toLowerCase()}`, value); + } + core.setOutput('verification-setup-pending', 'true'); + return; + } + if (Date.now() >= deadline) + throw new verification_setup_policy_1.VerificationSetupError('timeout'); + const session = (0, native_verification_protocol_1.nativeSession)(root); + try { + process.kill(session.pid, 0); + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('process-failed'); + } + await (0, native_verification_protocol_1.nativePause)(100); + } +} +async function continueNativeVerification(root, id, outcome, environment = process.env) { + (0, native_verification_protocol_1.nativeSession)(root); + const request = (0, native_verification_protocol_1.readNativeRecord)(root, 'request.json', 32768); + if (!request || request.id !== id || !/^[a-f0-9-]{36}$/u.test(id) + || (0, node_fs_1.existsSync)((0, node_path_1.join)(root, `response-${id}.json`))) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + const response = { id, success: outcome === 'success', path: environment.PATH ?? '', + ...(environment.JAVA_HOME ? { javaHome: environment.JAVA_HOME } : {}) }; + (0, node_fs_1.rmSync)((0, node_path_1.join)(root, 'request.json')); + (0, native_verification_protocol_1.writeNativeRecord)(root, `response-${id}.json`, response); + await awaitNativeBoundary(root); +} +async function cleanupNativeVerification(root, waitMs = 10000, deferProfileCleanup = false) { + if (!(0, node_fs_1.existsSync)(root)) + return; + const session = (0, native_verification_protocol_1.nativeSession)(root); + if (!(0, native_verification_protocol_1.readNativeRecord)(root, 'done.json')) { + (0, native_verification_protocol_1.writeNativeRecord)(root, 'cancel.json', { cancelled: true }); + const deadline = Date.now() + waitMs; + while (!(0, native_verification_protocol_1.readNativeRecord)(root, 'done.json') && Date.now() < deadline) + await (0, native_verification_protocol_1.nativePause)(100); + if (!(0, native_verification_protocol_1.readNativeRecord)(root, 'done.json')) { + try { + if (process.platform === 'win32') + (0, node_child_process_1.spawnSync)((0, agent_trusted_system_tools_1.trustedWindowsSystemTool)('taskkill.exe'), ['/PID', String(session.pid), '/T', '/F'], { windowsHide: true, shell: false, timeout: 5000, stdio: 'ignore' }); + else + process.kill(-session.pid, 'SIGKILL'); + } + catch { /* Worker may have exited while cancellation completed. */ } + } + } + if (!deferProfileCleanup) + (0, native_verification_staging_1.cleanupNativeVerificationAction)(root, session.workspace); + drainNativeWorkerLog(root, true); + if (!deferProfileCleanup) { + (0, native_verification_profiles_1.removeNativeVerificationProfiles)(root); + (0, node_fs_1.rmSync)(root, { recursive: true, force: true }); + } +} +async function startNativeVerification() { + for (const secret of (0, native_verification_redaction_1.nativeWorkerSecrets)(process.env)) + core.setSecret(secret); + const git = (0, verification_executable_1.verificationExecutable)('git', { PATH: process.env.PATH ?? '', PATHEXT: process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM' }); + core.saveState('native-verification-git', git); + core.setOutput('verification-git', git); + const root = (0, node_fs_1.realpathSync)((0, agent_private_install_root_1.createPrivateVerificationRuntimeRoot)()); + for (const name of ['worker-output', 'worker-env', 'worker-path', 'worker-log', 'worker-summary']) + (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, name), '', { flag: 'wx', mode: 0o600 }); + const log = (0, node_fs_1.openSync)((0, node_path_1.join)(root, 'worker-log'), 'a'); + try { + const worker = (0, node_child_process_1.spawn)(process.execPath, [process.argv[1]], { cwd: process.cwd(), detached: true, windowsHide: true, + stdio: ['ignore', log, log], env: { ...process.env, COPILOT_NATIVE_PHASE: '', COPILOT_NATIVE_WORKER_SESSION: root, + GITHUB_STEP_SUMMARY: (0, node_path_1.join)(root, 'worker-summary'), GITHUB_OUTPUT: (0, node_path_1.join)(root, 'worker-output'), GITHUB_ENV: (0, node_path_1.join)(root, 'worker-env'), GITHUB_PATH: (0, node_path_1.join)(root, 'worker-path') } }); + await new Promise((resolve, reject) => { worker.once('spawn', resolve); worker.once('error', reject); }); + worker.unref(); + (0, native_verification_protocol_1.writeNativeRecord)(root, 'session.json', { schemaVersion: 1, workspace: (0, node_fs_1.realpathSync)(process.cwd()), run: process.env.GITHUB_RUN_ID ?? '', + attempt: process.env.GITHUB_RUN_ATTEMPT ?? '', job: process.env.GITHUB_JOB ?? '', pid: worker.pid }); + core.saveState('native-verification-ticket', root); + core.saveState('native-verification-owner', 'true'); + core.setOutput('verification-ticket', root); + await awaitNativeBoundary(root); + } + catch (error) { + if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, 'session.json'))) + await cleanupNativeVerification(root); + else + (0, node_fs_1.rmSync)(root, { recursive: true, force: true }); + throw error; + } + finally { + (0, node_fs_1.closeSync)(log); + } +} +/** Native phases are runner-only control messages; no credentials or agent operations are replayed. */ +async function runNativeVerificationEntry(run) { + const post = core.getState('copilot-native-post'); + if (post === 'true') { + const root = core.getState('native-verification-ticket'); + if (root && core.getState('native-verification-owner') === 'true') + await cleanupNativeVerification(root); + else if (root && (0, node_fs_1.existsSync)(root) && core.getState('native-verification-source')) { + const session = (0, native_verification_protocol_1.nativeSession)(root); + try { + const snapshot = (0, verification_setup_snapshot_1.captureVerificationSetupAtCommit)(core.getState('native-verification-source'), session.workspace, core.getState('native-verification-git')); + if (snapshot.kind !== 'ready' || (0, verification_setup_snapshot_1.verificationSetupDigest)(snapshot) !== core.getState('native-verification-digest')) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + (0, native_verification_staging_1.stageNativeVerificationAction)(snapshot, root); + } + catch { + // A later native post must fail to load rather than execute candidate-modified metadata. + (0, native_verification_staging_1.cleanupNativeVerificationAction)(root, session.workspace); + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + } + } + const sourceRuntime = core.getState('native-source-runtime'); + if (sourceRuntime) + (0, native_verification_source_runtime_1.cleanupNativeSourceRuntime)(sourceRuntime, (0, node_fs_1.realpathSync)(process.cwd())); + return; + } + core.saveState('copilot-native-post', 'true'); + if (process.env.COPILOT_NATIVE_PHASE === 'prepare' && process.env.COPILOT_NATIVE_RUNTIME_CACHE) + core.saveState('native-source-runtime', process.env.COPILOT_NATIVE_RUNTIME_CACHE); + const worker = process.env.COPILOT_NATIVE_WORKER_SESSION; + if (worker) { + const deadline = Date.now() + 30000; + while (!(0, native_verification_protocol_1.readNativeRecord)(worker, 'session.json')) { + if (Date.now() >= deadline) + throw new verification_setup_policy_1.VerificationSetupError('timeout'); + await (0, native_verification_protocol_1.nativePause)(10); + } + if ((0, native_verification_protocol_1.nativeSession)(worker).pid !== process.pid) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + const restoreLog = (0, native_verification_redaction_1.redactNativeWorkerLogs)(); + try { + await run(); + } + finally { + restoreLog(); + (0, native_verification_protocol_1.writeNativeRecord)(worker, 'done.json', { exitCode: Number(process.exitCode ?? 0) }); + } + return; + } + const phase = process.env.COPILOT_NATIVE_PHASE; + if (!phase || phase === 'prepare' && (0, github_action_input_1.getGithubActionInput)('verification-setup') !== 'true') { + await run(); + return; + } + if (process.env.GITHUB_ACTIONS !== 'true') + throw new verification_setup_policy_1.VerificationSetupError('native-workflow-required'); + if (phase === 'prepare') { + await startNativeVerification(); + return; + } + const root = process.env.COPILOT_NATIVE_TICKET; + if (!root) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, native_verification_protocol_1.nativeSession)(root); + core.saveState('native-verification-ticket', root); + core.saveState('native-verification-source', process.env.COPILOT_NATIVE_SOURCE ?? ''); + core.saveState('native-verification-digest', process.env.COPILOT_NATIVE_DIGEST ?? ''); + core.saveState('native-verification-git', process.env.COPILOT_NATIVE_GIT ?? ''); + if (phase === 'continue') { + await continueNativeVerification(root, process.env.COPILOT_NATIVE_REQUEST ?? '', process.env.COPILOT_NATIVE_OUTCOME ?? ''); + return; + } + if (phase === 'finish') { + (0, native_verification_protocol_1.nativeSession)(root); + const done = (0, native_verification_protocol_1.readNativeRecord)(root, 'done.json'); + if (done) { + emitWorkerOutput(root); + if (done.exitCode !== 0) + core.setFailed('Copilot execution failed.'); + } + else + core.setFailed(`Repository setup did not complete within ${verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.requests} native verification boundaries. Pending verification and subsequent publication are blocked; inspect earlier operation results for completed effects.`); + await cleanupNativeVerification(root, 10000, true); + return; + } + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); +} + + +/***/ }), + +/***/ 22290: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.nativeVerificationProfilePaths = nativeVerificationProfilePaths; +exports.registerNativeVerificationProfile = registerNativeVerificationProfile; +exports.nativeVerificationRetainsProfile = nativeVerificationRetainsProfile; +exports.removeNativeVerificationProfiles = removeNativeVerificationProfiles; +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const native_verification_protocol_1 = __nccwpck_require__(88709); +function nativeVerificationProfilePaths(root) { + const stored = (0, native_verification_protocol_1.readNativeRecord)(root, 'profiles.json', 65536); + const paths = stored === undefined ? [] : stored; + if (!Array.isArray(paths) || paths.length > 20 || new Set(paths).size !== paths.length || paths.some(path => typeof path !== 'string' + || (0, node_path_1.dirname)(path) !== (0, node_path_1.dirname)(root) || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(path)) || path === root)) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + return paths; +} +/** Native actions may have post steps. Preserve dependency-only profiles until those steps finish. */ +function registerNativeVerificationProfile(root, profile) { + const previous = nativeVerificationProfilePaths(root); + if (previous.length >= 20 || previous.includes(profile) || (0, node_path_1.dirname)(profile) !== (0, node_path_1.dirname)(root) + || profile === root || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(profile)) + || (0, node_fs_1.realpathSync)(profile) !== profile || !(0, node_fs_1.lstatSync)(profile).isDirectory() || (0, node_fs_1.lstatSync)(profile).isSymbolicLink()) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.writeFileSync)((0, node_path_1.join)(profile, 'native-session-owner'), (0, node_crypto_1.createHash)('sha256').update(root).digest('hex'), { mode: 0o600, flag: 'wx' }); + (0, native_verification_protocol_1.writeNativeRecord)(root, 'profiles.json', [...previous, profile]); +} +function nativeVerificationRetainsProfile(profile) { + const root = process.env.COPILOT_NATIVE_WORKER_SESSION; + return !!root && nativeVerificationProfilePaths(root).includes(profile); +} +function removeNativeVerificationProfiles(root) { + for (const path of nativeVerificationProfilePaths(root)) { + let stat; + try { + stat = (0, node_fs_1.lstatSync)(path); + } + catch (error) { + if (error.code === 'ENOENT') + continue; + throw error; + } + if (!stat.isDirectory() || stat.isSymbolicLink() || (0, node_fs_1.realpathSync)(path) !== path || (0, node_fs_1.readFileSync)((0, node_path_1.join)(path, 'native-session-owner'), 'utf8') !== (0, node_crypto_1.createHash)('sha256').update(root).digest('hex')) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.rmSync)(path, { recursive: true, force: true }); + } +} + + +/***/ }), + +/***/ 88709: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.nativePause = void 0; +exports.writeNativeRecord = writeNativeRecord; +exports.readNativeRecord = readNativeRecord; +exports.nativeSession = nativeSession; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const node_crypto_1 = __nccwpck_require__(6005); +const verification_setup_policy_1 = __nccwpck_require__(65326); +function writeNativeRecord(root, name, value) { + const temporary = (0, node_path_1.join)(root, `${(0, node_crypto_1.randomUUID)()}.tmp`); + (0, node_fs_1.writeFileSync)(temporary, JSON.stringify(value), { mode: 0o600, flag: 'wx' }); + (0, node_fs_1.renameSync)(temporary, (0, node_path_1.join)(root, name)); +} +function readNativeRecord(root, name, limit = 16384) { + const file = (0, node_path_1.join)(root, name); + let descriptor; + try { + const before = (0, node_fs_1.lstatSync)(file, { bigint: true }); + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(limit)) + throw new Error(); + descriptor = (0, node_fs_1.openSync)(file, node_fs_1.constants.O_RDONLY | (node_fs_1.constants.O_NOFOLLOW ?? 0)); + const after = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || after.mtimeNs !== before.mtimeNs) + throw new Error(); + const data = (0, node_fs_1.readFileSync)(descriptor); + const final = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (data.length > limit || final.size !== after.size || final.mtimeNs !== after.mtimeNs) + throw new Error(); + return JSON.parse(data.toString('utf8')); + } + catch (error) { + if (error.code === 'ENOENT') + return undefined; + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + } + finally { + if (descriptor !== undefined) + (0, node_fs_1.closeSync)(descriptor); + } +} +function nativeSession(root, environment = process.env) { + if (!/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test((0, node_path_1.basename)(root)) || (0, node_fs_1.realpathSync)(root) !== root + || !(0, node_fs_1.lstatSync)(root).isDirectory() || (0, node_fs_1.lstatSync)(root).isSymbolicLink()) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + if (process.platform !== 'win32' && ((0, node_fs_1.lstatSync)(root).mode & 0o077) !== 0) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + const session = readNativeRecord(root, 'session.json'); + if (session?.schemaVersion !== 1 || session.workspace !== (0, node_fs_1.realpathSync)(process.cwd()) + || session.run !== (environment.GITHUB_RUN_ID ?? '') || session.attempt !== (environment.GITHUB_RUN_ATTEMPT ?? '') + || session.job !== (environment.GITHUB_JOB ?? '') || !Number.isSafeInteger(session.pid) || session.pid <= 0) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + return session; +} +const nativePause = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds)); +exports.nativePause = nativePause; + + +/***/ }), + +/***/ 27632: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.nativeWorkerSecrets = nativeWorkerSecrets; +exports.redactNativeWorkerLogs = redactNativeWorkerLogs; +const node_string_decoder_1 = __nccwpck_require__(76915); +const SECRET_KEY = /(?:TOKEN|SECRET|API_KEY|PASSWORD|AUTH_JSON)/iu; +function nativeWorkerSecrets(environment) { + const values = Object.entries(environment).filter(([key, value]) => SECRET_KEY.test(key) && value).map(([, value]) => value); + if (environment.INPUT_VARS_JSON) { + try { + const inputs = JSON.parse(environment.INPUT_VARS_JSON); + for (const [key, value] of Object.entries(inputs)) + if (SECRET_KEY.test(key) && typeof value === 'string') + values.push(value); + } + catch { /* The existing input parser reports invalid configuration without raw values. */ } + } + return [...new Set(values.flatMap(value => [value, ...value.split(/[\r\n]/u)]).filter(value => value.length >= 3))].sort((a, b) => b.length - a.length); +} +/** Secrets are removed before bytes reach a private log file, not merely masked in the runner UI. */ +function redactNativeWorkerLogs(environment = process.env) { + const secrets = nativeWorkerSecrets(environment); + const restores = []; + for (const stream of [process.stdout, process.stderr]) { + const original = stream.write; + const decoder = new node_string_decoder_1.StringDecoder('utf8'); + let pending = ''; + const emit = (line) => { + const command = /^::add-mask::(.*?)(?:\r?\n)?$/u.exec(line); + if (command) { + const value = command[1].replace(/%0D/gu, '\r').replace(/%0A/gu, '\n').replace(/%25/gu, '%'); + secrets.push(...value.split(/[\r\n]/u).filter(part => part.length >= 3)); + return; + } + for (const secret of secrets) + line = line.split(secret).join('[REDACTED]'); + original.call(stream, line); + }; + stream.write = ((chunk, encoding, callback) => { + pending += typeof chunk === 'string' ? chunk : decoder.write(Buffer.from(chunk)); + let end; + while ((end = pending.indexOf('\n')) >= 0) { + const line = pending.slice(0, end + 1); + pending = pending.slice(end + 1); + emit(line); + } + if (pending.length > 1048576) + throw new Error('Copilot worker log line exceeded its fixed limit.'); + if (typeof encoding === 'function') + encoding(); + else + callback?.(); + return true; + }); + restores.push(() => { pending += decoder.end(); if (pending) + emit(pending); stream.write = original; }); + } + return () => { for (const restore of restores) + restore(); }; +} + + +/***/ }), + +/***/ 85290: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.cleanupNativeSourceRuntime = cleanupNativeSourceRuntime; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +/** Only source workflows use this snapshot; installed remote Actions already live outside the candidate. */ +function cleanupNativeSourceRuntime(root, workspace) { + if (!root || !(0, node_fs_1.existsSync)(root)) + return; + const allowed = ['.copilot-native-runtime', '.copilot-native-fixture-runtime'].map(name => (0, node_path_1.join)((0, node_path_1.dirname)(workspace), name)); + if (!allowed.includes(root) || (0, node_fs_1.lstatSync)(root).isSymbolicLink() || (0, node_fs_1.realpathSync)(root) !== root) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + let receipt; + try { + receipt = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(root, '.copilot-runtime-snapshot.json'), 'utf8')); + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + } + if (receipt?.kind !== 'copilot-native-runtime' || receipt.workspace !== workspace) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.rmSync)(root, { recursive: true, force: true }); +} + + +/***/ }), + +/***/ 63587: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.NATIVE_VERIFICATION_ACTION = void 0; +exports.stageNativeVerificationAction = stageNativeVerificationAction; +exports.cleanupNativeVerificationAction = cleanupNativeVerificationAction; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const native_verification_protocol_1 = __nccwpck_require__(88709); +exports.NATIVE_VERIFICATION_ACTION = '.git/copilot-verification-setup'; +function targetPath(workspace) { + const metadata = (0, node_path_1.join)(workspace, '.git'); + const stat = (0, node_fs_1.lstatSync)(metadata); + if (!stat.isDirectory() || stat.isSymbolicLink() || (0, node_fs_1.realpathSync)(metadata) !== metadata) + throw new verification_setup_policy_1.VerificationSetupError('native-workflow-required'); + return (0, node_path_1.join)(workspace, exports.NATIVE_VERIFICATION_ACTION); +} +/** Native composite metadata must remain available until its deferred post steps have finished. */ +function stageNativeVerificationAction(setup, session) { + const target = targetPath(setup.workspace); + const record = (0, native_verification_protocol_1.readNativeRecord)(session, 'staging.json'); + if (record && record.target !== target || !record && !!(0, node_fs_1.lstatSync)(target, { throwIfNoEntry: false })) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'staging.json', { target }); + (0, node_fs_1.rmSync)(target, { recursive: true, force: true }); + (0, node_fs_1.mkdirSync)(target, { mode: 0o700 }); + for (const file of setup.files) { + const destination = (0, node_path_1.join)(target, file.path); + const remainder = (0, node_path_1.relative)(target, destination); + if (!remainder || remainder.startsWith(`..${node_path_1.sep}`) || remainder === '..') + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(destination), { recursive: true, mode: 0o700 }); + (0, node_fs_1.writeFileSync)(destination, Buffer.from(file.base64, 'base64'), { mode: file.executable ? 0o700 : 0o600, flag: 'wx' }); + } +} +function cleanupNativeVerificationAction(session, workspace) { + const record = (0, native_verification_protocol_1.readNativeRecord)(session, 'staging.json'); + if (!record) + return; + const target = targetPath(workspace); + if (record.target !== target) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, node_fs_1.rmSync)(target, { recursive: true, force: true }); + (0, node_fs_1.rmSync)((0, node_path_1.join)(session, 'staging.json'), { force: true }); +} + + /***/ }), /***/ 35849: @@ -88895,7 +89637,7 @@ const ALLOWED_VARIABLES = [ * credentials or paths to local agent authentication stores. */ function prepareUntrustedCommandEnvironment(source = process.env, platform = process.platform) { - const runtimeHome = (0, agent_private_install_root_1.createPrivateVerificationRuntimeRoot)(); + const runtimeHome = (0, node_fs_1.realpathSync)((0, agent_private_install_root_1.createPrivateVerificationRuntimeRoot)()); const environment = { HOME: runtimeHome }; for (const variable of ALLOWED_VARIABLES) { const value = source[variable]; @@ -88923,6 +89665,39 @@ function prepareUntrustedCommandEnvironment(source = process.env, platform = pro } +/***/ }), + +/***/ 20878: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationExecutable = verificationExecutable; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +/** @actions/exec resolves basenames against its own process PATH; native tools belong to the verifier PATH. */ +function verificationExecutable(program, environment, platform = process.platform) { + const explicit = (0, node_path_1.isAbsolute)(program) || /[\\/]/u.test(program); + const candidates = explicit ? [(0, node_path_1.resolve)(program)] : (environment.PATH ?? '').split(node_path_1.delimiter).filter(Boolean).map(directory => (0, node_path_1.resolve)((0, node_path_1.join)(directory, program))); + const windowsExtensions = (environment.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean); + const hasWindowsExtension = windowsExtensions.some(extension => program.toLowerCase().endsWith(extension.toLowerCase())); + const extensions = platform === 'win32' && !hasWindowsExtension ? windowsExtensions : ['']; + for (const candidate of candidates) + for (const extension of extensions) { + const file = candidate + extension; + try { + if ((0, node_fs_1.statSync)(file).isFile()) { + (0, node_fs_1.accessSync)(file, node_fs_1.constants.X_OK); + return file; + } + } + catch { /* Try only the next executable in the isolated PATH. */ } + } + throw new Error('Verification executable is unavailable in the isolated PATH.'); +} + + /***/ }), /***/ 91053: @@ -89049,6 +89824,264 @@ function checkRuntime(requirement, environment, run, platform, job) { } +/***/ }), + +/***/ 23358: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.applyVerificationSetupResult = applyVerificationSetupResult; +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const verification_setup_policy_1 = __nccwpck_require__(65326); +function contained(root, path) { + const remainder = (0, node_path_1.relative)(root, path); + return remainder === '' || (remainder !== '..' && !remainder.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(remainder)); +} +/** Accept only existing contained directories, checking both lexical paths and symlink targets. */ +function approvedDirectory(value, roots) { + if (!(0, node_path_1.isAbsolute)(value) || value.includes(node_path_1.delimiter) || value.split(/[\\/]/u).includes('..') + || !roots.some(root => contained(root, value))) + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + const actual = (0, node_fs_1.realpathSync)(value); + if (!roots.some(root => contained(root, actual)) || !(0, node_fs_1.lstatSync)(actual).isDirectory()) + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + return actual; +} +function applyVerificationSetupResult(file, environment, roots) { + let descriptor; + try { + let before; + try { + before = (0, node_fs_1.lstatSync)(file, { bigint: true }); + } + catch (error) { + if (error.code === 'ENOENT') + return; + throw error; + } + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes)) + throw new Error(); + descriptor = (0, node_fs_1.openSync)(file, node_fs_1.constants.O_RDONLY | (node_fs_1.constants.O_NOFOLLOW ?? 0)); + const opened = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (!opened.isFile() || opened.nlink !== 1n || opened.dev !== before.dev || opened.ino !== before.ino + || opened.size !== before.size || opened.mtimeNs !== before.mtimeNs) + throw new Error(); + const bytes = Buffer.alloc(verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes + 1); + const size = (0, node_fs_1.readSync)(descriptor, bytes, 0, bytes.length, 0); + if (size > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.resultBytes) + throw new Error(); + const after = (0, node_fs_1.fstatSync)(descriptor, { bigint: true }); + if (after.size !== opened.size || after.mtimeNs !== opened.mtimeNs) + throw new Error(); + const result = (0, verification_setup_policy_1.parseVerificationSetupResult)(JSON.parse(bytes.subarray(0, size).toString('utf8'))); + const paths = result.path.map(value => approvedDirectory(value, roots)); + const javaHome = result.javaHome ? approvedDirectory(result.javaHome, roots) : undefined; + if (paths.length) + environment.PATH = [...paths, ...(environment.PATH ? [environment.PATH] : [])].join(node_path_1.delimiter); + if (javaHome) + environment.JAVA_HOME = javaHome; + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + } + finally { + if (descriptor !== undefined) + (0, node_fs_1.closeSync)(descriptor); + } +} + + +/***/ }), + +/***/ 11709: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prepareRepositoryVerification = prepareRepositoryVerification; +const verification_setup_snapshot_1 = __nccwpck_require__(62148); +const native_verification_profiles_1 = __nccwpck_require__(22290); +const node_fs_1 = __nccwpck_require__(87561); +const node_path_1 = __nccwpck_require__(49411); +const node_crypto_1 = __nccwpck_require__(6005); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const verification_setup_result_1 = __nccwpck_require__(23358); +const logging_ports_1 = __nccwpck_require__(6152); +const native_verification_protocol_1 = __nccwpck_require__(88709); +const native_verification_staging_1 = __nccwpck_require__(63587); +const native_verification_candidate_1 = __nccwpck_require__(18883); +/** The runner executes YAML. This adapter pauses the admitted call stack at its verification boundary. */ +async function prepareRepositoryVerification(setup, environment) { + if (setup.kind === 'disabled') + return; + if (setup.kind === 'blocked') + throw new verification_setup_policy_1.VerificationSetupError(setup.reason, setup.sourceSha); + const session = process.env.COPILOT_NATIVE_WORKER_SESSION; + if (!session) + throw new verification_setup_policy_1.VerificationSetupError('native-workflow-required', setup.sourceSha); + try { + (0, native_verification_protocol_1.nativeSession)(session); + const requests = (0, native_verification_protocol_1.readNativeRecord)(session, 'request-count.json')?.count ?? 0; + if (requests >= verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.requests) + throw new verification_setup_policy_1.VerificationSetupError('handoff-invalid'); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'request-count.json', { count: requests + 1 }); + if ((0, node_fs_1.realpathSync)(process.cwd()) !== setup.workspace) + throw new verification_setup_policy_1.VerificationSetupError('workspace-changed'); + (0, native_verification_profiles_1.registerNativeVerificationProfile)(session, environment.HOME); + const identity = (0, native_verification_candidate_1.verificationCandidateIdentity)(setup.workspace, environment); + (0, native_verification_staging_1.stageNativeVerificationAction)(setup, session); + const id = (0, node_crypto_1.randomUUID)(); + (0, native_verification_protocol_1.writeNativeRecord)(session, 'request.json', { id, sourceSha: setup.sourceSha, digest: (0, verification_setup_snapshot_1.verificationSetupDigest)(setup), environment }); + (0, logging_ports_1.logInfo)(`Preparing repository verification (${verification_setup_policy_1.VERIFICATION_SETUP_PATH}; trusted source SHA: ${setup.sourceSha}).`); + const deadline = Date.now() + verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.timeoutMs; + let response; + while (!(response = (0, native_verification_protocol_1.readNativeRecord)(session, `response-${id}.json`))) { + if ((0, native_verification_protocol_1.readNativeRecord)(session, 'cancel.json')) + throw new verification_setup_policy_1.VerificationSetupError('cancelled'); + if (Date.now() >= deadline) + throw new verification_setup_policy_1.VerificationSetupError('timeout'); + await (0, native_verification_protocol_1.nativePause)(100); + } + if (response.id !== id || response.success !== true) + throw new verification_setup_policy_1.VerificationSetupError('process-failed'); + if ((0, native_verification_candidate_1.verificationCandidateIdentity)(setup.workspace, environment) !== identity) + throw new verification_setup_policy_1.VerificationSetupError('candidate-changed'); + if (typeof response.path !== 'string' || response.path.length > 16384 || response.javaHome !== undefined && typeof response.javaHome !== 'string') + throw new verification_setup_policy_1.VerificationSetupError('result-invalid'); + const original = new Set((environment.PATH ?? '').split(node_path_1.delimiter)); + const previousProfiles = (0, native_verification_profiles_1.nativeVerificationProfilePaths)(session).filter(profile => profile !== environment.HOME); + const fromPreviousProfile = (path) => previousProfiles.some(profile => { + const part = (0, node_path_1.relative)(profile, path); + return part === '' || part !== '..' && !part.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(part); + }); + const additions = [...new Set(response.path.split(node_path_1.delimiter).filter(path => path && !original.has(path) && !fromPreviousProfile(path)))]; + const result = (0, node_path_1.join)(environment.HOME, 'native-setup-result.json'); + (0, node_fs_1.writeFileSync)(result, JSON.stringify({ schemaVersion: 1, path: additions, + environment: response.javaHome && response.javaHome !== environment.JAVA_HOME ? { JAVA_HOME: response.javaHome } : {} }), { mode: 0o600, flag: 'wx' }); + (0, verification_setup_result_1.applyVerificationSetupResult)(result, environment, [setup.workspace, environment.HOME, ...(setup.toolRoot ? [setup.toolRoot] : [])]); + (0, logging_ports_1.logInfo)('Repository setup completed; runtime preflight and verification may proceed.'); + } + catch (error) { + throw new verification_setup_policy_1.VerificationSetupError(error instanceof verification_setup_policy_1.VerificationSetupError ? error.reason : 'handoff-invalid', setup.sourceSha); + } + finally { + (0, node_fs_1.rmSync)((0, node_path_1.join)(session, 'request.json'), { force: true }); + } +} + + +/***/ }), + +/***/ 62148: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.verificationSetupDigest = verificationSetupDigest; +exports.captureVerificationSetup = captureVerificationSetup; +exports.captureVerificationSetupAtCommit = captureVerificationSetupAtCommit; +const node_path_1 = __nccwpck_require__(49411); +const node_child_process_1 = __nccwpck_require__(17718); +const node_crypto_1 = __nccwpck_require__(6005); +const node_fs_1 = __nccwpck_require__(87561); +const js_yaml_1 = __nccwpck_require__(783); +const verification_setup_ports_1 = __nccwpck_require__(26509); +const verification_setup_policy_1 = __nccwpck_require__(65326); +const untrusted_command_environment_1 = __nccwpck_require__(2304); +/** Only immutable Git objects are read. Errors remain deferred on non-verifying routes. */ +function verificationSetupDigest(setup) { + return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(setup.files)).digest('hex'); +} +function captureVerificationSetup(enabled, mainBranch, root = process.cwd()) { + return capture(enabled, mainBranch, root); +} +/** Post steps use the already pinned commit from runner state, never a mutable branch ref. */ +function captureVerificationSetupAtCommit(commit, root = process.cwd(), gitExecutable) { + return capture(true, '', root, commit, gitExecutable); +} +function capture(enabled, mainBranch, root, pinned, gitExecutable) { + if (!enabled) + return verification_setup_ports_1.DISABLED_VERIFICATION_SETUP; + let sourceSha; + let runtime; + let snapshot; + try { + runtime = (0, untrusted_command_environment_1.prepareUntrustedCommandEnvironment)(); + const environment = runtime.environment; + if (!pinned && (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,200}$/u.test(mainBranch) || mainBranch.includes('..') + || mainBranch.includes('//') || mainBranch.endsWith('/') || mainBranch.endsWith('.lock'))) + throw new Error(); + if (gitExecutable !== undefined && !(0, node_path_1.isAbsolute)(gitExecutable)) + throw new Error(); + const workspace = (0, node_fs_1.realpathSync)(root); + const git = (...args) => { + const result = (0, node_child_process_1.spawnSync)(gitExecutable ?? 'git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, + shell: false, windowsHide: true, timeout: 5000, maxBuffer: verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.snapshotBytes + 4096 }); + if (result.error || result.status !== 0) + throw new Error(); + return result.stdout; + }; + const sha = pinned ?? git('rev-parse', '--verify', `refs/remotes/origin/${mainBranch}^{commit}`).toString('utf8').trim(); + if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sha)) + throw new Error(); + sourceSha = sha; + const entries = git('ls-tree', '-rz', sourceSha, '--', verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY).toString('utf8').split('\0').filter(Boolean); + if (!entries.length || entries.length > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.files) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + let total = 0; + const files = entries.map(entry => { + const match = /^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t(.+)$/u.exec(entry); + if (!match || !match[3].startsWith(`${verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY}/`)) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const path = match[3].slice(verification_setup_policy_1.VERIFICATION_SETUP_DIRECTORY.length + 1); + if (!/^[A-Za-z0-9_./-]{1,200}$/u.test(path) || path.split('/').some(part => !part || part === '.' || part === '..' || part.endsWith('.') || /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part))) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const bytes = git('cat-file', 'blob', match[2]); + total += bytes.length; + const digest = (0, node_crypto_1.createHash)(match[2].length === 40 ? 'sha1' : 'sha256').update(`blob ${bytes.length}\0`).update(bytes).digest('hex'); + if (bytes.length > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.scriptBytes || total > verification_setup_policy_1.VERIFICATION_SETUP_LIMITS.snapshotBytes || digest !== match[2]) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + return Object.freeze({ path, base64: bytes.toString('base64'), executable: match[1] === '100755' }); + }); + const manifest = files.find(file => file.path === 'action.yml'); + if (!manifest) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const bytes = Buffer.from(manifest.base64, 'base64'); + const text = bytes.toString('utf8'); + if (!bytes.length || !bytes.equals(Buffer.from(text)) || text.includes('\0')) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + let action; + try { + action = (0, js_yaml_1.load)(text); + } + catch { + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + } + if (action?.runs?.using !== 'composite' || !Array.isArray(action.runs.steps) || !action.runs.steps.length || action.runs.steps.length > 32) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + if (action.runs.steps.some(step => !step || typeof step !== 'object' || 'uses' in step && typeof step.uses === 'string' && step.uses.startsWith('./'))) + throw new verification_setup_policy_1.VerificationSetupError('helper-invalid'); + const toolRoot = environment.RUNNER_TOOL_CACHE ? (0, node_fs_1.realpathSync)(environment.RUNNER_TOOL_CACHE) : undefined; + snapshot = Object.freeze({ kind: 'ready', files: Object.freeze(files), sourceSha, workspace, ...(toolRoot ? { toolRoot } : {}) }); + } + catch (error) { + snapshot = Object.freeze({ kind: 'blocked', reason: error instanceof verification_setup_policy_1.VerificationSetupError ? error.reason : 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); + } + try { + runtime?.cleanup(); + } + catch { + snapshot = Object.freeze({ kind: 'blocked', reason: 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); + } + return snapshot; +} + + /***/ }), /***/ 92540: @@ -91830,6 +92863,14 @@ module.exports = require("node:stream"); /***/ }), +/***/ 76915: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:string_decoder"); + +/***/ }), + /***/ 31764: /***/ ((module) => { diff --git a/docs/configuration.mdx b/docs/configuration.mdx index a987ad8aa..f2490172c 100644 --- a/docs/configuration.mdx +++ b/docs/configuration.mdx @@ -63,6 +63,7 @@ PR bot approval uses the versioned `pullRequestApproval` setup object and `PR_AP ## Branch Management - `issue-managed-branches`: Create and verify a linked branch after `in-progress` (default: "true") + - `verification-setup`: Run the trusted main-branch composite action before each configured verification command in generated GitHub Actions callbacks (default: "false"). See [repository-owned setup](/security-operations/operations/verification#repository-owned-setup). - `pre-branch-sdd`: Require a validated SDD before implementation for eligible issues (default: "false") - `branch-management-emoji`: Emoji for branched issues (default: "🧑‍💻") diff --git a/docs/development/architecture.mdx b/docs/development/architecture.mdx index bc828dca3..63611b0c4 100644 --- a/docs/development/architecture.mdx +++ b/docs/development/architecture.mdx @@ -633,3 +633,40 @@ The trusted credential health adapter is GET-only and pins packaged workflow content, default SHA, run identity, freshness and unchanged Secret metadata. It never inherits the legacy bootstrap or dispatch adapter. Architecture/cycle, SDK contract, replay, drift and presentation tests enforce these boundaries. + +## Repository verification setup + +The optional [repository-owned composite action](/security-operations/operations/verification#repository-owned-setup) +is captured with all reviewed auxiliary files at comment-route composition from +an immutable trusted main commit. Acquisition errors remain deferred. Existing +actor, fork, native-head and workspace gates decide whether verification is reached. + +Branch synchronization and shared automated commit preflight request the same +`untrusted + verifyRuntimePrerequisites` port intent. The Git adapter creates a +fresh private HOME. Its infrastructure pauses the admitted same-job worker exactly +before preflight, stages the trusted action in local Git metadata and lets GitHub Actions execute the +native YAML. Continuation matches the operation/request/run/job, validates the unchanged candidate identity and bounded runtime directories, then +resolves executables against the private runtime PATH and runs the existing probes +and verifier. Staging survives native post steps; trusted Copilot post phases +use the initially pinned absolute Git executable to reload pinned Git objects and verify their original digest before native post +execution, then the initial Action post step cleans staging and profiles. The agent and publication are not replayed. + +Pure policy owns limits, schemas and sanitized errors. Snapshot, staging, protocol, +profile and controller adapters own Git/filesystem/process details; application +use cases retain mutation policy. Native action post steps can use dependency-only +profiles until the original Action's final post cleanup removes them. They are +never reused by later commands. Native step failure/cancellation blocks publication +and triggers existing recovery. A final cleanup guard stops a leftover worker. + +Source callbacks first copy the Action manifest and runtime outside the mutable +workspace, so rebuilding a candidate cannot replace continuation or post code. +Remote installed Actions already use the runner action cache. Only native callback +phases register active Copilot post behavior. + +The workflow generator emits twenty conditional setup/continuation slots and a +completion step in each actual mutation callback. Structural validation compares +the complete native contract, including timeout and runtime-only setup environment. +All six configuration callbacks forward the opt-in, while only issue/review-comment +mutation compositions execute it. Push analysis does not become a verifier. +Native platform acceptance runs real `uses`/`run` on Linux, Windows and macOS; +local real-Git integration covers exact managed-ref push and abort behavior. diff --git a/docs/security-operations/operations/troubleshooting.mdx b/docs/security-operations/operations/troubleshooting.mdx index d8f38bd1a..b48a1c140 100644 --- a/docs/security-operations/operations/troubleshooting.mdx +++ b/docs/security-operations/operations/troubleshooting.mdx @@ -583,3 +583,29 @@ successful skip. A version field alone does not prove that the stored branch or operation facts are valid. Authorization and temporary GitHub read failures retain their own error categories; follow their permission/provider recovery instead of deleting configuration. + +## Repository setup blocks verification + +`Repository setup blocked verification` means preparation did not establish a safe +command environment. Verification and commit/push are withheld; branch sync uses +its existing merge abort. Inspect the native setup step and closed cause in the +run, then repair the reviewed action on `main-branch`. + +| Cause | Recovery | +| --- | --- | +| `source-unavailable` | Make the trusted origin ref available in the full checkout. | +| `helper-invalid` | Review the fixed native composite and regular auxiliary files; respect file/step bounds and avoid nested local `uses`. | +| `native-workflow-required` | Update generated callbacks and use their normal directory-based Git checkout in GitHub Actions. The local CLI cannot execute composite actions. | +| `workspace-changed` / `candidate-changed` | Keep setup dependency-only; do not change publishable source, index, branch or lockfiles. Retry from a fresh candidate. | +| `process-failed` | Inspect the native action's logs and fix its reviewed dependency installation. | +| `timeout` | Fit each native setup within five minutes; no limit override or automatic retry exists. | +| `output-limit` | A Copilot worker log line/output exceeded its framing bound; native setup logs use normal Actions limits. | +| `cancelled` | Retry the authorized operation after cancellation is resolved. | +| `handoff-invalid` | Rerun through updated generated callbacks; never reuse a session from another run/job or request. | +| `result-invalid` | Export only supported, existing PATH/JAVA_HOME directories under the candidate, private HOME or trusted tool cache. | + +The consumer file is `.github/actions/copilot-verification-setup/action.yml`. +Candidate edits do not replace the trusted snapshot. Enabling without a reviewed +action blocks verification; the disabled default preserves existing behavior. +See [repository-owned setup](/security-operations/operations/verification#repository-owned-setup) +for native Python/pnpm examples, profile lifetime, trust boundaries and migration. diff --git a/docs/security-operations/operations/verification.mdx b/docs/security-operations/operations/verification.mdx index 394eafca2..baadf536a 100644 --- a/docs/security-operations/operations/verification.mdx +++ b/docs/security-operations/operations/verification.mdx @@ -32,8 +32,10 @@ The source workflow validator accepts the setup's literal `runs-on: {group: vypd The setup's runner profile chooses **where** a job runs. It does not install a consumer project's language runtimes or dependencies. A passing application CI job does not prepare the separate `issue_comment`, PR-comment or automated-fix -job. Each job that runs `bugbot-fix-verify-commands` must prepare its own tools -before Copilot, including for `/copilot sync-branch --no-agent`. +job. Each job that runs `bugbot-fix-verify-commands` must prepare its own tools, +including for `/copilot sync-branch --no-agent`. Use the [repository-owned setup +action](#repository-owned-setup) to prepare the candidate without editing generated +workflows, or retain your reviewed same-job bootstrap. For a Kotlin/JVM project, commit a reviewed `.copilot/verification-prerequisites.json`: @@ -117,6 +119,173 @@ commit or push; automated fixes also withhold their commit and push. Correct the declaration or bootstrap, then retry the original operation. Dry runs and already-aligned branches do not execute verification or prove runtime readiness. +## Repository-owned setup + +Each repository can prepare its dependencies in a native composite action, without +editing generated Copilot workflows. GitHub Actions executes the YAML's `uses` and +`run` steps in the same job where Copilot verifies the candidate. + +```mermaid +flowchart LR + A[Copilot prepares changes] --> B[Repository composite installs dependencies] + B --> C[Runtime preflight and verification] + C --> D[Existing publication guards] +``` + +Text equivalent: prepare the candidate, install its dependencies, verify it, then +apply the existing publication guards. A separate CI job does not prepare this job. + +1. Review `.github/actions/copilot-verification-setup/action.yml` and its auxiliary + scripts onto your configured `main-branch` (normally production). Candidate or + model changes cannot activate a different setup implementation. +2. Update the generated comment workflows with `copilot setup`. They contain the + native setup and continuation steps; an older callback cannot execute YAML. +3. Enable Repository Variable `VERIFICATION_SETUP=true`, or plan it with setup + configuration `repository: { verificationSetup: true }`. The corresponding + boolean Action input is `verification-setup`; its default is `false`. +4. Retry the authorized modification or `/copilot sync-branch --no-agent`. + +Copilot captures the complete action directory from an immutable trusted Git SHA +before candidate changes. The trusted origin ref must be available locally; +generated comment workflows use a full checkout. The runner executes that snapshot +**after merge/fix changes and before each configured verifier**, reading the actual +candidate manifests. The admitted operation continues once; the agent is not rerun. +Copilot stages the reviewed snapshot in reserved local Git metadata at +`.git/copilot-verification-setup`; the consumer's candidate files stay unchanged. +The normal directory-based `.git` checkout from generated workflows is required. +Staging remains available for native post steps. Trusted Copilot post steps reload +the pinned Git objects and verify their original content digest before those +phases with the initially pinned Git executable, so later candidate edits cannot +supply post code. Source workflows also snapshot Copilot itself outside the +mutable workspace; normal remote Action installations already use a separate +runner cache. + +For Python, the consumer file can contain: + +```yaml +name: Prepare project verification +runs: + using: composite + steps: + - uses: actions/setup-python@v6 + id: python + with: + update-environment: false + python-version: '3.12' + - name: Install the candidate's test dependencies + run: | + "$PYTHON_EXECUTABLE" - <<'PYTHON' + import os, pathlib, subprocess, venv + home = pathlib.Path(os.environ['HOME']) / 'venv' + venv.create(home, with_pip=True) + tools = home / ('Scripts' if os.name == 'nt' else 'bin') + python = tools / ('python.exe' if os.name == 'nt' else 'python') + subprocess.check_call([str(python), '-m', 'pip', 'install', + '--no-cache-dir', '--require-hashes', '-r', 'requirements-test.lock']) + with open(os.environ['GITHUB_PATH'], 'a') as paths: + print(tools, file=paths) + PYTHON + shell: bash + env: + PYTHON_EXECUTABLE: ${{ steps.python.outputs.python-path }} +``` + +Keep pytest and all transitive dependencies pinned with hashes in the candidate's +`requirements-test.lock`. The verifier can remain `python -m pytest`. Adapt runtime +versions and dependency commands to your project; these examples do not infer them. +The example disables pip's cache so platform-specific cache discovery cannot create +publishable files in the candidate. To use a cache, set its path explicitly under +the private HOME or an ignored dependency directory. + +For Node/pnpm: + +```yaml +name: Prepare project verification +runs: + using: composite + steps: + - uses: actions/setup-node@v7 + with: + node-version: '24.x' + package-manager-cache: false + - uses: pnpm/action-setup@v5 + with: + version: 10.12.4 + dest: ${{ env.HOME }}/pnpm + standalone: false + - run: pnpm install --frozen-lockfile --ignore-scripts + shell: bash +``` + +The action may include reviewed auxiliary scripts, referenced through +`${{ github.action_path }}`. Use native external `uses` actions and `run` steps; +nested local `uses: ./...` actions are rejected because their deferred post steps +could otherwise load candidate code outside trusted staging. Review external action versions as +part of the executable setup. The composite inherits the job's operating system; +its metadata cannot declare jobs, runners or a matrix. + +Each command gets a **fresh private HOME**. These dependency-only profiles stay +private until native post-job actions finish, then Copilot's final post cleanup +removes them. They are never reused by another command. Keep dependencies that +must survive native post steps under the private HOME, an ignored candidate directory +or the trusted runner tool cache, rather than inside the reserved action staging folder. + +Native setup exports tools through normal Actions mechanisms such as `GITHUB_PATH` +and `JAVA_HOME`; no custom JSON helper protocol is required. Copilot accepts up to +eight new unique absolute PATH directories, each at most 2,048 characters, and +JAVA_HOME. They must exist inside the candidate workspace, its private HOME or the +captured `RUNNER_TOOL_CACHE`; unsafe links, traversal and escaping paths are rejected. +The internal closed runtime result is bounded to 16 KiB. Other `GITHUB_ENV` values +are not forwarded to verification. Use complete executable paths or supported +runtime directories when a tool needs additional environment configuration. + +Copilot's PAT, provider/model inputs, agent auth stores and evidence token are not +passed to the consumer action or verifier. The native runner and external actions +retain normal GitHub Actions facilities and the job's scoped `GITHUB_TOKEN` context. +This is the existing trusted-runner boundary, not an OS sandbox against malicious +maintainer code or service-level credentials. Review setup as executable code and +avoid printing secrets. Agent credential profiles close before verification. + +A native setup step has a fixed **five-minute timeout**; the worker also rejects +readiness after 300 seconds. There are at most twenty setup boundaries per operation, +one for each configured command. No automatic setup retry or limit override exists. +The action directory is bounded to 32 regular files, 64 KiB each and 256 KiB total; +its metadata must be `action.yml`, with `runs.using: composite` and 1–32 steps. +Use portable ASCII asset paths (letters, digits, `_`, `.`, `-` and `/`), at most +200 characters; parent/empty segments and Windows reserved filenames are rejected. +Native setup logs are visible in the run; there is no 64-KiB helper-output ceiling. +Copilot's own error/result comments contain only a closed cause, fixed path and +pinned trusted SHA, never copied raw setup output. + +Setup must install dependencies without changing publishable code or lockfiles. +Copilot compares HEAD, branch, index, tracked edits and publishable untracked file +contents, types and executable modes before allowing the verifier to continue. Executable lookup uses that verifier's +validated PATH, so its private Python/pnpm takes precedence over the worker tools. Observations are bounded to +16 MiB, 256 untracked files and 16 MiB aggregate untracked contents. Ignore dependency +caches and keep source changes within these bounds. Failure, changed candidate, +invalid runtime output, timeout or cancellation withholds verification and push; +branch synchronization follows its existing abort/recovery. + +Read-only questions/status, dry runs, already-aligned branches and empty command +lists execute no setup. Snapshot failures remain deferred until verification. +Setup/install/update preserves this consumer-owned directory and all its assets. + +If a runner crash leaves an unknown reserved staging directory, preparation fails +closed rather than overwriting it. Confirm the prior job has stopped, inspect its +run, and remove only that stale `.git/copilot-verification-setup` directory before +retrying. Normal completion/cancellation uses the Action post cleanup. + +Native composite execution is available in the generated **GitHub Actions** +callbacks. The local CLI cannot execute `uses` steps: opted-in local verification +fails with `native-workflow-required`. Run the authorized operation in Actions, +or keep the opt-in disabled and provision your local dependencies normally. +Direct custom callers must implement the documented generated native callback +structure; setting the flag on an old single Action step is insufficient. + +To roll back, set `VERIFICATION_SETUP=false` and provision dependencies before +Copilot in the same job. The unreleased `.copilot/verification-setup.mjs` prototype +is superseded; it is not executed or installed by this contract. + ## Reused credential evidence The reuse journey performs GET-only inspection of trusted installed health diff --git a/scripts/coverage-budgets.json b/scripts/coverage-budgets.json index 4336d03af..784bc5834 100644 --- a/scripts/coverage-budgets.json +++ b/scripts/coverage-budgets.json @@ -900,6 +900,37 @@ } ], "successMessage": "approval evidence diagnostics coverage: PASS (95% lines/statements, 90% branches/functions)" + }, + { + "name": "Verification setup", + "missingEntryLabel": "verification setup", + "rules": [ + { + "files": [ + "src/application/policies/verification_setup_policy.ts" + ], + "mode": "each", + "thresholdProfile": "default" + }, + { + "files": [ + "src/infrastructure/verification_setup_snapshot.ts", + "src/infrastructure/verification_setup_result.ts", + "src/infrastructure/verification_setup_runner.ts", + "src/infrastructure/native_verification_protocol.ts", + "src/infrastructure/native_verification_candidate.ts", + "src/infrastructure/native_verification_staging.ts", + "src/infrastructure/native_verification_controller.ts", + "src/infrastructure/native_verification_profiles.ts", + "src/infrastructure/native_verification_redaction.ts", + "src/infrastructure/verification_executable.ts", + "src/infrastructure/native_verification_source_runtime.ts" + ], + "mode": "aggregate", + "thresholdProfile": "default" + } + ], + "successMessage": "verification setup coverage: PASS (95% lines/statements, 90% branches/functions)" } ] } diff --git a/scripts/generate-native-verification-workflows.cjs b/scripts/generate-native-verification-workflows.cjs new file mode 100644 index 000000000..b8110af05 --- /dev/null +++ b/scripts/generate-native-verification-workflows.cjs @@ -0,0 +1,63 @@ +const fs = require('node:fs'); +const path = require('node:path'); +const yaml = require('js-yaml'); + +const MAX_REQUESTS = 20; +const ACTION = './.git/copilot-verification-setup'; +const { SOURCE_ACTION } = require('./prepare-native-source-action.cjs'); +const MARKER = ' # copilot:native-verification:start'; +const CALLBACKS = ['copilot_issue_comment.yml', 'copilot_pull_request_comment.yml']; + +function phaseSteps(action, inputAlias = 'copilot-inputs', id = 'copilot') { + const steps = []; + let previous = id; + for (let count = 1; count <= MAX_REQUESTS; count++) { + const setup = `verification_setup_${count}`; + const continuation = `verification_continue_${count}`; + const pending = `steps.${previous}.outputs.verification-setup-pending == 'true'`; + const output = key => `\${{ steps.${previous}.outputs.verification-${key} }}`; + steps.push({ name: `Prepare repository verification ${count}`, id: setup, + if: `\${{ !cancelled() && steps.${previous}.outcome == 'success' && ${pending} }}`, uses: ACTION, 'timeout-minutes': 5, + env: Object.fromEntries(['HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PATH', 'JAVA_HOME', 'TMP', 'TEMP', 'TMPDIR'].map(key => [key, output(key.toLowerCase())])) }); + steps.push({ name: `Continue repository verification ${count}`, id: continuation, + if: `\${{ always() && ${pending} }}`, uses: action, with: { alias: inputAlias }, + env: { COPILOT_NATIVE_PHASE: 'continue', COPILOT_NATIVE_SOURCE: `\${{ steps.${id}.outputs.verification-source }}`, COPILOT_NATIVE_DIGEST: `\${{ steps.${id}.outputs.verification-digest }}`, COPILOT_NATIVE_GIT: `\${{ steps.${id}.outputs.verification-git }}`, COPILOT_NATIVE_TICKET: `\${{ steps.${id}.outputs.verification-ticket }}`, + COPILOT_NATIVE_REQUEST: `\${{ steps.${previous}.outputs.verification-request }}`, + COPILOT_NATIVE_OUTCOME: `\${{ steps.${setup}.outcome }}` } }); + previous = continuation; + } + steps.push({ name: 'Complete repository verification', id: 'verification_complete', + if: `\${{ always() && steps.${id}.outputs.verification-ticket != '' }}`, uses: action, with: { alias: inputAlias }, + env: { COPILOT_NATIVE_PHASE: 'finish', COPILOT_NATIVE_SOURCE: `\${{ steps.${id}.outputs.verification-source }}`, COPILOT_NATIVE_DIGEST: `\${{ steps.${id}.outputs.verification-digest }}`, COPILOT_NATIVE_GIT: `\${{ steps.${id}.outputs.verification-git }}`, COPILOT_NATIVE_TICKET: `\${{ steps.${id}.outputs.verification-ticket }}` } }); + return steps; +} + +function renderSteps(action, alias = 'copilot-inputs', id = 'copilot') { + return yaml.dump(phaseSteps(action, alias, id), { lineWidth: -1, noRefs: true, quotingType: '"' }) + .replace(/with:\n alias: ([^\n]+)/g, 'with: *$1') + .split('\n').map(line => line ? ` ${line}` : '').join('\n'); +} + +function generate(file) { + let text = fs.readFileSync(file, 'utf8').split(MARKER)[0].trimEnd() + '\n'; + const action = file.startsWith('setup/') ? 'vypdev/copilot@v3' : SOURCE_ACTION; + if (!file.startsWith('setup/')) { + text = text.replace(' - uses: ./\n', ` - name: Snapshot the Copilot runtime before candidate changes\n id: prepare_native_runtime\n run: node scripts/prepare-native-source-action.cjs\n shell: bash\n\n - uses: ${SOURCE_ACTION}\n`); + if (!text.includes(' COPILOT_NATIVE_RUNTIME_CACHE:')) text = text.replace(' COPILOT_NATIVE_PHASE: prepare\n', ' COPILOT_NATIVE_PHASE: prepare\n COPILOT_NATIVE_RUNTIME_CACHE: ${{ steps.prepare_native_runtime.outputs.path }}\n'); + } + const start = ` - uses: ${action}\n`; + text = text.replace(/(?: id: copilot\n)+/g, ''); + if (text.includes(start)) text = text.replace(start, ` - uses: ${action}\n id: copilot\n`); + text = text.replace(' with:\n debug:', ' with: &copilot-inputs\n debug:'); + // Distributed templates start with agent inputs rather than debug. + const index = text.indexOf(' id: copilot\n'); + const before = text.slice(0, index); + let operation = text.slice(index); + operation = operation.replace(' with:\n', ' with: &copilot-inputs\n'); + if (!operation.includes(' COPILOT_NATIVE_PHASE: prepare')) operation = operation.replace(' env:\n', ' env:\n COPILOT_NATIVE_PHASE: prepare\n'); + text = before + operation; + fs.writeFileSync(file, `${text}\n${MARKER}\n${renderSteps(action)} # copilot:native-verification:end\n`); +} + +if (require.main === module) for (const directory of ['.github/workflows', 'setup/workflows']) for (const file of CALLBACKS) generate(path.join(directory, file)); +module.exports = { MAX_REQUESTS, ACTION, SOURCE_ACTION, CALLBACKS, phaseSteps, renderSteps }; diff --git a/scripts/prepare-native-source-action.cjs b/scripts/prepare-native-source-action.cjs new file mode 100644 index 000000000..8863d6c79 --- /dev/null +++ b/scripts/prepare-native-source-action.cjs @@ -0,0 +1,42 @@ +const fs = require('node:fs'); +const path = require('node:path'); +const crypto = require('node:crypto'); + +const SOURCE_ACTION = './../.copilot-native-runtime'; +const FIXTURE_ACTION = './../.copilot-native-fixture-runtime'; +const RECEIPT = '.copilot-runtime-snapshot.json'; + +function prepareNativeSourceAction(workspace, fixture = false) { + workspace = fs.realpathSync(workspace); + const target = path.resolve(workspace, fixture ? FIXTURE_ACTION : SOURCE_ACTION); + const receipt = path.join(target, RECEIPT); + if (fs.existsSync(target)) { + const stat = fs.lstatSync(target); + if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('Native runtime snapshot path is not an owned directory.'); + let previous; + try { previous = JSON.parse(fs.readFileSync(receipt, 'utf8')); } catch { throw new Error('Native runtime snapshot directory is not owned by Copilot.'); } + if (previous?.kind !== 'copilot-native-runtime' || previous.workspace !== workspace) throw new Error('Native runtime snapshot ownership does not match.'); + fs.rmSync(target, { recursive: true, force: true }); + } + fs.mkdirSync(target, { mode: 0o700 }); + const source = fixture ? path.join(workspace, '.github/actions/copilot-native-fixture') : workspace; + const files = fixture ? ['action.yml', 'dist/index.js'] : ['action.yml', 'build/github_action/index.js']; + const hashes = {}; + for (const file of files) { + const from = path.join(source, file); + if (!fs.lstatSync(from).isFile() || fs.lstatSync(from).isSymbolicLink()) throw new Error('Native runtime source must be a regular file.'); + const data = fs.readFileSync(from); + fs.mkdirSync(path.dirname(path.join(target, file)), { recursive: true, mode: 0o700 }); + fs.writeFileSync(path.join(target, file), data, { mode: 0o600, flag: 'wx' }); + hashes[file] = crypto.createHash('sha256').update(data).digest('hex'); + } + if (!fixture && fs.existsSync(path.join(workspace, 'setup'))) fs.cpSync(path.join(workspace, 'setup'), path.join(target, 'setup'), { recursive: true, dereference: false }); + fs.writeFileSync(receipt, JSON.stringify({ kind: 'copilot-native-runtime', workspace, hashes }), { mode: 0o600, flag: 'wx' }); + return target; +} + +if (require.main === module) { + const target = prepareNativeSourceAction(process.cwd(), process.argv.includes('--fixture')); + if (process.env.GITHUB_OUTPUT) fs.appendFileSync(process.env.GITHUB_OUTPUT, `path=${target}\n`); +} +module.exports = { prepareNativeSourceAction, SOURCE_ACTION, FIXTURE_ACTION }; diff --git a/scripts/validate-workflow-contract.cjs b/scripts/validate-workflow-contract.cjs index 64a69df77..93bd54194 100644 --- a/scripts/validate-workflow-contract.cjs +++ b/scripts/validate-workflow-contract.cjs @@ -144,9 +144,15 @@ function relativeWorkflow(file) { return path.relative(repositoryRoot, file).replaceAll(path.sep, '/'); } +function expectedCopilotAction(relativeFile) { + if (relativeFile.startsWith('setup/workflows/')) return DISTRIBUTED_COPILOT_ACTION; + const native = require('./generate-native-verification-workflows.cjs'); + return native.CALLBACKS.includes(path.basename(relativeFile)) ? native.SOURCE_ACTION : './'; +} + function isCopilotAction(step) { return typeof step?.uses === 'string' - && (step.uses === './' || /(?:^|\/)copilot@/.test(step.uses)); + && (step.uses === './' || step.uses === './../.copilot-native-runtime' || /(?:^|\/)copilot@/.test(step.uses)); } function shouldPersistCheckoutCredentials(relativeFile, jobId) { @@ -165,7 +171,7 @@ function assertMajorActionReferences(file, workflow) { .filter(value => typeof value === 'string'); for (const uses of actionUses) { if (isCopilotAction({ uses })) { - const expected = relativeFile.startsWith('setup/workflows/') ? DISTRIBUTED_COPILOT_ACTION : './'; + const expected = expectedCopilotAction(relativeFile); if (uses !== expected) { throw new Error(`${relativeFile} job ${jobId} must invoke Copilot with ${expected}.`); } @@ -1369,7 +1375,7 @@ function assertSequentialMutationWorkflow(file, workflow) { function assertIssueWorkflowProfileInput(file, workflow) { if (!ISSUE_WORKFLOW_PROFILE_FILES.has(path.basename(file))) return; const relativeFile = relativeWorkflow(file); - const expectedAction = relativeFile.startsWith('setup/workflows/') ? DISTRIBUTED_COPILOT_ACTION : './'; + const expectedAction = expectedCopilotAction(relativeFile); const actionSteps = Object.values(workflow.jobs ?? {}).flatMap(job => job?.steps ?? []) .filter(step => step?.uses === expectedAction); if (actionSteps.length === 0 || actionSteps.some(step => @@ -1387,21 +1393,45 @@ function assertIssueBranchConfigurationInputs(file, workflow) { ? { 'issue-managed-branches': "${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }}", 'pre-branch-sdd': "${{ vars.PRE_BRANCH_SDD || 'false' }}", + 'verification-setup': "${{ vars.VERIFICATION_SETUP || 'false' }}", } : { 'issue-managed-branches': branches.issueManagedBranches, 'pre-branch-sdd': branches.preBranchSdd, + 'verification-setup': "${{ vars.VERIFICATION_SETUP || 'false' }}", }; const actionSteps = Object.values(workflow.jobs ?? {}).flatMap(job => job?.steps ?? []) .filter(isCopilotAction); if (actionSteps.length === 0 || actionSteps.some(step => Object.entries(expected).some(([input, value]) => step.with?.[input] !== value))) { - throw new Error(`${relativeFile} must pass consistent issue-managed-branches and pre-branch-sdd inputs to every Copilot callback.`); + throw new Error(`${relativeFile} must pass consistent issue-managed-branches, pre-branch-sdd and verification-setup inputs to every Copilot callback.`); } } +function assertNativeVerificationWorkflow(file, workflow) { + const contract = require('./generate-native-verification-workflows.cjs'); + if (!contract.CALLBACKS.includes(path.basename(file))) return; + const relativeFile = relativeWorkflow(file); + const action = expectedCopilotAction(relativeFile); + const jobs = Object.values(workflow.jobs ?? {}).filter(job => (job.steps ?? []).some(step => step.id === 'copilot')); + if (jobs.length !== 1) throw new Error(`${relativeFile} must have exactly one native verification operation.`); + const steps = jobs[0].steps; + const start = steps.findIndex(step => step.id === 'copilot'); + const original = steps[start]; + if (!relativeFile.startsWith('setup/workflows/')) { + const snapshot = steps[start - 1]; + if (snapshot?.id !== 'prepare_native_runtime' || snapshot.run !== 'node scripts/prepare-native-source-action.cjs' + || original.env?.COPILOT_NATIVE_RUNTIME_CACHE !== '${{ steps.prepare_native_runtime.outputs.path }}') throw new Error(`${relativeFile} must snapshot its Action runtime before candidate changes.`); + } + if (original.uses !== action || original.env?.COPILOT_NATIVE_PHASE !== 'prepare') throw new Error(`${relativeFile} must prepare the admitted native operation.`); + const expected = contract.phaseSteps(action).map(step => ({ ...step, ...(step.with ? { with: original.with } : {}) })); + const actual = steps.slice(start + 1); + if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error(`${relativeFile} must retain the exact bounded native setup/continuation contract, runtime-only environment, timeout and completion cleanup.`); +} + function validateWorkflow(file, workflow) { if (!workflow || typeof workflow !== 'object') throw new Error('workflow document is empty.'); + assertNativeVerificationWorkflow(file, workflow); assertManualRepoWiseWorkflow(file, workflow); assertDirectEventTriggers(file, workflow); assertApprovalObserverWorkflow(file, workflow); @@ -1447,6 +1477,7 @@ function main() { if (require.main === module) main(); module.exports = { + assertNativeVerificationWorkflow, QUEUE_WAIT_MINUTES, MIN_QUEUE_JOB_TIMEOUT_MINUTES, DEPLOYMENT_VALIDATION_TIMEOUT_MINUTES, diff --git a/setup/workflows/copilot_commit.yml b/setup/workflows/copilot_commit.yml index e289a0932..c5421cf99 100644 --- a/setup/workflows/copilot_commit.yml +++ b/setup/workflows/copilot_commit.yml @@ -43,6 +43,7 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 with: @@ -62,6 +63,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} desired-reviewers-count: ${{ vars.DESIRED_REVIEWERS_COUNT || '1' }} diff --git a/setup/workflows/copilot_issue.yml b/setup/workflows/copilot_issue.yml index 6955abe07..05ce55b50 100644 --- a/setup/workflows/copilot_issue.yml +++ b/setup/workflows/copilot_issue.yml @@ -21,6 +21,7 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 with: @@ -39,6 +40,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} diff --git a/setup/workflows/copilot_issue_comment.yml b/setup/workflows/copilot_issue_comment.yml index d977a5eba..ea8c42d0a 100644 --- a/setup/workflows/copilot_issue_comment.yml +++ b/setup/workflows/copilot_issue_comment.yml @@ -23,9 +23,11 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 - with: + id: copilot + with: &copilot-inputs repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }} issues-locale: ${{ vars.ISSUES_LOCALE || '' }} pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }} @@ -41,6 +43,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} @@ -97,6 +100,7 @@ jobs: project-ids: ${{ vars.PROJECT_IDS }} token: ${{ secrets.PAT }} env: + COPILOT_NATIVE_PHASE: prepare AGENT_PROVIDER: ${{ vars.AGENT_PROVIDER || 'codex' }} AGENT_MODEL_PROVIDER: ${{ vars.AGENT_MODEL_PROVIDER || 'openai' }} AGENT_MODEL: ${{ vars.AGENT_MODEL }} @@ -112,3 +116,577 @@ jobs: CURSOR_API_KEY: ${{ (vars.AGENT_PROVIDER == 'cursor' || vars.FINDINGS_PROVIDER == 'cursor' || vars.FIXER_PROVIDER == 'cursor' || vars.PLANNER_PROVIDER == 'cursor' || vars.REVIEWER_PROVIDER == 'cursor' || vars.TESTER_PROVIDER == 'cursor') && secrets.CURSOR_API_KEY || '' }} CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} COPILOT_EVIDENCE_TOKEN: ${{ github.token }} + + # copilot:native-verification:start + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot.outcome == 'success' && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot.outputs.verification-localappdata }} + PATH: ${{ steps.copilot.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot.outputs.verification-java_home }} + TMP: ${{ steps.copilot.outputs.verification-tmp }} + TEMP: ${{ steps.copilot.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot.outputs.verification-ticket != '' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + # copilot:native-verification:end diff --git a/setup/workflows/copilot_pull_request.yml b/setup/workflows/copilot_pull_request.yml index f6dd20108..1127ec283 100644 --- a/setup/workflows/copilot_pull_request.yml +++ b/setup/workflows/copilot_pull_request.yml @@ -39,6 +39,7 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 with: @@ -58,6 +59,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} desired-reviewers-count: ${{ vars.DESIRED_REVIEWERS_COUNT || '1' }} diff --git a/setup/workflows/copilot_pull_request_comment.yml b/setup/workflows/copilot_pull_request_comment.yml index b9e2bae5b..c5e05f433 100644 --- a/setup/workflows/copilot_pull_request_comment.yml +++ b/setup/workflows/copilot_pull_request_comment.yml @@ -23,9 +23,11 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 - with: + id: copilot + with: &copilot-inputs repository-locale: ${{ vars.REPOSITORY_LOCALE || 'en-US' }} issues-locale: ${{ vars.ISSUES_LOCALE || '' }} pull-requests-locale: ${{ vars.PULL_REQUESTS_LOCALE || '' }} @@ -42,6 +44,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} desired-reviewers-count: ${{ vars.DESIRED_REVIEWERS_COUNT || '1' }} @@ -97,6 +100,7 @@ jobs: project-ids: ${{ vars.PROJECT_IDS }} token: ${{ secrets.PAT }} env: + COPILOT_NATIVE_PHASE: prepare AGENT_PROVIDER: ${{ vars.AGENT_PROVIDER || 'codex' }} AGENT_MODEL_PROVIDER: ${{ vars.AGENT_MODEL_PROVIDER || 'openai' }} AGENT_MODEL: ${{ vars.AGENT_MODEL }} @@ -112,3 +116,577 @@ jobs: CURSOR_API_KEY: ${{ (vars.AGENT_PROVIDER == 'cursor' || vars.FINDINGS_PROVIDER == 'cursor' || vars.FIXER_PROVIDER == 'cursor' || vars.PLANNER_PROVIDER == 'cursor' || vars.REVIEWER_PROVIDER == 'cursor' || vars.TESTER_PROVIDER == 'cursor') && secrets.CURSOR_API_KEY || '' }} CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} COPILOT_EVIDENCE_TOKEN: ${{ github.token }} + + # copilot:native-verification:start + - name: Prepare repository verification 1 + id: verification_setup_1 + if: ${{ !cancelled() && steps.copilot.outcome == 'success' && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.copilot.outputs.verification-home }} + USERPROFILE: ${{ steps.copilot.outputs.verification-userprofile }} + APPDATA: ${{ steps.copilot.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.copilot.outputs.verification-localappdata }} + PATH: ${{ steps.copilot.outputs.verification-path }} + JAVA_HOME: ${{ steps.copilot.outputs.verification-java_home }} + TMP: ${{ steps.copilot.outputs.verification-tmp }} + TEMP: ${{ steps.copilot.outputs.verification-temp }} + TMPDIR: ${{ steps.copilot.outputs.verification-tmpdir }} + - name: Continue repository verification 1 + id: verification_continue_1 + if: ${{ always() && steps.copilot.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.copilot.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_1.outcome }} + - name: Prepare repository verification 2 + id: verification_setup_2 + if: ${{ !cancelled() && steps.verification_continue_1.outcome == 'success' && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_1.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_1.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_1.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_1.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_1.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_1.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_1.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_1.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_1.outputs.verification-tmpdir }} + - name: Continue repository verification 2 + id: verification_continue_2 + if: ${{ always() && steps.verification_continue_1.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_1.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_2.outcome }} + - name: Prepare repository verification 3 + id: verification_setup_3 + if: ${{ !cancelled() && steps.verification_continue_2.outcome == 'success' && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_2.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_2.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_2.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_2.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_2.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_2.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_2.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_2.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_2.outputs.verification-tmpdir }} + - name: Continue repository verification 3 + id: verification_continue_3 + if: ${{ always() && steps.verification_continue_2.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_2.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_3.outcome }} + - name: Prepare repository verification 4 + id: verification_setup_4 + if: ${{ !cancelled() && steps.verification_continue_3.outcome == 'success' && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_3.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_3.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_3.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_3.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_3.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_3.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_3.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_3.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_3.outputs.verification-tmpdir }} + - name: Continue repository verification 4 + id: verification_continue_4 + if: ${{ always() && steps.verification_continue_3.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_3.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_4.outcome }} + - name: Prepare repository verification 5 + id: verification_setup_5 + if: ${{ !cancelled() && steps.verification_continue_4.outcome == 'success' && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_4.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_4.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_4.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_4.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_4.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_4.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_4.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_4.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_4.outputs.verification-tmpdir }} + - name: Continue repository verification 5 + id: verification_continue_5 + if: ${{ always() && steps.verification_continue_4.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_4.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_5.outcome }} + - name: Prepare repository verification 6 + id: verification_setup_6 + if: ${{ !cancelled() && steps.verification_continue_5.outcome == 'success' && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_5.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_5.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_5.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_5.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_5.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_5.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_5.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_5.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_5.outputs.verification-tmpdir }} + - name: Continue repository verification 6 + id: verification_continue_6 + if: ${{ always() && steps.verification_continue_5.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_5.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_6.outcome }} + - name: Prepare repository verification 7 + id: verification_setup_7 + if: ${{ !cancelled() && steps.verification_continue_6.outcome == 'success' && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_6.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_6.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_6.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_6.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_6.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_6.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_6.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_6.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_6.outputs.verification-tmpdir }} + - name: Continue repository verification 7 + id: verification_continue_7 + if: ${{ always() && steps.verification_continue_6.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_6.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_7.outcome }} + - name: Prepare repository verification 8 + id: verification_setup_8 + if: ${{ !cancelled() && steps.verification_continue_7.outcome == 'success' && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_7.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_7.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_7.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_7.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_7.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_7.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_7.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_7.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_7.outputs.verification-tmpdir }} + - name: Continue repository verification 8 + id: verification_continue_8 + if: ${{ always() && steps.verification_continue_7.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_7.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_8.outcome }} + - name: Prepare repository verification 9 + id: verification_setup_9 + if: ${{ !cancelled() && steps.verification_continue_8.outcome == 'success' && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_8.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_8.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_8.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_8.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_8.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_8.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_8.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_8.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_8.outputs.verification-tmpdir }} + - name: Continue repository verification 9 + id: verification_continue_9 + if: ${{ always() && steps.verification_continue_8.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_8.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_9.outcome }} + - name: Prepare repository verification 10 + id: verification_setup_10 + if: ${{ !cancelled() && steps.verification_continue_9.outcome == 'success' && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_9.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_9.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_9.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_9.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_9.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_9.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_9.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_9.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_9.outputs.verification-tmpdir }} + - name: Continue repository verification 10 + id: verification_continue_10 + if: ${{ always() && steps.verification_continue_9.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_9.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_10.outcome }} + - name: Prepare repository verification 11 + id: verification_setup_11 + if: ${{ !cancelled() && steps.verification_continue_10.outcome == 'success' && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_10.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_10.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_10.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_10.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_10.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_10.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_10.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_10.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_10.outputs.verification-tmpdir }} + - name: Continue repository verification 11 + id: verification_continue_11 + if: ${{ always() && steps.verification_continue_10.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_10.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_11.outcome }} + - name: Prepare repository verification 12 + id: verification_setup_12 + if: ${{ !cancelled() && steps.verification_continue_11.outcome == 'success' && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_11.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_11.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_11.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_11.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_11.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_11.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_11.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_11.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_11.outputs.verification-tmpdir }} + - name: Continue repository verification 12 + id: verification_continue_12 + if: ${{ always() && steps.verification_continue_11.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_11.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_12.outcome }} + - name: Prepare repository verification 13 + id: verification_setup_13 + if: ${{ !cancelled() && steps.verification_continue_12.outcome == 'success' && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_12.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_12.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_12.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_12.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_12.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_12.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_12.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_12.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_12.outputs.verification-tmpdir }} + - name: Continue repository verification 13 + id: verification_continue_13 + if: ${{ always() && steps.verification_continue_12.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_12.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_13.outcome }} + - name: Prepare repository verification 14 + id: verification_setup_14 + if: ${{ !cancelled() && steps.verification_continue_13.outcome == 'success' && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_13.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_13.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_13.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_13.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_13.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_13.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_13.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_13.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_13.outputs.verification-tmpdir }} + - name: Continue repository verification 14 + id: verification_continue_14 + if: ${{ always() && steps.verification_continue_13.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_13.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_14.outcome }} + - name: Prepare repository verification 15 + id: verification_setup_15 + if: ${{ !cancelled() && steps.verification_continue_14.outcome == 'success' && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_14.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_14.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_14.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_14.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_14.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_14.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_14.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_14.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_14.outputs.verification-tmpdir }} + - name: Continue repository verification 15 + id: verification_continue_15 + if: ${{ always() && steps.verification_continue_14.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_14.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_15.outcome }} + - name: Prepare repository verification 16 + id: verification_setup_16 + if: ${{ !cancelled() && steps.verification_continue_15.outcome == 'success' && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_15.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_15.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_15.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_15.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_15.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_15.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_15.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_15.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_15.outputs.verification-tmpdir }} + - name: Continue repository verification 16 + id: verification_continue_16 + if: ${{ always() && steps.verification_continue_15.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_15.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_16.outcome }} + - name: Prepare repository verification 17 + id: verification_setup_17 + if: ${{ !cancelled() && steps.verification_continue_16.outcome == 'success' && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_16.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_16.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_16.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_16.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_16.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_16.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_16.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_16.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_16.outputs.verification-tmpdir }} + - name: Continue repository verification 17 + id: verification_continue_17 + if: ${{ always() && steps.verification_continue_16.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_16.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_17.outcome }} + - name: Prepare repository verification 18 + id: verification_setup_18 + if: ${{ !cancelled() && steps.verification_continue_17.outcome == 'success' && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_17.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_17.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_17.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_17.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_17.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_17.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_17.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_17.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_17.outputs.verification-tmpdir }} + - name: Continue repository verification 18 + id: verification_continue_18 + if: ${{ always() && steps.verification_continue_17.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_17.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_18.outcome }} + - name: Prepare repository verification 19 + id: verification_setup_19 + if: ${{ !cancelled() && steps.verification_continue_18.outcome == 'success' && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_18.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_18.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_18.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_18.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_18.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_18.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_18.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_18.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_18.outputs.verification-tmpdir }} + - name: Continue repository verification 19 + id: verification_continue_19 + if: ${{ always() && steps.verification_continue_18.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_18.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_19.outcome }} + - name: Prepare repository verification 20 + id: verification_setup_20 + if: ${{ !cancelled() && steps.verification_continue_19.outcome == 'success' && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: ./.git/copilot-verification-setup + timeout-minutes: 5 + env: + HOME: ${{ steps.verification_continue_19.outputs.verification-home }} + USERPROFILE: ${{ steps.verification_continue_19.outputs.verification-userprofile }} + APPDATA: ${{ steps.verification_continue_19.outputs.verification-appdata }} + LOCALAPPDATA: ${{ steps.verification_continue_19.outputs.verification-localappdata }} + PATH: ${{ steps.verification_continue_19.outputs.verification-path }} + JAVA_HOME: ${{ steps.verification_continue_19.outputs.verification-java_home }} + TMP: ${{ steps.verification_continue_19.outputs.verification-tmp }} + TEMP: ${{ steps.verification_continue_19.outputs.verification-temp }} + TMPDIR: ${{ steps.verification_continue_19.outputs.verification-tmpdir }} + - name: Continue repository verification 20 + id: verification_continue_20 + if: ${{ always() && steps.verification_continue_19.outputs.verification-setup-pending == 'true' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: continue + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + COPILOT_NATIVE_REQUEST: ${{ steps.verification_continue_19.outputs.verification-request }} + COPILOT_NATIVE_OUTCOME: ${{ steps.verification_setup_20.outcome }} + - name: Complete repository verification + id: verification_complete + if: ${{ always() && steps.copilot.outputs.verification-ticket != '' }} + uses: vypdev/copilot@v3 + with: *copilot-inputs + env: + COPILOT_NATIVE_PHASE: finish + COPILOT_NATIVE_SOURCE: ${{ steps.copilot.outputs.verification-source }} + COPILOT_NATIVE_DIGEST: ${{ steps.copilot.outputs.verification-digest }} + COPILOT_NATIVE_GIT: ${{ steps.copilot.outputs.verification-git }} + COPILOT_NATIVE_TICKET: ${{ steps.copilot.outputs.verification-ticket }} + # copilot:native-verification:end diff --git a/setup/workflows/copilot_pull_request_review_state.yml b/setup/workflows/copilot_pull_request_review_state.yml index ab7cfce09..8942d1d7f 100644 --- a/setup/workflows/copilot_pull_request_review_state.yml +++ b/setup/workflows/copilot_pull_request_review_state.yml @@ -27,6 +27,7 @@ jobs: # Before Copilot, provision project runtimes/dependencies in this same job when # repository verification is configured. A separate CI job does not prepare it. # Declare reviewed versions in .copilot/verification-prerequisites.json. + # Optional trusted-main setup: VERIFICATION_SETUP=true and .github/actions/copilot-verification-setup/action.yml. # https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites - uses: vypdev/copilot@v3 with: @@ -46,6 +47,7 @@ jobs: chore-tree: ${{ vars.CHORE_TREE || 'chore' }} issue-managed-branches: ${{ vars.ISSUE_MANAGED_BRANCHES || 'true' }} pre-branch-sdd: ${{ vars.PRE_BRANCH_SDD || 'false' }} + verification-setup: ${{ vars.VERIFICATION_SETUP || 'false' }} reopen-issue-on-push: ${{ vars.REOPEN_ISSUE_ON_PUSH || 'true' }} desired-assignees-count: ${{ vars.DESIRED_ASSIGNEES_COUNT || '1' }} desired-reviewers-count: ${{ vars.DESIRED_REVIEWERS_COUNT || '1' }} diff --git a/specs/CATALOG.md b/specs/CATALOG.md index 46b62664a..1500839c4 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -16,7 +16,7 @@ debt or convert unknown historic intent into a design decision. | `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 63 paths · 2026-10-21 | | `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 104 paths · 2026-10-21 | | `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 72 paths · 2026-10-13 | -| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 5 companion | 326 paths · 2026-10-20 | +| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 5 companion | 328 paths · 2026-10-20 | | `local-web-setup-assistant` | Proposed | Offer a packaged, loopback-only Svelte setup interface over the existing engine with four-language guidance, English CLI help, basic/custom first-run paths, editable question and plan review, source-labelled GitHub facts, assisted CI and Project evidence, role-separated PATs, and truthful itemized outcomes; inspect existing configuration and apply independent, scope-aware runtime adjustments with permission, drift and read-back verification | [Local web setup assistant](./local-web-setup-assistant.md) + 1 companion | 238 paths · 2026-10-21 | | `guided-bot-pat-onboarding` | Proposed | Guide creation of the persistent workflow PAT, verify bot identity and grants, recover explicitly from wrong-account input within the session, and install the approved Actions Secret | [Guided bot PAT onboarding](./guided-bot-pat-onboarding.md) | 39 paths · 2026-10-07 | | `temporary-setup-operator-authorization` | Proposed | Guide creation of the one-run operator PAT through GitHub's official form, verify final setup access, and report user-owned deletion accurately | [Assisted setup PAT creation](./temporary-setup-operator-authorization.md) | 36 paths · 2026-10-18 | @@ -24,7 +24,7 @@ debt or convert unknown historic intent into a design decision. | `managed-issue-lifecycle` | As-built baseline | Convert typed issues into traceable work branches, project state, and lifecycle state | [Managed issue and branch lifecycle](./managed-issue-and-branch-lifecycle.md) | 71 paths · 2026-10-20 | | `comment-automation` | Implemented | Admit only explicit commands or exact mentions, then route them while protecting repository mutations | [Comment automation and authorization](./comment-automation-and-authorization.md) | 79 paths · 2026-10-11 | | `bugbot-analysis-and-autofix` | Implemented | Select one canonical PR, exhaustively analyze its bounded diff partitions, publish stable findings atomically, and apply authorized verified fixes | [Bugbot analysis, finding publication, and autofix](./bugbot-analysis-publication-and-autofix.md) + 2 companion | 109 paths · 2026-10-13 | -| `branch-synchronization` | Implemented | Observe parent drift with one localized status card and transition-only notifications, then safely merge a parent branch into a linked working branch | [Branch synchronization and conflict recovery](./branch-synchronization-and-conflict-recovery.md) + 1 companion | 90 paths · 2026-10-20 | +| `branch-synchronization` | Implemented | Observe parent drift with one localized status card and transition-only notifications, then safely merge a parent branch into a linked working branch | [Branch synchronization and conflict recovery](./branch-synchronization-and-conflict-recovery.md) + 2 companion | 119 paths · 2026-10-21 | | `pull-request-lifecycle` | Implemented | Enrich linked and unlinked pull requests with safe issue linkage, projects, metadata, reviewers, concise descriptions, and distinct workflow evidence | [Pull request lifecycle and enrichment](./pull-request-lifecycle-and-enrichment.md) | 77 paths · 2026-10-20 | | `agent-runtime` | Implemented | Resolve, provision, authenticate, authorize, and execute only the agent roles reachable by a run | [Agent runtime, provider, model, and role routing](./agent-runtime-provider-and-model-routing.md) + 1 companion | 99 paths · 2026-10-17 | | `cli-and-single-actions` | As-built baseline | Expose bounded local commands and workflow-dispatched operations through the shared application core | [CLI and single-action execution](./cli-and-single-action-execution.md) | 44 paths · 2026-10-19 | @@ -107,8 +107,8 @@ debt or convert unknown historic intent into a design decision. - Specifications: [`specs/setup-configuration-credentials-and-doctor.md`](./setup-configuration-credentials-and-doctor.md) · [`specs/setup-doctor-architecture-hardening.md`](./setup-doctor-architecture-hardening.md) · [`specs/setup-pat-permission-guidance-and-verification.md`](./setup-pat-permission-guidance-and-verification.md) · [`specs/github-runtime-doctor-command.md`](./github-runtime-doctor-command.md) · [`specs/organization-configuration-reuse-and-batch-setup.md`](./organization-configuration-reuse-and-batch-setup.md) · [`specs/setup-runner-profiles-and-installation-parity.md`](./setup-runner-profiles-and-installation-parity.md) - Workflows: [`setup/workflows/agent-cli-provisioning.yml`](../setup/workflows/agent-cli-provisioning.yml) · [`setup/workflows/copilot_credential_health.yml`](../setup/workflows/copilot_credential_health.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) · [`.github/workflows/agent-cli-provisioning.yml`](../.github/workflows/agent-cli-provisioning.yml) · [`.github/workflows/copilot_credential_health.yml`](../.github/workflows/copilot_credential_health.yml) · [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_branch_sync.yml`](../.github/workflows/copilot_branch_sync.yml) · [`setup/workflows/copilot_commit.yml`](../setup/workflows/copilot_commit.yml) · [`setup/workflows/copilot_branch_sync.yml`](../setup/workflows/copilot_branch_sync.yml) · [`setup/workflows/copilot_issue_comment.yml`](../setup/workflows/copilot_issue_comment.yml) · [`setup/workflows/copilot_pull_request_comment.yml`](../setup/workflows/copilot_pull_request_comment.yml) - Entrypoints: [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) · [`src/application/usecases/issue_comment_use_case.ts`](../src/application/usecases/issue_comment_use_case.ts) · [`src/application/usecases/pull_request_review_comment_use_case.ts`](../src/application/usecases/pull_request_review_comment_use_case.ts) -- Core code: [`src/domain/setup.ts`](../src/domain/setup.ts) · [`src/domain/setup_questionnaire.ts`](../src/domain/setup_questionnaire.ts) · [`src/domain/setup_token_permissions.ts`](../src/domain/setup_token_permissions.ts) · [`src/application/ports/setup_terminal_ports.ts`](../src/application/ports/setup_terminal_ports.ts) · [`src/application/ports/setup_wizard_ports.ts`](../src/application/ports/setup_wizard_ports.ts) · [`src/application/ports/branch_tag_ports.ts`](../src/application/ports/branch_tag_ports.ts) · [`src/application/policies/setup_issue_workflow_policy.ts`](../src/application/policies/setup_issue_workflow_policy.ts) · [`src/data/repository/release/repository_version_tags_query_repository.ts`](../src/data/repository/release/repository_version_tags_query_repository.ts) · [`src/application/ports/setup_token_permission_ports.ts`](../src/application/ports/setup_token_permission_ports.ts) · [`src/application/policies/setup_token_permission_evidence_policy.ts`](../src/application/policies/setup_token_permission_evidence_policy.ts) · [`src/application/policies/setup_token_permission_policy.ts`](../src/application/policies/setup_token_permission_policy.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/application/policies/setup_configuration_plan.ts`](../src/application/policies/setup_configuration_plan.ts) · [`src/application/policies/merge_setup_overrides_policy.ts`](../src/application/policies/merge_setup_overrides_policy.ts) · [`src/application/policies/setup_configuration_storage_policy.ts`](../src/application/policies/setup_configuration_storage_policy.ts) · [`src/application/policies/setup_doctor_message_catalog.ts`](../src/application/policies/setup_doctor_message_catalog.ts) · [`src/application/policies/setup_doctor_report_policy.ts`](../src/application/policies/setup_doctor_report_policy.ts) · [`src/application/policies/setup_journey_policy.ts`](../src/application/policies/setup_journey_policy.ts) · [`src/application/policies/setup_permission_summary_policy.ts`](../src/application/policies/setup_permission_summary_policy.ts) · [`src/application/usecases/setup/setup_wizard_use_case.ts`](../src/application/usecases/setup/setup_wizard_use_case.ts) · [`src/application/usecases/setup/setup_journey_use_case.ts`](../src/application/usecases/setup/setup_journey_use_case.ts) · [`src/application/usecases/setup/setup_session_coordinator.ts`](../src/application/usecases/setup/setup_session_coordinator.ts) · [`src/application/usecases/setup/setup_questionnaire_controller.ts`](../src/application/usecases/setup/setup_questionnaire_controller.ts) · [`src/application/usecases/setup/setup_credentials_use_case.ts`](../src/application/usecases/setup/setup_credentials_use_case.ts) · [`src/application/usecases/setup/setup_token_permissions_use_case.ts`](../src/application/usecases/setup/setup_token_permissions_use_case.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/usecases/setup/merge_queue_readiness_use_case.ts`](../src/application/usecases/setup/merge_queue_readiness_use_case.ts) · [`src/application/usecases/actions/initial_setup_workflow.ts`](../src/application/usecases/actions/initial_setup_workflow.ts) · [`src/application/usecases/actions/setup_resource_provisioning.ts`](../src/application/usecases/actions/setup_resource_provisioning.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/policies/setup_configuration_validation.ts`](../src/application/policies/setup_configuration_validation.ts) · [`src/infrastructure/setup_workspace_adapter.ts`](../src/infrastructure/setup_workspace_adapter.ts) · [`src/data/repository/repository_variables_repository.ts`](../src/data/repository/repository_variables_repository.ts) · [`src/infrastructure/github/ports/github_repository_variables_protocol.ts`](../src/infrastructure/github/ports/github_repository_variables_protocol.ts) · [`src/infrastructure/setup_remote_credential_health_adapter.ts`](../src/infrastructure/setup_remote_credential_health_adapter.ts) · [`src/infrastructure/setup_credential_validation_adapter.ts`](../src/infrastructure/setup_credential_validation_adapter.ts) · [`src/infrastructure/setup_token_permission_query_adapter.ts`](../src/infrastructure/setup_token_permission_query_adapter.ts) · [`src/infrastructure/setup_permission_actions_probe.ts`](../src/infrastructure/setup_permission_actions_probe.ts) · [`src/infrastructure/setup_permission_issue_probe.ts`](../src/infrastructure/setup_permission_issue_probe.ts) · [`src/infrastructure/setup_permission_issue_cleanup.ts`](../src/infrastructure/setup_permission_issue_cleanup.ts) · [`src/infrastructure/setup_permission_probe_journal.ts`](../src/infrastructure/setup_permission_probe_journal.ts) · [`src/infrastructure/setup_permission_probe_transaction.ts`](../src/infrastructure/setup_permission_probe_transaction.ts) · [`src/infrastructure/setup_permission_resource_probes.ts`](../src/infrastructure/setup_permission_resource_probes.ts) · [`src/infrastructure/setup_projects_probe_page_policy.ts`](../src/infrastructure/setup_projects_probe_page_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/cli/setup_question_renderer.ts`](../src/cli/setup_question_renderer.ts) · [`src/cli/setup_journey_presenter.ts`](../src/cli/setup_journey_presenter.ts) · [`src/cli/setup_plan_presenter.ts`](../src/cli/setup_plan_presenter.ts) · [`src/cli/setup_doctor_presenter.ts`](../src/cli/setup_doctor_presenter.ts) · [`src/cli/setup_prompt_rendering.ts`](../src/cli/setup_prompt_rendering.ts) · [`src/cli/setup_credential_prompt_adapter.ts`](../src/cli/setup_credential_prompt_adapter.ts) · [`src/cli/setup_credential_collection.ts`](../src/cli/setup_credential_collection.ts) · [`src/cli/commands/setup_execution.ts`](../src/cli/commands/setup_execution.ts) · [`src/cli/setup_token_permission_presenter.ts`](../src/cli/setup_token_permission_presenter.ts) · [`src/infrastructure/composition/setup_credentials_composition_root.ts`](../src/infrastructure/composition/setup_credentials_composition_root.ts) · [`src/infrastructure/composition/setup_token_permissions_composition_root.ts`](../src/infrastructure/composition/setup_token_permissions_composition_root.ts) · [`src/infrastructure/composition/setup_doctor_composition_root.ts`](../src/infrastructure/composition/setup_doctor_composition_root.ts) · [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/documentation_pat_exception_policy.cjs`](../scripts/documentation_pat_exception_policy.cjs) · [`scripts/validate-documentation-contract.cjs`](../scripts/validate-documentation-contract.cjs) · [`src/infrastructure/http_deadline.ts`](../src/infrastructure/http_deadline.ts) · [`src/infrastructure/setup_permission_denial.ts`](../src/infrastructure/setup_permission_denial.ts) · [`src/infrastructure/setup_permission_read_probe.ts`](../src/infrastructure/setup_permission_read_probe.ts) · [`src/infrastructure/setup_permission_read_evidence.ts`](../src/infrastructure/setup_permission_read_evidence.ts) · [`src/infrastructure/setup_permission_read_shape.ts`](../src/infrastructure/setup_permission_read_shape.ts) · [`src/infrastructure/setup_permission_projects_read.ts`](../src/infrastructure/setup_permission_projects_read.ts) · [`src/infrastructure/setup_permission_projects_access.ts`](../src/infrastructure/setup_permission_projects_access.ts) · [`src/infrastructure/setup_permission_workflow_cleanup.ts`](../src/infrastructure/setup_permission_workflow_cleanup.ts) · [`src/infrastructure/setup_permission_project_cleanup.ts`](../src/infrastructure/setup_permission_project_cleanup.ts) · [`src/infrastructure/setup_permission_probe_http.ts`](../src/infrastructure/setup_permission_probe_http.ts) · [`src/infrastructure/setup_permission_pull_request_probe.ts`](../src/infrastructure/setup_permission_pull_request_probe.ts) · [`src/infrastructure/git_authentication_environment.ts`](../src/infrastructure/git_authentication_environment.ts) · [`src/actions/github_action_input.ts`](../src/actions/github_action_input.ts) · [`src/infrastructure/setup_github_identity_query_adapter.ts`](../src/infrastructure/setup_github_identity_query_adapter.ts) · [`src/infrastructure/github_secret_encryption.ts`](../src/infrastructure/github_secret_encryption.ts) · [`src/infrastructure/setup_permission_organization_owner.ts`](../src/infrastructure/setup_permission_organization_owner.ts) · [`src/data/repository/github/github_actions_resource_inspector.ts`](../src/data/repository/github/github_actions_resource_inspector.ts) · [`src/data/repository/github/github_actions_resource_commands.ts`](../src/data/repository/github/github_actions_resource_commands.ts) · [`src/data/repository/github/github_actions_resource_collection.ts`](../src/data/repository/github/github_actions_resource_collection.ts) · [`src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts`](../src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts) · [`src/cli/setup_workflow_pat_storage_copy.ts`](../src/cli/setup_workflow_pat_storage_copy.ts) · [`src/cli/commands/setup_policy.ts`](../src/cli/commands/setup_policy.ts) · [`src/application/usecases/execution/execution_issue_number_policy.ts`](../src/application/usecases/execution/execution_issue_number_policy.ts) · [`src/application/usecases/execution/setup_execution_workflow.ts`](../src/application/usecases/execution/setup_execution_workflow.ts) · [`src/domain/github_runtime_doctor.ts`](../src/domain/github_runtime_doctor.ts) · [`src/domain/copilot_command.ts`](../src/domain/copilot_command.ts) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/policies/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/ports/github_runtime_doctor_ports.ts`](../src/application/ports/github_runtime_doctor_ports.ts) · [`src/application/policies/github_runtime_doctor_policy.ts`](../src/application/policies/github_runtime_doctor_policy.ts) · [`src/application/policies/doctor_workflow_condition_policy.ts`](../src/application/policies/doctor_workflow_condition_policy.ts) · [`src/application/policies/github_runtime_doctor_presentation_policy.ts`](../src/application/policies/github_runtime_doctor_presentation_policy.ts) · [`src/application/usecases/read_copilot_doctor_workflow.ts`](../src/application/usecases/read_copilot_doctor_workflow.ts) · [`src/data/repository/github/runtime_doctor_repository.ts`](../src/data/repository/github/runtime_doctor_repository.ts) · [`src/infrastructure/composition/github_runtime_doctor_composition_root.ts`](../src/infrastructure/composition/github_runtime_doctor_composition_root.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/application/policies/doctor_workflow_matrix_policy.ts`](../src/application/policies/doctor_workflow_matrix_policy.ts) · [`src/application/contracts/setup_batch_view.ts`](../src/application/contracts/setup_batch_view.ts) · [`src/application/policies/issue_resource_input_policy.ts`](../src/application/policies/issue_resource_input_policy.ts) · [`src/application/policies/setup_batch_access_policy.ts`](../src/application/policies/setup_batch_access_policy.ts) · [`src/application/policies/setup_batch_policy.ts`](../src/application/policies/setup_batch_policy.ts) · [`src/application/policies/setup_batch_retry_policy.ts`](../src/application/policies/setup_batch_retry_policy.ts) · [`src/application/policies/setup_batch_selection_policy.ts`](../src/application/policies/setup_batch_selection_policy.ts) · [`src/application/policies/setup_batch_view_policy.ts`](../src/application/policies/setup_batch_view_policy.ts) · [`src/application/policies/setup_credential_binding_policy.ts`](../src/application/policies/setup_credential_binding_policy.ts) · [`src/application/policies/setup_organization_import_policy.ts`](../src/application/policies/setup_organization_import_policy.ts) · [`src/application/policies/setup_organization_variable_bindings.ts`](../src/application/policies/setup_organization_variable_bindings.ts) · [`src/application/policies/setup_organization_variable_reuse_policy.ts`](../src/application/policies/setup_organization_variable_reuse_policy.ts) · [`src/application/ports/setup_batch_ports.ts`](../src/application/ports/setup_batch_ports.ts) · [`src/application/usecases/setup/apply_setup_batch_use_case.ts`](../src/application/usecases/setup/apply_setup_batch_use_case.ts) · [`src/application/usecases/setup/configure_setup_batch_target_use_case.ts`](../src/application/usecases/setup/configure_setup_batch_target_use_case.ts) · [`src/application/usecases/setup/prepare_setup_batch_use_case.ts`](../src/application/usecases/setup/prepare_setup_batch_use_case.ts) · [`src/application/usecases/setup/select_setup_batch_targets_use_case.ts`](../src/application/usecases/setup/select_setup_batch_targets_use_case.ts) · [`src/application/usecases/setup/setup_batch_session_use_case.ts`](../src/application/usecases/setup/setup_batch_session_use_case.ts) · [`src/cli/setup_batch_adapter.ts`](../src/cli/setup_batch_adapter.ts) · [`src/cli/setup_source_adapter.ts`](../src/cli/setup_source_adapter.ts) · [`src/domain/setup_batch.ts`](../src/domain/setup_batch.ts) · [`src/domain/setup_credential_health.ts`](../src/domain/setup_credential_health.ts) · [`src/domain/setup_organization_reuse.ts`](../src/domain/setup_organization_reuse.ts) · [`src/infrastructure/composition/setup_batch_composition_root.ts`](../src/infrastructure/composition/setup_batch_composition_root.ts) · [`src/infrastructure/github/ports/github_setup_batch_protocol.ts`](../src/infrastructure/github/ports/github_setup_batch_protocol.ts) · [`src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts`](../src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts) · [`src/infrastructure/setup_batch_github_apply_adapter.ts`](../src/infrastructure/setup_batch_github_apply_adapter.ts) · [`src/infrastructure/setup_batch_github_read_adapter.ts`](../src/infrastructure/setup_batch_github_read_adapter.ts) · [`src/infrastructure/setup_batch_journal_adapter.ts`](../src/infrastructure/setup_batch_journal_adapter.ts) · [`src/infrastructure/setup_batch_workspace_adapter.ts`](../src/infrastructure/setup_batch_workspace_adapter.ts) · [`src/infrastructure/setup_trusted_credential_health_adapter.ts`](../src/infrastructure/setup_trusted_credential_health_adapter.ts) · [`src/application/policies/setup_remote_facts_policy.ts`](../src/application/policies/setup_remote_facts_policy.ts) · [`src/cli/setup_confirmation_adapter.ts`](../src/cli/setup_confirmation_adapter.ts) · [`src/cli/web_setup_adapters.ts`](../src/cli/web_setup_adapters.ts) · [`scripts/validate-agent-documentation.cjs`](../scripts/validate-agent-documentation.cjs) · [`src/utils/setup_workflow_upgrade.ts`](../src/utils/setup_workflow_upgrade.ts) · [`src/domain/setup_runner.ts`](../src/domain/setup_runner.ts) · [`src/application/policies/setup_runner_policy.ts`](../src/application/policies/setup_runner_policy.ts) · [`src/application/policies/setup_runner_question_policy.ts`](../src/application/policies/setup_runner_question_policy.ts) · [`src/application/policies/setup_runner_guidance_policy.ts`](../src/application/policies/setup_runner_guidance_policy.ts) · [`src/application/ports/setup_runner_ports.ts`](../src/application/ports/setup_runner_ports.ts) · [`src/infrastructure/setup_runner_discovery_adapter.ts`](../src/infrastructure/setup_runner_discovery_adapter.ts) · [`src/infrastructure/composition/setup_runner_composition_root.ts`](../src/infrastructure/composition/setup_runner_composition_root.ts) · [`src/utils/setup_installation_review.ts`](../src/utils/setup_installation_review.ts) · [`src/cli/setup_installation_review_presenter.ts`](../src/cli/setup_installation_review_presenter.ts) · [`src/application/policies/setup_runner_installation_summary_policy.ts`](../src/application/policies/setup_runner_installation_summary_policy.ts) · [`src/utils/setup_files.ts`](../src/utils/setup_files.ts) · [`src/domain/specification_catalog_path_policy.cjs`](../src/domain/specification_catalog_path_policy.cjs) · [`src/domain/sdd_prerequisite_policy.cjs`](../src/domain/sdd_prerequisite_policy.cjs) · [`src/application/contracts/setup_documentation.ts`](../src/application/contracts/setup_documentation.ts) · [`src/application/policies/setup_question_documentation_policy.ts`](../src/application/policies/setup_question_documentation_policy.ts) · [`web/src/components/SetupHelp.svelte`](../web/src/components/SetupHelp.svelte) · [`web/src/components/CompletedSetupHelp.svelte`](../web/src/components/CompletedSetupHelp.svelte) · [`src/application/policies/verification_runtime_policy.ts`](../src/application/policies/verification_runtime_policy.ts) · [`src/infrastructure/verification_runtime_manifest.ts`](../src/infrastructure/verification_runtime_manifest.ts) · [`src/infrastructure/verification_runtime_preflight.ts`](../src/infrastructure/verification_runtime_preflight.ts) · [`src/infrastructure/untrusted_command_environment.ts`](../src/infrastructure/untrusted_command_environment.ts) · [`src/application/ports/git_ports.ts`](../src/application/ports/git_ports.ts) · [`src/application/ports/bugbot_git_ports.ts`](../src/application/ports/bugbot_git_ports.ts) · [`src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts`](../src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts) · [`src/application/usecases/steps/commit/bugbot/verify_command_runner.ts`](../src/application/usecases/steps/commit/bugbot/verify_command_runner.ts) -- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) · [`src/domain/__tests__/github_runtime_doctor.test.ts`](../src/domain/__tests__/github_runtime_doctor.test.ts) · [`src/domain/__tests__/copilot_command.test.ts`](../src/domain/__tests__/copilot_command.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_policy.test.ts) · [`src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts) · [`src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts`](../src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts) · [`src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts`](../src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts) · [`src/data/repository/github/__tests__/runtime_doctor_repository.test.ts`](../src/data/repository/github/__tests__/runtime_doctor_repository.test.ts) · [`src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts`](../src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts) · [`src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts`](../src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts) · [`src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts) · [`src/application/policies/__tests__/setup_approval_operator_permissions.test.ts`](../src/application/policies/__tests__/setup_approval_operator_permissions.test.ts) · [`src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts`](../src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts) · [`src/application/policies/__tests__/setup_batch_permission_policy.test.ts`](../src/application/policies/__tests__/setup_batch_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_policy.test.ts`](../src/application/policies/__tests__/setup_batch_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_retry_policy.test.ts`](../src/application/policies/__tests__/setup_batch_retry_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_selection_policy.test.ts`](../src/application/policies/__tests__/setup_batch_selection_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_approval_draft.test.ts`](../src/application/policies/__tests__/setup_organization_approval_draft.test.ts) · [`src/application/policies/__tests__/setup_organization_import_policy.test.ts`](../src/application/policies/__tests__/setup_organization_import_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_import_safety.test.ts`](../src/application/policies/__tests__/setup_organization_import_safety.test.ts) · [`src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts`](../src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts) · [`src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts) · [`src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts`](../src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts) · [`src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts`](../src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_batch_flow.test.ts`](../src/application/usecases/setup/__tests__/setup_batch_flow.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts) · [`src/cli/__tests__/setup_source_adapter.test.ts`](../src/cli/__tests__/setup_source_adapter.test.ts) · [`src/cli/__tests__/web_setup_batch_components.test.ts`](../src/cli/__tests__/web_setup_batch_components.test.ts) · [`src/cli/__tests__/web_setup_batch_selection.test.ts`](../src/cli/__tests__/web_setup_batch_selection.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_local_adapters.test.ts`](../src/infrastructure/__tests__/setup_batch_local_adapters.test.ts) · [`src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts`](../src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts) · [`src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts`](../src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts) · [`src/application/policies/__tests__/setup_batch_access_and_view.test.ts`](../src/application/policies/__tests__/setup_batch_access_and_view.test.ts) · [`src/cli/__tests__/setup_batch_adapter.test.ts`](../src/cli/__tests__/setup_batch_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_composition.test.ts`](../src/infrastructure/__tests__/setup_batch_composition.test.ts) · [`src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts`](../src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts) · [`src/utils/__tests__/setup_workflow_upgrade.test.ts`](../src/utils/__tests__/setup_workflow_upgrade.test.ts) · [`src/cli/__tests__/setup_pat_intent_adapter.test.ts`](../src/cli/__tests__/setup_pat_intent_adapter.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/policies/__tests__/setup_runner_policy.test.ts`](../src/application/policies/__tests__/setup_runner_policy.test.ts) · [`src/application/policies/__tests__/setup_runner_question_policy.test.ts`](../src/application/policies/__tests__/setup_runner_question_policy.test.ts) · [`src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts`](../src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts) · [`src/utils/__tests__/setup_runner_installation.test.ts`](../src/utils/__tests__/setup_runner_installation.test.ts) · [`src/cli/__tests__/web_setup_runner_components.test.ts`](../src/cli/__tests__/web_setup_runner_components.test.ts) · [`src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts`](../src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_runner_planning.test.ts`](../src/application/usecases/setup/__tests__/setup_runner_planning.test.ts) · [`src/cli/__tests__/setup_runner_config_file.test.ts`](../src/cli/__tests__/setup_runner_config_file.test.ts) · [`src/infrastructure/__tests__/setup_runner_composition.test.ts`](../src/infrastructure/__tests__/setup_runner_composition.test.ts) · [`src/tooling/__tests__/setup_provisioning_workflow.test.ts`](../src/tooling/__tests__/setup_provisioning_workflow.test.ts) · [`src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts`](../src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts) · [`src/cli/__tests__/setup_documentation_navigation.test.ts`](../src/cli/__tests__/setup_documentation_navigation.test.ts) · [`src/application/policies/__tests__/setup_question_documentation_policy.test.ts`](../src/application/policies/__tests__/setup_question_documentation_policy.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) +- Core code: [`src/domain/setup.ts`](../src/domain/setup.ts) · [`src/domain/setup_questionnaire.ts`](../src/domain/setup_questionnaire.ts) · [`src/domain/setup_token_permissions.ts`](../src/domain/setup_token_permissions.ts) · [`src/application/ports/setup_terminal_ports.ts`](../src/application/ports/setup_terminal_ports.ts) · [`src/application/ports/setup_wizard_ports.ts`](../src/application/ports/setup_wizard_ports.ts) · [`src/application/ports/branch_tag_ports.ts`](../src/application/ports/branch_tag_ports.ts) · [`src/application/policies/setup_issue_workflow_policy.ts`](../src/application/policies/setup_issue_workflow_policy.ts) · [`src/data/repository/release/repository_version_tags_query_repository.ts`](../src/data/repository/release/repository_version_tags_query_repository.ts) · [`src/application/ports/setup_token_permission_ports.ts`](../src/application/ports/setup_token_permission_ports.ts) · [`src/application/policies/setup_token_permission_evidence_policy.ts`](../src/application/policies/setup_token_permission_evidence_policy.ts) · [`src/application/policies/setup_token_permission_policy.ts`](../src/application/policies/setup_token_permission_policy.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/application/policies/setup_configuration_plan.ts`](../src/application/policies/setup_configuration_plan.ts) · [`src/application/policies/merge_setup_overrides_policy.ts`](../src/application/policies/merge_setup_overrides_policy.ts) · [`src/application/policies/setup_configuration_storage_policy.ts`](../src/application/policies/setup_configuration_storage_policy.ts) · [`src/application/policies/setup_doctor_message_catalog.ts`](../src/application/policies/setup_doctor_message_catalog.ts) · [`src/application/policies/setup_doctor_report_policy.ts`](../src/application/policies/setup_doctor_report_policy.ts) · [`src/application/policies/setup_journey_policy.ts`](../src/application/policies/setup_journey_policy.ts) · [`src/application/policies/setup_permission_summary_policy.ts`](../src/application/policies/setup_permission_summary_policy.ts) · [`src/application/usecases/setup/setup_wizard_use_case.ts`](../src/application/usecases/setup/setup_wizard_use_case.ts) · [`src/application/usecases/setup/setup_journey_use_case.ts`](../src/application/usecases/setup/setup_journey_use_case.ts) · [`src/application/usecases/setup/setup_session_coordinator.ts`](../src/application/usecases/setup/setup_session_coordinator.ts) · [`src/application/usecases/setup/setup_questionnaire_controller.ts`](../src/application/usecases/setup/setup_questionnaire_controller.ts) · [`src/application/usecases/setup/setup_credentials_use_case.ts`](../src/application/usecases/setup/setup_credentials_use_case.ts) · [`src/application/usecases/setup/setup_token_permissions_use_case.ts`](../src/application/usecases/setup/setup_token_permissions_use_case.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/usecases/setup/merge_queue_readiness_use_case.ts`](../src/application/usecases/setup/merge_queue_readiness_use_case.ts) · [`src/application/usecases/actions/initial_setup_workflow.ts`](../src/application/usecases/actions/initial_setup_workflow.ts) · [`src/application/usecases/actions/setup_resource_provisioning.ts`](../src/application/usecases/actions/setup_resource_provisioning.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/policies/setup_configuration_validation.ts`](../src/application/policies/setup_configuration_validation.ts) · [`src/infrastructure/setup_workspace_adapter.ts`](../src/infrastructure/setup_workspace_adapter.ts) · [`src/data/repository/repository_variables_repository.ts`](../src/data/repository/repository_variables_repository.ts) · [`src/infrastructure/github/ports/github_repository_variables_protocol.ts`](../src/infrastructure/github/ports/github_repository_variables_protocol.ts) · [`src/infrastructure/setup_remote_credential_health_adapter.ts`](../src/infrastructure/setup_remote_credential_health_adapter.ts) · [`src/infrastructure/setup_credential_validation_adapter.ts`](../src/infrastructure/setup_credential_validation_adapter.ts) · [`src/infrastructure/setup_token_permission_query_adapter.ts`](../src/infrastructure/setup_token_permission_query_adapter.ts) · [`src/infrastructure/setup_permission_actions_probe.ts`](../src/infrastructure/setup_permission_actions_probe.ts) · [`src/infrastructure/setup_permission_issue_probe.ts`](../src/infrastructure/setup_permission_issue_probe.ts) · [`src/infrastructure/setup_permission_issue_cleanup.ts`](../src/infrastructure/setup_permission_issue_cleanup.ts) · [`src/infrastructure/setup_permission_probe_journal.ts`](../src/infrastructure/setup_permission_probe_journal.ts) · [`src/infrastructure/setup_permission_probe_transaction.ts`](../src/infrastructure/setup_permission_probe_transaction.ts) · [`src/infrastructure/setup_permission_resource_probes.ts`](../src/infrastructure/setup_permission_resource_probes.ts) · [`src/infrastructure/setup_projects_probe_page_policy.ts`](../src/infrastructure/setup_projects_probe_page_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/cli/setup_question_renderer.ts`](../src/cli/setup_question_renderer.ts) · [`src/cli/setup_journey_presenter.ts`](../src/cli/setup_journey_presenter.ts) · [`src/cli/setup_plan_presenter.ts`](../src/cli/setup_plan_presenter.ts) · [`src/cli/setup_doctor_presenter.ts`](../src/cli/setup_doctor_presenter.ts) · [`src/cli/setup_prompt_rendering.ts`](../src/cli/setup_prompt_rendering.ts) · [`src/cli/setup_credential_prompt_adapter.ts`](../src/cli/setup_credential_prompt_adapter.ts) · [`src/cli/setup_credential_collection.ts`](../src/cli/setup_credential_collection.ts) · [`src/cli/commands/setup_execution.ts`](../src/cli/commands/setup_execution.ts) · [`src/cli/setup_token_permission_presenter.ts`](../src/cli/setup_token_permission_presenter.ts) · [`src/infrastructure/composition/setup_credentials_composition_root.ts`](../src/infrastructure/composition/setup_credentials_composition_root.ts) · [`src/infrastructure/composition/setup_token_permissions_composition_root.ts`](../src/infrastructure/composition/setup_token_permissions_composition_root.ts) · [`src/infrastructure/composition/setup_doctor_composition_root.ts`](../src/infrastructure/composition/setup_doctor_composition_root.ts) · [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/documentation_pat_exception_policy.cjs`](../scripts/documentation_pat_exception_policy.cjs) · [`scripts/validate-documentation-contract.cjs`](../scripts/validate-documentation-contract.cjs) · [`src/infrastructure/http_deadline.ts`](../src/infrastructure/http_deadline.ts) · [`src/infrastructure/setup_permission_denial.ts`](../src/infrastructure/setup_permission_denial.ts) · [`src/infrastructure/setup_permission_read_probe.ts`](../src/infrastructure/setup_permission_read_probe.ts) · [`src/infrastructure/setup_permission_read_evidence.ts`](../src/infrastructure/setup_permission_read_evidence.ts) · [`src/infrastructure/setup_permission_read_shape.ts`](../src/infrastructure/setup_permission_read_shape.ts) · [`src/infrastructure/setup_permission_projects_read.ts`](../src/infrastructure/setup_permission_projects_read.ts) · [`src/infrastructure/setup_permission_projects_access.ts`](../src/infrastructure/setup_permission_projects_access.ts) · [`src/infrastructure/setup_permission_workflow_cleanup.ts`](../src/infrastructure/setup_permission_workflow_cleanup.ts) · [`src/infrastructure/setup_permission_project_cleanup.ts`](../src/infrastructure/setup_permission_project_cleanup.ts) · [`src/infrastructure/setup_permission_probe_http.ts`](../src/infrastructure/setup_permission_probe_http.ts) · [`src/infrastructure/setup_permission_pull_request_probe.ts`](../src/infrastructure/setup_permission_pull_request_probe.ts) · [`src/infrastructure/git_authentication_environment.ts`](../src/infrastructure/git_authentication_environment.ts) · [`src/actions/github_action_input.ts`](../src/actions/github_action_input.ts) · [`src/infrastructure/setup_github_identity_query_adapter.ts`](../src/infrastructure/setup_github_identity_query_adapter.ts) · [`src/infrastructure/github_secret_encryption.ts`](../src/infrastructure/github_secret_encryption.ts) · [`src/infrastructure/setup_permission_organization_owner.ts`](../src/infrastructure/setup_permission_organization_owner.ts) · [`src/data/repository/github/github_actions_resource_inspector.ts`](../src/data/repository/github/github_actions_resource_inspector.ts) · [`src/data/repository/github/github_actions_resource_commands.ts`](../src/data/repository/github/github_actions_resource_commands.ts) · [`src/data/repository/github/github_actions_resource_collection.ts`](../src/data/repository/github/github_actions_resource_collection.ts) · [`src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts`](../src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts) · [`src/cli/setup_workflow_pat_storage_copy.ts`](../src/cli/setup_workflow_pat_storage_copy.ts) · [`src/cli/commands/setup_policy.ts`](../src/cli/commands/setup_policy.ts) · [`src/application/usecases/execution/execution_issue_number_policy.ts`](../src/application/usecases/execution/execution_issue_number_policy.ts) · [`src/application/usecases/execution/setup_execution_workflow.ts`](../src/application/usecases/execution/setup_execution_workflow.ts) · [`src/domain/github_runtime_doctor.ts`](../src/domain/github_runtime_doctor.ts) · [`src/domain/copilot_command.ts`](../src/domain/copilot_command.ts) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/policies/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/ports/github_runtime_doctor_ports.ts`](../src/application/ports/github_runtime_doctor_ports.ts) · [`src/application/policies/github_runtime_doctor_policy.ts`](../src/application/policies/github_runtime_doctor_policy.ts) · [`src/application/policies/doctor_workflow_condition_policy.ts`](../src/application/policies/doctor_workflow_condition_policy.ts) · [`src/application/policies/github_runtime_doctor_presentation_policy.ts`](../src/application/policies/github_runtime_doctor_presentation_policy.ts) · [`src/application/usecases/read_copilot_doctor_workflow.ts`](../src/application/usecases/read_copilot_doctor_workflow.ts) · [`src/data/repository/github/runtime_doctor_repository.ts`](../src/data/repository/github/runtime_doctor_repository.ts) · [`src/infrastructure/composition/github_runtime_doctor_composition_root.ts`](../src/infrastructure/composition/github_runtime_doctor_composition_root.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/application/policies/doctor_workflow_matrix_policy.ts`](../src/application/policies/doctor_workflow_matrix_policy.ts) · [`src/application/contracts/setup_batch_view.ts`](../src/application/contracts/setup_batch_view.ts) · [`src/application/policies/issue_resource_input_policy.ts`](../src/application/policies/issue_resource_input_policy.ts) · [`src/application/policies/setup_batch_access_policy.ts`](../src/application/policies/setup_batch_access_policy.ts) · [`src/application/policies/setup_batch_policy.ts`](../src/application/policies/setup_batch_policy.ts) · [`src/application/policies/setup_batch_retry_policy.ts`](../src/application/policies/setup_batch_retry_policy.ts) · [`src/application/policies/setup_batch_selection_policy.ts`](../src/application/policies/setup_batch_selection_policy.ts) · [`src/application/policies/setup_batch_view_policy.ts`](../src/application/policies/setup_batch_view_policy.ts) · [`src/application/policies/setup_credential_binding_policy.ts`](../src/application/policies/setup_credential_binding_policy.ts) · [`src/application/policies/setup_organization_import_policy.ts`](../src/application/policies/setup_organization_import_policy.ts) · [`src/application/policies/setup_organization_variable_bindings.ts`](../src/application/policies/setup_organization_variable_bindings.ts) · [`src/application/policies/setup_organization_variable_reuse_policy.ts`](../src/application/policies/setup_organization_variable_reuse_policy.ts) · [`src/application/ports/setup_batch_ports.ts`](../src/application/ports/setup_batch_ports.ts) · [`src/application/usecases/setup/apply_setup_batch_use_case.ts`](../src/application/usecases/setup/apply_setup_batch_use_case.ts) · [`src/application/usecases/setup/configure_setup_batch_target_use_case.ts`](../src/application/usecases/setup/configure_setup_batch_target_use_case.ts) · [`src/application/usecases/setup/prepare_setup_batch_use_case.ts`](../src/application/usecases/setup/prepare_setup_batch_use_case.ts) · [`src/application/usecases/setup/select_setup_batch_targets_use_case.ts`](../src/application/usecases/setup/select_setup_batch_targets_use_case.ts) · [`src/application/usecases/setup/setup_batch_session_use_case.ts`](../src/application/usecases/setup/setup_batch_session_use_case.ts) · [`src/cli/setup_batch_adapter.ts`](../src/cli/setup_batch_adapter.ts) · [`src/cli/setup_source_adapter.ts`](../src/cli/setup_source_adapter.ts) · [`src/domain/setup_batch.ts`](../src/domain/setup_batch.ts) · [`src/domain/setup_credential_health.ts`](../src/domain/setup_credential_health.ts) · [`src/domain/setup_organization_reuse.ts`](../src/domain/setup_organization_reuse.ts) · [`src/infrastructure/composition/setup_batch_composition_root.ts`](../src/infrastructure/composition/setup_batch_composition_root.ts) · [`src/infrastructure/github/ports/github_setup_batch_protocol.ts`](../src/infrastructure/github/ports/github_setup_batch_protocol.ts) · [`src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts`](../src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts) · [`src/infrastructure/setup_batch_github_apply_adapter.ts`](../src/infrastructure/setup_batch_github_apply_adapter.ts) · [`src/infrastructure/setup_batch_github_read_adapter.ts`](../src/infrastructure/setup_batch_github_read_adapter.ts) · [`src/infrastructure/setup_batch_journal_adapter.ts`](../src/infrastructure/setup_batch_journal_adapter.ts) · [`src/infrastructure/setup_batch_workspace_adapter.ts`](../src/infrastructure/setup_batch_workspace_adapter.ts) · [`src/infrastructure/setup_trusted_credential_health_adapter.ts`](../src/infrastructure/setup_trusted_credential_health_adapter.ts) · [`src/application/policies/setup_remote_facts_policy.ts`](../src/application/policies/setup_remote_facts_policy.ts) · [`src/cli/setup_confirmation_adapter.ts`](../src/cli/setup_confirmation_adapter.ts) · [`src/cli/web_setup_adapters.ts`](../src/cli/web_setup_adapters.ts) · [`scripts/validate-agent-documentation.cjs`](../scripts/validate-agent-documentation.cjs) · [`src/utils/setup_workflow_upgrade.ts`](../src/utils/setup_workflow_upgrade.ts) · [`src/domain/setup_runner.ts`](../src/domain/setup_runner.ts) · [`src/application/policies/setup_runner_policy.ts`](../src/application/policies/setup_runner_policy.ts) · [`src/application/policies/setup_runner_question_policy.ts`](../src/application/policies/setup_runner_question_policy.ts) · [`src/application/policies/setup_runner_guidance_policy.ts`](../src/application/policies/setup_runner_guidance_policy.ts) · [`src/application/ports/setup_runner_ports.ts`](../src/application/ports/setup_runner_ports.ts) · [`src/infrastructure/setup_runner_discovery_adapter.ts`](../src/infrastructure/setup_runner_discovery_adapter.ts) · [`src/infrastructure/composition/setup_runner_composition_root.ts`](../src/infrastructure/composition/setup_runner_composition_root.ts) · [`src/utils/setup_installation_review.ts`](../src/utils/setup_installation_review.ts) · [`src/cli/setup_installation_review_presenter.ts`](../src/cli/setup_installation_review_presenter.ts) · [`src/application/policies/setup_runner_installation_summary_policy.ts`](../src/application/policies/setup_runner_installation_summary_policy.ts) · [`src/utils/setup_files.ts`](../src/utils/setup_files.ts) · [`src/domain/specification_catalog_path_policy.cjs`](../src/domain/specification_catalog_path_policy.cjs) · [`src/domain/sdd_prerequisite_policy.cjs`](../src/domain/sdd_prerequisite_policy.cjs) · [`src/application/contracts/setup_documentation.ts`](../src/application/contracts/setup_documentation.ts) · [`src/application/policies/setup_question_documentation_policy.ts`](../src/application/policies/setup_question_documentation_policy.ts) · [`web/src/components/SetupHelp.svelte`](../web/src/components/SetupHelp.svelte) · [`web/src/components/CompletedSetupHelp.svelte`](../web/src/components/CompletedSetupHelp.svelte) · [`src/application/policies/verification_runtime_policy.ts`](../src/application/policies/verification_runtime_policy.ts) · [`src/infrastructure/verification_runtime_manifest.ts`](../src/infrastructure/verification_runtime_manifest.ts) · [`src/infrastructure/verification_runtime_preflight.ts`](../src/infrastructure/verification_runtime_preflight.ts) · [`src/infrastructure/untrusted_command_environment.ts`](../src/infrastructure/untrusted_command_environment.ts) · [`src/application/ports/git_ports.ts`](../src/application/ports/git_ports.ts) · [`src/application/ports/bugbot_git_ports.ts`](../src/application/ports/bugbot_git_ports.ts) · [`src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts`](../src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts) · [`src/application/usecases/steps/commit/bugbot/verify_command_runner.ts`](../src/application/usecases/steps/commit/bugbot/verify_command_runner.ts) · [`src/cli/setup_config_file.ts`](../src/cli/setup_config_file.ts) +- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) · [`src/domain/__tests__/github_runtime_doctor.test.ts`](../src/domain/__tests__/github_runtime_doctor.test.ts) · [`src/domain/__tests__/copilot_command.test.ts`](../src/domain/__tests__/copilot_command.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_policy.test.ts) · [`src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts) · [`src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts`](../src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts) · [`src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts`](../src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts) · [`src/data/repository/github/__tests__/runtime_doctor_repository.test.ts`](../src/data/repository/github/__tests__/runtime_doctor_repository.test.ts) · [`src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts`](../src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts) · [`src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts`](../src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts) · [`src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts) · [`src/application/policies/__tests__/setup_approval_operator_permissions.test.ts`](../src/application/policies/__tests__/setup_approval_operator_permissions.test.ts) · [`src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts`](../src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts) · [`src/application/policies/__tests__/setup_batch_permission_policy.test.ts`](../src/application/policies/__tests__/setup_batch_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_policy.test.ts`](../src/application/policies/__tests__/setup_batch_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_retry_policy.test.ts`](../src/application/policies/__tests__/setup_batch_retry_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_selection_policy.test.ts`](../src/application/policies/__tests__/setup_batch_selection_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_approval_draft.test.ts`](../src/application/policies/__tests__/setup_organization_approval_draft.test.ts) · [`src/application/policies/__tests__/setup_organization_import_policy.test.ts`](../src/application/policies/__tests__/setup_organization_import_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_import_safety.test.ts`](../src/application/policies/__tests__/setup_organization_import_safety.test.ts) · [`src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts`](../src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts) · [`src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts) · [`src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts`](../src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts) · [`src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts`](../src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_batch_flow.test.ts`](../src/application/usecases/setup/__tests__/setup_batch_flow.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts) · [`src/cli/__tests__/setup_source_adapter.test.ts`](../src/cli/__tests__/setup_source_adapter.test.ts) · [`src/cli/__tests__/web_setup_batch_components.test.ts`](../src/cli/__tests__/web_setup_batch_components.test.ts) · [`src/cli/__tests__/web_setup_batch_selection.test.ts`](../src/cli/__tests__/web_setup_batch_selection.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_local_adapters.test.ts`](../src/infrastructure/__tests__/setup_batch_local_adapters.test.ts) · [`src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts`](../src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts) · [`src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts`](../src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts) · [`src/application/policies/__tests__/setup_batch_access_and_view.test.ts`](../src/application/policies/__tests__/setup_batch_access_and_view.test.ts) · [`src/cli/__tests__/setup_batch_adapter.test.ts`](../src/cli/__tests__/setup_batch_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_composition.test.ts`](../src/infrastructure/__tests__/setup_batch_composition.test.ts) · [`src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts`](../src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts) · [`src/utils/__tests__/setup_workflow_upgrade.test.ts`](../src/utils/__tests__/setup_workflow_upgrade.test.ts) · [`src/cli/__tests__/setup_pat_intent_adapter.test.ts`](../src/cli/__tests__/setup_pat_intent_adapter.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/policies/__tests__/setup_runner_policy.test.ts`](../src/application/policies/__tests__/setup_runner_policy.test.ts) · [`src/application/policies/__tests__/setup_runner_question_policy.test.ts`](../src/application/policies/__tests__/setup_runner_question_policy.test.ts) · [`src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts`](../src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts) · [`src/utils/__tests__/setup_runner_installation.test.ts`](../src/utils/__tests__/setup_runner_installation.test.ts) · [`src/cli/__tests__/web_setup_runner_components.test.ts`](../src/cli/__tests__/web_setup_runner_components.test.ts) · [`src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts`](../src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_runner_planning.test.ts`](../src/application/usecases/setup/__tests__/setup_runner_planning.test.ts) · [`src/cli/__tests__/setup_runner_config_file.test.ts`](../src/cli/__tests__/setup_runner_config_file.test.ts) · [`src/infrastructure/__tests__/setup_runner_composition.test.ts`](../src/infrastructure/__tests__/setup_runner_composition.test.ts) · [`src/tooling/__tests__/setup_provisioning_workflow.test.ts`](../src/tooling/__tests__/setup_provisioning_workflow.test.ts) · [`src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts`](../src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts) · [`src/cli/__tests__/setup_documentation_navigation.test.ts`](../src/cli/__tests__/setup_documentation_navigation.test.ts) · [`src/application/policies/__tests__/setup_question_documentation_policy.test.ts`](../src/application/policies/__tests__/setup_question_documentation_policy.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) · [`src/infrastructure/__tests__/verification_setup_configuration.test.ts`](../src/infrastructure/__tests__/verification_setup_configuration.test.ts) - User documentation: [`README.md`](../README.md) · [`docs/how-to-use.mdx`](../docs/how-to-use.mdx) · [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/authentication.mdx`](../docs/authentication.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/security-operations/operations/provisioning.mdx`](../docs/security-operations/operations/provisioning.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/security-operations/security/credentials.mdx`](../docs/security-operations/security/credentials.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/setup-pat-provider-audit-2026-10-06.md`](../docs/development/setup-pat-provider-audit-2026-10-06.md) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/setup-web.mdx`](../docs/setup-web.mdx) · [`docs/first-workflow.mdx`](../docs/first-workflow.mdx) · [`docs/using-the-docs.mdx`](../docs/using-the-docs.mdx) · [`docs/guides/index.mdx`](../docs/guides/index.mdx) · [`docs/guides/issues-and-branches.mdx`](../docs/guides/issues-and-branches.mdx) · [`docs/guides/review-pull-requests.mdx`](../docs/guides/review-pull-requests.mdx) · [`docs/guides/work-with-the-bot.mdx`](../docs/guides/work-with-the-bot.mdx) · [`docs/guides/releases-and-hotfixes.mdx`](../docs/guides/releases-and-hotfixes.mdx) · [`docs/agents/repository-collaboration.mdx`](../docs/agents/repository-collaboration.mdx) ### `local-web-setup-assistant` — Local web setup assistant @@ -191,12 +191,12 @@ debt or convert unknown historic intent into a design decision. ### `branch-synchronization` — Branch synchronization and conflict recovery - Owner: Copilot maintainers -- Last verified: 2026-10-20 -- Specifications: [`specs/branch-synchronization-and-conflict-recovery.md`](./branch-synchronization-and-conflict-recovery.md) · [`specs/repository-verification-runtime-prerequisites.md`](./repository-verification-runtime-prerequisites.md) +- Last verified: 2026-10-21 +- Specifications: [`specs/branch-synchronization-and-conflict-recovery.md`](./branch-synchronization-and-conflict-recovery.md) · [`specs/repository-verification-runtime-prerequisites.md`](./repository-verification-runtime-prerequisites.md) · [`specs/repository-verification-setup-hooks.md`](./repository-verification-setup-hooks.md) - Workflows: [`.github/workflows/copilot_branch_sync.yml`](../.github/workflows/copilot_branch_sync.yml) · [`setup/workflows/copilot_branch_sync.yml`](../setup/workflows/copilot_branch_sync.yml) · [`.github/workflows/copilot_issue.yml`](../.github/workflows/copilot_issue.yml) · [`setup/workflows/copilot_issue.yml`](../setup/workflows/copilot_issue.yml) · [`setup/workflows/copilot_issue_comment.yml`](../setup/workflows/copilot_issue_comment.yml) · [`setup/workflows/copilot_pull_request_comment.yml`](../setup/workflows/copilot_pull_request_comment.yml) - Entrypoints: [`src/application/usecases/branch_sync/sync_branch_use_case.ts`](../src/application/usecases/branch_sync/sync_branch_use_case.ts) · [`src/application/usecases/actions/observe_branch_sync_use_case.ts`](../src/application/usecases/actions/observe_branch_sync_use_case.ts) -- Core code: [`src/domain/branch_sync_command.ts`](../src/domain/branch_sync_command.ts) · [`src/application/ports/branch_sync_ports.ts`](../src/application/ports/branch_sync_ports.ts) · [`src/application/policies/branch_sync_message_catalog.ts`](../src/application/policies/branch_sync_message_catalog.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/usecases/branch_sync/branch_sync_execution_policy.ts`](../src/application/usecases/branch_sync/branch_sync_execution_policy.ts) · [`src/application/usecases/actions/observe_branch_sync_use_case.ts`](../src/application/usecases/actions/observe_branch_sync_use_case.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/application/usecases/steps/common/transition_notification_workflow.ts`](../src/application/usecases/steps/common/transition_notification_workflow.ts) · [`src/data/repository/branch_sync/branch_dependency_policy.ts`](../src/data/repository/branch_sync/branch_dependency_policy.ts) · [`src/data/repository/branch_sync/branch_dependency_repository.ts`](../src/data/repository/branch_sync/branch_dependency_repository.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) · [`src/infrastructure/composition/push_single_action_capability_port_binding.ts`](../src/infrastructure/composition/push_single_action_capability_port_binding.ts) · [`src/infrastructure/branch_sync_workspace_adapter.ts`](../src/infrastructure/branch_sync_workspace_adapter.ts) · [`src/prompts/branch_sync_conflicts.ts`](../src/prompts/branch_sync_conflicts.ts) · [`src/data/model/application_error.ts`](../src/data/model/application_error.ts) · [`src/application/policies/application_error_message_catalog.ts`](../src/application/policies/application_error_message_catalog.ts) · [`src/infrastructure/git_commit_adapter.ts`](../src/infrastructure/git_commit_adapter.ts) · [`src/infrastructure/branch_sync_sdd_catalog.ts`](../src/infrastructure/branch_sync_sdd_catalog.ts) · [`src/domain/branch_sync_validation.ts`](../src/domain/branch_sync_validation.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/infrastructure/branch_sync_communication_budget.ts`](../src/infrastructure/branch_sync_communication_budget.ts) · [`src/application/usecases/actions/retire_branch_sync_use_case.ts`](../src/application/usecases/actions/retire_branch_sync_use_case.ts) · [`src/application/ports/issue_completion_ports.ts`](../src/application/ports/issue_completion_ports.ts) · [`src/application/usecases/pull_request_workflow.ts`](../src/application/usecases/pull_request_workflow.ts) · [`src/application/usecases/pull_request_workflow_context.ts`](../src/application/usecases/pull_request_workflow_context.ts) · [`src/infrastructure/composition/pull_request_use_case_composition_root.ts`](../src/infrastructure/composition/pull_request_use_case_composition_root.ts) · [`src/actions/common_action.ts`](../src/actions/common_action.ts) · [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`scripts/validate-workflow-contract.cjs`](../scripts/validate-workflow-contract.cjs) · [`src/application/ports/reply_request_read_ports.ts`](../src/application/ports/reply_request_read_ports.ts) · [`src/domain/reply_request_digest.ts`](../src/domain/reply_request_digest.ts) · [`src/infrastructure/composition/reply_request_composition_root.ts`](../src/infrastructure/composition/reply_request_composition_root.ts) · [`src/data/repository/reply_request_repository.ts`](../src/data/repository/reply_request_repository.ts) · [`src/application/policies/verification_runtime_policy.ts`](../src/application/policies/verification_runtime_policy.ts) · [`src/infrastructure/verification_runtime_manifest.ts`](../src/infrastructure/verification_runtime_manifest.ts) · [`src/infrastructure/verification_runtime_preflight.ts`](../src/infrastructure/verification_runtime_preflight.ts) · [`src/infrastructure/untrusted_command_environment.ts`](../src/infrastructure/untrusted_command_environment.ts) · [`src/application/ports/git_ports.ts`](../src/application/ports/git_ports.ts) · [`src/application/ports/bugbot_git_ports.ts`](../src/application/ports/bugbot_git_ports.ts) · [`src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts`](../src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts) · [`src/application/usecases/steps/commit/bugbot/verify_command_runner.ts`](../src/application/usecases/steps/commit/bugbot/verify_command_runner.ts) · [`src/domain/closed_issue_reconciliation.ts`](../src/domain/closed_issue_reconciliation.ts) · [`src/application/errors/closed_issue_reconciliation_error.ts`](../src/application/errors/closed_issue_reconciliation_error.ts) -- Tests: [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts) · [`src/application/usecases/branch_sync/__tests__/sync_branch_use_case.test.ts`](../src/application/usecases/branch_sync/__tests__/sync_branch_use_case.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts) · [`src/data/repository/branch_sync/__tests__/branch_dependency_policy.test.ts`](../src/data/repository/branch_sync/__tests__/branch_dependency_policy.test.ts) · [`src/data/repository/branch_sync/__tests__/branch_dependency_repository.test.ts`](../src/data/repository/branch_sync/__tests__/branch_dependency_repository.test.ts) · [`src/infrastructure/__tests__/branch_sync_workspace_adapter.test.ts`](../src/infrastructure/__tests__/branch_sync_workspace_adapter.test.ts) · [`src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts) · [`src/application/usecases/branch_sync/__tests__/branch_sync_execution_policy.test.ts`](../src/application/usecases/branch_sync/__tests__/branch_sync_execution_policy.test.ts) · [`src/application/errors/__tests__/application_error.test.ts`](../src/application/errors/__tests__/application_error.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/infrastructure/__tests__/branch_sync_sdd_catalog.test.ts`](../src/infrastructure/__tests__/branch_sync_sdd_catalog.test.ts) · [`src/infrastructure/__tests__/branch_sync_generated_bundle_validation.test.ts`](../src/infrastructure/__tests__/branch_sync_generated_bundle_validation.test.ts) · [`src/application/policies/__tests__/branch_sync_lifecycle_recovery.test.ts`](../src/application/policies/__tests__/branch_sync_lifecycle_recovery.test.ts) · [`src/infrastructure/__tests__/branch_sync_resolved_workspace.test.ts`](../src/infrastructure/__tests__/branch_sync_resolved_workspace.test.ts) · [`src/infrastructure/__tests__/branch_sync_communication_budget.test.ts`](../src/infrastructure/__tests__/branch_sync_communication_budget.test.ts) · [`src/application/usecases/actions/__tests__/retire_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/retire_branch_sync_use_case.test.ts) · [`src/application/usecases/__tests__/pull_request_use_case.test.ts`](../src/application/usecases/__tests__/pull_request_use_case.test.ts) · [`src/actions/__tests__/common_action.test.ts`](../src/actions/__tests__/common_action.test.ts) · [`src/actions/__tests__/github_action.test.ts`](../src/actions/__tests__/github_action.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) · [`src/data/repository/__tests__/reply_request_repository.test.ts`](../src/data/repository/__tests__/reply_request_repository.test.ts) · [`src/infrastructure/composition/__tests__/reply_request_composition_root.test.ts`](../src/infrastructure/composition/__tests__/reply_request_composition_root.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) +- Core code: [`src/domain/branch_sync_command.ts`](../src/domain/branch_sync_command.ts) · [`src/application/ports/branch_sync_ports.ts`](../src/application/ports/branch_sync_ports.ts) · [`src/application/policies/branch_sync_message_catalog.ts`](../src/application/policies/branch_sync_message_catalog.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/usecases/branch_sync/branch_sync_execution_policy.ts`](../src/application/usecases/branch_sync/branch_sync_execution_policy.ts) · [`src/application/usecases/actions/observe_branch_sync_use_case.ts`](../src/application/usecases/actions/observe_branch_sync_use_case.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/application/usecases/steps/common/transition_notification_workflow.ts`](../src/application/usecases/steps/common/transition_notification_workflow.ts) · [`src/data/repository/branch_sync/branch_dependency_policy.ts`](../src/data/repository/branch_sync/branch_dependency_policy.ts) · [`src/data/repository/branch_sync/branch_dependency_repository.ts`](../src/data/repository/branch_sync/branch_dependency_repository.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) · [`src/infrastructure/composition/push_single_action_capability_port_binding.ts`](../src/infrastructure/composition/push_single_action_capability_port_binding.ts) · [`src/infrastructure/branch_sync_workspace_adapter.ts`](../src/infrastructure/branch_sync_workspace_adapter.ts) · [`src/prompts/branch_sync_conflicts.ts`](../src/prompts/branch_sync_conflicts.ts) · [`src/data/model/application_error.ts`](../src/data/model/application_error.ts) · [`src/application/policies/application_error_message_catalog.ts`](../src/application/policies/application_error_message_catalog.ts) · [`src/infrastructure/git_commit_adapter.ts`](../src/infrastructure/git_commit_adapter.ts) · [`src/infrastructure/branch_sync_sdd_catalog.ts`](../src/infrastructure/branch_sync_sdd_catalog.ts) · [`src/domain/branch_sync_validation.ts`](../src/domain/branch_sync_validation.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/infrastructure/branch_sync_communication_budget.ts`](../src/infrastructure/branch_sync_communication_budget.ts) · [`src/application/usecases/actions/retire_branch_sync_use_case.ts`](../src/application/usecases/actions/retire_branch_sync_use_case.ts) · [`src/application/ports/issue_completion_ports.ts`](../src/application/ports/issue_completion_ports.ts) · [`src/application/usecases/pull_request_workflow.ts`](../src/application/usecases/pull_request_workflow.ts) · [`src/application/usecases/pull_request_workflow_context.ts`](../src/application/usecases/pull_request_workflow_context.ts) · [`src/infrastructure/composition/pull_request_use_case_composition_root.ts`](../src/infrastructure/composition/pull_request_use_case_composition_root.ts) · [`src/actions/common_action.ts`](../src/actions/common_action.ts) · [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`scripts/validate-workflow-contract.cjs`](../scripts/validate-workflow-contract.cjs) · [`src/application/ports/reply_request_read_ports.ts`](../src/application/ports/reply_request_read_ports.ts) · [`src/domain/reply_request_digest.ts`](../src/domain/reply_request_digest.ts) · [`src/infrastructure/composition/reply_request_composition_root.ts`](../src/infrastructure/composition/reply_request_composition_root.ts) · [`src/data/repository/reply_request_repository.ts`](../src/data/repository/reply_request_repository.ts) · [`src/application/policies/verification_runtime_policy.ts`](../src/application/policies/verification_runtime_policy.ts) · [`src/infrastructure/verification_runtime_manifest.ts`](../src/infrastructure/verification_runtime_manifest.ts) · [`src/infrastructure/verification_runtime_preflight.ts`](../src/infrastructure/verification_runtime_preflight.ts) · [`src/infrastructure/untrusted_command_environment.ts`](../src/infrastructure/untrusted_command_environment.ts) · [`src/application/ports/git_ports.ts`](../src/application/ports/git_ports.ts) · [`src/application/ports/bugbot_git_ports.ts`](../src/application/ports/bugbot_git_ports.ts) · [`src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts`](../src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts) · [`src/application/usecases/steps/commit/bugbot/verify_command_runner.ts`](../src/application/usecases/steps/commit/bugbot/verify_command_runner.ts) · [`src/domain/closed_issue_reconciliation.ts`](../src/domain/closed_issue_reconciliation.ts) · [`src/application/errors/closed_issue_reconciliation_error.ts`](../src/application/errors/closed_issue_reconciliation_error.ts) · [`src/application/policies/verification_setup_policy.ts`](../src/application/policies/verification_setup_policy.ts) · [`src/application/ports/verification_setup_ports.ts`](../src/application/ports/verification_setup_ports.ts) · [`src/infrastructure/verification_setup_snapshot.ts`](../src/infrastructure/verification_setup_snapshot.ts) · [`src/infrastructure/verification_setup_result.ts`](../src/infrastructure/verification_setup_result.ts) · [`src/infrastructure/verification_setup_runner.ts`](../src/infrastructure/verification_setup_runner.ts) · [`src/infrastructure/native_verification_protocol.ts`](../src/infrastructure/native_verification_protocol.ts) · [`src/infrastructure/native_verification_candidate.ts`](../src/infrastructure/native_verification_candidate.ts) · [`src/infrastructure/native_verification_staging.ts`](../src/infrastructure/native_verification_staging.ts) · [`src/infrastructure/native_verification_controller.ts`](../src/infrastructure/native_verification_controller.ts) · [`src/infrastructure/native_verification_profiles.ts`](../src/infrastructure/native_verification_profiles.ts) · [`src/infrastructure/native_verification_redaction.ts`](../src/infrastructure/native_verification_redaction.ts) · [`scripts/generate-native-verification-workflows.cjs`](../scripts/generate-native-verification-workflows.cjs) · [`src/infrastructure/verification_executable.ts`](../src/infrastructure/verification_executable.ts) · [`src/infrastructure/native_verification_source_runtime.ts`](../src/infrastructure/native_verification_source_runtime.ts) · [`scripts/prepare-native-source-action.cjs`](../scripts/prepare-native-source-action.cjs) +- Tests: [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/observe_branch_sync_use_case.test.ts) · [`src/application/usecases/branch_sync/__tests__/sync_branch_use_case.test.ts`](../src/application/usecases/branch_sync/__tests__/sync_branch_use_case.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/transition_notification_workflow.test.ts) · [`src/data/repository/branch_sync/__tests__/branch_dependency_policy.test.ts`](../src/data/repository/branch_sync/__tests__/branch_dependency_policy.test.ts) · [`src/data/repository/branch_sync/__tests__/branch_dependency_repository.test.ts`](../src/data/repository/branch_sync/__tests__/branch_dependency_repository.test.ts) · [`src/infrastructure/__tests__/branch_sync_workspace_adapter.test.ts`](../src/infrastructure/__tests__/branch_sync_workspace_adapter.test.ts) · [`src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts) · [`src/application/usecases/branch_sync/__tests__/branch_sync_execution_policy.test.ts`](../src/application/usecases/branch_sync/__tests__/branch_sync_execution_policy.test.ts) · [`src/application/errors/__tests__/application_error.test.ts`](../src/application/errors/__tests__/application_error.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/infrastructure/__tests__/branch_sync_sdd_catalog.test.ts`](../src/infrastructure/__tests__/branch_sync_sdd_catalog.test.ts) · [`src/infrastructure/__tests__/branch_sync_generated_bundle_validation.test.ts`](../src/infrastructure/__tests__/branch_sync_generated_bundle_validation.test.ts) · [`src/application/policies/__tests__/branch_sync_lifecycle_recovery.test.ts`](../src/application/policies/__tests__/branch_sync_lifecycle_recovery.test.ts) · [`src/infrastructure/__tests__/branch_sync_resolved_workspace.test.ts`](../src/infrastructure/__tests__/branch_sync_resolved_workspace.test.ts) · [`src/infrastructure/__tests__/branch_sync_communication_budget.test.ts`](../src/infrastructure/__tests__/branch_sync_communication_budget.test.ts) · [`src/application/usecases/actions/__tests__/retire_branch_sync_use_case.test.ts`](../src/application/usecases/actions/__tests__/retire_branch_sync_use_case.test.ts) · [`src/application/usecases/__tests__/pull_request_use_case.test.ts`](../src/application/usecases/__tests__/pull_request_use_case.test.ts) · [`src/actions/__tests__/common_action.test.ts`](../src/actions/__tests__/common_action.test.ts) · [`src/actions/__tests__/github_action.test.ts`](../src/actions/__tests__/github_action.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) · [`src/data/repository/__tests__/reply_request_repository.test.ts`](../src/data/repository/__tests__/reply_request_repository.test.ts) · [`src/infrastructure/composition/__tests__/reply_request_composition_root.test.ts`](../src/infrastructure/composition/__tests__/reply_request_composition_root.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) · [`src/application/policies/__tests__/verification_setup_policy.test.ts`](../src/application/policies/__tests__/verification_setup_policy.test.ts) · [`src/infrastructure/__tests__/verification_setup_snapshot.test.ts`](../src/infrastructure/__tests__/verification_setup_snapshot.test.ts) · [`src/infrastructure/__tests__/verification_setup_runner.test.ts`](../src/infrastructure/__tests__/verification_setup_runner.test.ts) · [`src/infrastructure/__tests__/verification_setup_configuration.test.ts`](../src/infrastructure/__tests__/verification_setup_configuration.test.ts) · [`src/infrastructure/__tests__/verification_setup_sync.test.ts`](../src/infrastructure/__tests__/verification_setup_sync.test.ts) · [`src/infrastructure/__tests__/native_verification_runner.test.ts`](../src/infrastructure/__tests__/native_verification_runner.test.ts) · [`src/infrastructure/__tests__/native_verification_protocol.test.ts`](../src/infrastructure/__tests__/native_verification_protocol.test.ts) · [`src/infrastructure/__tests__/native_verification_controller.test.ts`](../src/infrastructure/__tests__/native_verification_controller.test.ts) · [`src/infrastructure/__tests__/native_verification_redaction.test.ts`](../src/infrastructure/__tests__/native_verification_redaction.test.ts) · [`src/infrastructure/__tests__/native_verification_workflow.test.ts`](../src/infrastructure/__tests__/native_verification_workflow.test.ts) · [`src/infrastructure/__tests__/native_verification_profiles.test.ts`](../src/infrastructure/__tests__/native_verification_profiles.test.ts) · [`src/infrastructure/__tests__/verification_executable.test.ts`](../src/infrastructure/__tests__/verification_executable.test.ts) · [`src/infrastructure/__tests__/native_verification_post.test.ts`](../src/infrastructure/__tests__/native_verification_post.test.ts) · [`src/infrastructure/__tests__/native_verification_source_runtime.test.ts`](../src/infrastructure/__tests__/native_verification_source_runtime.test.ts) - User documentation: [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/notifications-and-auto-close.mdx`](../docs/issues/notifications-and-auto-close.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/agents/repository-collaboration.mdx`](../docs/agents/repository-collaboration.mdx) ### `pull-request-lifecycle` — Pull request lifecycle and enrichment diff --git a/specs/branch-synchronization-and-conflict-recovery.md b/specs/branch-synchronization-and-conflict-recovery.md index 59eff22fb..a40fb096c 100644 --- a/specs/branch-synchronization-and-conflict-recovery.md +++ b/specs/branch-synchronization-and-conflict-recovery.md @@ -719,3 +719,7 @@ retained effects and named failures for incomplete cleanup. Each card uses at most two targeted witness reads. This does not widen synchronization, branch, comment creation or deletion authority. Its eight new race/readback and bounded-diagnostic cases are counted only in the lifecycle extension's budget. + +## Repository setup amendment — issue #647 + +The [repository-owned setup contract](./repository-verification-setup-hooks.md) prepares the actual temporary merge immediately before each verification command when explicitly enabled. Composition captures immutable helper bytes from trusted main before workspace mutation; candidate edits never replace them. Setup and runtime preflight share the command's private environment. Setup failure follows existing abort behavior and withholds commit/push. Read-only status, dry run, aligned branches and empty command lists never execute the helper. The companion owns its incremental budget and local same-managed-ref acceptance; no release is included. diff --git a/specs/catalog.json b/specs/catalog.json index b66029e09..e9832f860 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -934,7 +934,8 @@ "src/application/ports/git_ports.ts", "src/application/ports/bugbot_git_ports.ts", "src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts", - "src/application/usecases/steps/commit/bugbot/verify_command_runner.ts" + "src/application/usecases/steps/commit/bugbot/verify_command_runner.ts", + "src/cli/setup_config_file.ts" ], "tests": [ "src/data/repository/__tests__/repository_version_tags_query_repository.test.ts", @@ -1064,7 +1065,8 @@ "src/infrastructure/__tests__/untrusted_command_environment.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts", - "src/tooling/__tests__/verification_runtime_guidance.test.ts" + "src/tooling/__tests__/verification_runtime_guidance.test.ts", + "src/infrastructure/__tests__/verification_setup_configuration.test.ts" ], "documentation": [ "README.md", @@ -1938,10 +1940,11 @@ "status": "implemented", "scope": "Observe parent drift with one localized status card and transition-only notifications, then safely merge a parent branch into a linked working branch", "owner": "Copilot maintainers", - "lastVerified": "2026-10-20", + "lastVerified": "2026-10-21", "specs": [ "specs/branch-synchronization-and-conflict-recovery.md", - "specs/repository-verification-runtime-prerequisites.md" + "specs/repository-verification-runtime-prerequisites.md", + "specs/repository-verification-setup-hooks.md" ], "workflows": [ ".github/workflows/copilot_branch_sync.yml", @@ -1998,7 +2001,22 @@ "src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts", "src/application/usecases/steps/commit/bugbot/verify_command_runner.ts", "src/domain/closed_issue_reconciliation.ts", - "src/application/errors/closed_issue_reconciliation_error.ts" + "src/application/errors/closed_issue_reconciliation_error.ts", + "src/application/policies/verification_setup_policy.ts", + "src/application/ports/verification_setup_ports.ts", + "src/infrastructure/verification_setup_snapshot.ts", + "src/infrastructure/verification_setup_result.ts", + "src/infrastructure/verification_setup_runner.ts", + "src/infrastructure/native_verification_protocol.ts", + "src/infrastructure/native_verification_candidate.ts", + "src/infrastructure/native_verification_staging.ts", + "src/infrastructure/native_verification_controller.ts", + "src/infrastructure/native_verification_profiles.ts", + "src/infrastructure/native_verification_redaction.ts", + "scripts/generate-native-verification-workflows.cjs", + "src/infrastructure/verification_executable.ts", + "src/infrastructure/native_verification_source_runtime.ts", + "scripts/prepare-native-source-action.cjs" ], "tests": [ "src/application/policies/__tests__/branch_sync_notification_policy.test.ts", @@ -2032,7 +2050,21 @@ "src/infrastructure/__tests__/untrusted_command_environment.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts", - "src/tooling/__tests__/verification_runtime_guidance.test.ts" + "src/tooling/__tests__/verification_runtime_guidance.test.ts", + "src/application/policies/__tests__/verification_setup_policy.test.ts", + "src/infrastructure/__tests__/verification_setup_snapshot.test.ts", + "src/infrastructure/__tests__/verification_setup_runner.test.ts", + "src/infrastructure/__tests__/verification_setup_configuration.test.ts", + "src/infrastructure/__tests__/verification_setup_sync.test.ts", + "src/infrastructure/__tests__/native_verification_runner.test.ts", + "src/infrastructure/__tests__/native_verification_protocol.test.ts", + "src/infrastructure/__tests__/native_verification_controller.test.ts", + "src/infrastructure/__tests__/native_verification_redaction.test.ts", + "src/infrastructure/__tests__/native_verification_workflow.test.ts", + "src/infrastructure/__tests__/native_verification_profiles.test.ts", + "src/infrastructure/__tests__/verification_executable.test.ts", + "src/infrastructure/__tests__/native_verification_post.test.ts", + "src/infrastructure/__tests__/native_verification_source_runtime.test.ts" ], "documentation": [ "docs/issues/branch-synchronization.mdx", diff --git a/specs/repository-verification-runtime-prerequisites.md b/specs/repository-verification-runtime-prerequisites.md index 1d3b45837..46f96ce6a 100644 --- a/specs/repository-verification-runtime-prerequisites.md +++ b/specs/repository-verification-runtime-prerequisites.md @@ -317,3 +317,7 @@ this change adds no stateful retry or remote race boundary. inference, credential broadening and parsing untrusted failed-command output. - Outside scope: live consumer Kotlin/Gradle retest, other home/cache variables, automatic setup questionnaire requirements, automatic dependency provisioning. + +## Optional repository setup amendment — issue #647 + +The [repository-owned setup contract](./repository-verification-setup-hooks.md) adds an explicit disabled-default `verification-setup` boolean. When enabled, a trusted main-branch Node helper prepares each candidate command environment before these existing fixed runtime probes. The runtime declaration remains a version-only policy and never supplies script paths, commands, credentials or installer arguments. With setup disabled, prerequisite behavior is unchanged. The companion owns its incremental tests and bounded setup limits; all existing no-push, isolation and preflight constraints remain required. diff --git a/specs/repository-verification-setup-hooks.md b/specs/repository-verification-setup-hooks.md new file mode 100644 index 000000000..9b9d2848d --- /dev/null +++ b/specs/repository-verification-setup-hooks.md @@ -0,0 +1,282 @@ +# Repository-owned native verification setup + +- Status: Ready for implementation +- Date: 2026-10-21 +- Catalog capability ID: branch-synchronization +- Last verified: 2026-10-21 +- Owners: Copilot maintainers +- Scope: Prepare authorized repository verification through a trusted native composite action, without editing generated workflows. +- Related issues/PRs: [#647](https://github.com/vypdev/copilot/issues/647), [#652](https://github.com/vypdev/copilot/pull/652) +- Required review gates: product UX, architecture, testing, documentation, security/operations +- Open decisions blocking readiness: none; the maintainer selected a same-job composite action on 2026-10-21. + +## 1. Executive summary + +A repository MAY opt into a consumer-owned `.github/actions/copilot-verification-setup/action.yml` using `runs.using: composite`. GitHub Actions MUST execute its native `uses`/`run` steps after Copilot prepares the candidate and before runtime preflight and each verification command. Copilot MUST retain its existing authorization, verification and publication gates. + +```text +Authorized operation -> prepare candidate -> native composite setup + -> credential-free runtime preflight -> verification -> publication guards -> push +``` + +This replaces the unreleased Node ESM prototype in PR #652. There is no custom YAML interpreter and no reusable-workflow candidate transport. + +## 2. Problem, current behavior, and evidence + +### 2.1 Problem + +Consumer projects need different runtimes and dependencies. Editing generated Copilot workflows to install them creates maintenance drift. A separate application CI job does not provision the job doing a temporary branch merge or automated fix. + +### 2.2 Current behavior + +The existing `GitCommitAdapter` prepares a fresh credential-free environment for every verification command. Runtime prerequisites are probed before the command. `SyncBranchUseCase` validates the resulting merge and rereads remote heads before publication; the shared commit preflight validates safe workspace paths. These gates remain authoritative. + +### 2.3 Evidence + +- Central boundaries: `SyncBranchUseCase.verifyPreparedMerge`, shared `runCommitAndPushPreflight`, `GitCommitAdapter.execute`. +- Only issue-comment and review-comment composition roots construct the mutation adapter. Issue/PR/push/review-state analysis callbacks may forward configuration but do not thereby execute verification. `CommitUseCase` analyzes push metadata and is not a mutation verifier. +- [Composite action metadata](https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax) defines native steps; [GitHub's tutorial](https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action) documents local actions. +- Consumer incident: [homeassistant-gateway #19](https://github.com/vypdev/homeassistant-gateway/issues/19). + +### 2.4 Retrospective classification + +Not applicable: this is a prospective change. The earlier Node implementation is superseded, not a supported compatibility surface. + +## 3. Actors, surfaces, and terminology + +| Actor | Goal | Surface | +|---|---|---| +| Repository maintainer | Review dependencies once and enable preparation | Consumer action, setup configuration, Repository Variable | +| Authorized contributor | Synchronize or request file changes | Existing issue/PR commands and result comments | +| Operator | Locate setup failure and recover | Named native Actions steps, existing result card, run logs | + +The **trusted source** is the configured main/production remote branch captured at an immutable Git commit. The **candidate** is the actual workspace after merge/fixer changes. A **native boundary** pauses an already admitted operation; it does not authorize or replay one. + +## 4. Goals, non-goals, and fixed invariants + +### 4.1 Goals + +1. Let each repository declare native setup actions and shell steps. +2. Prepare the candidate's current dependencies before every configured verifier. +3. Preserve consumer YAML and auxiliary assets during setup/update. +4. Provide inspectable native steps and actionable failures. + +### 4.2 Non-goals + +Reusable workflows, job matrices owned by the consumer hook, YAML emulation, local Actions emulation, arbitrary helper paths and release/deployment changes are out of scope. + +### 4.3 Fixed product/safety invariants + +- Setup MUST NOT grant modification authority or run for forks rejected by existing admission. +- Read-only, dry-run, aligned/no-op and empty-verifier operations MUST NOT execute setup. +- Candidate edits MUST NOT replace the captured action or its auxiliary files. +- Failure, timeout, malformed handoff, changed candidate or invalid runtime paths MUST withhold the command and publication. +- Each verification command MUST use a fresh private HOME; only validated runtime directories and JAVA_HOME return to its environment. + +## 5. Current versus proposed product journey + +| Stage | Existing | Proposed | +|---|---|---| +| Adoption | Edit generated workflow or provision runner | Review consumer composite onto trusted main, update generated workflows, enable opt-in | +| Candidate preparation | Agent/temporary merge | Unchanged | +| Dependencies | Preinstalled in same job | Native consumer composite, in that same job and candidate workspace | +| Verification/publication | Credential-free checks and existing guards | Same checks and guards after successful setup | + +```mermaid +sequenceDiagram + participant C as Admitted Copilot operation + participant R as GitHub Actions runner + participant Y as Trusted consumer composite + C->>C: Prepare candidate and close agent profiles + C->>R: Pause at verification boundary + R->>Y: Execute pinned YAML and assets + Y-->>R: Native success or failure, runtime paths + R->>C: Continue the existing operation once + C->>C: Validate unchanged candidate and runtime paths + C->>C: Preflight, verify, apply publication guards +``` + +Text equivalent: Copilot prepares the candidate, pauses, the runner executes trusted setup, and the same operation resumes only after validating the unchanged candidate. No agent is rerun. + +## 6. Functional behavior and state model + +### 6.1 Happy path + +1. Composition MUST capture immutable trusted Git objects before candidate mutation. The action directory is limited to 32 regular files, 64 KiB per file and 256 KiB total. Its root metadata MUST be `action.yml`, valid composite YAML with 1–32 steps. Asset paths MUST use portable ASCII letters/digits/underscore/dot/dash/slash, at most 200 characters, without empty/parent segments or Windows reserved names. Symlinks, submodules, unsafe paths and nested local `uses` actions are rejected. Native external actions and reviewed auxiliary scripts are supported. +2. At the existing verifier boundary, a private same-job worker MUST retain the admitted call stack. Generated workflows do not rerun the full operation to continue it. +3. The worker MUST bind HEAD, symbolic branch, index, tracked changes and publishable untracked contents. Git observations are bounded to 16 MiB, 256 untracked files and 16 MiB aggregate untracked contents; oversized candidates fail closed. Ignored dependency caches are excluded. +4. The trusted snapshot MUST be staged at the reserved local metadata path `.git/copilot-verification-setup`, outside publishable candidate files. Native execution requires the normal directory-based `.git` checkout produced by generated `actions/checkout` steps. The candidate's consumer action directory MUST never be rewritten. Staging MUST persist until native post steps finish. Runner state MUST retain the initial source SHA and digest of all captured file paths, bytes and modes. Before each composite post phase, a trusted Copilot post step MUST use its initially pinned absolute Git executable to restore that exact snapshot from Git objects at the pinned SHA and verify the saved digest, never a moved branch or candidate file. An unknown preexisting staging directory MUST fail closed without being overwritten. +5. The continuation MUST match the session, current request, run, attempt, job and workspace. Duplicate/stale responses fail closed. The candidate identity MUST still match. +6. Only new existing PATH directories within the workspace, private HOME or trusted tool cache, and an approved JAVA_HOME, MAY reach the verifier. The existing closed 16-KiB result validation and eight-directory ceiling remain in force. +7. Runtime preflight and the configured command MUST execute in that same fresh environment. Basename resolution MUST use its validated PATH, preserving virtual-environment invocation paths rather than the worker PATH or canonical binary target. Subsequent commands repeat setup with a new HOME and the current manifests. + +### 6.2 Alternative paths + +Disabled setup preserves existing behavior. Enabled setup without native workflow support, including local CLI verification, MUST fail closed with an explanation to update workflows and run in GitHub Actions. Snapshot errors are deferred on routes that do not verify. No automatic setup retry or shared readiness cache is permitted. + +### 6.3 State machine + +| State | Meaning | Next/recovery | +|---|---|---| +| Disabled/skipped | No preparation requested | Existing operation | +| Prepared candidate | Authorized workspace ready | Pending native setup | +| Pending setup | Native runner owns current setup request | Ready, failed, canceled, timed out | +| Ready | Native success and candidate/runtime validation passed | Preflight and command | +| Blocked | No safe successful continuation | Existing abort/result publication; operator repairs and reruns | +| Complete | Existing operation and its result publication completed | Private session cleanup | + +## 7. User-facing configuration + +| Surface | Name/type | Default/range | Scope | +|---|---|---|---| +| Action | `verification-setup`, boolean | `false`; only true/false | Invocation | +| Repository Variable | `VERIFICATION_SETUP` | `false`; true/false | Generated workflow | +| Setup config | `repository.verificationSetup`, optional boolean | absent means false | Installation plan | +| Consumer file | Fixed composite directory above | No path override | Repository-owned, never installer-owned | + +The setup configuration plans the Variable; generated workflows forward its value. An explicit direct Action input applies to that invocation. Invalid values MUST fail validation. Runtimes are not inferred. Recommended adoption: review the action onto trusted main, update generated workflows with `copilot setup`, then enable the Variable. Alternative: remain disabled and provision dependencies before Copilot in the same job. + +The 20-command limit, five-minute native setup timeout, snapshot/result/path bounds, source trust and publication checks are intentionally non-configurable. The former Node helper protocol and 64-KiB suppressed-output ceiling are retired; native step logs use GitHub Actions semantics. + +## 8. Clean Architecture design + +### 8.1 Responsibilities and dependency direction + +| Boundary | Responsibility | Forbidden responsibility | +|---|---|---| +| Pure policy/application ports | Closed limits/errors, immutable prepared snapshot | Runner APIs or process implementation | +| Existing application use cases | Admission, candidate preparation, verification and publication decisions | Native YAML interpretation or duplicate phase orchestration | +| Git adapter/setup runner | Pause precisely before probes/command, validate resume | New mutation authorization | +| Infrastructure snapshot/staging/protocol/controller | Trusted objects, persistent native staging, same-job process lifecycle | Provider business policy or model execution replay | +| Entrypoint/generated workflows | Dispatch native phases and native `uses` | Reimplement verifier/publication rules | +| Presentation | Existing localized result/error surfaces | Executable helper output as policy | + +### 8.2 Contracts, state, and trust boundaries + +The session contains bounded non-secret metadata and private operational logs. Worker stdout/stderr redact configured secrets and dynamic masking commands before bytes reach disk; PAT/model credentials are never serialized as log content. Credentials remain in the admitted worker's memory/environment as in the existing Action; child verifier environments remain allowlisted. Session provenance, request UUIDs and one-use responses prevent accidental replay across jobs/candidates. Root Action post-job cleanup owns cancellation and private directory removal. Source callbacks MUST copy their Action manifest, bundled runtime and setup assets outside the mutable candidate before execution; their continuations and post steps use that snapshot. Remote installed Actions already have an independent runner cache. Post hooks run only for the native callback phases, preserving ordinary direct Action behavior. Dependency-only profiles remain private until nested native post steps finish, then the original Action post step removes them; no later verifier reuses them. No artifacts or remote checkpoints are created. + +The workflow's fixed native phases support at most 20 setup boundaries per operation. Worker log forwarding preserves complete runner-command lines, with a 1-MiB single-line memory bound; this is distinct from native setup logs. Worker outputs are bounded to 256 KiB. + +### 8.3 Executable architecture constraints + +Existing architecture/dependency tests remain mandatory. The workflow validator MUST compare all native setup/continue/finish steps with the canonical generator, including conditions, timeout, runtime-only env and consistent Action reference/inputs. Source and distributed callbacks MUST agree. All six existing configuration callbacks retain explicit opt-in forwarding. + +## 9. UI/UX and content contract + +### 9.1 Information hierarchy + +Existing issue/PR result cards remain the primary operation status. Named Actions steps expose preparation and continuation; no new comment stream or labels are introduced. Users see impact, cause, action and retained state before technical details. + +### 9.2 Representative primary views + +| State | Representative content | +|---|---| +| Pending | Actions: “Prepare repository verification 1”. Operation remains in progress; no user action. | +| Action required | “Repository setup blocked verification (native-workflow-required). Update the generated Copilot workflows and run this operation in GitHub Actions; the local CLI cannot execute composite actions.” | +| Failed | “Repository setup blocked verification (process-failed). Review the trusted action and rerun. Verification, commit and push are withheld.” | +| Partially complete | Candidate prepared; setup failed; no remote publication. Existing sync abort restores the local merge. | +| Complete | “Repository setup completed; runtime preflight and verification may proceed.” Existing operation then reports its actual publication result. | + +### 9.3 Issue, PR, and comment behavior + +No additional comments: the existing bounded result publication owns success/failure and links to the run. Native raw setup logs MUST NOT be copied into issue/PR content. Diagnostics identify the fixed path, closed cause and pinned source SHA. + +### 9.4 Accessibility and localization + +Existing localized result cards and plain-text error fallback remain in force. Native diagnostic step names are English operational labels. Text accompanies every diagram; no color-only states or new responsive layout is introduced. Helper stdout is never interpreted as commands to Copilot or Markdown publication content. + +## 10. Failure, recovery, and cleanup + +| Failure | Impact and action | Cleanup/retry | +|---|---|---| +| Missing/invalid trusted action | No verifier/push; review trusted directory | No automatic retry | +| Native failure or timeout | No verifier/push; inspect native step logs and repair | Worker abort/finally preserves candidate files and uses existing sync recovery | +| Stale/cross-job/replayed response | No continuation; rerun fresh operation | Private session canceled | +| Candidate changed during setup | No verifier/push; make setup dependency-only | Candidate action unchanged; existing recovery | +| Invalid runtime output | No verifier/push; install under approved roots | Fresh HOME disposed | +| Job canceled/worker exits | No new publication; inspect retained operation result | Post action signals cancellation, waits up to 10 seconds, terminates remaining worker and cleans owned staging after native posts | + +Native setup steps have `timeout-minutes: 5`; the worker also rejects readiness after 300 seconds. The enclosing existing job timeout remains authoritative. A force-killed worker cannot promise application finally blocks ran: the Action cleans its own staging, and normal runner checkout cleanup/operator recovery owns remaining job-local candidate state. Already-completed remote effects MUST retain their existing truthful result semantics. + +## 11. Security, permissions, and privacy + +Native setup receives only runtime metadata from the private verifier environment, never Copilot's PAT/model inputs or evidence token. Generated workflows MUST keep provider secrets scoped to the initial Copilot Action step, not the job or consumer action. Checkout credentials remain unpersisted. Native actions can use runner facilities required for normal Actions execution; they are reviewed executable maintainer code. + +This preserves the trusted-runner boundary and makes no OS-sandbox claim: same-user processes and service-level credentials remain an operator trust concern. PATH/JAVA_HOME are validated after setup; arbitrary `GITHUB_ENV` keys do not enter the verifier. Native external action versions and dependency caches are maintained by the repository owner. Nested local `uses` is excluded because its deferred post scripts could otherwise refer to candidate assets outside the trusted staging tree. + +## 12. Observability and operational UX + +Logs name the fixed action and pinned source SHA, preparation success, and the existing verification result. Native Actions logs expose setup commands normally; maintainers MUST avoid printing secrets. No new secret-bearing artifacts, telemetry fields or result comments are added. The operation worker drains ordinary Action logs in order so masking commands retain their framing. + +## 13. Compatibility, migration, rollout, and rollback + +Existing installations remain disabled by default. The unreleased `.copilot/verification-setup.mjs` prototype is removed rather than maintained as a second execution mode. Consumers opting in MUST update generated callbacks before activation and review the composite directory onto trusted main. Setup/install/update MUST preserve that directory and all its assets. Rollback disables the Variable and restores ordinary same-job provisioning; no remote migration, release or tag is required. + +## 14. Testing strategy and numeric budget + +Minimum **150 distinct automated cases**, allocated without double counting: + +| Area | Minimum | Risk | +|---|---:|---| +| Policy/configuration | 30 | Defaults, invalid values, closed runtime schema | +| Trusted snapshot/assets | 22 | Pinning, hashes, file modes, malformed/native metadata | +| Native protocol/controller | 36 | Lifecycle, output framing, replay, timeout, cancellation | +| Candidate/runner orchestration | 24 | Ordering, fresh profiles, candidate identity, failure gates | +| Workflows/setup contracts | 18 | Native step structure, env, limits, source/distribution, preservation | +| Real Git/composition integration | 8 | Exact managed ref, merge manifests, abort/push guards | +| Runtime result filesystem security | 12 | Bounds, links, containment, races, owned staging | +| **Total** | **150** | Native rewrite adds at least 70 cases beyond retained applicable baseline | + +Repository-wide thresholds remain in force. New pure policy targets 100% branches; changed setup adapters target aggregate 95% lines/statements and 90% branches/functions. Deterministic unit fakes cover runner responses without interpreting YAML. Separate native CI acceptance MUST execute real composite `uses`/`run` steps with Python/pytest and Node/pnpm on hosted Linux, Windows and macOS, asserting candidate changes, trusted-source selection, fresh profiles candidate preservation, source-runtime immutability even after candidate bundle changes, and final post cleanup. Controlled local Git acceptance covers the exact same managed branch and no release. Native CI evidence is additional to the distinct Jest budget. + +## 15. Documentation and discoverability + +| Audience | Deliverable | Validation | +|---|---|---| +| User/operator | Verification page: normal flow, Python/Node YAML, native logs, limits, failures and CLI constraint | Documentation links and workflow fixtures | +| Setup owner | Setup configuration reference and same-job adoption order | Setup parser/planning and update-preservation tests | +| Contributor | Generated agent guide, source contract, SDD/catalog evidence | Guidance/specification validation | + +## 16. Acceptance scenarios + +1. Disabled repositories run existing verification unchanged. +2. Enabled authorized sync prepares dependencies from the temporary merged manifests, verifies and pushes only its existing managed branch. +3. A candidate/model change to the action does not execute; trusted YAML/assets run, the original candidate bytes remain unchanged. +4. Setup success precedes runtime probes; every command gets a fresh HOME. Read-only, dry-run, no-op and empty-command paths execute no setup. +5. Failed/canceled/timed-out setup, changed candidate, malformed output or replayed/cross-job continuation blocks verification and publication and cleans owned staging. +6. Native Python/pytest and Node/pnpm acceptance passes on all three hosted platforms. No PAT/model credentials are forwarded to the consumer action or verifier. +7. Updating Copilot twice preserves the consumer directory. Old/missing native callback support and CLI opt-in fail with actionable help. +8. Existing post-verification remote-head/workspace guards and truthful partial-success reporting remain authoritative. + +## 17. Requirements traceability + +| Requirements | Implementation/evidence | Documentation | +|---|---|---| +| §§4–7 source/defaults/routes | Policy, snapshot, config/composition and sync tests | Verification/setup reference | +| §6 native handoff/candidate | Runner, protocol, staging, controller tests | Architecture/recovery | +| §§8–11 workflows/security/cleanup | Structural mutation tests, result filesystem tests, platform fixture | Native steps and trust boundary | +| §§12–16 compatibility/acceptance | Setup preservation, real Git integration, hosted native acceptance | Migration/operator guide | + +## 18. Implementation sequence + +1. Resolve native contract and update specification/issue/PR. +2. Capture composite assets, implement bounded same-job native handoff and cleanup. +3. Generate native callbacks and enforce their structure. +4. Replace prototype tests; add native platform acceptance and real Git integration. +5. Update docs/guidance/catalog/bundles and validate all repository gates. +6. Resolve findings, wait for every required check on the final head, then merge to develop as explicitly authorized. + +## 19. Definition of Done + +- [x] Normative acceptance and traceability are covered. +- [ ] Native runner executes the composite at the actual candidate verification boundary. +- [x] Architecture, numeric budget, coverage and workflow mutation checks pass. +- [x] Defaults, limits, CLI behavior, failures, replay and cancellation match documentation. +- [x] User/setup/operator/contributor docs, native UI labels and safe diagnostics are reviewed. +- [x] Generated guidance/catalog/bundles and required validations pass. +- [ ] All three hosted native acceptance jobs pass; controlled Git sync preserves the same managed ref. +- [ ] Final-head checks pass and no unresolved review findings remain before merge. + +## 20. References and decisions + +The maintainer selected the composite approach in the task on 2026-10-21. [Issue decision record](https://github.com/vypdev/copilot/issues/647#issuecomment-6318551564). Related contracts: `branch-synchronization`, repository verification prerequisites and the shared automated commit pipeline in the specification catalog. A reusable workflow and a Node ESM helper were rejected for this delivery; no second execution mode is retained. diff --git a/src/actions/github_action.ts b/src/actions/github_action.ts index e94c26acb..83967c200 100644 --- a/src/actions/github_action.ts +++ b/src/actions/github_action.ts @@ -1,3 +1,4 @@ +import { runNativeVerificationEntry } from '../infrastructure/native_verification_controller'; import { createNativeStatusReadPort } from '../infrastructure/composition/issue_completion_composition_root'; import * as core from '@actions/core'; import * as github from '@actions/github'; @@ -255,5 +256,5 @@ function earlyGithubActionErrorMessage(): ApplicationErrorMessageReader { // Only auto-run when executed as the action entry (not when imported by tests) /* istanbul ignore next -- the bundled production entry is covered by the Action smoke path. */ if (typeof process.env.JEST_WORKER_ID === 'undefined') { - void runGitHubActionEntry(); + void runGitHubActionEntry(() => runNativeVerificationEntry(runGitHubActionEntry)); } diff --git a/src/actions/github_action_execution.ts b/src/actions/github_action_execution.ts index 003e6cfb7..333506181 100644 --- a/src/actions/github_action_execution.ts +++ b/src/actions/github_action_execution.ts @@ -86,6 +86,7 @@ export async function buildGithubActionExecution( eventInputs, ), preBranchSdd: parseIssueWorkflowBoolean(getInput(INPUT_KEYS.PRE_BRANCH_SDD), INPUT_KEYS.PRE_BRANCH_SDD, false), + verificationSetup: parseIssueWorkflowBoolean(getInput(INPUT_KEYS.VERIFICATION_SETUP), INPUT_KEYS.VERIFICATION_SETUP, false), pullRequest: buildPullRequest( parseIntegerInput(getInput(INPUT_KEYS.PULL_REQUEST_DESIRED_ASSIGNEES_COUNT), 0), parseIntegerInput(getInput(INPUT_KEYS.PULL_REQUEST_DESIRED_REVIEWERS_COUNT), 0), diff --git a/src/actions/local_action_configuration_sections.ts b/src/actions/local_action_configuration_sections.ts index 40c614b98..03960c691 100644 --- a/src/actions/local_action_configuration_sections.ts +++ b/src/actions/local_action_configuration_sections.ts @@ -185,6 +185,7 @@ export function readLocalWorkflowConfiguration( commitPrefixBuilder: read(INPUT_KEYS.COMMIT_PREFIX_TRANSFORMS) || 'replace-slash', issueManagedBranches: parseIssueWorkflowBoolean(read(INPUT_KEYS.ISSUE_MANAGED_BRANCHES), INPUT_KEYS.ISSUE_MANAGED_BRANCHES, true), preBranchSdd: parseIssueWorkflowBoolean(read(INPUT_KEYS.PRE_BRANCH_SDD), INPUT_KEYS.PRE_BRANCH_SDD, false), + verificationSetup: parseIssueWorkflowBoolean(read(INPUT_KEYS.VERIFICATION_SETUP), INPUT_KEYS.VERIFICATION_SETUP, false), reopenIssueOnPush: isEnabledInput(read(INPUT_KEYS.REOPEN_ISSUE_ON_PUSH)), issueDesiredAssigneesCount: parseIntegerInput(read(INPUT_KEYS.DESIRED_ASSIGNEES_COUNT), 0), pullRequestDesiredAssigneesCount: parseIntegerInput(read(INPUT_KEYS.PULL_REQUEST_DESIRED_ASSIGNEES_COUNT), 0), diff --git a/src/actions/local_action_execution.ts b/src/actions/local_action_execution.ts index 5cc94c60d..0300b59e2 100644 --- a/src/actions/local_action_execution.ts +++ b/src/actions/local_action_execution.ts @@ -17,7 +17,7 @@ export function buildLocalActionExecution( debug, singleAction, singleActionIssue, singleActionVersion, singleActionTitle, singleActionChangelog, singleActionMessage, singleActionCommentId, singleActionCommentMode, singleActionOperationId, inactivityThresholdHours, - commitPrefixBuilder, issueManagedBranches, preBranchSdd, reopenIssueOnPush, issueDesiredAssigneesCount, + commitPrefixBuilder, issueManagedBranches, preBranchSdd, verificationSetup, reopenIssueOnPush, issueDesiredAssigneesCount, pullRequestDesiredAssigneesCount, pullRequestDesiredReviewersCount, titleEmoji, branchManagementEmoji, token, agentModel, aiPullRequestDescriptionMode, aiMembersOnly, aiIgnoreFiles, aiIncludeReasoning, bugbotSeverity, @@ -58,6 +58,7 @@ export function buildLocalActionExecution( commitPrefixBuilder, issue: buildIssue(issueManagedBranches, reopenIssueOnPush, issueDesiredAssigneesCount, additionalParams), preBranchSdd, + verificationSetup, pullRequest: buildPullRequest(pullRequestDesiredAssigneesCount, pullRequestDesiredReviewersCount, additionalParams), emoji: buildEmoji(titleEmoji, branchManagementEmoji), tokens: buildTokens(token), diff --git a/src/application/contracts/input_keys.ts b/src/application/contracts/input_keys.ts index 9debb36ff..2a6725222 100644 --- a/src/application/contracts/input_keys.ts +++ b/src/application/contracts/input_keys.ts @@ -219,6 +219,7 @@ export const INPUT_KEYS = { // Issue ISSUE_MANAGED_BRANCHES: 'issue-managed-branches', PRE_BRANCH_SDD: 'pre-branch-sdd', + VERIFICATION_SETUP: 'verification-setup', REOPEN_ISSUE_ON_PUSH: 'reopen-issue-on-push', DESIRED_ASSIGNEES_COUNT: 'desired-assignees-count', diff --git a/src/application/policies/__tests__/verification_setup_policy.test.ts b/src/application/policies/__tests__/verification_setup_policy.test.ts new file mode 100644 index 000000000..45f773334 --- /dev/null +++ b/src/application/policies/__tests__/verification_setup_policy.test.ts @@ -0,0 +1,39 @@ +import { parseVerificationSetupResult, VerificationSetupError, VERIFICATION_SETUP_LIMITS, VERIFICATION_SETUP_PATH } from '../verification_setup_policy'; + +describe('repository verification setup policy', () => { + it('fixes the helper and limits without caller overrides', () => { + expect(VERIFICATION_SETUP_PATH).toBe('.github/actions/copilot-verification-setup/action.yml'); + expect(VERIFICATION_SETUP_LIMITS).toEqual({ timeoutMs: 300_000, resultBytes: 16_384, scriptBytes: 65_536, snapshotBytes: 262_144, files: 32, requests: 20 }); + expect(Object.isFrozen(VERIFICATION_SETUP_LIMITS)).toBe(true); + }); + it.each([ + [{ schemaVersion: 1 }, { path: [] }], + [{ schemaVersion: 1, path: ['/tools/bin'] }, { path: ['/tools/bin'] }], + [{ schemaVersion: 1, environment: { JAVA_HOME: '/tools/java' } }, { path: [], javaHome: '/tools/java' }], + ])('accepts closed protocol v1 output %j', (input, expected) => { + const parsed = parseVerificationSetupResult(input); + expect(parsed).toEqual(expected); + expect(Object.isFrozen(parsed)).toBe(true); + expect(Object.isFrozen(parsed.path)).toBe(true); + }); + it.each([ + null, [], false, 'value', {}, { schemaVersion: 2 }, { schemaVersion: 1, timeout: 1 }, + { schemaVersion: 1, path: 'bin' }, { schemaVersion: 1, path: null }, { schemaVersion: 1, environment: null }, { schemaVersion: 1, path: Array(9).fill('/bin') }, + { schemaVersion: 1, path: ['/bin', '/bin'] }, { schemaVersion: 1, path: [''] }, + { schemaVersion: 1, path: [false] }, { schemaVersion: 1, path: ['a'.repeat(2049)] }, + { schemaVersion: 1, path: ['line\nbreak'] }, { schemaVersion: 1, environment: [] }, + { schemaVersion: 1, environment: 'env' }, { schemaVersion: 1, environment: { HOME: '/tmp' } }, + { schemaVersion: 1, environment: { NODE_OPTIONS: '--require candidate' } }, + { schemaVersion: 1, environment: { JAVA_HOME: '' } }, { schemaVersion: 1, environment: { JAVA_HOME: 3 } }, + ])('rejects invalid or unrestricted output %j', input => { + expect(() => parseVerificationSetupResult(input)).toThrow(expect.objectContaining({ reason: 'result-invalid' })); + }); + it('reports only fixed facts and safe pinned source/exit metadata', () => { + expect(new VerificationSetupError('timeout', 'a'.repeat(40), 7).message).toContain('Exit status: 7'); + const error = new VerificationSetupError('source-unavailable', 'secret\n@everyone', 999); + expect(error.message).toContain('Trusted source SHA: unavailable'); + expect(error.message).toContain('Verification, commit and push are withheld'); + expect(error.message).not.toMatch(/secret|@everyone|999/); + expect(error).not.toHaveProperty('cause'); + }); +}); diff --git a/src/application/policies/repository_agent_guidance_policy.ts b/src/application/policies/repository_agent_guidance_policy.ts index 1fb70f2f6..9e3e3bdac 100644 --- a/src/application/policies/repository_agent_guidance_policy.ts +++ b/src/application/policies/repository_agent_guidance_policy.ts @@ -200,6 +200,8 @@ The setup runner profile selects placement; it does not install the project's la Declare reviewed version prefixes in \`.copilot/verification-prerequisites.json\` (schemaVersion 1, runtimes such as java 17, node 24, pnpm 10). Copilot checks declared versions in the credential-free verification environment before each configured verification command; a missing or incompatible runtime blocks publication. Without this optional declaration, existing verification behavior remains and no language is inferred. Java provisioning may use \`actions/setup-java\`; \`JAVA_HOME\` and \`PATH\` survive isolation, while agent credentials and tool-option injection variables do not. Do not add an unreviewed bootstrap or weaken verification to work around a missing runtime. Read [verification prerequisites and same-job setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites). +The optional repository-owned composite action prepares each candidate before verification. Review \`.github/actions/copilot-verification-setup/action.yml\` and its assets onto trusted main, update generated workflows, then enable \`VERIFICATION_SETUP=true\` (Action input \`verification-setup\`, disabled by default). GitHub Actions executes the trusted snapshot before each command with a fresh private HOME; candidate changes cannot replace it. Native post steps finish before profile cleanup. Setup failure blocks verification and commit/push. The local CLI cannot execute composite actions and fails closed when opted in. Read [repository-owned setup](https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup). + For the full contributor flow, read [repository collaboration](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-collaboration.mdx). For drift and recovery, read [guidance troubleshooting](https://github.com/vypdev/copilot/blob/master/docs/agents/repository-guidance-troubleshooting.mdx). `; } diff --git a/src/application/policies/setup_configuration_defaults.ts b/src/application/policies/setup_configuration_defaults.ts index 1da4832ba..44064ec94 100644 --- a/src/application/policies/setup_configuration_defaults.ts +++ b/src/application/policies/setup_configuration_defaults.ts @@ -103,6 +103,7 @@ export function createDefaultSetupConfiguration(): SetupConfiguration { choreTree: 'chore', issueManagedBranches: true, preBranchSdd: false, + verificationSetup: false, reopenIssueOnPush: true, desiredAssigneesCount: 1, desiredReviewersCount: 1, diff --git a/src/application/policies/setup_configuration_plan.ts b/src/application/policies/setup_configuration_plan.ts index fe5c50f48..1a771cdcb 100644 --- a/src/application/policies/setup_configuration_plan.ts +++ b/src/application/policies/setup_configuration_plan.ts @@ -140,6 +140,7 @@ export function buildSetupRepositoryVariables(configuration: SetupConfiguration) add('CHORE_TREE', repository.choreTree); add('ISSUE_MANAGED_BRANCHES', repository.issueManagedBranches); add('PRE_BRANCH_SDD', repository.preBranchSdd); + add('VERIFICATION_SETUP', repository.verificationSetup ?? false); add('REOPEN_ISSUE_ON_PUSH', repository.reopenIssueOnPush); add('DESIRED_ASSIGNEES_COUNT', repository.desiredAssigneesCount); add('DESIRED_REVIEWERS_COUNT', repository.desiredReviewersCount); @@ -207,6 +208,7 @@ export function buildSetupActionInputs(configuration: SetupConfiguration): Recor 'chore-tree': repository.choreTree, 'issue-managed-branches': String(repository.issueManagedBranches), 'pre-branch-sdd': String(repository.preBranchSdd), + 'verification-setup': String(repository.verificationSetup ?? false), 'reopen-issue-on-push': String(repository.reopenIssueOnPush), 'desired-assignees-count': String(repository.desiredAssigneesCount), 'desired-reviewers-count': String(repository.desiredReviewersCount), diff --git a/src/application/policies/setup_configuration_validation.ts b/src/application/policies/setup_configuration_validation.ts index 6f2162dc2..76e734a45 100644 --- a/src/application/policies/setup_configuration_validation.ts +++ b/src/application/policies/setup_configuration_validation.ts @@ -42,6 +42,9 @@ export function validateSetupConfiguration(configuration: SetupConfiguration, op || typeof configuration.repository.preBranchSdd !== 'boolean') { errors.push('issue-managed-branches and pre-branch-sdd must be boolean values.'); } + if (configuration.repository.verificationSetup !== undefined && typeof configuration.repository.verificationSetup !== 'boolean') { + errors.push('verification-setup must be a boolean value.'); + } if (configuration.repository.preBranchSdd && !configuration.repository.issueManagedBranches) { errors.push('pre-branch-sdd requires issue-managed-branches.'); } diff --git a/src/application/policies/setup_organization_variable_bindings.ts b/src/application/policies/setup_organization_variable_bindings.ts index ce9483b2f..f89fe7836 100644 --- a/src/application/policies/setup_organization_variable_bindings.ts +++ b/src/application/policies/setup_organization_variable_bindings.ts @@ -14,6 +14,7 @@ const repositoryFields = [ ['HOTFIX_TREE', 'hotfixTree'], ['RELEASE_TREE', 'releaseTree'], ['DOCS_TREE', 'docsTree'], ['CHORE_TREE', 'choreTree'], ['ISSUE_MANAGED_BRANCHES', 'issueManagedBranches'], ['PRE_BRANCH_SDD', 'preBranchSdd'], + ['VERIFICATION_SETUP', 'verificationSetup'], ['REOPEN_ISSUE_ON_PUSH', 'reopenIssueOnPush'], ['DESIRED_ASSIGNEES_COUNT', 'desiredAssigneesCount'], ['DESIRED_REVIEWERS_COUNT', 'desiredReviewersCount'], ['INACTIVITY_THRESHOLD_HOURS', 'inactivityThresholdHours'], ['REPOSITORY_LOCALE', 'repositoryLocale'], ['ISSUES_LOCALE', 'issueLocale'], diff --git a/src/application/policies/verification_setup_policy.ts b/src/application/policies/verification_setup_policy.ts new file mode 100644 index 000000000..07784a412 --- /dev/null +++ b/src/application/policies/verification_setup_policy.ts @@ -0,0 +1,48 @@ +export const VERIFICATION_SETUP_DIRECTORY = '.github/actions/copilot-verification-setup'; +export const VERIFICATION_SETUP_PATH = `${VERIFICATION_SETUP_DIRECTORY}/action.yml`; +export const VERIFICATION_SETUP_LIMITS = Object.freeze({ timeoutMs: 300_000, resultBytes: 16_384, scriptBytes: 65_536, snapshotBytes: 262_144, files: 32, requests: 20 }); +export const VERIFICATION_SETUP_HELP = 'https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup'; + +export type VerificationSetupFailure = 'source-unavailable' | 'helper-invalid' | 'workspace-changed' | 'process-failed' | 'timeout' | 'cancelled' | 'output-limit' | 'result-invalid' | 'native-workflow-required' | 'candidate-changed' | 'handoff-invalid'; + +/** Closed facts only. Provider/helper output and raw exception causes never cross this boundary. */ +export class VerificationSetupError extends Error { + constructor(readonly reason: VerificationSetupFailure, sourceSha?: string, readonly exitCode?: number) { + const sha = sourceSha && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sourceSha) ? sourceSha : 'unavailable'; + const exit = Number.isInteger(exitCode) && exitCode! >= 0 && exitCode! <= 255 ? ` Exit status: ${exitCode}.` : ''; + const action = reason === 'native-workflow-required' + ? 'Update the generated Copilot workflows and run this operation in GitHub Actions; the local CLI cannot execute composite actions.' + : `Review ${VERIFICATION_SETUP_PATH} on the trusted main branch and rerun.`; + super(`Repository setup blocked verification (${reason}). ${action} Trusted source SHA: ${sha}.${exit} Verification, commit and push are withheld. Help: ${VERIFICATION_SETUP_HELP}`); + this.name = 'VerificationSetupError'; + } +} + +export interface VerificationSetupResult { + readonly path: readonly string[]; + readonly javaHome?: string; +} + +function record(value: unknown): value is Record { + return !!value && typeof value === 'object' && !Array.isArray(value); +} + +function pathString(value: unknown): value is string { + return typeof value === 'string' && value.length > 0 && value.length <= 2048 + && ![...value].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127); +} + +/** Filesystem containment is checked by the adapter, after this closed schema is parsed. */ +export function parseVerificationSetupResult(value: unknown): VerificationSetupResult { + if (!record(value) || value.schemaVersion !== 1 || Object.keys(value).some(key => !['schemaVersion', 'path', 'environment'].includes(key))) { + throw new VerificationSetupError('result-invalid'); + } + const paths = value.path === undefined ? [] : value.path; + const environment = value.environment === undefined ? {} : value.environment; + if (!Array.isArray(paths) || paths.length > 8 || !paths.every(pathString) || new Set(paths).size !== paths.length + || !record(environment) || Object.keys(environment).some(key => key !== 'JAVA_HOME') + || ('JAVA_HOME' in environment && !pathString(environment.JAVA_HOME))) { + throw new VerificationSetupError('result-invalid'); + } + return Object.freeze({ path: Object.freeze([...paths]), ...(environment.JAVA_HOME ? { javaHome: environment.JAVA_HOME as string } : {}) }); +} diff --git a/src/application/ports/verification_setup_ports.ts b/src/application/ports/verification_setup_ports.ts new file mode 100644 index 000000000..6b1a292c8 --- /dev/null +++ b/src/application/ports/verification_setup_ports.ts @@ -0,0 +1,11 @@ +import type { VerificationSetupFailure } from '../policies/verification_setup_policy'; + +export interface VerificationSetupFile { readonly path: string; readonly base64: string; readonly executable: boolean } + +/** An operation-private immutable snapshot, captured before candidate mutation. No credentials. */ +export type PreparedVerificationSetup = + | { readonly kind: 'disabled' } + | { readonly kind: 'blocked'; readonly reason: VerificationSetupFailure; readonly sourceSha?: string } + | { readonly kind: 'ready'; readonly files: readonly VerificationSetupFile[]; readonly sourceSha: string; readonly workspace: string; readonly toolRoot?: string }; + +export const DISABLED_VERIFICATION_SETUP: PreparedVerificationSetup = Object.freeze({ kind: 'disabled' }); diff --git a/src/application/usecases/steps/commit/bugbot/verify_command_runner.ts b/src/application/usecases/steps/commit/bugbot/verify_command_runner.ts index c82012da8..0930a00dd 100644 --- a/src/application/usecases/steps/commit/bugbot/verify_command_runner.ts +++ b/src/application/usecases/steps/commit/bugbot/verify_command_runner.ts @@ -1,6 +1,7 @@ import { logError } from '../../../../ports/logging_ports'; import { parseVerifyCommand, type ParsedVerifyCommand } from './verify_command_policy'; import { VerificationPrerequisiteError } from '../../../../policies/verification_runtime_policy'; +import { VerificationSetupError } from '../../../../policies/verification_setup_policy'; export interface VerifyCommandResult { success: boolean; @@ -33,7 +34,7 @@ async function executeVerifyCommand( ? { success: true } : { success: false, failedCommand: formatCommandForDiagnostics(parsed) }; } catch (error) { - if (error instanceof VerificationPrerequisiteError) { + if (error instanceof VerificationPrerequisiteError || error instanceof VerificationSetupError) { return { success: false, failedCommand: formatCommandForDiagnostics(parsed), error: `${error.message} Verification command: ${formatCommandForDiagnostics(parsed)}.` }; } diff --git a/src/cli/setup_config_file.ts b/src/cli/setup_config_file.ts index 712137495..8507a9331 100644 --- a/src/cli/setup_config_file.ts +++ b/src/cli/setup_config_file.ts @@ -48,7 +48,7 @@ const REPOSITORY_STRING_KEYS = new Set([ 'orchestrationPresentationMode', 'orchestrationCommentMode', ]); -const REPOSITORY_BOOLEAN_KEYS = new Set(['issueManagedBranches', 'preBranchSdd', 'reopenIssueOnPush', 'orchestrationDiagrams']); +const REPOSITORY_BOOLEAN_KEYS = new Set(['issueManagedBranches', 'preBranchSdd', 'verificationSetup', 'reopenIssueOnPush', 'orchestrationDiagrams']); const REPOSITORY_NUMBER_KEYS = new Set(['desiredAssigneesCount', 'desiredReviewersCount', 'inactivityThresholdHours']); const REPOSITORY_STRUCTURED_KEYS = new Set(['mergeQueueCheckAttestations']); const AI_STRING_KEYS = new Set(['ignoreFiles', 'pullRequestDescriptionMode', 'bugbotSeverity', 'bugbotFixVerifyCommands', 'bugbotEffort', 'bugbotOrganizationRules', 'provisioningMode']); diff --git a/src/data/model/execution.ts b/src/data/model/execution.ts index 0c2296b40..b56d86c16 100644 --- a/src/data/model/execution.ts +++ b/src/data/model/execution.ts @@ -65,6 +65,7 @@ export class Execution { currentIssueWorkflowAdmission?: IssueWorkflowAdmission; issueWorkflowRuntimeMode: IssueWorkflowRuntimeMode = 'execute'; readonly preBranchSdd: boolean; + readonly verificationSetup: boolean; get eventName(): string { return this.inputs?.eventName ?? ''; @@ -206,6 +207,7 @@ export class Execution { this.currentIssueWorkflowAdmission = components.issueWorkflowAdmission; this.currentConfiguration.issueWorkflowProfileDigest = components.issueWorkflowProfileDigest; this.preBranchSdd = components.preBranchSdd ?? false; + this.verificationSetup = components.verificationSetup ?? false; } } diff --git a/src/data/model/execution_components.ts b/src/data/model/execution_components.ts index 9f906253d..e9a9c0886 100644 --- a/src/data/model/execution_components.ts +++ b/src/data/model/execution_components.ts @@ -46,4 +46,5 @@ export interface ExecutionComponents { issueWorkflowProfileDigest?: string; issueWorkflowAdmission?: IssueWorkflowAdmission; preBranchSdd?: boolean; + verificationSetup?: boolean; } diff --git a/src/domain/setup.ts b/src/domain/setup.ts index 1aa80db77..2623f1481 100644 --- a/src/domain/setup.ts +++ b/src/domain/setup.ts @@ -55,6 +55,8 @@ export interface SetupRepositoryConfiguration { choreTree: string; issueManagedBranches: boolean; preBranchSdd: boolean; + /** Advanced opt-in; absent legacy configuration remains disabled. */ + verificationSetup?: boolean; reopenIssueOnPush: boolean; desiredAssigneesCount: number; desiredReviewersCount: number; diff --git a/src/infrastructure/__tests__/native_verification_controller.test.ts b/src/infrastructure/__tests__/native_verification_controller.test.ts new file mode 100644 index 000000000..fa35164ec --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_controller.test.ts @@ -0,0 +1,164 @@ +import fs from 'node:fs'; +import cp from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { join } from 'node:path'; +import * as core from '@actions/core'; +import * as protocol from '../native_verification_protocol'; +import { awaitNativeBoundary, cleanupNativeVerification, continueNativeVerification, drainNativeWorkerLog, runNativeVerificationEntry } from '../native_verification_controller'; +import { stageNativeVerificationAction } from '../native_verification_staging'; +import { nativeVerificationFixture } from './native_verification_fixture'; + +jest.mock('@actions/core', () => ({ setOutput: jest.fn(), saveState: jest.fn(), getState: jest.fn(() => ''), getInput: jest.fn(() => 'true'), setFailed: jest.fn(), setSecret: jest.fn() })); + +describe('native Actions phase controller', () => { + let f: ReturnType; let before: NodeJS.ProcessEnv; + const roots: string[] = []; + const done = (exitCode = 0) => protocol.writeNativeRecord(f.session, 'done.json', { exitCode }); + const request = (edit: Record = {}) => protocol.writeNativeRecord(f.session, 'request.json', { + id: f.id, sourceSha: 'a'.repeat(40), digest: 'b'.repeat(64), environment: f.runtime.environment, ...edit, + }); + beforeEach(() => { before = { ...process.env }; f = nativeVerificationFixture(); jest.clearAllMocks(); jest.mocked(core.getState).mockReturnValue(''); jest.mocked(core.getInput).mockReturnValue('true'); }); + afterEach(async () => { + jest.restoreAllMocks(); + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); + f.cleanup(); process.env = before; + }); + it('emits a pending request with only the closed runtime outputs', async () => { + request(); await awaitNativeBoundary(f.session); + expect(core.setOutput).toHaveBeenCalledWith('verification-setup-pending', 'true'); + expect(core.setOutput).toHaveBeenCalledWith('verification-request', f.id); + expect(core.setOutput).toHaveBeenCalledWith('verification-home', f.runtime.environment.HOME); + }); + it.each([{id: 'wrong'}, {sourceSha: 'wrong'}, {environment: {}}, {environment: {HOME: '\nsecret'}}])('rejects malformed pending request %j', async edit => { + request(edit); await expect(awaitNativeBoundary(f.session)).rejects.toThrow('handoff-invalid'); + }); + it('reports completion and copies native action outputs into the current step', async () => { + process.env.GITHUB_OUTPUT = join(f.session, 'parent-output'); fs.writeFileSync(join(f.session, 'worker-output'), 'telemetry=value\n'); done(); + await awaitNativeBoundary(f.session); + expect(fs.readFileSync(process.env.GITHUB_OUTPUT, 'utf8')).toBe('telemetry=value\n'); + expect(core.setOutput).toHaveBeenCalledWith('verification-setup-pending', 'false'); expect(core.setFailed).not.toHaveBeenCalled(); + }); + it('preserves worker failure instead of treating a resolved entrypoint as success', async () => { + done(1); await awaitNativeBoundary(f.session); expect(core.setFailed).toHaveBeenCalled(); + }); + it('publishes the existing Job Summary once at completion', async () => { + process.env.GITHUB_STEP_SUMMARY = join(f.session, 'parent-summary'); fs.writeFileSync(join(f.session, 'worker-summary'), 'Verified candidate\n'); done(); + await awaitNativeBoundary(f.session); await awaitNativeBoundary(f.session); + expect(fs.readFileSync(process.env.GITHUB_STEP_SUMMARY, 'utf8')).toBe('Verified candidate\n'); + }); + it('rejects oversized worker outputs without forwarding them', async () => { + process.env.GITHUB_OUTPUT = join(f.session, 'parent-output'); fs.writeFileSync(join(f.session, 'worker-output'), 'x'.repeat(262_145)); done(); + await expect(awaitNativeBoundary(f.session)).rejects.toThrow('output-limit'); + }); + it('bounds the operation wait without restarting the agent', async () => { + await expect(awaitNativeBoundary(f.session, 0)).rejects.toThrow('timeout'); + }); + it('detects a worker that exited before a result or handoff', async () => { + jest.spyOn(process, 'kill').mockImplementation(() => { throw new Error('not alive'); }); + await expect(awaitNativeBoundary(f.session)).rejects.toThrow('process-failed'); + }); + it('frames log lines once and flushes the remaining final line', () => { + fs.writeFileSync(join(f.session, 'worker-log'), '::add-mask::secret\nlast'); + const out = jest.spyOn(process.stdout, 'write').mockReturnValue(true); + drainNativeWorkerLog(f.session); drainNativeWorkerLog(f.session); drainNativeWorkerLog(f.session, true); + expect(out.mock.calls.map(call => Buffer.from(call[0] as Buffer).toString()).join('')).toBe('::add-mask::secret\nlast'); + }); + it('streams logs larger than a single read buffer', () => { + const data = 'line\n'.repeat(20_000); fs.writeFileSync(join(f.session, 'worker-log'), data); + const out = jest.spyOn(process.stdout, 'write').mockReturnValue(true); drainNativeWorkerLog(f.session, true); + expect(out.mock.calls.map(call => Buffer.from(call[0] as Buffer).toString()).join('')).toBe(data); + }); + it('bounds a worker log line rather than accumulating unbounded memory', () => { + fs.writeFileSync(join(f.session, 'worker-log'), 'x'.repeat(1_100_000)); + expect(() => drainNativeWorkerLog(f.session)).toThrow('output-limit'); + }); + it.each(['success', 'failure', 'cancelled', 'skipped'])('delivers native %s and waits for the existing operation', async outcome => { + request(); const write = protocol.writeNativeRecord; + jest.spyOn(protocol, 'writeNativeRecord').mockImplementation((root, name, value) => { write(root, name, value); if (name.startsWith('response-')) write(root, 'done.json', { exitCode: 0 }); }); + await continueNativeVerification(f.session, f.id, outcome, { PATH: 'approved', JAVA_HOME: 'java' }); + expect(protocol.readNativeRecord(f.session, `response-${f.id}.json`)).toEqual({ id: f.id, success: outcome === 'success', path: 'approved', javaHome: 'java' }); + expect(fs.existsSync(join(f.session, 'request.json'))).toBe(false); + }); + it('rejects a replayed continuation', async () => { + request(); protocol.writeNativeRecord(f.session, `response-${f.id}.json`, {}); + await expect(continueNativeVerification(f.session, f.id, 'success')).rejects.toThrow('handoff-invalid'); + }); + it('rejects a stale request before signaling the worker', async () => { + request(); await expect(continueNativeVerification(f.session, 'wrong', 'success')).rejects.toThrow('handoff-invalid'); + }); + it('cleans a completed operation and makes post-job cleanup idempotent', async () => { + done(); await cleanupNativeVerification(f.session); await cleanupNativeVerification(f.session); expect(fs.existsSync(f.session)).toBe(false); + }); + it('cancels and kills a stuck worker before restoring its candidate action', async () => { + fs.mkdirSync(f.action, { recursive: true }); fs.writeFileSync(join(f.action, 'action.yml'), 'candidate'); stageNativeVerificationAction(f.setup as never, f.session); + const kill = jest.spyOn(process, 'kill').mockReturnValue(true); jest.spyOn(cp, 'spawnSync').mockReturnValue({status: 0} as never); + await cleanupNativeVerification(f.session, 0); expect(fs.readFileSync(join(f.action, 'action.yml'), 'utf8')).toBe('candidate'); + if (process.platform !== 'win32') expect(kill).toHaveBeenCalledWith(-process.pid, 'SIGKILL'); + }); + it('allows an already-exited worker during cancellation', async () => { + jest.spyOn(process, 'kill').mockImplementation(() => { throw new Error('exited'); }); jest.spyOn(cp, 'spawnSync').mockReturnValue({status: 0} as never); + await cleanupNativeVerification(f.session, 0); expect(fs.existsSync(f.session)).toBe(false); + }); + it('runs an admitted operation directly when there is no native phase', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; delete process.env.COPILOT_NATIVE_PHASE; const run = jest.fn(); await runNativeVerificationEntry(run); expect(run).toHaveBeenCalledTimes(1); + }); + it('keeps opt-out workflows on their ordinary entrypoint', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.COPILOT_NATIVE_PHASE = 'prepare'; jest.mocked(core.getInput).mockReturnValue('false'); + const run = jest.fn(); await runNativeVerificationEntry(run); expect(run).toHaveBeenCalledTimes(1); + }); + it('executes the worker callback once and retains its final status', async () => { + const run = jest.fn().mockResolvedValue(undefined); await runNativeVerificationEntry(run); + expect(run).toHaveBeenCalledTimes(1); expect(protocol.readNativeRecord(f.session, 'done.json')).toEqual({ exitCode: 0 }); + }); + it('rejects native phases outside GitHub Actions', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.COPILOT_NATIVE_PHASE = 'prepare'; delete process.env.GITHUB_ACTIONS; + await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('native-workflow-required'); + }); + it('rejects a worker session owned by a different process', async () => { + const session = protocol.nativeSession(f.session); protocol.writeNativeRecord(f.session, 'session.json', { ...session, pid: process.pid + 1 }); + const run = jest.fn(); await expect(runNativeVerificationEntry(run)).rejects.toThrow('handoff-invalid'); expect(run).not.toHaveBeenCalled(); + }); + it('waits for publication of its initial worker session with a fixed deadline', async () => { + fs.rmSync(join(f.session, 'session.json')); let now = 0; jest.spyOn(Date, 'now').mockImplementation(() => now++ ? 30_001 : 0); + await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('timeout'); + }); + it('rejects a native continuation without a ticket', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'continue'; delete process.env.COPILOT_NATIVE_TICKET; + await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('handoff-invalid'); + }); + it('rejects unknown phases without running the operation', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'unknown'; process.env.COPILOT_NATIVE_TICKET = f.session; + await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('handoff-invalid'); + }); + it('finishes a successful operation and removes its private state', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'finish'; process.env.COPILOT_NATIVE_TICKET = f.session; done(); + await runNativeVerificationEntry(jest.fn()); expect(core.setFailed).not.toHaveBeenCalled(); expect(fs.existsSync(f.session)).toBe(true); + }); + it('finishes failed operations without overriding their status', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'finish'; process.env.COPILOT_NATIVE_TICKET = f.session; done(1); + await runNativeVerificationEntry(jest.fn()); expect(core.setFailed).toHaveBeenCalled(); + }); + it('uses the post phase only for cleanup', async () => { + jest.mocked(core.getState).mockImplementation(key => key === 'copilot-native-post' || key === 'native-verification-owner' ? 'true' : key === 'native-verification-ticket' ? f.session : ''); done(); const run = jest.fn(); + await runNativeVerificationEntry(run); expect(run).not.toHaveBeenCalled(); expect(fs.existsSync(f.session)).toBe(false); + }); + it('allows post phases without an active verification session', async () => { + jest.mocked(core.getState).mockImplementation(key => key === 'copilot-native-post' ? 'true' : ''); const run = jest.fn(); await runNativeVerificationEntry(run); expect(run).not.toHaveBeenCalled(); + }); + it('starts one detached worker with private output channels and no credential file', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'prepare'; + jest.spyOn(cp, 'spawn').mockImplementation((_program, _args, options) => { + const root = options!.env!.COPILOT_NATIVE_WORKER_SESSION!; roots.push(root); const child = new EventEmitter() as EventEmitter & {pid: number; unref(): void}; + child.pid = 12345; child.unref = jest.fn(); queueMicrotask(() => { protocol.writeNativeRecord(root, 'done.json', { exitCode: 0 }); child.emit('spawn'); }); return child as never; + }); + const run = jest.fn(); await runNativeVerificationEntry(run); expect(run).not.toHaveBeenCalled(); expect(cp.spawn).toHaveBeenCalledTimes(1); + expect(core.saveState).toHaveBeenCalledWith('native-verification-ticket', roots[0]); expect(fs.readdirSync(roots[0])).not.toContain('credentials'); + }); + it('cleans a failed worker launch', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; process.env.GITHUB_ACTIONS = 'true'; process.env.COPILOT_NATIVE_PHASE = 'prepare'; + jest.spyOn(cp, 'spawn').mockImplementation((_program, _args, options) => { + roots.push(options!.env!.COPILOT_NATIVE_WORKER_SESSION!); const child = new EventEmitter(); queueMicrotask(() => child.emit('error', new Error('spawn'))); return child as never; + }); + await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('spawn'); expect(fs.existsSync(roots[0])).toBe(false); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_fixture.ts b/src/infrastructure/__tests__/native_verification_fixture.ts new file mode 100644 index 000000000..e5691f832 --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_fixture.ts @@ -0,0 +1,41 @@ +import { createPrivateVerificationRuntimeRoot } from '../agents/agent_private_install_root'; +import { removeNativeVerificationProfiles } from '../native_verification_profiles'; +import fs from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { randomUUID } from 'node:crypto'; +import { prepareUntrustedCommandEnvironment } from '../untrusted_command_environment'; +import { writeNativeRecord, readNativeRecord, type NativeVerificationRequest } from '../native_verification_protocol'; +import { VERIFICATION_SETUP_DIRECTORY } from '../../application/policies/verification_setup_policy'; +import type { PreparedVerificationSetup } from '../../application/ports/verification_setup_ports'; + +export const COMPOSITE = 'name: Fixture\nruns:\n using: composite\n steps:\n - run: echo reviewed\n shell: bash\n'; +export function nativeVerificationFixture() { + const root = fs.realpathSync(fs.mkdtempSync(join(tmpdir(), 'copilot-native-fixture-'))); + const runtime = prepareUntrustedCommandEnvironment(); + const session = fs.realpathSync(createPrivateVerificationRuntimeRoot()); + const previous = process.env.COPILOT_NATIVE_WORKER_SESSION; + const git = (...args: string[]) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + git('init'); git('config', 'user.name', 'Fixture'); git('config', 'user.email', 'fixture@example.test'); + fs.writeFileSync(join(root, 'manifest'), 'initial'); git('add', '.'); git('commit', '-m', 'fixture'); + const cwd = jest.spyOn(process, 'cwd').mockReturnValue(root); + process.env.COPILOT_NATIVE_WORKER_SESSION = session; + writeNativeRecord(session, 'session.json', { schemaVersion: 1, workspace: root, run: process.env.GITHUB_RUN_ID ?? '', + attempt: process.env.GITHUB_RUN_ATTEMPT ?? '', job: process.env.GITHUB_JOB ?? '', pid: process.pid }); + const setup: PreparedVerificationSetup = Object.freeze({ kind: 'ready', workspace: root, sourceSha: 'a'.repeat(40), files: Object.freeze([ + Object.freeze({ path: 'action.yml', base64: Buffer.from(COMPOSITE).toString('base64'), executable: false }), + Object.freeze({ path: 'scripts/prepare.sh', base64: Buffer.from('echo reviewed').toString('base64'), executable: true }), + ]) }); + const respond = (edit?: (request: NativeVerificationRequest) => Record) => { + const request = readNativeRecord(session, 'request.json', 32_768)!; + if (!request) throw new Error('No pending native setup'); + writeNativeRecord(session, `response-${request.id}.json`, { id: request.id, success: true, path: request.environment.PATH ?? '', ...edit?.(request) }); + }; + return { root, runtime, session, git, setup, respond, action: join(root, VERIFICATION_SETUP_DIRECTORY), staged: join(root, '.git/copilot-verification-setup'), + id: randomUUID(), cleanup: () => { + cwd.mockRestore(); + if (previous === undefined) delete process.env.COPILOT_NATIVE_WORKER_SESSION; else process.env.COPILOT_NATIVE_WORKER_SESSION = previous; + if (fs.existsSync(session)) removeNativeVerificationProfiles(session); fs.rmSync(session, { recursive: true, force: true }); runtime.cleanup(); fs.rmSync(root, { recursive: true, force: true }); + } }; +} diff --git a/src/infrastructure/__tests__/native_verification_post.test.ts b/src/infrastructure/__tests__/native_verification_post.test.ts new file mode 100644 index 000000000..50f6bd0d6 --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_post.test.ts @@ -0,0 +1,46 @@ +import { verificationExecutable } from '../verification_executable'; +import fs from 'node:fs'; +import { join } from 'node:path'; +import * as core from '@actions/core'; +import { runNativeVerificationEntry } from '../native_verification_controller'; +import { captureVerificationSetupAtCommit, verificationSetupDigest } from '../verification_setup_snapshot'; +import { stageNativeVerificationAction } from '../native_verification_staging'; +import { nativeVerificationFixture, COMPOSITE } from './native_verification_fixture'; + +jest.mock('@actions/core', () => ({getState: jest.fn(), saveState: jest.fn(), setOutput: jest.fn(), setFailed: jest.fn(), setSecret: jest.fn()})); + +describe('trusted snapshot during native post phases', () => { + let f: ReturnType; let source: string; let digest: string; let gitPath: string; + beforeEach(() => { + f = nativeVerificationFixture(); gitPath = verificationExecutable('git', f.runtime.environment); fs.mkdirSync(f.action, {recursive: true}); fs.writeFileSync(join(f.action, 'action.yml'), COMPOSITE); + f.git('add', '.'); f.git('commit', '-m', 'reviewed source'); source = f.git('rev-parse', 'HEAD'); + const snapshot = captureVerificationSetupAtCommit(source, f.root); if (snapshot.kind !== 'ready') throw new Error('snapshot'); + digest = verificationSetupDigest(snapshot); stageNativeVerificationAction(snapshot, f.session); + jest.mocked(core.getState).mockImplementation(key => ({'copilot-native-post': 'true', 'native-verification-ticket': f.session, + 'native-verification-source': source, 'native-verification-git': gitPath, 'native-verification-digest': digest}[key] ?? '')); + }); + afterEach(() => { jest.restoreAllMocks(); f.cleanup(); }); + it('keeps trusted composite metadata available for deferred post steps without replaying the operation', async () => { + fs.writeFileSync(join(f.action, 'action.yml'), 'candidate'); fs.writeFileSync(join(f.staged, 'action.yml'), 'tampered staging'); + const run = jest.fn(); await runNativeVerificationEntry(run); + expect(run).not.toHaveBeenCalled(); expect(fs.readFileSync(join(f.staged, 'action.yml'), 'utf8')).toBe(COMPOSITE); + expect(fs.readFileSync(join(f.action, 'action.yml'), 'utf8')).toBe('candidate'); + }); + it('rereads the original commit rather than a later candidate or moved branch', async () => { + fs.writeFileSync(join(f.action, 'action.yml'), 'runs: {using: composite, steps: [{run: "exit 99", shell: bash}]}'); + f.git('add', '.'); f.git('commit', '-m', 'candidate'); f.git('update-ref', 'refs/remotes/origin/main', f.git('rev-parse', 'HEAD')); + await runNativeVerificationEntry(jest.fn()); expect(fs.readFileSync(join(f.staged, 'action.yml'), 'utf8')).toBe(COMPOSITE); + }); + it('pins Git before native PATH changes instead of executing a tool supplied by the candidate', async () => { + const before = process.env.PATH; + try { process.env.PATH = f.root; await runNativeVerificationEntry(jest.fn()); } + finally { process.env.PATH = before; } + expect(fs.readFileSync(join(f.staged, 'action.yml'), 'utf8')).toBe(COMPOSITE); + }); + it('removes executable staging before failing a mismatched original content digest', async () => { + digest = '0'.repeat(64); await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('helper-invalid'); expect(fs.existsSync(f.staged)).toBe(false); + }); + it('removes executable staging when pinned source objects are unavailable', async () => { + source = '0'.repeat(40); await expect(runNativeVerificationEntry(jest.fn())).rejects.toThrow('helper-invalid'); expect(fs.existsSync(f.staged)).toBe(false); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_profiles.test.ts b/src/infrastructure/__tests__/native_verification_profiles.test.ts new file mode 100644 index 000000000..3f4c63beb --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_profiles.test.ts @@ -0,0 +1,38 @@ +import fs from 'node:fs'; +import { join } from 'node:path'; +import { nativeVerificationFixture } from './native_verification_fixture'; +import { nativeVerificationProfilePaths, nativeVerificationRetainsProfile, registerNativeVerificationProfile, removeNativeVerificationProfiles } from '../native_verification_profiles'; +import { writeNativeRecord } from '../native_verification_protocol'; + +describe('dependency-only native profile lifecycle', () => { + let f: ReturnType; + beforeEach(() => { f = nativeVerificationFixture(); }); + afterEach(() => { fs.rmSync(join(f.session, 'profiles.json'), { force: true }); f.cleanup(); }); + it('retains an operation-owned profile for native post steps, then removes it', () => { + expect(nativeVerificationRetainsProfile(f.runtime.environment.HOME)).toBe(false); + registerNativeVerificationProfile(f.session, f.runtime.environment.HOME); expect(nativeVerificationRetainsProfile(f.runtime.environment.HOME)).toBe(true); + removeNativeVerificationProfiles(f.session); expect(fs.existsSync(f.runtime.environment.HOME)).toBe(false); + removeNativeVerificationProfiles(f.session); + }); + it('rejects duplicate registration without corrupting the registry', () => { + registerNativeVerificationProfile(f.session, f.runtime.environment.HOME); + expect(() => registerNativeVerificationProfile(f.session, f.runtime.environment.HOME)).toThrow('handoff-invalid'); + expect(nativeVerificationProfilePaths(f.session)).toEqual([f.runtime.environment.HOME]); + }); + it.each([null, {}, ['/outside'], Array(21).fill('/outside')])('rejects malformed profile metadata %j', value => { + writeNativeRecord(f.session, 'profiles.json', value); expect(() => nativeVerificationProfilePaths(f.session)).toThrow('handoff-invalid'); + }); + it('never registers the controller root or an unrelated directory', () => { + expect(() => registerNativeVerificationProfile(f.session, f.session)).toThrow('handoff-invalid'); + expect(() => registerNativeVerificationProfile(f.session, f.root)).toThrow('handoff-invalid'); + expect(nativeVerificationProfilePaths(f.session)).toEqual([]); + }); + it('refuses cleanup of a profile whose owner marker changed', () => { + registerNativeVerificationProfile(f.session, f.runtime.environment.HOME); + fs.writeFileSync(join(f.runtime.environment.HOME, 'native-session-owner'), 'other session'); + expect(() => removeNativeVerificationProfiles(f.session)).toThrow('handoff-invalid'); expect(fs.existsSync(f.runtime.environment.HOME)).toBe(true); + }); + it('does not retain a profile outside a native worker', () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; expect(nativeVerificationRetainsProfile(f.runtime.environment.HOME)).toBe(false); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_protocol.test.ts b/src/infrastructure/__tests__/native_verification_protocol.test.ts new file mode 100644 index 000000000..fc0e2dc7e --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_protocol.test.ts @@ -0,0 +1,78 @@ +import fs from 'node:fs'; +import { join } from 'node:path'; +import { nativeSession, readNativeRecord, writeNativeRecord } from '../native_verification_protocol'; +import { verificationCandidateIdentity } from '../native_verification_candidate'; +import { stageNativeVerificationAction, cleanupNativeVerificationAction } from '../native_verification_staging'; +import { nativeVerificationFixture } from './native_verification_fixture'; + +describe('native session provenance and filesystem contracts', () => { + let f: ReturnType; + beforeEach(() => { f = nativeVerificationFixture(); }); + afterEach(() => { jest.restoreAllMocks(); f.cleanup(); }); + it('reads atomically published operation records and absent records', () => { + writeNativeRecord(f.session, 'record.json', { value: 42 }); expect(readNativeRecord(f.session, 'record.json')).toEqual({ value: 42 }); + expect(readNativeRecord(f.session, 'absent.json')).toBeUndefined(); expect(nativeSession(f.session).pid).toBe(process.pid); + }); + it.each(['run', 'attempt', 'job', 'workspace', 'pid', 'schemaVersion'])('rejects a session with mismatched %s', field => { + const session = nativeSession(f.session); writeNativeRecord(f.session, 'session.json', { ...session, [field]: 'other' }); + expect(() => nativeSession(f.session)).toThrow('handoff-invalid'); + }); + it('rejects missing sessions', () => { + fs.rmSync(join(f.session, 'session.json')); expect(() => nativeSession(f.session)).toThrow('handoff-invalid'); + }); + it('rejects arbitrary ticket roots before reading or cleaning them', () => { + expect(() => nativeSession(f.root)).toThrow('handoff-invalid'); + }); + it('rejects writable operation directories on POSIX', () => { + if (process.platform === 'win32') return; + fs.chmodSync(f.session, 0o777); expect(() => nativeSession(f.session)).toThrow('handoff-invalid'); fs.chmodSync(f.session, 0o700); + }); + it.each(['oversize', 'directory', 'hardlink', 'invalid-json'])('rejects %s records', kind => { + const file = join(f.session, 'record.json'); + if (kind === 'directory') fs.mkdirSync(file); + else if (kind === 'hardlink') { fs.writeFileSync(join(f.session, 'original'), '{}'); fs.linkSync(join(f.session, 'original'), file); } + else fs.writeFileSync(file, kind === 'oversize' ? 'x'.repeat(16_385) : 'not json'); + expect(() => readNativeRecord(f.session, 'record.json')).toThrow('handoff-invalid'); + }); + it('rejects replaced records before consuming their content', () => { + writeNativeRecord(f.session, 'record.json', {}); const original = fs.fstatSync; + jest.spyOn(fs, 'fstatSync').mockImplementation(((fd: number, options: {bigint: true}) => ({ ...original(fd, options), ino: 0n })) as typeof fs.fstatSync); + expect(() => readNativeRecord(f.session, 'record.json')).toThrow('handoff-invalid'); + }); + it('restores originals on explicit cancellation and makes cleanup idempotent', () => { + fs.mkdirSync(f.action, { recursive: true }); fs.writeFileSync(join(f.action, 'action.yml'), 'original'); + stageNativeVerificationAction(f.setup as never, f.session); cleanupNativeVerificationAction(f.session, f.root); cleanupNativeVerificationAction(f.session, f.root); + expect(fs.readFileSync(join(f.action, 'action.yml'), 'utf8')).toBe('original'); + }); + it('rejects a nonstandard Git metadata checkout', () => { + fs.renameSync(join(f.root, '.git'), join(f.root, '.git-original')); fs.writeFileSync(join(f.root, '.git'), 'gitdir: elsewhere'); + expect(() => stageNativeVerificationAction(f.setup as never, f.session)).toThrow('native-workflow-required'); + }); + it('never overwrites a preexisting unowned staging directory', () => { + fs.mkdirSync(f.staged); fs.writeFileSync(join(f.staged, 'action.yml'), 'unrelated'); + expect(() => stageNativeVerificationAction(f.setup as never, f.session)).toThrow('handoff-invalid'); + expect(fs.readFileSync(join(f.staged, 'action.yml'), 'utf8')).toBe('unrelated'); + }); + it('preserves an unowned broken staging link', () => { + fs.symlinkSync(join(f.root, 'missing'), f.staged, process.platform === 'win32' ? 'junction' : 'dir'); + expect(() => stageNativeVerificationAction(f.setup as never, f.session)).toThrow('handoff-invalid'); + expect(fs.lstatSync(f.staged).isSymbolicLink()).toBe(true); + }); + it('rejects malformed staging records without deleting unrelated content', () => { + writeNativeRecord(f.session, 'staging.json', { target: '/other' }); + expect(() => cleanupNativeVerificationAction(f.session, f.root)).toThrow('handoff-invalid'); + }); + it('binds tracked contents, branch and staged content, not filesystem timestamps', () => { + const before = verificationCandidateIdentity(f.root, f.runtime.environment); + fs.utimesSync(join(f.root, 'manifest'), new Date(), new Date()); expect(verificationCandidateIdentity(f.root, f.runtime.environment)).toBe(before); + f.git('checkout', '-b', 'other'); expect(verificationCandidateIdentity(f.root, f.runtime.environment)).not.toBe(before); + }); + it('rejects excessive publishable untracked files', () => { + for (let i = 0; i < 257; i++) fs.writeFileSync(join(f.root, `untracked-${i}`), 'x'); + expect(() => verificationCandidateIdentity(f.root, f.runtime.environment)).toThrow('candidate-changed'); + }); + it('binds untracked file content and links without following the link', () => { + fs.writeFileSync(join(f.root, 'untracked'), 'one'); const before = verificationCandidateIdentity(f.root, f.runtime.environment); + fs.writeFileSync(join(f.root, 'untracked'), 'two'); expect(verificationCandidateIdentity(f.root, f.runtime.environment)).not.toBe(before); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_redaction.test.ts b/src/infrastructure/__tests__/native_verification_redaction.test.ts new file mode 100644 index 000000000..ffa8c1ffa --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_redaction.test.ts @@ -0,0 +1,40 @@ +import { nativeWorkerSecrets, redactNativeWorkerLogs } from '../native_verification_redaction'; + +describe('native worker private-log redaction', () => { + afterEach(() => jest.restoreAllMocks()); + it('collects credentials from Action env and JSON input without treating metadata as secrets', () => { + expect(nativeWorkerSecrets({ INPUT_TOKEN: 'pat-secret', OPENAI_API_KEY: 'model-secret', INPUT_VARS_JSON: '{"token":"json-secret","main-branch":"master"}', GITHUB_RUN_ID: '12345' })) + .toEqual(expect.arrayContaining(['pat-secret', 'model-secret', 'json-secret'])); + expect(nativeWorkerSecrets({ GITHUB_RUN_ID: '12345', INPUT_VARS_JSON: 'invalid' })).toEqual([]); + }); + it('redacts secrets spanning chunks before stdout reaches disk', () => { + const writes: string[] = []; jest.spyOn(process.stdout, 'write').mockImplementation((data) => { writes.push(String(data)); return true; }); + const restore = redactNativeWorkerLogs({ INPUT_TOKEN: 'private-token' }); + process.stdout.write('prefix private-'); process.stdout.write('token suffix\n'); restore(); + expect(writes.join('')).toBe('prefix [REDACTED] suffix\n'); + }); + it('redacts stderr and flushes a final unterminated line', () => { + const writes: string[] = []; jest.spyOn(process.stderr, 'write').mockImplementation(data => { writes.push(String(data)); return true; }); + const restore = redactNativeWorkerLogs({ MODEL_API_KEY: 'private-key' }); process.stderr.write(Buffer.from('private-key')); restore(); expect(writes.join('')).toBe('[REDACTED]'); + }); + it('consumes dynamic mask commands and never stores their payload', () => { + const writes: string[] = []; jest.spyOn(process.stdout, 'write').mockImplementation(data => { writes.push(String(data)); return true; }); + const restore = redactNativeWorkerLogs({}); process.stdout.write('::add-mask::dynamic%25secret\n'); process.stdout.write('dynamic%secret\n'); restore(); + expect(writes.join('')).toBe('[REDACTED]\n'); + }); + it('supports multiline credentials and callbacks', () => { + const writes: string[] = []; jest.spyOn(process.stdout, 'write').mockImplementation(data => { writes.push(String(data)); return true; }); + const restore = redactNativeWorkerLogs({ TOKEN: 'first-line\nsecond-line' }); const callback = jest.fn(); + process.stdout.write('first-line\nsecond-line\n', callback); process.stdout.write('ok\n', 'utf8', callback); restore(); + expect(writes.join('')).toBe('[REDACTED]\n[REDACTED]\nok\n'); expect(callback).toHaveBeenCalledTimes(2); + }); + it('preserves split UTF-8 and ordinary operational logs', () => { + const writes: string[] = []; jest.spyOn(process.stdout, 'write').mockImplementation(data => { writes.push(String(data)); return true; }); + const restore = redactNativeWorkerLogs({}); const bytes = Buffer.from('verificación\n'); + process.stdout.write(bytes.subarray(0, 12)); process.stdout.write(bytes.subarray(12)); restore(); expect(writes.join('')).toBe('verificación\n'); + }); + it('bounds an unterminated line', () => { + jest.spyOn(process.stdout, 'write').mockReturnValue(true); const restore = redactNativeWorkerLogs({}); + expect(() => process.stdout.write('x'.repeat(1_048_577))).toThrow('fixed limit'); restore(); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_runner.test.ts b/src/infrastructure/__tests__/native_verification_runner.test.ts new file mode 100644 index 000000000..e0adfaa24 --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_runner.test.ts @@ -0,0 +1,119 @@ +import fs from 'node:fs'; +import { join, delimiter } from 'node:path'; +import { prepareRepositoryVerification } from '../verification_setup_runner'; +import { GitCommitAdapter } from '../git_commit_adapter'; +import { runVerifyCommands } from '../../application/usecases/steps/commit/bugbot/verify_command_runner'; +import { writeNativeRecord } from '../native_verification_protocol'; +import { nativeVerificationFixture, COMPOSITE } from './native_verification_fixture'; + +// The test driver supplies native runner results; production never interprets YAML. +describe('native verification boundary', () => { + let f: ReturnType; + beforeEach(() => { f = nativeVerificationFixture(); }); + afterEach(() => { jest.restoreAllMocks(); f.cleanup(); }); + it('does no work for disabled setup', async () => { + await prepareRepositoryVerification({ kind: 'disabled' }, f.runtime.environment); + expect(fs.existsSync(join(f.session, 'request.json'))).toBe(false); + }); + it('defers blocked acquisition until an actual verifier runs', async () => { + const execute = jest.fn().mockResolvedValue(0); + const adapter = new GitCommitAdapter(execute, jest.fn(), { kind: 'blocked', reason: 'source-unavailable' }); + await adapter.execute('git', ['status'], { untrusted: true }); + await expect(adapter.execute('node', ['test'], { untrusted: true, verifyRuntimePrerequisites: true })).rejects.toMatchObject({ reason: 'source-unavailable' }); + expect(execute).toHaveBeenCalledTimes(1); + }); + it('executes no setup for empty verification lists', async () => { + const execute = jest.fn(); expect(await runVerifyCommands([], execute)).toEqual({ success: true }); expect(execute).not.toHaveBeenCalled(); + }); + it('fails closed in the local CLI or an old workflow without native continuation', async () => { + delete process.env.COPILOT_NATIVE_WORKER_SESSION; + await expect(prepareRepositoryVerification(f.setup, f.runtime.environment)).rejects.toMatchObject({ reason: 'native-workflow-required' }); + }); + it('stages only pinned YAML and assets, then restores the candidate byte for byte', async () => { + fs.mkdirSync(f.action, { recursive: true }); fs.writeFileSync(join(f.action, 'action.yml'), 'candidate'); + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); + expect(fs.readFileSync(join(f.staged, 'action.yml'), 'utf8')).toBe(COMPOSITE); + expect(fs.readFileSync(join(f.action, 'action.yml'), 'utf8')).toBe('candidate'); + expect(fs.readFileSync(join(f.staged, 'scripts/prepare.sh'), 'utf8')).toBe('echo reviewed'); + f.respond(); await running; + expect(fs.readFileSync(join(f.action, 'action.yml'), 'utf8')).toBe('candidate'); + expect(fs.existsSync(join(f.action, 'scripts'))).toBe(false); + }); + it('retains native metadata outside the publishable candidate for deferred post steps', async () => { + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); f.respond(); await running; + expect(fs.existsSync(f.action)).toBe(false); expect(fs.existsSync(f.staged)).toBe(true); expect(f.git('status', '--porcelain')).toBe(''); + }); + it('prepares each changed candidate with a fresh HOME before preflight and verification', async () => { + const homes: string[] = []; const order: string[] = []; + const adapter = new GitCommitAdapter(async (_p, _a, options) => { order.push('verify'); homes.push(options!.env!.HOME); return 0; }, () => { order.push('preflight'); }, f.setup); + for (const version of ['first', 'changed']) { + fs.writeFileSync(join(f.root, 'manifest'), version); + const running = adapter.execute('node', ['test'], { untrusted: true, verifyRuntimePrerequisites: true }); + f.respond(request => { + expect(fs.readFileSync(join(f.root, 'manifest'), 'utf8')).toBe(version); + for (const key of ['PAT', 'GITHUB_TOKEN', 'OPENAI_API_KEY', 'CODEX_HOME', 'NODE_OPTIONS']) expect(request.environment).not.toHaveProperty(key); + return {}; + }); + await running; + } + expect(order).toEqual(['preflight', 'verify', 'preflight', 'verify']); expect(new Set(homes).size).toBe(2); + expect(homes.every(home => fs.existsSync(home))).toBe(true); + }); + it('accepts only validated runtime directory additions and JAVA_HOME', async () => { + const bin = join(f.root, 'tools'); fs.mkdirSync(bin); const running = prepareRepositoryVerification(f.setup, f.runtime.environment); + f.respond(request => ({ path: `${bin}${delimiter}${request.environment.PATH}`, javaHome: bin })); await running; + expect(f.runtime.environment.PATH.split(delimiter)[0]).toBe(bin); expect(f.runtime.environment.JAVA_HOME).toBe(bin); + }); + it('drops earlier profiles from the runner PATH instead of reusing their readiness', async () => { + const adapter = new GitCommitAdapter(async (_p, _a, options) => { expect(options!.env!.PATH).not.toContain('stale-tools'); return 0; }, () => {}, f.setup); + let firstHome = ''; + const first = adapter.execute('node', ['test'], { untrusted: true, verifyRuntimePrerequisites: true }); + f.respond(request => { firstHome = request.environment.HOME; fs.mkdirSync(join(firstHome, 'stale-tools')); return {}; }); await first; + const second = adapter.execute('node', ['test'], { untrusted: true, verifyRuntimePrerequisites: true }); + f.respond(request => ({ path: `${join(firstHome, 'stale-tools')}${delimiter}${request.environment.PATH}` })); await second; + }); + it.each(['tracked', 'untracked', 'index', 'head'])('withholds verification when setup changes the %s candidate', async kind => { + if (kind === 'untracked') fs.writeFileSync(join(f.root, 'new-file'), 'candidate'); + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); + if (kind === 'tracked') fs.writeFileSync(join(f.root, 'manifest'), 'modified'); + if (kind === 'untracked') fs.writeFileSync(join(f.root, 'new-file'), 'modified'); + if (kind === 'index') { fs.writeFileSync(join(f.root, 'manifest'), 'modified'); f.git('add', '.'); } + if (kind === 'head') { f.git('commit', '--allow-empty', '-m', 'unexpected'); } + f.respond(); await expect(running).rejects.toMatchObject({ reason: 'candidate-changed' }); + expect(fs.existsSync(f.action)).toBe(false); + }); + (process.platform === 'win32' ? it.skip : it).each(['executable mode', 'file type'])('withholds verification when setup changes an untracked candidate %s without changing its bytes', async kind => { + const file = join(f.root, 'new-script'); + fs.writeFileSync(file, 'same'); fs.chmodSync(file, 0o644); + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); + if (kind === 'executable mode') fs.chmodSync(file, 0o755); + else { fs.rmSync(file); fs.symlinkSync('same', file); } + f.respond(); await expect(running).rejects.toMatchObject({ reason: 'candidate-changed' }); + }); + it.each([{success: false}, {id: 'wrong'}, {path: 1}, {javaHome: 1}, {path: '/outside/invalid'}])('rejects failed or malformed native outcomes %j', async response => { + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); f.respond(() => response); + await expect(running).rejects.toThrow('Verification, commit and push are withheld'); + expect(fs.existsSync(f.action)).toBe(false); + }); + it('withholds the command and probes after a setup failure', async () => { + const execute = jest.fn(); const preflight = jest.fn(); const adapter = new GitCommitAdapter(execute, preflight, f.setup); + const running = adapter.execute('node', ['test'], { untrusted: true, verifyRuntimePrerequisites: true }); f.respond(() => ({ success: false })); + await expect(running).rejects.toMatchObject({ reason: 'process-failed' }); expect(execute).not.toHaveBeenCalled(); expect(preflight).not.toHaveBeenCalled(); + }); + it('cancels a pending setup and restores the candidate', async () => { + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); writeNativeRecord(f.session, 'cancel.json', { cancelled: true }); + await expect(running).rejects.toMatchObject({ reason: 'cancelled' }); expect(fs.existsSync(f.action)).toBe(false); + }); + it('times out without retrying or publishing', async () => { + let now = 0; jest.spyOn(Date, 'now').mockImplementation(() => now); + const running = prepareRepositoryVerification(f.setup, f.runtime.environment); now = 300_001; + await expect(running).rejects.toMatchObject({ reason: 'timeout' }); expect(fs.existsSync(f.action)).toBe(false); + }); + it('enforces the fixed native invocation ceiling', async () => { + writeNativeRecord(f.session, 'request-count.json', { count: 20 }); + await expect(prepareRepositoryVerification(f.setup, f.runtime.environment)).rejects.toMatchObject({ reason: 'handoff-invalid' }); + }); + it('rejects workspace switches before setup', async () => { + await expect(prepareRepositoryVerification({ ...f.setup, workspace: f.session } as typeof f.setup, f.runtime.environment)).rejects.toMatchObject({ reason: 'workspace-changed' }); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_source_runtime.test.ts b/src/infrastructure/__tests__/native_verification_source_runtime.test.ts new file mode 100644 index 000000000..790ac49b8 --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_source_runtime.test.ts @@ -0,0 +1,42 @@ +import fs from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { cleanupNativeSourceRuntime } from '../native_verification_source_runtime'; +const {prepareNativeSourceAction} = require('../../../scripts/prepare-native-source-action.cjs') as {prepareNativeSourceAction(root: string, fixture?: boolean): string}; + +describe('immutable source Action runtime snapshot', () => { + let container: string; let workspace: string; + beforeEach(() => { + container = fs.realpathSync(fs.mkdtempSync(join(tmpdir(), 'copilot-source-action-'))); workspace = join(container, 'workspace'); + fs.mkdirSync(join(workspace, 'build/github_action'), {recursive: true}); fs.writeFileSync(join(workspace, 'action.yml'), 'runs: {using: node24, main: build/github_action/index.js}'); + fs.writeFileSync(join(workspace, 'build/github_action/index.js'), 'original'); + }); + afterEach(() => { jest.restoreAllMocks(); fs.rmSync(container, {recursive: true, force: true}); }); + it('captures the manifest and runtime outside the mutable candidate', () => { + const root = prepareNativeSourceAction(workspace); fs.writeFileSync(join(workspace, 'build/github_action/index.js'), 'candidate'); + expect(root).toBe(join(container, '.copilot-native-runtime')); expect(fs.readFileSync(join(root, 'build/github_action/index.js'), 'utf8')).toBe('original'); + cleanupNativeSourceRuntime(root, workspace); expect(fs.existsSync(root)).toBe(false); cleanupNativeSourceRuntime(root, workspace); + }); + it('replaces an owned stale snapshot and preserves setup assets', () => { + fs.mkdirSync(join(workspace, 'setup')); fs.writeFileSync(join(workspace, 'setup/file.yml'), 'asset'); const root = prepareNativeSourceAction(workspace); + fs.writeFileSync(join(workspace, 'build/github_action/index.js'), 'reviewed-new-version'); prepareNativeSourceAction(workspace); + expect(fs.readFileSync(join(root, 'build/github_action/index.js'), 'utf8')).toBe('reviewed-new-version'); expect(fs.readFileSync(join(root, 'setup/file.yml'), 'utf8')).toBe('asset'); + }); + it('supports the native CI fixture with the same isolation contract', () => { + const source = join(workspace, '.github/actions/copilot-native-fixture'); fs.mkdirSync(join(source, 'dist'), {recursive: true}); + fs.writeFileSync(join(source, 'action.yml'), 'fixture'); fs.writeFileSync(join(source, 'dist/index.js'), 'fixture-code'); + const root = prepareNativeSourceAction(workspace, true); expect(fs.readFileSync(join(root, 'dist/index.js'), 'utf8')).toBe('fixture-code'); cleanupNativeSourceRuntime(root, workspace); + }); + it('refuses to overwrite an unrelated directory', () => { + fs.mkdirSync(join(container, '.copilot-native-runtime')); fs.writeFileSync(join(container, '.copilot-native-runtime/keep'), 'original'); + expect(() => prepareNativeSourceAction(workspace)).toThrow('not owned'); expect(fs.readFileSync(join(container, '.copilot-native-runtime/keep'), 'utf8')).toBe('original'); + }); + it('refuses a receipt from another workspace', () => { + const root = prepareNativeSourceAction(workspace); fs.writeFileSync(join(root, '.copilot-runtime-snapshot.json'), JSON.stringify({kind: 'copilot-native-runtime', workspace: 'other'})); + expect(() => prepareNativeSourceAction(workspace)).toThrow('ownership'); expect(() => cleanupNativeSourceRuntime(root, workspace)).toThrow('handoff-invalid'); + }); + it('rejects unsafe source assets and cleanup paths', () => { + fs.rmSync(join(workspace, 'build/github_action/index.js')); fs.mkdirSync(join(workspace, 'build/github_action/index.js')); + expect(() => prepareNativeSourceAction(workspace)).toThrow('regular file'); expect(() => cleanupNativeSourceRuntime(workspace, workspace)).toThrow('handoff-invalid'); + }); +}); diff --git a/src/infrastructure/__tests__/native_verification_workflow.test.ts b/src/infrastructure/__tests__/native_verification_workflow.test.ts new file mode 100644 index 000000000..a127183ad --- /dev/null +++ b/src/infrastructure/__tests__/native_verification_workflow.test.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { load } from 'js-yaml'; +const { assertNativeVerificationWorkflow } = require('../../../scripts/validate-workflow-contract.cjs') as { assertNativeVerificationWorkflow(file: string, workflow: any): void }; +const { MAX_REQUESTS, phaseSteps } = require('../../../scripts/generate-native-verification-workflows.cjs') as {MAX_REQUESTS: number; phaseSteps(action: string): any[]}; + +describe('native verification workflow contract', () => { + it.each(['.github/workflows', 'setup/workflows'])('parses canonical native YAML and anchors from %s', directory => { + for (const name of ['copilot_issue_comment.yml', 'copilot_pull_request_comment.yml']) { + const file = path.join(process.cwd(), directory, name); const workflow = load(fs.readFileSync(file, 'utf8')); + expect(() => assertNativeVerificationWorkflow(file, workflow)).not.toThrow(); + } + }); + it('has exactly twenty native setup slots and one completion, with five-minute native timeouts', () => { + const steps = phaseSteps('./'); expect(MAX_REQUESTS).toBe(20); expect(steps).toHaveLength(41); + expect(steps.filter(step => step.uses === './.git/copilot-verification-setup').every(step => step['timeout-minutes'] === 5)).toBe(true); + }); + it.each(['timeout', 'env', 'condition', 'reference', 'request', 'outcome', 'finish', 'extra', 'remove', 'prepare'])('rejects a weakened %s contract', kind => { + const file = path.join(process.cwd(), 'setup/workflows/copilot_issue_comment.yml'); const workflow = load(fs.readFileSync(file, 'utf8')) as any; + const job = Object.values(workflow.jobs)[0] as any; const start = job.steps.findIndex((step: any) => step.id === 'copilot'); const steps = job.steps; + if (kind === 'timeout') steps[start + 1]['timeout-minutes'] = 60; + if (kind === 'env') steps[start + 1].env.OPENAI_API_KEY = '${{ secrets.OPENAI_API_KEY }}'; + if (kind === 'condition') steps[start + 1].if = '${{ always() }}'; + if (kind === 'reference') steps[start + 2].uses = 'vypdev/copilot@other'; + if (kind === 'request') steps[start + 2].env.COPILOT_NATIVE_REQUEST = 'stale'; + if (kind === 'outcome') steps[start + 2].env.COPILOT_NATIVE_OUTCOME = 'success'; + if (kind === 'finish') steps[steps.length - 1].if = '${{ success() }}'; + if (kind === 'extra') steps.push({run: 'echo bypass'}); + if (kind === 'remove') steps.splice(start + 1, 2); + if (kind === 'prepare') delete steps[start].env.COPILOT_NATIVE_PHASE; + expect(() => assertNativeVerificationWorkflow(file, workflow)).toThrow(/native/u); + }); +}); diff --git a/src/infrastructure/__tests__/verification_executable.test.ts b/src/infrastructure/__tests__/verification_executable.test.ts new file mode 100644 index 000000000..b8fc671c2 --- /dev/null +++ b/src/infrastructure/__tests__/verification_executable.test.ts @@ -0,0 +1,38 @@ +import fs from 'node:fs'; +import { join, delimiter } from 'node:path'; +import { tmpdir } from 'node:os'; +import { verificationExecutable } from '../verification_executable'; + +describe('verification executable selection', () => { + let root: string; + beforeEach(() => { root = fs.mkdtempSync(join(tmpdir(), 'copilot-executable-')); }); + afterEach(() => { jest.restoreAllMocks(); fs.rmSync(root, { recursive: true, force: true }); }); + it('selects the executable from the isolated PATH rather than the worker PATH', () => { + const binary = join(root, process.platform === 'win32' ? 'python.EXE' : 'python'); fs.writeFileSync(binary, 'fixture', {mode: 0o700}); + expect(verificationExecutable('python', {PATH: root})).toBe(binary); + }); + it('preserves an explicit invocation path rather than resolving away virtual-environment links', () => { + const binary = join(root, 'tool.exe'); fs.writeFileSync(binary, 'fixture', {mode: 0o700}); + expect(verificationExecutable(binary, {PATH: ''}, 'win32')).toBe(binary); + }); + it('skips non-files and unavailable candidates without falling back to the host PATH', () => { + fs.mkdirSync(join(root, process.platform === 'win32' ? 'node.EXE' : 'node')); expect(() => verificationExecutable('node', {PATH: root})).toThrow('isolated PATH'); + }); + it('honors Windows executable extensions', () => { + const binary = join(root, 'pnpm.CMD'); fs.writeFileSync(binary, 'fixture', {mode: 0o700}); + expect(verificationExecutable('pnpm', {PATH: root, PATHEXT: '.EXE;.CMD'}, 'win32')).toBe(binary); + }); + it('selects the Windows command shim rather than its extensionless Unix sibling', () => { + fs.writeFileSync(join(root, 'pnpm'), 'Unix shell wrapper', {mode: 0o700}); fs.writeFileSync(join(root, 'pnpm.CMD'), 'Windows command wrapper', {mode: 0o700}); + expect(verificationExecutable('pnpm', {PATH: root, PATHEXT: '.EXE;.CMD'}, 'win32')).toBe(join(root, 'pnpm.CMD')); + }); + it('supports the Windows default extension list', () => { + const binary = join(root, 'tool.EXE'); fs.writeFileSync(binary, 'fixture', {mode: 0o700}); + expect(verificationExecutable('tool', {PATH: root}, 'win32')).toBe(binary); + }); + it('fails closed for absent PATH and inaccessible tools', () => { + expect(() => verificationExecutable('python', {})).toThrow('isolated PATH'); + const file = join(root, process.platform === 'win32' ? 'tool.EXE' : 'tool'); fs.writeFileSync(file, 'fixture', {mode: 0o700}); jest.spyOn(fs, 'accessSync').mockImplementation(() => { throw new Error('denied'); }); + expect(() => verificationExecutable('tool', {PATH: `${root}${delimiter}/missing`})).toThrow('isolated PATH'); + }); +}); diff --git a/src/infrastructure/__tests__/verification_setup_configuration.test.ts b/src/infrastructure/__tests__/verification_setup_configuration.test.ts new file mode 100644 index 000000000..1f4350a48 --- /dev/null +++ b/src/infrastructure/__tests__/verification_setup_configuration.test.ts @@ -0,0 +1,64 @@ +import fs from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import * as yaml from 'js-yaml'; +import { createDefaultSetupConfiguration, mergeSetupConfiguration, buildSetupActionInputs, buildSetupRepositoryVariables, validateSetupConfiguration } from '../../application/policies/setup_configuration_policy'; +import { loadSetupConfigurationOverrides } from '../../cli/setup_config_file'; +import { SETUP_ORGANIZATION_VARIABLE_BINDINGS } from '../../application/policies/setup_organization_variable_bindings'; +import { copySetupFiles, ensureGitHubDirs } from '../../utils/setup_files'; +import { parseIssueWorkflowBoolean } from '../../actions/input_boolean_policy'; + +jest.mock('../../utils/logger', () => ({ logInfo: jest.fn() })); +describe('verification setup installation contract', () => { + let root: string; + beforeEach(() => { root = fs.mkdtempSync(path.join(tmpdir(), 'copilot-setup-config-')); }); + afterEach(() => { fs.rmSync(root, { recursive: true, force: true }); }); + it('defaults off and accepts legacy configuration without the additive property', () => { + const config = createDefaultSetupConfiguration(); + expect(config.repository.verificationSetup).toBe(false); + delete config.repository.verificationSetup; + expect(validateSetupConfiguration(config)).not.toContain('verification-setup must be a boolean value.'); + expect(buildSetupActionInputs(config)['verification-setup']).toBe('false'); + expect(buildSetupRepositoryVariables(config)).toContainEqual({ name: 'VERIFICATION_SETUP', value: 'false' }); + }); + it('loads and plans the explicit opt-in consistently', () => { + const file = path.join(root, 'setup.yml'); fs.writeFileSync(file, 'repository:\n verificationSetup: true\n'); + const config = mergeSetupConfiguration(createDefaultSetupConfiguration(), loadSetupConfigurationOverrides(file)); + expect(buildSetupActionInputs(config)['verification-setup']).toBe('true'); + expect(buildSetupRepositoryVariables(config)).toContainEqual({ name: 'VERIFICATION_SETUP', value: 'true' }); + expect(SETUP_ORGANIZATION_VARIABLE_BINDINGS).toContainEqual({ variable: 'VERIFICATION_SETUP', path: 'repository.verificationSetup' }); + }); + it.each(['true', '/arbitrary/helper', 1])('rejects nonboolean opt-in %j in config files and plans', value => { + const file = path.join(root, 'setup.json'); fs.writeFileSync(file, JSON.stringify({ repository: { verificationSetup: value } })); + expect(() => loadSetupConfigurationOverrides(file)).toThrow('repository.verificationSetup must be a boolean'); + const config = createDefaultSetupConfiguration(); Object.assign(config.repository, { verificationSetup: value }); + expect(validateSetupConfiguration(config)).toContain('verification-setup must be a boolean value.'); + }); + it.each(['yes', 'script.mjs'])('rejects arbitrary Action opt-in %s', value => { + expect(() => parseIssueWorkflowBoolean(value, 'verification-setup', false)).toThrow('verification-setup must be true or false'); + }); + it.each(['copilot_issue.yml', 'copilot_issue_comment.yml', 'copilot_pull_request.yml', 'copilot_pull_request_comment.yml', 'copilot_pull_request_review_state.yml', 'copilot_commit.yml'])( + 'keeps source and distributed %s opt-in explicit and disabled by default', name => { + for (const directory of ['.github/workflows', 'setup/workflows']) { + const workflow = yaml.load(fs.readFileSync(path.resolve(__dirname, '../../..', directory, name), 'utf8')) as {jobs: Record}[]}>}; + const actions = Object.values(workflow.jobs).flatMap(job => job.steps).filter(step => step.uses === './' || step.uses === './../.copilot-native-runtime' || step.uses?.startsWith('vypdev/copilot@')); + expect(actions.length).toBeGreaterThan(0); + for (const action of actions) expect(action.with?.['verification-setup']).toBe("${{ vars.VERIFICATION_SETUP || 'false' }}"); + } + }, + ); + it('preserves the consumer helper through install and repeated workflow updates', () => { + const helper = path.join(root, '.github/actions/copilot-verification-setup/action.yml'); fs.mkdirSync(path.dirname(helper), { recursive: true }); + const bytes = 'export const repositoryOwned = true;\n'; fs.writeFileSync(helper, bytes); + const config = createDefaultSetupConfiguration(); config.repository.verificationSetup = true; + const source = path.resolve(__dirname, '../../../setup'); + ensureGitHubDirs(root); copySetupFiles(root, source, config.features, { setupConfiguration: config }); + const approved = fs.readdirSync(path.join(root, '.github/workflows')); + for (let count = 0; count < 2; count++) { + copySetupFiles(root, source, config.features, { setupConfiguration: config, updateExistingWorkflows: true, approvedWorkflowFiles: approved }); + expect(fs.readFileSync(helper, 'utf8')).toBe(bytes); + } + const manifest = fs.readFileSync(path.join(root, '.copilot/setup-manifest.json'), 'utf8'); + expect(manifest).not.toContain('copilot-verification-setup/action.yml'); + }); +}); diff --git a/src/infrastructure/__tests__/verification_setup_runner.test.ts b/src/infrastructure/__tests__/verification_setup_runner.test.ts new file mode 100644 index 000000000..0b8e231b5 --- /dev/null +++ b/src/infrastructure/__tests__/verification_setup_runner.test.ts @@ -0,0 +1,80 @@ +import fs from 'node:fs'; +import { tmpdir } from 'node:os'; +import { delimiter, join } from 'node:path'; +import { applyVerificationSetupResult } from '../verification_setup_result'; +import { VerificationSetupError } from '../../application/policies/verification_setup_policy'; + +describe('bounded setup result filesystem contract', () => { + let root: string; let environment: Record; let result: string; + beforeEach(() => { + root = fs.realpathSync(fs.mkdtempSync(join(tmpdir(), 'copilot-setup-result-'))); + environment = { HOME: root, PATH: 'baseline' }; result = join(root, 'result.json'); + }); + afterEach(() => { jest.restoreAllMocks(); fs.rmSync(root, { recursive: true, force: true }); }); + it('accepts an absent result without changing verification environment', () => { + applyVerificationSetupResult(result, environment, [root]); expect(environment).toEqual({ HOME: root, PATH: 'baseline' }); + }); + it('prepends approved directories and changes only JAVA_HOME', () => { + const bin = join(root, 'bin'); fs.mkdirSync(bin); + fs.writeFileSync(result, JSON.stringify({ schemaVersion: 1, path: [bin], environment: { JAVA_HOME: root } })); + applyVerificationSetupResult(result, environment, [root]); + expect(environment).toEqual({ HOME: root, PATH: `${bin}${delimiter}baseline`, JAVA_HOME: root }); + }); + it('supports a PATH addition with no inherited PATH', () => { + delete environment.PATH; fs.writeFileSync(result, JSON.stringify({ schemaVersion: 1, path: [root] })); + applyVerificationSetupResult(result, environment, [root]); expect(environment.PATH).toBe(root); + }); + it.each([ + 'not json secret', 'x'.repeat(16_385), JSON.stringify({ schemaVersion: 1, environment: { PAT: 'secret' } }), + JSON.stringify({ schemaVersion: 1, path: ['relative'] }), + ])('rejects unsafe result content %#', content => { + fs.writeFileSync(result, content); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(expect.objectContaining({ reason: 'result-invalid' })); + expect(environment).toEqual({ HOME: root, PATH: 'baseline' }); + }); + it('rejects directory results', () => { + fs.mkdirSync(result); expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + }); + it('rejects hard-linked result files', () => { + const original = join(root, 'original'); fs.writeFileSync(original, '{"schemaVersion":1}'); fs.linkSync(original, result); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + }); + it.each(['outside', 'traversal', 'delimiter', 'missing', 'file', 'link'])('rejects %s PATH directories', kind => { + const outside = fs.realpathSync(fs.mkdtempSync(join(tmpdir(), 'copilot-setup-outside-'))); + const file = join(root, 'ordinary'); fs.writeFileSync(file, 'text'); + const link = join(root, 'link'); fs.symlinkSync(outside, link, process.platform === 'win32' ? 'junction' : 'dir'); + const values: Record = { outside, traversal: join(root, 'child') + '/../../escape', delimiter: root + delimiter + outside, missing: join(root, 'missing'), file, link }; + fs.writeFileSync(result, JSON.stringify({ schemaVersion: 1, path: [values[kind]] })); + try { expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); } + finally { fs.rmSync(outside, { recursive: true, force: true }); } + }); + it('does not apply partial PATH changes when JAVA_HOME is rejected', () => { + fs.writeFileSync(result, JSON.stringify({ schemaVersion: 1, path: [root], environment: { JAVA_HOME: '/outside/untrusted' } })); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + expect(environment.PATH).toBe('baseline'); + }); + it('rejects a result replaced between lstat and open', () => { + fs.writeFileSync(result, '{"schemaVersion":1}'); const original = fs.fstatSync; + jest.spyOn(fs, 'fstatSync').mockImplementation(((fd: number, options: {bigint?: boolean}) => { + const stat = original(fd, options as { bigint: true }); return { ...stat, ino: stat.ino + 1n }; + }) as typeof fs.fstatSync); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + }); + it('bounds a result that grows after its initial stat', () => { + fs.writeFileSync(result, '{"schemaVersion":1}'); + jest.spyOn(fs, 'readSync').mockReturnValueOnce(16_385); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + }); + it('rejects a result mutated during the bounded read', () => { + fs.writeFileSync(result, '{"schemaVersion":1}'); const original = fs.fstatSync; let calls = 0; + jest.spyOn(fs, 'fstatSync').mockImplementation(((fd: number, options: {bigint?: boolean}) => { + const stat = original(fd, options as { bigint: true }); calls++; + return calls === 2 ? { ...stat, mtimeNs: stat.mtimeNs + 1n } : stat; + }) as typeof fs.fstatSync); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(VerificationSetupError); + }); + it('maps access denial without exposing raw filesystem diagnostics', () => { + jest.spyOn(fs, 'lstatSync').mockImplementationOnce(() => { throw Object.assign(new Error('secret path'), { code: 'EACCES' }); }); + expect(() => applyVerificationSetupResult(result, environment, [root])).toThrow(expect.objectContaining({ reason: 'result-invalid', message: expect.not.stringContaining('secret') })); + }); +}); diff --git a/src/infrastructure/__tests__/verification_setup_snapshot.test.ts b/src/infrastructure/__tests__/verification_setup_snapshot.test.ts new file mode 100644 index 000000000..533ebd53f --- /dev/null +++ b/src/infrastructure/__tests__/verification_setup_snapshot.test.ts @@ -0,0 +1,125 @@ +import cp from 'node:child_process'; +import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { captureVerificationSetup } from '../verification_setup_snapshot'; +import { VERIFICATION_SETUP_PATH } from '../../application/policies/verification_setup_policy'; +import * as environment from '../untrusted_command_environment'; + +describe('trusted repository setup snapshot', () => { + let root: string; + const git = (...args: string[]) => cp.execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + const commit = (script: string | Buffer): string => { + mkdirSync(join(root, '.github/actions/copilot-verification-setup'), { recursive: true }); + writeFileSync(join(root, VERIFICATION_SETUP_PATH), script); + git('add', '.'); git('commit', '-m', 'fixture'); + const sha = git('rev-parse', 'HEAD'); + git('update-ref', 'refs/remotes/origin/main', sha); + return sha; + }; + beforeEach(() => { + root = realpathSync(mkdtempSync(join(tmpdir(), 'copilot-setup-snapshot-'))); + git('init'); git('config', 'user.name', 'Test'); git('config', 'user.email', 'test@example.test'); + }); + afterEach(() => { jest.restoreAllMocks(); rmSync(root, { recursive: true, force: true }); }); + + it('does no acquisition when disabled, even with absent workspace/ref', () => { + expect(captureVerificationSetup(false, 'invalid', '/missing')).toEqual({ kind: 'disabled' }); + }); + it('pins trusted bytes and SHA before candidate changes or remote-ref mutation', () => { + const sha = commit('runs:\n using: composite\n steps:\n - run: echo trusted\n shell: bash\n'); + const snapshot = captureVerificationSetup(true, 'main', root); + commit('throw new Error("candidate");'); + expect(snapshot).toMatchObject({ kind: 'ready', sourceSha: sha, files: [expect.objectContaining({ path: 'action.yml' })], workspace: root }); + expect(Object.isFrozen(snapshot)).toBe(true); + }); + it('defers acquisition failure on query/no-op routes', () => { + expect(captureVerificationSetup(true, 'main', '/absent/workspace')).toEqual({ kind: 'blocked', reason: 'source-unavailable' }); + }); + it('defers private acquisition cleanup failure instead of failing a read-only route', () => { + const cleanup = jest.fn(() => { throw new Error('private runtime failure'); }); + jest.spyOn(environment, 'prepareUntrustedCommandEnvironment').mockReturnValue({ environment: {}, cleanup }); + expect(captureVerificationSetup(true, '--invalid', root)).toEqual({ kind: 'blocked', reason: 'source-unavailable' }); + expect(cleanup).toHaveBeenCalledTimes(1); + }); + it('defers missing trusted refs without consulting the candidate helper', () => { + expect(captureVerificationSetup(true, 'main', root)).toEqual({ kind: 'blocked', reason: 'source-unavailable' }); + }); + it('captures the canonical trusted tool root independently of later environment changes', () => { + commit('runs:\n using: composite\n steps:\n - run: echo ready\n shell: bash\n'); const previous = process.env.RUNNER_TOOL_CACHE; + try { + process.env.RUNNER_TOOL_CACHE = root; + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ kind: 'ready', toolRoot: root }); + } finally { if (previous === undefined) delete process.env.RUNNER_TOOL_CACHE; else process.env.RUNNER_TOOL_CACHE = previous; } + }); + it('verifies SHA-256 Git object stores without truncating their identity', () => { + rmSync(join(root, '.git'), { recursive: true }); git('init', '--object-format=sha256'); + git('config', 'user.name', 'Test'); git('config', 'user.email', 'test@example.test'); + const sha = commit('runs:\n using: composite\n steps:\n - run: echo ready\n shell: bash\n'); expect(sha).toHaveLength(64); + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ kind: 'ready', sourceSha: sha }); + }); + it('rejects enabled missing helpers at the captured trusted SHA', () => { + git('commit', '--allow-empty', '-m', 'empty'); const sha = git('rev-parse', 'HEAD'); + git('update-ref', 'refs/remotes/origin/main', sha); + expect(captureVerificationSetup(true, 'main', root)).toEqual({ kind: 'blocked', reason: 'helper-invalid', sourceSha: sha }); + }); + it.each(['--bad', 'main..branch', 'a//b', 'a/', 'a.lock', 'a\nbranch'])('rejects unsafe trusted source %j', branch => { + expect(captureVerificationSetup(true, branch, root)).toMatchObject({ kind: 'blocked', reason: 'source-unavailable' }); + }); + it.each([Buffer.from(''), Buffer.from([255, 254]), Buffer.from('a\0b'), Buffer.alloc(65_537, 97)])('rejects unsafe helper bytes %#', bytes => { + commit(bytes); + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ kind: 'blocked', reason: 'helper-invalid' }); + }); + it('rejects Git symlink mode without following the candidate filesystem', () => { + commit('target'); const blob = git('rev-parse', `HEAD:${VERIFICATION_SETUP_PATH}`); + git('update-index', '--cacheinfo', `120000,${blob},${VERIFICATION_SETUP_PATH}`); git('commit', '-m', 'link'); + git('update-ref', 'refs/remotes/origin/main', git('rev-parse', 'HEAD')); + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ reason: 'helper-invalid' }); + }); + it('never forwards runner credentials or inherited auth profiles to Git reads', () => { + commit('runs:\n using: composite\n steps:\n - run: echo ready\n shell: bash\n'); + const original = cp.spawnSync; + const reads = jest.spyOn(cp, 'spawnSync').mockImplementation(((program: string, args: string[], options: cp.SpawnSyncOptions) => { + expect(options.env).not.toHaveProperty('PAT'); + expect(options.env).not.toHaveProperty('OPENAI_API_KEY'); + expect(options.env?.HOME).not.toBe(process.env.HOME); + expect(options.shell).toBe(false); + return original(program, args, options); + }) as typeof cp.spawnSync); + expect(captureVerificationSetup(true, 'main', root).kind).toBe('ready'); + expect(reads).toHaveBeenCalledTimes(3); + }); + it('detects corrupted blob responses instead of executing mismatched bytes', () => { + commit('runs:\n using: composite\n steps:\n - run: echo ready\n shell: bash\n'); const original = cp.spawnSync; + jest.spyOn(cp, 'spawnSync').mockImplementation(((program: string, args: string[], options: cp.SpawnSyncOptions) => { + const value = original(program, args, options); + return args.includes('cat-file') ? { ...value, stdout: Buffer.from('different') } : value; + }) as typeof cp.spawnSync); + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ reason: 'helper-invalid' }); + }); +}); + +// These metadata failures are independent native contract cases, not shell execution tests. +describe('native composite metadata validation', () => { + let root: string; + const git = (...args: string[]) => cp.execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + beforeEach(() => { + root = realpathSync(mkdtempSync(join(tmpdir(), 'copilot-native-metadata-'))); git('init'); git('config', 'user.name', 'Test'); git('config', 'user.email', 'test@example.test'); + mkdirSync(join(root, '.github/actions/copilot-verification-setup'), { recursive: true }); + }); + afterEach(() => rmSync(root, { recursive: true, force: true })); + it.each([ + 'runs: {using: node24, main: index.js}', 'runs: {using: composite, steps: []}', 'runs: [', + 'runs: {using: composite, steps: [null]}', 'runs: {using: composite, steps: [{uses: "./candidate-action"}]}', + 'runs:\n using: composite\n steps:\n' + Array(33).fill(' - run: echo x\n shell: bash\n').join(''), + ])('rejects invalid native metadata %#', metadata => { + writeFileSync(join(root, VERIFICATION_SETUP_PATH), metadata); git('add', '.'); git('commit', '-m', 'fixture'); git('update-ref', 'refs/remotes/origin/main', git('rev-parse', 'HEAD')); + expect(captureVerificationSetup(true, 'main', root)).toMatchObject({ kind: 'blocked', reason: 'helper-invalid' }); + }); + it('captures reviewed auxiliary script bytes independently of candidate edits', () => { + writeFileSync(join(root, VERIFICATION_SETUP_PATH), 'runs: {using: composite, steps: [{run: "echo ready", shell: bash}]}'); + writeFileSync(join(root, '.github/actions/copilot-verification-setup/prepare.sh'), 'reviewed'); git('add', '.'); git('commit', '-m', 'fixture'); git('update-ref', 'refs/remotes/origin/main', git('rev-parse', 'HEAD')); + const snapshot = captureVerificationSetup(true, 'main', root); writeFileSync(join(root, '.github/actions/copilot-verification-setup/prepare.sh'), 'candidate'); + expect(snapshot).toMatchObject({kind: 'ready', files: expect.arrayContaining([expect.objectContaining({path: 'prepare.sh', base64: Buffer.from('reviewed').toString('base64')})])}); + }); +}); diff --git a/src/infrastructure/__tests__/verification_setup_sync.test.ts b/src/infrastructure/__tests__/verification_setup_sync.test.ts new file mode 100644 index 000000000..31e3047e0 --- /dev/null +++ b/src/infrastructure/__tests__/verification_setup_sync.test.ts @@ -0,0 +1,126 @@ +import { removeNativeVerificationProfiles } from '../native_verification_profiles'; +import fs from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { captureVerificationSetup } from '../verification_setup_snapshot'; +import { GitCommitAdapter } from '../git_commit_adapter'; +import { BranchSyncWorkspaceAdapter } from '../branch_sync_workspace_adapter'; +import { SyncBranchUseCase } from '../../application/usecases/branch_sync/sync_branch_use_case'; +import { projectBranchSyncContext } from '../../application/usecases/push_single_action_contexts'; +import { prepareUntrustedCommandEnvironment } from '../untrusted_command_environment'; +import { readNativeRecord, writeNativeRecord, type NativeVerificationRequest } from '../native_verification_protocol'; +import { VERIFICATION_SETUP_PATH } from '../../application/policies/verification_setup_policy'; +import type { Execution } from '../../data/model/execution'; + +const branch = 'feature/42-managed-fixture'; +const helper = 'runs:\n using: composite\n steps:\n - run: echo reviewed\n shell: bash\n'; +const verifier = `import fs from 'node:fs'; import path from 'node:path'; import assert from 'node:assert/strict'; + assert.equal(fs.readFileSync(path.join(process.env.HOME,'tools','prepared'),'utf8'),fs.readFileSync('dependencies.lock','utf8'));`; + +/** Real local Git remote, exact same managed ref, actual merge/verify/abort/push; no GitHub or network. */ +describe('repository setup during same-managed-branch synchronization', () => { + let directory: string; let root: string; let remote: string; let childSha: string; + const git = (...args: string[]) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + const remoteHead = () => git('--git-dir', remote, 'rev-parse', `refs/heads/${branch}`); + beforeEach(() => { + directory = fs.realpathSync(fs.mkdtempSync(path.join(tmpdir(), 'copilot-setup-sync-'))); + root = path.join(directory, 'workspace'); remote = path.join(directory, 'origin.git'); fs.mkdirSync(root); + git('init', '--bare', remote); git('init', '-b', 'main'); + const emptyHooks = path.join(directory, 'empty-hooks'); fs.mkdirSync(emptyHooks); + git('config', 'core.hooksPath', emptyHooks); git('--git-dir', remote, 'config', 'core.hooksPath', emptyHooks); + git('config', 'user.name', 'Test'); git('config', 'user.email', 'test@example.test'); + fs.mkdirSync(path.dirname(path.join(root, VERIFICATION_SETUP_PATH)), { recursive: true }); + fs.writeFileSync(path.join(root, VERIFICATION_SETUP_PATH), helper); + fs.writeFileSync(path.join(root, 'verify.mjs'), verifier); + fs.writeFileSync(path.join(root, 'dependencies.lock'), 'version-one'); + git('add', '.'); git('commit', '-m', 'trusted main'); git('remote', 'add', 'origin', remote); git('push', 'origin', 'main'); + git('checkout', '-b', 'develop'); git('push', 'origin', 'develop'); git('checkout', '-b', branch); + fs.writeFileSync(path.join(root, VERIFICATION_SETUP_PATH), 'throw new Error("candidate helper must never execute");'); + git('add', '.'); git('commit', '-m', 'candidate helper change'); git('push', 'origin', branch); childSha = git('rev-parse', 'HEAD'); + git('checkout', 'develop'); fs.writeFileSync(path.join(root, 'dependencies.lock'), 'version-two'); + git('add', '.'); git('commit', '-m', 'parent dependency update'); git('push', 'origin', 'develop'); git('checkout', branch); + jest.spyOn(process, 'cwd').mockReturnValue(root); + }); + afterEach(() => { jest.restoreAllMocks(); fs.rmSync(directory, { recursive: true, force: true }); }); + async function synchronize(enabled: boolean, commands = ['node verify.mjs'], dryRun = false) { + const gitPort = new GitCommitAdapter(async (program, args, options) => { + const output = execFileSync(program, args, { cwd: root, env: options?.env, stdio: ['ignore', 'pipe', 'pipe'] }); + options?.stdout?.(output); return 0; + }, undefined, captureVerificationSetup(enabled, 'main', root)); + const workspace = new BranchSyncWorkspaceAdapter(gitPort, root); + const fixer = { fix: jest.fn() }; + const useCase = new SyncBranchUseCase({ + resolveTarget: async () => ({ issueNumber: 42, conversationNumber: 42, parentBranch: 'develop', workingBranch: branch }), + listOpenDependencies: async () => [], + }, { + prepare: (parent, child) => workspace.prepare(parent, child, ''), + validatePreparedMerge: paths => workspace.validatePreparedMerge(paths), + assertRemoteHeadsUnchanged: (parent, parentSha, child, sha) => workspace.assertRemoteHeadsUnchanged(parent, parentSha, child, sha, ''), + commitAndPush: (child, message, author) => workspace.commitAndPush(child, message, author, ''), + abort: () => workspace.abort(), + }, fixer, { getUserDetails: async () => ({ name: 'Test', email: 'test@example.test' }) } as never, gitPort); + const context = projectBranchSyncContext({ owner: 'fixture', repo: 'local', tokens: { token: '' }, issue: { number: 42 }, + pullRequest: { number: -1 }, issueNumber: 42, + ai: { getAgentConfiguration: () => ({ provider: 'codex', model: '' }), getBugbotFixVerifyCommands: () => commands }, + } as unknown as Execution); + const runtime = prepareUntrustedCommandEnvironment(); + const session = runtime.environment.HOME; + const previous = process.env.COPILOT_NATIVE_WORKER_SESSION; + process.env.COPILOT_NATIVE_WORKER_SESSION = session; + writeNativeRecord(session, 'session.json', { schemaVersion: 1, workspace: root, run: process.env.GITHUB_RUN_ID ?? '', + attempt: process.env.GITHUB_RUN_ATTEMPT ?? '', job: process.env.GITHUB_JOB ?? '', pid: process.pid }); + const answered = new Set(); + // Test double for native Actions; it reads merged manifests, never parses or executes YAML. + const driver = setInterval(() => { + const request = readNativeRecord(session, 'request.json', 32_768); + if (!request || answered.has(request.id)) return; + answered.add(request.id); + const tools = path.join(request.environment.HOME, 'tools'); fs.mkdirSync(tools); + fs.writeFileSync(path.join(tools, 'prepared'), fs.readFileSync(path.join(root, 'dependencies.lock'))); + writeNativeRecord(session, `response-${request.id}.json`, { id: request.id, success: true, path: request.environment.PATH }); + }, 5); + let result; + try { [result] = await useCase.invoke({ context, options: { dryRun, useAgent: false } }); } + finally { + clearInterval(driver); removeNativeVerificationProfiles(session); runtime.cleanup(); + if (previous === undefined) delete process.env.COPILOT_NATIVE_WORKER_SESSION; else process.env.COPILOT_NATIVE_WORKER_SESSION = previous; + } + expect(fixer.fix).not.toHaveBeenCalled(); + return result; + } + it('fails without preparation, aborting the merge and preserving the exact remote head', async () => { + const result = await synchronize(false); + expect(result.success).toBe(false); expect(remoteHead()).toBe(childSha); expect(git('rev-parse', 'HEAD')).toBe(childSha); + expect(git('status', '--porcelain')).toBe(''); + }); + it('prepares merged manifests from trusted main, verifies twice, and pushes only the same managed ref', async () => { + const result = await synchronize(true, ['node verify.mjs', 'node verify.mjs']); + expect(result).toMatchObject({ success: true, payload: { outcome: 'merged-cleanly', verificationCount: 2 } }); + expect(remoteHead()).toBe(git('rev-parse', 'HEAD')); expect(remoteHead()).not.toBe(childSha); + expect(git('show', `${remoteHead()}:dependencies.lock`)).toBe('version-two'); + expect(git('show', `${remoteHead()}:${VERIFICATION_SETUP_PATH}`)).toContain('candidate helper must never execute'); + expect(git('--git-dir', remote, 'for-each-ref', '--format=%(refname)', 'refs/heads').split('\n').sort()) + .toEqual(['refs/heads/develop', `refs/heads/${branch}`, 'refs/heads/main']); + }, process.platform === 'win32' ? 30_000 : 5_000); + it('skips setup in dry runs even when the trusted helper is invalid', async () => { + git('update-ref', 'refs/remotes/origin/main', childSha); + const result = await synchronize(true, ['node verify.mjs'], true); + expect(result).toMatchObject({ success: true, payload: { outcome: 'dry-run-clean' } }); expect(remoteHead()).toBe(childSha); + }); + it('does not run setup for aligned branches', async () => { + git('merge', '--no-edit', 'develop'); git('push', 'origin', branch); childSha = remoteHead(); + git('update-ref', 'refs/remotes/origin/main', childSha); + expect(await synchronize(true)).toMatchObject({ success: true, payload: { outcome: 'already-aligned' } }); + expect(remoteHead()).toBe(childSha); + }); + it('retains existing no-command policy without invoking an invalid helper', async () => { + git('update-ref', 'refs/remotes/origin/main', childSha); + expect(await synchronize(true, [])).toMatchObject({ success: true, payload: { verificationCount: 0 } }); + }); + it('aborts and withholds push when trusted setup fails', async () => { + git('update-ref', 'refs/remotes/origin/main', childSha); + const result = await synchronize(true); + expect(result.success).toBe(false); expect(remoteHead()).toBe(childSha); expect(git('status', '--porcelain')).toBe(''); + }); +}); diff --git a/src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts b/src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts index 7c5073484..3c91cb1eb 100644 --- a/src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts +++ b/src/infrastructure/composition/__tests__/main_run_route_composition_root.test.ts @@ -9,6 +9,7 @@ import { ThinkUseCase } from "../../../application/usecases/steps/common/think_u import { RememberBugbotRuleUseCase } from "../../../application/usecases/steps/commit/bugbot/remember_bugbot_rule_use_case"; import { CheckIssueCommentLanguageUseCase } from "../../../application/usecases/steps/issue_comment/check_issue_comment_language_use_case"; import { GitCommitAdapter } from "../../git_commit_adapter"; +import { captureVerificationSetup } from '../../verification_setup_snapshot'; import type { ProjectBoardCommandPort } from "../../../application/ports/project_board_command_ports"; import type { Execution } from "../../../data/model/execution"; import { @@ -84,10 +85,23 @@ jest.mock( "../../../application/usecases/steps/issue_comment/check_issue_comment_language_use_case", ); jest.mock("../../git_commit_adapter"); +jest.mock('../../verification_setup_snapshot', () => ({ captureVerificationSetup: jest.fn(() => ({ kind: 'disabled' })) })); describe("main run route composition root", () => { beforeEach(() => jest.clearAllMocks()); + it.each(['issue-comment', 'pull-request-review-comment'] as const)('captures trusted setup before %s can invoke an agent', async route => { + const snapshot = { kind: 'blocked', reason: 'source-unavailable' } as const; + jest.mocked(captureVerificationSetup).mockReturnValueOnce(snapshot); + const execution = { owner: 'owner', repo: 'repo', tokens: { token: 'token' }, branches: { main: 'production' }, verificationSetup: true } as Execution; + const handlers = createMainRunRouteCompositionRoot({} as ProjectBoardCommandPort, 'local'); + await handlers[route](execution); + expect(captureVerificationSetup).toHaveBeenCalledWith(true, 'production'); + expect(GitCommitAdapter).toHaveBeenCalledWith(undefined, undefined, snapshot); + const useCase = route === 'issue-comment' ? jest.mocked(IssueCommentUseCase).mock.instances[0] : jest.mocked(PullRequestReviewCommentUseCase).mock.instances[0]; + expect(jest.mocked(captureVerificationSetup).mock.invocationCallOrder[0]).toBeLessThan(jest.mocked(useCase.invoke).mock.invocationCallOrder[0]); + }); + it("shares route-scoped Bugbot, agent, and Git commit capabilities", () => { const useCase = createIssueCommentUseCaseCompositionRoot(binding); @@ -199,6 +213,8 @@ describe("main run route composition root", () => { owner: 'owner', repo: 'repo', tokens: { token: 'token' }, + branches: { main: 'master' }, + verificationSetup: false, } as Execution; const handlers = createMainRunRouteCompositionRoot( {} as ProjectBoardCommandPort, diff --git a/src/infrastructure/composition/main_run_route_composition_root.ts b/src/infrastructure/composition/main_run_route_composition_root.ts index f026e87cb..8528c1857 100644 --- a/src/infrastructure/composition/main_run_route_composition_root.ts +++ b/src/infrastructure/composition/main_run_route_composition_root.ts @@ -201,13 +201,15 @@ function createDeploymentOrchestrationUseCase( } import { createCopilotHistoryReadPort } from './copilot_history_port_factory'; +import { captureVerificationSetup } from '../verification_setup_snapshot'; +import { DISABLED_VERIFICATION_SETUP, type PreparedVerificationSetup } from '../../application/ports/verification_setup_ports'; -export function createIssueCommentUseCaseCompositionRoot(binding: BugbotScmBinding): IssueCommentUseCase { +export function createIssueCommentUseCaseCompositionRoot(binding: BugbotScmBinding, setup: PreparedVerificationSetup = DISABLED_VERIFICATION_SETUP): IssueCommentUseCase { const bugbot = createBugbotCompositionRoot(binding); const findings = createFindingsQueryPort(); const language = createLanguageQueryPort(); const fixer = createFixerQueryPort(); - const gitCommit = new GitCommitAdapter(); + const gitCommit = new GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = createAuthenticatedUserCompositionRoot(); const bugbotGit = new BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new UpdatePullRequestDescriptionUseCase( @@ -265,12 +267,12 @@ export function createIssueCommentUseCaseCompositionRoot(binding: BugbotScmBindi ); } -export function createPullRequestReviewCommentUseCaseCompositionRoot(binding: BugbotScmBinding): PullRequestReviewCommentUseCase { +export function createPullRequestReviewCommentUseCaseCompositionRoot(binding: BugbotScmBinding, setup: PreparedVerificationSetup = DISABLED_VERIFICATION_SETUP): PullRequestReviewCommentUseCase { const bugbot = createBugbotCompositionRoot(binding); const findings = createFindingsQueryPort(); const language = createLanguageQueryPort(); const fixer = createFixerQueryPort(); - const gitCommit = new GitCommitAdapter(); + const gitCommit = new GitCommitAdapter(undefined, undefined, setup); const authenticatedUser = createAuthenticatedUserCompositionRoot(); const bugbotGit = new BoundBugbotGitMutationAdapter(gitCommit, authenticatedUser, binding.token); const pullRequestDescription = new UpdatePullRequestDescriptionUseCase( @@ -362,7 +364,7 @@ export function createMainRunRouteCompositionRoot( return singleAction.invoke(execution); }, "issue-comment": async (execution) => { - issueComment ??= createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution)); + issueComment ??= createIssueCommentUseCaseCompositionRoot(bugbotBinding(execution), captureVerificationSetup(execution.verificationSetup, execution.branches.main)); return issueComment.invoke(execution); }, issue: async (execution) => { @@ -370,7 +372,7 @@ export function createMainRunRouteCompositionRoot( return issue.invoke(execution); }, "pull-request-review-comment": async (execution) => { - pullRequestReviewComment ??= createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution)); + pullRequestReviewComment ??= createPullRequestReviewCommentUseCaseCompositionRoot(bugbotBinding(execution), captureVerificationSetup(execution.verificationSetup, execution.branches.main)); return pullRequestReviewComment.invoke(execution); }, "pull-request": async (execution) => { diff --git a/src/infrastructure/git_commit_adapter.ts b/src/infrastructure/git_commit_adapter.ts index dd2c57dc4..9149aa1e8 100644 --- a/src/infrastructure/git_commit_adapter.ts +++ b/src/infrastructure/git_commit_adapter.ts @@ -1,17 +1,21 @@ +import { verificationExecutable } from './verification_executable'; +import { nativeVerificationRetainsProfile } from './native_verification_profiles'; import * as exec from '@actions/exec'; import type { GitCommitPort } from '../application/ports/git_ports'; import { buildGitAuthenticationEnvironment } from './git_authentication_environment'; import { prepareUntrustedCommandEnvironment } from './untrusted_command_environment'; import { withoutGitHooks } from './git_hook_isolation'; import { assertVerificationRuntimePrerequisites } from './verification_runtime_preflight'; +import { DISABLED_VERIFICATION_SETUP, type PreparedVerificationSetup } from '../application/ports/verification_setup_ports'; +import { prepareRepositoryVerification } from './verification_setup_runner'; -type GitCommandOptions = { stdout?: (data: Buffer) => void; env?: Record; untrusted?: boolean; verifyRuntimePrerequisites?: boolean }; +type GitCommandOptions = { stdout?: (data: Buffer) => void; env?: Record; untrusted?: boolean; verifyRuntimePrerequisites?: boolean; resolveFromEnvironment?: boolean }; type GitCommandExecutor = (program: string, args: string[], options?: GitCommandOptions) => Promise; export class GitCommitAdapter implements GitCommitPort { constructor( private readonly executeCommand: GitCommandExecutor = (program, args, options) => options - ? exec.exec(program, args, { + ? exec.exec(options.resolveFromEnvironment && options.env ? verificationExecutable(program, options.env) : program, args, { // Captured Git reads can contain NUL-delimited records that // hide subsequent lines in GitHub's Actions log viewer. ...(options.stdout ? { silent: true } : {}), @@ -20,6 +24,7 @@ export class GitCommitAdapter implements GitCommitPort { }) : exec.exec(program, args), private readonly checkPrerequisites: (environment: Record) => void = assertVerificationRuntimePrerequisites, + private readonly verificationSetup: PreparedVerificationSetup = DISABLED_VERIFICATION_SETUP, ) {} async execute(program: string, args: string[], options?: GitCommandOptions): Promise { @@ -28,13 +33,17 @@ export class GitCommitAdapter implements GitCommitPort { if (options.env) throw new Error('Untrusted command execution does not accept a caller-supplied environment.'); const runtime = prepareUntrustedCommandEnvironment(); try { - if (options.verifyRuntimePrerequisites) this.checkPrerequisites(runtime.environment); + if (options.verifyRuntimePrerequisites) { + await prepareRepositoryVerification(this.verificationSetup, runtime.environment); + this.checkPrerequisites(runtime.environment); + } return await this.executeCommand(program, args, { ...(options.stdout ? { stdout: options.stdout } : {}), env: runtime.environment, + ...(options.verifyRuntimePrerequisites && this.verificationSetup.kind === 'ready' ? { resolveFromEnvironment: true } : {}), }); } finally { - runtime.cleanup(); + if (!nativeVerificationRetainsProfile(runtime.environment.HOME)) runtime.cleanup(); } } diff --git a/src/infrastructure/native_verification_candidate.ts b/src/infrastructure/native_verification_candidate.ts new file mode 100644 index 000000000..f65d61132 --- /dev/null +++ b/src/infrastructure/native_verification_candidate.ts @@ -0,0 +1,36 @@ +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstatSync, readFileSync, readlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import { VerificationSetupError } from '../application/policies/verification_setup_policy'; + +/** Bind setup to the exact HEAD, branch, index, tracked edits and publishable untracked files. */ +export function verificationCandidateIdentity(workspace: string, environment: Record): string { + const hash = createHash('sha256'); + const git = (...args: string[]): Buffer => { + const result = spawnSync('git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, shell: false, + windowsHide: true, timeout: 10_000, maxBuffer: 16 * 1024 * 1024 }); + if (result.error || result.status !== 0) throw new VerificationSetupError('candidate-changed'); + hash.update(result.stdout).update('\0'); + return result.stdout; + }; + git('rev-parse', '--verify', 'HEAD'); + git('rev-parse', '--symbolic-full-name', 'HEAD'); + git('ls-files', '--stage', '-z'); + git('diff', '--no-ext-diff', '--no-textconv', '--binary', 'HEAD', '--'); + const paths = git('ls-files', '--others', '--exclude-standard', '-z').toString('utf8').split('\0').filter(Boolean); + let total = 0; + if (paths.length > 256) throw new VerificationSetupError('candidate-changed'); + for (const path of paths) { + const file = join(workspace, path); + const stat = lstatSync(file); + total += stat.size; + if (total > 16 * 1024 * 1024) throw new VerificationSetupError('candidate-changed'); + hash.update(path).update('\0'); + if (stat.isSymbolicLink()) hash.update('120000\0').update(readlinkSync(file)); + else if (stat.isFile()) hash.update(stat.mode & 0o100 ? '100755\0' : '100644\0').update(readFileSync(file)); + else throw new VerificationSetupError('candidate-changed'); + hash.update('\0'); + } + return hash.digest('hex'); +} diff --git a/src/infrastructure/native_verification_controller.ts b/src/infrastructure/native_verification_controller.ts new file mode 100644 index 000000000..9f712516c --- /dev/null +++ b/src/infrastructure/native_verification_controller.ts @@ -0,0 +1,216 @@ +import { cleanupNativeSourceRuntime } from './native_verification_source_runtime'; +import { verificationExecutable } from './verification_executable'; +import { nativeWorkerSecrets, redactNativeWorkerLogs } from './native_verification_redaction'; +import { removeNativeVerificationProfiles } from './native_verification_profiles'; +import * as core from '@actions/core'; +import { spawn, spawnSync } from 'node:child_process'; +import { appendFileSync, closeSync, existsSync, openSync, readSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { createPrivateVerificationRuntimeRoot } from './agents/agent_private_install_root'; +import { trustedWindowsSystemTool } from './agents/agent_trusted_system_tools'; +import { getGithubActionInput } from './github_action_input'; +import { VerificationSetupError, VERIFICATION_SETUP_LIMITS } from '../application/policies/verification_setup_policy'; +import { nativePause, nativeSession, readNativeRecord, writeNativeRecord, type NativeVerificationRequest, type NativeVerificationResponse } from './native_verification_protocol'; +import { cleanupNativeVerificationAction, stageNativeVerificationAction } from './native_verification_staging'; +import { captureVerificationSetupAtCommit, verificationSetupDigest } from './verification_setup_snapshot'; + +function emitWorkerOutput(root: string): void { + const file = join(root, 'worker-output'); + if (process.env.GITHUB_OUTPUT && existsSync(file)) appendFileSync(process.env.GITHUB_OUTPUT, requireRead(file)); + const summary = join(root, 'worker-summary'); + if (process.env.GITHUB_STEP_SUMMARY && existsSync(summary) && !readNativeRecord(root, 'summary-emitted.json')) { + appendFileSync(process.env.GITHUB_STEP_SUMMARY, requireRead(summary, 1_048_576)); + writeNativeRecord(root, 'summary-emitted.json', { emitted: true }); + } +} + +function requireRead(file: string, limit = 262_144): Buffer { + const fd = openSync(file, 'r'); + try { const size = statSync(file).size; if (size > limit) throw new VerificationSetupError('output-limit'); const data = Buffer.alloc(size); readSync(fd, data, 0, data.length, 0); return data; } + finally { closeSync(fd); } +} + +/** Drain complete lines so runner masking and workflow commands keep their native framing. */ +export function drainNativeWorkerLog(root: string, flush = false): void { + const file = join(root, 'worker-log'); + if (!existsSync(file)) return; + const cursor = readNativeRecord<{offset: number}>(root, 'log-cursor.json')?.offset ?? 0; + const fd = openSync(file, 'r'); + let offset = cursor; + try { + const buffer = Buffer.alloc(65_536); + let pending = Buffer.alloc(0); + let size: number; + while ((size = readSync(fd, buffer, 0, buffer.length, offset + pending.length))) { + pending = Buffer.concat([pending, buffer.subarray(0, size)]); + const lastLine = pending.lastIndexOf(10); + if (lastLine >= 0) { + const complete = pending.subarray(0, lastLine + 1); + process.stdout.write(complete); + offset += complete.length; + pending = pending.subarray(lastLine + 1); + } + // Existing action logs may contain long lines; retain framing without unbounded buffering. + if (pending.length > 1_048_576) throw new VerificationSetupError('output-limit'); + } + if (flush && pending.length) { process.stdout.write(pending); offset += pending.length; } + writeNativeRecord(root, 'log-cursor.json', { offset }); + } finally { closeSync(fd); } +} + +export async function awaitNativeBoundary(root: string, timeoutMs = 7_200_000): Promise { + const deadline = Date.now() + timeoutMs; + while (true) { + drainNativeWorkerLog(root); + const done = readNativeRecord<{exitCode: number}>(root, 'done.json'); + if (done) { + drainNativeWorkerLog(root, true); + emitWorkerOutput(root); + core.setOutput('verification-setup-pending', 'false'); + if (done.exitCode !== 0) core.setFailed('Copilot execution failed; inspect the preceding operation result.'); + return; + } + const request = readNativeRecord(root, 'request.json', 32_768); + if (request) { + if (!/^[a-f0-9-]{36}$/u.test(request.id) || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(request.sourceSha) + || !/^[a-f0-9]{64}$/u.test(request.digest) || !request.environment || typeof request.environment.HOME !== 'string') throw new VerificationSetupError('handoff-invalid'); + core.setOutput('verification-request', request.id); + core.setOutput('verification-source', request.sourceSha); + core.setOutput('verification-digest', request.digest); + core.saveState('native-verification-digest', request.digest); + core.saveState('native-verification-source', request.sourceSha); + for (const key of ['HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PATH', 'JAVA_HOME', 'TMP', 'TEMP', 'TMPDIR']) { + const value = request.environment[key] ?? ''; + if (typeof value !== 'string' || /[\r\n\0]/u.test(value)) throw new VerificationSetupError('handoff-invalid'); + core.setOutput(`verification-${key.toLowerCase()}`, value); + } + core.setOutput('verification-setup-pending', 'true'); + return; + } + if (Date.now() >= deadline) throw new VerificationSetupError('timeout'); + const session = nativeSession(root); + try { process.kill(session.pid, 0); } catch { throw new VerificationSetupError('process-failed'); } + await nativePause(100); + } +} + +export async function continueNativeVerification(root: string, id: string, outcome: string, environment = process.env): Promise { + nativeSession(root); + const request = readNativeRecord(root, 'request.json', 32_768); + if (!request || request.id !== id || !/^[a-f0-9-]{36}$/u.test(id) + || existsSync(join(root, `response-${id}.json`))) throw new VerificationSetupError('handoff-invalid'); + const response: NativeVerificationResponse = { id, success: outcome === 'success', path: environment.PATH ?? '', + ...(environment.JAVA_HOME ? { javaHome: environment.JAVA_HOME } : {}) }; + rmSync(join(root, 'request.json')); + writeNativeRecord(root, `response-${id}.json`, response); + await awaitNativeBoundary(root); +} + +export async function cleanupNativeVerification(root: string, waitMs = 10_000, deferProfileCleanup = false): Promise { + if (!existsSync(root)) return; + const session = nativeSession(root); + if (!readNativeRecord(root, 'done.json')) { + writeNativeRecord(root, 'cancel.json', { cancelled: true }); + const deadline = Date.now() + waitMs; + while (!readNativeRecord(root, 'done.json') && Date.now() < deadline) await nativePause(100); + if (!readNativeRecord(root, 'done.json')) { + try { + if (process.platform === 'win32') spawnSync(trustedWindowsSystemTool('taskkill.exe'), ['/PID', String(session.pid), '/T', '/F'], { windowsHide: true, shell: false, timeout: 5_000, stdio: 'ignore' }); + else process.kill(-session.pid, 'SIGKILL'); + } catch { /* Worker may have exited while cancellation completed. */ } + } + } + if (!deferProfileCleanup) cleanupNativeVerificationAction(root, session.workspace); + drainNativeWorkerLog(root, true); + if (!deferProfileCleanup) { removeNativeVerificationProfiles(root); rmSync(root, { recursive: true, force: true }); } +} + +async function startNativeVerification(): Promise { + for (const secret of nativeWorkerSecrets(process.env)) core.setSecret(secret); + const git = verificationExecutable('git', { PATH: process.env.PATH ?? '', PATHEXT: process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM' }); + core.saveState('native-verification-git', git); + core.setOutput('verification-git', git); + const root = realpathSync(createPrivateVerificationRuntimeRoot()); + for (const name of ['worker-output', 'worker-env', 'worker-path', 'worker-log', 'worker-summary']) writeFileSync(join(root, name), '', { flag: 'wx', mode: 0o600 }); + const log = openSync(join(root, 'worker-log'), 'a'); + try { + const worker = spawn(process.execPath, [process.argv[1]], { cwd: process.cwd(), detached: true, windowsHide: true, + stdio: ['ignore', log, log], env: { ...process.env, COPILOT_NATIVE_PHASE: '', COPILOT_NATIVE_WORKER_SESSION: root, + GITHUB_STEP_SUMMARY: join(root, 'worker-summary'), GITHUB_OUTPUT: join(root, 'worker-output'), GITHUB_ENV: join(root, 'worker-env'), GITHUB_PATH: join(root, 'worker-path') } }); + await new Promise((resolve, reject) => { worker.once('spawn', resolve); worker.once('error', reject); }); + worker.unref(); + writeNativeRecord(root, 'session.json', { schemaVersion: 1, workspace: realpathSync(process.cwd()), run: process.env.GITHUB_RUN_ID ?? '', + attempt: process.env.GITHUB_RUN_ATTEMPT ?? '', job: process.env.GITHUB_JOB ?? '', pid: worker.pid }); + core.saveState('native-verification-ticket', root); + core.saveState('native-verification-owner', 'true'); + core.setOutput('verification-ticket', root); + await awaitNativeBoundary(root); + } catch (error) { + if (existsSync(join(root, 'session.json'))) await cleanupNativeVerification(root); + else rmSync(root, { recursive: true, force: true }); + throw error; + } finally { closeSync(log); } +} + +/** Native phases are runner-only control messages; no credentials or agent operations are replayed. */ +export async function runNativeVerificationEntry(run: () => Promise): Promise { + const post = core.getState('copilot-native-post'); + if (post === 'true') { + const root = core.getState('native-verification-ticket'); + if (root && core.getState('native-verification-owner') === 'true') await cleanupNativeVerification(root); + else if (root && existsSync(root) && core.getState('native-verification-source')) { + const session = nativeSession(root); + try { + const snapshot = captureVerificationSetupAtCommit(core.getState('native-verification-source'), session.workspace, core.getState('native-verification-git')); + if (snapshot.kind !== 'ready' || verificationSetupDigest(snapshot) !== core.getState('native-verification-digest')) throw new VerificationSetupError('helper-invalid'); + stageNativeVerificationAction(snapshot, root); + } catch { + // A later native post must fail to load rather than execute candidate-modified metadata. + cleanupNativeVerificationAction(root, session.workspace); + throw new VerificationSetupError('helper-invalid'); + } + } + const sourceRuntime = core.getState('native-source-runtime'); + if (sourceRuntime) cleanupNativeSourceRuntime(sourceRuntime, realpathSync(process.cwd())); + return; + } + core.saveState('copilot-native-post', 'true'); + if (process.env.COPILOT_NATIVE_PHASE === 'prepare' && process.env.COPILOT_NATIVE_RUNTIME_CACHE) core.saveState('native-source-runtime', process.env.COPILOT_NATIVE_RUNTIME_CACHE); + const worker = process.env.COPILOT_NATIVE_WORKER_SESSION; + if (worker) { + const deadline = Date.now() + 30_000; + while (!readNativeRecord(worker, 'session.json')) { + if (Date.now() >= deadline) throw new VerificationSetupError('timeout'); + await nativePause(10); + } + if (nativeSession(worker).pid !== process.pid) throw new VerificationSetupError('handoff-invalid'); + const restoreLog = redactNativeWorkerLogs(); + try { await run(); } + finally { restoreLog(); writeNativeRecord(worker, 'done.json', { exitCode: Number(process.exitCode ?? 0) }); } + return; + } + const phase = process.env.COPILOT_NATIVE_PHASE; + if (!phase || phase === 'prepare' && getGithubActionInput('verification-setup') !== 'true') { await run(); return; } + if (process.env.GITHUB_ACTIONS !== 'true') throw new VerificationSetupError('native-workflow-required'); + if (phase === 'prepare') { await startNativeVerification(); return; } + const root = process.env.COPILOT_NATIVE_TICKET; + if (!root) throw new VerificationSetupError('handoff-invalid'); + nativeSession(root); + core.saveState('native-verification-ticket', root); + core.saveState('native-verification-source', process.env.COPILOT_NATIVE_SOURCE ?? ''); + core.saveState('native-verification-digest', process.env.COPILOT_NATIVE_DIGEST ?? ''); + core.saveState('native-verification-git', process.env.COPILOT_NATIVE_GIT ?? ''); + if (phase === 'continue') { + await continueNativeVerification(root, process.env.COPILOT_NATIVE_REQUEST ?? '', process.env.COPILOT_NATIVE_OUTCOME ?? ''); + return; + } + if (phase === 'finish') { + nativeSession(root); + const done = readNativeRecord<{exitCode: number}>(root, 'done.json'); + if (done) { emitWorkerOutput(root); if (done.exitCode !== 0) core.setFailed('Copilot execution failed.'); } + else core.setFailed(`Repository setup did not complete within ${VERIFICATION_SETUP_LIMITS.requests} native verification boundaries. Pending verification and subsequent publication are blocked; inspect earlier operation results for completed effects.`); + await cleanupNativeVerification(root, 10_000, true); + return; + } + throw new VerificationSetupError('handoff-invalid'); +} diff --git a/src/infrastructure/native_verification_profiles.ts b/src/infrastructure/native_verification_profiles.ts new file mode 100644 index 000000000..d3db3bd8b --- /dev/null +++ b/src/infrastructure/native_verification_profiles.ts @@ -0,0 +1,37 @@ +import { createHash } from 'node:crypto'; +import { lstatSync, realpathSync, rmSync, writeFileSync, readFileSync } from 'node:fs'; +import { basename, dirname, join } from 'node:path'; +import { VerificationSetupError } from '../application/policies/verification_setup_policy'; +import { readNativeRecord, writeNativeRecord } from './native_verification_protocol'; + +export function nativeVerificationProfilePaths(root: string): string[] { + const stored = readNativeRecord(root, 'profiles.json', 65_536); + const paths = stored === undefined ? [] : stored; + if (!Array.isArray(paths) || paths.length > 20 || new Set(paths).size !== paths.length || paths.some(path => typeof path !== 'string' + || dirname(path) !== dirname(root) || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test(basename(path)) || path === root)) throw new VerificationSetupError('handoff-invalid'); + return paths; +} + +/** Native actions may have post steps. Preserve dependency-only profiles until those steps finish. */ +export function registerNativeVerificationProfile(root: string, profile: string): void { + const previous = nativeVerificationProfilePaths(root); + if (previous.length >= 20 || previous.includes(profile) || dirname(profile) !== dirname(root) + || profile === root || !/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test(basename(profile)) + || realpathSync(profile) !== profile || !lstatSync(profile).isDirectory() || lstatSync(profile).isSymbolicLink()) throw new VerificationSetupError('handoff-invalid'); + writeFileSync(join(profile, 'native-session-owner'), createHash('sha256').update(root).digest('hex'), { mode: 0o600, flag: 'wx' }); + writeNativeRecord(root, 'profiles.json', [...previous, profile]); +} + +export function nativeVerificationRetainsProfile(profile: string): boolean { + const root = process.env.COPILOT_NATIVE_WORKER_SESSION; + return !!root && nativeVerificationProfilePaths(root).includes(profile); +} + +export function removeNativeVerificationProfiles(root: string): void { + for (const path of nativeVerificationProfilePaths(root)) { + let stat; + try { stat = lstatSync(path); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue; throw error; } + if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(path) !== path || readFileSync(join(path, 'native-session-owner'), 'utf8') !== createHash('sha256').update(root).digest('hex')) throw new VerificationSetupError('handoff-invalid'); + rmSync(path, { recursive: true, force: true }); + } +} diff --git a/src/infrastructure/native_verification_protocol.ts b/src/infrastructure/native_verification_protocol.ts new file mode 100644 index 000000000..086e0754f --- /dev/null +++ b/src/infrastructure/native_verification_protocol.ts @@ -0,0 +1,63 @@ +import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs'; +import { basename, join } from 'node:path'; +import { randomUUID } from 'node:crypto'; +import { VerificationSetupError } from '../application/policies/verification_setup_policy'; + +export interface NativeVerificationSession { + readonly schemaVersion: 1; + readonly workspace: string; + readonly run: string; + readonly attempt: string; + readonly job: string; + readonly pid: number; +} +export interface NativeVerificationRequest { + readonly id: string; + readonly sourceSha: string; + readonly digest: string; + readonly environment: Record; +} +export interface NativeVerificationResponse { + readonly id: string; + readonly success: boolean; + readonly path: string; + readonly javaHome?: string; +} + +export function writeNativeRecord(root: string, name: string, value: unknown): void { + const temporary = join(root, `${randomUUID()}.tmp`); + writeFileSync(temporary, JSON.stringify(value), { mode: 0o600, flag: 'wx' }); + renameSync(temporary, join(root, name)); +} + +export function readNativeRecord(root: string, name: string, limit = 16_384): T | undefined { + const file = join(root, name); + let descriptor: number | undefined; + try { + const before = lstatSync(file, { bigint: true }); + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(limit)) throw new Error(); + descriptor = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const after = fstatSync(descriptor, { bigint: true }); + if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || after.mtimeNs !== before.mtimeNs) throw new Error(); + const data = readFileSync(descriptor); + const final = fstatSync(descriptor, { bigint: true }); + if (data.length > limit || final.size !== after.size || final.mtimeNs !== after.mtimeNs) throw new Error(); + return JSON.parse(data.toString('utf8')) as T; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; + throw new VerificationSetupError('handoff-invalid'); + } finally { if (descriptor !== undefined) closeSync(descriptor); } +} + +export function nativeSession(root: string, environment = process.env): NativeVerificationSession { + if (!/^copilot-verify-runtime-[A-Za-z0-9]+$/u.test(basename(root)) || realpathSync(root) !== root + || !lstatSync(root).isDirectory() || lstatSync(root).isSymbolicLink()) throw new VerificationSetupError('handoff-invalid'); + if (process.platform !== 'win32' && (lstatSync(root).mode & 0o077) !== 0) throw new VerificationSetupError('handoff-invalid'); + const session = readNativeRecord(root, 'session.json'); + if (session?.schemaVersion !== 1 || session.workspace !== realpathSync(process.cwd()) + || session.run !== (environment.GITHUB_RUN_ID ?? '') || session.attempt !== (environment.GITHUB_RUN_ATTEMPT ?? '') + || session.job !== (environment.GITHUB_JOB ?? '') || !Number.isSafeInteger(session.pid) || session.pid <= 0) throw new VerificationSetupError('handoff-invalid'); + return session; +} + +export const nativePause = (milliseconds: number): Promise => new Promise(resolve => setTimeout(resolve, milliseconds)); diff --git a/src/infrastructure/native_verification_redaction.ts b/src/infrastructure/native_verification_redaction.ts new file mode 100644 index 000000000..b1446d352 --- /dev/null +++ b/src/infrastructure/native_verification_redaction.ts @@ -0,0 +1,45 @@ +import { StringDecoder } from 'node:string_decoder'; + +const SECRET_KEY = /(?:TOKEN|SECRET|API_KEY|PASSWORD|AUTH_JSON)/iu; + +export function nativeWorkerSecrets(environment: NodeJS.ProcessEnv): string[] { + const values = Object.entries(environment).filter(([key, value]) => SECRET_KEY.test(key) && value).map(([,value]) => value!); + if (environment.INPUT_VARS_JSON) { + try { + const inputs = JSON.parse(environment.INPUT_VARS_JSON) as Record; + for (const [key, value] of Object.entries(inputs)) if (SECRET_KEY.test(key) && typeof value === 'string') values.push(value); + } catch { /* The existing input parser reports invalid configuration without raw values. */ } + } + return [...new Set(values.flatMap(value => [value, ...value.split(/[\r\n]/u)]).filter(value => value.length >= 3))].sort((a,b) => b.length - a.length); +} + +/** Secrets are removed before bytes reach a private log file, not merely masked in the runner UI. */ +export function redactNativeWorkerLogs(environment: NodeJS.ProcessEnv = process.env): () => void { + const secrets = nativeWorkerSecrets(environment); + const restores: (() => void)[] = []; + for (const stream of [process.stdout, process.stderr]) { + const original = stream.write; + const decoder = new StringDecoder('utf8'); + let pending = ''; + const emit = (line: string): void => { + const command = /^::add-mask::(.*?)(?:\r?\n)?$/u.exec(line); + if (command) { + const value = command[1].replace(/%0D/gu, '\r').replace(/%0A/gu, '\n').replace(/%25/gu, '%'); + secrets.push(...value.split(/[\r\n]/u).filter(part => part.length >= 3)); + return; + } + for (const secret of secrets) line = line.split(secret).join('[REDACTED]'); + original.call(stream, line); + }; + stream.write = ((chunk: string | Uint8Array, encoding?: BufferEncoding | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean => { + pending += typeof chunk === 'string' ? chunk : decoder.write(Buffer.from(chunk)); + let end: number; + while ((end = pending.indexOf('\n')) >= 0) { const line = pending.slice(0, end + 1); pending = pending.slice(end + 1); emit(line); } + if (pending.length > 1_048_576) throw new Error('Copilot worker log line exceeded its fixed limit.'); + if (typeof encoding === 'function') encoding(); else callback?.(); + return true; + }) as typeof stream.write; + restores.push(() => { pending += decoder.end(); if (pending) emit(pending); stream.write = original; }); + } + return () => { for (const restore of restores) restore(); }; +} diff --git a/src/infrastructure/native_verification_source_runtime.ts b/src/infrastructure/native_verification_source_runtime.ts new file mode 100644 index 000000000..ce00f01d8 --- /dev/null +++ b/src/infrastructure/native_verification_source_runtime.ts @@ -0,0 +1,15 @@ +import { existsSync, lstatSync, readFileSync, realpathSync, rmSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { VerificationSetupError } from '../application/policies/verification_setup_policy'; + +/** Only source workflows use this snapshot; installed remote Actions already live outside the candidate. */ +export function cleanupNativeSourceRuntime(root: string, workspace: string): void { + if (!root || !existsSync(root)) return; + const allowed = ['.copilot-native-runtime', '.copilot-native-fixture-runtime'].map(name => join(dirname(workspace), name)); + if (!allowed.includes(root) || lstatSync(root).isSymbolicLink() || realpathSync(root) !== root) throw new VerificationSetupError('handoff-invalid'); + let receipt: {kind?: string; workspace?: string}; + try { receipt = JSON.parse(readFileSync(join(root, '.copilot-runtime-snapshot.json'), 'utf8')) as typeof receipt; } + catch { throw new VerificationSetupError('handoff-invalid'); } + if (receipt?.kind !== 'copilot-native-runtime' || receipt.workspace !== workspace) throw new VerificationSetupError('handoff-invalid'); + rmSync(root, { recursive: true, force: true }); +} diff --git a/src/infrastructure/native_verification_staging.ts b/src/infrastructure/native_verification_staging.ts new file mode 100644 index 000000000..08967a8e4 --- /dev/null +++ b/src/infrastructure/native_verification_staging.ts @@ -0,0 +1,41 @@ +import { lstatSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join, relative, sep } from 'node:path'; +import type { PreparedVerificationSetup } from '../application/ports/verification_setup_ports'; +import { VerificationSetupError } from '../application/policies/verification_setup_policy'; +import { readNativeRecord, writeNativeRecord } from './native_verification_protocol'; + +export const NATIVE_VERIFICATION_ACTION = '.git/copilot-verification-setup'; +interface StagingRecord { readonly target: string } + +function targetPath(workspace: string): string { + const metadata = join(workspace, '.git'); + const stat = lstatSync(metadata); + if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(metadata) !== metadata) throw new VerificationSetupError('native-workflow-required'); + return join(workspace, NATIVE_VERIFICATION_ACTION); +} + +/** Native composite metadata must remain available until its deferred post steps have finished. */ +export function stageNativeVerificationAction(setup: Extract, session: string): void { + const target = targetPath(setup.workspace); + const record = readNativeRecord(session, 'staging.json'); + if (record && record.target !== target || !record && !!lstatSync(target, { throwIfNoEntry: false })) throw new VerificationSetupError('handoff-invalid'); + writeNativeRecord(session, 'staging.json', { target }); + rmSync(target, { recursive: true, force: true }); + mkdirSync(target, { mode: 0o700 }); + for (const file of setup.files) { + const destination = join(target, file.path); + const remainder = relative(target, destination); + if (!remainder || remainder.startsWith(`..${sep}`) || remainder === '..') throw new VerificationSetupError('helper-invalid'); + mkdirSync(dirname(destination), { recursive: true, mode: 0o700 }); + writeFileSync(destination, Buffer.from(file.base64, 'base64'), { mode: file.executable ? 0o700 : 0o600, flag: 'wx' }); + } +} + +export function cleanupNativeVerificationAction(session: string, workspace: string): void { + const record = readNativeRecord(session, 'staging.json'); + if (!record) return; + const target = targetPath(workspace); + if (record.target !== target) throw new VerificationSetupError('handoff-invalid'); + rmSync(target, { recursive: true, force: true }); + rmSync(join(session, 'staging.json'), { force: true }); +} diff --git a/src/infrastructure/untrusted_command_environment.ts b/src/infrastructure/untrusted_command_environment.ts index f8f0604b4..57b5045c8 100644 --- a/src/infrastructure/untrusted_command_environment.ts +++ b/src/infrastructure/untrusted_command_environment.ts @@ -1,4 +1,4 @@ -import { mkdirSync, rmSync } from 'node:fs'; +import { mkdirSync, realpathSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { createPrivateVerificationRuntimeRoot } from './agents/agent_private_install_root'; @@ -45,7 +45,7 @@ export function prepareUntrustedCommandEnvironment( source: NodeJS.ProcessEnv = process.env, platform: NodeJS.Platform = process.platform, ): PreparedUntrustedCommandEnvironment { - const runtimeHome = createPrivateVerificationRuntimeRoot(); + const runtimeHome = realpathSync(createPrivateVerificationRuntimeRoot()); const environment: Record = { HOME: runtimeHome }; for (const variable of ALLOWED_VARIABLES) { const value = source[variable]; diff --git a/src/infrastructure/verification_executable.ts b/src/infrastructure/verification_executable.ts new file mode 100644 index 000000000..ffcaa52d0 --- /dev/null +++ b/src/infrastructure/verification_executable.ts @@ -0,0 +1,17 @@ +import { accessSync, constants, statSync } from 'node:fs'; +import { delimiter, isAbsolute, join, resolve } from 'node:path'; + +/** @actions/exec resolves basenames against its own process PATH; native tools belong to the verifier PATH. */ +export function verificationExecutable(program: string, environment: Record, platform = process.platform): string { + const explicit = isAbsolute(program) || /[\\/]/u.test(program); + const candidates = explicit ? [resolve(program)] : (environment.PATH ?? '').split(delimiter).filter(Boolean).map(directory => resolve(join(directory, program))); + const windowsExtensions = (environment.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean); + const hasWindowsExtension = windowsExtensions.some(extension => program.toLowerCase().endsWith(extension.toLowerCase())); + const extensions = platform === 'win32' && !hasWindowsExtension ? windowsExtensions : ['']; + for (const candidate of candidates) for (const extension of extensions) { + const file = candidate + extension; + try { if (statSync(file).isFile()) { accessSync(file, constants.X_OK); return file; } } + catch { /* Try only the next executable in the isolated PATH. */ } + } + throw new Error('Verification executable is unavailable in the isolated PATH.'); +} diff --git a/src/infrastructure/verification_setup_result.ts b/src/infrastructure/verification_setup_result.ts new file mode 100644 index 000000000..909bdeada --- /dev/null +++ b/src/infrastructure/verification_setup_result.ts @@ -0,0 +1,42 @@ +import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync } from 'node:fs'; +import { delimiter, isAbsolute, relative, sep } from 'node:path'; +import { parseVerificationSetupResult, VerificationSetupError, VERIFICATION_SETUP_LIMITS } from '../application/policies/verification_setup_policy'; + +function contained(root: string, path: string): boolean { + const remainder = relative(root, path); + return remainder === '' || (remainder !== '..' && !remainder.startsWith(`..${sep}`) && !isAbsolute(remainder)); +} + +/** Accept only existing contained directories, checking both lexical paths and symlink targets. */ +function approvedDirectory(value: string, roots: readonly string[]): string { + if (!isAbsolute(value) || value.includes(delimiter) || value.split(/[\\/]/u).includes('..') + || !roots.some(root => contained(root, value))) throw new VerificationSetupError('result-invalid'); + const actual = realpathSync(value); + if (!roots.some(root => contained(root, actual)) || !lstatSync(actual).isDirectory()) throw new VerificationSetupError('result-invalid'); + return actual; +} + +export function applyVerificationSetupResult(file: string, environment: Record, roots: readonly string[]): void { + let descriptor: number | undefined; + try { + let before; + try { before = lstatSync(file, { bigint: true }); } + catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return; throw error; } + if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > BigInt(VERIFICATION_SETUP_LIMITS.resultBytes)) throw new Error(); + descriptor = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const opened = fstatSync(descriptor, { bigint: true }); + if (!opened.isFile() || opened.nlink !== 1n || opened.dev !== before.dev || opened.ino !== before.ino + || opened.size !== before.size || opened.mtimeNs !== before.mtimeNs) throw new Error(); + const bytes = Buffer.alloc(VERIFICATION_SETUP_LIMITS.resultBytes + 1); + const size = readSync(descriptor, bytes, 0, bytes.length, 0); + if (size > VERIFICATION_SETUP_LIMITS.resultBytes) throw new Error(); + const after = fstatSync(descriptor, { bigint: true }); + if (after.size !== opened.size || after.mtimeNs !== opened.mtimeNs) throw new Error(); + const result = parseVerificationSetupResult(JSON.parse(bytes.subarray(0, size).toString('utf8'))); + const paths = result.path.map(value => approvedDirectory(value, roots)); + const javaHome = result.javaHome ? approvedDirectory(result.javaHome, roots) : undefined; + if (paths.length) environment.PATH = [...paths, ...(environment.PATH ? [environment.PATH] : [])].join(delimiter); + if (javaHome) environment.JAVA_HOME = javaHome; + } catch { throw new VerificationSetupError('result-invalid'); } + finally { if (descriptor !== undefined) closeSync(descriptor); } +} diff --git a/src/infrastructure/verification_setup_runner.ts b/src/infrastructure/verification_setup_runner.ts new file mode 100644 index 000000000..7b8f801b6 --- /dev/null +++ b/src/infrastructure/verification_setup_runner.ts @@ -0,0 +1,60 @@ +import { verificationSetupDigest } from './verification_setup_snapshot'; +import { registerNativeVerificationProfile, nativeVerificationProfilePaths } from './native_verification_profiles'; +import { realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { delimiter, join, relative, isAbsolute, sep } from 'node:path'; +import { randomUUID } from 'node:crypto'; +import type { PreparedVerificationSetup } from '../application/ports/verification_setup_ports'; +import { VerificationSetupError, VERIFICATION_SETUP_LIMITS, VERIFICATION_SETUP_PATH } from '../application/policies/verification_setup_policy'; +import { applyVerificationSetupResult } from './verification_setup_result'; +import { logInfo } from '../application/ports/logging_ports'; +import { nativePause, nativeSession, readNativeRecord, writeNativeRecord, type NativeVerificationResponse } from './native_verification_protocol'; +import { stageNativeVerificationAction } from './native_verification_staging'; +import { verificationCandidateIdentity } from './native_verification_candidate'; + + +/** The runner executes YAML. This adapter pauses the admitted call stack at its verification boundary. */ +export async function prepareRepositoryVerification(setup: PreparedVerificationSetup, environment: Record): Promise { + if (setup.kind === 'disabled') return; + if (setup.kind === 'blocked') throw new VerificationSetupError(setup.reason, setup.sourceSha); + const session = process.env.COPILOT_NATIVE_WORKER_SESSION; + if (!session) throw new VerificationSetupError('native-workflow-required', setup.sourceSha); + try { + nativeSession(session); + const requests = readNativeRecord<{count: number}>(session, 'request-count.json')?.count ?? 0; + if (requests >= VERIFICATION_SETUP_LIMITS.requests) throw new VerificationSetupError('handoff-invalid'); + writeNativeRecord(session, 'request-count.json', { count: requests + 1 }); + if (realpathSync(process.cwd()) !== setup.workspace) throw new VerificationSetupError('workspace-changed'); + registerNativeVerificationProfile(session, environment.HOME); + const identity = verificationCandidateIdentity(setup.workspace, environment); + stageNativeVerificationAction(setup, session); + const id = randomUUID(); + writeNativeRecord(session, 'request.json', { id, sourceSha: setup.sourceSha, digest: verificationSetupDigest(setup), environment }); + logInfo(`Preparing repository verification (${VERIFICATION_SETUP_PATH}; trusted source SHA: ${setup.sourceSha}).`); + const deadline = Date.now() + VERIFICATION_SETUP_LIMITS.timeoutMs; + let response: NativeVerificationResponse | undefined; + while (!(response = readNativeRecord(session, `response-${id}.json`))) { + if (readNativeRecord(session, 'cancel.json')) throw new VerificationSetupError('cancelled'); + if (Date.now() >= deadline) throw new VerificationSetupError('timeout'); + await nativePause(100); + } + if (response.id !== id || response.success !== true) throw new VerificationSetupError('process-failed'); + if (verificationCandidateIdentity(setup.workspace, environment) !== identity) throw new VerificationSetupError('candidate-changed'); + if (typeof response.path !== 'string' || response.path.length > 16_384 || response.javaHome !== undefined && typeof response.javaHome !== 'string') throw new VerificationSetupError('result-invalid'); + const original = new Set((environment.PATH ?? '').split(delimiter)); + const previousProfiles = nativeVerificationProfilePaths(session).filter(profile => profile !== environment.HOME); + const fromPreviousProfile = (path: string): boolean => previousProfiles.some(profile => { + const part = relative(profile, path); + return part === '' || part !== '..' && !part.startsWith(`..${sep}`) && !isAbsolute(part); + }); + const additions = [...new Set(response.path.split(delimiter).filter(path => path && !original.has(path) && !fromPreviousProfile(path)))]; + const result = join(environment.HOME, 'native-setup-result.json'); + writeFileSync(result, JSON.stringify({ schemaVersion: 1, path: additions, + environment: response.javaHome && response.javaHome !== environment.JAVA_HOME ? { JAVA_HOME: response.javaHome } : {} }), { mode: 0o600, flag: 'wx' }); + applyVerificationSetupResult(result, environment, [setup.workspace, environment.HOME, ...(setup.toolRoot ? [setup.toolRoot] : [])]); + logInfo('Repository setup completed; runtime preflight and verification may proceed.'); + } catch (error) { + throw new VerificationSetupError(error instanceof VerificationSetupError ? error.reason : 'handoff-invalid', setup.sourceSha); + } finally { + rmSync(join(session, 'request.json'), { force: true }); + } +} diff --git a/src/infrastructure/verification_setup_snapshot.ts b/src/infrastructure/verification_setup_snapshot.ts new file mode 100644 index 000000000..90314f7d9 --- /dev/null +++ b/src/infrastructure/verification_setup_snapshot.ts @@ -0,0 +1,77 @@ +import { isAbsolute } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { realpathSync } from 'node:fs'; +import { load } from 'js-yaml'; +import type { PreparedVerificationSetup, VerificationSetupFile } from '../application/ports/verification_setup_ports'; +import { DISABLED_VERIFICATION_SETUP } from '../application/ports/verification_setup_ports'; +import { VERIFICATION_SETUP_DIRECTORY, VERIFICATION_SETUP_LIMITS, VerificationSetupError } from '../application/policies/verification_setup_policy'; +import { prepareUntrustedCommandEnvironment } from './untrusted_command_environment'; + +/** Only immutable Git objects are read. Errors remain deferred on non-verifying routes. */ +export function verificationSetupDigest(setup: Extract): string { + return createHash('sha256').update(JSON.stringify(setup.files)).digest('hex'); +} + +export function captureVerificationSetup(enabled: boolean, mainBranch: string, root = process.cwd()): PreparedVerificationSetup { + return capture(enabled, mainBranch, root); +} + +/** Post steps use the already pinned commit from runner state, never a mutable branch ref. */ +export function captureVerificationSetupAtCommit(commit: string, root = process.cwd(), gitExecutable?: string): PreparedVerificationSetup { + return capture(true, '', root, commit, gitExecutable); +} + +function capture(enabled: boolean, mainBranch: string, root: string, pinned?: string, gitExecutable?: string): PreparedVerificationSetup { + if (!enabled) return DISABLED_VERIFICATION_SETUP; + let sourceSha: string | undefined; + let runtime: ReturnType | undefined; + let snapshot: PreparedVerificationSetup; + try { + runtime = prepareUntrustedCommandEnvironment(); + const environment = runtime.environment; + if (!pinned && (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,200}$/u.test(mainBranch) || mainBranch.includes('..') + || mainBranch.includes('//') || mainBranch.endsWith('/') || mainBranch.endsWith('.lock'))) throw new Error(); + if (gitExecutable !== undefined && !isAbsolute(gitExecutable)) throw new Error(); + const workspace = realpathSync(root); + const git = (...args: string[]): Buffer => { + const result = spawnSync(gitExecutable ?? 'git', ['--no-replace-objects', ...args], { cwd: workspace, env: environment, + shell: false, windowsHide: true, timeout: 5_000, maxBuffer: VERIFICATION_SETUP_LIMITS.snapshotBytes + 4096 }); + if (result.error || result.status !== 0) throw new Error(); + return result.stdout; + }; + const sha = pinned ?? git('rev-parse', '--verify', `refs/remotes/origin/${mainBranch}^{commit}`).toString('utf8').trim(); + if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(sha)) throw new Error(); + sourceSha = sha; + const entries = git('ls-tree', '-rz', sourceSha, '--', VERIFICATION_SETUP_DIRECTORY).toString('utf8').split('\0').filter(Boolean); + if (!entries.length || entries.length > VERIFICATION_SETUP_LIMITS.files) throw new VerificationSetupError('helper-invalid'); + let total = 0; + const files: VerificationSetupFile[] = entries.map(entry => { + const match = /^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t(.+)$/u.exec(entry); + if (!match || !match[3].startsWith(`${VERIFICATION_SETUP_DIRECTORY}/`)) throw new VerificationSetupError('helper-invalid'); + const path = match[3].slice(VERIFICATION_SETUP_DIRECTORY.length + 1); + if (!/^[A-Za-z0-9_./-]{1,200}$/u.test(path) || path.split('/').some(part => !part || part === '.' || part === '..' || part.endsWith('.') || /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part))) throw new VerificationSetupError('helper-invalid'); + const bytes = git('cat-file', 'blob', match[2]); + total += bytes.length; + const digest = createHash(match[2].length === 40 ? 'sha1' : 'sha256').update(`blob ${bytes.length}\0`).update(bytes).digest('hex'); + if (bytes.length > VERIFICATION_SETUP_LIMITS.scriptBytes || total > VERIFICATION_SETUP_LIMITS.snapshotBytes || digest !== match[2]) throw new VerificationSetupError('helper-invalid'); + return Object.freeze({ path, base64: bytes.toString('base64'), executable: match[1] === '100755' }); + }); + const manifest = files.find(file => file.path === 'action.yml'); + if (!manifest) throw new VerificationSetupError('helper-invalid'); + const bytes = Buffer.from(manifest.base64, 'base64'); + const text = bytes.toString('utf8'); + if (!bytes.length || !bytes.equals(Buffer.from(text)) || text.includes('\0')) throw new VerificationSetupError('helper-invalid'); + let action: { runs?: { using?: unknown; steps?: unknown[] } } | null; + try { action = load(text) as typeof action; } catch { throw new VerificationSetupError('helper-invalid'); } + if (action?.runs?.using !== 'composite' || !Array.isArray(action.runs.steps) || !action.runs.steps.length || action.runs.steps.length > 32) throw new VerificationSetupError('helper-invalid'); + if (action.runs.steps.some(step => !step || typeof step !== 'object' || 'uses' in step && typeof step.uses === 'string' && step.uses.startsWith('./'))) throw new VerificationSetupError('helper-invalid'); + const toolRoot = environment.RUNNER_TOOL_CACHE ? realpathSync(environment.RUNNER_TOOL_CACHE) : undefined; + snapshot = Object.freeze({ kind: 'ready', files: Object.freeze(files), sourceSha, workspace, ...(toolRoot ? { toolRoot } : {}) }); + } catch (error) { + snapshot = Object.freeze({ kind: 'blocked', reason: error instanceof VerificationSetupError ? error.reason : 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); + } + try { runtime?.cleanup(); } + catch { snapshot = Object.freeze({ kind: 'blocked', reason: 'source-unavailable', ...(sourceSha ? { sourceSha } : {}) }); } + return snapshot; +} diff --git a/src/tooling/__tests__/fixtures/verification_native_fixture.ts b/src/tooling/__tests__/fixtures/verification_native_fixture.ts new file mode 100644 index 000000000..3f8f77ec2 --- /dev/null +++ b/src/tooling/__tests__/fixtures/verification_native_fixture.ts @@ -0,0 +1,88 @@ +/** Native CI acceptance: the real Actions runner executes the reviewed YAML between candidate and verifier. */ +import * as core from '@actions/core'; +import { execFileSync } from 'node:child_process'; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { runNativeVerificationEntry } from '../../../infrastructure/native_verification_controller'; +import { captureVerificationSetup } from '../../../infrastructure/verification_setup_snapshot'; +import { GitCommitAdapter } from '../../../infrastructure/git_commit_adapter'; +import { VERIFICATION_SETUP_DIRECTORY, VERIFICATION_SETUP_PATH } from '../../../application/policies/verification_setup_policy'; + +const yaml = `name: Native setup acceptance +runs: + using: composite + steps: + - uses: actions/setup-python@v6 + id: python + with: + update-environment: false + python-version: '3.12' + - uses: pnpm/action-setup@v5 + with: + version: 10.12.4 + dest: \${{ env.HOME }}/native-pnpm + standalone: false + - name: Install candidate test dependencies privately + run: | + "$PYTHON_EXECUTABLE" - <<'PYTHON' + import os, pathlib, subprocess, venv + home = pathlib.Path(os.environ['HOME']) / 'venv' + venv.create(home, with_pip=True) + tools = home / ('Scripts' if os.name == 'nt' else 'bin') + python = tools / ('python.exe' if os.name == 'nt' else 'python') + subprocess.check_call([str(python), '-m', 'pip', 'install', '--no-cache-dir', '--disable-pip-version-check', '-r', 'requirements-native-fixture.txt']) + with open(os.environ['GITHUB_PATH'], 'a') as paths: + print(tools, file=paths) + PYTHON + shell: bash + env: + PYTHON_EXECUTABLE: \${{ steps.python.outputs.python-path }} + - run: python -c "from pathlib import Path; assert Path('.copilot-native-candidate').read_text() == 'after-agent'" + shell: bash +`; + +async function fixture(): Promise { + const git = (...args: string[]): string => execFileSync('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + const inputGit = (input: string, ...args: string[]): string => execFileSync('git', args, { input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + const blob = inputGit(yaml, 'hash-object', '-w', '--stdin'); + let tree = inputGit(`100644 blob ${blob}\taction.yml\n`, 'mktree'); + for (const name of ['copilot-verification-setup', 'actions', '.github']) tree = inputGit(`040000 tree ${tree}\t${name}\n`, 'mktree'); + const commit = execFileSync('git', ['commit-tree', tree, '-m', 'Native setup fixture'], { encoding: 'utf8', env: { ...process.env, + GIT_AUTHOR_NAME: 'Fixture', GIT_AUTHOR_EMAIL: 'fixture@example.test', GIT_COMMITTER_NAME: 'Fixture', GIT_COMMITTER_EMAIL: 'fixture@example.test' } }).trim(); + git('update-ref', 'refs/remotes/origin/copilot-native-fixture', commit); + const setup = captureVerificationSetup(true, 'copilot-native-fixture'); + if (setup.kind !== 'ready') throw new Error('Native fixture snapshot was not captured'); + const candidateAction = join(process.cwd(), VERIFICATION_SETUP_DIRECTORY); + const marker = join(process.cwd(), '.copilot-native-candidate'); + const testFile = join(process.cwd(), 'test_copilot_native_candidate.py'); + const requirements = join(process.cwd(), 'requirements-native-fixture.txt'); + const candidateYaml = 'runs:\n using: composite\n steps:\n - run: exit 99\n shell: bash\n'; + mkdirSync(candidateAction, { recursive: true }); + writeFileSync(join(candidateAction, 'action.yml'), candidateYaml); + writeFileSync(marker, 'after-agent'); + writeFileSync(requirements, 'pytest==8.4.2\n'); + writeFileSync(testFile, 'from pathlib import Path\ndef test_candidate():\n assert Path(".copilot-native-candidate").read_text() == "after-agent"\n'); + try { + const adapter = new GitCommitAdapter(undefined, undefined, setup); + await adapter.execute('python', ['-m', 'pytest', '-q', testFile, '-p', 'no:cacheprovider'], { untrusted: true, verifyRuntimePrerequisites: true }); + await adapter.execute('pnpm', ['--version'], { untrusted: true, verifyRuntimePrerequisites: true }); + if (readFileSync(VERIFICATION_SETUP_PATH, 'utf8') !== candidateYaml) throw new Error('Candidate action was not restored'); + writeFileSync(join(process.cwd(), '.github/actions/copilot-native-fixture/dist/index.js'), 'throw new Error("Candidate runtime must never execute during continuation or post");'); + core.info('Native composite acceptance passed: candidate manifests, trusted snapshot, Python/pytest and Node/pnpm, two fresh profiles.'); + } catch (error) { + // This controlled fixture contains no credentials. File names, rather than file contents, + // identify dependency installers that unexpectedly alter the publishable candidate. + core.info(`Native fixture candidate status: ${git('status', '--porcelain=v1').slice(0, 8_192)}`); + core.info(`Native fixture tracked changes: ${git('diff', '--no-ext-diff', '--no-textconv', '--numstat', 'HEAD', '--').slice(0, 8_192)}`); + throw error; + } finally { + rmSync(candidateAction, { recursive: true, force: true }); rmSync(marker, { force: true }); rmSync(testFile, { force: true }); rmSync(requirements, { force: true }); + rmSync(join(process.cwd(), '__pycache__'), { recursive: true, force: true }); + git('update-ref', '-d', 'refs/remotes/origin/copilot-native-fixture', commit); + } +} + +void runNativeVerificationEntry(async () => { + try { await fixture(); } + catch (error) { core.setFailed(error instanceof Error ? error.message : 'Native acceptance failed'); } +}).catch(error => core.setFailed(error instanceof Error ? error.message : 'Native continuation failed')); diff --git a/src/tooling/__tests__/validate_workflow_contract.test.ts b/src/tooling/__tests__/validate_workflow_contract.test.ts index db7489c7a..e050dcb1f 100644 --- a/src/tooling/__tests__/validate_workflow_contract.test.ts +++ b/src/tooling/__tests__/validate_workflow_contract.test.ts @@ -153,10 +153,10 @@ describe('workflow contract validator', () => { } }); - it.each(['issue-managed-branches', 'pre-branch-sdd'])( + it.each(['issue-managed-branches', 'pre-branch-sdd', 'verification-setup'])( 'rejects a PR comment callback that omits or disables the installed %s policy', input => { const { file, workflow } = loadMutationWorkflow('.github/workflows', 'copilot_pull_request_comment.yml'); - const action = workflow.jobs['copilot-pull-requests'].steps.find((step: { uses?: string }) => step.uses === './'); + const action = workflow.jobs['copilot-pull-requests'].steps.find((step: { id?: string }) => step.id === 'copilot'); delete action.with[input]; expect(() => assertIssueBranchConfigurationInputs(file, workflow)).toThrow('must pass consistent'); action.with[input] = false; diff --git a/src/utils/__tests__/setup_files.test.ts b/src/utils/__tests__/setup_files.test.ts index e5b3146c7..4e79692df 100644 --- a/src/utils/__tests__/setup_files.test.ts +++ b/src/utils/__tests__/setup_files.test.ts @@ -141,9 +141,9 @@ describe('setup_files', () => { const url = new URL(destination); if (url.origin === 'https://docs.page') { expect(url.pathname).toBe('/vypdev/copilot/security-operations/operations/verification'); - expect(url.hash).toBe('#repository-verification-prerequisites'); + expect(['#repository-verification-prerequisites', '#repository-owned-setup']).toContain(url.hash); expect(fs.readFileSync(path.join(sourceRoot, 'docs/security-operations/operations/verification.mdx'), 'utf8')) - .toContain('## Repository verification prerequisites'); + .toContain(url.hash === '#repository-owned-setup' ? '## Repository-owned setup' : '## Repository verification prerequisites'); } else { expect(url.origin).toBe('https://github.com'); expect(url.pathname).toMatch(/^\/vypdev\/copilot\/blob\/master\/docs\/agents\//u); @@ -158,6 +158,7 @@ describe('setup_files', () => { expect(documentationLinks).toEqual([ 'https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-verification-prerequisites', + 'https://docs.page/vypdev/copilot/security-operations/operations/verification#repository-owned-setup', 'https://github.com/vypdev/copilot/blob/master/docs/agents/repository-collaboration.mdx', 'https://github.com/vypdev/copilot/blob/master/docs/agents/repository-guidance-troubleshooting.mdx', ]);