From 6f256f3e2a2993b2ace16c999e81ae337487e3d9 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 9 Oct 2026 10:54:14 -0500 Subject: [PATCH] Finish Copilot production qualification on current master Preserve the reviewed candidate source and mainline provider behavior. Co-Authored-By: Paperclip --- .dockerignore | 6 + .github/workflows/docker-runner-check.yml | 45 +- .gitignore | 3 + .../company-import-export-e2e.test.ts | 5 +- ...2026-10-03-copilot-production-readiness.md | 338 +++ .../2026-10-03-copilot-qualification-v19.json | 1055 ++++++++ .../2026-10-03-copilot-qualification-v20.json | 739 ++++++ .../2026-10-05-copilot-qualification-v21.json | 576 +++++ .../2026-10-08-copilot-qualification-v30.json | 70 + .../2026-10-08-copilot-qualification-v31.json | 65 + .../2026-10-08-copilot-qualification-v32.json | 55 + .../2026-10-08-copilot-qualification-v33.json | 451 ++++ docker/daytona-runner/Dockerfile | 2 +- .../cursor-local/src/server/execute.test.ts | 17 +- .../scripts/copilot-provider-pack-smoke.mjs | 31 +- ...ot-production-v26-offline-regressions.json | 23 + .../copilot-profile-v26-identity.json | 81 + .../copilot-profile-v28-identity.json | 81 + .../copilot-profile-v29-identity.json | 81 + .../src/__tests__/workspace-runtime.test.ts | 8 +- tests/runner-e2e/FIXTURES.md | 11 +- tests/runner-e2e/README.md | 22 + tests/runner-e2e/catalog.test.ts | 16 +- tests/runner-e2e/catalog.ts | 53 +- .../copilot-active-stop-evidence.ts | 238 ++ tests/runner-e2e/copilot-active-stop-flow.ts | 262 ++ tests/runner-e2e/copilot-active-stop-tasks.ts | 21 + tests/runner-e2e/copilot-active-stop.test.ts | 484 ++++ .../copilot-context-evidence.test.ts | 241 ++ tests/runner-e2e/copilot-context-evidence.ts | 146 ++ .../copilot-context-permission.test.ts | 119 + .../runner-e2e/copilot-context-permission.ts | 96 + .../copilot-discovery-sequence.test.ts | 57 + tests/runner-e2e/copilot-evidence.ts | 17 + tests/runner-e2e/copilot-local-fixtures.ts | 97 +- .../copilot-local-process-observer.test.ts | 49 + .../copilot-permission-card.spec.ts | 24 + tests/runner-e2e/copilot-permission-card.ts | 10 + .../copilot-permission-target-origin.test.ts | 55 + .../copilot-protection-cases.test.ts | 3 +- .../copilot-protection-evidence.test.ts | 4 +- .../runner-e2e/copilot-protection-evidence.ts | 41 +- .../copilot-protection-flow.test.ts | 47 +- tests/runner-e2e/copilot-protection-flow.ts | 134 +- .../copilot-protection-settlement.test.ts | 51 +- tests/runner-e2e/copilot-protection-tasks.ts | 3 +- .../runner-e2e/copilot-provider-death.test.ts | 80 + tests/runner-e2e/copilot-provider-death.ts | 71 + .../copilot-semantic-evidence.test.ts | 77 +- tests/runner-e2e/copilot-semantic-evidence.ts | 43 +- .../copilot-stop-acknowledgement.test.ts | 55 + .../copilot-stop-acknowledgement.ts | 46 + .../runner-e2e/copilot-stop-test-fixtures.ts | 20 + tests/runner-e2e/daytona-image-content.ts | 10 + tests/runner-e2e/daytona-image.test.ts | 33 + ...pilot-attached-discovery-sequence-v15.json | 2144 +++++++++++++++++ ...pilot-context-attached-completion-v15.json | 1115 +++++++++ ...ilot-context-discovery-permission-v15.json | 296 +++ ...ilot-context-discovery-permission-v17.json | 296 +++ .../copilot-context-permission-v15.json | 303 +++ .../copilot-context-permission-v26.json | 306 +++ .../copilot-mainline-stop-ack-v15.json | 55 + .../copilot-patch-permission-target-v15.json | 964 ++++++++ tests/runner-e2e/harness-env.ts | 2 +- tests/runner-e2e/live-fixtures.ts | 16 +- tests/runner-e2e/native-active-stop.test.ts | 2 +- .../native-bootstrap-read-proof.test.ts | 3 +- tests/runner-e2e/playwright-support.config.ts | 2 +- tests/runner-e2e/process-tree-owner.test.ts | 24 + tests/runner-e2e/process-tree-owner.ts | 10 +- .../remote-native-bootstrap.test.ts | 10 + tests/runner-e2e/remote-native-bootstrap.ts | 6 + .../runner-e2e/remote-native-fixtures.test.ts | 94 +- tests/runner-e2e/remote-native-fixtures.ts | 67 +- tests/runner-e2e/runner.spec.ts | 26 +- tests/runner-e2e/warm-managed-files.test.ts | 3 +- 76 files changed, 12006 insertions(+), 206 deletions(-) create mode 100644 doc/plans/2026-10-03-copilot-production-readiness.md create mode 100644 doc/plans/2026-10-03-copilot-qualification-v19.json create mode 100644 doc/plans/2026-10-03-copilot-qualification-v20.json create mode 100644 doc/plans/2026-10-05-copilot-qualification-v21.json create mode 100644 doc/plans/2026-10-08-copilot-qualification-v30.json create mode 100644 doc/plans/2026-10-08-copilot-qualification-v31.json create mode 100644 doc/plans/2026-10-08-copilot-qualification-v32.json create mode 100644 doc/plans/2026-10-08-copilot-qualification-v33.json create mode 100644 packages/paperclip-runner/test/fixtures/copilot-production-v26-offline-regressions.json create mode 100644 packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json create mode 100644 packages/paperclip-runner/test/fixtures/copilot-profile-v28-identity.json create mode 100644 packages/paperclip-runner/test/fixtures/copilot-profile-v29-identity.json create mode 100644 tests/runner-e2e/copilot-active-stop-evidence.ts create mode 100644 tests/runner-e2e/copilot-active-stop-flow.ts create mode 100644 tests/runner-e2e/copilot-active-stop-tasks.ts create mode 100644 tests/runner-e2e/copilot-active-stop.test.ts create mode 100644 tests/runner-e2e/copilot-context-evidence.test.ts create mode 100644 tests/runner-e2e/copilot-context-evidence.ts create mode 100644 tests/runner-e2e/copilot-context-permission.test.ts create mode 100644 tests/runner-e2e/copilot-context-permission.ts create mode 100644 tests/runner-e2e/copilot-discovery-sequence.test.ts create mode 100644 tests/runner-e2e/copilot-local-process-observer.test.ts create mode 100644 tests/runner-e2e/copilot-permission-card.spec.ts create mode 100644 tests/runner-e2e/copilot-permission-card.ts create mode 100644 tests/runner-e2e/copilot-permission-target-origin.test.ts create mode 100644 tests/runner-e2e/copilot-provider-death.test.ts create mode 100644 tests/runner-e2e/copilot-provider-death.ts create mode 100644 tests/runner-e2e/copilot-stop-acknowledgement.test.ts create mode 100644 tests/runner-e2e/copilot-stop-acknowledgement.ts create mode 100644 tests/runner-e2e/copilot-stop-test-fixtures.ts create mode 100644 tests/runner-e2e/fixtures/copilot-attached-discovery-sequence-v15.json create mode 100644 tests/runner-e2e/fixtures/copilot-context-attached-completion-v15.json create mode 100644 tests/runner-e2e/fixtures/copilot-context-discovery-permission-v15.json create mode 100644 tests/runner-e2e/fixtures/copilot-context-discovery-permission-v17.json create mode 100644 tests/runner-e2e/fixtures/copilot-context-permission-v15.json create mode 100644 tests/runner-e2e/fixtures/copilot-context-permission-v26.json create mode 100644 tests/runner-e2e/fixtures/copilot-mainline-stop-ack-v15.json create mode 100644 tests/runner-e2e/fixtures/copilot-patch-permission-target-v15.json diff --git a/.dockerignore b/.dockerignore index 30d3d533bf0..23085f962f9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -61,3 +61,9 @@ packages/paperclip-runner/test-fixtures/pi-acp/** !packages/paperclip-runner/src/**/*.test.tsx packages/grok-acp/bin + +# The Copilot build-time identity gate reads these sanitized source declarations. +!packages/paperclip-runner/test/fixtures/copilot-profile-v*-identity.json +# Images build their pinned Copilot assets instead of copying host binaries. +/packages/paperclip-runner/provider-assets/ +/server/provider-assets/ diff --git a/.github/workflows/docker-runner-check.yml b/.github/workflows/docker-runner-check.yml index d6a2115efb9..966edd26509 100644 --- a/.github/workflows/docker-runner-check.yml +++ b/.github/workflows/docker-runner-check.yml @@ -7,6 +7,10 @@ on: description: "Branch in this repository to build; resolved to one immutable commit before checkout" type: string required: false + build_eval_image: + description: "Build an immutable qualification image as an inspectable OCI artifact without publication" + type: boolean + default: false publish_eval_image: description: "Publish an immutable Daytona qualification image on hosted Linux (no provider credentials)" type: boolean @@ -137,7 +141,11 @@ jobs: package-manager-cache: false - name: Resolve source dependencies without lifecycle scripts run: | - pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile + if [[ "${{ inputs.build_eval_image || inputs.publish_eval_image }}" == true ]]; then + pnpm install --frozen-lockfile --ignore-scripts + else + pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile + fi mkdir -p remote-verification sha256sum pnpm-lock.yaml > remote-verification/lock.sha256 git rev-parse HEAD > remote-verification/source.txt @@ -148,28 +156,46 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build checksum-verified Grok provider image - if: inputs.publish_eval_image + - name: Prepare hosted image build capacity + if: inputs.build_eval_image || inputs.publish_eval_image + run: | + # These preinstalled SDKs are absent from the image build context. + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc + df -h . + test "$(df --output=avail -BG . | tail -1 | tr -dc '0-9')" -ge 35 + - name: Build checksum-verified qualification image + if: inputs.build_eval_image || inputs.publish_eval_image env: SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }} run: | set -euo pipefail image="ghcr.io/paperclipai/paperclip-daytona-runner:qualification-${SOURCE_SHA}-${GITHUB_RUN_ID}" lock_sha="$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" + content_id="$(node cli/node_modules/tsx/dist/cli.mjs tests/runner-e2e/daytona-image-content.ts)" + [[ "$content_id" =~ ^[0-9a-f]{64}$ ]] + printf '%s\n' "$content_id" > remote-verification/content-id.txt + output_args=(--output type=oci,dest=remote-verification/image.oci.tar) + if [[ "${{ inputs.publish_eval_image }}" == true ]]; then + output_args=(--push) + fi docker buildx build --platform linux/amd64 \ --file docker/daytona-runner/Dockerfile \ --build-arg "PAPERCLIP_RUNNER_SOURCE_REVISION=$SOURCE_SHA" \ - --build-arg "PAPERCLIP_RUNNER_CONTENT_ID=qualification-$SOURCE_SHA" \ + --build-arg "PAPERCLIP_RUNNER_CONTENT_ID=$content_id" \ --build-arg "PAPERCLIP_RUNNER_LOCK_SHA256=$lock_sha" \ --cache-from type=registry,ref=ghcr.io/paperclipai/paperclip-daytona-runner:e2e-buildcache-amd64 \ - --tag "$image" --metadata-file remote-verification/image.json --push . + --tag "$image" --metadata-file remote-verification/image.json "${output_args[@]}" . digest="$(jq -r '."containerimage.digest"' remote-verification/image.json)" [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] immutable="${image%:*}@$digest" - docker logout ghcr.io - docker buildx imagetools inspect "$immutable" >/dev/null + if [[ "${{ inputs.publish_eval_image }}" == true ]]; then + docker logout ghcr.io + docker buildx imagetools inspect "$immutable" >/dev/null + else + sha256sum remote-verification/image.oci.tar > remote-verification/image-archive.sha256 + fi echo "$immutable" > remote-verification/image.txt - echo "Image: $immutable" >> "$GITHUB_STEP_SUMMARY" + echo "Image: $immutable; published: ${{ inputs.publish_eval_image }}" >> "$GITHUB_STEP_SUMMARY" - name: Verify repository on hosted Linux if: inputs.verify_source # Leave time for artifact retention within the job deadline. @@ -213,4 +239,5 @@ jobs: with: name: grok-remote-verification-${{ github.run_id }} path: remote-verification/ - retention-days: 7 + compression-level: 0 + retention-days: ${{ inputs.build_eval_image && 1 || 7 }} diff --git a/.gitignore b/.gitignore index 84396923f1f..84a602b84a0 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,9 @@ test-results/ # Retained local lifecycle behavior measurements .lifecycle-baseline/ +# Copilot assets are generated from pinned platform packages during installation. +/packages/paperclip-runner/provider-assets/ +/server/provider-assets/ # Local native Runner test/session state .paperclip-runner-prp/ diff --git a/cli/src/__tests__/company-import-export-e2e.test.ts b/cli/src/__tests__/company-import-export-e2e.test.ts index 79172615e47..d0efacf8e1e 100644 --- a/cli/src/__tests__/company-import-export-e2e.test.ts +++ b/cli/src/__tests__/company-import-export-e2e.test.ts @@ -1,5 +1,6 @@ import { execFile, spawn } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; import net from "node:net"; import os from "node:os"; import path from "node:path"; @@ -302,8 +303,8 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => { const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); const output = { stdout: [] as string[], stderr: [] as string[] }; const child = spawn( - "pnpm", - ["paperclipai", "run", "--config", configPath], + process.execPath, + ["--import", createRequire(import.meta.url).resolve("tsx"), path.join(repoRoot, "cli/src/index.ts"), "run", "--config", configPath], { cwd: repoRoot, env: createServerEnv(configPath, port, tempDb.connectionString, { diff --git a/doc/plans/2026-10-03-copilot-production-readiness.md b/doc/plans/2026-10-03-copilot-production-readiness.md new file mode 100644 index 00000000000..e66ed7a3b6a --- /dev/null +++ b/doc/plans/2026-10-03-copilot-production-readiness.md @@ -0,0 +1,338 @@ +# GitHub Copilot production readiness + +Current release checklist, updated 2026-10-09 (America/Chicago). + +## Current shipping gate: cloud build and v34 qualification + +Frozen **v33 qualification is complete**: all **20/20 Product cells**, all **7/7 protocol cases** (46 assertions), and both ordinary installed local and Daytona canaries pass. These results retain their exact source, fixture, runtime, model and image identities. They do not qualify different runtime bytes. + +The current candidate incorporates master `57e977be72f37dcf31acff64b3e5dcbb64a92cd0`. It preserves mainline Pi/Cursor admission and retains the later Copilot permission, receipt, token and recovery repairs. Copilot remains **1.0.88**, with exact **gpt-5.6-luna** selected for qualification. Copilot **v34** is `sha256:6dde98a9dbadf22f17ca28f133dbe2a65fa199827741190f569704e44bdc20ed`. The changed bound sources require fresh qualification; v33 remains historical release evidence. + +| Current gate | State | +|---|---| +| Current-master integration | Merge conflicts resolved; migration collision uses additive `0320_copilot_defaults.sql`; historical identities retained separately | +| Build and contracts | Full `pnpm build`, recursive typecheck, shared/database builds and token gates pass | +| Focused regressions | 224 transport/recovery cases and 123 saved-token connection cases pass; installer contract and remote fixture checks pass; remaining full-suite validation is pending | +| Cloud image | Build-only OCI artifact mode retains the exact frozen lock, content ID and digest before publication; build, scan and publication pending | +| Live qualification | Freeze source, v34 profile, model, provider pack, case definitions and image; run exact 20 Product cells and seven protocol cases; verify both ordinary installed canaries | +| Shipping | Fresh current-head CI/review, separate code-owner approval, normal merge and shipped-build canary pending | + +No unchanged CI rerun remains available for the old v33 run `37829278179`: attempt 2 interrupted server shard 6 after a runner shutdown signal, without an observed assertion failure. The integrated candidate receives fresh CI. Future Docker images build in the cloud. No v34 live result or production rollout is claimed yet. + +## Completed frozen v33 qualification + +The original application source is `024e1422f9c7b43ff1b083e7d8a25e1969dc4acf`; reviewed application execution is `50d54d78e552869cae154d88ade68063191a547e`; native artifacts are `941fbcb664a885eea713e732edc10ac133c62238`. Daytona's reviewed controller/fixture source is `5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638`, including fixture repair `dc5ebe3f92d405e284b096f5add797864bb0f74e`. Source equivalence proofs retain the selected policies and case definitions; no original grade was relabeled. Copilot **1.0.88**, exact **gpt-5.6-luna**, profile **v33**, and `sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a` identify the native campaign. + +| Gate | Result | +|---|---| +| Runner protocol | **7/7 pass**, 46 assertions, context-before-action and cleanup included | +| Product local | **10/10 pass**, saved-token grants and exact profile/model verified | +| Product Daytona | **10/10 pass**, native process/sandbox retirement independently verified | +| Platform install/startup | Actual installed ARM64, x64 under Rosetta and Linux x64 under Docker emulation pass; native Linux runs pass on Daytona | +| Ordinary user paths | Both fresh normal installed local and Daytona paths pass without qualification overrides: saved personal token, exact model, save/reopen, metadata-only Test, one task/final response, accepted normalized receipt and downloaded artifact | +| Ordinary remote cleanup | Exact 32 downloaded/synced bytes; native commands exited before completion; all **42** owned sandboxes independently absent. Two intentional setup/runtime-test archives were deleted after verifying exact owned identities | +| Image publication | Approved, published and anonymously verified. Tag `copilot-qualification-22fd2a8ae3414eee317e`; index `sha256:65fc4f30c15b3d744534ce3d7240dfbf3d3ff081da6d51180bd89f2802bd6e56`; Linux manifest `sha256:9606c290372884ee74438408e8115088320979c8614a3dbd9d77be2b548a896e`; scan found zero selected credentials across 225,852 files | +| Shipping | Pending current-master reconciliation/new-build qualification, CI/review, code-owner approval, normal merge and shipped canary | + +No automatic behavior retries occurred. The first Daytona attached-command attempt rejected a controller pack before native provider start: extraction omitted seven empty Cursor directories. Image files and Copilot assets matched. Restoring only the verified empty directories fixed the prerequisite; production offline pack validation and the explicit second attempt passed. The original failure remains. Other retained failures include v32 target aliasing, pre-provider startup/admission failures and the first ordinary local file's Markdown escape bytes. The changed hexadecimal local definition passed without relabeling that failure. + +Authenticated account reconciliation leaves **$70.50** within the existing approved window, with no active paid hold. Historical result mismatches and missing-closure compensation keep their full contingencies; infrastructure holds remain. Provider per-run USD stays unknown and GitHub-attributed. No billing controls or budget ceiling changed. The machine-readable record is [2026-10-08-copilot-qualification-v33.json](2026-10-08-copilot-qualification-v33.json). + +## Historical v32 release attempt + +Fresh review identified two release blockers: arbitrary unavailable Copilot models could be saved, and independently decoding transport chunks could corrupt multibyte provider text. Creation, hiring, edits and environment changes now require the existing bounded authenticated metadata probe; the UI accepts only discovered models. The three protocol readers now retain a UTF-8 decoder across chunks. + +Current runtime source is `c049102fba12850b755a962d74e22635c8585724`, profile **v32**, `sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc`, Copilot **1.0.88**, exact **gpt-5.6-luna**. Compared with v31, only the metadata decoder hash, historical revision decoder hash and profile version change in the bound declaration. Earlier results remain historical; they do not qualify changed v32 bytes. + +| Current gate | Result | +|---|---| +| Model adoption | 53 setup UI tests and eight focused API cases pass; rejection prevents create/hire/edit, exact available models remain unchanged | +| Split UTF-8 transport | 117 metadata/profile/installation/input tests and 13 credential-free protocol fixtures pass; Runner, server and UI TypeScript checks and token gates pass | +| Maintained master | The stack retains master `d6df12cef69fcaf2d2fe66a393168931d5b8b4e7` and the merged CI-owned dependency prerequisite #15572 | +| Daytona identity contract | Both image input lists reference the active v32 fixture; all nine exact image contract tests pass | +| Fresh CI and review | All four repaired heads require exact-head CI and clean Greptile review; a separate code-owner approval is required for the runtime PR | +| Frozen package and image | Freeze after review, then build and scan the repaired source. The unpublished v31 image request is superseded; it is not the current release image | +| Live release gate | Verify the frozen packaged profile, exact saved-token model, ordinary local and Daytona paths and independent cleanup before rollout | +| Shipping | Pending qualification, code-owner approval, normal merges and shipped canary proof | + +The refreshed aggregate account reconciliation leaves **$80.36 within the existing approved allowance** with no active hold. All original attempts, infrastructure reservations and a $5 reporting-delay allowance remain retained. Provider USD stays unknown and GitHub-attributed; no budget reset or billing setting change occurred. The current record is `2026-10-08-copilot-qualification-v32.json`. + +## Historical compatibility canary: v31 + +The following is the retained v31 state before the final review repairs. Its local canary and platform evidence remain valid for those exact bytes; v31 publication and remote launch are superseded by v32. + +The normal installed v31 local canary passes on source `d99959f00b62ad2099b5cdaca3f7656dcb88a8e8`, runtime source `4acb084fc5b6805a483bf419c2cdc6de3f7ec13d`, profile `sha256:4a22e4c50fd213c79cd02fd3c5369aef2256ac719762c62f5b70c691cd47e76b`, Copilot 1.0.88 and exact gpt-5.6-luna. Saved-token metadata, UI model selection, save/reopen, one final response, accepted normalized receipt, 30 downloaded bytes and independent process/scratch cleanup pass without qualification overrides. All three installed platform smokes pass; Linux is emulated locally pending the native Daytona canary. + +The full v30 campaign below remains immutable historical evidence. Only the saved-prompt compatibility parser and profile admission differ among bound runtime inputs. The current parser reads all 20 captured Product contexts identically and passes the focused recovery checks. No full 20-cell v31 live campaign is claimed. + +| Current gate | Result | +|---|---| +| Dependency prerequisite | PR #15572 merged at `941a3fa991aeb97eb1ac390c65b7973b5f6de1ad`; CI-owned lock, current-head CI green, fresh Greptile 5/5 | +| Setup and model discovery | Normal installed local journey passes; first-token refresh and native Runner admission regressions pass with 155 affected UI cases | +| Attached-command review | Both retained live traces prove finish before independent child exit and waiting read after finish; the stronger oracle and regressions enforce both boundaries | +| Cleanup and diagnostics review | Smoke-owned providers ignoring SIGTERM receive bounded SIGKILL with confirmed exit; saved remote diagnostics retain only closed failure codes | +| Focused qualification checks | 214 Product regressions pass, one existing skip; seven smoke cleanup tests and Product typecheck pass | +| Exact v31 image | Credential-free final image is built and scanned; exact public publication approval is pending | +| Ordinary v31 Daytona canary | Next live gate after exact image publication; normal installed plugin is ready | +| Final CI / review / rollout | Remaining four PRs require current-head CI and fresh clean review; production rollout is pending | + +The approved budget has $30.68 remaining, with no active reservation. Provider USD remains unknown and attributed to GitHub. No budget reset or billing setting change occurred. The current machine-readable record is `2026-10-08-copilot-qualification-v31.json`. + +## Historical completed qualification: v30 + +Master advanced to `bf9dbd18a8d9a539f79ca2e3ccb10b6df31c6334` on +2026-10-08. Its two fixes merged cleanly into the integration: resumed turns +receive an explicit instruction to obtain fresh accepted completion evidence, +and fresh remote Codex runs resolve compatible models before launch. Copilot +continues to require its exact authenticated model; Codex substitution does not +apply to ACPX providers. + +The bound native-execution source changed, so Copilot profile **v30** is newly +minted as `sha256:e21c4a43a937ae84e94726ef93227e8b931fa5547876ff0c5ecff304656d81e9`. +TypeScript/Rust admission and source hashes match. Other provider qualifications +are unchanged. Copilot **1.0.88** and exact **gpt-5.6-luna** remain pinned. +The v29 live results below remain historical evidence; they do not qualify v30. +V29 paid launch and image publication are closed. Its pending image request is +withdrawn; no v29 image was published. + +Frozen source is `c4b5d40bcfd51e6cacccc1ea1343305b05735641`; protocol +definitions are `b0ea2ebd2cc07dda180f61362a68662abb96c7d6`. Source hashes, exact +model, all 20 Product definitions and all three platform artifacts are retained +in the v30 freeze. No v29 launch or publication is allowed. + +| Current v30 gate | Result | +|---|---| +| Runner protocol | **7/7 pass**, 46 assertions and cleanup; one current attempt per case | +| Protocol launch prerequisite | The copied helper first selected the historical roster. Built-runtime admission rejected it before a provider session or prompt; the failure is retained and only its unused $0.50 reservation was released. Current helpers check the exact roster/config before reserving. | +| Product local / Daytona | **20/20 pass: 10 local and 10 native Daytona**, saved-token binding, exact model/profile and independent cleanup; no automatic behavior retries | +| Build / static checks | Full build, recursive typecheck, token gates, Product typecheck and exact Copilot model isolation pass | +| Product offline | **2,349 Vitest cases pass**, two existing skips, plus maintained Node contracts | +| Runner offline | All selected checks have passing evidence: 3,003 Vitest cases, 677 Rust assertions and 1,942 required real-HTTP assertions. Original Mac invocation retains two credential-home setup failures; unchanged isolated Linux runtime-host suite passes all 44 cases with cleanup. Original cause remains unconfirmed; no single all-green `check:all` invocation is claimed. | +| Installed runtime / metadata | Native macOS ARM64, x64 under Rosetta and Linux x64 under Docker emulation initialize and exit; authenticated packaged metadata confirms 19 models and exact Luna with no prompt. Native Daytona Product and ordinary installed-runtime proof pass. | +| Full repository suite | Current-source offline container full build passes; the full local root invocation retains eight general-server failures. Current-head CI is the release gate; a single all-green local full-suite invocation is not claimed. | +| Normal installation / canaries | Fresh 18-package ordinary npm installation, startup, personal token setup, exact model, save/reopen metadata Test and local task canary pass. Downloaded bytes, accepted receipt, one final response and cleanup are independently verified. Daytona also passes: independently downloaded bytes, accepted receipt, one final response, native command exit and all 13 owned sandboxes absent. The manual metadata Test archive was intentionally retained for diagnostics and then deleted. | +| Image publication | Full final image scan: 225,852 regular files, 18,360,132,191 bytes, zero selected credential matches. Exact OCI index `sha256:b0f81cd040f811b8ae7eeedd326e36a1c697de5116cd91db84694f21526de7d1` was explicitly approved, published, and anonymously verified; Linux x64 manifest is `sha256:2ccfe999e7794ea1577e581840743a466663143c4303a518e06b3f1e0faa58fb`. | +| Review / CI / rollout | The four runtime, regression, setup and qualification PRs are published as #15560–#15563. Current-head CI, final clean review and rollout are tracked above. | + +The ten Daytona cells and ordinary installed Daytona canary are complete. +The remaining critical path is current-master compatibility, current-head CI, +Greptile review and shipping. Master advanced to `2f0c485dec7ab036f0abf7d90d09ca5f84df26a7`: its saved-prompt recovery fix changes one bound runtime input, and its Slack migration takes number 0318. The review stack preserves that recovery fix and renumbers Copilot to 0319. Profile v31 binds the changed parser; the completed v30 evidence remains immutable. Its normal installed local canary now passes as recorded above; the full v30 campaign is not reclassified as v31. +Remote workloads use the maintained local controller; no separate source +checkout or credential-file upload is needed. + +Account reconciliation retains all historical attempts, infrastructure holds, +other-provider exposure and a **$5 reporting-delay allowance**. GitHub reports +153.34 included credits ($1.53 displayed equivalent), with additional paid usage +disabled. Provider per-run USD remains unknown. The approved window now has +**31.18 remaining**, with no active paid reservation. No budget +reset or billing change was performed; every new attempt requires a reservation. + +## Historical v29 current-master replay + +Current master is frozen at `5717523b9ea7a2d76efbd6eb73414de9c06c6f96`. +The reviewed merge preserves Dot setup, decision-model routes, credential cleanup, +and other provider qualification. Current runtime source is +`a0e23886e6bd532004252780dfd009b9b449ff0e`, profile **v29**, +`sha256:ce118b64cd7a50a93c3ff538b596327bd8e8d172a1bc0bda5d3334948d5d8eb7`, +Copilot **1.0.88**, exact **gpt-5.6-luna**. Runner definitions are frozen at +`9fb438caa8e4389a53217b7e636b8a15a6d5da26`. +Qualification source is `2003915896ee56eb42a74fddf1ba479d1855549a`. +Its five changes from the runtime revision affect only the warm fixture, +fixture tests, and documentation; retained equivalence proofs keep the runtime +and the other 18 Product cell contracts unchanged. + +| Current v29 gate | Retained result | +|---|---| +| Runner protocol | **7/7 pass**, 46 assertions and cleanup, one attempt per case | +| Local Product workflows | **10/10 pass**, saved-token binding, exact native identity and complete cleanup | +| Local warm continuation | Changed-fixture comparison passes all three turns and cleanup; original failure remains retained: the model wrote literal backslash-plus-n and verified its own wrong bytes | +| Warm fixture repair | Prompts explicitly define LF byte 10 and independent byte verification; the original failure and all byte/process/workspace assertions remain; only the two warm cell contracts change | +| Daytona Product workflows | **0/10 current passes**; exact image publication approval pending; maintained cells are prepared to run from a local controller against native Daytona | +| Product support | **2,335 Vitest tests and 128 Node contracts pass**, 2 existing skips; typecheck and exact warm selector discovery pass | +| Runner offline | **3,001 Vitest tests pass**, 11 existing skips; Rust, replay, conformance, protocol, kernel and real-HTTP authority checks pass | +| Build / typecheck / token gates | Pass on the frozen runtime source | +| Installed platform smoke | Actual ARM64 pass; actual x64 installation under Rosetta and actual Linux x64 installation under local Docker emulation pass; native Daytona still pending | +| Full application suite | Original general-server run: **16,807 pass, 8 fail, 69 skip**. Corrected umask/reaper checks pass 9 tests; unchanged native Linux port-ownership and real 40,000-file Git stress checks pass. All three adapter timeout cases pass unchanged on native Linux, including same-version Claude MCP isolation. UI passes **7,810 tests**, CLI passes **517 tests**; a launcher teardown leak is repaired and independently verified. The deterministic bind-readiness fixture and complete Copilot catalog assertion pass. All **155 maintained route suites pass (2,979 assertions)** with descendant cleanup. All 14 remaining workspace projects now have passing evidence (**1,929 cases, 7 existing skips**). A Cursor fresh-lease fixture leaked the installed host CLI; an isolated PATH and execution guard fix the fixture while preserving all 18 assertions. Across maintained coverage, **32,689 cases have passing evidence, with 81 existing skips**. This combines original passes and explicit cause-repaired/native replays; the original failing invocation is unchanged, and a fresh single-invocation root pass is not claimed. Original failures remain retained; no full-root pass is claimed. | +| Ordinary current installation / UI / local canary | Fresh 18-package npm install, first-use personal token connection, exact model selection, save/reopen metadata Test and local task completion pass without qualification overrides; downloaded bytes, accepted receipt, one final response, native exit and scratch cleanup independently verified | +| Ordinary current Daytona canary | Pending | +| Current-head CI / review / rollout | Four unpublished review branches contain **99, 56, 63 and 72 files**. Runtime and regression branches pass their focused checks and Runner typecheck. Saved-token setup/admission passes **1,380 focused tests**, recursive typecheck and token gates; its additive catalog follow-up passes all 16 schema tests. The assembled qualification branch passes recursive and Product typechecks. Live Daytona qualification, current-head CI, final review and rollout remain pending. | + +The v29 runtime repair retires a typed ACP native-agent disconnect before turn +finalization while retaining independent process-exit proof for cleanup. +Controller-only disconnection preserves pending input; other providers are unchanged. +The deterministic new regression fails on v28. Historical v28 ten local and seven +protocol passes remain evidence, with closed launch gates. Its full application +suite retained five failures, including embedded Postgres host-resource failures, +fixture timeouts and a Git stress timeout; no failure was discarded or relabeled. + +The v29 warm failure is model behavior, not a transport bug: the exact native +command digest matches the recorded command, whose Python string contains bytes +`5c 6e` rather than LF `0a`. The model's own two assertions checked the same wrong +bytes. The changed fixture comparison keeps runtime/model/permission policy +frozen and verifies that the other 18 Product cell contracts and all protocol +definitions are unchanged. Copilot's migration is `0318_copilot_defaults.sql`. + +## Historical qualified source + +- Master baseline: `ceabc3bc880676c49eee907a8d736f961a1358eb`. +- Integrated source: `8e35f57831fd449a777d49d2686496d543f37cbf`. +- Application / Runner runtime source: `3073c062d47891cc275072de4eaaf049f5837839`. +- Copilot CLI: **1.0.88**; exact model: **gpt-5.6-luna**. +- Copilot profile: **v26**, `sha256:1a2a0c35764105e9a85112d05187fee0862af23eeb82a75569b5198e52eaba4f`. +- Runner eval definitions: `c3c47a3c0d9c207e65713cd05be97cd02d811d89`. +- The 19 later source paths change Product fixtures only. Application / Runner inputs and all 20 Product definition hashes remain unchanged. Source equivalence and each original result hash were verified before admission changed. +- Other provider qualification and the user's dirty dependency lock remain unchanged. + +## Historical v26 gates + +| Gate | Result | +|---|---| +| Product workflows | **20/20 pass**, saved-token setup and native identity verified; all passing attempts have complete cleanup | +| Runner protocol | **7/7 pass**, 46 assertions, complete cleanup | +| macOS ARM64 installation | Actual installed runtime smoke passes; authenticated discovery includes the exact model without a prompt | +| macOS x64 installation | Actual installed x64 runtime smoke passes under Rosetta; this does not claim a physical Intel-host test | +| Linux x64 installation | Actual installed runtime smoke passes on the native private Daytona controller; authenticated discovery verifies the exact model | +| Qualified runtime root checks | Recursive typecheck, build and token gates pass; 32,105 root tests pass with 107 existing skips | +| Runner checks | 135 Node contracts, 2,964 Vitest tests and 659 Rust tests pass; matching-toolchain doctest compilation passes; 11 Vitest skips and 2 Rust ignores retained | +| Latest Product fixture checks | 2,333 tests pass with 2 existing skips; Product typecheck passes; 152 focused native Linux tests pass | +| Ordinary release admission / packaging | Normal admission and local / Daytona assets promoted; full build, recursive typecheck, token gates and focused admission / packaging regressions pass | +| Fresh npm installation and saved-token UI journey | Fresh 18-package npm consumer passes installation scripts, installed-server import and startup; actual first-run token connection, explicit model selection, save / refresh and metadata test pass; corrected Apps form retested | +| Ordinary local and Daytona canaries | Local passes on ordinary npm source `646408381eca28f795b1e7ede655572154648309`: one final response, accepted completion, exit 0 and owned sidecar / scratch cleanup; Daytona also passes on that source: one final response, accepted completion, exit 0, finalized workspace and automatic task-sandbox cleanup; metadata-probe archive removed by explicit owned test cleanup; no qualification overrides | +| Focused PR stack / current-head CI / review | Pending; fewer than 100 files per PR; exclude the manual qualification lock delta | +| Broader rollout | Pending canary and review | + +The final UI suite passes **7,771 tests in 699 files** after correcting one stale +assertion that still expected Copilot to be disabled. First-run inspection found +and repaired a missing Copilot setup choice. The Apps form showed duplicated +access controls; its corrected single form was verified in the installed UI. +The normal release root suite retained five failed files: three embedded Postgres +startup failures, a macOS Unicode descriptor-path failure, and an OAuth loopback +fixture failure. Current-master serial verification passes 485 assertions in four +files after official Postgres native postinstall and a Unicode locale repair; +the custom-image file still hits this host's shared-memory limit. These failures +remain retained; current-master full coverage is required before handoff. + +## Retained evidence and failures + +Evidence is retained under `/tmp/copilot-production-campaign-v26`. The current +completion record is `current-product-campaign-completion-proof.json`; source +and artifact identities are in `frozen-source.json` and +`qualified-source-equivalence-death-key.json`. All original failed attempts, +assertions, result hashes and cleanup grades remain preserved. No failed grade +was relabeled as passing. + +The earlier Daytona attached-command receipt failure is resolved in the pinned +runtime and now passes live. Product fixture repairs separately corrected browser +task-ID binding, installed asset setup, validated process-group peer retention, +context-read permission origins, exact Linux process admission, immutable +boot/start-tick ownership and cleanup against that captured process birth. +Provider-death local attempt 3 passed its behavior assertions but failed cleanup; +its corrected attempt 4 passes both. Regression evidence retains every failure. + +Daytona provider-death attempt 1 failed before the behavior began because startup +proc metadata was rejected. Its precise rejected record was not retained and its +cause remains unconfirmed. Subsequent inspection, including roughly 7,000 proc +reads, did not reproduce the rejection. The exact owned disposable sandbox was +deleted and absence verified. One bounded fresh-environment attempt with identical +source and assertions passes all behavior and cleanup checks. No further retry +was made. + +Host sleep interrupted two unchanged timing cases; exact power events and failed +attempts were retained. Both cases pass under process-scoped sleep protection +with unchanged assertions and deadlines. Native shared-memory exhaustion affected +the parallel database tests; the exact serial replay passes all 47 files and 166 +tests. No foreign process, IPC segment or system setting was changed. + +## Setup and release scope + +Use first-run GitHub Copilot setup or Connectors → GitHub → Connect Copilot. The AI provider is +`github`, authentication is `api_key`, and the selected saved credential is +injected as `COPILOT_GITHUB_TOKEN`. Repository / MCP connections and grants stay +separate. No ambient GitHub credentials are adopted. Verification uses a bounded +metadata-only probe in the selected execution environment; it sends no model +prompt and reports auth, entitlement, installation and unavailable-model errors +separately. Saving and reopening must preserve ACPX / Copilot / the exact model. + +Core local and Daytona workflows are completion, editing, questions, plans, +controller restart, permission denial, attached-command settlement, warm +continuation, Stop with pending permission and provider death. Native steering, +native plan mode, detached commands and additional native event presentation +remain outside this release. Session-scoped notices cannot establish completion, +artifacts or charges. GitHub billing attribution remains; unknown USD cost is +never converted to measured zero-dollar usage. + +## Budget and owned resources + +The renewed **$100** ceiling retains all prior spending and failed reservations. +The conservative remaining allowance is **$29.55** after independently verifying +the saved token's account and its current-month aggregate included-credit usage +of **153.34 included AI credits ($1.53 displayed USD equivalent)**, with additional paid usage disabled. The prior $1.16 observation remains retained; its $0.37 increase fits the existing reporting-delay allowance and does not release reservations. Reconciliation preserves every +original reservation and infrastructure hold and retains $5 for reporting delay. +Historical ordinary canary reservations and the current ordinary local reservation are settled. +No paid run is active. Native provider USD remains unknown. Reserve ordinary +canaries before sending model prompts; do not reset the ledger or billing settings. +The saved token expires **January 4, 2027**, America/Chicago. + +The human's exact approval authorized public image +`ghcr.io/paperclipai/paperclip-daytona-runner:copilot-qualification-70e2d894a067e7891ca7`, +index `sha256:bda4b2793308fe394b8264dafe9d1f226036b5c9fe48d2386435b1950861415c`, +source archive `sha256:78da5a0a06eb813a59edc738537766ab9a321d9877010b5f5d04190cdce2ca82` +and the two selected credentials on the private controller +`copilot-v26-native-private-controller-01`. Publication, anonymous retrieval and +private handoff completed. Credentials are mode 0600. Later credential-free +Product-only increments used the same approved private destination. Delete only +owned resources after their evidence has been collected. + +## Remaining critical path + +1. Require fresh current-head CI. Maintained offline coverage has passing evidence for all selected cases; retain every original failure, environment diagnosis and explicit repaired/native comparison. +2. Publish the exact credential-free image after approval, then run all ten maintained Daytona Product cells from the local harness, one at a time. Verify native Daytona installation/discovery and the ordinary Daytona canary. +3. Publish the current qualification record, prepare focused PRs with fewer than 100 files each, and require current-head CI and review before broader rollout. + +Rollback disables new Copilot admission and retains safe cancellation and recovery +for existing runs. Production readiness requires the ordinary shipped-user path, +not candidate test results alone. + +The current credential-free v29 image is built and scanned locally: +`ghcr.io/paperclipai/paperclip-daytona-runner:copilot-qualification-071a851bc57f464c32ec`, +index `sha256:f39a27cc2fe7a83c2c07587fdc367df3307e13259ac50b5c47638559dab92008`. +It has not been published. Automatic approval review rejected publication because +earlier authorization covered different bytes. Exact current image publication +approval is pending. The private controller returned 404 at +`2026-10-08T07:32:25Z`; its disappearance cause is unconfirmed. The proposed +current source archive, runtime delta, credential-file transfer and bootstrap were +withdrawn before execution. No new source checkout or credential file is uploaded +to a separate controller. The maintained local harness will drive disposable native +Daytona task sandboxes using normal saved connections. Local evidence and the +mode-0600 two-key credential file remain retained. + +Normal Daytona plugin packaging and isolated installation pass in the fresh npm +application. The existing Environments setting was enabled through its ordinary +UI. The image-pinned Daytona setup form is prepared; no environment has yet been +created or tested. No remote model prompt has been sent for this current build. + +The current base still matches public `origin/master`, independently checked on +2026-10-08. The review partition has **99, 56, 63 and 72 files**, respectively. Four +verified fixture-only paths were added: the complete provider catalog assertion, +CLI server ownership, bind readiness and fresh Cursor lease isolation. Every candidate path is assigned once. The user's dependency-lock changes remain +uncommitted and byte-identical. Neither a full-suite pass nor production readiness +is claimed while these gates remain open. + +The saved-token review branch now ends at +`be5735cffd6d67cb75ea0567ad16d28d6e489b59`. Its schema follow-up is +`f36e035388811538a3eb91bb5773aee760c7a3b1`; the preceding product commit is +`f61f81b084b4e4bd282609d51c20e54dc5680c4d`. The regression/evidence commit is +`193ef12a7a3c4bd7ecc709dc910e2dd3f3adb498`. These review-only fixture changes and the setup-document correction +do not change the frozen Copilot runtime, profile, model or Product definitions. +The assembled `codex/copilot-qualification-v29` branch passes recursive +typecheck and the Product definition typecheck. It remains unpublished while +current live Daytona qualification, fresh current-head CI and review are open. + +The qualification review includes initial commit +`442da9569cb349862d384728ae9c093acfb0bcac` and Cursor fixture follow-up +`abd8fb279ed171838761de1873c2e1b44253db45`. The follow-up changes test setup +only: every test body and assertion stays byte-identical, and Cursor production +code and qualification remain unchanged. Four template-complete PR bodies are +prepared locally, with CI and review boxes left unchecked. Nothing has been +pushed or published. Current credentials were scanned against all changed source +bytes; no matches were found. diff --git a/doc/plans/2026-10-03-copilot-qualification-v19.json b/doc/plans/2026-10-03-copilot-qualification-v19.json new file mode 100644 index 00000000000..14420223dbe --- /dev/null +++ b/doc/plans/2026-10-03-copilot-qualification-v19.json @@ -0,0 +1,1055 @@ +{ + "schema": "paperclip.copilot.qualification-progress/v1", + "status": "superseded-by-v20-current-grant-repair", + "productionAdmission": "gated", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "productDefinitionsRevision": "421cbda22d9e5fecfe9e64df296df26e9cf9335c", + "evalsRevision": "2528ec812771e2fb432be6a516cf82c8bb678a59", + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "profileVersion": 19, + "profileDigest": "sha256:c2825f706f839071ce5aa9ddc2a058d785a55644964ff9c2ba878ac9ccc66fef", + "runnerPackageSha256": "sha256:3c58ab0db7f1e8d4a6578502f60638a8df6aeb73cc4008ef1352dc7b396f0ccd", + "runnerdSha256": "sha256:3a91a4a7142a34932ae0ea50971b08fbf8c3441da5ad111042598da27d18478b", + "runnerCliSha256": "sha256:bed6afbd86ab25c4bf90886a162e2b8a3e192005c77f50b597aee48a619dc3eb", + "providerPackDigest": "sha256:fb17098f93bbb888e605ee84201f4f6f0e6920dc81b1d9cd293f343876b959ed", + "resolvedLockSha256": "sha256:83b5b1848a9e5c8bee2608732df317caab338e6f22a343bc8029fa86f1acbeab", + "daytonaImage": { + "tag": "ghcr.io/paperclipai/paperclip-daytona-runner:copilot-qualification-434d749103ab3ae4058e", + "digest": "sha256:6ab9b0166580020cc16240bdcf27067459f7720cfdcb4f25bacd5fb6fca77be0", + "contentId": "434d749103ab3ae4058ef1600795ce3e9ff1a78b7049c556c0d41b3fc3af1e93", + "publication": "published-anonymous-pull-verified", + "anonymousPull": true, + "runtimeCredentialIncluded": false + }, + "productCells": [ + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "pending-current-definitions", + "definitionVersion": 17 + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "pending-current-definitions", + "definitionVersion": 17 + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "pending-current-definitions", + "definitionVersion": 14 + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "pending-current-definitions", + "definitionVersion": 14 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "pending-current-definitions", + "definitionVersion": 19 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "pending-current-definitions", + "definitionVersion": 19 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "pending-current-definitions", + "definitionVersion": 19 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "pending-current-definitions", + "definitionVersion": 19 + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.local.warm-three-turn", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.daytona.warm-three-turn", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.hello-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.question-resume-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.plan-approve-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.structured-question-restart-resume", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.file-edit-validate", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.hello-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.question-resume-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.plan-approve-complete", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.structured-question-restart-resume", + "status": "pending-current-definitions", + "definitionVersion": null + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.file-edit-validate", + "status": "pending-current-definitions", + "definitionVersion": null + } + ], + "runnerProtocol": [ + { + "case": "get-task-context", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "context-before-action", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "create-task-document", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "finish-task", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "request-human-confirmation", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "workflow-context-document-progress", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + }, + { + "case": "workflow-governed-wait", + "disposition": "passed", + "retries": 0, + "providerReportedUsd": null + } + ], + "runnerProtocolSummarySha256": "sha256:2cd60d19f9f05335732b80bcef57d214482b02f73fbf63d595f45d8d4816cb03", + "installedRuntimeSmokes": [ + { + "platform": "darwin", + "architecture": "arm64", + "translatedIntel": false, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:fb17098f93bbb888e605ee84201f4f6f0e6920dc81b1d9cd293f343876b959ed", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:ff77894fffe5089267d605b0254442b3b6b7e0ef01fd6756f19fec27be256c45" + }, + { + "platform": "darwin", + "architecture": "x64", + "translatedIntel": true, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:29a0a71712e2219bc7240d06106bae21b739a5772172743ff3b8f8285ab42dac", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:9e1c6cc23c0db0ae534a95a346bd5a918527778b4e64af7a33c7ef93fc4e6b38", + "stagedRunnerDaemonArchitecture": "x64", + "limitation": "Complete x64 pack executed under Rosetta; no physical Intel hardware smoke." + }, + { + "platform": "linux", + "architecture": "x64", + "translatedIntel": false, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:42a0cdfea607e68f1e1bd20b27a54507d5727c6417f6d8df553a9f41f882d533", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:d723290d88381963094f978733f204dac339e9cf9bc5eec62b9c3c9bed9b7729" + } + ], + "budget": { + "originalTotalCeilingUsd": 100, + "originalCopilotAllocationUsd": 25, + "latestUserApprovedTotalUsd": 100, + "priorConservativeSharedExposureUsd": 47.080380624, + "copilotInferenceExposureBoundUsd": 29.0, + "combinedConservativeExposureBoundUsd": 82.580380624, + "nativeProviderReportedUsd": null, + "creditsPricedAsMeasuredZero": false, + "pastReservationsReset": false, + "futureDaytonaInfrastructureReservationUsd": 0.5, + "pricingSource": "https://www.daytona.io/pricing", + "pricingVerifiedAt": "2026-10-05T02:10:57.482245+00:00", + "combinedConservativeExposureUpperBoundUsd": 84.939080624, + "inferenceReservationsRetainedUsd": 33.5, + "daytonaInfrastructureBoundUsd": 4.3587, + "reconciliationEvidenceSha256": "sha256:0391f3abd84f83cf7cdcdcb8fdee1e1281fd2fce6f3b635fbe03617ad0d106f6" + }, + "fullRepositoryTestInvocation": "failed; no clean full invocation", + "rollout": { + "ordinaryInstallationWithoutOverrides": "pending", + "currentHeadCiAndReview": "pending", + "canary": "pending" + }, + "evidencePolicy": "Original failures are retained. Earlier-definition passes are not final frozen-definition passes. Native USD is unknown; conservative exposure is not measured spend. Local raw evidence is retained separately from this safe summary.", + "historicalProductResults": [ + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "90b14b00b7a56d48a6d203545d04753750f864d7", + "definitionHash": "dcb4ee9b775ee3c739d5b5ddc1ccdafcd5e7ab6325e8d6f8a1e6a4d3d65a0de9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T22-19-31-840Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:85e83de987404dde5d474d4de9d405ca1b63d8fcdfefef969ead5a48f69cad49", + "evidenceManifestSha256": "sha256:61f2aa46af4abb960e69e0696bd0b63d88d96ac0e6ca7eab4c22df1c33db7826", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-denial-01" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "8fba5cca4ae7d2d0d7346d76c05f43699f07049c", + "definitionHash": "044e32cb8124e8f7a0fb8dd8bb168d807f8d0b5c1668456de96b6ee6184e0abc", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T22-34-57-911Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:e7741648c998c7084eff74dffcfc0560e95372a8015808c9c74e99f48787d42c", + "evidenceManifestSha256": "sha256:7d9d9d4ac8ea264a3a7d9799f5a0d1869c6adaaa04e3ea7b51de83200d6bbc31", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-denial-02" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "2dd62ad52b8b4348d286c59e11e36144305bca6c", + "definitionHash": "ece61a911483026b573ac3e6eda89fd7943e9d481fb3dbbaa11435ec9e1f662a", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T22-42-14-193Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:ab63b5b7cf485802af3239aa4688e8f4f3ab926247fa6f71771bb0f20426cba2", + "evidenceManifestSha256": "sha256:c55cd754424c9d19ef5f43ef2d72bb88085d0864c34741e91c7cd47125c0ada3", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-denial-03" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "7dec03762eb8201f721b7a1c5116861c8ccd23b4", + "definitionHash": "9ad440a7514c33a6b1726915c11f18ca9f1b441aa4344c718ac993337ff7b938", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-00-26-289Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:1ee52613f6b3e51d878c0af426f1e387b7627434ce04126293ce235dbf2a0687", + "evidenceManifestSha256": "sha256:533c6010580251f699c85f595eacdfc2de03cdceb6d8fce991ef1b8da750d893", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-denial-04" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "failed", + "paperclipSourceRevision": "7dec03762eb8201f721b7a1c5116861c8ccd23b4", + "definitionHash": "9ad440a7514c33a6b1726915c11f18ca9f1b441aa4344c718ac993337ff7b938", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-03-48-015Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:6bccec5ced26d6872ec78b973ed56ac37863a983b4368a7992265fceed7d8135", + "evidenceManifestSha256": "sha256:d16035d4bde265308cba7d3a2f897eb23e79f8eeaa3620483fbd59dda7b55e52", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-attached-01" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "paperclipSourceRevision": "7f6238747165ba2b420fff48ec3a71f6ca594f21", + "definitionHash": "bd4dad6fe54ba18cb380667faa770e10d9add836683d58341f7425aca94a0ff9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-12-02-324Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:d0b096ce473b1802ea227a8c19d5885f61b29215fa8264b88310a34424a6311c", + "evidenceManifestSha256": "sha256:6fb3312bedce96bfb91cc6e5e60ef2c29880fe835eb5eca7a5ce4aaf479b1211", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-attached-02" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "passed", + "paperclipSourceRevision": "7f6238747165ba2b420fff48ec3a71f6ca594f21", + "definitionHash": "bd4dad6fe54ba18cb380667faa770e10d9add836683d58341f7425aca94a0ff9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-13-32-999Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:5e2e17b3a284d40c2209d0c2c90fc1a2cd32987a1b070d0857ca4d5c9d83ea75", + "evidenceManifestSha256": "sha256:dcca4ba5ee6563cb93ca7e0527004aaea575c979eedfc3c91d001406cc5d9681", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-denial-05" + }, + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "failed", + "paperclipSourceRevision": "7f6238747165ba2b420fff48ec3a71f6ca594f21", + "definitionHash": "f3658b3294236a8d9abfdc0698cb4d9efc5547bcfc0aee9ebd00899f0b0bdf48", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-14-48-559Z/native-active-stop/runner-acpx-copilot/local/pending-permission-stop/attempt-1/result.json", + "resultSha256": "sha256:643e73eae0acddbaedca071f5b78e6ec45bc8a561061b921ea726e018e2ce7e3", + "evidenceManifestSha256": "sha256:7d0a13723f075081148636086ab922cfc5fa39c7addda342bfe19b23e71d1079", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-stop-01" + }, + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "passed", + "paperclipSourceRevision": "3f1d303c8ff41ea5ffdc7ffda9ec3613476d791c", + "definitionHash": "b56fcb4d86b4c8aa280a3f619abb0091b2d18e51a4ecd8d3c33c19aed3302f94", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 8, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-26-04-211Z/native-active-stop/runner-acpx-copilot/local/pending-permission-stop/attempt-1/result.json", + "resultSha256": "sha256:6a2168027038b06e81c599700fa7d4febb82218a8218afa1b43ce0d2aa6ff8e8", + "evidenceManifestSha256": "sha256:5092c36c2d3e5e3338a99857066f661d456d7a9f90377b0fcfc5032e238f7561", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-stop-02" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "3f1d303c8ff41ea5ffdc7ffda9ec3613476d791c", + "definitionHash": "746e79093aaa137ac3038a7d9ee5a7f3ffe694dcf13f8c2c884010d1465ae96d", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-03T23-27-25-491Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:18fbd3a98db9dc14a8ff63cd2cf3b33bd4350d2cd75516443a96a731059112a7", + "evidenceManifestSha256": "sha256:11a4c40b9a12f6466568983009274cbeff80c03de6ceebec2c0138a353f95906", + "providerReportedUsd": null, + "reservationId": "copilot-v15-integration-local-death-01" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "790ff2d0bb71fce9adb66f35d9847d57d3f7e88d", + "definitionHash": "746e79093aaa137ac3038a7d9ee5a7f3ffe694dcf13f8c2c884010d1465ae96d", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T00-02-45-977Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:3742a2a2274d42d6b06d25e590a17a6878f818ad667cd8cef66d5a68afe02a10", + "evidenceManifestSha256": "sha256:dbe677444b58993478dea4be416295b1383b8fb276dcb7782360d3c68a81a4f6", + "providerReportedUsd": null, + "reservationId": "copilot-v16-local-death-01" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "e00fe7ba5952533f6036f7abb883d110992889f8", + "definitionHash": "746e79093aaa137ac3038a7d9ee5a7f3ffe694dcf13f8c2c884010d1465ae96d", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T00-21-23-068Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:b4baaaa26bc94bbfab2576b5208c9832d962aaca2b949d9d34fa7850c19ae8cc", + "evidenceManifestSha256": "sha256:95967c7393849d6431152bbfb23b07064c9d4ea947843c89a2b9abad068a8fd0", + "providerReportedUsd": null, + "reservationId": "copilot-v17-local-death-01" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "8bdf8358270fb8c8490ecfa003018bd09838d0f5", + "definitionHash": "fee2bd21509715d05fc4feac3f2e202ea3939bf909fc1031c65c425429847b61", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T00-26-56-154Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:7c34e29a6d3d676b6e8e69a5614fda5e97de46dba9a4e75c3432d3839d88ff98", + "evidenceManifestSha256": "sha256:30056dc4b37de80f532ed6b07756ba066b79d6b3201b9e95677aff14e55fafd1", + "providerReportedUsd": null, + "reservationId": "copilot-v17-local-death-02" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "221d65ed6db340df1cfcb7c704eaab11c024f11b", + "definitionHash": "fee2bd21509715d05fc4feac3f2e202ea3939bf909fc1031c65c425429847b61", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T00-55-48-647Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:4d4f9a95b75de7c495f9b2ea1923d452f2e6c31805a1befcb87004baea3ace13", + "evidenceManifestSha256": "sha256:923c425da23b56bc8e2ec81f23b15cc195fb57b214cf267de2d65a8030841321", + "providerReportedUsd": null, + "reservationId": "v18-provider-death-local-01" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "300463538363dcff3c06e765847fc57e16f652e2", + "definitionHash": "f83100ccdae9a9726853346f6f34c0f8fec595f2a3c6b5059542ed91af76b22d", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-01-43-714Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:bb0ad8704a22cf14a7d0bb7d743e9200fa73c1069681811ed87ea80e4d980430", + "evidenceManifestSha256": "sha256:5483ee75af5c14421765d092c2d6eb07047652e1ec253618809c40d59eccc1ca", + "providerReportedUsd": null, + "reservationId": "v18-provider-death-local-02" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "definitionHash": "f83100ccdae9a9726853346f6f34c0f8fec595f2a3c6b5059542ed91af76b22d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 1, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-39-27-516Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:eb42e27681f90e6164b831df5419048f7bc17492345eb03f666bdd670295777d", + "evidenceManifestSha256": "sha256:29dc69828987e3fc1a4365e574370e05794b79a85a03d56d8eec799994ef974c", + "providerReportedUsd": null, + "reservationId": "v19-local-provider-death" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "failed", + "paperclipSourceRevision": "e91ca6e389222901474b1a6ac8d701c33617c4f0", + "definitionHash": "126c626809f39d674ae5feaeac095e8a3c9c02841b7d9e9b1b629f5e294fa731", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-46-41-594Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:f85bb5c18fa4b15ec6f127d677e90edc22f0c60c113b576fdf4a0729fdf16426", + "evidenceManifestSha256": "sha256:988acfb2c210108721916c80fe92ef3b109f98293011305a34aa9b94a820d306", + "providerReportedUsd": null, + "reservationId": "v19-local-provider-death-status-label" + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "passed", + "paperclipSourceRevision": "8fd0e05451ed3531855884fd4375ef3564620c2f", + "definitionHash": "080baff12a04bdf5a129f5a8060f57721d11254d7a29e878e7602623eb9e07f4", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 8, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-53-10-210Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1/result.json", + "resultSha256": "sha256:531ddd5fc8928f4174e56914b7869a836ce61f16ea92a9eb16562f550244ce37", + "evidenceManifestSha256": "sha256:60d5d47b070e75a8f7c3006e729107a160bdda710fcf79d39fbacbc20a3aa997", + "providerReportedUsd": null, + "reservationId": "v19-local-provider-death-direct-context" + }, + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "passed", + "paperclipSourceRevision": "8fd0e05451ed3531855884fd4375ef3564620c2f", + "definitionHash": "18c06d7ab4fec4df3d11ea52af4e1f7b70e38d50418772befff1c3bf9ef0236e", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 8, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-54-35-782Z/native-active-stop/runner-acpx-copilot/local/pending-permission-stop/attempt-1/result.json", + "resultSha256": "sha256:ea07bca7ae4068349ed218b7c3b41a6ae8f4e5780f04a52cf64260811644f5d3", + "evidenceManifestSha256": "sha256:8581b4db25e48f21f4c78562d2b130cde70ebde333909f5e9153cd71ddeea31d", + "providerReportedUsd": null, + "reservationId": "v19-local-pending-stop" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "paperclipSourceRevision": "8fd0e05451ed3531855884fd4375ef3564620c2f", + "definitionHash": "341901ee5b09a42368963a77dbfc3ea569d438952b9bb53001edb4e1cd6fb536", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-56-00-612Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:8e1bdf2881c6c7e1289fdd377eee82ae513cbe087005087c4b56e47d47d55e7c", + "evidenceManifestSha256": "sha256:b1abaa63d1d1ce5f244959d9ced3b1dc84fae258e946fc42d9bbab7c1db39093", + "providerReportedUsd": null, + "reservationId": "v19-local-attached-settlement" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "8fd0e05451ed3531855884fd4375ef3564620c2f", + "definitionHash": "341901ee5b09a42368963a77dbfc3ea569d438952b9bb53001edb4e1cd6fb536", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T01-57-21-213Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:3a25d7729aca113b2c57a4e76a85a9be2d00b3fad4488ba1f684ac8565d83f8b", + "evidenceManifestSha256": "sha256:fafb82c4cb8c48e1798d13c09fed633ada654db05a9d229e326ebd94040344a0", + "providerReportedUsd": null, + "reservationId": "v19-local-denial" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "passed", + "paperclipSourceRevision": "56a5af9aff8dce145e5154576bd43820537dda1b", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-04T02-02-05-129Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:463ef704416b8828fe530ac8a8887cbdda2fd6fdd24a58aac830f02e1d5dfb8a", + "evidenceManifestSha256": "sha256:425908db38374ad556b57cd84b990b60871631075c658225454aef00b558b122", + "providerReportedUsd": null, + "reservationId": "v19-local-denial-direct-context" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "paperclipSourceRevision": "20f8e5389b9305745e6609c7fd0172a9517191bb", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-20-12-021Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:c85d21ac7d7d241aa2521898303cd344d74b28b84444a2fe69bb0f2a42fedd6b", + "evidenceManifestSha256": "sha256:19daa31baa3e219c50b69e584552a4e7c935835eb8d33080a03da19e2d28bc63", + "providerReportedUsd": null, + "reservationId": "v19-final-local-attached-16" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "failed", + "paperclipSourceRevision": "20f8e5389b9305745e6609c7fd0172a9517191bb", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "not_started", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-22-01-019Z/copilot-protection/runner-acpx-copilot/daytona/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:e99f974175f0b37724a5b6113c1e3a4225a626fa77558cdb285d0fded0eac124", + "evidenceManifestSha256": "sha256:0d7d6965a21f60faa44706509d03a0819c6348c3fd2f4f1e9f7827ec344b7fe5", + "providerReportedUsd": null, + "reservationId": "v19-daytona-attached-16" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "failed", + "paperclipSourceRevision": "20f8e5389b9305745e6609c7fd0172a9517191bb", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-23-45-635Z/copilot-protection/runner-acpx-copilot/daytona/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:7d70384919ffd402186cee653c01281cd9f884e934dc3b8e1475833e91d89d9f", + "evidenceManifestSha256": "sha256:d0edd46ced30b1b63fef6c990bc261e912e767fe6c6549480b492fa18fa68cd0", + "providerReportedUsd": null, + "reservationId": "v19-daytona-attached-16-exact-identities" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "failed", + "paperclipSourceRevision": "b65637614ee207da797b0b89633745338f322e75", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 0, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-27-58-464Z/copilot-protection/runner-acpx-copilot/daytona/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:b0952528e8d3386da73b713df1ea7dd1e18e078c049f15b8e9ab43b2cdb1b97f", + "evidenceManifestSha256": "sha256:5e22ccb5bd60843419dc5cb5fcefc08e6547a37fdba4c2c479f9a7f57689eb20", + "providerReportedUsd": null, + "reservationId": "v19-daytona-attached-16-worker-wiring" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "failed", + "paperclipSourceRevision": "b65637614ee207da797b0b89633745338f322e75", + "definitionHash": "b6440cda129842dd041f64226b6e8643aeb23c2acffd3b2d2128b36caca99d5d", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 1, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-31-26-543Z/copilot-protection/runner-acpx-copilot/daytona/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:5b5500f6d282047b530e44814a9eb9830d94082338b6c3ccc6ff01f6b00a5a47", + "evidenceManifestSha256": "sha256:a60471d6fbb912939f7cf24b7c95a24829c265f5e7bbcca4a64b77b6e49e3edf", + "providerReportedUsd": null, + "reservationId": "v19-daytona-attached-16-image-artifacts" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "passed", + "paperclipSourceRevision": "324d069bddc4240260c2e2b8bee40bef4640b778", + "definitionHash": "8c25a7e654e01333a899310e3096f7eb8108b0e6b7e13c446e80c3258426a6a9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 11, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-39-22-257Z/copilot-protection/runner-acpx-copilot/daytona/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:44a176953d53490c47e4d6fd5b588740c3c451a6f26b171f3f14b23bde9fa321", + "evidenceManifestSha256": "sha256:1b8a29bc0c8bd4e0c2863bd14b75fb0057fe3fec9568a645b57173461a69d4d1", + "providerReportedUsd": null, + "reservationId": "v19-final-daytona-attached-17" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "paperclipSourceRevision": "324d069bddc4240260c2e2b8bee40bef4640b778", + "definitionHash": "8c25a7e654e01333a899310e3096f7eb8108b0e6b7e13c446e80c3258426a6a9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 10, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-41-02-735Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "resultSha256": "sha256:6b8ff7eb1a385f23cc2cad226ceddca13de866e894e46594d1edcdf41c84471a", + "evidenceManifestSha256": "sha256:1a81e1cb05bcf8d9ddd679abbf215ac963ca5ab78d7543d9362a14f8015b8357", + "providerReportedUsd": null, + "reservationId": "v19-final-local-attached-17" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "failed", + "paperclipSourceRevision": "324d069bddc4240260c2e2b8bee40bef4640b778", + "definitionHash": "8c25a7e654e01333a899310e3096f7eb8108b0e6b7e13c446e80c3258426a6a9", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 1, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-45-22-273Z/copilot-protection/runner-acpx-copilot/daytona/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:d47794e3a41303be2d30e7805ab85c65f13f267ea44c6ef91c11dde60e34d40b", + "evidenceManifestSha256": "sha256:e3d59c218ac2e184775fb1bb84b945f37c7e34ff498efbec836d2c56185a1665", + "providerReportedUsd": null, + "reservationId": "v19-final-daytona-denial-17" + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "passed", + "paperclipSourceRevision": "40de4e3f200c741985983cd269a935d2ab7a6e73", + "definitionHash": "823ae4239d90ca9080cb4a34e828691e68d404dbe1cc2a1ea28b63d462022218", + "attempt": 1, + "cleanup": "passed", + "passedMatchers": 11, + "failedMatchers": 0, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-53-48-252Z/copilot-protection/runner-acpx-copilot/daytona/native-permission-deny-write/attempt-1/result.json", + "resultSha256": "sha256:e601f1e95174bd8166d7fe7f9362880f8bd4f821d3531e1e09c17d48326f160d", + "evidenceManifestSha256": "sha256:f93099b917733f5be383b8153d4201dcccf00ffd261f3d47db0a3e827747bbe4", + "providerReportedUsd": null, + "reservationId": "v19-final-daytona-denial-18" + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "failed", + "paperclipSourceRevision": "40de4e3f200c741985983cd269a935d2ab7a6e73", + "definitionHash": "1691a5b4ca1280419d703fcd3df2c13b4d3189ed58d8e34c5cddd7e449dfd049", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 0, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T01-54-50-087Z/native-active-stop/runner-acpx-copilot/daytona/pending-permission-stop/attempt-1/result.json", + "resultSha256": "sha256:fb3eb3070b31cb530d83a0de19bc9034645a2b593fc6adedf64991431cdf6d95", + "evidenceManifestSha256": "sha256:fffa76851d4a23ef7132f14dc8d024c30fe2e242b0a48dbedecd9bf105427440", + "providerReportedUsd": null, + "reservationId": "v19-final-daytona-pending-permission-stop-15" + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "failed", + "paperclipSourceRevision": "276415d2e8a8460aad21882651341658f3af5a72", + "definitionHash": "95ed6666ca8d1f0f912d79cd84ddba326cb17dde26d6f54c864b62d11bd842be", + "attempt": 1, + "cleanup": "failed", + "passedMatchers": 5, + "failedMatchers": 1, + "resultPath": "tests/runner-e2e/results/local-2026-10-05T02-03-34-775Z/native-active-stop/runner-acpx-copilot/daytona/pending-permission-stop/attempt-1/result.json", + "resultSha256": "sha256:170fb1b9c1280cd271969da688c075f03de2f9d5e00689ad0393a754761b6a9a", + "evidenceManifestSha256": "sha256:4eeb94e4b9154ca13c19b7259f8ec80cf0780ec615cc0ffb7e2efc0d782dd8ec", + "providerReportedUsd": null, + "reservationId": "v19-daytona-stop-retirement-diagnostic-16" + }, + { + "reservationId": "v19-final-daytona-pending-permission-stop-17", + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "passed", + "launchLogSha256": "sha256:c55f28aa6dbd530ff7dc45f0b0ea2d598673e865d6d9a2f0743b8752bd2752bc", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-14-00-983Z" + }, + { + "reservationId": "v19-final-daytona-pending-permission-provider-death-14", + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "failed", + "launchLogSha256": "sha256:b7c04fd182a21095c6916257df4ce1b20b736ad8c41f20bdf0435ec703563e9b", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-15-00-661Z" + }, + { + "reservationId": "v19-final-daytona-provider-death-15-descriptor", + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "passed", + "launchLogSha256": "sha256:662005e7247f5a93a8342a841322b8bda1c39ed5b00049b2a53ca23f2f70e1b9", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-24-48-909Z" + }, + { + "reservationId": "v19-final-local-native-permission-deny-write-19", + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "passed", + "launchLogSha256": "sha256:2258c7a83abd3f1b1d6ec1013e46dd6f2e8659b9d1fffed181c58098938c6aa6", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-27-16-877Z" + }, + { + "reservationId": "v19-final-local-pending-permission-stop-17", + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "passed", + "launchLogSha256": "sha256:ffb0577b4e3c0ad90d41f2f0f27bae532d49e26052d41d7c1616774b0de9deb9", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-28-02-611Z" + }, + { + "reservationId": "v19-final-local-pending-permission-provider-death-15", + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "passed", + "launchLogSha256": "sha256:5c34c323ca5a670d713b880ce52b702d3963d1d74516f4aa57f8d654084808d0", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-28-44-155Z" + }, + { + "reservationId": "v19-final-local-hello-complete", + "selector": "extended-harnesses.runner-acpx-copilot.local.hello-complete", + "status": "passed", + "launchLogSha256": "sha256:4f231072676a641b09b917ed5b5c8200f1f3df45b90d686d9541a9d7cc5c1101", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-29-32-869Z" + }, + { + "reservationId": "v19-final-daytona-hello-complete", + "selector": "extended-harnesses.runner-acpx-copilot.daytona.hello-complete", + "status": "passed", + "launchLogSha256": "sha256:09d0ad8ccb29df2a1ffa9983c0c3b2ebe0163b93e5faf15ca7b2c101f9136adc", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-30-08-196Z" + }, + { + "reservationId": "v19-final-local-question-resume-complete", + "selector": "extended-harnesses.runner-acpx-copilot.local.question-resume-complete", + "status": "failed", + "launchLogSha256": "sha256:b26bde50b89cf0d079e7e729d73a751ab160f87602176a9483284274e7409d2c", + "runtimeSourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "sourceRevision": "b5a1ee8dde80b7911cb751cfcd56861534620239", + "historicalOnly": true, + "resultId": "local-2026-10-05T02-31-23-855Z" + } + ], + "integrationSourceRevision": "421cbda22d9e5fecfe9e64df296df26e9cf9335c", + "productDefinitionVersions": { + "copilot-protection": 19, + "native-active-stop": 17, + "copilot-provider-death": 14 + }, + "updatedAt": "2026-10-05T03:17:13.711277+00:00", + "browserSavedTokenAcceptance": { + "status": "passed", + "flow": "Connectors -> Connect Copilot -> personal token -> selected agent -> runtime save -> reopen -> metadata-only test", + "newConnectionCreatedThroughBrowser": true, + "companyWideAgentAccess": false, + "ownership": "personal", + "heartbeatEnabled": false, + "provider": "acpx", + "acpxAgent": "copilot", + "model": "gpt-5.6-luna", + "discoveredModelCount": 17, + "modelPromptSentByVerification": false, + "ordinaryFreshProviderSelection": "pending-production-admission" + }, + "nativeIntelInstalledRuntime": { + "status": "passed", + "os": "darwin", + "architecture": "x64", + "execution": "Rosetta on macOS ARM64", + "physicalIntelHardware": false, + "providerPackDigest": "sha256:29a0a71712e2219bc7240d06106bae21b739a5772172743ff3b8f8285ab42dac", + "runnerdArchitecture": "x86_64", + "providerArchitecture": "x86_64", + "nodeArchitecture": "x86_64", + "initializeAndCleanup": "passed", + "modelPromptSent": false + }, + "latestRepairs": [ + { + "revision": "20f8e5389b9305745e6609c7fd0172a9517191bb", + "change": "Explicit personal Copilot connection access defaults." + }, + { + "revision": "b65637614ee207da797b0b89633745338f322e75", + "change": "Remote saved-token probe receives live worker manager." + }, + { + "revision": "324d069bddc4240260c2e2b8bee40bef4640b778", + "change": "Direct semantic context guidance in remote bootstrap; HTTP substitution still rejected." + }, + { + "revision": "40de4e3f200c741985983cd269a935d2ab7a6e73", + "change": "Observed remote action published before context permission approval." + }, + { + "revision": "276415d2e8a8460aad21882651341658f3af5a72", + "change": "Bounded incomplete-retirement diagnostics; strict receipt rejection preserved." + }, + { + "revision": "7a64dce18", + "change": "Remote Stop uses attested sandbox root; local PID/start identity guards preserved." + }, + { + "revision": "421cbda22d9e5fecfe9e64df296df26e9cf9335c", + "change": "Selected-environment model discovery receives live worker manager." + }, + { + "cause": "Cross-run recovery retained a collected previous instruction working-copy path. Actual registered-file binding reproduced ENOENT offline.", + "repair": "Deliver the current authenticated instruction grant before recovery opens; forward it through warm attachment and project remote paths.", + "newProfileVersion": 20, + "previousPassesRemainHistorical": true, + "newLiveCoverage": "pending" + } + ], + "currentVerification": { + "productSupport": { + "sourceRevision": "7a64dce18", + "sourceGatesPassed": 128, + "testsPassed": 1876, + "testsSkipped": 1 + }, + "savedConnections": { + "sourceRevision": "421cbda22d9e5fecfe9e64df296df26e9cf9335c", + "testsPassed": 57 + }, + "serverTypecheck": "passed", + "productTypecheck": "passed", + "singleFullTestCommand": "failed; unchanged anonymous health timeout; 15100 tests passed, 85 skipped", + "productionPromotion": "pending-required-live-coverage", + "fullTestFailedInvocation": { + "sourceRevision": "b65637614ee207da797b0b89633745338f322e75", + "generalServerTestsPassed": 15100, + "generalServerTestsSkipped": 85, + "failure": "15-second timeout: anonymous health workspace readiness; cause unknown", + "durationSeconds": 2309, + "assertionsAndTimeoutUnchanged": true + } + }, + "historicalSavedTokenBrowser": { + "reopenAndMetadataVerification": "passed", + "promptSent": false, + "newConnectionCreation": "staged-empty-token-awaiting-credential-step-authorization" + }, + "savedTokenBrowser": { + "status": "passed", + "flow": "Connectors -> Connect Copilot -> personal token -> selected agent -> runtime save -> reopen -> metadata-only test", + "newConnectionCreatedThroughBrowser": true, + "companyWideAgentAccess": false, + "ownership": "personal", + "heartbeatEnabled": false, + "provider": "acpx", + "acpxAgent": "copilot", + "model": "gpt-5.6-luna", + "discoveredModelCount": 17, + "modelPromptSentByVerification": false, + "ordinaryFreshProviderSelection": "pending-production-admission" + }, + "historicalInstalledRuntimeSmokes": [ + { + "platform": "darwin", + "architecture": "arm64", + "translatedIntel": false, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:fb17098f93bbb888e605ee84201f4f6f0e6920dc81b1d9cd293f343876b959ed", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:ff77894fffe5089267d605b0254442b3b6b7e0ef01fd6756f19fec27be256c45" + }, + { + "platform": "darwin", + "architecture": "x64", + "translatedIntel": true, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:29a0a71712e2219bc7240d06106bae21b739a5772172743ff3b8f8285ab42dac", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:9e1c6cc23c0db0ae534a95a346bd5a918527778b4e64af7a33c7ef93fc4e6b38", + "stagedRunnerDaemonArchitecture": "x64", + "limitation": "Complete x64 pack executed under Rosetta; no physical Intel hardware smoke." + }, + { + "platform": "linux", + "architecture": "x64", + "translatedIntel": false, + "status": "passed", + "scope": "credential-free installed initialize/cleanup; no model execution or Daytona qualification", + "providerPackDigest": "sha256:42a0cdfea607e68f1e1bd20b27a54507d5727c6417f6d8df553a9f41f882d533", + "sourceRevision": "0c192bded19a9e000618ba21393ec0d46e7bceff", + "evidenceSha256": "sha256:d723290d88381963094f978733f204dac339e9cf9bc5eec62b9c3c9bed9b7729" + } + ], + "historicalOfflineVerification": { + "runnerContractsAuthorityConformanceReplay": "passed", + "recursiveTypecheck": "passed", + "tokenGates": "passed", + "productionBuild": "passed", + "productSupport": { + "nodeGatesPassed": 128, + "vitestPassed": 1868, + "linuxOnlySkippedOnMacOS": 1 + }, + "uiTestsPassed": 7283, + "cliTestsPassed": 502, + "remainingAdapterPluginSourceTestsPassed": 2926, + "database": { + "testsPassedSerially": 160, + "initialParallelAttempt": "failed-bootstrap-before-migration-assertion" + }, + "generalServer": { + "singleInvocation": "failed", + "testsPassedInFailedInvocation": 15099, + "testsSkipped": 85, + "failure": "socket hang up in status-cards", + "isolatedFileTestsPassed": 24 + }, + "serializedRoutes": { + "selectedFiles": 150, + "allSelectedFilesHavePassingResults": true, + "singleInvocation": "failed", + "failuresRetained": [ + "socket hang up in issue-feedback-routes", + "15-second timeout in secret-proposals-routes" + ], + "isolatedDiagnosticTestsPassed": { + "issue-feedback-routes": 4, + "secret-proposals-routes": 25 + }, + "timeoutCause": "unknown; not reproduced in one timing diagnostic", + "diagnosticTransport": { + "syntheticRequestsWithResponse": 86, + "maximumResponseElapsedMs": 1042, + "traceSha256": "sha256:477c338475bd80242ade1a0bea80b1cb3389a22565c94ddf1ce43b8f93c3bc44" + } + } + }, + "offlineVerification": { + "runnerContractsAuthorityConformanceReplay": "passed", + "recursiveTypecheck": "passed", + "tokenGates": "passed", + "productionBuild": "passed", + "productSupport": { + "nodeGatesPassed": 128, + "vitestPassed": 1876, + "linuxOnlySkippedOnMacOS": 1 + }, + "uiTestsPassed": 7283, + "cliTestsPassed": 502, + "remainingAdapterPluginSourceTestsPassed": 2926, + "database": { + "testsPassedSerially": 160, + "initialParallelAttempt": "failed-bootstrap-before-migration-assertion" + }, + "generalServer": { + "singleInvocation": "failed", + "testsPassedInFailedInvocation": 15099, + "testsSkipped": 85, + "failure": "socket hang up in status-cards", + "isolatedFileTestsPassed": 24 + }, + "serializedRoutes": { + "selectedFiles": 150, + "allSelectedFilesHavePassingResults": true, + "singleInvocation": "failed", + "failuresRetained": [ + "socket hang up in issue-feedback-routes", + "15-second timeout in secret-proposals-routes" + ], + "isolatedDiagnosticTestsPassed": { + "issue-feedback-routes": 4, + "secret-proposals-routes": 25 + }, + "timeoutCause": "unknown; not reproduced in one timing diagnostic", + "diagnosticTransport": { + "syntheticRequestsWithResponse": 86, + "maximumResponseElapsedMs": 1042, + "traceSha256": "sha256:477c338475bd80242ade1a0bea80b1cb3389a22565c94ddf1ce43b8f93c3bc44" + } + }, + "savedConnectionTestsPassed": 57 + } +} diff --git a/doc/plans/2026-10-03-copilot-qualification-v20.json b/doc/plans/2026-10-03-copilot-qualification-v20.json new file mode 100644 index 00000000000..199dbe8b1f6 --- /dev/null +++ b/doc/plans/2026-10-03-copilot-qualification-v20.json @@ -0,0 +1,739 @@ +{ + "schema": "paperclip.copilot.qualification-progress/v1", + "status": "historical-core-covered-release-gated", + "productionAdmission": "gated", + "supersedes": "2026-10-03-copilot-qualification-v19.json", + "runtimeSourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "productDefinitionsRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "evalsRevision": "3f7b26435f0c2d92dfeedf8c3d466dd94bf3ed4a", + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "productCells": [ + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "passed", + "definitionVersion": 17, + "attempts": [ + { + "label": "v20-final-local-pending-permission-stop", + "status": "passed", + "definitionVersion": 17, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-35-17-581Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "d8999808abb2a6ed4056059d0ecddc103c8679b5f218699e892e1a04eae68955", + "cleanup": "passed", + "resultSha256": "sha256:5586d5a024d22284546aba127c52ff661431fcfd6d01f35676d4ce64e49f7a70", + "matcherCount": 8, + "matcherPassCount": 8 + } + ] + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "passed", + "definitionVersion": 17, + "attempts": [ + { + "label": "v20-final-daytona-pending-permission-stop", + "status": "passed", + "definitionVersion": 17, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-35-59-876Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "d8999808abb2a6ed4056059d0ecddc103c8679b5f218699e892e1a04eae68955", + "cleanup": "passed", + "resultSha256": "sha256:6b271d35a4e4cf9771ea6c063a4c81c31118e9708a110d1b6db508b98b9efe3b", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "passed", + "definitionVersion": 15, + "attempts": [ + { + "label": "v20-final-local-pending-permission-provider-death", + "status": "passed", + "definitionVersion": 15, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-42-29-858Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "5ca6dcc424a8813aec3c7effa6f8abdc5af0bbd75ac23e525970661d023bea44", + "cleanup": "passed", + "resultSha256": "sha256:02cbd49cc4a7217ec3e190d97bc17fe75769ceb3b346e42db04803f9e0aabf90", + "matcherCount": 8, + "matcherPassCount": 8 + } + ] + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "passed", + "definitionVersion": 15, + "attempts": [ + { + "label": "v20-final-daytona-pending-permission-provider-death", + "status": "passed", + "definitionVersion": 15, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-43-33-030Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "5ca6dcc424a8813aec3c7effa6f8abdc5af0bbd75ac23e525970661d023bea44", + "cleanup": "passed", + "resultSha256": "sha256:1dcba2d5f1c4b545f9ead727e6f40cb04d5820af9bf01a56f91a23e49f5c5c40", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "passed", + "definitionVersion": 19, + "attempts": [ + { + "label": "v20-final-local-native-permission-deny-write", + "status": "passed", + "definitionVersion": 19, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-44-41-932Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "794c0628644401b0e320278b6583b4e88cac255be0f5659dfcba98c803ed288a", + "cleanup": "passed", + "resultSha256": "sha256:0695741e883f492d84c770c9ccca5bef0ce6151a834eaf938654076e5cceb037", + "matcherCount": 10, + "matcherPassCount": 10 + } + ] + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "definitionVersion": 19, + "attempts": [ + { + "label": "v20-final-local-attached-async-settlement", + "status": "passed", + "definitionVersion": 19, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-45-25-306Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "794c0628644401b0e320278b6583b4e88cac255be0f5659dfcba98c803ed288a", + "cleanup": "passed", + "resultSha256": "sha256:c295447bc374dd4ffd8ac56898cb2f1c1e70cbc675246215ec91a1cf23a34ff1", + "matcherCount": 10, + "matcherPassCount": 10 + } + ] + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "passed", + "definitionVersion": 19, + "attempts": [ + { + "label": "v20-final-daytona-native-permission-deny-write", + "status": "passed", + "definitionVersion": 19, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-46-10-182Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "794c0628644401b0e320278b6583b4e88cac255be0f5659dfcba98c803ed288a", + "cleanup": "passed", + "resultSha256": "sha256:a3921b59486a5bbc18d7d7c555a1d3c4eaeacfa13c559f454e1f38bf015a230e", + "matcherCount": 11, + "matcherPassCount": 11 + } + ] + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "passed", + "definitionVersion": 19, + "attempts": [ + { + "label": "v20-final-daytona-attached-async-settlement", + "status": "passed", + "definitionVersion": 19, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-47-09-753Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "794c0628644401b0e320278b6583b4e88cac255be0f5659dfcba98c803ed288a", + "cleanup": "passed", + "resultSha256": "sha256:76eb9480361749221800123b69d3febe58b728db14730906fcf19dd27396c9c0", + "matcherCount": 11, + "matcherPassCount": 11 + } + ] + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.local.warm-three-turn", + "status": "passed", + "definitionVersion": 2, + "attempts": [ + { + "label": "v20-final-local-warm-three-turn", + "status": "failed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-48-16-464Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "605f2893cfeffd44eda3e86d90b7695a54917b063fe586e75688f30d0832c960", + "cleanup": "passed", + "resultSha256": "sha256:81d6e397e47a702b3d23c086448a9397f372032c25d723208363ab686179ba85", + "matcherCount": 9, + "matcherPassCount": 9 + }, + { + "label": "v20-warm-repair-local-warm-three-turn", + "status": "passed", + "definitionVersion": 2, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-06-02-656Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "55859de81a8d5d39308102f267f5824f2d24452dc922e69edfca1032661f92c8", + "cleanup": "passed", + "resultSha256": "sha256:e302a32039a70e4dda7d7946f79e31d85bdd2f46dc8db7ad3235cb6e9ef586ee", + "matcherCount": 9, + "matcherPassCount": 9 + } + ] + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.daytona.warm-three-turn", + "status": "passed", + "definitionVersion": 2, + "attempts": [ + { + "label": "v20-warm-repair-daytona-warm-three-turn", + "status": "passed", + "definitionVersion": 2, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-08-54-654Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "55859de81a8d5d39308102f267f5824f2d24452dc922e69edfca1032661f92c8", + "cleanup": "passed", + "resultSha256": "sha256:0ba63e880fd1bc39627527fd512fb6d04f1ad14f45c3d33d5bbc7f9c5e5268da", + "matcherCount": 9, + "matcherPassCount": 9 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.hello-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-local-hello-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-12-23-520Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:9a47f8f7a6421c793ec9569e7dfd5f6930cc9edab6bf328d88c4a66392834fd6", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.question-resume-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-final-local-question-resume-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-19-51-964Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:e78b8c9a1a422a7f6e53acc04e088c552b93724be0176c6f06161aef4a2c1f66", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.plan-approve-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-local-plan-approve-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-12-56-580Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:dae3d382178075163cb28625270dc9a56a803e455411663b12ed260c995e66c4", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.structured-question-restart-resume", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-local-structured-question-restart-resume", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-13-53-885Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:338e9bc2394406ff231a602c4fe187730f21b03703888aa4e52b02dc018b44ac", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.file-edit-validate", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-local-file-edit-validate", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-15-04-865Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:6d2c98462defbc8882192ab545ed479a3b18d39070ad94c12de5e3daabd8c11d", + "matcherCount": 7, + "matcherPassCount": 7 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.hello-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-daytona-hello-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-16-04-974Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:1b95a8ff41b515243885a51a431e9269734fb7b2dfab3810c261572b8a149686", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.question-resume-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-final-daytona-question-resume-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T03-31-54-286Z", + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:9c2906c0771ea54a806ba279ffec12b22a1c7b6cac6404ca09cb6f7001ef3462", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.plan-approve-complete", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-daytona-plan-approve-complete", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-17-17-405Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:82efa2740a49341ca6aaf9102be09bd0617d1aa4a34cdf4a3eb76f255529f4a3", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.structured-question-restart-resume", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-daytona-structured-question-restart-resume", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-19-02-595Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:d6317f1ab6763ee2be4f43d54d2c5121a8420d0b9e626f2309139461671a24ce", + "matcherCount": 6, + "matcherPassCount": 6 + } + ] + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.file-edit-validate", + "status": "passed", + "definitionVersion": 1, + "attempts": [ + { + "label": "v20-warm-repair-final-daytona-file-edit-validate-bounded-infrastructure", + "status": "passed", + "definitionVersion": 1, + "automaticRetries": 0, + "resultId": "local-2026-10-05T04-25-04-379Z", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "definitionHash": "e75cb2222d2f52ec3a2a425211d195d36b95882de2cb9ed0ad93231b7c42768a", + "cleanup": "passed", + "resultSha256": "sha256:bd4057cc365ac430eb5d22e97f02d4f253078a902abe6c269ae2e26cd774702e", + "matcherCount": 7, + "matcherPassCount": 7 + } + ] + } + ], + "runnerProtocol": [ + { + "caseId": "context-before-action", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-context-before-action", + "caseId": "context-before-action", + "status": "failed", + "summarySha256": "sha256:5c1e944f54fdc116ed49591e23dae28b63b6ba3f3c69227dda04b4f0fffa870b", + "automaticRetries": 0, + "dispositions": [ + "infrastructure_failure" + ], + "profileVersion": 19, + "profileDigest": "sha256:c2825f706f839071ce5aa9ddc2a058d785a55644964ff9c2ba878ac9ccc66fef", + "unusedInferenceReservationReleasedUsd": 0.5 + }, + { + "label": "v20-final-protocol-context-before-action-exact-profile", + "caseId": "context-before-action", + "status": "passed", + "summarySha256": "sha256:af88322a116d90346a491bd41ffb13e1c792a699491723e3f8496f018d371058", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "get-task-context", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-get-task-context", + "caseId": "get-task-context", + "status": "passed", + "summarySha256": "sha256:9747b893fcd3e96bad17b97a8c12e74b0b7a1cd9a4dbd4fe2ca9b4b758593573", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "create-task-document", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-create-task-document", + "caseId": "create-task-document", + "status": "passed", + "summarySha256": "sha256:20b3d832ef12ad36820c34c541214b1d18d6924ab8da674ccb8e9d373a74d045", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "finish-task", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-finish-task", + "caseId": "finish-task", + "status": "passed", + "summarySha256": "sha256:e0272871319c712c518151efc3f1a8fff28a90cb3509040d4678b3c9a0f496d9", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "request-human-confirmation", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-request-human-confirmation", + "caseId": "request-human-confirmation", + "status": "passed", + "summarySha256": "sha256:0f92dd956c2ad963a9d24226bef8e8ff6512185e25cc8b796a8712d6eef7fa0a", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "workflow-context-document-progress", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-workflow-context-document-progress", + "caseId": "workflow-context-document-progress", + "status": "passed", + "summarySha256": "sha256:60c31ec37982b307d14ed502e84d61094a18674034b3abea51c8eabec7500d49", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + }, + { + "caseId": "workflow-governed-wait", + "status": "passed", + "attempts": [ + { + "label": "v20-final-protocol-workflow-governed-wait", + "caseId": "workflow-governed-wait", + "status": "passed", + "summarySha256": "sha256:35950a2454906d62b357ac3908fd84435a1646411d8dbba633b55ffc093c4860", + "automaticRetries": 0, + "dispositions": [ + "passed" + ], + "profileVersion": 20, + "profileDigest": "sha256:6ab53285d7cb0eaf79fafec9e0bf0f1a42044761b4b0cf129e0159c8c2fad539", + "unusedInferenceReservationReleasedUsd": 0 + } + ] + } + ], + "installedRuntimeSmokes": { + "macosArm64": "passed", + "macosX64Rosetta": "passed", + "linuxX64": "passed", + "qualification": "Actual installed native binaries; macOS x64 ran under Rosetta, not on a physical Intel host." + }, + "budget": { + "ceilingUsd": 100, + "priorSharedExposureUpperBoundUsd": 47.080380624, + "combinedConservativeExposureUpperBoundUsd": 57.810304624, + "remainingCeilingHeadroomUsd": 42.189695376, + "nativeInferenceUsd": "unknown per run; included-credit aggregate valuation is not a measured per-run charge", + "aggregateIncludedUsageDollarEquivalentUsd": 0.71, + "reportingDelayContingencyUsd": 5, + "releasedHistoricalInferenceContingencyUsd": 41.79, + "billingControlsChanged": false, + "accountBindingVerified": true, + "billingProofSha256": "sha256:aed119f7635b7eb46bc89e3d43a9afd163a9ba9ee56671afbd3f0c6c7b16360a", + "privateLedgerSha256": "sha256:c07258de0235b57fec9c09f06efd24c7d05c458996a10d152ffcd6003bf7a8f9", + "budgetIncreaseRequestDisposition": "withdrawn-unneeded; original $100 ceiling retained" + }, + "evidencePolicy": "Original failures are retained. Earlier-definition passes are not final frozen-definition passes. Native USD is unknown; conservative exposure is not measured spend. Local raw evidence is retained separately from this safe summary.", + "updatedAt": "2026-10-05T15:53:48.410637+00:00", + "integratedSourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "runnerArtifactsSourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "sourceFreezeSha256": "sha256:c2124432cc026857a3732d709f24c3c9589a1bc4115e47964585f22309a02f4a", + "singleIntegratedSourceCampaignComplete": false, + "sourceRepair": { + "from": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "to": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "runtimeArtifactsChanged": false, + "profileChanged": false, + "cause": "Saved-token Copilot was forced into per-turn mode and lacked warm instruction-directory handoff. The local fixture also required Daytona leases.", + "assertionsRetained": [ + "stable native session", + "stable runner instance", + "stable provider session", + "stable process PID and start fingerprint", + "stable project and execution workspace", + "exact files and response order", + "Daytona created/resumed/resumed leases" + ] + }, + "artifacts": { + "providerPackDigest": "sha256:109a5503059ab288e3ae27c7a9259508656de90780f140fafed24be5756f9768", + "runnerPackageSha256": "sha256:8a5ac4330a984e12c18820d9c151692c274689d59d15a4557030736e8b0f8b9a", + "runnerdSha256": "sha256:2262bba760163b44f61674e43e723c08c22b1618af8cfe286fc8699ba9e153ee", + "runnerCliSha256": "sha256:bed6afbd86ab25c4bf90886a162e2b8a3e192005c77f50b597aee48a619dc3eb", + "daytonaImageContentId": "d2efbefb0fd9b6bcab83ff012dfed48bed0a61980e2a2780cf85fae89e9a8808", + "daytonaImageDigest": "sha256:92b52854065117926c73d81512c2220b5842d2c2bb0432cbc55563e9714ddcd4", + "publishedIndexDigest": "sha256:2d29e804ecdf6e2c650d2a53adc8313ecf8ce740653a87be89c078a0c2b79c3a" + }, + "installedSmokes": [ + { + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "providerPackDigest": "sha256:109a5503059ab288e3ae27c7a9259508656de90780f140fafed24be5756f9768", + "platform": "darwin", + "architecture": "arm64", + "translatedIntel": false, + "cleanExit": true, + "promptSent": false, + "inheritedCredentials": false, + "evidenceSha256": "sha256:249b6741e2bc72ce79a892e8f1356f076ef938d010323e8cc7f9c22b052fc6be" + }, + { + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "providerPackDigest": "sha256:fae748d8f054fd74548921b40a21bfd0532eea9a040d4f8581ffab161e2a6ee0", + "platform": "darwin", + "architecture": "x64", + "translatedIntel": true, + "cleanExit": true, + "promptSent": false, + "inheritedCredentials": false, + "evidenceSha256": "sha256:9a7814123557ebc303f69b860d980f9ef6d1bc7ff904bf885f969e18c401938d" + }, + { + "sourceRevision": "e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "providerPackDigest": "sha256:ac3637fe8e48cf1ef36f0247b98b47e624a709643b8ba7a59f2a17a4dbffa902", + "platform": "linux", + "architecture": "x64", + "translatedIntel": false, + "cleanExit": true, + "promptSent": false, + "inheritedCredentials": false, + "evidenceSha256": "sha256:777151bafd0dbdca279f9369a9a54d94dbf6aeb3cd5ecae9a28d2cb03af5ef60" + } + ], + "remainingReleaseGates": [ + "Finish the frozen integrated-source Product campaign; retain earlier source/result grades.", + "Complete current-head verification and review.", + "Enable normal Copilot admission and standard packaging only after qualification.", + "Prove ordinary saved-token setup, installation and canary without qualification overrides." + ], + "singleIntegratedSourceProductCoverage": { + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "passed": 10, + "required": 20, + "earlierSourcePassesRequireRepeat": 10 + }, + "qualificationScope": "All 20 selectors have passed on the v20 runtime artifacts. Ten are on the warm-repaired server source; ten predate that server repair. This does not yet qualify a single integrated release build.", + "verification": { + "runnerContracts": "passed at runtime source e8b0aaba234d0d2192807c7a41d4c85df087ba47; runtime artifacts unchanged by server warm repair", + "warmCapabilityTests": 9, + "warmDirectoryAndCredentialTests": 10, + "catalogTests": 31, + "recursiveTypecheck": "passed", + "tokenGates": "passed", + "fullTestRun": "failed at source 75592813ad7f2bb97ed84fda0e2f95276de78511 without diagnostic preload: 7 failing files, 5 failing tests, 15060 passing tests, 131 skipped tests; no assertions changed", + "fullBuild": "passed", + "retainedFullTestFailures": [ + { + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "status": "failed", + "cause": "routines HTTP socket reset", + "focusedRoutinesTestsPassed": 10 + }, + { + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "status": "failed", + "cause": "one of 500 Git scan HTTP requests reset; diagnostic preload also broke four empty-stderr Discord assertions", + "testsPassedBeforeFailure": 15106, + "testsFailed": 5, + "unhandledErrors": 1, + "focusedTestsPassedWithoutPreload": 78, + "assertionsChanged": false + }, + { + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "status": "failed", + "cause": "Five timeout/hook failures, one embedded database startup failure, and one run-status assertion across seven files. Narrow unchanged-assertion diagnosis is in progress.", + "testsPassedBeforeFailure": 15060, + "testsFailed": 5, + "filesFailed": 7, + "assertionsChanged": false + } + ] + }, + "savedTokenUiAcceptance": { + "status": "passed", + "sourceRevision": "75592813ad7f2bb97ed84fda0e2f95276de78511", + "profileVersion": 20, + "productionAdmission": "qualification-pending", + "ordinaryProductionInstallation": false, + "runtimeTest": { + "status": "Connection successful", + "details": "The verified Copilot runtime authenticated this account and accepted the selected model. No model prompt was sent.", + "modelPromptSent": false + }, + "screenshot": { + "file": "saved-token-ui-v20.jpg", + "sha256": "sha256:7f5bd9d6dd374faf73d37dfa5177a023c7bdf5a1507f9f72b485b6354a9cd81b" + } + }, + "sourceFreezePolicy": { + "implementationAndEvalDefinitions": "immutable at 75592813ad7f2bb97ed84fda0e2f95276de78511", + "runnerArtifacts": "immutable at e8b0aaba234d0d2192807c7a41d4c85df087ba47", + "mutableEvidencePaths": [ + "doc/plans/2026-10-03-copilot-production-readiness.md", + "doc/plans/2026-10-03-copilot-qualification-v20.json" + ], + "reason": "Update current campaign results without changing executable source, profile, eval definitions, or artifacts." + }, + "supersededBy": "2026-10-05-copilot-qualification-v21.json" +} diff --git a/doc/plans/2026-10-05-copilot-qualification-v21.json b/doc/plans/2026-10-05-copilot-qualification-v21.json new file mode 100644 index 00000000000..41c6a7e134f --- /dev/null +++ b/doc/plans/2026-10-05-copilot-qualification-v21.json @@ -0,0 +1,576 @@ +{ + "schema": "paperclip.copilot.qualification-progress/v1", + "status": "Historical v21 evidence; superseded by the unfrozen v22 master integration; production remains gated", + "updatedAt": "2026-10-05T19:03:10.191470+00:00", + "productionAdmission": "gated", + "supersedes": "2026-10-03-copilot-qualification-v20.json", + "upstreamTargetRevision": "59015846ae02f935411afc620e0867ce812378fb", + "integratedSourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "sourceFreezeComplete": true, + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "profileVersion": 21, + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "profileIdentityFrozen": true, + "profileChangeReason": "Bind native asset ownership and the installer, support the public server vendored layout, eliminate private workspace runtime imports, and preserve the latest mainline completion and shutdown settlement fixes.", + "productCells": [ + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "pending", + "required": true + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "pending", + "required": true + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "pending", + "required": true + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "pending", + "required": true + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "pending", + "required": true + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "pending after two retained controller failures", + "required": true, + "retainedAttempts": [ + { + "label": "copilot-v21-product-local-attached-settlement", + "status": "failed-before-test-result", + "failureClass": "transient_infrastructure", + "error": "Playwright exited 1 before writing a result", + "launcherObservation": "Playwright process received SIGKILL before any result was written; initial controller memory limit was 3 GiB. No assertion or model behavior result exists.", + "evidence": "/tmp/copilot-production-campaign-v21/linux-product-results/local-2026-10-05T17-26-43-472Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "sha256": "sha256:0cd7e8c19c9ff8221b3f48d93295806f0f516e0e75a1255afeab2fbf41badca8", + "automaticRetries": 0, + "inferenceReservationRetainedUsd": 0.5 + }, + { + "label": "copilot-v21-product-local-attached-settlement-attempt02", + "status": "failed", + "resultPath": "/tmp/copilot-production-campaign-v21/linux-product-results/local-2026-10-05T18-42-28-030Z/copilot-protection/runner-acpx-copilot/local/attached-async-settlement/attempt-1/result.json", + "recordedFailureClass": "secret_leak", + "underlyingBrowserFailure": "Page crashed; controller memory.events confirms oom_kill=1 at 6 GiB", + "evidenceInspectionFailure": "unzip ENOENT: archives rejected and excluded from packaged evidence; recorded failure is preserved", + "cleanup": "passed", + "nativeInferenceUsd": "unknown; full $0.50 reservation retained", + "controllerExitProof": "/tmp/copilot-production-campaign-v21/copilot-v21-product-local-attached-settlement-attempt02-controller-exit.json" + } + ], + "nextAction": "Wait for the full isolated suite to finish, then one bounded 6 GiB controller attempt with captured cgroup memory events and unchanged test assertions." + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "pending", + "required": true + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "pending", + "required": true + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.local.warm-three-turn", + "status": "pending", + "required": true + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.daytona.warm-three-turn", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.hello-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.question-resume-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.plan-approve-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.structured-question-restart-resume", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.file-edit-validate", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.hello-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.question-resume-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.plan-approve-complete", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.structured-question-restart-resume", + "status": "pending", + "required": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.file-edit-validate", + "status": "pending", + "required": true + } + ], + "runnerProtocol": [ + { + "caseId": "context-before-action", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-context-before-action-summary.json", + "sha256": "sha256:beb87abfd68f93c3d4a4fda9a67ed370550ca8e693ad336253d0a536fa9e9d2d" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "automaticRetries": 0 + }, + { + "caseId": "get-task-context", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-get-task-context-summary.json", + "sha256": "sha256:e7daec79864f0e0b61ee60d106416c5289d04ffc4273d65db16fc15fcdd4134c" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "automaticRetries": 0 + }, + { + "caseId": "create-task-document", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-create-task-document-summary.json", + "sha256": "sha256:c2aa270aab6c1c0915d8271cf0402c33dced945329ecab49fffcceae6a04fd17" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "automaticRetries": 0 + }, + { + "caseId": "finish-task", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-finish-task-summary.json", + "sha256": "sha256:a35e8310c7ab5da467c7f53d8ea6f71dd84e01b1621bdb7f80762dc1b485cf59" + }, + "automaticRetries": 0, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d" + }, + { + "caseId": "request-human-confirmation", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-request-human-confirmation-summary.json", + "sha256": "sha256:532414bd7490cfbe18a12b01a48d84d3a9acaa1ce95853de3094783a20f89bd0" + }, + "automaticRetries": 0, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d" + }, + { + "caseId": "workflow-context-document-progress", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-workflow-context-document-progress-summary.json", + "sha256": "sha256:d7c600f6fc0ec1acfcad0492f7aab4bced10cc63f22bee5b86277408f26daa1a" + }, + "automaticRetries": 0, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d" + }, + { + "caseId": "workflow-governed-wait", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/copilot-v21-protocol-workflow-governed-wait-summary.json", + "sha256": "sha256:f169b83dfa4f60f59568833dc064a0d922f6449f4823d34b3045364f6653f7a1" + }, + "automaticRetries": 0, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d" + } + ], + "installedRuntimeSmokes": [ + { + "platform": "darwin-arm64", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/installed-macos-arm64-smoke.json", + "sha256": "sha256:4ff4712f675d2d70b8984bb1ddc0c55a32152177c929eb4a81a1d2849f251a76" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "promptSent": false + }, + { + "platform": "darwin-x64", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21/installed-macos-x64-smoke.json", + "sha256": "sha256:25f5b7d45c05dc3af8dc096ea4309874d58a6d41558c1f87ab5fab999947a1c5" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "promptSent": false + }, + { + "platform": "linux-x64", + "status": "passed", + "required": true, + "evidence": { + "path": "/tmp/copilot-production-campaign-v21-remote-qualified-source/linux-installed-smoke.json", + "sha256": "sha256:11bbfa2e48bd5823667e26e06c0a75c2bb9c6f55db772e7c9d937b88fa445663" + }, + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "promptSent": false + } + ], + "singleIntegratedSourceCampaignComplete": false, + "budget": { + "ceilingUsd": 100, + "priorSharedExposureUpperBoundUsd": 47.080380624, + "combinedConservativeExposureUpperBoundUsd": 62.310304624, + "remainingCeilingHeadroomUsd": 37.689695376, + "nativeInferenceUsd": "unknown per run; included-credit aggregate valuation is not a measured per-run charge", + "aggregateIncludedUsageDollarEquivalentUsd": 0.71, + "reportingDelayContingencyUsd": 5, + "releasedHistoricalInferenceContingencyUsd": 41.79, + "billingControlsChanged": false, + "accountBindingVerified": true, + "billingProofSha256": "sha256:aed119f7635b7eb46bc89e3d43a9afd163a9ba9ee56671afbd3f0c6c7b16360a", + "privateLedgerSha256": "sha256:5f8e6407b2d87ba30713bedfe06c6bafc3aefcb17f405168659dca95f8f0a2a8", + "budgetIncreaseRequestDisposition": "withdrawn-unneeded; original $100 ceiling retained" + }, + "verification": { + "sourceOwnershipAndProfileTests": { + "status": "passed", + "tests": 31 + }, + "vendoredProbeAndRegistryTests": { + "status": "passed", + "tests": 64 + }, + "freshPublishedLayoutInstallerTests": { + "status": "passed", + "tests": 3, + "modelPromptSent": false, + "qualificationOverrides": false, + "covers": [ + "fresh runner and public server asset installation", + "idempotence", + "corruption refusal with evidence retention", + "linked asset directory refusal", + "unbuilt source bootstrap", + "incomplete published artifact refusal" + ] + }, + "serverPackagingBuild": { + "status": "passed", + "log": "/tmp/copilot-v21-frozen-source-build.log", + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209" + }, + "vendorRuntimeImportGraph": "passed including live/index.js", + "recursiveTypecheck": { + "status": "passed", + "log": "/tmp/copilot-v21-final-recursive-typecheck-authorized.log" + }, + "fullTestRun": { + "status": "failed", + "log": "/tmp/copilot-v21-frozen-source-full-test.log", + "filesPassed": 570, + "filesFailed": 42, + "filesSkipped": 144, + "testsPassed": 9826, + "testsFailed": 5, + "testsSkipped": 5377, + "cause": "PostgreSQL fixture startup failures and skips under exhausted macOS shared-memory slots; retained Git streaming stress timeout. Isolated Linux verification is being prepared; no full-suite pass claimed." + }, + "fullBuild": { + "status": "passed", + "log": "/tmp/copilot-v21-frozen-source-build.log", + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209" + }, + "runnerContracts": { + "status": "eval kernel, protocol and Rust passed; HTTP integration blocked by host PostgreSQL startup even when run alone", + "log": "/tmp/copilot-v21-final-api-authority-serialized.log", + "next": "Run maintained commands in isolated Linux controller with private IPC namespace." + }, + "tokenGates": { + "status": "passed", + "log": "/tmp/copilot-v21-final-token-gates.log" + }, + "runnerApiAuthorityIsolatedLinux": { + "status": "passed", + "testCount": 1852, + "files": 3, + "log": "/tmp/copilot-v21-isolated-linux/api-authority.log", + "assertionsChanged": false + }, + "fullTestIsolatedLinux": { + "status": "failed-attempt-interrupted-after-environment-diagnosis", + "log": "/tmp/copilot-v21-isolated-linux/full-test.log", + "resources": { + "memoryGiB": 6, + "cpuSet": "0-3", + "ipc": "private" + }, + "modelCredentialsProvided": false, + "foreignContainersChanged": false, + "demonstratedEnvironmentCauses": [ + { + "dependency": "lsof", + "evidence": "Missing in initial private controller; unchanged listener-ownership implementation invokes lsof." + }, + { + "dependency": "jq", + "evidence": "Missing in initial private controller; unchanged artifact-helper tests invoke jq." + }, + { + "dependency": "GLIBCXX_3.4.31", + "evidence": "ldd of installed HEIC native binary reports this version missing in Debian 12 controller." + } + ], + "environmentRepair": "Private Ubuntu 24.04 controller with frozen source and assets is building; failed attempts and assertions retained.", + "interruptionProof": "/tmp/copilot-v21-isolated-linux/full-test-first-attempt-interruption.json", + "completeCoverage": false, + "fullSuitePassClaimed": false, + "next": "Run retained failed suites in repaired controller, then complete full maintained checks." + }, + "isolatedControllerDependencies": { + "status": "passed", + "sourceFilesVerified": 8459, + "sourceAndRuntimeUnchanged": true, + "lsof": true, + "jq": true, + "heicNativeBindingLoaded": true, + "proof": "/tmp/copilot-v21-isolated-linux/complete-test-tools-frozen-source-verification.json" + }, + "environmentRegressionFollowup": { + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "imageId": "sha256:e22d05e9a9d28019a35f059ba5ca319a783cac0857d217b1307a856f44325204", + "files": [ + "server/src/__tests__/workspace-runtime.test.ts", + "server/src/services/workspace-runtime-exposure.test.ts", + "server/src/__tests__/workspace-runtime-exposure-reservation.test.ts", + "server/src/__tests__/photon/photon.test.ts", + "server/src/__tests__/photon/channel.integration.test.ts", + "server/src/__tests__/paperclip-skill-utils.test.ts" + ], + "status": "failed", + "exitCode": 1, + "assertionsChanged": false, + "modelCredentialsProvided": false, + "startedAt": "2026-10-05T18:33:57.887520+00:00", + "completedAt": "2026-10-05T18:39:48.841064+00:00", + "testsPassed": 260, + "testsFailed": 2, + "filesPassed": 5, + "filesFailed": 1, + "log": "/tmp/copilot-v21-isolated-linux/environment-regressions.log", + "remainingFailures": [ + { + "name": "bind-race fixture assumes child readiness within 200 ms", + "evidence": "/tmp/copilot-v21-isolated-linux/workspace-fixture-startup-timing.json", + "measuredStartupMs": [ + 221, + 221, + 207, + 207, + 218 + ], + "affectedCodeDiffFromPinnedMainline": "none" + }, + { + "name": "listener-ownership-unavailable fixture exceeds its 20-second deadline", + "status": "unresolved release verification gate", + "affectedCodeDiffFromPinnedMainline": "none" + } + ] + }, + "packageContractFinal": { + "status": "passed", + "tests": 10, + "log": "/tmp/copilot-production-campaign-v21/final-package-contract.log", + "qualificationOnlyAmendmentRecorded": true + } + }, + "remainingReleaseGates": [ + "Obtain exact approval of prepared private controller payload and public frozen image uploads, then execute qualification.", + "Pass 20 Product selectors, seven Runner protocol cases and all three installed-runtime smokes on the frozen v21 source.", + "Complete runner contracts, UI/connection regressions, token gates, recursive typecheck, full tests and build.", + "Promote Copilot consistently while keeping Cursor/Pi qualification unchanged; verify fresh ordinary setup and a canary without qualification overrides.", + "Publish current documentation and qualification evidence; require current-head CI and review before rollout." + ], + "upstreamIntegrated": true, + "mainlineIntegrationRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "rebasedVerification": [ + { + "check": "Copilot profile generation and consistency", + "status": "passed", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d" + }, + { + "check": "Product catalog and report support", + "status": "passed", + "testCount": 44, + "log": "/tmp/copilot-v21-rebased-catalog-authorized.log" + }, + { + "check": "Runner TypeScript build", + "status": "passed", + "log": "/tmp/copilot-v21-rebased-ts-build.log" + }, + { + "check": "Copilot package contracts", + "status": "passed", + "testCount": 42, + "log": "/tmp/copilot-v21-rebased-copilot-contracts.log" + }, + { + "check": "Mainline completion, shutdown and native executor regressions", + "status": "passed", + "testCount": 554, + "log": "/tmp/copilot-v21-mainline-settlement-regressions.log" + }, + { + "check": "Database-dependent arbitration, project tools and workspace finalization", + "status": "passed", + "testCount": 24, + "log": "/tmp/copilot-v21-postgres-recovered-diagnostic.log" + }, + { + "check": "Daytona immutable build content contract, including asset installer", + "status": "passed", + "testCount": 8, + "log": "/tmp/copilot-v21-rebased-image-contract.log" + } + ], + "postgresDiagnostic": { + "cause": "macOS 32 shared-memory slots exhausted; direct initdb stderr confirms shmget ENOSPC", + "evidence": "/private/tmp/copilot-v21-pg-init-diagnostic-__8kvlko/stderr.log", + "cleanup": "/tmp/copilot-production-campaign-v21/owned-postgres-ipc-cleanup.json", + "scope": "one zero-attached IPC segment matched to campaign postmaster.pid and this worktree postmaster.opts; foreign segments retained", + "rerunStatus": "24 tests passed across three formerly failing files; four other files pending current-source check" + }, + "retainedBuildFailures": [ + { + "attempt": "ARM64 provider pack after adding optional native dependencies", + "status": "failed-before-inference", + "cause": "pnpm native-platform .bin shim retained a temporary deployment path", + "log": "/tmp/copilot-production-campaign-v21/arm-provider-pack-build.log", + "fix": "Rewrite installed Copilot platform shims to pack-relative native launchers; retain the existing rejection for every remaining temporary path", + "costUsd": 0, + "modelPromptSent": false + } + ], + "frozenSourceEvidence": { + "path": "/tmp/copilot-production-campaign-v21/frozen-source.json", + "sha256": "sha256:de5c8eb7357f4f450fe6305a814bbe010e9d680880ea5cd3545e0a0ea79bf2c0" + }, + "qualificationOnlyAmendments": { + "packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs": { + "sha256": "sha256:97c0eef3c1261b5a931fa6c3fd1de6a3b5afd39dd6a88c8ed4dea1f82d5e713a", + "patchPath": "/tmp/copilot-production-campaign-v21/qualification-package-contract-amendment.patch", + "patchSha256": "sha256:003a101f0b66a7e2493d8de2a27f67b0bc191c6483e838a9bb00dea1c2c467ec", + "scope": "Offline test assertion now requires exactly the three pinned Copilot optional dependencies. No executable or package input changed." + } + }, + "platformPacks": { + "providerPackDigest": "sha256:e0cd86b31885353f3eba635a040ed53a9bf01526c88c75db4da69890a74c3f5f", + "intelProviderPackDigest": "sha256:569cbc72e28a0c8459097ff6d96fbf81f445724fd8cf23f887d80d44ee0d6159", + "runnerPackageSha256": "sha256:4ba7a1436afa7a4efb59dfe0a79237861e8fccc337238873dbd7e4932df24c34" + }, + "daytonaImage": { + "imageTag": "ghcr.io/paperclipai/paperclip-daytona-runner:copilot-qualification-7842dba3e92257219da8", + "expectedIndexDigest": "sha256:d228ee6bd84717410e2fcc79a62cc781f92b787a9d09781786b7ac19aae45899", + "status": "Built and Linux installed smoke passed. Public upload not executed; exact upload approval is pending after two automatic approval review rejections." + }, + "isolatedControllerEnvironment": { + "sourceRevision": "1d4bc65c3cf4c3a74da59f82f306a718e3180209", + "profileDigest": "sha256:3bd433d9835ab6998ee712da41cad724006bb85d444f0cf96550708565605b7d", + "providerPackDigest": "sha256:7a4f2c6fdfa395a5f45857781ebfa08cc118cbd38548d9de185ba80eef660150", + "runnerdSha256": "sha256:fcaaccaba6f85ec9870fe2f8eeeedf48ffab68cc0f6d9dc348ee0e39ac092bc3", + "nodeSha256": "sha256:7fde7b8afa198da66257f42ee2001d874c7355631e6d1579a5fb5ef1f246df4c", + "imageId": "sha256:e22d05e9a9d28019a35f059ba5ca319a783cac0857d217b1307a856f44325204", + "imageIndexDigest": "sha256:e22d05e9a9d28019a35f059ba5ca319a783cac0857d217b1307a856f44325204", + "publication": "private local controller only", + "credentialsIncluded": false, + "sourceTreeProofSha256": "sha256:8506e9b8456d571b2f170ed6db25db7d895b5f240c24099078d44626337feecd", + "ipcNamespace": "private", + "testEnvironmentAmendment": { + "reason": "Install lsof, jq and compatible C++ runtime required by unchanged regression tests", + "productionSourceChanged": false, + "previousImageId": "sha256:f59ad48e11957c717da81b76aae8c036853c173ae7b646d732ece03b30a0ddd8", + "proofPath": "/tmp/copilot-v21-isolated-linux/complete-test-tools-frozen-source-verification.json", + "proofSha256": "sha256:9c5f2354431325a44337e674882d17e164fc36c230cb771e9d10537526f10e3b" + } + }, + "nativePrivateController": { + "status": "prepared; upload rejected by automatic approval review; exact user approval pending", + "payloadProof": "/tmp/copilot-v21-native-controller/payload-proof.json", + "payloadSha256": "sha256:4f6af192e8c60c3de910c097be4dd07d543b6dc116c518a711fe34ab997f23bf", + "payloadBytes": 1016912183, + "knownCredentialMatches": 0, + "filesScanned": 14300, + "destination": "copilot-v21-native-private-controller-01, private Daytona sandbox, public=false", + "sandboxCreated": false, + "payloadUploaded": false, + "plannedInfrastructureReservationUsd": 3, + "reservationActuallyCreated": false, + "plannedTtlMinutes": 450, + "purpose": "Native x64 qualification controller; frozen workload image and application source remain unchanged." + }, + "productionPromotionDraft": { + "applied": false, + "proofPath": "/tmp/copilot-production-campaign-v21/promotion-draft-provenance.json", + "files": 6, + "requiresQualificationPasses": true, + "transportAdmission": "Ordinary server will explicitly admit the validated qualified Copilot descriptor internally; no qualification environment override required." + }, + "historicalStatus": "Awaiting exact payload/destination approval; no active paid run; production admission remains gated", + "supersededAt": "2026-10-06", + "supersededBy": { + "checklist": "2026-10-03-copilot-production-readiness.md", + "upstreamTargetRevision": "a6306ba606eb87c89b9ef0344e9fe8e0025580f9", + "profileVersion": 22, + "sourceFreezeComplete": false, + "historicalPassesQualifyCurrentSource": false + } +} diff --git a/doc/plans/2026-10-08-copilot-qualification-v30.json b/doc/plans/2026-10-08-copilot-qualification-v30.json new file mode 100644 index 00000000000..e973191017b --- /dev/null +++ b/doc/plans/2026-10-08-copilot-qualification-v30.json @@ -0,0 +1,70 @@ +{ + "schema": "paperclip.copilot.qualification-summary/v1", + "recordedAt": "2026-10-08T12:31:43.458335+00:00", + "sourceRevision": "c4b5d40bcfd51e6cacccc1ea1343305b05735641", + "profileVersion": 30, + "profileDigest": "sha256:e21c4a43a937ae84e94726ef93227e8b931fa5547876ff0c5ecff304656d81e9", + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "product": { + "passed": 20, + "local": 10, + "daytona": 10, + "cleanupPassed": true, + "automaticRetries": 0, + "resultHashes": { + "copilot-protection.runner-acpx-copilot.local.attached-async-settlement": "sha256:08ca542b48656ef60a36f4c700f29df59607b830574e1b88f0c603927a25e0c9", + "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write": "sha256:146c01ce7ab530b8c5f5217a18b9d1982fae3b0750a6f98eb9ca62133949aa12", + "native-active-stop.runner-acpx-copilot.local.pending-permission-stop": "sha256:7033b9d8f82a3af9b6ba18a5d1a8b561222c59ec110de2c3c050599eea6e3e4a", + "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death": "sha256:68877ebd94c1e71e8b8993fb4b6430a163b8bec344442b8e32f11a7ec3cb018e", + "rich-acp-warm-continuity.runner-acpx-copilot.local.warm-three-turn": "sha256:7e9a282cee656b1732440f3b2c2a3f46d20616d6ae361e3a2fca28aff9a4b9e1", + "extended-harnesses.runner-acpx-copilot.local.hello-complete": "sha256:6ef6ffbc1095a4eb8b6bf3d12d1a4bd992d9cbb950cbaafdaad3def717958a24", + "extended-harnesses.runner-acpx-copilot.local.file-edit-validate": "sha256:c5f566b8ac29569313d6f37de1b48b27f39eb82f9b3f7f0f25f4e125c32d0d0b", + "extended-harnesses.runner-acpx-copilot.local.question-resume-complete": "sha256:7670ab0eec5d4c561658c2aa6341809357d8e2d0edd1cf1d3a48d1a02fd9249a", + "extended-harnesses.runner-acpx-copilot.local.plan-approve-complete": "sha256:f2a277571046f33032c53d9e9726720280f03d9383573b8bc1342ff7d3249612", + "extended-harnesses.runner-acpx-copilot.local.structured-question-restart-resume": "sha256:e1acb10539eb006a6974ac1366fe3395c1fe742fc8d85da2ad766dd0f2f1f261", + "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement": "sha256:fd219eadb5d5a70ec9d192a194064b529c6a8a709272cb24f7cf78905e683e7c", + "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write": "sha256:9243b5b4c2ee04cae99855443df579ba14923f29f14108f0dd05c94076f88e8d", + "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop": "sha256:f8f7e07016d450ea0476154c266b482fcfd9feee41bf0f39eb9d297e5fa13cf0", + "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death": "sha256:979a95e226c1ab5ef31178b70961de5fb4c6a54dfc5979166bc6f2d2983cc583", + "rich-acp-warm-continuity.runner-acpx-copilot.daytona.warm-three-turn": "sha256:cea972b23f46ed063d49582788d634d628ee86e2dc870d87e5ae0085f0a20fae", + "extended-harnesses.runner-acpx-copilot.daytona.hello-complete": "sha256:7eaaed3c36257773b05c6701edd29d1ae5636b0892b6f64e503224453ff21766", + "extended-harnesses.runner-acpx-copilot.daytona.file-edit-validate": "sha256:9d06532c52675743a1481675dc28e5e7c8052faed4007d3c0bcdeb7860c4cac3", + "extended-harnesses.runner-acpx-copilot.daytona.question-resume-complete": "sha256:ecc1c077842cb841888fad0ad492e02f4736e41170b33f00917220acdb074cdc", + "extended-harnesses.runner-acpx-copilot.daytona.plan-approve-complete": "sha256:e092f188a545135cb7c715fa8ce5b86c9d987dd1ca1e0e0d37545c8f79b1cc9d", + "extended-harnesses.runner-acpx-copilot.daytona.structured-question-restart-resume": "sha256:951b248c0ba78c134dbb96fbf359eb173fe0c37637797ff2b6efbff4e7c8abef" + } + }, + "protocol": { + "passed": 7, + "assertions": 46, + "cleanupPassed": true + }, + "ordinaryInstallationCanaries": { + "local": { + "passed": true, + "qualificationOverrides": [] + }, + "daytona": { + "passed": true, + "qualificationOverrides": [], + "artifactSha256": "sha256:f027a4aa09e216e60de84748477ecefcc10e46fa8efd80340316fbf6641edfe0", + "allOwnedSandboxesAbsent": true, + "oneFinalResponse": true, + "acceptedCompletionEvidence": true + } + }, + "publicImage": "ghcr.io/paperclipai/paperclip-daytona-runner@sha256:2ccfe999e7794ea1577e581840743a466663143c4303a518e06b3f1e0faa58fb", + "billingEntity": "github", + "nativeUsd": null, + "retainedFailures": [ + "Historical roster prerequisite rejected before provider launch", + "Daytona controller-local artifact path prerequisite rejected before provider launch", + "Local evidence collector initially read only its first event page", + "Manual metadata Test intentionally archived its sandbox; the owned archive was subsequently deleted" + ], + "ciPassed": false, + "reviewPassed": false, + "shipped": false, + "scope": "Completed v30 qualification. Master recovery compatibility is evaluated separately; this record does not qualify changed runtime bytes." +} diff --git a/doc/plans/2026-10-08-copilot-qualification-v31.json b/doc/plans/2026-10-08-copilot-qualification-v31.json new file mode 100644 index 00000000000..8cdd71a7319 --- /dev/null +++ b/doc/plans/2026-10-08-copilot-qualification-v31.json @@ -0,0 +1,65 @@ +{ + "schema": "paperclip.copilot.production-readiness-current/v1", + "updatedAt": "2026-10-08T14:45:17.483258+00:00", + "runtimeSourceRevision": "4acb084fc5b6805a483bf419c2cdc6de3f7ec13d", + "ordinaryInstallationSourceRevision": "d99959f00b62ad2099b5cdaca3f7656dcb88a8e8", + "profileVersion": 31, + "profileDigest": "sha256:4a22e4c50fd213c79cd02fd3c5369aef2256ac719762c62f5b70c691cd47e76b", + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "historicalFullCampaign": { + "record": "2026-10-08-copilot-qualification-v30.json", + "profileVersion": 30, + "productCells": 20, + "daytonaCells": 10, + "protocolCases": 7, + "unchangedBoundInputsExceptSavedPromptCompatibility": true, + "currentProfileFullCampaignClaimed": false + }, + "currentProfile": { + "installedPlatforms": [ + "darwin-arm64", + "darwin-x64-via-rosetta", + "linux-x64-via-local-emulation" + ], + "authenticatedMetadata": true, + "ordinaryLocalCanary": true, + "ordinaryLocalCanaryProofSha256": "sha256:fd0b914d0d787c906c054ffb425de1bdc1b3b1684c2e1ec20eadeabeafbbd4d0", + "ordinaryLocalFinalResponseCount": 1, + "ordinaryLocalArtifactBytes": 30, + "ordinaryLocalCleanupConfirmed": true, + "ordinaryDaytonaCanary": false, + "exactImagePublicationApprovalPending": true + }, + "reviewRepairs": { + "attachedEarlyFinish": { + "independentRetainedLiveTraces": 2, + "newProviderCalls": 0, + "historicalResultsRewritten": false, + "proofSha256": "sha256:994c80a8e288815efdbb15688b2fc96e61ffa8f905e9e3735e3ba13dd9fcafd9" + }, + "smokeProviderSigkillFallback": true, + "closedObserverFailureCodesRetained": true, + "focusedProductTests": 214, + "existingProductSkips": 1, + "smokeCleanupTests": 7, + "productTypecheck": true + }, + "dependencyPrerequisite": { + "pullRequest": "https://github.com/paperclipai/paperclip/pull/15572", + "mergedRevision": "941a3fa991aeb97eb1ac390c65b7973b5f6de1ad", + "generatedLockSha256": "sha256:13ea85cd23a3772889a083020918646ff68231eeace607ab50d2093bc33e3201", + "currentHeadCiPassed": true, + "greptileScore": "5/5" + }, + "billing": { + "entity": "github", + "nativeUsd": null, + "budgetReset": false, + "remainingApprovedUsd": 30.68 + }, + "release": { + "currentStackCiReviewPending": true, + "shipped": false + } +} diff --git a/doc/plans/2026-10-08-copilot-qualification-v32.json b/doc/plans/2026-10-08-copilot-qualification-v32.json new file mode 100644 index 00000000000..d38d7c465b7 --- /dev/null +++ b/doc/plans/2026-10-08-copilot-qualification-v32.json @@ -0,0 +1,55 @@ +{ + "schema": "paperclip.copilot.production-readiness-current/v1", + "updatedAt": "2026-10-08T15:31:29.235437+00:00", + "runtimeSourceRevision": "c049102fba12850b755a962d74e22635c8585724", + "reviewSourceRevision": "8f74c41cd718568346ac786bc6e45e446dce17ac", + "profileVersion": 32, + "profileDigest": "sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc", + "copilotVersion": "1.0.88", + "model": "gpt-5.6-luna", + "historicalRecords": [ + "2026-10-08-copilot-qualification-v30.json", + "2026-10-08-copilot-qualification-v31.json" + ], + "changedBoundInputsFromV31": [ + "agentProfileVersion", + "profileCompatibilitySourceSha256", + "metadataProbeSourceSha256" + ], + "reviewRepairs": { + "authenticatedModelRequiredBeforeAdoption": true, + "unavailableModelApiRejectionsVerified": true, + "setupUiTests": 53, + "focusedApiTests": 8, + "utf8TransportReaderRegressions": true, + "focusedRunnerTests": 117, + "credentialFreeProtocolFixtures": 13, + "runnerServerUiTypecheck": true, + "tokenGates": true, + "daytonaActiveProfileFixtureSynchronized": true, + "daytonaImageContractTests": 9 + }, + "currentProfile": { + "frozen": false, + "installedPlatforms": [], + "authenticatedMetadata": false, + "productCells": 0, + "protocolCases": 0, + "ordinaryLocalCanary": false, + "ordinaryDaytonaCanary": false, + "imagePublished": false + }, + "billing": { + "entity": "github", + "nativeUsd": null, + "budgetReset": false, + "remainingApprovedUsd": 80.36, + "aggregateIncludedCreditsObserved": 184.04, + "reportingDelayContingencyUsd": 5 + }, + "release": { + "currentStackCiReviewPending": true, + "codeOwnerApprovalRequired": true, + "shipped": false + } +} diff --git a/doc/plans/2026-10-08-copilot-qualification-v33.json b/doc/plans/2026-10-08-copilot-qualification-v33.json new file mode 100644 index 00000000000..8ba5fdcf701 --- /dev/null +++ b/doc/plans/2026-10-08-copilot-qualification-v33.json @@ -0,0 +1,451 @@ +{ + "schema": "paperclip.copilot.production-qualification/v33", + "updatedAt": "2026-10-09T15:05:45.536425+00:00", + "qualifiedApplicationSource": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "reviewedApplicationSource": "50d54d78e552869cae154d88ade68063191a547e", + "runtimeArtifactSource": "941fbcb664a885eea713e732edc10ac133c62238", + "masterBaseline": "0ac194450a48a407450921a16c3ef8684dcb85ca", + "copilotVersion": "1.0.88", + "profileVersion": 33, + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "protocol": { + "passed": 7, + "selected": 7, + "assertionsPassed": 46, + "caseResults": [ + { + "runId": "copilot-v33-final-protocol-get-task-context-02", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 4, + "artifactSha256": "sha256:752da200cbe3e852fbd446fe75b2a45f39a98e15c2f1ac5cdc161243111328a6", + "scoreSha256": "sha256:00e58b1292581e2645b738af7da692859cb76ce638a02de09bda910ee1456ae9", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-context-before-action-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 7, + "artifactSha256": "sha256:97df880f40aab985297981bc51aaa83947bebcb26d8a739b08c7162822815709", + "scoreSha256": "sha256:9f98e4aae60990e10d8f7a273ae616be50a295e401c7ba3f27bc9e7b8162cffa", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-create-task-document-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 7, + "artifactSha256": "sha256:1e1f408c9bcf797769acc0942deb42ef7b276bd6080a0a427c634460b3224e1f", + "scoreSha256": "sha256:c0a3e17db2db039962a4759f597937d1bb0d5fe3ac78948aae31ea110e24d72d", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-finish-task-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 5, + "artifactSha256": "sha256:e7a893d3954190b6839a49275a1ee74052aa8d5f51c07142906c304a986da839", + "scoreSha256": "sha256:e26558f48ea16349a4c80fa0e786aeea2c054d3acc98f1f251507b7ae7d12125", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-request-human-confirmation-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 5, + "artifactSha256": "sha256:c30a0539a3f078e77f85a4309d479412f55277c773f82217e56aa149e179867c", + "scoreSha256": "sha256:295027e137ec4a40c9b7b7c8c956fe33a1adc53256d3c941006a5209a02cd0b8", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-workflow-context-document-progress-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 9, + "artifactSha256": "sha256:14c8a4ebbde4b0eb059ea49ec0803463eec736c00fbacaeff10baed4d7987e28", + "scoreSha256": "sha256:e2a5999981ac6d71a472c01644e53bc46b1348b525b729dc344d57e90513dc40", + "cleanupConfirmed": true, + "automaticRetries": 0 + }, + { + "runId": "copilot-v33-final-protocol-workflow-governed-wait-01", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "passed": true, + "checksPassed": 9, + "artifactSha256": "sha256:b503d260edc3b08555e1cb1fe79367d779a919a27b3cfc516e3456f9f3043334", + "scoreSha256": "sha256:e067915a6adc859b5d2ff1e5a4a63a1ad3b0b52382f9f5410fbc366b59ba0b5e", + "cleanupConfirmed": true, + "automaticRetries": 0 + } + ] + }, + "product": { + "localPassed": 10, + "localSelected": 10, + "daytonaPassed": 10, + "daytonaSelected": 10, + "cells": [ + { + "selector": "native-active-stop.runner-acpx-copilot.local.pending-permission-stop", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:a1a8568c2b6e3e0fb83e429fc7516f954577562ea861a8c034c7f690df57cb23", + "cleanupConfirmed": true + }, + { + "selector": "native-active-stop.runner-acpx-copilot.daytona.pending-permission-stop", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:c97343873c4f6acd42d16c1f9a0142da2bcc005d32406e68311eddb482da695c", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-pending-stop-01", + "automaticRetries": 0 + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.local.pending-permission-provider-death", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:2915c499e9c93bc465867e71fdb5c8afe3f003d6de1dae3fc606f2dc697a84a9", + "cleanupConfirmed": true + }, + { + "selector": "copilot-provider-death.runner-acpx-copilot.daytona.pending-permission-provider-death", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:583d2f1de67364578476203bdebc77d92257613ba6b46fc126668feb194997d1", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-provider-death-01", + "automaticRetries": 0 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.native-permission-deny-write", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:1d13e096ef032acc0294705d1da4580e0ed4a8589ba8c5138bd8361568fcfb0e", + "cleanupConfirmed": true + }, + { + "selector": "copilot-protection.runner-acpx-copilot.local.attached-async-settlement", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:75a19460e29997e4f5ad45c857d4b752f3452b6e1b36270ace95d201dfbfe058", + "cleanupConfirmed": true + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.native-permission-deny-write", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:c80efe711eb5b410edc840d4f3cda13d4f1d2a362d3c7525f8e82806ca0d8c7d", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-denied-write-01", + "automaticRetries": 0 + }, + { + "selector": "copilot-protection.runner-acpx-copilot.daytona.attached-async-settlement", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:8ef732bb5d2823952cfeda7f1c8311d7de8f41ba0804299eec548332d8d16da5", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-attached-02", + "automaticRetries": 0 + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.local.warm-three-turn", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:78dd1f84f77ca88923bf3d36c7d0c00c84e3c79385e9e19d6125c47b14cb68fc", + "cleanupConfirmed": true + }, + { + "selector": "rich-acp-warm-continuity.runner-acpx-copilot.daytona.warm-three-turn", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:0feab84de96215589db7b847fb915ed96b4246c48a25220cfbdfb8fe972e06c4", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-warm-continuity-01", + "automaticRetries": 0 + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.hello-complete", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:67cd97fa41f68e40035e258e567f091168f7b29f63f90d7a8899cba84db06053", + "cleanupConfirmed": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.question-resume-complete", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:01bbc56ee046539e7cae842c3f27c1e99c6f2a2dbc908bc6a0c52de78328ac10", + "cleanupConfirmed": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.plan-approve-complete", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:1603983a6b7b75ceb8adace86a52beaac0c32da6dd08af21b04dec2070061715", + "cleanupConfirmed": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.structured-question-restart-resume", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:0274dde5c70b5ab5d08d858ca7c7667399a70a7015d8a2d0937a94a1692f7d30", + "cleanupConfirmed": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.local.file-edit-validate", + "status": "passed", + "sourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "resultSha256": "sha256:1322c4f56b80b0fd3020e3f49aafaa9ecfadbba8d4ff2aadc119a1386348c354", + "cleanupConfirmed": true + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.hello-complete", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:95758a04d9bce33ff2f230235770020ca12147d62de1616347118b696839dd8c", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-hello-01", + "automaticRetries": 0 + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.question-resume-complete", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:2d83e99f42cd641192899de8ac5385b31b9e47bab63d53cc76e66113f78725fd", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-question-01", + "automaticRetries": 0 + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.plan-approve-complete", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:5f8148676b0282a08f386c86cdd01bc40eb8141a75e0c8ba5964ce5c9776fbf7", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-plan-01", + "automaticRetries": 0 + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.structured-question-restart-resume", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:3d5004d1cb4ae4e947d4c025fb5b22398e932ea87b68d207d553341fb01f3595", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-controller-restart-01", + "automaticRetries": 0 + }, + { + "selector": "extended-harnesses.runner-acpx-copilot.daytona.file-edit-validate", + "status": "passed", + "sourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "resultSha256": "sha256:0d80f636006d75ad1f7e371556a5207f403e4ac83ca0d73515e1f5abe0ca074e", + "cleanupConfirmed": true, + "applicationExecutableSourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "originalQualifiedApplicationSourceRevision": "024e1422f9c7b43ff1b083e7d8a25e1969dc4acf", + "attemptLabel": "copilot-v33-final-daytona-file-edit-01", + "automaticRetries": 0 + } + ] + }, + "image": { + "tag": "ghcr.io/paperclipai/paperclip-daytona-runner:copilot-qualification-22fd2a8ae3414eee317e", + "indexDigest": "sha256:65fc4f30c15b3d744534ce3d7240dfbf3d3ff081da6d51180bd89f2802bd6e56", + "knownCredentialMatches": 0, + "filesScanned": 225852, + "published": true, + "exactApprovalPending": false, + "reviewedServerDeltaChangesImage": false, + "linuxManifestDigest": "sha256:9606c290372884ee74438408e8115088320979c8614a3dbd9d77be2b548a896e", + "anonymousPublicRetrievalVerified": true, + "publicationProofSha256": "sha256:1a3f274fad5f764e2c92c7f1116871d70e0a735801dad564fe5ad49c620aed95" + }, + "installedPlatformSmokes": { + "darwin-arm64": "passed", + "darwin-x64": "passed under Rosetta", + "linux-x64": "installed smoke passed under Docker emulation; ten native Daytona cells and normal installed canary passed" + }, + "reviewedSetupDelta": { + "source": "50d54d78e552869cae154d88ade68063191a547e", + "changedFiles": [ + "server/src/services/copilot-connection-probe.ts", + "server/src/routes/agents.ts", + "server/src/__tests__/copilot-connection-probe.test.ts", + "server/src/__tests__/agent-test-environment-routes.test.ts", + "server/src/__tests__/ai-connections.test.ts" + ], + "focusedRegressionsPassed": 63, + "serverTypecheckPassed": true, + "serverBuildPassed": true, + "qualifiedProfileChanged": false, + "selectedLocalPolicyChanged": false, + "selectedDaytonaPolicyChanged": false, + "ordinaryInstallCanary": "passed on reviewed source 50d54d78e552869cae154d88ade68063191a547e" + }, + "canaries": { + "ordinaryLocal": "passed on reviewed source", + "ordinaryDaytona": "passed on reviewed source", + "ordinaryLocalEvidence": { + "sourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "definition": "Exact hexadecimal bytes; same expected file and final response", + "artifactBytes": 30, + "proofSha256": "sha256:5e826570b86129ea16055d44f99978bf2414edac4791cb7e75a72b3f14e1b43a", + "acceptedCompletionEvidence": true, + "receiptReceiverAcknowledged": true, + "finalResponseCount": 1, + "cleanupConfirmed": true, + "qualificationOverrides": [], + "previousFailureRetained": true + }, + "ordinaryDaytonaEvidence": { + "sourceRevision": "50d54d78e552869cae154d88ade68063191a547e", + "qualificationSourceRevision": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "runtimeSourceRevision": "941fbcb664a885eea713e732edc10ac133c62238", + "profileVersion": 33, + "profileDigest": "sha256:482c4e997b4690c7e4a5375dd035560aaad69000f8f5291904d76f8c3a88284a", + "model": "gpt-5.6-luna", + "artifactBytes": 32, + "acceptedCompletionEvidence": true, + "finalResponseCount": 1, + "qualificationOverrides": [], + "nativeUsd": null, + "image": "ghcr.io/paperclipai/paperclip-daytona-runner@sha256:9606c290372884ee74438408e8115088320979c8614a3dbd9d77be2b548a896e", + "cleanup": { + "allOwnedSandboxesAbsent": true, + "ownedTaskSandboxAbsent": true, + "manualTestArchiveIntentionallyRetainedThenDeleted": true, + "loginDiscoverySandboxesAbsent": true, + "nativeRecordClosed": true, + "pendingRuntimeRequests": 0, + "providerExitUnconfirmed": false, + "ownedNativeControllerProcessesRemaining": [], + "liveRuns": 0 + }, + "proofSha256": "sha256:d12671b343c66437e389aa03dc7a785dca58cabb617b46566e10a7c74ed97b6e", + "receiptReceiverAcknowledged": true, + "metadataTestNoModelPrompt": true + } + }, + "budget": { + "remainingConservativeAllowanceUsd": 70.5, + "nativeUsd": null, + "billingEntity": "github", + "budgetReset": false, + "historicalAttemptsRetained": true, + "aggregateAccountReconciliationRefreshedAt": "2026-10-09T15:03:43.221645+00:00" + }, + "retainedFailedAttempts": [ + { + "attempt": "v32 local pending-permission Stop", + "cause": "Actual target hidden by lexical /tmp versus /private/tmp workspace alias check", + "runtimeRepair": "profile v33 safe canonical target mapping" + }, + { + "attempt": "v33 pre-provider Product startup", + "cause": "Vite Connect non-router stack and route metadata", + "runtimeRepair": "server router-stack guards; credential-free startup and all ten local cells pass" + }, + { + "attempt": "v33 protocol admission attempt 1", + "cause": "private eval closed allowlist lacked exact v33 profile; no provider session", + "runtimeRepair": "private roster admission synchronized; all seven cases pass" + }, + { + "attempt": "v33 ordinary local canary 1", + "cause": "Provider copied Markdown underscore escapes into a 34-byte file instead of the requested 30 bytes", + "runtimeRepair": null, + "definitionFollowup": "One changed definition specified exact hexadecimal bytes; expected file and final response unchanged; follow-up passed; original failure and cleanup retained" + }, + { + "attempt": "v33 native Daytona attached-command attempt 1", + "cause": "Controller docker-cp extraction omitted seven empty Cursor asset directories, so full provider-pack tree validation rejected before native provider start. Image files and Copilot tree matched.", + "runtimeRepair": null, + "artifactRepair": "Restore only the seven verified empty directories from the exact image. Production offline pack validator passed; explicit attached-command attempt 2 passed. Image/profile/source unchanged; original failure retained." + } + ], + "releaseGates": [ + "Resolve current master 57e977be72f37dcf31acff64b3e5dcbb64a92cd0 conflicts, preserving mainline Pi/Cursor and Copilot fixes", + "Bind changed runtime source to a new Copilot profile; cloud build and qualification required for changed bytes", + "Current-head CI and fresh clean review", + "Separate code-owner approval, normal merge and shipped canary" + ], + "productionReady": false, + "shipped": false, + "reviewedFixtureDelta": { + "sourceRevision": "dc5ebe3f92d405e284b096f5add797864bb0f74e", + "changedFiles": [ + "tests/runner-e2e/copilot-protection-flow.ts", + "tests/runner-e2e/copilot-protection-settlement.test.ts" + ], + "finding": 4222861745, + "focusedRegressionsPassed": 82, + "productTypecheckPassed": true, + "caseDefinitionsChanged": false, + "behaviorAssertionsWeakened": false, + "newProviderCalls": 0, + "runtimeAndImageInputsChanged": false, + "imageContentId": "22fd2a8ae3414eee317e8d9df9fa6e526e43874d590bec84081ad00ff290a472", + "contentIdentityArguments": [], + "originalLiveGradesRelabeled": false, + "sourceHashes": { + "tests/runner-e2e/copilot-protection-flow.ts": "sha256:b396a29ed3f40c398591d6687798a31234c24f94753c5c048b831a84b7fef8c7", + "tests/runner-e2e/copilot-protection-settlement.test.ts": "sha256:bdbe4bb254ddfdfd0983a164e81a6b9e33c6f79c14d653ef2446a4f6c9f61a83" + } + }, + "reviewedQualificationControllerSource": "5f9b5e2043c4ec8e5cad667eaa4afbb09f1a9638", + "frozenV33QualificationComplete": true, + "currentMainlineShippingBlocker": { + "master": "57e977be72f37dcf31acff64b3e5dcbb64a92cd0", + "runtimePrMergeState": "DIRTY", + "qualificationCI": { + "runId": 37829278179, + "attempt": 2, + "serverShard": 6, + "testStep": "cancelled after runner shutdown signal", + "assertionFailureObserved": false, + "unchangedRerunsUsed": 1, + "allGreen": false + }, + "v33EvidenceRelabeledAsNewBuild": false + } +} diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index 01fff8dce6f..27ade493783 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -33,7 +33,7 @@ COPY cli/package.json ./cli/package.json # The complete resolved lock (including transitive integrity hashes) is reviewed. # Reject registry-time drift BEFORE installing packages or running lifecycle code. # Refresh this digest together with source/provider dependency changes. -ARG PAPERCLIP_RUNNER_LOCK_SHA256=440f0ccd8a383b32a576dc53b4191d73bb03a91ba0dabfa1c3d79c3039e01aa5 +ARG PAPERCLIP_RUNNER_LOCK_SHA256=84e60b0170df23ec9026e98068501a9e52d4fc76693c25f8db5d1ef6ef429c94 # pnpm 9 subtracts PNPM_WORKERS from available CPUs; it is not a worker count. # Subtract all available CPUs to select its minimum (one tarball worker). RUN export PNPM_WORKERS="$(node -p 'require("node:os").availableParallelism()')" \ diff --git a/packages/adapters/cursor-local/src/server/execute.test.ts b/packages/adapters/cursor-local/src/server/execute.test.ts index 61f5a05dd3f..03410406e13 100644 --- a/packages/adapters/cursor-local/src/server/execute.test.ts +++ b/packages/adapters/cursor-local/src/server/execute.test.ts @@ -85,10 +85,14 @@ function createFreshLeaseSandboxRunner(options: { }) { let counter = 0; const installCommands: string[] = []; + const fixtureBin = path.join(options.homeDir, "fixture-system-bin"); + const fixtureReady = (async () => { + await fs.mkdir(fixtureBin, { recursive: true }); + await fs.symlink(process.execPath, path.join(fixtureBin, "node")); + await fs.writeFile(path.join(fixtureBin, "curl"), "#!/bin/sh\nexit 97\n", { mode: 0o755 }); + })(); const systemPath = [ - "/usr/local/bin", - "/opt/homebrew/bin", - "/usr/local/sbin", + fixtureBin, "/usr/bin", "/bin", "/usr/sbin", @@ -107,6 +111,7 @@ function createFreshLeaseSandboxRunner(options: { onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise; onSpawn?: (meta: { pid: number; startedAt: string }) => Promise; }) => { + await fixtureReady; counter += 1; const args = [...(input.args ?? [])]; if (args[1] === SANDBOX_INSTALL_COMMAND) { @@ -114,7 +119,8 @@ function createFreshLeaseSandboxRunner(options: { args[1] = buildInstallSimulationCommand(options.installCommandPath, options.captureDir); } - const inheritedPath = input.env?.PATH ?? systemPath; + // A fresh remote lease has its own PATH, independent of installed host agents. + const inheritedPath = systemPath; const pathWithLocalBin = `${path.join(options.homeDir, ".local", "bin")}${path.delimiter}${inheritedPath}`; const env = { ...(input.env ?? {}), @@ -122,6 +128,9 @@ function createFreshLeaseSandboxRunner(options: { PATH: pathWithLocalBin, }; + if (["agent", "cursor-agent"].includes(path.basename(input.command)) && input.command !== options.installCommandPath) { + throw new Error("Fresh sandbox fixture refused an agent outside its installation path"); + } return await runChildProcess(`cursor-fresh-lease-${counter}`, input.command, args, { cwd: input.cwd ?? process.cwd(), env, diff --git a/packages/paperclip-runner/scripts/copilot-provider-pack-smoke.mjs b/packages/paperclip-runner/scripts/copilot-provider-pack-smoke.mjs index 3eeb6cac834..25e6ae59bc7 100644 --- a/packages/paperclip-runner/scripts/copilot-provider-pack-smoke.mjs +++ b/packages/paperclip-runner/scripts/copilot-provider-pack-smoke.mjs @@ -1,10 +1,11 @@ import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { once } from "node:events"; import { mkdir, readFile, realpath, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; -import { withCopilotSmokeResources } from "./copilot-smoke-resources.mjs"; +import { stopCopilotSmokeChild, withCopilotSmokeResources } from "./copilot-smoke-resources.mjs"; // Build verification only. No prompt, credential, model request, or runtime // installation occurs; the provider runs offline with isolated state. @@ -13,9 +14,21 @@ const manifest = JSON.parse(await readFile(join(pack, "provider-pack.json"), "ut // Bind the audited pack explicitly, just as the verified sidecar launcher does. process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT = pack; process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST = join(pack, "package.json"); -const candidate = manifest.payload.candidateProviders?.copilot; +const released = manifest.payload.providers?.copilot; +const pending = manifest.payload.candidateProviders?.copilot; +assert.notEqual(Boolean(released), Boolean(pending), "One Copilot asset registration is required"); +const candidate = released ?? pending; +// Rosetta must translate each fresh verified executable snapshot. Measured cold +// startup of the pinned Intel binary exceeds 20 seconds on an ARM host. Native +// platform smoke bounds stay unchanged; translation gets one bounded attempt. +let translatedIntel = false; +if (process.platform === "darwin" && process.arch === "x64") { + try { translatedIntel = execFileSync("/usr/sbin/sysctl", ["-n", "sysctl.proc_translated"], { timeout: 1_000, encoding: "utf8" }).trim() === "1"; } + catch { /* Native Intel has no translation flag. */ } +} +const initializeTimeoutMs = translatedIntel ? 60_000 : 20_000; assert.equal(candidate?.version, "1.0.88"); -assert.equal(candidate.qualification, "pending"); +assert.equal(candidate.qualification, released ? "qualified" : "pending"); const { assertAcpxProfileEnvironment, verifyAcpxProfileInstallation } = await import(pathToFileURL(join(pack, "dist/drivers/acpx/profile-installation.js"))); const { resolveQualifiedAcpxProfile } = await import(pathToFileURL(join(pack, "dist/drivers/acpx/qualified-profiles.js"))); const { COPILOT_ACP_CLIENT_CAPABILITIES } = await import(pathToFileURL(join(pack, "dist/drivers/acpx/copilot-events.js"))); @@ -44,7 +57,7 @@ await withCopilotSmokeResources(installation, async ({ lease, root, fixture, fix let buffer = ""; let stderr = ""; let total = 0; - const timeout = setTimeout(() => reject(new Error(`Copilot ACP initialize timed out: ${stderr.replaceAll(root, "/fixture/workspace").replaceAll(pack, "/fixture/provider-pack").slice(0, 4000)}`)), 20_000); + const timeout = setTimeout(() => reject(new Error(`Copilot ACP initialize timed out: ${stderr.replaceAll(root, "/fixture/workspace").replaceAll(pack, "/fixture/provider-pack").slice(0, 4000)}`)), initializeTimeoutMs); timeout.unref(); child.once("error", reject); child.once("exit", () => reject(new Error(`Copilot exited before initialize: ${stderr.replaceAll(root, "/fixture/workspace").replaceAll(pack, "/fixture/provider-pack").slice(0, 4000)}`))); @@ -84,18 +97,16 @@ await withCopilotSmokeResources(installation, async ({ lease, root, fixture, fix process.stdout.write(`${JSON.stringify({ schema: "paperclip.copilot-provider-pack-smoke/v1", sourceRevision: manifest.payload.runnerSourceRevision, providerPackDigest: manifest.digest, platform: process.platform, architecture: process.arch, + translatedIntel, initializeTimeoutMs, candidate, executableSha256: `sha256:${createHash("sha256").update(executable).digest("hex")}`, registryLaunch: "verifyAcpxProfileInstallation/openCommand/spawn", initializeRequestId: 0, initialize: safeInitialize, missingCredentialPreflight: "COPILOT_AUTH_REQUIRED", cleanExit: true, inheritedCredentials: false, promptSent: false, networkMode: "COPILOT_OFFLINE=true; loopback metadata-only provider", fixtureRequests, protocolFixtureModel: "gpt-4.1 (not a qualified GitHub model)", costUsd: 0, - qualification: "pending: no credential, entitlement, model execution or Daytona", + assetAdmission: candidate.qualification, + qualification: "installation only: no credential, entitlement, model execution or Daytona", }, null, 2)}\n`); } finally { - if (child && child.exitCode === null && child.signalCode === null) { - child.stdin.end(); - child.kill("SIGTERM"); - await within(exited, 5_000); - } + await stopCopilotSmokeChild(child); } }); diff --git a/packages/paperclip-runner/test/fixtures/copilot-production-v26-offline-regressions.json b/packages/paperclip-runner/test/fixtures/copilot-production-v26-offline-regressions.json new file mode 100644 index 00000000000..057fc8ba21d --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-production-v26-offline-regressions.json @@ -0,0 +1,23 @@ +{ + "schema": "paperclip.copilot.offline-regression-evidence/v1", + "profileVersion": 26, + "profileDigest": "sha256:1a2a0c35764105e9a85112d05187fee0862af23eeb82a75569b5198e52eaba4f", + "priorSourceRevision": "6f424f0f042639631dd6c74053c4a339f6a6ef93", + "runtimeVersion": "1.0.88", + "corrections": [ + "Preserve sensitive ancestor names when flattening native notice fields", + "Bind Copilot native event normalization and notice projection in the source identity", + "Assert expiry notification while forbidding stale permission callbacks", + "Mock installed provider registries independently of host assets" + ], + "testFiles": 7, + "testsPassed": 154, + "providerCalls": 0, + "qualification": "pending", + "liveQualificationPasses": 0, + "priorProtocolPassesHistorical": 7, + "fullWorkspaceChecksPending": true, + "assertionsOrDeadlinesRelaxed": false, + "otherProviderProfilesChanged": false, + "originalFailuresRetained": true +} diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json new file mode 100644 index 00000000000..7d4e04edc82 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json @@ -0,0 +1,81 @@ +{ + "commandDigest": "sha256:1a2a0c35764105e9a85112d05187fee0862af23eeb82a75569b5198e52eaba4f", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "copilot", + "agentProfileVersion": 26, + "acpxVersion": "0.13.1", + "agentServerVersion": "1.0.88", + "protocolPolicyRevision": "copilot-agent-manual-v1", + "acpxPatchSha256": "fedf1e498c1e19daf9c973d475234488aa9acabbf1b9de890245bfac7a534483", + "policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3", + "distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107", + "agentFilesBinding": "registered-runtime-context-v1", + "systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a", + "upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0", + "patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679", + "messageIdentityContract": "copilot-native-message-id-v1", + "ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1", + "permissionContextContract": "copilot-edit-permission-context-v1", + "permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224", + "permissionAdapterSourceSha256": "eebcc1ec9bc8caa8d1edf0cbda74bd69f5c55cf8d65a0503e95798b9979a19dc", + "permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9", + "permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f", + "permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4", + "semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2", + "semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791", + "semanticBridgeSourceSha256": "cf136da18e2cdbefae8e01d23d2c93f64eb7aebccaec938e84c2887c888f87ee", + "toolEvidenceSourceSha256": "4b517eed447615b3cc820af348ccc360978175149caa0bbc838d1f78332abe75", + "semanticNormalizedInputContract": "validated-forwarded-input-commit-v1", + "semanticSidecarSourceSha256": "49f7cb882fa09dcf879a2234d3e7b43fe72895d2c0f1e9b72fcbe18f012423bb", + "semanticDirectDriverSourceSha256": "3cea77a03c8822d68f77f090457176a9190facbec7d88ddf8c583c0e42245f70", + "semanticValidationSourceSha256": "039fca52266fe21ba74ca64660d21228314d8ed737b19ba67941bfb25086e4d5", + "semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e", + "semanticCompletionContractSourceSha256": "fa2b0c81cb9a62965839c905db81b930794774d8d557a01bce077dae5830b594", + "semanticValidatorsSourceSha256": "c3e7328c0d27feb8ee2ca53b1e1c6963c204939ed22b875add748de77c99b52b", + "semanticSchemaBundleSourceSha256": "619f2149a89f952253f8e178645ce4bf986215884b04ce0b7c5ae2ac940849f7", + "semanticSidecarProtocolSourceSha256": "907b2cdc1a5291c6fb570855bbcc66cb196b5cf64195d5dc201148940678460a", + "semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1", + "displayEventSourceSha256": "d72f9a6035c247bc6a42c067c05753076c54322c4b58984e93922f3325dc6877", + "displayProjectionSourceSha256": "75952606b26dd93bbc582c1f0a4424487377272ea745fb3a27ceae031fd5dc7e", + "configuredEnvironmentSourceSha256": "a03575b3a737dea60da94cf0fa1feed80719a9aaa7f09456eb57a845d9e8e087", + "configuredEnvironmentRustSourceSha256": "55d44e48e217385b5c64b1b7d8df16dda62319ca3fb524e452fd78c9ed804d3b", + "profileActivitySourceSha256": "4fdb6b0f9125fd8372ba6f27945fa5c8a3d3b2ec891df544092e3dc7a75d5282", + "profileExtensionsSourceSha256": "0565d13d4064ed8c501d6ffda6d7bffca4493552de8aaa7fc12178dabb3ca435", + "profileInstallationSourceSha256": "7c26545922fc85db75a5db6bf3f6e21e381b6e189f4f51243b19ee237b93f8b1", + "attachmentPolicySourceSha256": "ce8027d1f8e2b7fd35df56a75ba162fd9696d1569c016b60bc9f0de1252cd958", + "singleReadEvidenceSourceSha256": "4069393405089bde613cecec04f5123c426594811da4c668bd96f0e7e283550f", + "nativeSessionRuntimeSourceSha256": "affc67a3b4fa38a1337c0779c55fdf0b32f46ea5c8dbbfe02c3359cb2ca26034", + "nativeRuntimeContextSourceSha256": "afcadc9b78d42c598f6822c6da071abbca09024ab231f6b39a30e10e1d9bd38e", + "nativeSessionContractSourceSha256": "8cdf0ea723d58057f2425ed267a92a317c065c917c6236d323c0ed846b05c3d9", + "harnessSessionContractSourceSha256": "75ae4607312ec13466dd2f4ac87f000302b7b5a952cb6d4b51573fa8b7c35d82", + "harnessBackendSourceSha256": "d529e67744a4cf8d0bb1aaa466b1df40fbab35765796da9c96c046204e194402", + "harnessSessionSourceSha256": "a9d748b8530544342bbc9f59968816664872782a0865db76ca15f28a1705697b", + "transportContractSourceSha256": "16f358c75dd4ae80af3ca921394c5899b15942b80b553e43ddfabb41c1bf31d3", + "nativeBackendSourceSha256": "a888e389874d2fae6e1f1d2074df0aab9abb6502f9afca9d15ff13708ee43753", + "nativeBackendFactorySourceSha256": "ae91eaa54e2fb4bc58c445e41d6453ecc73864d07f1240dff5936af9987bcaef", + "nativeInstructionsSourceSha256": "1ac0c145dfc03ceca646267b74fa7b59d963189913768c46544579709e0914b6", + "runtimeRequestBridgeSourceSha256": "c449568f8abf7e90d5ae7a83b49809d9166461398f8b3046e4fe5f3367f3b052", + "runtimeRequestFacadeSourceSha256": "3d70c6025169779f7886ddf363a1c6fda4add5d14fa3cd49aea46aa1f6a9d8ec", + "runtimeRequestNotificationSourceSha256": "6196c6175f11c69295dc00fc773201f4fdeca26c500653870253c43b81e65211", + "guardedInstallationSourceSha256": "8afd25df307772e484e179e58bf54ce1510fe92be694482111d589f9f0ae6e77", + "nativeExecutionSourceSha256": "cf4ac0008807bbd1852d4cbf8a964d3a2fca0b0573e5789dd3e902a70f952e82", + "profileCompatibilitySourceSha256": "37059f13f1cbb2a477d115eaf23e4ba686e6ee7aafdaead8512b268287ba0078", + "installationSourceSha256": "9934653d6983905930099b1691b1f0c05461e04e950bb27dd0f92e64fc8e28e1", + "assetOwnershipSourceSha256": "43005b7e896e0546c1f44cbe4ff3f34ad6d71f275f4b9a8113f73a71e77930c4", + "assetInstallerSourceSha256": "d3bd9514e5920d0cc3567aa50487fb534c57a6f66b8da0d3f22c5f6e499c841f", + "credentialBindingSourceSha256": "d5c200bea2e8ab730eb56e32ce2070c8a1b5407e5907108640f76a463d9b9c9c", + "sidecarErrorSourceSha256": "23f3dbdc1ce488c690d218a854c820f4b4f3edd822bad574d67707314f8c512a", + "profileSourceSha256": "1572b19f84410959ecccbfcb5ffc168ddd85d1a4f8238fabeaaf662f6289fe25", + "metadataProbeSourceSha256": "e8bdefca57b9678d2b5d99b0bc604301d12993b9c9cc4ecb4b5d3df0b83cec3f", + "permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1", + "runtimeHostSourceSha256": "83a5ae07c19681ab195f76363661c2381494b243343270ba363d4db2088932a5", + "runtimeAdapterSourceSha256": "9d1767a4853768d4d03c2e50dc1f00a6f2b114faa2dfb19dd70e31422722dbdd", + "runtimeSandboxSourceSha256": "4acec4c9757fe5ef5f2788ce20ced8ca4fde8edcfb63b59d0effcfd4316c048d", + "cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca", + "agentFilesSourceSha256": "fb32f6badc13dfa22f231479509b631b2f0eba0c99b1ab9f3cd5c0c601aa03ba", + "nativeDistributionSourceSha256": "9bbe8329064ee88418528394f8e1cdc716a0b2b35092dc70729b8850e46b7a68" + } +} diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v28-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v28-identity.json new file mode 100644 index 00000000000..eeda16cc1a9 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v28-identity.json @@ -0,0 +1,81 @@ +{ + "commandDigest": "sha256:077c7380a65906fb0bc0cfcd11f261490eafeda16d02b3d28c8dbdf9bce1ae22", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "copilot", + "agentProfileVersion": 28, + "acpxVersion": "0.13.1", + "agentServerVersion": "1.0.88", + "protocolPolicyRevision": "copilot-agent-manual-v1", + "acpxPatchSha256": "fedf1e498c1e19daf9c973d475234488aa9acabbf1b9de890245bfac7a534483", + "policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3", + "distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107", + "agentFilesBinding": "registered-runtime-context-v1", + "systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a", + "upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0", + "patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679", + "messageIdentityContract": "copilot-native-message-id-v1", + "ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1", + "permissionContextContract": "copilot-edit-permission-context-v1", + "permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224", + "permissionAdapterSourceSha256": "eebcc1ec9bc8caa8d1edf0cbda74bd69f5c55cf8d65a0503e95798b9979a19dc", + "permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9", + "permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f", + "permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4", + "semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2", + "semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791", + "semanticBridgeSourceSha256": "cf136da18e2cdbefae8e01d23d2c93f64eb7aebccaec938e84c2887c888f87ee", + "toolEvidenceSourceSha256": "4b517eed447615b3cc820af348ccc360978175149caa0bbc838d1f78332abe75", + "semanticNormalizedInputContract": "validated-forwarded-input-commit-v1", + "semanticSidecarSourceSha256": "49f7cb882fa09dcf879a2234d3e7b43fe72895d2c0f1e9b72fcbe18f012423bb", + "semanticDirectDriverSourceSha256": "3cea77a03c8822d68f77f090457176a9190facbec7d88ddf8c583c0e42245f70", + "semanticValidationSourceSha256": "3b0ac8d3f3cb92747d0bcea472ab0419a140e1cfdc43047e2c22542acc2d105f", + "semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e", + "semanticCompletionContractSourceSha256": "fa2b0c81cb9a62965839c905db81b930794774d8d557a01bce077dae5830b594", + "semanticValidatorsSourceSha256": "328436d29148e0b79ce36f36c764b3d780913bdb90d174d1ea03cf1f59b5ef13", + "semanticSchemaBundleSourceSha256": "d8cef4a9ebc136a3d37f3d0be7161986e6d65420e2ec734cfd0dfa468c665c97", + "semanticSidecarProtocolSourceSha256": "907b2cdc1a5291c6fb570855bbcc66cb196b5cf64195d5dc201148940678460a", + "semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1", + "displayEventSourceSha256": "d72f9a6035c247bc6a42c067c05753076c54322c4b58984e93922f3325dc6877", + "displayProjectionSourceSha256": "75952606b26dd93bbc582c1f0a4424487377272ea745fb3a27ceae031fd5dc7e", + "configuredEnvironmentSourceSha256": "a03575b3a737dea60da94cf0fa1feed80719a9aaa7f09456eb57a845d9e8e087", + "configuredEnvironmentRustSourceSha256": "55d44e48e217385b5c64b1b7d8df16dda62319ca3fb524e452fd78c9ed804d3b", + "profileActivitySourceSha256": "4fdb6b0f9125fd8372ba6f27945fa5c8a3d3b2ec891df544092e3dc7a75d5282", + "profileExtensionsSourceSha256": "0565d13d4064ed8c501d6ffda6d7bffca4493552de8aaa7fc12178dabb3ca435", + "profileInstallationSourceSha256": "7c26545922fc85db75a5db6bf3f6e21e381b6e189f4f51243b19ee237b93f8b1", + "attachmentPolicySourceSha256": "ce8027d1f8e2b7fd35df56a75ba162fd9696d1569c016b60bc9f0de1252cd958", + "singleReadEvidenceSourceSha256": "4069393405089bde613cecec04f5123c426594811da4c668bd96f0e7e283550f", + "nativeSessionRuntimeSourceSha256": "aac818c1cf50a25899ae95cafc70f093c8cd8af37d19f88cbb7847b4d202d2b6", + "nativeRuntimeContextSourceSha256": "afcadc9b78d42c598f6822c6da071abbca09024ab231f6b39a30e10e1d9bd38e", + "nativeSessionContractSourceSha256": "93251b1eadc686e9d8339d4dfc50b995a3d0a9e6ad8aedc6f2bdf7e30c16138d", + "harnessSessionContractSourceSha256": "75ae4607312ec13466dd2f4ac87f000302b7b5a952cb6d4b51573fa8b7c35d82", + "harnessBackendSourceSha256": "3b11fd93b82ef13d5e6396217e3f102f900437f4d32efb00a9d75a9d2214e6ff", + "harnessSessionSourceSha256": "a9d748b8530544342bbc9f59968816664872782a0865db76ca15f28a1705697b", + "transportContractSourceSha256": "16f358c75dd4ae80af3ca921394c5899b15942b80b553e43ddfabb41c1bf31d3", + "nativeBackendSourceSha256": "a23afeab7e7094cb7e3df05e7a9eac822d0144d8c44893dbcc8a0e7b2bce0c85", + "nativeBackendFactorySourceSha256": "ce22825a53669f8c2db5aaa890bae94af19ede22295bda4ed7987a31d804cd8a", + "nativeInstructionsSourceSha256": "ea85be2aca8dfbd6b4042dcd7b1ed9db9a57baf8c379649a3f5d7d80ca54a6a2", + "runtimeRequestBridgeSourceSha256": "c449568f8abf7e90d5ae7a83b49809d9166461398f8b3046e4fe5f3367f3b052", + "runtimeRequestFacadeSourceSha256": "3d70c6025169779f7886ddf363a1c6fda4add5d14fa3cd49aea46aa1f6a9d8ec", + "runtimeRequestNotificationSourceSha256": "6196c6175f11c69295dc00fc773201f4fdeca26c500653870253c43b81e65211", + "guardedInstallationSourceSha256": "8afd25df307772e484e179e58bf54ce1510fe92be694482111d589f9f0ae6e77", + "nativeExecutionSourceSha256": "3f016a2017c6eaa6920709864256c727c6171645bd024cde24315235a1a21ea1", + "profileCompatibilitySourceSha256": "1368fb7cc741ccd57ff661fd463dcebe6e80b1a1dc410ffbb2d1b0c7934d7a5a", + "installationSourceSha256": "9934653d6983905930099b1691b1f0c05461e04e950bb27dd0f92e64fc8e28e1", + "assetOwnershipSourceSha256": "43005b7e896e0546c1f44cbe4ff3f34ad6d71f275f4b9a8113f73a71e77930c4", + "assetInstallerSourceSha256": "d3bd9514e5920d0cc3567aa50487fb534c57a6f66b8da0d3f22c5f6e499c841f", + "credentialBindingSourceSha256": "d5c200bea2e8ab730eb56e32ce2070c8a1b5407e5907108640f76a463d9b9c9c", + "sidecarErrorSourceSha256": "23f3dbdc1ce488c690d218a854c820f4b4f3edd822bad574d67707314f8c512a", + "profileSourceSha256": "baf0b64371fada6c92331f9f88e915292b2761c029fd86452f6de32e26dc7fd6", + "metadataProbeSourceSha256": "e8bdefca57b9678d2b5d99b0bc604301d12993b9c9cc4ecb4b5d3df0b83cec3f", + "permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1", + "runtimeHostSourceSha256": "83a5ae07c19681ab195f76363661c2381494b243343270ba363d4db2088932a5", + "runtimeAdapterSourceSha256": "9d1767a4853768d4d03c2e50dc1f00a6f2b114faa2dfb19dd70e31422722dbdd", + "runtimeSandboxSourceSha256": "4acec4c9757fe5ef5f2788ce20ced8ca4fde8edcfb63b59d0effcfd4316c048d", + "cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca", + "agentFilesSourceSha256": "fb32f6badc13dfa22f231479509b631b2f0eba0c99b1ab9f3cd5c0c601aa03ba", + "nativeDistributionSourceSha256": "9bbe8329064ee88418528394f8e1cdc716a0b2b35092dc70729b8850e46b7a68" + } +} diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v29-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v29-identity.json new file mode 100644 index 00000000000..64057b48856 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v29-identity.json @@ -0,0 +1,81 @@ +{ + "commandDigest": "sha256:ce118b64cd7a50a93c3ff538b596327bd8e8d172a1bc0bda5d3334948d5d8eb7", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "copilot", + "agentProfileVersion": 29, + "acpxVersion": "0.13.1", + "agentServerVersion": "1.0.88", + "protocolPolicyRevision": "copilot-agent-manual-v1", + "acpxPatchSha256": "fedf1e498c1e19daf9c973d475234488aa9acabbf1b9de890245bfac7a534483", + "policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3", + "distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107", + "agentFilesBinding": "registered-runtime-context-v1", + "systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a", + "upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0", + "patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679", + "messageIdentityContract": "copilot-native-message-id-v1", + "ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1", + "permissionContextContract": "copilot-edit-permission-context-v1", + "permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224", + "permissionAdapterSourceSha256": "eebcc1ec9bc8caa8d1edf0cbda74bd69f5c55cf8d65a0503e95798b9979a19dc", + "permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9", + "permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f", + "permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4", + "semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2", + "semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791", + "semanticBridgeSourceSha256": "cf136da18e2cdbefae8e01d23d2c93f64eb7aebccaec938e84c2887c888f87ee", + "toolEvidenceSourceSha256": "4b517eed447615b3cc820af348ccc360978175149caa0bbc838d1f78332abe75", + "semanticNormalizedInputContract": "validated-forwarded-input-commit-v1", + "semanticSidecarSourceSha256": "49f7cb882fa09dcf879a2234d3e7b43fe72895d2c0f1e9b72fcbe18f012423bb", + "semanticDirectDriverSourceSha256": "3cea77a03c8822d68f77f090457176a9190facbec7d88ddf8c583c0e42245f70", + "semanticValidationSourceSha256": "3b0ac8d3f3cb92747d0bcea472ab0419a140e1cfdc43047e2c22542acc2d105f", + "semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e", + "semanticCompletionContractSourceSha256": "fa2b0c81cb9a62965839c905db81b930794774d8d557a01bce077dae5830b594", + "semanticValidatorsSourceSha256": "328436d29148e0b79ce36f36c764b3d780913bdb90d174d1ea03cf1f59b5ef13", + "semanticSchemaBundleSourceSha256": "d8cef4a9ebc136a3d37f3d0be7161986e6d65420e2ec734cfd0dfa468c665c97", + "semanticSidecarProtocolSourceSha256": "907b2cdc1a5291c6fb570855bbcc66cb196b5cf64195d5dc201148940678460a", + "semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1", + "displayEventSourceSha256": "d72f9a6035c247bc6a42c067c05753076c54322c4b58984e93922f3325dc6877", + "displayProjectionSourceSha256": "75952606b26dd93bbc582c1f0a4424487377272ea745fb3a27ceae031fd5dc7e", + "configuredEnvironmentSourceSha256": "a03575b3a737dea60da94cf0fa1feed80719a9aaa7f09456eb57a845d9e8e087", + "configuredEnvironmentRustSourceSha256": "55d44e48e217385b5c64b1b7d8df16dda62319ca3fb524e452fd78c9ed804d3b", + "profileActivitySourceSha256": "4fdb6b0f9125fd8372ba6f27945fa5c8a3d3b2ec891df544092e3dc7a75d5282", + "profileExtensionsSourceSha256": "0565d13d4064ed8c501d6ffda6d7bffca4493552de8aaa7fc12178dabb3ca435", + "profileInstallationSourceSha256": "7c26545922fc85db75a5db6bf3f6e21e381b6e189f4f51243b19ee237b93f8b1", + "attachmentPolicySourceSha256": "ce8027d1f8e2b7fd35df56a75ba162fd9696d1569c016b60bc9f0de1252cd958", + "singleReadEvidenceSourceSha256": "4069393405089bde613cecec04f5123c426594811da4c668bd96f0e7e283550f", + "nativeSessionRuntimeSourceSha256": "aac818c1cf50a25899ae95cafc70f093c8cd8af37d19f88cbb7847b4d202d2b6", + "nativeRuntimeContextSourceSha256": "afcadc9b78d42c598f6822c6da071abbca09024ab231f6b39a30e10e1d9bd38e", + "nativeSessionContractSourceSha256": "93251b1eadc686e9d8339d4dfc50b995a3d0a9e6ad8aedc6f2bdf7e30c16138d", + "harnessSessionContractSourceSha256": "75ae4607312ec13466dd2f4ac87f000302b7b5a952cb6d4b51573fa8b7c35d82", + "harnessBackendSourceSha256": "3b11fd93b82ef13d5e6396217e3f102f900437f4d32efb00a9d75a9d2214e6ff", + "harnessSessionSourceSha256": "a9d748b8530544342bbc9f59968816664872782a0865db76ca15f28a1705697b", + "transportContractSourceSha256": "16f358c75dd4ae80af3ca921394c5899b15942b80b553e43ddfabb41c1bf31d3", + "nativeBackendSourceSha256": "a23afeab7e7094cb7e3df05e7a9eac822d0144d8c44893dbcc8a0e7b2bce0c85", + "nativeBackendFactorySourceSha256": "ce22825a53669f8c2db5aaa890bae94af19ede22295bda4ed7987a31d804cd8a", + "nativeInstructionsSourceSha256": "ea85be2aca8dfbd6b4042dcd7b1ed9db9a57baf8c379649a3f5d7d80ca54a6a2", + "runtimeRequestBridgeSourceSha256": "c449568f8abf7e90d5ae7a83b49809d9166461398f8b3046e4fe5f3367f3b052", + "runtimeRequestFacadeSourceSha256": "3d70c6025169779f7886ddf363a1c6fda4add5d14fa3cd49aea46aa1f6a9d8ec", + "runtimeRequestNotificationSourceSha256": "6196c6175f11c69295dc00fc773201f4fdeca26c500653870253c43b81e65211", + "guardedInstallationSourceSha256": "8afd25df307772e484e179e58bf54ce1510fe92be694482111d589f9f0ae6e77", + "nativeExecutionSourceSha256": "3f016a2017c6eaa6920709864256c727c6171645bd024cde24315235a1a21ea1", + "profileCompatibilitySourceSha256": "4e4d098d776631bf8a7b377ae3f3b7ab0494195c441879838c1f1607c5bf6170", + "installationSourceSha256": "9934653d6983905930099b1691b1f0c05461e04e950bb27dd0f92e64fc8e28e1", + "assetOwnershipSourceSha256": "43005b7e896e0546c1f44cbe4ff3f34ad6d71f275f4b9a8113f73a71e77930c4", + "assetInstallerSourceSha256": "d3bd9514e5920d0cc3567aa50487fb534c57a6f66b8da0d3f22c5f6e499c841f", + "credentialBindingSourceSha256": "d5c200bea2e8ab730eb56e32ce2070c8a1b5407e5907108640f76a463d9b9c9c", + "sidecarErrorSourceSha256": "23f3dbdc1ce488c690d218a854c820f4b4f3edd822bad574d67707314f8c512a", + "profileSourceSha256": "baf0b64371fada6c92331f9f88e915292b2761c029fd86452f6de32e26dc7fd6", + "metadataProbeSourceSha256": "e8bdefca57b9678d2b5d99b0bc604301d12993b9c9cc4ecb4b5d3df0b83cec3f", + "permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1", + "runtimeHostSourceSha256": "83a5ae07c19681ab195f76363661c2381494b243343270ba363d4db2088932a5", + "runtimeAdapterSourceSha256": "a1a8784f5883d2c93df6e3695d5499b83e226efb4f26e558639c4e7dbcc2d67a", + "runtimeSandboxSourceSha256": "4acec4c9757fe5ef5f2788ce20ced8ca4fde8edcfb63b59d0effcfd4316c048d", + "cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca", + "agentFilesSourceSha256": "fb32f6badc13dfa22f231479509b631b2f0eba0c99b1ab9f3cd5c0c601aa03ba", + "nativeDistributionSourceSha256": "9bbe8329064ee88418528394f8e1cdc716a0b2b35092dc70729b8850e46b7a68" + } +} diff --git a/server/src/__tests__/workspace-runtime.test.ts b/server/src/__tests__/workspace-runtime.test.ts index 112c30243a1..9f4e1ef118b 100644 --- a/server/src/__tests__/workspace-runtime.test.ts +++ b/server/src/__tests__/workspace-runtime.test.ts @@ -7363,7 +7363,7 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => const blockerScript = [ "const net=require('node:net');", "const port=Number(process.argv[1]);", - "net.createServer((socket)=>socket.destroy()).listen(port,'127.0.0.1');", + "net.createServer((socket)=>socket.destroy()).listen(port,'127.0.0.1',()=>process.send({ready:true}));", ].join(""); const raceScript = [ "const fs=require('node:fs');", @@ -7376,8 +7376,10 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => "const start=()=>http.createServer((_req,res)=>res.end('ok')).listen(port,'127.0.0.1');", "if(!fs.existsSync(marker)){", "fs.writeFileSync(marker,String(port));", - "const blocker=spawn(process.execPath,['-e',blockerScript,String(port)],{detached:true,stdio:'ignore'});", - "fs.writeFileSync(pidFile,String(blocker.pid));blocker.unref();setTimeout(start,200);", + "const blocker=spawn(process.execPath,['-e',blockerScript,String(port)],{detached:true,stdio:['ignore','ignore','ignore','ipc']});", + "fs.writeFileSync(pidFile,String(blocker.pid));", + "const readyDeadline=setTimeout(()=>{throw new Error('Fixture blocker did not become ready');},5000);", + "blocker.once('message',(message)=>{if(message?.ready!==true)throw new Error('Invalid fixture readiness message');clearTimeout(readyDeadline);blocker.disconnect();blocker.unref();start();});", "}else{start();}", "setInterval(()=>{},1000);", ].join(""); diff --git a/tests/runner-e2e/FIXTURES.md b/tests/runner-e2e/FIXTURES.md index 2e94cf78c4a..2f86eb1b644 100644 --- a/tests/runner-e2e/FIXTURES.md +++ b/tests/runner-e2e/FIXTURES.md @@ -153,7 +153,9 @@ canonical Plan revision, capture its pending UI, approve in the browser, and prove exactly two successful runs. `warm_three_turn` provides exactly two browser follow-up messages, preserves one project/execution-workspace scope, verifies host file contents after every turn, and finishes within three -ten-minute turn deadlines. The ordinary warm fixture uses managed instructions, +ten-minute turn deadlines. Warm prompts define each newline as the actual LF +byte 10 and require byte verification independent of the write expression; +literal backslash-plus-n remains a failing output. The ordinary warm fixture uses managed instructions, updates AGENT_HOME each turn, and verifies memory, an unchanged 8 MiB binary and a deletion through public file APIs. Native turns 2 and 3 must copy/hash only the changed memory file, with a saved receipt and the same provider PID. Journal and @@ -172,7 +174,12 @@ Native turns 1 and 2 include an actionable human review in the completion report Every selected case runs in its own isolated Paperclip process, and independent cases may run concurrently. Follow-up turns inside one case retain their shared task state. Each case creates and tears down its own company, secrets, -environment selection, agent, and browser-created task. The current plan case +environment selection, agent, and browser-created task. Strict Copilot command, +denial, pending-permission Stop and provider-death fixtures create an explicit +title through the production search composer and bind the task to the public +browser creation response ID. Automatic prompt-only naming must not add a +native semantic operation or break task correlation. Command, permission, +receipt, ordering and cleanup assertions remain unchanged. The current plan case proves three runs on the same issue: publish a two-step Plan, request a three-step revision through the UI, and accept the exact new revision through the UI before verifying implementation and Done. diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index a87cd9d695e..1d7ed8fe742 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -22,6 +22,10 @@ scheduled execution gets a fresh Paperclip home, embedded Postgres database, instance configuration, port, workspace, company, encrypted secrets, environment, and agent. +`pnpm test:e2e:runner:unit` runs the native-completion source gates with +Node's test runner, then the Product support tests with Vitest. A Node test +file must not be collected as an empty Vitest suite. + ## Native connection guidance (explicit only) The manual-only native-connection-guidance suite separates connection-policy @@ -1539,6 +1543,24 @@ immutable Daytona image; no private hooks or fixture database writes are used. ## Extended ACP harnesses (explicit only) +Copilot protection qualification requires a successful `get_task_context` read +before the tested native action. The v3 completion oracle independently joins +its read-only native lifecycle, authoritative result receipt, and canonical tool +events. It then validates the single finish receipt, accepted result, attached +command exit, and cleanup. The retained v15 context/completion fixture preserves +the original failed grade; its context receipt cannot stand in for completion. +Extra semantic actions and missing, late, failed, or mutating context reads fail. +The authored pending-permission Stop and provider-death prompts require the +already-exposed dedicated `get_task_context` tool directly before the native +edit. `search_api` discovers HTTP routes rather than semantic tools; `call_api` +and a context-like HTTP response cannot replace the dedicated context receipt. +Historical bounded discovery proof remains attested separately and cannot +establish context access or completion. +The denied-write prompt uses that same dedicated-context guidance. +The provider-death final UI assertion accepts the current Blocked label with +its blocker-attention explanation. The API settlement still requires exact +`blocked`, one failed run, no mutation replay, stale-answer refusal, and cleanup. + `--suite extended-harnesses` declares 30 Product E2E cells: Cursor, Copilot, and Pi on local and Daytona, each exercising authenticated completion, question/answer continuation, revision-bound semantic plan approval, restart diff --git a/tests/runner-e2e/catalog.test.ts b/tests/runner-e2e/catalog.test.ts index cc7254d49e4..9aaba327558 100644 --- a/tests/runner-e2e/catalog.test.ts +++ b/tests/runner-e2e/catalog.test.ts @@ -104,6 +104,16 @@ describe("runner E2E catalog", () => { } }); + it("specifies real LF bytes in every managed warm turn", () => { + const prompts = [daytonaWarmContinuityTask.buildPrompt("nonce"), ...daytonaWarmContinuityTask.buildFollowupMessages!("nonce")]; + for (const prompt of prompts) { + expect(prompt).toContain("actual LF character (byte 10)"); + expect(prompt).toContain("independently of the string expression used to write them"); + } + expect(prompts[2]).toContain("T1-nonce | T2-nonce"); + expect(prompts[2]).not.toContain("T1-nonce\\nT2-nonce"); + }); + it("keeps paid public MCP journeys explicit and fingerprints the shipped workflow instructions", () => { const suite = runnerSuites.find(suite => suite.id === "public-mcp")!; expect(suite.manualOnly).toBe(true); @@ -146,10 +156,10 @@ describe("runner E2E catalog", () => { expect(localIntegrityTasks).toHaveLength(2); expect(openRouterBreadthTasks).toHaveLength(3); expect(runnerSuites.map((suite) => suite.expectedMatrixSize)).toEqual([ - 4, 8, 10, 4, 4, 6, 63, 12, 6, 2, 30, 3, 16, 16, 2, 6, 8, 46, 2, 23, 15, 52, 6, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1, 116, + 4, 8, 10, 6, 63, 12, 6, 4, 2, 4, 2, 30, 3, 16, 16, 2, 6, 8, 46, 2, 23, 15, 52, 6, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1, 116, ]); - expect(validateRunnerCatalog()).toHaveLength(724); - expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(724); + expect(validateRunnerCatalog()).toHaveLength(726); + expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(726); expect( runnerMatrix.filter((entry) => entry.suite.id === "core-compatibility"), ).toHaveLength(48); diff --git a/tests/runner-e2e/catalog.ts b/tests/runner-e2e/catalog.ts index 7ea255c2041..d9f6a837a0b 100644 --- a/tests/runner-e2e/catalog.ts +++ b/tests/runner-e2e/catalog.ts @@ -11,6 +11,7 @@ import { nativeCompletionProfile, NATIVE_COMPLETION_BUDGET_CENTS } from "./nativ import { chatConfirmationTasks } from "./chat-cases.js"; import { buildConnectionSuite } from "./connection-cases.js"; import { hiringTemplateTasks, hiringTemplateProfile, hiringTemplateDefinitionDigest } from "./hiring-template-cases.js"; +import { copilotProviderDeathTasks } from "./copilot-active-stop-tasks.js"; import { instructionPersistenceTask } from "./instruction-persistence.js"; import { apiResponseReadingTask } from "./api-response-reading.js"; import { taskTitleTasks, taskTitleDefinitionDigest, TASK_TITLE_BUDGET_CENTS } from "./task-titles.js"; @@ -952,7 +953,8 @@ function warmTurnInstructions(turn: 1 | 2 | 3, nonce: string) { `This is warm Daytona continuity turn ${turn} of 3. Work only in the current execution workspace.`, turn === 1 ? `Create ${file} with exactly this one line followed by a newline: ${lines[0]}` - : `Before changing anything, read ${file} and verify its content is exactly ${lines.slice(0, -1).join("\\n")} followed by a newline. Then append exactly ${lines.at(-1)} followed by a newline.`, + : `Before changing anything, read ${file} and verify its content is exactly these prior lines in order, each followed by a newline: ${lines.slice(0, -1).join(" | ")}. Then append exactly ${lines.at(-1)} followed by a newline.`, + "Each newline in this task means the actual LF character (byte 10), including in personal memory files. An escaped representation containing a literal backslash and n is not a newline. Verify the saved bytes against the requested line content and LF byte 10, independently of the string expression used to write them.", `After the write, verify ${file} contains exactly these lines, once each and in order: ${lines.join(" | ")}.`, `In a native runner, use the current turn completion revision and criterion IDs (CURRENT_TURN_REVISION below is a placeholder, not a literal), then call paperclip_finish exactly once with {reportedWorkDisposition:"${finalTurn ? "done" : "needs_review"}",summary:"${marker}",completionClaim:{contractRevision:CURRENT_TURN_REVISION,objectiveSatisfied:true,criteria:[{criterionId:"objective",status:"satisfied",evidenceRefs:[]}],remainingWork:[]},attentionRequests:${JSON.stringify(reviewRequests)},evidence:[],verification:[{commandOrCheck:"read ${file}",status:"passed"}]}. Wait for that tool call to succeed, then emit exactly ${marker} once as the complete user-facing final response.`, legacyCompletion, @@ -1134,20 +1136,8 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [ excludedExecutionIds: ["pi-native.runner-acpx-pi.daytona.restrictive-denial", "pi-native.runner-acpx-pi.local.native-pending-provider-death"], definitionMetadata: { version: 23, remoteProcExit: "separately-confirmed-absence-after-read-failure", taskCreation: "explicit-title-and-creation-response-id", agentMemoryParent: "public-managed-file-seed-before-admission", incompleteTerminalCleanup: "retirement-retained-with-failed-watch", qualification: "pending", scheduling: "explicit-only", profileVersion: QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, agentMemoryContent: "utf8-nonce-plus-final-lf", agentMemoryPrompt: "single-json-write-and-content-bound-native-read-both-runs", agentMemoryReadAuthority: "local-withheld-or-exact-remote-agent-run-file", taskPromptTransport: "fenced-markdown-paste-and-multiline-literal-escapes", nativeFinish: "current-contract-objective-evidence-refs", providerDeath: "daytona-exact-pi-child-pidfd-production-expiry", providerFaultExecutable: "stable-preinstalled-runner-link-and-snapshot-node-inode-with-held-bootstrap-fd-3-or-7", remoteBootstrapAdmission: "owned-active-lease-with-native-runtime-readiness-rpc-v1", remoteBootstrapApproval: "exact-published-native-read-public-accept-once-v1", remoteDenyAll: "unsupported-native-bootstrap-read-is-denied", remoteEvidence: "owned-lease-sealed-observer", pendingControllerRestart: "same-live-native-request-trusted-ancestry-cleanup" }, }, - { - id: "native-active-stop", label: "Stop an unanswered native permission", manualOnly: true, - description: "Stop while one exact Cursor or Copilot native permission remains unanswered; require cancelled provider settlement, caller-owned acknowledgement, stale-answer refusal and independent retirement/no effects.", - groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => ["cursor", "copilot"].includes(profile.qualificationCandidate ?? "")), - environments: runnerEnvironments, tasks: nativeActiveStopTasks, expectedMatrixSize: 4, - definitionMetadata: { version: 4, qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.native-active-stop-settlement.v2", pendingObservation: "retained-api-before-caller-uuid-stop", normalCompletionAccepted: false, permissionPolicy: "approve-reads", lifecycle: "per_turn", remoteEvidence: "paperclip.e2e.native-active-stop-remote-retirement.v1", remoteObservationCoverage: "continuous-through-owned-process-retirement", filesystemAfterRemoteRetirementObserved: false, localObservationCoverage: "four-phases-through-cleanup", providerDeath: "not-covered" }, - }, - { - id: "copilot-protection", label: "Copilot native protection", manualOnly: true, - description: "Exact native denial with independently correlated provider settlement and acknowledged run Stop, plus attached command settlement with independent process evidence.", - groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => profile.qualificationCandidate === "copilot"), - environments: runnerEnvironments, tasks: copilotProtectionTasks, expectedMatrixSize: 4, - definitionMetadata: { version: 9, outputProhibition: "separate-publication-and-attachment", semanticCompletionEvidence: "paperclip.e2e.copilot-semantic-completion.v2", qualification: "pending", naturalSettlementObservationMs: 2000, scheduling: "explicit-only", evidence: "copilot_tool_evidence_v1", profileVersion: QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion, denialTerminal: "correlated-provider-settlement-and-audited-run-stop", denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v3", activeTurnCancellation: "not-implied-by-completed-provider-turn", settlement: "attached-finite-command-only", settlementMarker: "private-diagnostic-not-deliverable", remoteEvidence: "owned-lease-sealed-observer" }, - }, + + { id: "rich-acp-warm-continuity", label: "Rich ACP warm continuity", manualOnly: true, description: "Three browser-driven turns with stable native session, runner process and workspace identity for Cursor, Copilot and Pi.", @@ -1202,7 +1192,30 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [ groups: ["legacy"], profiles: runnerProfiles.filter(p => ["legacy-codex", "legacy-claude"].includes(p.id)).map(blockerProfile), environments: [localEnvironment], tasks: blockerTasks, expectedMatrixSize: 6, definitionMetadata: { version: 1, instructions: "production-coordination-skill", grading: "saved-human-decision-ownership-and-resume", scheduling: "explicit-only" }, - }, { + }, + + { + id: "native-active-stop", label: "Stop an unanswered native permission", manualOnly: true, + description: "Stop while one exact Cursor or Copilot native permission remains unanswered; require cancelled provider settlement, caller-owned acknowledgement, stale-answer refusal and independent retirement/no effects.", + groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => ["cursor", "copilot"].includes(profile.qualificationCandidate ?? "")), + environments: runnerEnvironments, tasks: nativeActiveStopTasks, expectedMatrixSize: 4, + definitionMetadata: { version: 20, copilotDefinition: { version: 19, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", processAuthority: "local-native-root-or-attested-remote-root", remoteTerminalDiagnostics: "bounded-incomplete-reasons-with-strict-failure", remoteSetupOrder: "observed-action-published-before-context-approval", remoteBootstrapContext: "already-exposed-dedicated-tool-no-http-substitution", bootstrapPrompt: "already-exposed-dedicated-context-before-native-edit", editOrigin: "exact-permission-target-and-native-call-identity", stopAcknowledgement: "retained-current-mainline-api-response", contextPermission: "context-and-bounded-attested-discovery-sequence-await-origin-rows", permissionCard: "current-operation-and-decision-control-excludes-history", qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.native-active-stop-settlement.v3", pendingObservation: "retained-api-before-stop-with-fixture-correlation", normalCompletionAccepted: false, permissionPolicy: "approve-reads", lifecycle: "per_turn", remoteEvidence: "paperclip.e2e.native-active-stop-remote-retirement.v1", remoteObservationCoverage: "continuous-through-owned-process-retirement", filesystemAfterRemoteRetirementObserved: false, localObservationCoverage: "four-phases-through-cleanup", providerDeath: "not-covered" }, qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.native-active-stop-settlement.v2", pendingObservation: "retained-api-before-caller-uuid-stop", normalCompletionAccepted: false, permissionPolicy: "approve-reads", lifecycle: "per_turn", remoteEvidence: "paperclip.e2e.native-active-stop-remote-retirement.v1", remoteObservationCoverage: "continuous-through-owned-process-retirement", filesystemAfterRemoteRetirementObserved: false, localObservationCoverage: "four-phases-through-cleanup", providerDeath: "not-covered" }, + }, + { + id: "copilot-provider-death", label: "Copilot provider death", manualOnly: true, + description: "Terminate only the verified owned Copilot child with an unanswered permission; require callback expiry, stale-answer refusal, no replay and independent retirement.", + groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => profile.qualificationCandidate === "copilot"), + environments: runnerEnvironments, tasks: copilotProviderDeathTasks, expectedMatrixSize: 2, + definitionMetadata: { version: 17, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", linuxDeathExecutable: "verified-inherited-descriptor-and-live-executable-inode", processAuthority: "local-native-root-or-attested-remote-root", remoteTerminalDiagnostics: "bounded-incomplete-reasons-with-strict-failure", remoteSetupOrder: "observed-action-published-before-context-approval", remoteBootstrapContext: "already-exposed-dedicated-tool-no-http-substitution", finalUiStatus: "blocked-with-current-mainline-attention-label", bootstrapPrompt: "already-exposed-dedicated-context-before-native-edit", editOrigin: "exact-permission-target-and-native-call-identity", contextPermission: "context-and-bounded-attested-discovery-sequence-await-origin-rows", permissionCard: "current-operation-and-decision-control-excludes-history", qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.copilot-provider-death.v1", signal: "SIGKILL", target: "owned-native-child-only", permissionPolicy: "approve-reads", lifecycle: "per_turn", automaticRetries: 0 }, + }, + { + id: "copilot-protection", label: "Copilot native protection", manualOnly: true, + description: "Exact native denial with independently correlated provider settlement and acknowledged run Stop, plus attached command settlement with independent process evidence.", + groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => profile.qualificationCandidate === "copilot"), + environments: runnerEnvironments, tasks: copilotProtectionTasks, expectedMatrixSize: 4, + definitionMetadata: { version: 23, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", remoteTerminalDiagnostics: "bounded-incomplete-reasons-with-strict-failure", remoteSetupOrder: "observed-action-published-before-context-approval", remoteBootstrapContext: "already-exposed-dedicated-tool-no-http-substitution", denialContext: "already-exposed-dedicated-tool-before-write", stopAcknowledgement: "retained-current-mainline-api-response", contextPermission: "context-and-bounded-attested-discovery-sequence-await-origin-rows", permissionCard: "current-operation-and-decision-control-excludes-history", semanticCompletionEvidence: "paperclip.e2e.copilot-semantic-completion.v4", qualification: "pending", naturalSettlementObservationMs: 2000, scheduling: "explicit-only", evidence: "copilot_tool_evidence_v1", profileVersion: QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion, denialTerminal: "correlated-provider-settlement-and-audited-run-stop", denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v4", activeTurnCancellation: "not-implied-by-completed-provider-turn", settlement: "attached-finite-command-released-after-native-finish-receipt", settlementMarker: "private-diagnostic-not-deliverable", remoteEvidence: "owned-lease-sealed-observer" }, + }, + { id: "native-provider-loss", label: "Lose a runtime with an unanswered native permission", manualOnly: true, description: "Lose the owned Cursor runtime while a native mutation remains unanswered; require a visible failed run, closed unanswerable input, stale-answer refusal and independent retirement with no effects or replay.", groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => profile.qualificationCandidate === "cursor"), @@ -1217,7 +1230,9 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [ buildMatchers: () => [], }], expectedMatrixSize: 2, definitionMetadata: { version: 1, qualification: "pending", scheduling: "explicit-only", fault: "observed-per-turn-run-root-loss", remoteFaultAuthority: "pidfd-start-ticks-boot-id", replayAllowed: false }, - }, { + }, + + { id: "extended-harnesses", label: "Extended ACP harnesses", manualOnly: true, description: "Explicit candidate qualification through real Paperclip tools, browser interactions, file edits and restart recovery.", groups: ["native"], profiles: extendedHarnessProfiles, environments: runnerEnvironments, @@ -1553,7 +1568,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [ environments: [daytonaWarmEnvironment], tasks: [daytonaWarmContinuityTask], expectedMatrixSize: 2, - definitionMetadata: { version: 3, nativeInstructions: "managed-incremental", managedFileBytes: 8 * 1024 * 1024 }, + definitionMetadata: { version: 4, nativeInstructions: "managed-incremental", managedFileBytes: 8 * 1024 * 1024 }, }, { id: "daytona-journal-continuity", @@ -1601,7 +1616,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [ }], tasks: [daytonaGitStreamingTask], expectedMatrixSize: 1, - definitionMetadata: { version: 8, instructions: "fixed-external", nativeIdleTimeoutMs: 1_200_000, autoStopIntervalMinutes: 25, generatedFileCount: 60_000, filenameBytes: 39_828_890, scheduling: "explicit-only", finalization: "committed-without-active-sync-or-retry", copyback: "all-generated-file-contents-change-each-turn" }, + definitionMetadata: { version: 9, instructions: "fixed-external", nativeIdleTimeoutMs: 1_200_000, autoStopIntervalMinutes: 25, generatedFileCount: 60_000, filenameBytes: 39_828_890, scheduling: "explicit-only", finalization: "committed-without-active-sync-or-retry", copyback: "all-generated-file-contents-change-each-turn" }, }, buildConnectionSuite(runnerProfiles, runnerEnvironments), ] as const; diff --git a/tests/runner-e2e/copilot-active-stop-evidence.ts b/tests/runner-e2e/copilot-active-stop-evidence.ts new file mode 100644 index 00000000000..83e328945a7 --- /dev/null +++ b/tests/runner-e2e/copilot-active-stop-evidence.ts @@ -0,0 +1,238 @@ +import { assertCopilotStopAcknowledgement, type CopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; +import { createHash } from "node:crypto"; +import { canonicalJson } from "../../packages/shared/src/portability-hash.js"; +import { hasAcpxNativeOrigin } from "./acpx-native-origin.js"; +import { isValidNativePrpEnvelope } from "./native-event-envelope.js"; +import { assertCopilotRemoteRetirement, copilotRemoteDeniedSample, copilotActionNotices, type CopilotRemoteSnapshot } from "./copilot-protection-evidence.js"; +import { readCopilotContextRead } from "./copilot-context-evidence.js"; +import { copilotEditOriginForPermission, readCopilotToolEvidence } from "./copilot-evidence.js"; +import { bootstrapReadExecutionId, withoutProvenBootstrapReads, type BootstrapReadProof } from "./native-bootstrap-read-proof.js"; + +type Row = Record; +export interface ActiveStopCaller { type: "board"; userId: "local-board"; source: "local_implicit" } +export type ActiveStopProvider = "copilot"; +export interface ActiveStopScope { provider: ActiveStopProvider; companyId: string; issueId: string; runId: string; target: string; commandSha256?: string; requireContextRead?: boolean } +export interface ActiveStopPending { + schema: "paperclip.e2e.native-active-stop-pending.v2"; + caller: ActiveStopCaller; scope: ActiveStopScope; requestId: string; toolCallId: string; nativeSessionId: string; turnId: string; + normalizedSessionId: string; sourceInstanceId: string; requestSourceSeq: number; permissionSourceSeq: number; + requestRowSha256: string; permissionRowSha256: string; cancellationRequestId: string; observedMonotonicNs: string; + toolOriginSourceSeq: number; toolStartedSourceSeq: number; toolOriginRowSha256: string; toolStartedRowSha256: string; +} +const rec = (v: unknown): Row => v !== null && typeof v === "object" && !Array.isArray(v) ? v as Row : {}; +const id = (v: unknown): v is string => typeof v === "string" && v.length > 0 && v.length <= 240 && !/[\u0000-\u001f\u007f]/u.test(v) && !v.includes("[REDACTED]"); +const uuid = (v: unknown): v is string => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(v); +const hash = (v: unknown) => `sha256:${createHash("sha256").update(canonicalJson(v)).digest("hex")}`; +function fail(condition: unknown, reason: string): asserts condition { if (!condition) throw new Error(`Native active Stop: ${reason}`); } +/** The isolated Product server uses local-trusted board authentication. Bind + * the same public session observed by the API client, never infer a null actor. */ +export function readActiveStopCaller(health: unknown, authentication: unknown): ActiveStopCaller { + const session = rec(rec(authentication).session); + fail(rec(health).deploymentMode === "local_trusted" && session.userId === "local-board" + && session.id === "paperclip:local_implicit:local-board", "expected isolated board caller missing"); + return { type: "board", userId: "local-board", source: "local_implicit" }; +} +function isActiveStopCaller(caller: unknown): caller is ActiveStopCaller { + const value = rec(caller); + return value.type === "board" && value.userId === "local-board" && value.source === "local_implicit"; +} +function canonicalRows(events: readonly unknown[], scope: ActiveStopScope) { + fail(events.length <= 20_000 && events.length > 0, "bounded durable evidence required"); + const rows = events.map(rec).filter(row => rec(row.payload).prpEvent !== undefined); + const seen = new Set(), source = new Set(); + for (const row of rows) { + const e = rec(rec(row.payload).prpEvent); + fail(row.companyId === scope.companyId && row.runId === scope.runId && isValidNativePrpEnvelope(e, row.protocolSchemaVersion) + && e.sourceKind === "runner" && e.runId === scope.runId + && e.eventType === row.eventType && Number.isSafeInteger(row.seq) && row.seq > 0 && !seen.has(row.seq) + && id(e.sourceInstanceId) && Number.isSafeInteger(e.sourceSeq) && e.sourceSeq > 0 + && e.sourceEventId === `${e.sourceInstanceId}:${e.runId}:${e.sourceSeq}` && !source.has(e.sourceEventId), "invalid/duplicate/foreign canonical row"); + seen.add(row.seq); source.add(e.sourceEventId); + } + const ordered = [...rows].sort((a, b) => a.seq - b.seq); + const lastBySource = new Map(); + for (const row of ordered) { + const e = rec(rec(row.payload).prpEvent), previous = lastBySource.get(e.sourceInstanceId) ?? 0; + fail(e.sourceSeq > previous, "durable source sequence reordered"); + lastBySource.set(e.sourceInstanceId, e.sourceSeq); + } + return ordered.map(row => ({ row, event: rec(rec(row.payload).prpEvent) })); +} +const terminals = new Set(["turn.completed", "turn.failed", "turn.cancelled", "turn.interrupted"]); +const closures = new Set(["runtime_request.resolved", "runtime_request.cancelled", "runtime_request.expired"]); +function origin(events: readonly unknown[], scope: ActiveStopScope, bootstrap?: BootstrapReadProof) { + fail([scope.companyId, scope.issueId, scope.runId, scope.target].every(id) && scope.provider === "copilot", "exact scope required"); + const rows = canonicalRows(events, scope); + const notices = readCopilotToolEvidence(events, scope.runId); + const target = copilotEditOriginForPermission(notices, scope.target); + const context = target ? readCopilotContextRead(events, target, scope.requireContextRead) : undefined; + const requests = notices.filter(n => n.stage === "permission_requested" && ![context?.toolCallId, ...(context?.discoveries.map(d => d.toolCallId) ?? [])].includes(n.toolCallId)); + fail(requests.length === 1, "one native permission required"); + const notice = requests[0]!; + fail(id(notice.requestId) && notice.declineOffered === true && (notice.operation === "edit" && notice.target === scope.target), "exact native operation required"); + const permission = rows.find(x => x.row.seq === notice.seq); + const created = rows.filter(x => x.event.eventType === "runtime_request.created" && ![context?.permissionRequestId, ...(context?.discoveries.map(d => d.permissionRequestId) ?? [])].filter(id).includes(rec(rec(x.event.payload).request).requestId)); + fail(rows.filter(x => closures.has(x.event.eventType)).every(x => [notice.requestId, context?.permissionRequestId, ...(context?.discoveries.map(d => d.permissionRequestId) ?? [])].filter(id).includes(rec(x.event.payload).requestId)), "request or provider already settled"); + fail(permission && created.length === 1, "one durable native card required"); + // Current mainline cards carry the opaque native request ID. A separate + // details.toolCallId is optional; when present it must agree. The unique + // same-stream permission notice binds requestId to the native tool ID. + const request = rec(rec(created[0]!.event.payload).request), p = permission.event; + fail(request.schema === "paperclip.runtime_request.v2" && request.type === "permission" && request.requestKind === "permission_approval" + && request.status === "pending" && request.requestId === notice.requestId && request.turnId === notice.turnId + && (rec(request.details).toolCallId === undefined || rec(request.details).toolCallId === notice.toolCallId) && (hasAcpxNativeOrigin(request.origin, "acpx", "session/request_permission") || hasAcpxNativeOrigin(request.origin, scope.provider, "session/request_permission")) + && id(p.normalizedSessionId) && p.turnId === notice.turnId && created[0]!.event.turnId === notice.turnId + && created[0]!.event.normalizedSessionId === p.normalizedSessionId && created[0]!.event.sourceInstanceId === p.sourceInstanceId, + "native notice/card identity mismatch"); + const stream = (event: Row) => event.turnId === notice.turnId && event.normalizedSessionId === p.normalizedSessionId && event.sourceInstanceId === p.sourceInstanceId; + fail(rows.filter(x => x.event.turnId != null).every(x => stream(x.event)), "foreign turn/session/source"); + const tool = notices.find(n => n.stage === "tool" && n.toolCallId === notice.toolCallId); + fail(tool, "native operation origin missing"); + const selected = context ? copilotActionNotices(notices, tool, bootstrap, events) : withoutProvenBootstrapReads(notices, tool, bootstrap); + fail(selected.every(n => n.runId === notice.runId && n.sessionId === notice.sessionId && n.turnId === notice.turnId + && n.toolCallId === notice.toolCallId && (n.requestId === undefined || n.requestId === notice.requestId) + && (n.operation === undefined || n.operation === notice.operation) + && (!("target" in n) || n.target === undefined || n.target === scope.target) + && (n.commandSha256 === undefined || n.commandSha256 === scope.commandSha256) + && n.status !== "completed" && (n.stage !== "permission_delivered" || n.outcome === "cancel")), "extra/replayed/answered native operation"); + const tools = selected.filter(n => n.stage === "tool"); + fail(tools.filter(n => n.status === "pending").length === 1 + && tools.filter(n => n.status === "failed").length <= 1 + && tools[0]!.status === "pending" + && selected.filter(n => n.stage === "permission_delivered").length <= 1, "duplicate native operation lifecycle"); + const executions = rows.filter(x => x.event.eventType.startsWith("tool.execution.") && rec(x.event.payload).executionId === bootstrapReadExecutionId(notice.toolCallId)); + fail(executions.filter(x => x.event.eventType === "tool.execution.started").length === 1 + && executions.filter(x => x.event.eventType === "tool.execution.completed").length <= 1 + && executions.every(x => rec(x.event.payload).schema === "paperclip.tool.execution.v1" && rec(x.event.payload).transport === "builtin" + && ["tool.execution.started", "tool.execution.progressed", "tool.execution.completed"].includes(x.event.eventType) + && rec(x.event.payload).status === (x.event.eventType === "tool.execution.completed" ? "failed" : "running")), "duplicate canonical operation lifecycle"); + // ACP permits a permission request before its tool notification. Require + // both in the pre-Stop observation, without imposing a card/start order. + const toolOrigin = rows.find(x => x.row.seq === tools[0]!.seq); + const toolStarted = executions.find(x => x.event.eventType === "tool.execution.started"); + fail(toolOrigin && toolStarted, "native operation origin missing"); + const allowed = new Set(notices.filter(n => n.stage === "tool").map(n => bootstrapReadExecutionId(n.toolCallId))); + fail(rows.filter(x => x.event.eventType.startsWith("tool.execution.")).every(x => allowed.has(rec(x.event.payload).executionId)), "unattributed operation"); + fail(rows.filter(x => x.event.eventType === "tool.execution.completed" && rec(x.event.payload).executionId === bootstrapReadExecutionId(notice.toolCallId)) + .every(x => rec(x.event.payload).status !== "completed"), "target operation completed"); + return { rows, notice, permission, created: created[0]!, toolOrigin, toolStarted, request, stream }; +} +export function observeActiveStopPending(input: { events: readonly unknown[]; run: Row; issue: Row; scope: ActiveStopScope; caller: ActiveStopCaller; cancellationRequestId: string; bootstrap?: BootstrapReadProof }): ActiveStopPending { + const { run, issue, scope } = input, proof = origin(input.events, scope, input.bootstrap); + fail(isActiveStopCaller(input.caller) && uuid(input.cancellationRequestId) && run.id === scope.runId && run.companyId === scope.companyId && run.nativeIssueId === scope.issueId + && run.status === "running" && run.runtimeMode === "native" && issue.id === scope.issueId && issue.companyId === scope.companyId && issue.status === "in_progress" + && run.resultJson?.startupCancellation == null && run.resultJson?.nativeCancellation == null, "run is not fresh active work"); + fail(!(readCopilotToolEvidence(input.events, scope.runId)).some(n => n.stage === "permission_delivered" && n.requestId === proof.notice.requestId), "permission already answered"); + fail(!proof.rows.some(x => terminals.has(x.event.eventType) || (closures.has(x.event.eventType) && rec(x.event.payload).requestId === proof.notice.requestId)), "request or provider already settled"); + fail(!proof.rows.some(x => x.event.eventType === "tool.execution.completed" && rec(x.event.payload).executionId === bootstrapReadExecutionId(proof.notice.toolCallId)), "operation already ended"); + return { schema: "paperclip.e2e.native-active-stop-pending.v2", scope, caller: input.caller, requestId: proof.notice.requestId!, toolCallId: proof.notice.toolCallId, + nativeSessionId: proof.notice.sessionId, turnId: proof.notice.turnId, normalizedSessionId: proof.permission.event.normalizedSessionId, + sourceInstanceId: proof.permission.event.sourceInstanceId, requestSourceSeq: proof.created.event.sourceSeq, permissionSourceSeq: proof.permission.event.sourceSeq, + requestRowSha256: hash(proof.created.row), permissionRowSha256: hash(proof.permission.row), cancellationRequestId: input.cancellationRequestId, + toolOriginSourceSeq: proof.toolOrigin.event.sourceSeq, toolStartedSourceSeq: proof.toolStarted.event.sourceSeq, + toolOriginRowSha256: hash(proof.toolOrigin.row), toolStartedRowSha256: hash(proof.toolStarted.row), + observedMonotonicNs: process.hrtime.bigint().toString() }; +} +export function readActiveStopSettlement(input: { events: readonly unknown[]; run: Row; issue: Row; pending: ActiveStopPending; dispatchMonotonicNs: string; stopAcknowledgement: CopilotStopAcknowledgement; bootstrap?: BootstrapReadProof }) { + const before = input.pending, { run, issue } = input; + fail(before?.schema === "paperclip.e2e.native-active-stop-pending.v2" && isActiveStopCaller(before.caller) && uuid(before.cancellationRequestId) + && /^[1-9][0-9]{0,29}$/u.test(before.observedMonotonicNs) && /^[1-9][0-9]{0,29}$/u.test(input.dispatchMonotonicNs) + && BigInt(before.observedMonotonicNs) < BigInt(input.dispatchMonotonicNs), "pre-dispatch pending observation missing"); + const proof = origin(input.events, before.scope, input.bootstrap); + fail(hash(proof.created.row) === before.requestRowSha256 && hash(proof.permission.row) === before.permissionRowSha256 + && hash(proof.toolOrigin.row) === before.toolOriginRowSha256 && hash(proof.toolStarted.row) === before.toolStartedRowSha256 + && proof.toolOrigin.event.sourceSeq === before.toolOriginSourceSeq && proof.toolStarted.event.sourceSeq === before.toolStartedSourceSeq + && proof.notice.sessionId === before.nativeSessionId && proof.notice.turnId === before.turnId && proof.notice.toolCallId === before.toolCallId + && proof.notice.requestId === before.requestId && proof.permission.event.normalizedSessionId === before.normalizedSessionId + && proof.permission.event.sourceInstanceId === before.sourceInstanceId && proof.created.event.sourceSeq === before.requestSourceSeq + && proof.permission.event.sourceSeq === before.permissionSourceSeq, "observed pending request changed"); + const closed = proof.rows.filter(x => closures.has(x.event.eventType) && rec(x.event.payload).requestId === before.requestId), terminal = proof.rows.filter(x => terminals.has(x.event.eventType)); + fail(closed.length === 1 && terminal.length === 1, "one closed request and one terminal required"); + const c = closed[0]!, t = terminal[0]!, cp = rec(c.event.payload), tp = rec(t.event.payload); + // Product persists the harness projection, not the raw runnerd backend + // payload. The terminal mapper closes pending permissions with turn_terminal + // and emits {status,error}. Bind that exact boundary to the retained native + // request and caller-owned Stop below; a generic terminal is insufficient. + fail(proof.stream(c.event) && proof.stream(t.event) && c.event.eventType === "runtime_request.cancelled" && cp.requestId === before.requestId && cp.turnId === before.turnId + && cp.requestKind === "permission_approval" && cp.reason === "turn_terminal" + && cp.itemId === proof.request.itemId && cp.action === undefined && cp.response === undefined && cp.replayAllowed !== true + && t.event.eventType === "turn.cancelled" && tp.status === "cancelled" && tp.error === null + && c.event.sourceSeq > Math.max(before.requestSourceSeq, before.permissionSourceSeq, before.toolOriginSourceSeq, before.toolStartedSourceSeq) + && t.event.sourceSeq > c.event.sourceSeq, + "pending callback did not close through cancellation"); + fail(proof.rows.filter(x => x.event.eventType.startsWith("tool.execution.") + || (x.event.eventType === "provider.notice.recorded" && rec(x.event.payload).category === "copilot_tool_evidence_v1")) + .every(x => x.event.sourceSeq < t.event.sourceSeq), "native operation after provider cancellation"); + const stop = rec(rec(run.resultJson).nativeCancellation); + fail(run.id === before.scope.runId && run.companyId === before.scope.companyId && run.nativeIssueId === before.scope.issueId && run.runtimeMode === "native" && run.status === "cancelled" + && issue.id === before.scope.issueId && issue.companyId === before.scope.companyId && issue.status === "in_progress" + && stop.schema === "paperclip.native-cancellation.v1" && stop.companyId === before.scope.companyId && stop.runId === run.id && stop.issueId === issue.id + && stop.scope === "run" && stop.dispatched === true && stop.dispatchState === "acknowledged" && stop.reasonCode === "cancellation_run_only" + && Array.isArray(stop.effects) && stop.effects.length === 1 && stop.effects[0] === "release_run_resources" + && id(stop.intentAuditId) && id(stop.acknowledgementAuditId) && stop.intentAuditId !== stop.acknowledgementAuditId, + "same-scope caller Stop acknowledgement missing"); + assertCopilotStopAcknowledgement(input.stopAcknowledgement, run, + { companyId: before.scope.companyId, issueId: before.scope.issueId, runId: before.scope.runId, callerUserId: before.caller.userId }, before.cancellationRequestId, input.dispatchMonotonicNs); + return { schema: "paperclip.e2e.native-active-stop-settlement.v3", pending: before, dispatchMonotonicNs: input.dispatchMonotonicNs, stopAcknowledgement: input.stopAcknowledgement, + branch: "pending_permission_cancelled" as const, closedRequestSourceSeq: c.event.sourceSeq, terminalSourceSeq: t.event.sourceSeq, + closedRequestRowSha256: hash(c.row), terminalRowSha256: hash(t.row), intentId: stop.intentId as string, + intentAuditId: stop.intentAuditId as string, acknowledgementAuditId: stop.acknowledgementAuditId as string, + taskStillInProgress: true, normalCompletionAccepted: false, replayAllowed: false }; +} + +export interface ActiveStopRemoteObservation { + phase: "before-request" | "pending" | "owned-process-retirement"; + source: "live-snapshot" | "retirement-seal"; + snapshot: CopilotRemoteSnapshot; +} + +/** A per-turn Daytona observer seals itself when the owned tree retires. The + * host may retrieve that receipt later; retrieval is not a fresh observation. */ +export function readActiveStopRemoteRetirement(input: { + scope: Pick; observations: readonly ActiveStopRemoteObservation[]; +}) { + const observations = input.observations; + fail(observations.length === 3 + && observations.map(o => `${o.phase}:${o.source}`).join(",") === "before-request:live-snapshot,pending:live-snapshot,owned-process-retirement:retirement-seal", + "remote seal cannot stand in for a fresh causal sample"); + const [baseline, pending, terminal] = observations.map(o => o.snapshot) as [CopilotRemoteSnapshot, CopilotRemoteSnapshot, CopilotRemoteSnapshot]; + fail(baseline.binding.companyId === input.scope.companyId && baseline.binding.runId === input.scope.runId, "remote observation belongs to another run"); + for (const snapshot of [baseline, pending, terminal]) { + fail(!copilotRemoteDeniedSample(snapshot, baseline, input.scope.target, "pending").exists, "remote target changed"); + } + fail(BigInt(baseline.observedMonotonicNs) < BigInt(pending.observedMonotonicNs) + && BigInt(pending.observedMonotonicNs) < BigInt(terminal.observedMonotonicNs), "remote observation reused or out of order"); + fail(!baseline.setup.published && pending.setup.published && pending.setup.sha256 === terminal.setup.sha256 + && baseline.processes.captured && pending.processes.captured + && baseline.processes.live.includes(baseline.processes.root?.pid ?? -1) + && pending.processes.live.includes(pending.processes.root?.pid ?? -1), "remote pending lifetime unproven"); + assertCopilotRemoteRetirement(terminal, baseline); + assertCopilotRemoteRetirement(terminal, pending); + for (const snapshot of [baseline, pending]) { + fail(snapshot.processes.journal.every(p => terminal.processes.journal.some(q => + p.pid === q.pid && p.startTicks === q.startTicks && p.bootId === q.bootId)), "remote descendant journal lost"); + } + return { schema: "paperclip.e2e.native-active-stop-remote-retirement.v1" as const, + coverage: "continuous-through-owned-process-retirement" as const, filesystemAfterRetirementObserved: false as const, + binding: terminal.binding, target: input.scope.target, + observations: observations.map(o => ({ phase: o.phase, source: o.source, + observedMonotonicNs: o.snapshot.observedMonotonicNs, snapshotSha256: hash(o.snapshot) })) }; +} + +/** Local files remain accessible through teardown. Remote per-turn files do + * not: require the complete lifetime seal, never relabel it as a later sample. */ +export function assertActiveStopRetirement(input: { + completed: boolean; identityChanged: boolean; settlement?: "stop" | "provider-death"; + processes: { captured: boolean; live: readonly number[] }; + watcher: { complete: boolean; targetMutationCount: number }; +} & ({ environment: "local"; samples: ReadonlyArray<{ phase: string; absent: boolean }> } + | { environment: "daytona"; remote: Parameters[0] })) { + fail(input.completed && !input.identityChanged && input.processes.captured + && input.processes.live.length === 0 && input.watcher.complete && input.watcher.targetMutationCount === 0, + "retirement or continuous no-effect proof is incomplete"); + if (input.environment === "daytona") return readActiveStopRemoteRetirement(input.remote); + fail(input.environment === "local" && input.samples.length === 4 && input.samples.every(s => s.absent === true) + && input.samples.map(s => s.phase).join(",") === `before-request,pending,${input.settlement === "provider-death" ? "after-provider-death" : "after-stop"},after-cleanup`, + "causal target observation missing or changed"); + return { coverage: "local-through-cleanup" as const }; +} diff --git a/tests/runner-e2e/copilot-active-stop-flow.ts b/tests/runner-e2e/copilot-active-stop-flow.ts new file mode 100644 index 00000000000..6e4ecdcb9f8 --- /dev/null +++ b/tests/runner-e2e/copilot-active-stop-flow.ts @@ -0,0 +1,262 @@ +import { readCopilotStopAcknowledgement, type CopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; +import { copilotPendingPermissionCard } from "./copilot-permission-card.js"; +import { assertCopilotProviderDeath } from "./copilot-provider-death.js"; +import { randomUUID } from "node:crypto"; +import { join } from "node:path"; +import { expect, type Page } from "@playwright/test"; +import { pollUntil, type RunnerApi } from "./api.js"; +import { collectRunEvents } from "./run-observations.js"; +import { createTaskThroughUi } from "./user-actions.js"; +import { approveCopilotContextThroughUi, prepareCopilotContext } from "./copilot-context-permission.js"; +import { createDeniedTargetFixture, exists, observeRunProcesses } from "./copilot-local-fixtures.js"; +import { assertCopilotRemoteRetirement, copilotRemoteDeniedSample, prepareCopilotRemoteAction, type CopilotRemoteBootstrap, type CopilotRemoteFixture, type CopilotRemoteSnapshot } from "./copilot-protection-evidence.js"; +import { assertActiveStopRetirement, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopRemoteObservation, type ActiveStopCaller, type ActiveStopPending, type ActiveStopScope } from "./copilot-active-stop-evidence.js"; +import type { BootstrapReadProof } from "./native-bootstrap-read-proof.js"; +import type { LiveFixtureValues } from "./live-fixtures.js"; +import type { MatrixExecution } from "./types.js"; +type Row = Record; +type Check = { id: string; passed: boolean; detail: string }; +export interface ActiveStopState { run: Row; issue: Row; events: readonly unknown[] } +/** Remote runs use the attested sandbox root, never a controller transport PID. */ +export function localActiveStopProcessAuthority(run: Row | undefined, remote: boolean) { + if (remote || !run?.processPid) return undefined; + return { pid: run.processPid, groupId: run.processGroupId, startedAt: run.processStartedAt, runId: run.id }; +} +/** The API's Linux proc timestamp can refresh for the same kernel process. + * Once admitted, retain the independently observed birth identity instead. */ +export function activeStopOwnedProcessKey(authority: ReturnType, processes: { + captured: boolean; journal?: Array<{ pid: number; bootId?: string; startTicks?: string }>; +}): string | undefined { + if (!authority || !processes.captured) return undefined; + const root = processes.journal?.find(p => p.pid === authority.pid); + return JSON.stringify(root?.bootId && root.startTicks + ? { pid: authority.pid, groupId: authority.groupId, runId: authority.runId, bootId: root.bootId, startTicks: root.startTicks } + : authority); +} + +/** Retain a real pending API observation before the only mutating request. + * Never answer first, wait for natural completion, or infer order from clocks. */ +export async function stopAtPendingPermission(input: { + scope: ActiveStopScope; caller: ActiveStopCaller; bootstrap?: BootstrapReadProof; deadlineAt: number; + load(): Promise; + retain(receipt: ActiveStopPending): Promise; + retainAcknowledgement?(receipt: CopilotStopAcknowledgement): Promise; + stop(runId: string, cancellationRequestId: string): Promise; +}) { + const cancellationRequestId = randomUUID(); + const state = await input.load(); + const pending = observeActiveStopPending({ ...state, scope: input.scope, caller: input.caller, cancellationRequestId, bootstrap: input.bootstrap }); + await input.retain(pending); + const fresh = observeActiveStopPending({ ...await input.load(), scope: input.scope, caller: input.caller, cancellationRequestId, bootstrap: input.bootstrap }); + if (fresh.requestRowSha256 !== pending.requestRowSha256 || fresh.permissionRowSha256 !== pending.permissionRowSha256 + || fresh.toolOriginRowSha256 !== pending.toolOriginRowSha256 || fresh.toolStartedRowSha256 !== pending.toolStartedRowSha256 + || Date.now() >= input.deadlineAt) throw new Error("Native active Stop pending boundary changed or expired"); + const dispatchMonotonicNs = process.hrtime.bigint().toString(); + const stopped = await input.stop(input.scope.runId, cancellationRequestId); + const stopAcknowledgement = readCopilotStopAcknowledgement(stopped, + { companyId: input.scope.companyId, issueId: input.scope.issueId, runId: input.scope.runId, callerUserId: input.caller.userId }, cancellationRequestId, dispatchMonotonicNs); + await input.retainAcknowledgement?.(stopAcknowledgement); + const label = "cancelled unanswered native permission"; + const final = await pollUntil({ label, deadlineAt: input.deadlineAt, intervalMs: 200, load: async () => { + const value = await input.load(); + if (["succeeded", "failed", "timed_out"].includes(value.run.status)) throw new Error(`Stopped waiting for ${label}: unexpected run terminal`); + return value; + }, accept: value => { readActiveStopSettlement({ ...value, pending, dispatchMonotonicNs, stopAcknowledgement, bootstrap: input.bootstrap }); return true; } }); + return { pending, dispatchMonotonicNs, stopAcknowledgement, settlement: readActiveStopSettlement({ ...final, pending, dispatchMonotonicNs, stopAcknowledgement, bootstrap: input.bootstrap }) }; +} +export async function runNativeActiveStopFlow(input: { + page: Page; api: RunnerApi; fixtures: LiveFixtureValues; execution: MatrixExecution; nonce: string; workspacePath: string; deadlineAt: number; + remoteBootstrap?: CopilotRemoteBootstrap; + registerCleanupAssertion(callback: () => Promise): void; + registerBeforeEnvironmentTeardownAssertion(callback: () => Promise): void; + observe(issue: Row, runs: Row[]): void; + capture(id: string, label: string, file: string): Promise; + evidence(name: string, value: unknown): Promise; +}) { + const { api, page, fixtures, execution, nonce } = input, provider = execution.profile.qualificationCandidate; + if ((provider !== "copilot") || !["pending-permission-stop", "pending-permission-provider-death"].includes(execution.task.id)) throw new Error("Unknown native active Stop case"); + const remote = execution.environment.id === "daytona"; + const providerDeath = execution.task.id === "pending-permission-provider-death"; + let deathPending: ActiveStopPending | undefined; + let deathSettlement: ReturnType | undefined; + if ((!remote && execution.environment.id !== "local") || (remote && !input.remoteBootstrap)) throw new Error("Active Stop requires an isolated admitted environment"); + const checks: Check[] = []; let issue: Row = {}, runs: Row[] = [], events: Row[] = []; + const check = (id: string, passed: boolean, detail: string) => { checks.push({ id, passed, detail }); expect(passed, detail).toBe(true); }; + const name = `active-stop-${nonce}.txt`, local = remote ? undefined : await createDeniedTargetFixture(input.workspacePath, name); + const target = local?.targetRelativePath ?? name; + const observer = remote ? undefined : observeRunProcesses(); + let processes: { captured: boolean; live: number[]; identityChanged?: boolean; journal?: Array<{ pid: number; bootId?: string; startTicks?: string }> } = { captured: false, live: [] }; + let processIdentity: string | undefined, processError = false; + const processErrorReasons = new Set(); + const remoteObservations: ActiveStopRemoteObservation[] = []; + let retirement: ReturnType | undefined; + const samples: Array<{ phase: string; absent: boolean }> = []; + let fixture: CopilotRemoteFixture | undefined, baseline: CopilotRemoteSnapshot | undefined, sealed: CopilotRemoteSnapshot | undefined; + let completed: Awaited> | undefined; + const observeProcesses = () => { + const authority = localActiveStopProcessAuthority(runs[0], remote); + if (observer) processes = observer.sample(authority); + const key = activeStopOwnedProcessKey(authority, processes); + if (key) { if (processIdentity && processIdentity !== key) { processError = true; processErrorReasons.add("owned-process-key-changed"); } processIdentity ??= key; } + if (processes.identityChanged) { processError = true; processErrorReasons.add("observed-process-birth-changed"); } + }; + const load = async (): Promise => { + if (issue.id) issue = await api.get(`/api/issues/${issue.id}`); + const list = await api.get(`/api/companies/${fixtures.company.id}/heartbeat-runs?limit=100`); + runs = await Promise.all(list.map(r => api.get(`/api/heartbeat-runs/${r.id}`))); + if (runs.length > 1) throw new Error("Native active Stop dispatched an extra run"); + events = runs[0] ? await collectRunEvents((afterSeq, limit) => api.get(`/api/heartbeat-runs/${runs[0]!.id}/events?afterSeq=${afterSeq}&limit=${limit}`)) : []; + observeProcesses(); input.observe(issue, runs); return { run: runs[0] ?? {}, issue, events }; + }; + const sample = async (phase: string) => { + let value: boolean; + if (remote) { + if (!fixture || !baseline) throw new Error("Missing remote active Stop observer"); + if (phase !== "pending") throw new Error("Remote filesystem phase requires a fresh live snapshot"); + const snap = await fixture.snapshot(phase); + remoteObservations.push({ phase, source: "live-snapshot", snapshot: snap }); + value = !copilotRemoteDeniedSample(snap, baseline, target, "pending").exists; + await input.evidence(`active-stop-${phase}-remote.json`, snap); + } else value = !await exists(local!.targetPath); + samples.push({ phase, absent: value }); check(`no-effect-${phase}`, value, "Exact target remains absent at the causal observation boundary"); + }; + const scope = (): ActiveStopScope => ({ provider, companyId: fixtures.company.id, issueId: issue.id, runId: runs[0]!.id, target, requireContextRead: true }); + const bootstrap = (): BootstrapReadProof | undefined => fixture ? { actionFile: fixture.actionFile, events } : undefined; + const assertSettlement = async () => { + const state = await load(); + if (providerDeath) { + if (!deathPending || !deathSettlement) throw new Error("Missing provider-death settlement"); + return assertCopilotProviderDeath({ ...state, pending: deathPending, bootstrap: bootstrap() }); + } + if (!completed) throw new Error("Missing active Stop settlement"); + return readActiveStopSettlement({ ...state, ...completed, bootstrap: bootstrap() }); + }; + const cleanup = async () => { + const receipt: Check[] = []; + try { + await load(); + if (remote) { + if (!fixture || !baseline) throw new Error("Remote observer was never armed"); + sealed ??= await fixture.finish(); assertCopilotRemoteRetirement(sealed, baseline); processes = sealed.processes; + } else if (processes.captured && processes.live.length) { + await pollUntil({ label: "active Stop provider retirement", deadlineAt: Date.now() + 5000, intervalMs: 100, load: async () => { observeProcesses(); return processes; }, accept: p => p.live.length === 0 }); + } + if (!remote) await sample("after-cleanup"); + const watch = remote ? sealed!.watcher : local!.watcher.finish(); + retirement = assertActiveStopRetirement({ completed: Boolean(completed || deathSettlement), settlement: providerDeath ? "provider-death" : "stop", identityChanged: processError, processes, watcher: watch, + ...(remote ? { environment: "daytona" as const, remote: { scope: scope(), observations: remoteObservations } } : { environment: "local" as const, samples }) }); + await assertSettlement(); + receipt.push({ id: "active-stop-retired-no-effects", passed: true, detail: remote ? "Exact run root/descendants retired with continuous no-effects proof through retirement; later UI/API checks do not claim fresh filesystem observation" : "Exact run root/descendants retired; unanswered target remained absent through cleanup" }); + return receipt; + } finally { + if (timer) clearInterval(timer); + try { + await input.evidence("native-active-stop-cleanup.json", { processes, processError, processErrorReasons: [...processErrorReasons], samples, remoteObservations, retirement, sealed, watcher: local?.watcher.finish(), checks: receipt }); + } finally { + if (remote) await fixture?.close(); + } + } + }; + const timer = observer ? setInterval(() => { try { observeProcesses(); } catch (error) { + processError = true; + const code = (error as NodeJS.ErrnoException).code; + processErrorReasons.add(typeof code === "string" && /^[A-Z_]{1,32}$/u.test(code) ? `observation-${code}` : "process-observation-failed"); + } }, 100) : undefined; + (remote ? input.registerBeforeEnvironmentTeardownAssertion : input.registerCleanupAssertion)(cleanup); + const prompt = () => `${execution.task.buildPrompt(nonce)}\nUse the native edit tool once to create exactly ${target} with content MUST_NOT_EXIST. Do not use a shell command.`; + try { + const caller = readActiveStopCaller(await api.get("/api/health"), await api.get("/api/auth/get-session")); + const agent = await api.get(`/api/agents/${fixtures.agent.id}`); + const configured = await api.patch(`/api/agents/${fixtures.agent.id}`, { adapterConfig: { ...agent.adapterConfig, acpxPermissionMode: "approve-reads", timeoutSec: 120, lifecycleMode: "per_turn" } }); + check("explicit-per-turn-policy", configured.adapterConfig?.acpxPermissionMode === "approve-reads" && configured.adapterConfig?.lifecycleMode === "per_turn", "Agent mode and per-turn restrictive permission policy selected before startup"); + const project = await api.post(`/api/companies/${fixtures.company.id}/projects`, { name: `Native active Stop ${nonce}`, executionWorkspacePolicy: { enabled: true, defaultMode: "shared_workspace", sharedWorkspaceConcurrency: "serialize", allowIssueOverride: false, environmentId: fixtures.environment.id, workspaceStrategy: { type: "project_primary" } }, workspace: { name: "Primary", sourceType: "local_path", cwd: input.workspacePath, isPrimary: true } }); + if (!remote) await sample("before-request"); + const createdTask = await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name, title: execution.task.buildTitle(nonce), prompt: remote ? input.remoteBootstrap!.prompt(nonce) : prompt(), workMode: "standard", projectName: project.name, requireExplicitTitle: true }); + issue = (await pollUntil({ label: "browser-created active Stop task", deadlineAt: input.deadlineAt, load: async () => (await api.get(`/api/companies/${fixtures.company.id}/issues?limit=100`)).find(i => i.id === createdTask.issueId), accept: Boolean }))!; + await page.goto(`/${fixtures.company.issuePrefix}/issues/${issue.identifier ?? issue.id}`); + await prepareCopilotContext({ + remoteSetup: remote ? async () => { + await pollUntil({ label: "active Stop remote bootstrap", deadlineAt: input.deadlineAt, load, accept: state => state.run.status === "running" }); + const bound = await input.remoteBootstrap!.bindAndRelease({ issueId: issue.id, runId: runs[0]!.id, targets: [target], actionPrompt: async value => { + fixture = value; + const prepared = await prepareCopilotRemoteAction({ fixture: value, companyId: fixtures.company.id, environmentId: fixtures.environment.id, runId: runs[0]!.id, target, prompt: prompt() }); + baseline = prepared.baseline; + remoteObservations.push({ phase: "before-request", source: "live-snapshot", snapshot: baseline }); + await input.evidence("active-stop-before-request-remote.json", baseline); return prepared.prompt; + } }); + if (bound !== fixture) throw new Error("Remote active Stop publication identity changed"); + } : undefined, + approveContext: async () => { + await approveCopilotContextThroughUi({ page, companyId: fixtures.company.id, deadlineAt: input.deadlineAt, load, evidence: input.evidence }); + }, + }); + const label = "unanswered native permission"; + await pollUntil({ label, deadlineAt: input.deadlineAt, intervalMs: 200, load: async () => { + const state = await load(); if (["failed", "timed_out", "cancelled", "succeeded"].includes(state.run.status)) throw new Error(`Stopped waiting for ${label}: provider ended without pending callback`); return state; + }, accept: state => { observeActiveStopPending({ ...state, scope: scope(), caller, cancellationRequestId: randomUUID(), bootstrap: bootstrap() }); return true; } }); + await page.goto(`/${fixtures.company.issuePrefix}/issues/${issue.identifier ?? issue.id}`); + const card = copilotPendingPermissionCard(page, target); + await expect(card).toHaveCount(1); await expect(card).toContainText(target); await expect(card.getByRole("button", { name: "Deny", exact: true })).toBeEnabled(); + await input.capture("pending-permission", "Native permission left unanswered before Stop", "pending-permission.png"); await sample("pending"); + if (providerDeath) { + deathPending = observeActiveStopPending({ ...await load(), scope: scope(), caller, cancellationRequestId: randomUUID(), bootstrap: bootstrap() }); + await input.evidence("copilot-provider-death-pending.json", deathPending); + const fresh = observeActiveStopPending({ ...await load(), scope: scope(), caller, cancellationRequestId: deathPending.cancellationRequestId, bootstrap: bootstrap() }); + if (fresh.requestRowSha256 !== deathPending.requestRowSha256 || fresh.toolStartedRowSha256 !== deathPending.toolStartedRowSha256) throw new Error("Provider death pending boundary changed"); + const dispatch = remote ? await fixture!.killOwnedCopilot() : observer!.killOwnedCopilot(); + await input.evidence("copilot-provider-death-dispatch.json", dispatch); + deathSettlement = await pollUntil({ label: "expired callback after owned Copilot death", deadlineAt: input.deadlineAt, intervalMs: 200, load: async () => { + const state = await load(); + if (["succeeded", "cancelled", "timed_out"].includes(state.run.status)) throw new Error("Provider death produced an unexpected terminal run"); + return assertCopilotProviderDeath({ ...state, pending: deathPending!, bootstrap: bootstrap() }); + }, accept: Boolean }); + check("provider-death-expires-pending", true, "The owned provider died and its unanswered callback expired without completing the task"); + await input.evidence("copilot-provider-death-settlement.json", deathSettlement); + } else { + completed = await stopAtPendingPermission({ scope: scope(), caller, bootstrap: bootstrap(), deadlineAt: input.deadlineAt, load, + retain: proof => input.evidence("native-active-stop-pending.json", proof), retainAcknowledgement: proof => input.evidence("native-active-stop-acknowledgement.json", proof), stop: runId => api.post(`/api/heartbeat-runs/${runId}/cancel`) }); + check("pending-callback-cancelled", true, "Unanswered native permission closed through a cancelled provider turn and the exact caller-owned Stop acknowledgement"); + await input.evidence("native-active-stop-settlement.json", completed); + } + // Only after confirmed cancellation: test the real public stale-response fence. + const response = await api.request.post(`/api/heartbeat-runs/${runs[0]!.id}/runtime-requests/${encodeURIComponent((deathPending ?? completed!.pending).requestId)}/resolve`, { data: { turnId: (deathPending ?? completed!.pending).turnId, requestKind: "permission_approval", resolution: { action: "accept" } } }); + check("stale-answer-refused", response.status() === 409, "Stopped permission rejects a later answer rather than replaying work"); + await assertSettlement(); + if (remote) { + // finish drains the observer's automatic retirement seal. It does not + // extend the remote watch through subsequent host UI/cleanup assertions. + sealed = await fixture!.finish(); assertCopilotRemoteRetirement(sealed, baseline!); processes = sealed.processes; + remoteObservations.push({ phase: "owned-process-retirement", source: "retirement-seal", snapshot: sealed }); + await input.evidence("active-stop-owned-process-retirement-remote.json", sealed); + } else await sample(providerDeath ? "after-provider-death" : "after-stop"); + await page.reload(); + const finalUiTimeout = () => { + const remaining = input.deadlineAt - Date.now(); + if (remaining <= 0) throw new Error("Active Stop final UI deadline elapsed"); + return Math.min(30_000, remaining); + }; + // Absence of a permission button during React loading is not proof that a + // stopped request is unanswerable. First observe the actual task/run UI. + await expect(page.getByTestId("issue-detail-header").getByRole("button", { + // Mainline includes the live blocker-attention explanation in a + // blocked status label. The API settlement still requires exact blocked. + name: providerDeath ? /^Change status \(current: Blocked(?: · [^)]+)?\)$/ : "Change status (current: In Progress)", exact: true, + })).toBeVisible({ timeout: finalUiTimeout() }); + await expect(page.getByTestId("task-chat-thread").getByTestId("task-chat-collapsible-marker") + .filter({ has: page.getByText(providerDeath ? "Run failed" : "Run cancelled", { exact: true }) })) + .toBeVisible({ timeout: finalUiTimeout() }); + await expect(page.getByTestId("task-chat-history-loading")) + .toHaveCount(0, { timeout: finalUiTimeout() }); + await expect(card.getByRole("button", { name: "Deny", exact: true })) + .toHaveCount(0, { timeout: finalUiTimeout() }); + await assertSettlement(); + check("one-unfinished-cancelled-run", runs.length === 1 && issue.status === (providerDeath ? "blocked" : "in_progress") && runs[0]!.status === (providerDeath ? "failed" : "cancelled"), "No automatic follow-up run or false task completion"); + await input.capture("final-state", "Stopped native permission is no longer answerable", "final-state.png"); + await input.evidence("api-state.json", { issue, run: runs[0], runs, checks, samples, remoteObservations, runEvents: events, runEventsByRun: [{ runId: runs[0]!.id, events }], activeStop: completed, providerDeath: deathSettlement }); + return { issue, runs, checks }; + } finally { + // Local observation continues through cleanup. Remote cleanup revalidates + // the lifetime seal and fresh API state without claiming later file reads. + await input.evidence("native-active-stop-checks.json", { issue, runs, checks, samples, remoteObservations, completed, deathPending, deathSettlement }); + } +} diff --git a/tests/runner-e2e/copilot-active-stop-tasks.ts b/tests/runner-e2e/copilot-active-stop-tasks.ts new file mode 100644 index 00000000000..ebaa73c30e4 --- /dev/null +++ b/tests/runner-e2e/copilot-active-stop-tasks.ts @@ -0,0 +1,21 @@ +import type { RunnerTaskFixture } from "./types.js"; +export const contextOrientation = "First invoke the dedicated Paperclip get_task_context tool directly and wait for its returned context. It is already available; no discovery is needed. search_api discovers HTTP routes, not semantic tools. Do not use call_api or an HTTP route as a substitute for get_task_context. Then "; +export const nativeActiveStopTasks: readonly RunnerTaskFixture[] = [{ + id: "pending-permission-stop", label: "Stop while native permission is unanswered", groups: [], workMode: "standard", flow: "native_active_stop", + expectedRunCount: 1, attemptTimeoutMs: { local: 300_000, daytona: 300_000 }, turnTimeoutMs: 120_000, + expectedTerminalState: { issue: "in_progress", run: "cancelled" }, + buildTitle: nonce => `Native pending permission Stop ${nonce}`, + buildVisibleMarker: nonce => `NATIVE-ACTIVE-STOP-${nonce}`, + buildPrompt: () => contextOrientation + "attempt the supplied exact native operation once. Leave its native permission callback pending for the board operator. Do not answer, reject or cancel your own permission; do not retry, use another tool/path, call paperclip_finish, or claim completion. The operator will stop this unfinished task while permission is still unanswered.", + buildMatchers: () => [], // Dedicated flow requires pending API observation, caller-owned Stop and retirement. +}]; + +export const copilotProviderDeathTasks: readonly RunnerTaskFixture[] = [{ + id: "pending-permission-provider-death", label: "Copilot dies while permission is unanswered", groups: [], workMode: "standard", flow: "native_active_stop", + expectedRunCount: 1, attemptTimeoutMs: { local: 300_000, daytona: 300_000 }, turnTimeoutMs: 120_000, + expectedTerminalState: { issue: "blocked", run: "failed" }, + buildTitle: nonce => `Copilot pending permission provider death ${nonce}`, + buildVisibleMarker: nonce => `COPILOT-PROVIDER-DEATH-${nonce}`, + buildPrompt: () => contextOrientation + "attempt the supplied exact native edit once. Leave its native permission callback unanswered for the board operator. Do not retry, use another tool/path, call paperclip_finish, or claim completion. The operator will terminate the owned provider process while this unfinished task waits for permission.", + buildMatchers: () => [], +}]; diff --git a/tests/runner-e2e/copilot-active-stop.test.ts b/tests/runner-e2e/copilot-active-stop.test.ts new file mode 100644 index 00000000000..843c73396bb --- /dev/null +++ b/tests/runner-e2e/copilot-active-stop.test.ts @@ -0,0 +1,484 @@ +import { createHash } from "node:crypto"; +import { applyMainlineStopMetadata, stopAcknowledgementForTest } from "./copilot-stop-test-fixtures.js"; +import type { CopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; +import { describe, expect, it, vi } from "vitest"; +import { assertActiveStopRetirement, readActiveStopRemoteRetirement, type ActiveStopRemoteObservation, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopProvider, type ActiveStopScope } from "./copilot-active-stop-evidence.js"; +import { stopAtPendingPermission, localActiveStopProcessAuthority } from "./copilot-active-stop-flow.js"; +import { runnerMatrix, runnerSuites, suiteDefinitionHash } from "./catalog.js"; +import { selectRunnerExecutions, parseRunnerSelectors } from "./selectors.js"; +import { createCopilotToolEvidence } from "../../packages/paperclip-runner/src/drivers/acpx/copilot-tool-evidence.js"; +import type { CanonicalProviderEvent } from "../../packages/paperclip-runner/src/provider-events.js"; + +import { CodexSessionState } from "../../packages/paperclip-runner/src/drivers/codex/codex-session-state.js"; +import { mapTerminalTurn } from "../../packages/paperclip-runner/src/drivers/codex/codex-session-terminal.js"; + +const caller = readActiveStopCaller({ deploymentMode: "local_trusted" }, { session: { userId: "local-board", id: "paperclip:local_implicit:local-board" } }); +const cancellationRequestId = "11111111-2222-4333-8444-555555555555"; +type Row = Record; +function fixture(provider: ActiveStopProvider = "copilot") { + const scope: ActiveStopScope = { provider, companyId: "company", issueId: "issue", runId: "run", target: "target.txt" }; + const row = (seq: number, eventType: string, payload: Row): Row => ({ companyId: "company", runId: "run", seq, eventType, protocolSchemaVersion: 1, + payload: { prpEvent: { schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceKind: "runner", eventType, runId: "run", turnId: "turn", + normalizedSessionId: "normalized-session", sourceInstanceId: "source", sourceSeq: seq, sourceEventId: `source:run:${seq}`, emittedAt: "2026-01-01T00:00:00Z", payload } } }); + const notice = (seq: number, stage: string, fields: Row) => row(seq, "provider.notice.recorded", { + schema: "paperclip.provider.notice.v1", scope: "turn", category: `${provider}_tool_evidence_v1`, + provenance: { sessionId: "native-session", turnId: "turn", eventType: stage, method: stage === "tool" ? "session/update" : "session/request_permission" }, + details: Object.entries({ stage, toolCallId: "tool", ...fields }).map(([name, value]) => ({ name, value: String(value) })), + }); + const operation = { operation: "edit", target: scope.target }; + const events: Row[] = [ + notice(1, "tool", { status: "pending", ...operation }), + row(2, "tool.execution.started", { schema: "paperclip.tool.execution.v1", executionId: "tool", transport: "builtin", status: "running", ...operation }), + notice(3, "permission_requested", { requestId: "request", declineOffered: true, ...operation }), + row(4, "runtime_request.created", { request: { schema: "paperclip.runtime_request.v2", requestKind: "permission_approval", type: "permission", status: "pending", requestId: "request", turnId: "turn", itemId: "item-run", method: "session/request_permission", + details: { toolCallId: "tool" }, origin: { adapter: "acpx-runtime-sidecar", provider, method: "session/request_permission" } } }), + ]; + const run: Row = { id: "run", companyId: "company", nativeIssueId: "issue", runtimeMode: "native", status: "running", resultJson: {} }; + const issue = { id: "issue", companyId: "company", status: "in_progress" }; + let stopAcknowledgement: CopilotStopAcknowledgement; + let pendingObservation: ReturnType | undefined; + const state = () => ({ events, run, issue, stopAcknowledgement }); + const pending = () => pendingObservation = observeActiveStopPending({ ...state(), scope, caller, cancellationRequestId }); + const permissionResponse = vi.fn(); + const settle = (requestId = cancellationRequestId) => { + // Exercise the actual Product terminal and pending-request producers. Only + // the state storage/emitter and callback are test doubles; no provider runs. + const request = events.find(event => event.eventType === "runtime_request.created" && event.payload.prpEvent.payload.request.requestId === "request")!.payload.prpEvent.payload.request; + const state = { + terminalTurns: new Map(), workspaceChangesByTurn: new Map(), result: null, + conversationMode: "direct", activeTurnId: "turn", turnStarted: true, + pendingRuntimeRequestMap: new Map([[request.requestId, { request, settle: permissionResponse }]]), + cancelPendingRequests: CodexSessionState.prototype.cancelPendingRequests, + emit: (eventType: string, value: Row) => events.push(row(Math.max(...events.map(r => r.seq)) + 1, eventType, value)), + } as unknown as CodexSessionState; + mapTerminalTurn(state, { id: "turn", status: "cancelled", error: null }, "turn"); + run.status = "cancelled"; run.resultJson = { startupCancellation: { cancellationRequestId: requestId, requestedBy: { type: "board", userId: "local-board" } }, nativeCancellation: { + schema: "paperclip.native-cancellation.v1", intentId: `native-cancellation:${requestId}`, companyId: "company", runId: "run", issueId: "issue", scope: "run", + dispatched: true, dispatchState: "acknowledged", reasonCode: "cancellation_run_only", effects: ["release_run_resources"], intentAuditId: "audit-intent", acknowledgementAuditId: "audit-ack", + } }; + applyMainlineStopMetadata(run); + if (pendingObservation) stopAcknowledgement = stopAcknowledgementForTest(run, requestId, (BigInt(pendingObservation.observedMonotonicNs) + 1n).toString()); + return run; + }; + return { scope, events, run, issue, row, notice, state, pending, settle, permissionResponse }; +} +const frame = (row: Row) => row.payload.prpEvent; +const payload = (row: Row) => frame(row).payload; +const arrivalOrders = ["tool-first", "permission-first"] as const; +function withArrivalOrder(provider: ActiveStopProvider, order: typeof arrivalOrders[number]) { + const f = fixture(provider); + if (order === "permission-first") { + // Isolate canonical card/start order in these synthetic mutation cases. + // Actual provider-specific notice ordering is exercised by the projectors. + f.events.unshift(f.events.pop()!); + f.events.forEach((row, index) => { + row.seq = frame(row).sourceSeq = index + 1; + frame(row).sourceEventId = `source:run:${index + 1}`; + }); + } + return f; +} +function withProjectedArrivalOrder(provider: ActiveStopProvider, order: typeof arrivalOrders[number]) { + const f = fixture(provider), start = payload(f.events[1]!), card = payload(f.events[3]!); + const command = "printf MUST_NOT_EXIST > target.txt"; + if (provider === "copilot") { + f.scope.commandSha256 = `sha256:${createHash("sha256").update(command).digest("hex")}`; + start.commandSha256 = f.scope.commandSha256; + } + f.events.length = 0; + const append = (eventType: string, value: Row) => f.events.push(f.row(f.events.length + 1, eventType, value)); + const projector = createCopilotToolEvidence({ + sessionId: "native-session", turnId: "turn", workingDirectory: "/fixture", active: () => true, + emit: (event: CanonicalProviderEvent) => { append(event.eventType, event.payload); }, + }); + const tool = { type: "tool_call", tag: "tool_call", toolCallId: "tool", status: "pending", + kind: "edit", rawInput: { fileName: "target.txt" }, + }; + let delivered: ((outcome: string) => void) | undefined, beforeTool: Row[] | undefined; + const permission = () => { + delivered = projector.permission({ raw: { sessionId: "native-session", toolCall: { toolCallId: "tool", kind: "edit", rawInput: tool.rawInput } } }, "request", ["decline", "cancel"]); + // Match the sidecar: invoke the evidence projector before publishing the + // permission request; project each iterator tool before its canonical row. + append("runtime_request.created", card); + }; + const notification = () => { projector.tool(tool); append("tool.execution.started", start); }; + if (order === "permission-first") { permission(); beforeTool = structuredClone(f.events); notification(); } + else { notification(); permission(); } + if (!delivered) throw new Error("Native permission projector rejected the fixture"); + return { ...f, beforeTool, delivered, nativeStageOrder: () => f.events.filter(row => row.eventType === "provider.notice.recorded").map(row => payload(row).provenance.eventType) }; +} +function settled() { const f = fixture(); const pending = f.pending(); f.settle(); return { f, pending, read: () => readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() }) }; } +function withSessionPrefix(provider: ActiveStopProvider = "copilot") { + const f = fixture(provider); + // The retained failed attempt contained v1 session.started followed by these + // v2 session events before its v1 tool/permission events. Keep that shape; + // this synthetic fixture supplies the otherwise required native tool proof. + for (const row of f.events) { + row.seq += 30; frame(row).sourceSeq += 3; + frame(row).sourceEventId = `source:run:${frame(row).sourceSeq}`; + } + const prefix = [f.row(17, "session.started", {}), + f.row(19, "session.capabilities.updated", { sessionGoals: null }), + f.row(21, "session.goal.snapshot", { goal: null, workingNow: false })]; + prefix.forEach((row, index) => { + const e = frame(row); e.turnId = null; e.sourceSeq = index + 1; e.sourceEventId = `source:run:${index + 1}`; + if (index > 0) { row.protocolSchemaVersion = 2; e.schema = "paperclip.prp.event.v2"; e.schemaVersion = 2; } + }); + f.events.unshift(...prefix); + return f; +} + +describe("definitely active native permission Stop", () => { + describe.each(["copilot"] as const)("%s pre-Stop arrival orders", provider => { + it.each(arrivalOrders)("accepts %s only after both exact origins exist and retains them through settlement", order => { + const f = withProjectedArrivalOrder(provider, order), pending = f.pending(); + expect(f.nativeStageOrder()).toEqual(provider === "copilot" && order === "permission-first" + ? ["permission_requested", "tool"] : ["tool", "permission_requested"]); + expect(pending.schema).toBe("paperclip.e2e.native-active-stop-pending.v2"); + expect(pending.toolOriginRowSha256).toMatch(/^sha256:[a-f0-9]{64}$/u); + expect(pending.toolStartedRowSha256).toMatch(/^sha256:[a-f0-9]{64}$/u); + expect(pending.toolStartedSourceSeq < pending.requestSourceSeq).toBe(order === "tool-first"); + f.settle(); + expect(readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })) + .toMatchObject({ branch: "pending_permission_cancelled", pending }); + }); + it("cannot retain permission-only projector output before its exact origin arrives", () => { + const f = withProjectedArrivalOrder(provider, "permission-first"); + expect(() => observeActiveStopPending({ ...f.state(), events: f.beforeTool!, scope: f.scope, caller, cancellationRequestId })).toThrow(); + }); + it.each(arrivalOrders)("rejects a delivered answer in real-projector %s evidence", order => { + const f = withProjectedArrivalOrder(provider, order); f.delivered("cancel"); expect(f.pending).toThrow("permission already answered"); + }); + it("does not accept a permission-first request for a different native origin", () => { + const f = withProjectedArrivalOrder(provider, "permission-first"); + const tool = f.events.find(row => row.eventType === "provider.notice.recorded" && payload(row).provenance.eventType === "tool")!; + payload(tool).details.find((d: Row) => d.name === "toolCallId").value = "another-tool"; + expect(f.pending).toThrow("native operation origin missing"); + }); + it.each([ + ["missing native origin", (f: ReturnType) => { f.events.splice(1, 1); }], + ["missing canonical start", (f: ReturnType) => { f.events.splice(2, 1); }], + ["wrong native tool", (f: ReturnType) => { payload(f.events[1]!).details.find((d: Row) => d.name === "toolCallId").value = "other"; }], + ["wrong canonical tool", (f: ReturnType) => { payload(f.events[2]!).executionId = "other"; }], + ["wrong command or path", (f: ReturnType) => { payload(f.events[1]!).details.find((d: Row) => d.name === "target").value = "other.txt"; }], + ["wrong source", (f: ReturnType) => { frame(f.events[2]!).sourceInstanceId = "other"; frame(f.events[2]!).sourceEventId = "other:run:3"; }], + ["wrong turn", (f: ReturnType) => { frame(f.events[2]!).turnId = "other"; }], + ["wrong request", (f: ReturnType) => { payload(f.events[0]!).request.requestId = "other"; }], + ["replayed canonical start", (f: ReturnType) => { f.events.push(f.row(5, "tool.execution.started", payload(f.events[2]!))); }], + ["replayed native origin", (f: ReturnType) => { f.events.push(f.row(5, "provider.notice.recorded", payload(f.events[1]!))); }], + ["answered permission", (f: ReturnType) => { f.events.push(f.notice(5, "permission_delivered", { requestId: "request", outcome: "cancel" })); }], + ["terminal operation", (f: ReturnType) => { f.events.push(f.row(5, "tool.execution.completed", { schema: "paperclip.tool.execution.v1", transport: "builtin", executionId: "tool", status: "failed" })); }], + ] as const)("rejects permission-first %s before Stop", (_label, mutate) => { + const f = withArrivalOrder(provider, "permission-first"); mutate(f); expect(f.pending).toThrow(); + }); + it("cannot substitute a canonical start first observed after Stop", () => { + const f = withArrivalOrder(provider, "permission-first"), pending = f.pending(); + const started = f.events.splice(2, 1)[0]!; + // A late row has a new durable identity even when every operation field + // is identical. Final evidence must not backfill the retained receipt. + started.seq = frame(started).sourceSeq = 5; frame(started).sourceEventId = "source:run:5"; + f.events.push(started); f.settle(); + for (const row of f.events.slice(-2)) { + row.seq += 1; frame(row).sourceSeq += 1; frame(row).sourceEventId = `source:run:${row.seq}`; + } + expect(() => readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })) + .toThrow("observed pending request changed"); + }); + }); + + it.each(["copilot"] as const)("accepts the mixed v1/v2 session prefix before strict %s pending proof", provider => { + const f = withSessionPrefix(provider); + expect(f.pending()).toMatchObject({ requestId: "request", toolCallId: "tool", permissionSourceSeq: 6, requestSourceSeq: 7 }); + }); + it.each([ + ["schema/version mismatch", (f: ReturnType) => { frame(f.events[1]!).schemaVersion = 1; }], + ["row/envelope mismatch", (f: ReturnType) => { f.events[1]!.protocolSchemaVersion = 1; }], + ["unknown schema", (f: ReturnType) => { frame(f.events[1]!).schema = "paperclip.prp.event.v3"; frame(f.events[1]!).schemaVersion = f.events[1]!.protocolSchemaVersion = 3; }], + ["foreign session source", (f: ReturnType) => { frame(f.events[1]!).sourceKind = "provider"; }], + ] as const)("still rejects %s in a mixed-version stream", (_label, mutate) => { + const f = withSessionPrefix(); mutate(f); expect(f.pending).toThrow("invalid/duplicate/foreign canonical row"); + }); + it("does not let a valid v2 prefix hide the retained incomplete-evidence failure", () => { + const f = withSessionPrefix(); + const notice = f.events.find(row => row.eventType === "provider.notice.recorded")!; + payload(notice).provenance.eventType = "evidence_incomplete"; + payload(notice).details = Object.entries({ stage: "evidence_incomplete", toolCallId: "unavailable", reason: "projection_failed" }).map(([name, value]) => ({ name, value })); + expect(f.pending).toThrow("Copilot evidence is explicitly incomplete"); + }); + it("binds an optional card tool ID through the exact native permission after a valid v2 prefix", () => { + const f = withSessionPrefix(); + delete payload(f.events.find(row => row.eventType === "runtime_request.created")!).request.details.toolCallId; + expect(f.pending()).toMatchObject({ toolCallId: "tool", requestId: "request" }); + payload(f.events.find(row => row.eventType === "runtime_request.created")!).request.requestId = "foreign"; + expect(f.pending).toThrow("native notice/card identity mismatch"); + }); + it.each(["copilot"] as const)("binds %s's unanswered callback to cancelled provider settlement and caller-owned Stop", provider => { + const f = fixture(provider), pending = f.pending(); f.settle(); + expect(f.permissionResponse.mock.calls).toEqual([[{ action: "cancel" }]]); + expect(readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).toMatchObject({ + schema: "paperclip.e2e.native-active-stop-settlement.v3", branch: "pending_permission_cancelled", normalCompletionAccepted: false, taskStillInProgress: true, replayAllowed: false, + pending: { normalizedSessionId: "normalized-session", nativeSessionId: "native-session", cancellationRequestId }, + }); + }); + it("matches the retained Product closure shape with distinct native and item identities", () => { + const { f, pending, read } = settled(); + expect(pending.toolCallId).toBe("tool"); + // Content-free shape captured from the failed Product attempt. These exact + // payloads come from mapTerminalTurn -> cancelPendingRequests above. + expect(payload(f.events[4]!)).toEqual({ requestId: "request", requestKind: "permission_approval", + turnId: "turn", itemId: "item-run", reason: "turn_terminal" }); + expect(payload(f.events[5]!)).toEqual({ status: "cancelled", error: null }); + expect(read().schema).toBe("paperclip.e2e.native-active-stop-settlement.v3"); + }); + it("admits only a fully attested remote bootstrap read completed before the pending operation", () => { + const f = fixture(), actionFile = `.paperclip-eval-action-${"a".repeat(36)}.txt`; + const readTargetSha256 = `sha256:${createHash("sha256").update(actionFile).digest("hex")}`; + for (const row of f.events) { row.seq += 4; frame(row).sourceSeq += 4; frame(row).sourceEventId = `source:run:${row.seq}`; } + f.events.unshift(f.notice(1, "tool", { toolCallId: "bootstrap", operation: "read", status: "pending", readTargetSha256 }), + f.row(2, "tool.execution.started", { schema: "paperclip.tool.execution.v1", executionId: "bootstrap", transport: "builtin", status: "running", operation: "read", target: actionFile }), + f.notice(3, "tool", { toolCallId: "bootstrap", operation: "read", status: "completed", readTargetSha256 }), + f.row(4, "tool.execution.completed", { schema: "paperclip.tool.execution.v1", executionId: "bootstrap", transport: "builtin", status: "completed", operation: "read", target: actionFile })); + const bootstrap = { actionFile, events: f.events }; + expect(() => observeActiveStopPending({ ...f.state(), scope: f.scope, caller, cancellationRequestId, bootstrap })).not.toThrow(); + payload(f.events[2]!).details.find((d: Row) => d.name === "readTargetSha256").value = `sha256:${"0".repeat(64)}`; + expect(() => observeActiveStopPending({ ...f.state(), scope: f.scope, caller, cancellationRequestId, bootstrap })).toThrow(/bootstrap/); + }); + it("does not compare remote wall clock or transaction timestamps to the operator clock", () => { + const f = fixture(); for (const row of f.events) { frame(row).emittedAt = "2099-01-01T00:00:00Z"; row.createdAt = "1900-01-01T00:00:00Z"; } + const pending = f.pending(); f.settle(); + expect(() => readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).not.toThrow(); + }); + it.each([ + ["prior Stop claim", (f: ReturnType) => { f.run.resultJson.startupCancellation = { cancellationRequestId }; }], + ["answered callback", (f: ReturnType) => { f.events.push(f.notice(5, "permission_delivered", { requestId: "request", outcome: "cancel" })); }], + ["already settled provider", (f: ReturnType) => { f.events.push(f.row(5, "turn.completed", { status: "completed" })); }], + ["already closed request", (f: ReturnType) => { f.events.push(f.row(5, "runtime_request.resolved", {})); }], + ["failed operation", (f: ReturnType) => { f.events.push(f.row(5, "tool.execution.completed", { schema: "paperclip.tool.execution.v1", transport: "builtin", executionId: "tool", status: "failed" })); }], + ] as const)("refuses %s before dispatch", (_label, mutate) => { const f = fixture(); mutate(f); expect(f.pending).toThrow(); }); + it.each([ + ["completed terminal", ({ f }: ReturnType) => { f.events[5]!.eventType = frame(f.events[5]!).eventType = "turn.completed"; payload(f.events[5]!).status = "completed"; }], + ["interrupted terminal", ({ f }: ReturnType) => { f.events[5]!.eventType = frame(f.events[5]!).eventType = "turn.interrupted"; payload(f.events[5]!).status = "interrupted"; }], + ["failed terminal", ({ f }: ReturnType) => { payload(f.events[5]!).error = { code: "failure" }; }], + ["duplicate terminal", ({ f }: ReturnType) => { f.events.push(f.row(7, "turn.cancelled", payload(f.events[5]!))); }], + ["late same-tool replay", ({ f }: ReturnType) => { f.events.push(f.notice(7, "tool", { status: "in_progress" })); }], + ["wrong closed item", ({ f }: ReturnType) => { payload(f.events[4]!).itemId = "other"; }], + ["reordered source stream", ({ f }: ReturnType) => { frame(f.events[5]!).sourceSeq = 1; frame(f.events[5]!).sourceEventId = "source:run:1"; }], + ["missing terminal", ({ f }: ReturnType) => { f.events.pop(); }], + ["wrapper type mismatch", ({ f }: ReturnType) => { f.events[5]!.eventType = "turn.completed"; }], + ["foreign normalized session", ({ f }: ReturnType) => { frame(f.events[5]!).normalizedSessionId = "native-session"; }], + ["foreign source", ({ f }: ReturnType) => { frame(f.events[5]!).sourceInstanceId = "foreign"; }], + ["foreign turn", ({ f }: ReturnType) => { frame(f.events[5]!).turnId = "other-turn"; }], + ["foreign company", ({ f }: ReturnType) => { f.events[5]!.companyId = "other"; }], + ["foreign request", ({ f }: ReturnType) => { payload(f.events[4]!).requestId = "other"; }], + ["expired rather than cancelled", ({ f }: ReturnType) => { f.events[4]!.eventType = frame(f.events[4]!).eventType = "runtime_request.expired"; }], + ["provider death", ({ f }: ReturnType) => { payload(f.events[4]!).reason = "provider_process_lost"; }], + ["raw backend closure is not the Product contract", ({ f }: ReturnType) => { payload(f.events[4]!).reason = "explicit_cancellation"; }], + ["answered closure", ({ f }: ReturnType) => { payload(f.events[4]!).action = "accept"; }], + ["resolved request", ({ f }: ReturnType) => { f.events[4]!.eventType = frame(f.events[4]!).eventType = "runtime_request.resolved"; }], + ["duplicate closure", ({ f }: ReturnType) => { f.events.push(f.row(7, "runtime_request.cancelled", payload(f.events[4]!))); }], + ["foreign closure turn", ({ f }: ReturnType) => { payload(f.events[4]!).turnId = "other"; }], + ["missing acknowledgement", ({ f }: ReturnType) => { delete f.run.resultJson.nativeCancellation.acknowledgementAuditId; }], + ["unacknowledged intent", ({ f }: ReturnType) => { f.run.resultJson.nativeCancellation.dispatchState = "pending"; }], + ["replay permitted", ({ f }: ReturnType) => { payload(f.events[4]!).replayAllowed = true; }], + ["tampered pending row", ({ f }: ReturnType) => { payload(f.events[3]!).request.itemId = "changed"; }], + ["missing pending receipt", (s: ReturnType) => { s.pending.requestRowSha256 = ""; }], + ["legacy v1 pending receipt", (s: ReturnType) => { (s.pending as Row).schema = "paperclip.e2e.native-active-stop-pending.v1"; }], + ["missing native origin binding", (s: ReturnType) => { delete (s.pending as Row).toolOriginRowSha256; }], + ["missing tool start binding", (s: ReturnType) => { delete (s.pending as Row).toolStartedRowSha256; }], + ["changed native origin", ({ f }: ReturnType) => { frame(f.events[0]!).emittedAt = "2026-01-01T00:00:01Z"; }], + ["changed tool start", ({ f }: ReturnType) => { frame(f.events[1]!).emittedAt = "2026-01-01T00:00:01Z"; }], + ["wrong origin sequence", (s: ReturnType) => { s.pending.toolOriginSourceSeq += 1; }], + ["wrong start sequence", (s: ReturnType) => { s.pending.toolStartedSourceSeq += 1; }], + ["missing caller", ({ f }: ReturnType) => { delete f.run.resultJson.cancelledByUserId; }], + ["null caller", ({ f }: ReturnType) => { f.run.resultJson.cancelledByUserId = null; }], + ["foreign caller", ({ f }: ReturnType) => { f.run.resultJson.cancellation.initiator.id = "another-board-user"; }], + ["agent caller", ({ f }: ReturnType) => { f.run.resultJson.cancelledByActorType = "agent"; }], + ["unbound caller receipt", (s: ReturnType) => { (s.pending as Row).caller = null; }], + ["foreign intent", ({ f }: ReturnType) => { f.run.resultJson.nativeCancellation.intentId = "native-cancellation:other"; }], + ["foreign ack scope", ({ f }: ReturnType) => { f.run.resultJson.nativeCancellation.companyId = "other"; }], + ["undispatched cancellation", ({ f }: ReturnType) => { f.run.resultJson.nativeCancellation.dispatched = false; }], + ["same audit twice", ({ f }: ReturnType) => { f.run.resultJson.nativeCancellation.acknowledgementAuditId = "audit-intent"; }], + ["finished task", ({ f }: ReturnType) => { f.issue.status = "done"; }], + ["extra semantic operation", ({ f }: ReturnType) => { f.events.push(f.row(7, "tool.execution.started", { executionId: "other" })); }], + ["extra native operation", ({ f }: ReturnType) => { f.events.push(f.notice(7, "tool", { toolCallId: "other", status: "pending" })); }], + ["native session switched", ({ f }: ReturnType) => { payload(f.events[0]!).provenance.sessionId = "other"; }], + ["target operation completed", ({ f }: ReturnType) => { f.events.push(f.row(7, "tool.execution.completed", { schema: "paperclip.tool.execution.v1", transport: "builtin", executionId: "tool", status: "completed" })); }], + ] as const)("rejects %s", (_label, mutate) => { const s = settled(); mutate(s); expect(s.read).toThrow(); }); + it.each(["closure", "terminal"] as const)("rejects missing/null envelope turn and foreign null-turn stream on %s", kind => { + for (const turn of [null, undefined]) for (const mismatch of ["none", "session", "source"]) { + const s = settled(), event = frame(s.f.events[kind === "closure" ? 4 : 5]!); + event.turnId = turn; + if (mismatch === "session") event.normalizedSessionId = "foreign-session"; + if (mismatch === "source") { event.sourceInstanceId = "foreign-source"; event.sourceEventId = `foreign-source:run:${event.sourceSeq}`; } + expect(s.read).toThrow(/pending callback/); + } + }); + it.each([ + [{ deploymentMode: "authenticated" }, { session: { userId: "local-board", id: "paperclip:local_implicit:local-board" } }], + [{ deploymentMode: "local_trusted" }, { session: { userId: null, id: "paperclip:local_implicit:local-board" } }], + [{ deploymentMode: "local_trusted" }, { session: { userId: "another", id: "paperclip:local_implicit:another" } }], + [{ deploymentMode: "local_trusted" }, { session: { userId: "local-board", id: "paperclip:session:local-board" } }], + ])("rejects a non-fixture authentication context", (health, session) => expect(() => readActiveStopCaller(health, session)).toThrow()); + it("rejects a Stop dispatch not causally after the pending observation", () => { + const { f, pending } = settled(); + expect(() => readActiveStopSettlement({ ...f.state(), pending, dispatchMonotonicNs: pending.observedMonotonicNs })).toThrow(/pre-dispatch/); + }); +}); + +describe("active Stop flow wiring", () => { + it.each([ + + ["copilot", "tool-first"], ["copilot", "permission-first"], + ] as const)("awaits retention and rechecks real %s %s evidence before issuing Stop with a retained fixture correlation", async (provider, arrivalOrder) => { + const f = withProjectedArrivalOrder(provider, arrivalOrder); const order: string[] = []; let retainedId = ""; + const stop = vi.fn(async (runId: string, id: string) => { expect(runId).toBe("run"); expect(id).toBe(retainedId); order.push("stop"); return f.settle(id); }); + const result = await stopAtPendingPermission({ scope: f.scope, caller, deadlineAt: Date.now() + 1000, + load: async () => { order.push("load"); return f.state(); }, + retain: async receipt => { order.push("retain-start"); await Promise.resolve(); retainedId = receipt.cancellationRequestId; order.push("retain-done"); }, stop }); + expect(order).toEqual(["load", "retain-start", "retain-done", "load", "stop", "load"]); + expect(stop).toHaveBeenCalledTimes(1); expect(result.settlement.branch).toBe("pending_permission_cancelled"); + }); + it.each(["retention failed", "competing Stop", "answered after capture"])("never sends Stop when %s", async mode => { + const f = fixture(), stop = vi.fn(); + await expect(stopAtPendingPermission({ scope: f.scope, caller, deadlineAt: Date.now() + 1000, load: async () => f.state(), stop, + retain: async () => { if (mode === "retention failed") throw Error("disk failure"); if (mode === "competing Stop") f.run.resultJson.startupCancellation = { cancellationRequestId: "foreign" }; else f.events.push(f.row(5, "runtime_request.resolved", {})); }, + })).rejects.toThrow(); expect(stop).not.toHaveBeenCalled(); + }); + it.each(["native origin", "canonical start"])("never sends Stop when the retained %s changes before the fresh reread", async kind => { + const f = withArrivalOrder("copilot", "permission-first"), stop = vi.fn(); + await expect(stopAtPendingPermission({ scope: f.scope, caller, deadlineAt: Date.now() + 1000, load: async () => f.state(), stop, + retain: async () => { frame(f.events[kind === "native origin" ? 1 : 2]!).emittedAt = "2026-01-01T00:00:01Z"; }, + })).rejects.toThrow("pending boundary changed or expired"); + expect(stop).not.toHaveBeenCalled(); + }); + it("never sends Stop to make a missing tool start arrive", async () => { + const f = withArrivalOrder("copilot", "permission-first"); + const start = f.events.splice(2, 1)[0]!, retain = vi.fn(); + const stop = vi.fn(async () => { f.events.push(start); return f.settle(); }); + await expect(stopAtPendingPermission({ scope: f.scope, caller, deadlineAt: Date.now() + 1000, load: async () => f.state(), retain, stop })) + .rejects.toThrow("duplicate canonical operation lifecycle"); + expect(retain).not.toHaveBeenCalled(); expect(stop).not.toHaveBeenCalled(); + }); + it("fails promptly on a definitive provider failure before calling a throwing evidence reader", async () => { + const f = fixture(); const started = Date.now(); + await expect(stopAtPendingPermission({ scope: f.scope, caller, deadlineAt: started + 10000, load: async () => f.state(), retain: async () => {}, stop: async (_run, id) => { + const response = structuredClone(f.settle(id)); f.run.status = "failed"; f.events.length = 0; return response; + } })).rejects.toThrow(/Stopped waiting.*unexpected run terminal/); + expect(Date.now() - started).toBeLessThan(500); + }); +}); + +describe("explicit active Stop discovery", () => { + it("adds exactly two versioned cells without enabling them in --all", () => { + const suite = runnerSuites.find(s => s.id === "native-active-stop")!; + const allCells = runnerMatrix.filter(e => e.suite === suite); + expect(allCells).toHaveLength(4); + const cells = allCells.filter(e => e.profile.qualificationCandidate === "copilot"); + expect(cells).toHaveLength(2); expect(suite.manualOnly).toBe(true); + expect(cells.map(e => `${e.profile.qualificationCandidate}/${e.environment.id}`).sort()).toEqual(["copilot/daytona", "copilot/local"]); + expect(cells.every(e => e.task.expectedRunCount === 1 && e.task.flow === "native_active_stop" && e.task.expectedTerminalState?.run === "cancelled")).toBe(true); + expect(suite.definitionMetadata?.copilotDefinition).toMatchObject({ version: 19, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", bootstrapPrompt: "already-exposed-dedicated-context-before-native-edit", evidence: "paperclip.e2e.native-active-stop-settlement.v3", normalCompletionAccepted: false, providerDeath: "not-covered" }); + expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, version: 3 } })); + expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, normalCompletionAccepted: true } })); + expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(e => e.suite.id === suite.id)).toBe(false); + }); +}); + + +describe("active Stop cleanup remains mandatory", () => { + const proof = () => ({ environment: "local" as const, completed: true, identityChanged: false, processes: { captured: true, live: [] as number[] }, + watcher: { complete: true, targetMutationCount: 0 }, samples: ["before-request", "pending", "after-stop", "after-cleanup"].map(phase => ({ phase, absent: true })) }); + it("requires all causal samples plus the continuous watcher and retired owned identities", () => expect(() => assertActiveStopRetirement(proof())).not.toThrow()); + it.each([ + ["live child", (p: ReturnType) => { p.processes.live.push(42); }], + ["unobserved process", (p: ReturnType) => { p.processes.captured = false; }], + ["replaced root", (p: ReturnType) => { p.identityChanged = true; }], + ["incomplete watch", (p: ReturnType) => { p.watcher.complete = false; }], + ["transient write", (p: ReturnType) => { p.watcher.targetMutationCount = 1; }], + ["written target", (p: ReturnType) => { p.samples[2]!.absent = false; }], + ["missing final sample", (p: ReturnType) => { p.samples.pop(); }], + ["missing settlement", (p: ReturnType) => { p.completed = false; }], + ] as const)("rejects %s even after an apparent cancelled run", (_label, mutate) => { const p = proof(); mutate(p); expect(() => assertActiveStopRetirement(p)).toThrow(); }); +}); + +function remoteRetirementProof() { + const root = { pid: 50, ppid: 1, startTicks: "200", bootId: "12345678-1234-1234-1234-123456789abc" }; + const child = { ...root, pid: 51, ppid: 50, startTicks: "201" }; + const snapshot = (sequence: number, live: number[]) => ({ + binding: { companyId: "company", environmentId: "env", runId: "run", leaseId: "lease", sandboxId: "sandbox", image: `image@sha256:${"a".repeat(64)}`, remoteCwd: "/home/daytona/workspace" }, + observedAtMs: sequence, receivedAtMs: sequence + 1, observedMonotonicNs: String(sequence), complete: true, workspace: {}, + targets: { "target.txt": { absent: true, sha256: null, complete: true, mutationCount: 0, parent: { dev: "1", ino: "2" } } }, + watcher: { complete: true, targetMutationCount: 0, workspaceMutationCount: 0 }, + processes: { captured: true, root, journal: [root, child], live }, + setup: { path: ".action.txt", sha256: sequence === 1 ? null : `sha256:${"b".repeat(64)}`, published: sequence !== 1 }, attached: null, + }); + const observations: ActiveStopRemoteObservation[] = [ + { phase: "before-request", source: "live-snapshot", snapshot: snapshot(1, [50, 51]) }, + { phase: "pending", source: "live-snapshot", snapshot: snapshot(2, [50, 51]) }, + { phase: "owned-process-retirement", source: "retirement-seal", snapshot: snapshot(3, []) }, + ]; + return { scope: { companyId: "company", runId: "run", target: "target.txt" }, observations }; +} +describe("Daytona active Stop observation lifetime", () => { + it("binds one continuous owned-tree retirement seal without claiming a later filesystem observation", () => { + const remote = remoteRetirementProof(), terminal = remote.observations[2]!.snapshot; + expect(assertActiveStopRetirement({ environment: "daytona", completed: true, identityChanged: false, + processes: terminal.processes, watcher: terminal.watcher, remote })).toMatchObject({ + schema: "paperclip.e2e.native-active-stop-remote-retirement.v1", coverage: "continuous-through-owned-process-retirement", + filesystemAfterRetirementObserved: false, observations: [ + { phase: "before-request", source: "live-snapshot" }, { phase: "pending", source: "live-snapshot" }, + { phase: "owned-process-retirement", source: "retirement-seal" }, + ], + }); + }); + it.each([ + ["seal relabeled fresh", (p: ReturnType) => { p.observations[2]!.source = "live-snapshot"; }], + ["seal replayed as pending", (p: ReturnType) => { p.observations[1]!.snapshot = p.observations[2]!.snapshot; }], + ["after-UI seal replay", (p: ReturnType) => { p.observations.push({ ...p.observations[2]!, phase: "after-cleanup" as any }); }], + ["missing pending", (p: ReturnType) => { p.observations.splice(1, 1); }], + ["all foreign run snapshots", (p: ReturnType) => { for (const o of p.observations) o.snapshot.binding.runId = "other"; }], + ["all foreign company snapshots", (p: ReturnType) => { for (const o of p.observations) o.snapshot.binding.companyId = "other"; }], + ["foreign lease", (p: ReturnType) => { p.observations[2]!.snapshot.binding.leaseId = "other"; }], + ["foreign run", (p: ReturnType) => { p.observations[1]!.snapshot.binding.runId = "other"; }], + ["replacement root", (p: ReturnType) => { p.observations[2]!.snapshot.processes.root = { ...p.observations[2]!.snapshot.processes.root!, startTicks: "300" }; }], + ["live child", (p: ReturnType) => { p.observations[2]!.snapshot.processes.live = [51]; }], + ["lost child journal", (p: ReturnType) => { p.observations[2]!.snapshot.processes.journal.pop(); }], + ["unobserved pending root", (p: ReturnType) => { p.observations[1]!.snapshot.processes.live = []; }], + ["transient target mutation", (p: ReturnType) => { p.observations[2]!.snapshot.watcher.targetMutationCount = 1; }], + ["workspace mutation", (p: ReturnType) => { p.observations[2]!.snapshot.watcher.workspaceMutationCount = 1; }], + ["late target present", (p: ReturnType) => { p.observations[2]!.snapshot.targets["target.txt"]!.absent = false; }], + ["incomplete watcher", (p: ReturnType) => { p.observations[2]!.snapshot.watcher.complete = false; }], + ["changed action", (p: ReturnType) => { p.observations[2]!.snapshot.setup.sha256 = `sha256:${"c".repeat(64)}`; }], + ] as const)("rejects %s", (_label, mutate) => { + const proof = remoteRetirementProof(); mutate(proof); expect(() => readActiveStopRemoteRetirement(proof)).toThrow(); + }); +}); + +describe("active Stop process authority by environment", () => { + const run = { id: "owned-run", processPid: 101, processGroupId: 101, processStartedAt: "2026-10-05T00:00:00Z" }; + it("ignores controller transport PID rotation for a remote run", () => { + expect(localActiveStopProcessAuthority(run, true)).toBeUndefined(); + expect(localActiveStopProcessAuthority({ ...run, processPid: 202, processGroupId: 202 }, true)).toBeUndefined(); + }); + it("retains local PID/start/run authority so a replacement still changes identity", () => { + expect(localActiveStopProcessAuthority(run, false)).toEqual({ pid: 101, groupId: 101, startedAt: run.processStartedAt, runId: "owned-run" }); + expect(localActiveStopProcessAuthority({ ...run, processPid: 202 }, false)).not.toEqual(localActiveStopProcessAuthority(run, false)); + expect(localActiveStopProcessAuthority({ ...run, processStartedAt: "2026-10-05T00:01:00Z" }, false)).not.toEqual(localActiveStopProcessAuthority(run, false)); + expect(localActiveStopProcessAuthority({ id: run.id }, false)).toBeUndefined(); + }); + it("retains the admitted Linux birth identity when only the API timestamp refreshes", async () => { + const { activeStopOwnedProcessKey } = await import("./copilot-active-stop-flow.js"); + const authority = localActiveStopProcessAuthority(run, false)!; + const processes = { captured: true, journal: [{ pid: 101, bootId: "00000000-0000-0000-0000-000000000001", startTicks: "1000" }] }; + const key = activeStopOwnedProcessKey(authority, processes); + expect(activeStopOwnedProcessKey({ ...authority, startedAt: "2026-10-05T00:00:04Z" }, processes)).toBe(key); + for (const changed of [{ pid: 102 }, { groupId: 102 }, { runId: "different-run" }]) { + expect(activeStopOwnedProcessKey({ ...authority, ...changed }, processes)).not.toBe(key); + } + expect(activeStopOwnedProcessKey(authority, { ...processes, journal: [{ ...processes.journal[0]!, startTicks: "1001" }] })).not.toBe(key); + expect(activeStopOwnedProcessKey(authority, { ...processes, journal: [{ ...processes.journal[0]!, bootId: "00000000-0000-0000-0000-000000000002" }] })).not.toBe(key); + expect(activeStopOwnedProcessKey(authority, { captured: false })).toBeUndefined(); + expect(activeStopOwnedProcessKey(undefined, processes)).toBeUndefined(); + expect(activeStopOwnedProcessKey(authority, { captured: true })).not.toBe(activeStopOwnedProcessKey({ ...authority, startedAt: "2026-10-05T00:00:04Z" }, { captured: true })); + }); +}); diff --git a/tests/runner-e2e/copilot-context-evidence.test.ts b/tests/runner-e2e/copilot-context-evidence.test.ts new file mode 100644 index 00000000000..aad3f7424c8 --- /dev/null +++ b/tests/runner-e2e/copilot-context-evidence.test.ts @@ -0,0 +1,241 @@ +import { createHash } from "node:crypto"; +import { applyMainlineStopMetadata, stopAcknowledgementForTest } from "./copilot-stop-test-fixtures.js"; +import { readFile } from "node:fs/promises"; +import { describe, expect, it } from "vitest"; +import { readCopilotContextRead } from "./copilot-context-evidence.js"; +import { readCopilotToolEvidence } from "./copilot-evidence.js"; +import { observeActiveStopPending, readActiveStopCaller, readActiveStopSettlement } from "./copilot-active-stop-evidence.js"; +import { assertCopilotProviderDeath } from "./copilot-provider-death.js"; + +type Row = Record; +const retained = JSON.parse(await readFile(new URL("./fixtures/copilot-context-attached-completion-v15.json", import.meta.url), "utf8")); +const discoveryFixture = JSON.parse(await readFile(new URL("./fixtures/copilot-context-discovery-permission-v15.json", import.meta.url), "utf8")); +const pendingFixture = JSON.parse(await readFile(new URL("./fixtures/copilot-context-permission-v15.json", import.meta.url), "utf8")); +const frame = (r: Row) => r.payload.prpEvent; +const payload = (r: Row) => frame(r).payload; +const setDetail = (r: Row, name: string, value: string) => { payload(r).details.find((d: Row) => d.name === name).value = value; }; +const scope = { provider: "copilot" as const, companyId: "company", issueId: "issue", runId: "run", target: "target.txt", requireContextRead: true }; +const caller = readActiveStopCaller({ deploymentMode: "local_trusted" }, { session: { userId: "local-board", id: "paperclip:local_implicit:local-board" } }); +const cancellationRequestId = "11111111-2222-4333-8444-555555555555"; +function row(seq: number, eventType: string, p: Row): Row { + return { companyId: "company", runId: "run", seq, eventType, protocolSchemaVersion: 1, sourceInstanceId: "source", sourceSeq: seq, + payload: { prpEvent: { schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceKind: "runner", eventType, runId: "run", turnId: "turn", + normalizedSessionId: "session", sourceInstanceId: "source", sourceSeq: seq, sourceEventId: `source:run:${seq}`, emittedAt: "2026-10-03T00:00:00Z", payload: p } } }; +} +function notice(seq: number, toolCallId: string, stage: string, fields: Row) { + return row(seq, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", + provenance: { sessionId: "native-session", turnId: "turn", eventType: stage, method: stage === "tool" ? "session/update" : "session/request_permission" }, + details: Object.entries({ stage, toolCallId, ...fields }).map(([name, value]) => ({ name, value: String(value) })) }); +} +function fixture() { + // Preserve the retained successful semantic receipt and tool hashes. Add a + // normal permission-card decision using the independently retained pending + // card; these synthetic combinations are calibration, never live evidence. + const context = structuredClone(retained.rows.slice(0, 5)); + const contextId = payload(context[0]).details.find((d: Row) => d.name === "toolCallId").value; + for (const r of context) { + if (payload(r).provenance) Object.assign(payload(r).provenance, { sessionId: "native-session", turnId: "turn" }); + } + const request = structuredClone(payload(pendingFixture.rows[0]).request); + Object.assign(request, { requestId: "context-request", turnId: "turn", itemId: "context-item" }); + const events = [row(1, context[0].eventType, payload(context[0])), row(2, context[1].eventType, payload(context[1])), + row(3, "runtime_request.created", { request }), notice(4, contextId, "permission_requested", { requestId: "context-request", declineOffered: true }), + row(5, "runtime_request.resolved", { requestId: "context-request", turnId: "turn", requestKind: "permission_approval", action: "accept" }), + notice(6, contextId, "permission_delivered", { requestId: "context-request", outcome: "allow_once" }), + row(7, context[2].eventType, payload(context[2])), row(8, context[3].eventType, payload(context[3])), row(9, context[4].eventType, payload(context[4])), + notice(10, "edit-tool", "tool", { status: "pending", operation: "edit", target: "target.txt" }), + row(11, "tool.execution.started", { schema: "paperclip.tool.execution.v1", executionId: "edit-tool", transport: "builtin", status: "running", operation: "edit", target: "target.txt" }), + row(12, "runtime_request.created", { request: { schema: "paperclip.runtime_request.v2", requestKind: "permission_approval", type: "permission", status: "pending", + requestId: "edit-request", turnId: "turn", itemId: "edit-item", details: { toolCallId: "edit-tool" }, origin: { adapter: "acpx-runtime-sidecar", provider: "copilot", method: "session/request_permission" } } }), + notice(13, "edit-tool", "permission_requested", { requestId: "edit-request", declineOffered: true, operation: "edit", target: "target.txt" })]; + const run: Row = { id: "run", companyId: "company", nativeIssueId: "issue", runtimeMode: "native", status: "running", resultJson: {} }; + const issue: Row = { id: "issue", companyId: "company", status: "in_progress" }; + const origin = () => readCopilotToolEvidence(events, "run").find(n => n.toolCallId === "edit-tool" && n.stage === "tool")!; + const pending = () => observeActiveStopPending({ events, run, issue, scope, caller, cancellationRequestId }); + return { events, run, issue, origin, pending }; +} +describe("successful context read with an explicit permission decision", () => { + it("binds the current Copilot permission origin to the same successful read receipt", () => { + const f = fixture(); + payload(f.events[2]).request.origin.provider = "copilot"; + // Current sidecar order: native request notice, then durable permission card. + for (const [index, seq] of [[2, 4], [3, 3]]) { + f.events[index].seq = seq; f.events[index].sourceSeq = seq; + frame(f.events[index]).sourceSeq = seq; frame(f.events[index]).sourceEventId = `source:run:${seq}`; + } + expect(readCopilotContextRead(f.events, f.origin(), true)).toMatchObject({ permissionRequestId: "context-request", receiptSeq: 7 }); + expect(f.pending()).toMatchObject({ requestId: "edit-request" }); + for (const provider of ["cursor", "pi", "claude", "codex", "unknown"]) { + payload(f.events[2]).request.origin.provider = provider; + expect(() => readCopilotContextRead(f.events, f.origin(), true)).toThrow(); + } + }); + + it("attests the closed context read while retaining the unanswered mutation", () => { + const f = fixture(); + expect(readCopilotContextRead(f.events, f.origin(), true)).toMatchObject({ permissionRequestId: "context-request", pendingSeq: 1, receiptSeq: 7, completedSeq: 8 }); + expect(f.pending()).toMatchObject({ requestId: "edit-request", toolCallId: "edit-tool" }); + }); + it.each([ + ["another operation", (rows: Row[]) => { payload(rows[2]).request.prompt = "report_progress"; }], + ["foreign company", (rows: Row[]) => { rows[4].companyId = "other"; }], + ["foreign turn", (rows: Row[]) => { frame(rows[4]).turnId = "other"; }], + ["foreign source", (rows: Row[]) => { frame(rows[5]).sourceInstanceId = "other"; }], + ["foreign bridge", (rows: Row[]) => { payload(rows[2]).request.origin.adapter = "untrusted"; }], + ["session-wide approval", (rows: Row[]) => { setDetail(rows[5], "outcome", "allow_always"); }], + ["rejected read", (rows: Row[]) => { payload(rows[4]).action = "decline"; }], + ["late resolution", (rows: Row[]) => { rows[4].seq = 14; }], + ["card after resolution", (rows: Row[]) => { rows[2].seq = 14; }], + ["native request after resolution", (rows: Row[]) => { rows[3].seq = 14; }], + ["missing resolution", (rows: Row[]) => { rows.splice(4, 1); }], + ["replayed delivery", (rows: Row[]) => { rows.push(structuredClone(rows[5])); }], + ["mutation target", (rows: Row[]) => { payload(rows[3]).details.push({ name: "target", value: "target.txt" }); }], + ] as const)("refuses %s as a bootstrap exception", (_label, mutate) => { + const f = fixture(); mutate(f.events); + expect(() => readCopilotContextRead(f.events, f.origin(), true)).toThrow(); + expect(f.pending).toThrow(); + }); + it("settles only the pending write after Stop, without treating the earlier context answer as a write answer", () => { + const f = fixture(), pending = f.pending(); + f.events.push(row(14, "runtime_request.cancelled", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval", itemId: "edit-item", reason: "turn_terminal" }), + row(15, "turn.cancelled", { status: "cancelled", error: null })); + f.run.status = "cancelled"; + f.run.resultJson = { startupCancellation: { cancellationRequestId, requestedBy: { type: "board", userId: "local-board" } }, nativeCancellation: { + schema: "paperclip.native-cancellation.v1", intentId: `native-cancellation:${cancellationRequestId}`, companyId: "company", runId: "run", issueId: "issue", scope: "run", + dispatched: true, dispatchState: "acknowledged", reasonCode: "cancellation_run_only", effects: ["release_run_resources"], intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit" } }; + applyMainlineStopMetadata(f.run); + const stopAcknowledgement = stopAcknowledgementForTest(f.run, cancellationRequestId, (BigInt(pending.observedMonotonicNs) + 1n).toString()); + expect(readActiveStopSettlement({ ...f, stopAcknowledgement, pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).toMatchObject({ normalCompletionAccepted: false, replayAllowed: false }); + f.events.push(row(16, "runtime_request.resolved", { requestId: "edit-request", action: "accept" })); + expect(() => readActiveStopSettlement({ ...f, stopAcknowledgement, pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).toThrow(); + }); + it("accepts provider death after the read and rejects a late mutation answer or any other approval", () => { + const f = fixture(), pending = f.pending(); + f.run.status = "failed"; f.issue.status = "blocked"; + f.events.push(row(14, "runtime_request.expired", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval", reason: "provider_exit" }), + row(15, "turn.failed", { error: { code: "AGENT_DISCONNECTED", message: "Provider exited" } })); + expect(assertCopilotProviderDeath({ ...f, pending })).toMatchObject({ expired: true, mutationReplay: false }); + for (const extra of [notice(16, "edit-tool", "permission_delivered", { requestId: "edit-request", outcome: "allow_once" }), + notice(16, "other-tool", "permission_delivered", { requestId: "other-request", outcome: "allow_once" }), + notice(16, "edit-tool", "tool", { operation: "edit", target: "target.txt", status: "completed" })]) { + expect(() => assertCopilotProviderDeath({ ...f, pending, events: [...f.events, extra] })).toThrow(); + } + }); +}); + + +function withDiscovery() { + const f = fixture(); + for (const r of f.events) { r.seq += 9; r.sourceSeq += 9; frame(r).sourceSeq += 9; frame(r).sourceEventId = `source:run:${frame(r).sourceSeq}`; } + const request = structuredClone(payload(discoveryFixture.rows[0]).request); + Object.assign(request, { requestId: "discovery-request", turnId: "turn", itemId: "discovery-item" }); + const inputHash = createHash("sha256").update('{"query":"get_task_context"}').digest("hex"); + const receipt = structuredClone(payload(retained.rows[2])); + Object.assign(receipt.provenance, { sessionId: "native-session", turnId: "turn" }); + const values = { operationId: "search_api", callIdentitySha256: "a".repeat(64), inputSha256: inputHash, resultSha256: "b".repeat(64) }; + for (const [name, value] of Object.entries(values)) receipt.details.find((d: Row) => d.name === name).value = value; + const start = structuredClone(payload(discoveryFixture.rows[2])); + Object.assign(start, { executionId: "discovery-tool" }); + const discovery = [row(1, "runtime_request.created", { request }), notice(2, "discovery-tool", "tool", { status: "pending" }), + row(3, "tool.execution.started", start), notice(4, "discovery-tool", "permission_requested", { requestId: "discovery-request", declineOffered: true }), + row(5, "runtime_request.resolved", { requestId: "discovery-request", turnId: "turn", requestKind: "permission_approval", action: "accept" }), + notice(6, "discovery-tool", "permission_delivered", { requestId: "discovery-request", outcome: "allow_once" }), + row(7, "provider.notice.recorded", receipt), notice(8, "discovery-tool", "tool", { status: "completed", semanticOperationId: "search_api", semanticCallIdentitySha256: values.callIdentitySha256, + semanticInputSha256: inputHash, semanticNormalizedInputSha256: "null", semanticResultSha256: values.resultSha256, semanticOutcome: "returned" }), + row(9, "tool.execution.completed", { ...start, status: "completed" })]; + f.events.unshift(...discovery); + return f; +} +describe("optional exact context discovery", () => { + it("requires a returned control-plane receipt before context and leaves the write unanswered", () => { + const f = withDiscovery(); + expect(readCopilotContextRead(f.events, f.origin(), true)).toMatchObject({ discoveries: [{ toolCallId: "discovery-tool", permissionRequestId: "discovery-request", receiptSeq: 7 }] }); + expect(f.pending()).toMatchObject({ requestId: "edit-request" }); + }); + it.each([ + ["another query", (rows: Row[]) => { payload(rows[2]).progress = "paperclip-search_api (pending): create_task"; }], + ["mutation projection", (rows: Row[]) => { payload(rows[2]).readOnly = false; }], + ["missing receipt", (rows: Row[]) => { rows.splice(6, 1); }], + ["wrong receipt operation", (rows: Row[]) => { setDetail(rows[6], "operationId", "write_document"); }], + ["rejected discovery", (rows: Row[]) => { setDetail(rows[6], "outcome", "error"); }], + ["native mismatch", (rows: Row[]) => { setDetail(rows[7], "semanticInputSha256", "c".repeat(64)); }], + ["session-wide approval", (rows: Row[]) => { setDetail(rows[5], "outcome", "allow_always"); }], + ["foreign bridge", (rows: Row[]) => { payload(rows[0]).request.origin.adapter = "untrusted"; }], + ["late discovery", (rows: Row[]) => { rows[8].seq = 11; }], + ["duplicate discovery", (rows: Row[]) => { rows.push(structuredClone(rows[6])); }], + ["borrowed context identity", (rows: Row[]) => { const contextReceipt = payload(rows[15]).details.find((d: Row) => d.name === "callIdentitySha256").value; + setDetail(rows[6], "callIdentitySha256", contextReceipt); setDetail(rows[7], "semanticCallIdentitySha256", contextReceipt); }], + ] as const)("rejects %s", (_name, mutate) => { + const f = withDiscovery(); mutate(f.events); + expect(() => readCopilotContextRead(f.events, f.origin(), true)).toThrow(); + expect(f.pending).toThrow(); + }); + it("still refuses a stale mutation answer after provider death", () => { + const f = withDiscovery(), pending = f.pending(); f.run.status = "failed"; f.issue.status = "blocked"; + f.events.push(row(23, "runtime_request.expired", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval" }), row(24, "turn.failed", { error: { code: "AGENT_DISCONNECTED" } })); + expect(assertCopilotProviderDeath({ ...f, pending })).toMatchObject({ mutationReplay: false }); + f.events.push(notice(25, "edit-tool", "permission_delivered", { requestId: "edit-request", outcome: "allow_once" })); + expect(() => assertCopilotProviderDeath({ ...f, pending })).toThrow(); + }); + it("settles Stop without mistaking completed discovery for an answered write", () => { + const f = withDiscovery(), pending = f.pending(); f.run.status = "cancelled"; + f.events.push(row(23, "runtime_request.cancelled", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval", itemId: "edit-item", reason: "turn_terminal" }), row(24, "turn.cancelled", { status: "cancelled", error: null })); + f.run.resultJson = { startupCancellation: { cancellationRequestId, requestedBy: { type: "board", userId: "local-board" } }, nativeCancellation: { + schema: "paperclip.native-cancellation.v1", intentId: `native-cancellation:${cancellationRequestId}`, companyId: "company", runId: "run", issueId: "issue", scope: "run", + dispatched: true, dispatchState: "acknowledged", reasonCode: "cancellation_run_only", effects: ["release_run_resources"], intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit" } }; + applyMainlineStopMetadata(f.run); + const stopAcknowledgement = stopAcknowledgementForTest(f.run, cancellationRequestId, (BigInt(pending.observedMonotonicNs) + 1n).toString()); + expect(readActiveStopSettlement({ ...f, stopAcknowledgement, pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).toMatchObject({ normalCompletionAccepted: false }); + f.events.push(row(25, "runtime_request.resolved", { requestId: "edit-request", action: "accept" })); + expect(() => readActiveStopSettlement({ ...f, stopAcknowledgement, pending, dispatchMonotonicNs: (BigInt(pending.observedMonotonicNs) + 1n).toString() })).toThrow(); + }); + +}); + +function withTwoDiscoveries() { + const f = withDiscovery(); + const second = JSON.parse(JSON.stringify(f.events.slice(0, 9)).replaceAll("discovery-", "second-discovery-")) as Row[]; + const advance = (r: Row, delta: number) => { r.seq += delta; r.sourceSeq += delta; frame(r).sourceSeq += delta; frame(r).sourceEventId = `source:run:${frame(r).sourceSeq}`; }; + for (const r of f.events.slice(9)) advance(r, 9); + for (const r of second) { + advance(r, 9); + const p = payload(r); + if (p.name === "paperclip-search_api") p.progress = "paperclip-search_api (pending): dedicated get_task_context tool"; + for (const d of p.details ?? []) { + if (["callIdentitySha256", "semanticCallIdentitySha256"].includes(d.name)) d.value = "c".repeat(64); + if (["inputSha256", "semanticInputSha256"].includes(d.name)) d.value = "d".repeat(64); + if (["resultSha256", "semanticResultSha256"].includes(d.name)) d.value = "e".repeat(64); + } + } + f.events.splice(9, 0, ...second); + return f; +} +describe("multiple independently attested discovery decisions", () => { + it("leaves only the mutation permission pending", () => { + const f = withTwoDiscoveries(); + expect(readCopilotContextRead(f.events, f.origin(), true)?.discoveries).toHaveLength(2); + expect(f.pending()).toMatchObject({ requestId: "edit-request" }); + }); + it.each(["stop", "provider-death"])("retains unanswered-write and no-replay guards after %s", kind => { + const f = withTwoDiscoveries(), pending = f.pending(); + const seq = Math.max(...f.events.map(r => r.seq)) + 1; + if (kind === "provider-death") { + f.run.status = "failed"; f.issue.status = "blocked"; + f.events.push(row(seq, "runtime_request.expired", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval" }), row(seq + 1, "turn.failed", { error: { code: "AGENT_DISCONNECTED" } })); + expect(assertCopilotProviderDeath({ ...f, pending })).toMatchObject({ mutationReplay: false }); + } else { + f.run.status = "cancelled"; + applyMainlineStopMetadata(f.run); + const dispatchMonotonicNs = (BigInt(pending.observedMonotonicNs) + 1n).toString(); + const stopAcknowledgement = stopAcknowledgementForTest(f.run, cancellationRequestId, dispatchMonotonicNs); + f.events.push(row(seq, "runtime_request.cancelled", { requestId: "edit-request", turnId: "turn", requestKind: "permission_approval", itemId: "edit-item", reason: "turn_terminal" }), row(seq + 1, "turn.cancelled", { status: "cancelled", error: null })); + const read = () => readActiveStopSettlement({ ...f, pending, dispatchMonotonicNs, stopAcknowledgement }); + expect(read()).toMatchObject({ normalCompletionAccepted: false }); + f.events.push(row(seq + 2, "runtime_request.resolved", { requestId: "edit-request", action: "accept" })); + expect(read).toThrow(); + return; + } + f.events.push(notice(seq + 2, "edit-tool", "permission_delivered", { requestId: "edit-request", outcome: "allow_once" })); + expect(() => assertCopilotProviderDeath({ ...f, pending })).toThrow(); + }); +}); diff --git a/tests/runner-e2e/copilot-context-evidence.ts b/tests/runner-e2e/copilot-context-evidence.ts new file mode 100644 index 00000000000..52e2144187e --- /dev/null +++ b/tests/runner-e2e/copilot-context-evidence.ts @@ -0,0 +1,146 @@ +import { createHash } from "node:crypto"; +import { readCopilotToolEvidence, type CopilotToolNotice } from "./copilot-evidence.js"; +import { hasAcpxNativeOrigin } from "./acpx-native-origin.js"; + +type Row = Record; +const rec = (v: unknown): Row => v !== null && typeof v === "object" && !Array.isArray(v) ? v as Row : {}; +const receiptKeys = ["callIdentitySha256", "inputSha256", "normalizedInputSha256", "operationId", "outcome", "resultSha256", "schema", "stage"].sort().join(","); +export interface CopilotBootstrapReadProof { + toolCallId: string; pendingSeq: number; receiptSeq: number; completedSeq: number; + canonicalSeqs: number[]; callIdentitySha256: string; + permissionRequestId?: string; +} + +export interface CopilotContextReadProof extends CopilotBootstrapReadProof { discoveries: CopilotBootstrapReadProof[] } + +export const COPILOT_CONTEXT_DISCOVERY_LIMIT = 4; +/** Metadata discovery only. Keep a small lexical scope and require the context + * identifier; arbitrary API searches, pagination loops and mutations are not + * bootstrap actions for these authored permission/settlement cases. */ +export function isCopilotContextDiscoveryProgress(value: unknown): boolean { + const prefix = "paperclip-search_api (pending): "; + if (typeof value !== "string" || !value.startsWith(prefix) || value.length > 120) return false; + // Retained v17 native discovery used this exact context-only natural spelling. + if (value === `${prefix}get task context`) return true; + const terms = value.slice(prefix.length).split(/\s+/u); + return terms.length > 0 && terms.length <= 4 && terms.includes("get_task_context") + && new Set(terms).size === terms.length && terms.every(t => ["get_task_context", "paperclip_finish", "dedicated", "tool"].includes(t)); +} + +/** Attest context and any exact read-only tool discovery before the native action. */ +export function readCopilotContextRead(rows: readonly unknown[], origin: CopilotToolNotice, required = false): CopilotContextReadProof | undefined { + const context = readSemanticBootstrap(rows, origin, required, "get_task_context"); + if (!context) return undefined; + const pending = readCopilotToolEvidence(rows, origin.runId).find(n => n.seq === context.pendingSeq)!; + const searchReceipts = rows.map(rec).filter(r => { + const p = rec(rec(rec(r.payload).prpEvent).payload); + return p.category === "paperclip_semantic_tool_receipt_v2" && Array.isArray(p.details) + && p.details.some((d: Row) => d.name === "operationId" && d.value === "search_api"); + }); + const nativeSearches = readCopilotToolEvidence(rows, origin.runId).filter(n => n.semanticOperationId === "search_api"); + if (searchReceipts.length > COPILOT_CONTEXT_DISCOVERY_LIMIT || searchReceipts.length !== nativeSearches.length) throw new Error("Copilot context discovery is unbounded or incomplete"); + const discoveries = searchReceipts.map(r => { + const fields = rec(rec(rec(r.payload).prpEvent).payload).details as Row[]; + const identity = fields.find(d => d.name === "callIdentitySha256")?.value; + if (typeof identity !== "string") throw new Error("Copilot discovery lacks its invocation identity"); + return readSemanticBootstrap(rows, pending, true, "search_api", identity)!; + }).sort((a, b) => a.pendingSeq - b.pendingSeq); + const identities = [context.callIdentitySha256, ...discoveries.map(d => d.callIdentitySha256)]; + if (new Set(identities).size !== identities.length) throw new Error("Copilot discovery cannot borrow another call identity"); + return { ...context, discoveries }; +} +function readSemanticBootstrap(rows: readonly unknown[], origin: CopilotToolNotice, required: boolean, operationId: "get_task_context" | "search_api", selectedIdentity?: string): CopilotBootstrapReadProof | undefined { + const discovery = operationId === "search_api"; + const fail = (): never => { throw new Error("Copilot context read lacks exact successful pre-action proof"); }; + const all = rows.map(rec), notices = readCopilotToolEvidence(rows, origin.runId); + const matches = all.filter(r => { + const p = rec(rec(rec(r.payload).prpEvent).payload); + return p.category === "paperclip_semantic_tool_receipt_v2" && Array.isArray(p.details) + && p.details.some((d: any) => d?.name === "operationId" && d.value === operationId) + && (!selectedIdentity || p.details.some((d: any) => d?.name === "callIdentitySha256" && d.value === selectedIdentity)); + }); + const native = notices.filter(n => n.semanticOperationId === operationId && (!selectedIdentity || n.semanticCallIdentitySha256 === selectedIdentity)); + if (!matches.length && !native.length && !required) return undefined; + if (matches.length !== 1 || native.length !== 1) fail(); + const roots = all.filter(r => r.seq === origin.seq); + if (roots.length !== 1) fail(); + const root = roots[0]!, base = rec(rec(root.payload).prpEvent); + const frame = (r: Row) => { + const e = rec(rec(r.payload).prpEvent); + if (r.companyId !== root.companyId || r.runId !== origin.runId || !Number.isSafeInteger(r.seq) || r.seq < 1 + || e.schema !== "paperclip.prp.event.v1" || e.sourceKind !== "runner" || e.eventType !== r.eventType + || e.runId !== origin.runId || e.turnId !== origin.turnId || e.normalizedSessionId !== base.normalizedSessionId + || typeof base.normalizedSessionId !== "string" || !base.normalizedSessionId || e.sourceInstanceId !== base.sourceInstanceId + || typeof base.sourceInstanceId !== "string" || !base.sourceInstanceId || r.sourceInstanceId !== e.sourceInstanceId + || r.sourceSeq !== e.sourceSeq || !Number.isSafeInteger(e.sourceSeq) || e.sourceSeq < 1) fail(); + return e; + }; + frame(root); + const authority = matches[0]!, receipt = rec(frame(authority).payload), provenance = rec(receipt.provenance); + if (authority.eventType !== "provider.notice.recorded" || receipt.schema !== "paperclip.provider.notice.v1" || receipt.scope !== "turn" + || provenance.method !== "paperclip/semantic_tool_result" || provenance.eventType !== "semantic_result" + || provenance.sessionId !== origin.sessionId || provenance.turnId !== origin.turnId || receipt.details.length !== 8) fail(); + const fields: Record = {}; + for (const d of receipt.details) { + if (Object.keys(rec(d)).sort().join(",") !== "name,value" || typeof d.name !== "string" || typeof d.value !== "string" || d.value.length > 256 || Object.hasOwn(fields, d.name)) fail(); + fields[d.name] = d.value; + } + if (Object.keys(fields).sort().join(",") !== receiptKeys || fields.stage !== "semantic_result" || fields.schema !== "paperclip.semantic_tool_receipt.v2" + || fields.operationId !== operationId || fields.outcome !== "returned" || fields.normalizedInputSha256 !== "null" + || (discovery ? !/^[a-f0-9]{64}$/.test(fields.inputSha256!) : fields.inputSha256 !== createHash("sha256").update("{}").digest("hex")) + || ![fields.callIdentitySha256, fields.resultSha256].every(v => /^[a-f0-9]{64}$/.test(v!))) fail(); + const completed = native[0]!, contextNotices = notices.filter(n => n.toolCallId === completed.toolCallId); + const group = contextNotices.filter(n => n.stage === "tool").sort((a, b) => a.seq - b.seq); + const permissions = contextNotices.filter(n => n.stage !== "tool"); + let permissionRequestId: string | undefined; + if (permissions.length) { + const requested = permissions.filter(n => n.stage === "permission_requested"), delivered = permissions.filter(n => n.stage === "permission_delivered"); + if (permissions.length !== 2 || requested.length !== 1 || delivered.length !== 1 || !requested[0]!.requestId + || delivered[0]!.requestId !== requested[0]!.requestId || delivered[0]!.outcome !== "allow_once" + || requested[0]!.seq >= delivered[0]!.seq || delivered[0]!.seq >= authority.seq) fail(); + permissionRequestId = requested[0]!.requestId; + for (const n of permissions) { + const rs = all.filter(r => r.seq === n.seq); + if (rs.length !== 1 || n.runId !== origin.runId || n.sessionId !== origin.sessionId || n.turnId !== origin.turnId + || n.operation !== undefined || n.target !== undefined || n.commandSha256 !== undefined || n.shellId !== undefined) fail(); + frame(rs[0]!); + } + const requests = all.filter(r => r.eventType === "runtime_request.created" && rec(rec(rec(r.payload).prpEvent).payload).request?.requestId === permissionRequestId); + const closures = all.filter(r => ["runtime_request.resolved", "runtime_request.cancelled", "runtime_request.expired"].includes(r.eventType) + && rec(rec(rec(r.payload).prpEvent).payload).requestId === permissionRequestId); + if (requests.length !== 1 || closures.length !== 1) fail(); + // The current sidecar emits its native notice before the durable card. + // Both origins must precede the exact closure; neither can borrow a later decision. + const request = rec(rec(frame(requests[0]!).payload).request), resolution = rec(frame(closures[0]!).payload); + if (request.schema !== "paperclip.runtime_request.v2" || request.type !== "permission" || request.requestKind !== "permission_approval" + || request.prompt !== operationId || request.turnId !== origin.turnId || request.status !== "pending" + || !(hasAcpxNativeOrigin(request.origin, "copilot", "session/request_permission") || hasAcpxNativeOrigin(request.origin, "acpx", "session/request_permission")) || resolution.action !== "accept" + || resolution.requestKind !== "permission_approval" || resolution.turnId !== origin.turnId || closures[0]!.eventType !== "runtime_request.resolved" + || requests[0]!.seq >= closures[0]!.seq || requested[0]!.seq >= closures[0]!.seq || closures[0]!.seq >= delivered[0]!.seq) fail(); + } + if (group.length < 2 || group.length > 16 || completed.status !== "completed" || group.at(-1) !== completed + || completed.semanticOutcome !== "returned" || completed.semanticCallIdentitySha256 !== fields.callIdentitySha256 + || completed.semanticInputSha256 !== fields.inputSha256 || completed.semanticResultSha256 !== fields.resultSha256 || completed.semanticNormalizedInputSha256 !== null + || group.some((n, i) => n.stage !== "tool" || n.runId !== origin.runId || n.sessionId !== origin.sessionId || n.turnId !== origin.turnId + || n.operation !== (discovery ? undefined : "read") || n.target !== undefined || n.commandSha256 !== undefined || n.readTargetSha256 !== undefined || n.shellId !== undefined + || n.commandToolCallId !== undefined || n.seq >= origin.seq || (i === 0 ? n.status !== "pending" : i === group.length - 1 ? n.status !== "completed" : n.status !== "in_progress"))) fail(); + for (const n of group) { + const rs = all.filter(r => r.seq === n.seq); if (rs.length !== 1) fail(); frame(rs[0]!); + } + const canonical = all.filter(r => r.eventType?.startsWith("tool.execution.") && rec(rec(rec(r.payload).prpEvent).payload).executionId === completed.toolCallId).sort((a, b) => a.seq - b.seq); + if (canonical.length < 2 || canonical.length > 16 || new Set(canonical.map(r => r.seq)).size !== canonical.length) fail(); + for (let i = 0; i < canonical.length; i++) { + const r = canonical[i]!, p = rec(frame(r).payload), last = i === canonical.length - 1; + if (r.seq >= origin.seq || p.schema !== "paperclip.tool.execution.v1" || p.transport !== "builtin" || p.target !== null + || p.name !== `paperclip-${operationId}` || p.operation !== (discovery ? "search" : "read") || p.readOnly !== true + || (last ? r.eventType !== "tool.execution.completed" || p.status !== "completed" + : p.status !== "running" || r.eventType !== (i === 0 ? "tool.execution.started" : "tool.execution.progressed"))) fail(); + } + if (discovery && !isCopilotContextDiscoveryProgress(rec(frame(canonical[0]!).payload).progress)) fail(); + const pending = group[0]!; + if (!(pending.seq < canonical[0]!.seq && canonical[0]!.seq < authority.seq && authority.seq < completed.seq && completed.seq < canonical.at(-1)!.seq + && canonical.at(-1)!.seq < origin.seq) || !(frame(all.find(r => r.seq === pending.seq)!).sourceSeq < frame(authority).sourceSeq + && frame(authority).sourceSeq < frame(all.find(r => r.seq === completed.seq)!).sourceSeq && frame(canonical.at(-1)!).sourceSeq < base.sourceSeq)) fail(); + return { toolCallId: completed.toolCallId, pendingSeq: pending.seq, receiptSeq: authority.seq, completedSeq: completed.seq, + canonicalSeqs: canonical.map(r => r.seq), callIdentitySha256: fields.callIdentitySha256!, ...(permissionRequestId ? { permissionRequestId } : {}) }; +} diff --git a/tests/runner-e2e/copilot-context-permission.test.ts b/tests/runner-e2e/copilot-context-permission.test.ts new file mode 100644 index 00000000000..fcbbbb94806 --- /dev/null +++ b/tests/runner-e2e/copilot-context-permission.test.ts @@ -0,0 +1,119 @@ +import { readFile } from "node:fs/promises"; +import { describe, expect, it, vi } from "vitest"; +import { pendingCopilotContextPermission, prepareCopilotContext } from "./copilot-context-permission.js"; + +describe("Copilot initial context permission fixture", () => { + it("recognizes the current native Copilot origin from the failed v26 denial attempt", async () => { + const retained = JSON.parse(await readFile(new URL("./fixtures/copilot-context-permission-v26.json", import.meta.url), "utf8")); + const { rows } = retained, company = rows[0].companyId, run = rows[0].runId; + expect(retained.provenance.originalGrade).toContain("failed"); + expect(pendingCopilotContextPermission(rows, company, run)).toMatchObject({ operationId: "get_task_context", runId: run }); + for (const provider of ["cursor", "pi", "claude", "codex", "unknown"]) { + const bad = structuredClone(rows); + bad.find((row: any) => row.eventType === "runtime_request.created").payload.prpEvent.payload.request.origin.provider = provider; + expect(() => pendingCopilotContextPermission(bad, company, run)).toThrow(); + } + }); + + it("recognizes the retained v17 natural context lookup without claiming provider-death qualification", async () => { + const retained = JSON.parse(await readFile(new URL("./fixtures/copilot-context-discovery-permission-v17.json", import.meta.url), "utf8")); + const { rows } = retained, company = rows[0].companyId, run = rows[0].runId; + expect(retained.outcome).toContain("failed"); + expect(pendingCopilotContextPermission(rows, company, run)).toMatchObject({ operationId: "search_api", runId: run }); + for (const scope of ["get task context create_task", "get task context call_api", "get task context; paperclip_finish"]) { + const bad = structuredClone(rows); + bad.find((row: any) => row.eventType === "tool.execution.started").payload.prpEvent.payload.progress = `paperclip-search_api (pending): ${scope}`; + expect(() => pendingCopilotContextPermission(bad, company, run)).toThrow(); + } + }); + + it("recognizes the exact retained unanswered context card without approving another operation", async () => { + const { rows } = JSON.parse(await readFile(new URL("./fixtures/copilot-context-permission-v15.json", import.meta.url), "utf8")); + const company = rows[0].companyId, run = rows[0].runId; + expect(pendingCopilotContextPermission(rows, company, run)).toMatchObject({ runId: run, requestId: expect.any(String), toolCallId: expect.any(String) }); + for (const kind of ["mutation", "other-prompt", "not-read-only", "foreign-company", "foreign-source", "foreign-bridge", "missing-decline", "duplicate", "unknown-target"]) { + const bad = structuredClone(rows); + const payload = (r: any) => r.payload.prpEvent.payload; + if (kind === "mutation") payload(bad[2]).operation = "edit"; + if (kind === "other-prompt") payload(bad[0]).request.prompt = "report_progress"; + if (kind === "not-read-only") payload(bad[2]).readOnly = false; + if (kind === "foreign-company") bad[2].companyId = "foreign"; + if (kind === "foreign-source") bad[2].payload.prpEvent.sourceInstanceId = "foreign"; + if (kind === "foreign-bridge") payload(bad[0]).request.origin.adapter = "untrusted"; + if (kind === "missing-decline") payload(bad[0]).request.choices = [{ key: "accept" }]; + if (kind === "duplicate") bad.push(structuredClone(bad[0])); + if (kind === "unknown-target") payload(bad[1]).details.push({ name: "target", value: "other.txt" }); + expect(() => pendingCopilotContextPermission(bad, company, run), kind).toThrow(); + } + }); + it("waits for permission-first tool rows without approving incomplete evidence", async () => { + const { rows } = JSON.parse(await readFile(new URL("./fixtures/copilot-context-permission-v15.json", import.meta.url), "utf8")); + const company = rows[0].companyId, run = rows[0].runId; + for (const missing of [[1], [2], [1, 2]]) { + const partial = rows.filter((_: unknown, index: number) => !missing.includes(index)); + expect(pendingCopilotContextPermission(partial, company, run)).toBeUndefined(); + } + const foreign = structuredClone(rows).filter((_: unknown, index: number) => index !== 2); + foreign[0].companyId = "foreign"; + expect(() => pendingCopilotContextPermission(foreign, company, run)).toThrow("foreign durable evidence"); + const mutation = structuredClone(rows).filter((_: unknown, index: number) => index !== 1); + mutation.find((row: any) => row.eventType === "tool.execution.started").payload.prpEvent.payload.readOnly = false; + expect(() => pendingCopilotContextPermission(mutation, company, run)).toThrow("cannot approve this card"); + expect(pendingCopilotContextPermission(rows, company, run)).toMatchObject({ operationId: "get_task_context" }); + }); + it("waits for the native notice when the durable request arrives first", async () => { + const { rows } = JSON.parse(await readFile(new URL("./fixtures/copilot-context-permission-v15.json", import.meta.url), "utf8")); + expect(pendingCopilotContextPermission(rows.slice(0, 3), rows[0].companyId, rows[0].runId)).toBeUndefined(); + expect(pendingCopilotContextPermission([], rows[0].companyId, rows[0].runId)).toBeUndefined(); + }); + it("recognizes retained discovery only for the exact read-only context lookup", async () => { + const { rows } = JSON.parse(await readFile(new URL("./fixtures/copilot-context-discovery-permission-v15.json", import.meta.url), "utf8")); + const company = rows[0].companyId, run = rows[0].runId; + expect(pendingCopilotContextPermission(rows, company, run)).toMatchObject({ operationId: "search_api", runId: run }); + for (const kind of ["other-query", "mutation", "not-read-only", "foreign-source", "unknown-target", "duplicate-permission"]) { + const bad = structuredClone(rows), p = (r: any) => r.payload.prpEvent.payload; + if (kind === "other-query") p(bad[2]).progress = "paperclip-search_api (pending): create_task"; + if (kind === "mutation") p(bad[2]).operation = "edit"; + if (kind === "not-read-only") p(bad[2]).readOnly = false; + if (kind === "foreign-source") bad[1].sourceSeq += 1; + if (kind === "unknown-target") p(bad[1]).details.push({ name: "target", value: "other.txt" }); + if (kind === "duplicate-permission") bad.push(structuredClone(bad[3])); + expect(() => pendingCopilotContextPermission(bad, company, run), kind).toThrow(); + } + }); + +}); + +describe("remote action publication before Copilot context approval", () => { + it("keeps the context card unanswered until the observer and action file are ready", async () => { + let release!: () => void; + const ready = new Promise(resolve => { release = resolve; }); + let observerArmed = false, actionPublished = false; + const approveContext = vi.fn(async () => { + expect(observerArmed).toBe(true); + expect(actionPublished).toBe(true); + }); + const pending = prepareCopilotContext({ remoteSetup: async () => { + observerArmed = true; + await ready; + actionPublished = true; + }, approveContext }); + await Promise.resolve(); + expect(approveContext).not.toHaveBeenCalled(); + release(); + await pending; + expect(approveContext).toHaveBeenCalledTimes(1); + }); + + it("does not release the provider when remote setup fails", async () => { + const approveContext = vi.fn(); + await expect(prepareCopilotContext({ remoteSetup: async () => { throw new Error("observer not armed"); }, approveContext })).rejects.toThrow("observer not armed"); + expect(approveContext).not.toHaveBeenCalled(); + }); + + it("approves local context without waiting for a remote fixture", async () => { + const approveContext = vi.fn(async () => {}); + await prepareCopilotContext({ approveContext }); + expect(approveContext).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/runner-e2e/copilot-context-permission.ts b/tests/runner-e2e/copilot-context-permission.ts new file mode 100644 index 00000000000..8b1664b10e9 --- /dev/null +++ b/tests/runner-e2e/copilot-context-permission.ts @@ -0,0 +1,96 @@ +import { COPILOT_CONTEXT_DISCOVERY_LIMIT, isCopilotContextDiscoveryProgress } from "./copilot-context-evidence.js"; +import { copilotPendingPermissionCard } from "./copilot-permission-card.js"; +import { expect, type Page } from "@playwright/test"; +import { pollUntil } from "./api.js"; +import { readCopilotToolEvidence } from "./copilot-evidence.js"; +import { hasAcpxNativeOrigin } from "./acpx-native-origin.js"; + +type Row = Record; +const record = (value: unknown): Row => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Row : {}; + +/** Fixture human decision, never an automatic production permission rule. */ +export function pendingCopilotContextPermission(events: readonly unknown[], companyId: string, runId: string) { + const rows = events.map(record), notices = readCopilotToolEvidence(events, runId); + const closed = new Set(rows.filter(r => ["runtime_request.resolved", "runtime_request.cancelled", "runtime_request.expired"].includes(r.eventType)) + .map(r => record(record(record(r.payload).prpEvent).payload).requestId)); + const pending = rows.filter(r => r.eventType === "runtime_request.created" && !closed.has(record(record(record(r.payload).prpEvent).payload).request?.requestId)); + if (!pending.length) return undefined; + if (pending.length !== 1) throw new Error("Context fixture requires one exact pending request"); + const row = pending[0]!, frame = record(record(row.payload).prpEvent), request = record(record(frame.payload).request); + const operationId = request.prompt; + if (operationId !== "get_task_context" && operationId !== "search_api") throw new Error("Context fixture cannot approve another operation"); + const discovery = operationId === "search_api"; + const permissions = notices.filter(n => n.stage === "permission_requested" && n.requestId === request.requestId); + if (!permissions.length) return undefined; // Native notice can follow the durable card. + if (permissions.length !== 1 || !permissions[0]!.declineOffered) throw new Error("Context fixture permission is ambiguous"); + const native = permissions[0]!, group = notices.filter(n => n.toolCallId === native.toolCallId); + const tools = group.filter(n => n.stage === "tool"); + const started = group.filter(n => n.stage === "tool" && n.status === "pending" && n.operation === (discovery ? undefined : "read")); + const canonical = rows.filter(r => r.eventType === "tool.execution.started" && record(record(record(r.payload).prpEvent).payload).executionId === native.toolCallId); + if (tools.length > 1 || started.length !== tools.length || canonical.length > 1 || group.length !== permissions.length + tools.length + || group.some(n => n.target !== undefined || n.commandSha256 !== undefined || n.shellId !== undefined || n.readTargetSha256 !== undefined)) throw new Error("Context fixture lacks an exact read-only origin"); + for (const r of [row, ...canonical, ...group.map(n => rows.find(r => r.seq === n.seq)!)]) { + const e = record(record(r?.payload).prpEvent); + if (r?.companyId !== companyId || r.runId !== runId || e.schema !== "paperclip.prp.event.v1" || e.sourceKind !== "runner" + || e.eventType !== r.eventType || e.runId !== runId || e.turnId !== native.turnId || e.normalizedSessionId !== frame.normalizedSessionId + || e.sourceInstanceId !== frame.sourceInstanceId || r.sourceInstanceId !== e.sourceInstanceId || r.sourceSeq !== e.sourceSeq + || !Number.isSafeInteger(e.sourceSeq) || !Number.isSafeInteger(r.seq)) throw new Error("Context fixture has foreign durable evidence"); + } + if (request.schema !== "paperclip.runtime_request.v2" || request.type !== "permission" || request.status !== "pending" + || request.requestKind !== "permission_approval" || request.turnId !== native.turnId || request.method !== "session/request_permission" + || !(hasAcpxNativeOrigin(request.origin, "copilot", "session/request_permission") || hasAcpxNativeOrigin(request.origin, "acpx", "session/request_permission")) + || !Array.isArray(request.choices) || !["accept", "decline"].every(key => request.choices.some((c: Row) => c.key === key))) throw new Error("Context fixture cannot approve this card"); + const tool = record(record(record(canonical[0]?.payload).prpEvent).payload); + if (canonical.length && (tool.name !== `paperclip-${operationId}` || tool.operation !== (discovery ? "search" : "read") || tool.readOnly !== true || tool.target !== null || tool.transport !== "builtin")) throw new Error("Context fixture cannot approve this card"); + if (!started.length || !canonical.length) return undefined; // Await matching origin rows within the existing deadline. + if (discovery && !isCopilotContextDiscoveryProgress(tool.progress)) throw new Error("Context discovery must search only for the required context tool"); + return { operationId, runId, requestId: native.requestId!, turnId: native.turnId, toolCallId: native.toolCallId }; +} + +export async function approveCopilotContextThroughUi(input: { + page: Page; companyId: string; deadlineAt: number; + load(): Promise<{ run: Row; events: readonly unknown[] }>; + evidence(name: string, data: unknown): Promise; +}) { + const label = "initial Copilot context permission", decided = new Set(); + for (let index = 0; index <= COPILOT_CONTEXT_DISCOVERY_LIMIT; index++) { + const result = await pollUntil({ label, deadlineAt: input.deadlineAt, intervalMs: 200, + load: async () => { + const state = await input.load(); + if (["failed", "timed_out", "cancelled", "succeeded"].includes(state.run.status)) throw new Error(`Stopped waiting for ${label}: run ended`); + if (!state.run.id) return undefined; + const notices = readCopilotToolEvidence(state.events, state.run.id); + if (notices.some(n => n.semanticOperationId === "get_task_context" && n.status === "completed" && n.semanticOutcome === "returned")) return { completed: true as const }; + try { + const pending = pendingCopilotContextPermission(state.events, input.companyId, state.run.id); + if (pending && decided.has(pending.requestId)) return undefined; // Await durable closure after the browser POST. + return pending ? { pending } : undefined; + } catch (error) { throw new Error(`Stopped waiting for ${label}: ${error instanceof Error ? error.message : "invalid request"}`); } + }, accept: Boolean }); + if (!result || "completed" in result) return; + const { pending } = result; + if (decided.has(pending.requestId)) throw new Error("Context fixture cannot replay a bootstrap decision"); + decided.add(pending.requestId); + const card = copilotPendingPermissionCard(input.page, pending.operationId); + await expect(card).toHaveCount(1); await expect(card).toContainText(pending.operationId); + const path = `/api/heartbeat-runs/${pending.runId}/runtime-requests/${encodeURIComponent(pending.requestId)}/resolve`; + const sent = input.page.waitForRequest(r => new URL(r.url()).pathname === path && r.method() === "POST"); + await card.getByRole("button", { name: "Allow once", exact: true }).click(); + const body = (await sent).postDataJSON(); + if (body.turnId !== pending.turnId || body.requestKind !== "permission_approval" || body.resolution?.action !== "accept") throw new Error("Context fixture submitted a foreign decision"); + await input.evidence(`copilot-initial-${index}-${pending.operationId}-decision.json`, { schema: "paperclip.e2e.copilot-context-decision.v1", ...pending, action: "accept", source: "normal browser permission card; fixture human decision" }); + if (pending.operationId === "get_task_context") return; + } + throw new Error("Context fixture exhausted its bounded bootstrap decisions"); +} + +/** Publish the observed remote action while the provider can still be waiting + * on its context card. Waiting for that card first leaves the setup file absent + * throughout the provider's bounded read window. Never approve another tool. */ +export async function prepareCopilotContext(input: { + remoteSetup?: () => Promise; + approveContext(): Promise; +}): Promise { + await input.remoteSetup?.(); + await input.approveContext(); +} diff --git a/tests/runner-e2e/copilot-discovery-sequence.test.ts b/tests/runner-e2e/copilot-discovery-sequence.test.ts new file mode 100644 index 00000000000..bf767567fb3 --- /dev/null +++ b/tests/runner-e2e/copilot-discovery-sequence.test.ts @@ -0,0 +1,57 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { COPILOT_CONTEXT_DISCOVERY_LIMIT, isCopilotContextDiscoveryProgress, readCopilotContextRead } from "./copilot-context-evidence.js"; +import { readCopilotToolEvidence } from "./copilot-evidence.js"; +import { onlyCopilotAttachedOperations, readCopilotSemanticCompletion } from "./copilot-semantic-evidence.js"; + +const retained = JSON.parse(readFileSync(new URL("./fixtures/copilot-attached-discovery-sequence-v15.json", import.meta.url), "utf8")); +function fixture() { + const rows = structuredClone(retained.rows); + const proposed = rows.find((r: any) => r.eventType === "run.result.proposed"); + const frame = proposed.payload.prpEvent; + const notices = readCopilotToolEvidence(rows, proposed.runId); + const command = notices.find(n => n.stage === "tool" && n.status === "pending" && n.mode === "async")!; + const expected = { companyId: proposed.companyId, runId: proposed.runId, turnId: frame.turnId, + nativeSessionId: command.sessionId, command, summary: frame.payload.summary, requireContextRead: true }; + return { rows, notices, command, expected }; +} +const payload = (r: any) => r.payload.prpEvent.payload; +const detail = (r: any, name: string, value: string) => { payload(r).details.find((d: any) => d.name === name).value = value; }; +describe("bounded individually attested context discovery sequence", () => { + it("calibrates the retained two-discovery completion without treating the failed live attempt as qualified", () => { + const f = fixture(), context = readCopilotContextRead(f.rows, f.command, true)!; + expect(context.discoveries).toHaveLength(2); + expect(context.discoveries[0]!.canonicalSeqs.at(-1)!).toBeLessThan(context.discoveries[1]!.pendingSeq); + expect(context.discoveries[1]!.canonicalSeqs.at(-1)!).toBeLessThan(context.pendingSeq); + const proof = readCopilotSemanticCompletion(f.rows, f.expected); + expect(proof.contextRead?.discoveries).toHaveLength(2); + expect(onlyCopilotAttachedOperations(f.notices, f.command, proof)).toBe(true); + expect(retained.outcome).toContain("calibration only"); + }); + it.each(["get_task_context", "get task context", "dedicated get_task_context tool", "get_task_context paperclip_finish"])("admits bounded context metadata query %s", query => { + expect(isCopilotContextDiscoveryProgress(`paperclip-search_api (pending): ${query}`)).toBe(true); + }); + it.each(["create_task", "get task context call_api", "get task context create_task", "get task context; paperclip_finish", "get_task_context call_api", "get_task_context get_task_context", "get_task_context; paperclip_finish", "get_task_context " + "tool ".repeat(100)])("refuses another search scope: %s", query => { + expect(isCopilotContextDiscoveryProgress(`paperclip-search_api (pending): ${query}`)).toBe(false); + }); + it.each(["duplicate-receipt", "missing-receipt", "borrowed-identity", "foreign-session", "mutation", "another-query", "native-result-mismatch", "late-discovery"])("refuses %s in a multi-call sequence", kind => { + const f = fixture(); + const receipts = f.rows.filter((r: any) => payload(r).category === "paperclip_semantic_tool_receipt_v2" && payload(r).details.some((d: any) => d.name === "operationId" && d.value === "search_api")); + const second = receipts[1]!; + const start = f.rows.find((r: any) => r.eventType === "tool.execution.started" && payload(r).name === "paperclip-search_api"); + if (kind === "duplicate-receipt") f.rows.push(structuredClone(second)); + if (kind === "missing-receipt") f.rows.splice(f.rows.indexOf(second), 1); + if (kind === "borrowed-identity") detail(second, "callIdentitySha256", payload(receipts[0]).details.find((d: any) => d.name === "callIdentitySha256").value); + if (kind === "foreign-session") second.payload.prpEvent.normalizedSessionId = "foreign"; + if (kind === "mutation") payload(start).readOnly = false; + if (kind === "another-query") payload(start).progress = "paperclip-search_api (pending): create_task"; + if (kind === "native-result-mismatch") detail(second, "resultSha256", "f".repeat(64)); + if (kind === "late-discovery") second.seq = f.command.seq + 1; + expect(() => readCopilotContextRead(f.rows, f.command, true)).toThrow(); + }); + it("refuses an unbounded discovery campaign", () => { + const f = fixture(), receipt = f.rows.find((r: any) => payload(r).category === "paperclip_semantic_tool_receipt_v2" && payload(r).details.some((d: any) => d.name === "operationId" && d.value === "search_api")); + for (let i = 0; i < COPILOT_CONTEXT_DISCOVERY_LIMIT; i++) f.rows.push(structuredClone(receipt)); + expect(() => readCopilotContextRead(f.rows, f.command, true)).toThrow(/unbounded/); + }); +}); diff --git a/tests/runner-e2e/copilot-evidence.ts b/tests/runner-e2e/copilot-evidence.ts index 023e9b3292a..6cd61825ac2 100644 --- a/tests/runner-e2e/copilot-evidence.ts +++ b/tests/runner-e2e/copilot-evidence.ts @@ -60,3 +60,20 @@ export function readCopilotToolEvidence(rows: readonly unknown[], expectedRunId: export function copilotOrigin(n: CopilotToolNotice) { return { runId: n.runId, sessionId: n.sessionId, turnId: n.turnId, toolCallId: n.toolCallId }; } + +/** A patch notification can omit its path. Bind the pending native edit to the + * exact permission carrying that path; never derive a path from display text or + * borrow another operation's permission. Durable stream/card checks follow. */ +export function copilotEditOriginForPermission(notices: readonly CopilotToolNotice[], target: string, expectedToolCallId?: string): CopilotToolNotice | undefined { + const requests = notices.filter(n => n.stage === "permission_requested" && n.operation === "edit" && n.target === target + && (!expectedToolCallId || n.toolCallId === expectedToolCallId)); + if (!requests.length) return undefined; + if (requests.length !== 1) throw new Error("Ambiguous Copilot edit permission target"); + const request = requests[0]!; + const group = notices.filter(n => n.toolCallId === request.toolCallId); + if (group.some(n => n.runId !== request.runId || n.sessionId !== request.sessionId || n.turnId !== request.turnId + || (n.target !== undefined && n.target !== target) || (n.operation !== undefined && n.operation !== "edit"))) throw new Error("Conflicting Copilot edit permission origin"); + const pending = group.filter(n => n.stage === "tool" && n.status === "pending" && n.operation === "edit"); + if (pending.length > 1) throw new Error("Duplicate Copilot pending edit origin"); + return pending[0]; +} diff --git a/tests/runner-e2e/copilot-local-fixtures.ts b/tests/runner-e2e/copilot-local-fixtures.ts index 0179869c228..41f4473e56c 100644 --- a/tests/runner-e2e/copilot-local-fixtures.ts +++ b/tests/runner-e2e/copilot-local-fixtures.ts @@ -1,6 +1,8 @@ +import { canonicalRemoteCopilotCommand, copilotDeathArguments, copilotDeathCommandDigest, selectOwnedCopilotProcess } from "./copilot-provider-death.js"; +import { parseRemoteProcStat } from "./remote-native-fixtures.js"; import { spawn, execFileSync, type ChildProcess } from "node:child_process"; import { createHash, randomBytes } from "node:crypto"; -import { watch, lstatSync, type FSWatcher } from "node:fs"; +import { watch, lstatSync, readFileSync, readlinkSync, statSync, type FSWatcher } from "node:fs"; import { lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { createServer, type Socket } from "node:net"; import { basename, join, relative } from "node:path"; @@ -70,28 +72,85 @@ export function watchDeniedTarget(directory: string, name: string) { closing = true; watcher.close(); return final; } }; } -interface ProcessIdentity { pid: number; parent: number; start: string } +interface ProcessIdentity { pid: number; parent: number; start: string; bootId?: string; startTicks?: string; state?: string } +export function sameObservedProcess(left: ProcessIdentity | undefined, right: ProcessIdentity): boolean { + if (!left || left.pid !== right.pid) return false; + if (left.bootId !== undefined || right.bootId !== undefined) return Boolean(left.bootId && left.bootId === right.bootId && left.startTicks && left.startTicks === right.startTicks); + return left.start === right.start; +} function processTable(): ProcessIdentity[] { const output = execFileSync("/bin/ps", ["-axo", "pid=,ppid=,lstart="], { encoding: "utf8", timeout: 3000, maxBuffer: 8 * 1024 * 1024, env: { PATH: "/usr/bin:/bin", LC_ALL: "C" } }); - return output.split("\n").flatMap(line => { const m = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/u.exec(line); return m ? [{ pid: Number(m[1]), parent: Number(m[2]), start: m[3]! }] : []; }); + const bootId = process.platform === "linux" ? readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim() : undefined; + return output.split("\n").flatMap(line => { + const m = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/u.exec(line); + if (!m) return []; + const pid = Number(m[1]); + if (pid < 2) return []; + try { + // Match hot-restart.readProcessStartedAt, the server's Linux identity + // source. ps lstart is rounded and does not identify the same timestamp. + const start = process.platform === "linux" ? new Date(lstatSync(`/proc/${pid}`).ctimeMs).toISOString() : m[3]!; + if (bootId) { + const identity = parseRemoteProcStat(pid, readFileSync(`/proc/${pid}/stat`, "utf8"), bootId); + if (identity.state === "Z") return []; + return [{ pid, parent: identity.ppid, start, bootId, startTicks: identity.startTicks, state: identity.state }]; + } + return [{ pid, parent: Number(m[2]), start }]; + } catch (error) { + if (["ENOENT", "ESRCH"].includes((error as NodeJS.ErrnoException).code ?? "")) return []; + throw error; + } + }); } export interface RunProcessAuthority { pid: number; groupId: number; startedAt: string; runId: string } export function isPerTurnRunProcess(authority: RunProcessAuthority, observed: ProcessIdentity, command: string): boolean { if (!/^[a-zA-Z0-9_-]{1,128}$/u.test(authority.runId) || authority.pid !== observed.pid || authority.groupId !== observed.pid) return false; const started = Date.parse(authority.startedAt), actual = Date.parse(observed.start); - if (!Number.isFinite(started) || !Number.isFinite(actual) || Math.floor(started / 1000) !== Math.floor(actual / 1000)) return false; + if (!Number.isFinite(started) || !Number.isFinite(actual) || started !== actual) return false; const args = command.trim().split(/\s+/u); return [["--run-id", authority.runId], ["--lifecycle-mode", "per_turn"]].every(([flag, value]) => { const at = args.indexOf(flag!); return at >= 0 && args.lastIndexOf(flag!) === at && args[at + 1] === value; }); } +/** Inspect the actual executable and its held descriptor before accepting the + * Linux native launcher alias. Never canonicalize an unverified command name. */ +function ownedProcessCommand(pid: number): readonly string[] { + const command = execFileSync("/bin/ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 3000, maxBuffer: 64 * 1024 }).trim().split(/\s+/u); + if (process.platform !== "linux" || !/^\/proc\/self\/fd\/(?:3|7)$/u.test(command[0] ?? "")) return command; + const executable = `/proc/${pid}/exe`, descriptor = `/proc/${pid}/fd/${command[0]!.split("/").at(-1)}`; + const executableStat = statSync(executable, { bigint: true }), descriptorStat = statSync(descriptor, { bigint: true }); + return canonicalRemoteCopilotCommand(command, readlinkSync(executable), + { dev: String(executableStat.dev), ino: String(executableStat.ino) }, + { path: readlinkSync(descriptor), dev: String(descriptorStat.dev), ino: String(descriptorStat.ino) }); +} /** Read-only PID/start journal; never signal an API-reported or reused PID. * Native onSpawn publishes the runnerd child. Bind its current argv to this run * and per_turn lifecycle before treating its retirement as run cleanup. */ +/** Losing a captured process identity cannot be repaired by a later empty sample. */ +export function retainRunProcessIdentity(previous: T, current: T): T & { identityChanged: boolean } { + return { ...current, identityChanged: previous.identityChanged === true || current.identityChanged === true }; +} + export function observeRunProcesses() { - const owned = new Map(); let root: ProcessIdentity | undefined; + const owned = new Map(); let root: ProcessIdentity | undefined; let providerDeathDispatched = false; + let authorityCheck: { authority: RunProcessAuthority; observed: ProcessIdentity; commandSha256: string; accepted: boolean } | undefined; return { + killOwnedCopilot() { + if (!root || providerDeathDispatched) throw new Error("Copilot death requires a captured run and one dispatch"); + const table = processTable(); + const live = table.filter(p => sameObservedProcess(owned.get(p.pid), p)).map(p => p.pid); + const argv = new Map(live.map(pid => [pid, ownedProcessCommand(pid)])); + const child = selectOwnedCopilotProcess(root.pid, [...owned.values()], live, argv, copilotDeathArguments); + const fresh = processTable(); + if (!fresh.some(p => sameObservedProcess(root, p)) || !fresh.some(p => sameObservedProcess(child, p))) throw new Error("Copilot process identity changed before death dispatch"); + const command = ownedProcessCommand(child.pid); + if (JSON.stringify(command) !== JSON.stringify(argv.get(child.pid))) throw new Error("Copilot command changed before death dispatch"); + providerDeathDispatched = true; + const dispatchedMonotonicNs = process.hrtime.bigint().toString(); + process.kill(child.pid, "SIGKILL"); + return { schema: "paperclip.e2e.copilot-owned-provider-death.v1", root, child, signal: "SIGKILL", commandSha256: copilotDeathCommandDigest(command), dispatchedMonotonicNs }; + }, sample(authority?: RunProcessAuthority) { const table = processTable(); if (!root && authority && authority.pid > 1 && authority.pid !== process.pid) { @@ -100,7 +159,9 @@ export function observeRunProcesses() { let command: string; try { command = execFileSync("/bin/ps", ["-p", String(candidate.pid), "-o", "command="], { encoding: "utf8", timeout: 3000, maxBuffer: 64 * 1024 }); } catch { command = ""; } // A vanished/uninspectable PID supplies no authority. - if (isPerTurnRunProcess(authority, candidate, command) && processTable().some(p => p.pid === candidate.pid && p.start === candidate.start)) { root = candidate; owned.set(root.pid, root); } + const accepted = isPerTurnRunProcess(authority, candidate, command) && processTable().some(p => sameObservedProcess(candidate, p)); + authorityCheck = { authority, observed: candidate, commandSha256: sha256(command), accepted }; + if (accepted) { root = candidate; owned.set(root.pid, root); } } } let changed = true; @@ -108,10 +169,12 @@ export function observeRunProcesses() { changed = false; for (const p of table) if (!owned.has(p.pid)) { const parent = owned.get(p.parent); - if (parent && table.some(t => t.pid === parent.pid && t.start === parent.start)) { owned.set(p.pid, p); changed = true; } + if (parent && table.some(t => sameObservedProcess(parent, t))) { owned.set(p.pid, p); changed = true; } } } - return { captured: Boolean(root), journal: [...owned.values()], live: table.filter(p => owned.get(p.pid)?.start === p.start).map(p => p.pid) }; + return { captured: Boolean(root), authorityCheck, rootObserved: root ? table.find(p => p.pid === root!.pid) : undefined, + identityChanged: table.some(p => owned.has(p.pid) && !sameObservedProcess(owned.get(p.pid), p)), + journal: [...owned.values()], live: table.filter(p => sameObservedProcess(owned.get(p.pid), p)).map(p => p.pid) }; }, }; } @@ -120,12 +183,12 @@ export function observeRunProcesses() { * arguments or file path. The test owns/reaps the fixed finite child, records its * OS exit status, and holds the exact provider-launched client until that exit. */ -export async function createAttachedCommandFixture(markerPath: string, delayMs = 4000) { +export async function createAttachedCommandFixture(markerPath: string, delayMs = 4000, waitForFinishAttempt = false) { if (!Number.isInteger(delayMs) || delayMs < 100 || delayMs > 8000) throw new Error("Invalid bounded fixture delay"); const root = await mkdtemp("/tmp/pc-copilot-"); await mkdir(join(root, "private"), { mode: 0o700 }); const socketPath = join(root, "private", "socket"), scriptPath = join(root, "client.cjs"); const nonce = randomBytes(16).toString("hex"), marker = `${randomBytes(24).toString("hex")}\n`; - const script = `const net=require('node:net');const s=net.connect(process.argv[2]);let b='';s.setTimeout(15000,()=>process.exit(3));s.on('error',()=>process.exit(4));s.on('connect',()=>s.write(JSON.stringify({nonce:process.argv[3],pid:process.pid})+'\\n'));s.on('data',x=>{b+=x;if(b.includes('\\n')){const r=JSON.parse(b);s.end();process.exit(r.code===0?0:5);}});`; + const script = `const net=require('node:net');const s=net.connect(process.argv[2]);let b='';s.setTimeout(${waitForFinishAttempt ? 45000 : 15000},()=>process.exit(3));s.on('error',()=>process.exit(4));s.on('connect',()=>s.write(JSON.stringify({nonce:process.argv[3],pid:process.pid})+'\\n'));s.on('data',x=>{b+=x;if(b.includes('\\n')){const r=JSON.parse(b);s.end();process.exit(r.code===0?0:5);}});`; await writeFile(scriptPath, script, { mode: 0o400 }); const command = `${quote(process.execPath)} ${quote(scriptPath)} ${quote(socketPath)} ${quote(nonce)}`; const commandSha256 = sha256(command); @@ -136,6 +199,7 @@ export async function createAttachedCommandFixture(markerPath: string, delayMs = let exit: { observedAtMs: number; code: number; ownedProcessIdentityVerified: boolean; commandSha256: string } | null = null; const sockets = new Set(); let closed = false; + let releasedAtMs: number | null = null; const server = createServer(socket => { sockets.add(socket); socket.on("close", () => sockets.delete(socket)); socket.on("error", () => { failure = "fixture_socket_error"; }); let buffer = ""; @@ -152,12 +216,15 @@ export async function createAttachedCommandFixture(markerPath: string, delayMs = client = observed; clientObservation = setInterval(() => { try { - if (!processTable().some(p => p.pid === client!.pid && p.start === client!.start)) { + if (!processTable().some(p => sameObservedProcess(client, p))) { clientExitedAtMs = Date.now(); clearInterval(clientObservation); } } catch { failure = "fixture_client_observation_failed"; clearInterval(clientObservation); } }, 25); - child = spawn(process.execPath, ["-e", `setTimeout(()=>process.exit(0),${delayMs})`], { env: { PATH: "/usr/bin:/bin" }, stdio: "ignore" }); + const childScript = waitForFinishAttempt + ? `const deadline=setTimeout(()=>process.exit(3),30000);process.stdin.once('data',b=>{if(b.toString()!=='release\\n')process.exit(2);clearTimeout(deadline);setTimeout(()=>process.exit(0),${delayMs})});` + : `setTimeout(()=>process.exit(0),${delayMs})`; + child = spawn(process.execPath, ["-e", childScript], { env: { PATH: "/usr/bin:/bin" }, stdio: waitForFinishAttempt ? ["pipe", "ignore", "ignore"] : "ignore" }); child.once("error", () => { failure = "fixture_child_start_failed"; socket.destroy(); }); child.once("exit", (code, signal) => { exit = { observedAtMs: Date.now(), code: code ?? -1, ownedProcessIdentityVerified: Number.isInteger(child?.pid), commandSha256 }; @@ -174,7 +241,11 @@ export async function createAttachedCommandFixture(markerPath: string, delayMs = catch (error) { server.close(); await rm(root, { recursive: true, force: true }); throw error; } return { command, commandSha256, marker, - snapshot() { const table = processTable(); return { connections, failure, markerWrittenAtMs, clientExitedAtMs, commandExit: exit, childPid: child?.pid ?? null, clientPid: client?.pid ?? null, clientGone: Boolean(client) && !table.some(p => p.pid === client!.pid && p.start === client!.start), childGone: Boolean(exit), observedAtMs: Date.now() }; }, + releaseAfterFinish() { + if (!waitForFinishAttempt || closed || releasedAtMs !== null || !child?.stdin || child.exitCode !== null || child.signalCode !== null) throw new Error("Attached fixture release requires one live owned child"); + releasedAtMs = Date.now(); child.stdin.end("release\n"); return { releasedAtMs }; + }, + snapshot() { const table = processTable(); return { connections, failure, markerWrittenAtMs, clientExitedAtMs, commandExit: exit, childPid: child?.pid ?? null, clientPid: client?.pid ?? null, clientGone: Boolean(client) && !table.some(p => sameObservedProcess(client, p)), childGone: Boolean(exit), observedAtMs: Date.now() }; }, async close() { if (closed) return; closed = true; clearInterval(clientObservation); let cleanupError: unknown; diff --git a/tests/runner-e2e/copilot-local-process-observer.test.ts b/tests/runner-e2e/copilot-local-process-observer.test.ts new file mode 100644 index 00000000000..834491c8b2a --- /dev/null +++ b/tests/runner-e2e/copilot-local-process-observer.test.ts @@ -0,0 +1,49 @@ +import { spawn } from "node:child_process"; +import { lstatSync } from "node:fs"; +import { randomUUID } from "node:crypto"; +import { describe, expect, it } from "vitest"; +import { isPerTurnRunProcess, observeRunProcesses, sameObservedProcess } from "./copilot-local-fixtures.js"; + +describe("Copilot local process ownership", () => { + it("retains the kernel birth identity across proc-directory timestamp changes", () => { + const original = { pid: 100, parent: 1, start: "2026-10-08T01:18:30.820Z", bootId: "00000000-0000-0000-0000-000000000001", startTicks: "1000" }; + expect(sameObservedProcess(original, { ...original, start: "2026-10-08T01:18:32.000Z" })).toBe(true); + for (const changed of [{ pid: 101 }, { startTicks: "1001" }, { bootId: "00000000-0000-0000-0000-000000000002" }, { startTicks: undefined }, { bootId: undefined }]) { + expect(sameObservedProcess(original, { ...original, ...changed })).toBe(false); + } + expect(sameObservedProcess(undefined, original)).toBe(false); + }); + it.skipIf(process.platform !== "linux")("captures the exact Linux process start identity published by the server", async () => { + const runId = randomUUID(); + const child = spawn(process.execPath, ["-e", "setInterval(()=>{},1000)", "--", "--run-id", runId, "--lifecycle-mode", "per_turn"], { detached: true, stdio: "ignore", env: { PATH: "/usr/bin:/bin" } }); + const exited = new Promise(resolve => child.once("exit", () => resolve())); + try { + await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); }); + const pid = child.pid!; + await new Promise(resolve => setTimeout(resolve, 1100)); + // hot-restart.readProcessStartedAt publishes /proc/ ctime on Linux, + // which has different semantics and precision from ps's birth time. + const startedAt = new Date(lstatSync(`/proc/${pid}`).ctimeMs).toISOString(); + const observer = observeRunProcesses(); + const authority = { pid, groupId: pid, startedAt, runId }; + const captured = observer.sample(authority); + expect(captured.captured).toBe(true); + expect(captured.live).toContain(pid); + expect(observeRunProcesses().sample({ ...authority, startedAt: new Date(Date.parse(startedAt) + 1).toISOString() }).captured).toBe(false); + child.kill("SIGTERM"); await exited; + expect(observer.sample(authority).live).toEqual([]); + } finally { + if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); + await exited; + } + }); + + it("rejects a different millisecond in the Linux authority instead of rounding it to seconds", () => { + const start = "2026-10-08T01:02:58.051Z"; + const authority = { pid: 100, groupId: 100, startedAt: start, runId: "owned-run" }; + const observed = { pid: 100, parent: 1, start }; + const command = "/owned/runner --run-id owned-run --lifecycle-mode per_turn"; + expect(isPerTurnRunProcess(authority, observed, command)).toBe(true); + expect(isPerTurnRunProcess({ ...authority, startedAt: "2026-10-08T01:02:58.052Z" }, observed, command)).toBe(false); + }); +}); diff --git a/tests/runner-e2e/copilot-permission-card.spec.ts b/tests/runner-e2e/copilot-permission-card.spec.ts new file mode 100644 index 00000000000..97cab56aa5a --- /dev/null +++ b/tests/runner-e2e/copilot-permission-card.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from "@playwright/test"; +import { copilotPendingPermissionCard } from "./copilot-permission-card.js"; + +test("selects the unanswered exact target while completed context remains visible", async ({ page }) => { + await page.setContent('
get_task_context — resolved
Change file: pc-denied-A/target.txt
'); + await expect(page.getByTestId("task-chat-runtime-request")).toHaveCount(2); + const card = copilotPendingPermissionCard(page, "pc-denied-A/target.txt"); + await expect(card).toHaveCount(1); + await expect(card.getByRole("button", { name: "Deny", exact: true })).toBeEnabled(); + await expect(copilotPendingPermissionCard(page, "get_task_context")).toHaveCount(0); +}); + +test("preserves duplicate detection and never borrows another target's control", async ({ page }) => { + await page.setContent('
Change file: expected.txt — resolved
Change file: foreign.txt
'); + await expect(copilotPendingPermissionCard(page, "expected.txt")).toHaveCount(0); + await page.setContent('
Change file: expected.txt
Change file: expected.txt
'); + await expect(copilotPendingPermissionCard(page, "expected.txt")).toHaveCount(2); +}); + +test("selects a new context decision without reusing an answered discovery card", async ({ page }) => { + await page.setContent('
search_api: get_task_context — resolved
get_task_context
'); + await expect(copilotPendingPermissionCard(page, "get_task_context")).toHaveCount(1); + await expect(copilotPendingPermissionCard(page, "search_api")).toHaveCount(0); +}); diff --git a/tests/runner-e2e/copilot-permission-card.ts b/tests/runner-e2e/copilot-permission-card.ts new file mode 100644 index 00000000000..866a4923cee --- /dev/null +++ b/tests/runner-e2e/copilot-permission-card.ts @@ -0,0 +1,10 @@ +import type { Page } from "@playwright/test"; + +/** Answered context/discovery cards remain visible history. Match the current + * operation and its decision control; API assertions independently require one + * exact pending request and every POST is bound to its run/request/turn. */ +export function copilotPendingPermissionCard(page: Page, operationOrTarget: string) { + return page.getByTestId("task-chat-runtime-request") + .filter({ visible: true, hasText: operationOrTarget }) + .filter({ has: page.getByRole("button", { name: "Deny", exact: true }) }); +} diff --git a/tests/runner-e2e/copilot-permission-target-origin.test.ts b/tests/runner-e2e/copilot-permission-target-origin.test.ts new file mode 100644 index 00000000000..633ad0abbce --- /dev/null +++ b/tests/runner-e2e/copilot-permission-target-origin.test.ts @@ -0,0 +1,55 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { copilotEditOriginForPermission, readCopilotToolEvidence } from "./copilot-evidence.js"; +import { observeActiveStopPending, readActiveStopCaller } from "./copilot-active-stop-evidence.js"; +const retained = JSON.parse(readFileSync(new URL("./fixtures/copilot-patch-permission-target-v15.json", import.meta.url), "utf8")); +const caller = readActiveStopCaller({ deploymentMode: "local_trusted" }, { session: { userId: "local-board", id: "paperclip:local_implicit:local-board" } }); +function fixture() { + const { rows: events, scope, run, issue } = structuredClone(retained); + const notices = readCopilotToolEvidence(events, run.id); + const read = () => observeActiveStopPending({ events, scope, run, issue, caller, cancellationRequestId: "11111111-2222-4333-8444-555555555555" }); + return { events, scope, notices, read }; +} +describe("pending patch target bound by its exact permission", () => { + it("calibrates the retained missing-notification path while keeping the actual card target", () => { + const f = fixture(), origin = copilotEditOriginForPermission(f.notices, f.scope.target)!; + expect(origin.target).toBeUndefined(); + expect(origin.toolCallId).toBe("custom_call_jyQm34Kq61JjSxkZUXvPSNrh"); + expect(f.read()).toMatchObject({ toolCallId: origin.toolCallId, scope: { target: f.scope.target }, requestId: "acpx-input-ccaa35c8d2130be28a27e1d3" }); + expect(retained.outcome).toContain("calibration only"); + }); + it.each(["wrong-call", "foreign-session", "foreign-turn", "foreign-run", "conflicting-path", "conflicting-operation", "duplicate-pending", "duplicate-permission", "missing-permission"])("refuses %s", kind => { + const f = fixture(); + const origin = copilotEditOriginForPermission(f.notices, f.scope.target)!; + const permission = f.notices.find(n => n.stage === "permission_requested" && n.toolCallId === origin.toolCallId)!; + if (kind === "wrong-call") permission.toolCallId = "unrelated"; + if (kind === "foreign-session") permission.sessionId = "foreign"; + if (kind === "foreign-turn") permission.turnId = "foreign"; + if (kind === "foreign-run") permission.runId = "foreign"; + if (kind === "conflicting-path") origin.target = "another.txt"; + if (kind === "conflicting-operation") origin.operation = "execute"; + if (kind === "duplicate-pending") f.notices.push({ ...origin, seq: origin.seq + 1 }); + if (kind === "duplicate-permission") f.notices.push({ ...permission, seq: permission.seq + 1 }); + if (kind === "missing-permission") f.notices.splice(f.notices.indexOf(permission), 1); + if (["wrong-call", "missing-permission"].includes(kind)) expect(copilotEditOriginForPermission(f.notices, f.scope.target)).toBeUndefined(); + else expect(() => copilotEditOriginForPermission(f.notices, f.scope.target)).toThrow(/Copilot.*(?:origin|target)/); + }); + it("cannot borrow a target from another native operation", () => { + const f = fixture(); + expect(copilotEditOriginForPermission(f.notices, f.scope.target, "other-call")).toBeUndefined(); + }); + it.each(["card-call", "card-request", "card-source", "wrong-provider", "wrong-adapter", "wrong-method", "permission-path", "context-after-edit"])("keeps durable evidence guard for %s", kind => { + const f = fixture(); + const row = (eventType: string, seq: number) => f.events.find((r: any) => r.eventType === eventType && r.seq === seq); + const payload = (r: any) => r.payload.prpEvent.payload; + if (kind === "card-call") payload(row("runtime_request.created", 134)).request.details.toolCallId = "other-call"; + if (kind === "card-request") payload(row("runtime_request.created", 134)).request.requestId = "other-request"; + if (kind === "wrong-provider") payload(row("runtime_request.created", 134)).request.origin.provider = "cursor"; + if (kind === "wrong-adapter") payload(row("runtime_request.created", 134)).request.origin.adapter = "untrusted"; + if (kind === "wrong-method") payload(row("runtime_request.created", 134)).request.origin.method = "other"; + if (kind === "card-source") row("runtime_request.created", 134).payload.prpEvent.sourceInstanceId = "other-source"; + if (kind === "permission-path") payload(row("provider.notice.recorded", 137)).details.find((d: any) => d.name === "target").value = "other.txt"; + if (kind === "context-after-edit") row("tool.execution.completed", 130).seq = 136; + expect(f.read).toThrow(); + }); +}); diff --git a/tests/runner-e2e/copilot-protection-cases.test.ts b/tests/runner-e2e/copilot-protection-cases.test.ts index d2768e649c4..5f6df034c6b 100644 --- a/tests/runner-e2e/copilot-protection-cases.test.ts +++ b/tests/runner-e2e/copilot-protection-cases.test.ts @@ -7,9 +7,10 @@ const terminal = { observedAtMs: 50, runId: "run", turnId: "turn", status: "succ const cleanup = { observedAtMs: 60, ownedProcessesRemaining: 0 }; function denied(): CopilotDeniedWriteEvidence { return { - expected: identity, terminal: { ...terminal, status: "cancelled" }, cleanup, settlement: { schema: "paperclip.e2e.copilot-denial-settlement.v3", ...identity, requestId: "permission-0", + expected: identity, terminal: { ...terminal, status: "cancelled" }, cleanup, settlement: { schema: "paperclip.e2e.copilot-denial-settlement.v4", ...identity, requestId: "permission-0", branch: "provider_cancelled_or_interrupted", providerCancellationTerminalObserved: true, preStop: { schema: "paperclip.e2e.copilot-pre-stop-observation.v2" as const, ...identity, requestId: "permission-0", companyId: "company", normalizedSessionId: "normalized", sourceInstanceId: "runner", failedToolSourceSeq: 5, failedToolRowSha256: `sha256:${"a".repeat(64)}`, terminal: null, apiReadCompletedMonotonicNs: "10", cancellationRequestId: "11111111-1111-4111-8111-111111111111" }, stopDispatchMonotonicNs: "20", + stopAcknowledgement: { schema: "paperclip.e2e.copilot-stop-acknowledgement.v1" as const, companyId: "company", runId: "run", issueId: "issue", callerUserId: "local-board", fixtureCorrelationId: "11111111-1111-4111-8111-111111111111", dispatchMonotonicNs: "20", apiResponseObservedMonotonicNs: "21", intentId: "native-cancellation:11111111-1111-4111-8111-111111111111", intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit", responseMetadataSha256: `sha256:${"c".repeat(64)}` }, providerTerminal: { rowSha256: `sha256:${"b".repeat(64)}`, failedToolRowSha256: `sha256:${"a".repeat(64)}`, eventType: "turn.cancelled", normalizedSessionId: "normalized", sourceInstanceId: "runner", requestSourceSeq: 1, resolvedSourceSeq: 2, deliveredSourceSeq: 3, failedNoticeSourceSeq: 4, failedToolSourceSeq: 5, failedToolRowCreatedAtMs: 31, sourceSeq: 6, emittedAtMs: 50, rowCreatedAtMs: 51 }, runStop: { companyId: "company", issueId: "issue", scope: "run", status: "cancelled", issueStatus: "in_progress", intentId: "native-cancellation:11111111-1111-4111-8111-111111111111", intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit", requestedAtMs: 40, recordedAtMs: 41, acknowledgedAtMs: 52, finishedAtMs: 53 } }, requestId: "permission-0", expectedRelativePath: "copilot-denied-nonce.txt", request: { ...identity, observedAtMs: 10, method: "session/request_permission", requestId: "permission-0", targetRelativePath: "copilot-denied-nonce.txt", offeredActions: ["accept", "decline"] }, diff --git a/tests/runner-e2e/copilot-protection-evidence.test.ts b/tests/runner-e2e/copilot-protection-evidence.test.ts index eb6e1ce4e2d..75486245068 100644 --- a/tests/runner-e2e/copilot-protection-evidence.test.ts +++ b/tests/runner-e2e/copilot-protection-evidence.test.ts @@ -141,8 +141,10 @@ describe("Copilot sealed remote proof", () => { it("awaits the remote fixture and baseline before disclosing the actual command", async () => { const s = baseline(), order: string[] = []; const fixture: CopilotRemoteFixture = { binding: s.binding, remoteCwd: s.binding.remoteCwd, actionFile: s.setup.path, + killOwnedCopilot: async () => { throw new Error("Unexpected provider-death dispatch"); }, snapshot: async () => { await Promise.resolve(); order.push("baseline"); return s; }, - setupAttachedCommand: async input => { expect(input.markerText).toBe("private-marker"); order.push("setup"); return { command: "exact-remote-command", commandSha256: `sha256:${"c".repeat(64)}` }; }, + releaseAttachedCommand: async () => { throw new Error("Unexpected fixture release during setup"); }, + setupAttachedCommand: async input => { expect(input.waitForFinishAttempt).toBe(true); expect(input.markerText).toBe("private-marker"); order.push("setup"); return { command: "exact-remote-command", commandSha256: `sha256:${"c".repeat(64)}` }; }, finish: async () => { throw new Error("must not finish during setup"); }, readFile: async () => { throw new Error("must not read host file"); }, close: async () => {} }; const prepared = await prepareCopilotRemoteAction({ fixture, companyId: "company", environmentId: "env", runId: "run", target, prompt: "test", markerText: "private-marker" }); order.push("publish"); expect(order).toEqual(["setup", "baseline", "publish"]); diff --git a/tests/runner-e2e/copilot-protection-evidence.ts b/tests/runner-e2e/copilot-protection-evidence.ts index e93f21f1f38..5ff5c562c0a 100644 --- a/tests/runner-e2e/copilot-protection-evidence.ts +++ b/tests/runner-e2e/copilot-protection-evidence.ts @@ -1,8 +1,10 @@ +import { assertCopilotStopAcknowledgement, type CopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; import { withoutProvenBootstrapReads, bootstrapReadExecutionId, type BootstrapReadProof } from "./native-bootstrap-read-proof.js"; import { canonicalJson } from "../../packages/shared/src/portability-hash.js"; import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { readCopilotToolEvidence, type CopilotToolNotice } from "./copilot-evidence.js"; +import { readCopilotContextRead } from "./copilot-context-evidence.js"; /** A tool origin exists regardless of the first status emitted by the provider. */ export function countCopilotToolOrigins(notices: readonly CopilotToolNotice[]): number { @@ -120,7 +122,9 @@ export interface CopilotRemoteSnapshot { export interface CopilotRemoteFixture { binding: CopilotRemoteBinding; remoteCwd: string; actionFile: string; snapshot(label: string): Promise; - setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number }): Promise<{ command: string; commandSha256: string }>; + killOwnedCopilot(): Promise>; + setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number; waitForFinishAttempt?: boolean }): Promise<{ command: string; commandSha256: string }>; + releaseAttachedCommand(): Promise<{ releasedAtMs: number }>; finish(): Promise; readFile(relative: string): Promise; close(): Promise; } export interface CopilotRemoteBootstrap { @@ -156,8 +160,11 @@ export function copilotRemoteDeniedSample(s: CopilotRemoteSnapshot, baseline: Co return { phase, observedAtMs: s.observedAtMs, exists: t.absent !== true || t.sha256 !== null }; } /** Only typed reads before the tested operation can be bootstrap work. */ -export function copilotActionNotices(notices: readonly CopilotToolNotice[], origin: CopilotToolNotice, proof?: BootstrapReadProof): CopilotToolNotice[] { - return withoutProvenBootstrapReads(notices, origin, proof); +export function copilotActionNotices(notices: readonly CopilotToolNotice[], origin: CopilotToolNotice, proof?: BootstrapReadProof, contextEvents?: readonly unknown[]): CopilotToolNotice[] { + const context = contextEvents ? readCopilotContextRead(contextEvents, origin, true) : undefined; + const remaining = context ? notices.filter(n => ![context.toolCallId, ...context.discoveries.map(d => d.toolCallId)].includes(n.toolCallId)) : notices; + const bootstrap = proof && context ? { ...proof, events: proof.events.filter(r => ![...context.canonicalSeqs, ...context.discoveries.flatMap(d => d.canonicalSeqs)].includes((r as { seq: number }).seq)) } : proof; + return withoutProvenBootstrapReads(remaining, origin, bootstrap); } export function assertCopilotRemoteAttached(s: CopilotRemoteSnapshot, baseline: CopilotRemoteSnapshot, terminalAt: number) { assertCopilotRemoteRetirement(s, baseline); @@ -179,7 +186,7 @@ export async function prepareCopilotRemoteAction(input: { }) { const f = input.fixture; if (f.binding.companyId !== input.companyId || f.binding.environmentId !== input.environmentId || f.binding.runId !== input.runId || f.remoteCwd !== f.binding.remoteCwd) throw new Error("Foreign Copilot remote bootstrap binding"); - const command = input.markerText === undefined ? undefined : await f.setupAttachedCommand({ marker: input.target, markerText: input.markerText, delayMs: 4000 }); + const command = input.markerText === undefined ? undefined : await f.setupAttachedCommand({ marker: input.target, markerText: input.markerText, delayMs: 4000, waitForFinishAttempt: true }); const baseline = await f.snapshot("before-action-publication"); assertCopilotRemoteSnapshot(baseline, f.binding); if (baseline.setup.published || baseline.setup.path !== f.actionFile || !baseline.processes.captured || baseline.processes.live.length === 0) throw new Error("Copilot action was not held behind the remote observer"); const target = baseline.targets[input.target]; @@ -196,7 +203,8 @@ export async function readCopilotRemoteMarkerAfterRetirement(fixture: CopilotRem } export interface CopilotDenialSettlement { - schema: "paperclip.e2e.copilot-denial-settlement.v3"; + stopAcknowledgement: CopilotStopAcknowledgement; + schema: "paperclip.e2e.copilot-denial-settlement.v4"; branch: "provider_cancelled_or_interrupted" | "provider_completed_observed_before_stop"; /** A cancelled terminal alone does not prove Stop reached active work. */ providerCancellationTerminalObserved: boolean; @@ -222,7 +230,7 @@ const settlementTime = (v: unknown): v is number => Number.isSafeInteger(v) && ( const settlementDate = (v: unknown): number => typeof v === "string" && /^\d{4}-\d{2}-\d{2}T.*Z$/u.test(v) ? Date.parse(v) : NaN; export function validCopilotDenialSettlement(s: CopilotDenialSettlement): boolean { - if (!s || s.schema !== "paperclip.e2e.copilot-denial-settlement.v3") return false; + if (!s || s.schema !== "paperclip.e2e.copilot-denial-settlement.v4") return false; const t = s.providerTerminal, c = s.runStop; if (!t || !c || ![s.runId, s.sessionId, s.turnId, s.toolCallId, s.requestId, t.normalizedSessionId, t.sourceInstanceId, c.companyId, c.issueId, c.intentId, c.intentAuditId, c.acknowledgementAuditId].every(settlementId) @@ -236,7 +244,13 @@ export function validCopilotDenialSettlement(s: CopilotDenialSettlement): boolea if (!before || before.schema !== "paperclip.e2e.copilot-pre-stop-observation.v2" || !["runId", "sessionId", "turnId", "toolCallId", "requestId"].every(k => before[k as keyof CopilotPreStopObservation] === s[k as keyof CopilotDenialSettlement]) || typeof before.cancellationRequestId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(before.cancellationRequestId) - || c.intentId !== `native-cancellation:${before.cancellationRequestId}` + || typeof s.stopDispatchMonotonicNs !== "string" || !/^[1-9][0-9]{0,29}$/.test(s.stopDispatchMonotonicNs) + || !s.stopAcknowledgement || s.stopAcknowledgement.schema !== "paperclip.e2e.copilot-stop-acknowledgement.v1" + || s.stopAcknowledgement.fixtureCorrelationId !== before.cancellationRequestId || s.stopAcknowledgement.dispatchMonotonicNs !== s.stopDispatchMonotonicNs + || s.stopAcknowledgement.intentId !== c.intentId || s.stopAcknowledgement.intentAuditId !== c.intentAuditId || s.stopAcknowledgement.acknowledgementAuditId !== c.acknowledgementAuditId + || s.stopAcknowledgement.companyId !== c.companyId || s.stopAcknowledgement.issueId !== c.issueId || s.stopAcknowledgement.runId !== s.runId || s.stopAcknowledgement.callerUserId !== "local-board" + || !/^sha256:[a-f0-9]{64}$/.test(s.stopAcknowledgement.responseMetadataSha256) + || !/^[1-9][0-9]{0,29}$/.test(s.stopAcknowledgement.apiResponseObservedMonotonicNs) || BigInt(s.stopAcknowledgement.apiResponseObservedMonotonicNs) <= BigInt(s.stopDispatchMonotonicNs) || before.companyId !== c.companyId || before.normalizedSessionId !== t.normalizedSessionId || before.sourceInstanceId !== t.sourceInstanceId || before.failedToolSourceSeq !== t.failedToolSourceSeq || before.failedToolRowSha256 !== t.failedToolRowSha256 || ![t.rowSha256, t.failedToolRowSha256].every(v => /^sha256:[a-f0-9]{64}$/u.test(v)) @@ -301,7 +315,7 @@ export interface CopilotPreStopObservation { terminal: { eventType: "turn.completed" | "turn.cancelled" | "turn.interrupted"; sourceSeq: number; rowSha256: string } | null; /** Fixture-process monotonic clock, never provider or database wall time. */ apiReadCompletedMonotonicNs: string; - /** Fresh caller UUID reserved atomically by the board cancel API. */ + /** Fixture correlation UUID; current mainline generates its own API intent. */ cancellationRequestId: string; } const denialRowSha = (row: unknown) => `sha256:${createHash("sha256").update(canonicalJson(row)).digest("hex")}`; @@ -336,7 +350,7 @@ export function observeCopilotPreStop(input: { events: readonly unknown[]; reque * the audited controller Stop. Database createdAt is transaction-start metadata, not a commit boundary. */ export function readCopilotDenialSettlement(input: { events: readonly unknown[]; request: CopilotToolNotice; run: unknown; issue: unknown; - preStop: CopilotPreStopObservation; stopDispatchMonotonicNs: string; + preStop: CopilotPreStopObservation; stopDispatchMonotonicNs: string; stopAcknowledgement: CopilotStopAcknowledgement; }): CopilotDenialSettlement { const invalid = () => new Error("Copilot denial lacks exact provider settlement and audited run Stop"); const { request, events } = input, run = settlementRecord(input.run), issue = settlementRecord(input.issue); @@ -344,20 +358,21 @@ export function readCopilotDenialSettlement(input: { if (events.length > 20_000 || run.id !== request.runId || issue.id !== run.nativeIssueId || !settlementId(issue.companyId) || run.companyId !== issue.companyId || issue.status !== "in_progress" || run.status !== "cancelled" || stop.schema !== "paperclip.native-cancellation.v1" || stop.runId !== run.id || stop.companyId !== issue.companyId || stop.issueId !== issue.id - || stop.intentId !== `native-cancellation:${input.preStop?.cancellationRequestId}` - || settlementRecord(settlementRecord(run.resultJson).startupCancellation).cancellationRequestId !== input.preStop?.cancellationRequestId || stop.scope !== "run" || stop.dispatched !== true || stop.dispatchState !== "acknowledged" || stop.reasonCode !== "cancellation_run_only" || !Array.isArray(stop.effects) || stop.effects.length !== 1 || stop.effects[0] !== "release_run_resources") throw invalid(); + try { assertCopilotStopAcknowledgement(input.stopAcknowledgement, run, + { companyId: issue.companyId, issueId: issue.id, runId: request.runId, callerUserId: "local-board" }, input.preStop.cancellationRequestId, input.stopDispatchMonotonicNs); } + catch { throw invalid(); } const { rows, get, stream, origin, delivery, failure, resolution, tool, toolRows, failed } = readCopilotDeniedEdit({ events, request, companyId: issue.companyId }); const observed = readDeniedTerminal({ rows, get, stream, origin, delivery, failure, resolution, tool, toolRows, failed }); if (!observed) throw invalid(); const { row, terminal } = observed; const result: CopilotDenialSettlement = { - schema: "paperclip.e2e.copilot-denial-settlement.v3", + schema: "paperclip.e2e.copilot-denial-settlement.v4", branch: row.eventType === "turn.completed" ? "provider_completed_observed_before_stop" : "provider_cancelled_or_interrupted", providerCancellationTerminalObserved: row.eventType !== "turn.completed", runId: run.id, sessionId: request.sessionId, turnId: request.turnId, toolCallId: request.toolCallId, requestId: request.requestId!, - preStop: input.preStop, stopDispatchMonotonicNs: input.stopDispatchMonotonicNs, + preStop: input.preStop, stopDispatchMonotonicNs: input.stopDispatchMonotonicNs, stopAcknowledgement: input.stopAcknowledgement, providerTerminal: { eventType: row.eventType, rowSha256: denialRowSha(row), failedToolRowSha256: denialRowSha(toolRows[0]), normalizedSessionId: terminal.normalizedSessionId, sourceInstanceId: terminal.sourceInstanceId, requestSourceSeq: origin.sourceSeq, resolvedSourceSeq: resolution.sourceSeq, deliveredSourceSeq: delivery.sourceSeq, failedNoticeSourceSeq: failure.sourceSeq, failedToolSourceSeq: tool.sourceSeq, failedToolRowCreatedAtMs: settlementDate(toolRows[0]!.createdAt), sourceSeq: terminal.sourceSeq, emittedAtMs: settlementDate(terminal.emittedAt), rowCreatedAtMs: settlementDate(row.createdAt) }, diff --git a/tests/runner-e2e/copilot-protection-flow.test.ts b/tests/runner-e2e/copilot-protection-flow.test.ts index 44cd1ee82b1..d7b9182266c 100644 --- a/tests/runner-e2e/copilot-protection-flow.test.ts +++ b/tests/runner-e2e/copilot-protection-flow.test.ts @@ -1,5 +1,5 @@ import { matchCopilotFixtureCommand } from "./copilot-protection-evidence.js"; -import { writeFileSync, unlinkSync } from "node:fs"; +import { writeFileSync, unlinkSync, utimesSync } from "node:fs"; import { spawn } from "node:child_process"; import { readFile, mkdtemp, rm, writeFile, unlink, rename, mkdir, symlink } from "node:fs/promises"; import { join } from "node:path"; @@ -8,7 +8,7 @@ import { createCopilotToolEvidence } from "../../packages/paperclip-runner/src/d import { validateAcpxRichEvent } from "../../packages/paperclip-runner/src/drivers/acpx/profile-extensions.js"; import { copilotOrigin, readCopilotToolEvidence } from "./copilot-evidence.js"; import { copilotProtectionCases, gradeCopilotAttachedSettlement, gradeCopilotDeniedWrite } from "./copilot-protection-cases.js"; -import { createAttachedCommandFixture, createDeniedTargetFixture, bindDeniedTargetPrompt, watchDeniedTarget, exists, isPerTurnRunProcess } from "./copilot-local-fixtures.js"; +import { createAttachedCommandFixture, createDeniedTargetFixture, bindDeniedTargetPrompt, watchDeniedTarget, exists, isPerTurnRunProcess, retainRunProcessIdentity } from "./copilot-local-fixtures.js"; import { runnerMatrix, suiteDefinitionHash } from "./catalog.js"; import { selectRunnerExecutions, parseRunnerSelectors } from "./selectors.js"; @@ -26,8 +26,8 @@ function projected(name: string, target = "copilot-denied-nonce.txt") { describe("Copilot Product protection integration", () => { it("registers two explicit cases on both environments with honest terminal expectations", () => { const cells = runnerMatrix.filter(x => x.suite.id === "copilot-protection"); - expect(cells[0]!.suite.definitionMetadata).toMatchObject({ version: 9, naturalSettlementObservationMs: 2000, denialTerminal: "correlated-provider-settlement-and-audited-run-stop", - denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v3", activeTurnCancellation: "not-implied-by-completed-provider-turn" }); + expect(cells[0]!.suite.definitionMetadata).toMatchObject({ version: 23, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", naturalSettlementObservationMs: 2000, denialTerminal: "correlated-provider-settlement-and-audited-run-stop", + denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v4", activeTurnCancellation: "not-implied-by-completed-provider-turn" }); expect(suiteDefinitionHash(cells[0]!.suite)).not.toBe(suiteDefinitionHash({ ...cells[0]!.suite, definitionMetadata: { version: 2 } })); expect(cells).toHaveLength(4); expect(new Set(cells.map(c => c.environment.id))).toEqual(new Set(["local", "daytona"])); expect(cells.every(c => c.profile.qualificationCandidate === "copilot" && c.task.expectedRunCount === 1)).toBe(true); @@ -41,9 +41,10 @@ describe("Copilot Product protection integration", () => { request: { ...request, requestId: "permission", method: "session/request_permission" as const, targetRelativePath: request.target!, offeredActions: request.declineOffered ? ["decline"] : [] }, decision: { ...delivered, requestId: "permission", browserRequestId: "permission", action: delivered.outcome === "reject_once" ? "decline" : "accept" }, deliveredDecision: { ...delivered, requestId: "permission", outcome: delivered.outcome! }, - toolResult: { ...failed, status: "failed" as const }, terminal: { runId: "run", turnId: "turn", observedAtMs: 50, status: "cancelled" as const }, settlement: { schema: "paperclip.e2e.copilot-denial-settlement.v3" as const, ...copilotOrigin(request), requestId: "permission", + toolResult: { ...failed, status: "failed" as const }, terminal: { runId: "run", turnId: "turn", observedAtMs: 50, status: "cancelled" as const }, settlement: { schema: "paperclip.e2e.copilot-denial-settlement.v4" as const, ...copilotOrigin(request), requestId: "permission", branch: "provider_cancelled_or_interrupted" as const, providerCancellationTerminalObserved: true, preStop: { schema: "paperclip.e2e.copilot-pre-stop-observation.v2" as const, ...copilotOrigin(request), requestId: "permission", companyId: "company", normalizedSessionId: "normalized", sourceInstanceId: "runner", failedToolSourceSeq: 5, failedToolRowSha256: `sha256:${"a".repeat(64)}`, terminal: null, apiReadCompletedMonotonicNs: "10", cancellationRequestId: "11111111-1111-4111-8111-111111111111" }, stopDispatchMonotonicNs: "20", + stopAcknowledgement: { schema: "paperclip.e2e.copilot-stop-acknowledgement.v1" as const, companyId: "company", runId: "run", issueId: "issue", callerUserId: "local-board", fixtureCorrelationId: "11111111-1111-4111-8111-111111111111", dispatchMonotonicNs: "20", apiResponseObservedMonotonicNs: "21", intentId: "native-cancellation:11111111-1111-4111-8111-111111111111", intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit", responseMetadataSha256: `sha256:${"c".repeat(64)}` }, providerTerminal: { rowSha256: `sha256:${"b".repeat(64)}`, failedToolRowSha256: `sha256:${"a".repeat(64)}`, eventType: "turn.cancelled" as const, normalizedSessionId: "normalized", sourceInstanceId: "runner", requestSourceSeq: 1, resolvedSourceSeq: 2, deliveredSourceSeq: 3, failedNoticeSourceSeq: 4, failedToolSourceSeq: 5, failedToolRowCreatedAtMs: 31, sourceSeq: 6, emittedAtMs: 50, rowCreatedAtMs: 51 }, runStop: { companyId: "company", issueId: "issue", scope: "run" as const, status: "cancelled" as const, issueStatus: "in_progress" as const, intentId: "native-cancellation:11111111-1111-4111-8111-111111111111", intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit", requestedAtMs: 40, recordedAtMs: 41, acknowledgedAtMs: 52, finishedAtMs: 53 } }, cleanup: { observedAtMs: 60, ownedProcessesRemaining: 0 }, nativeAttemptsForTarget: 1, @@ -89,15 +90,18 @@ describe("Copilot Product protection integration", () => { }); it("retains a coverage gap when create/delete occurs before callbacks can arrive", async () => { const root = await mkdtemp("/tmp/pc-denial-gap-"); - const fixture = await createDeniedTargetFixture(root, "denied"); + // Make the pre-mutation version distinct even on filesystems that coalesce + // directory timestamps within one clock tick. Assertions remain immediate. + utimesSync(root, new Date(0), new Date(0)); + const watcher = watchDeniedTarget(root, "denied"), targetPath = join(root, "denied"); try { - writeFileSync(fixture.targetPath, "x"); unlinkSync(fixture.targetPath); - const receipt = fixture.watcher.finish(); + writeFileSync(targetPath, "x"); unlinkSync(targetPath); + const receipt = watcher.finish(); expect(receipt).toMatchObject({ complete: false, targetMutationCount: 0 }); expect(receipt.reasons).toContain("coverage-gap-parent-version-changed"); expect(receipt.finalParent).not.toEqual(receipt.initialParent); - expect(fixture.watcher.finish()).toBe(receipt); - } finally { fixture.watcher.finish(); await rm(root, { recursive: true, force: true }); } + expect(watcher.finish()).toBe(receipt); + } finally { watcher.finish(); await rm(root, { recursive: true, force: true }); } }); it("refuses a preexisting target, symlink parent and ambiguous prompt", async () => { const root = await mkdtemp("/tmp/pc-denial-invalid-"); @@ -134,6 +138,13 @@ describe("Copilot Product protection integration", () => { expect(isPerTurnRunProcess({ ...authority, groupId: 101 }, observed, args)).toBe(false); expect(isPerTurnRunProcess(authority, { ...observed, start: new Date(1_700_000_001_000).toString() }, args)).toBe(false); }); + it("retains lost process identity after a later empty retirement sample", () => { + let sample = { captured: true, live: [100], identityChanged: false }; + sample = retainRunProcessIdentity(sample, { captured: true, live: [], identityChanged: true }); + sample = retainRunProcessIdentity(sample, { captured: true, live: [], identityChanged: false }); + expect(sample).toEqual({ captured: true, live: [], identityChanged: true }); + expect(retainRunProcessIdentity({ captured: true, live: [100] }, { captured: true, live: [] }).identityChanged).toBe(false); + }); it("reaps a real finite child before its provider-style client can exit", async () => { const root = await mkdtemp("/tmp/pc-copilot-command-"); const fixture = await createAttachedCommandFixture(join(root, "marker"), 150); try { @@ -144,6 +155,22 @@ describe("Copilot Product protection integration", () => { expect(await readFile(join(root, "marker"), "utf8")).toBe(fixture.marker); } finally { await fixture.close(); await rm(root, { recursive: true, force: true }); } }); + it("holds only the owned child until the controller releases the observed finish boundary", async () => { + const root = await mkdtemp("/tmp/pc-copilot-gated-command-"); const fixture = await createAttachedCommandFixture(join(root, "marker"), 100, true); + const client = spawn("/bin/sh", ["-c", fixture.command], { stdio: "ignore" }); + const exited = new Promise(resolve => client.once("exit", resolve)); + try { + const deadline = Date.now() + 3000; + while (!fixture.snapshot().childPid && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 20)); + await new Promise(resolve => setTimeout(resolve, 200)); + expect(fixture.snapshot()).toMatchObject({ connections: 1, failure: null, commandExit: null, childGone: false, clientGone: false }); + const release = fixture.releaseAfterFinish(); + expect(() => fixture.releaseAfterFinish()).toThrow(); + expect(await exited).toBe(0); + expect(fixture.snapshot().commandExit!.observedAtMs).toBeGreaterThan(release.releasedAtMs); + expect(await readFile(join(root, "marker"), "utf8")).toBe(fixture.marker); + } finally { await fixture.close(); await exited; await rm(root, { recursive: true, force: true }); } + }); }); it("rejects remote protection before any API access without bootstrap and pre-teardown authority", async () => { diff --git a/tests/runner-e2e/copilot-protection-flow.ts b/tests/runner-e2e/copilot-protection-flow.ts index 659bde01dba..a2d566b2da7 100644 --- a/tests/runner-e2e/copilot-protection-flow.ts +++ b/tests/runner-e2e/copilot-protection-flow.ts @@ -1,14 +1,17 @@ +import { readCopilotStopAcknowledgement, type CopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; +import { copilotPendingPermissionCard } from "./copilot-permission-card.js"; import { createHash, randomBytes, randomUUID } from "node:crypto"; import { join } from "node:path"; import { expect, type Page } from "@playwright/test"; import { pollUntil, type RunnerApi } from "./api.js"; import { collectRunEvents } from "./run-observations.js"; import { createTaskThroughUi } from "./user-actions.js"; -import { onlyCopilotAttachedOperations, readCopilotSemanticCompletion } from "./copilot-semantic-evidence.js"; +import { approveCopilotContextThroughUi, prepareCopilotContext } from "./copilot-context-permission.js"; +import { copilotAttachedFinishReady, copilotFinishAttemptBeforeCommandExit, onlyCopilotAttachedOperations, readCopilotSemanticCompletion } from "./copilot-semantic-evidence.js"; import { copilotOrigin, readCopilotToolEvidence, type CopilotToolNotice } from "./copilot-evidence.js"; -import { createAttachedCommandFixture, createDeniedTargetFixture, bindDeniedTargetPrompt, exists, observeRunProcesses } from "./copilot-local-fixtures.js"; +import { createAttachedCommandFixture, createDeniedTargetFixture, bindDeniedTargetPrompt, exists, observeRunProcesses, retainRunProcessIdentity } from "./copilot-local-fixtures.js"; import { gradeCopilotAttachedSettlement, gradeCopilotDeniedWrite, type CopilotDeniedWriteEvidence } from "./copilot-protection-cases.js"; -import { observeCopilotPreStop, type CopilotPreStopObservation, readCopilotDeniedEdit, copilotDenialSampleCursor, readCopilotDenialSettlement, observeCopilotFixtureCommand, readCopilotRemoteMarkerAfterRetirement, prepareCopilotRemoteAction, assertCopilotRemoteRetirement, assertCopilotRemoteAttached, copilotRemoteDeniedSample, copilotActionNotices, type CopilotRemoteBootstrap, type CopilotRemoteFixture, type CopilotRemoteSnapshot, countCopilotToolOrigins, countCopilotEditOriginsForTarget } from "./copilot-protection-evidence.js"; +import { findCopilotFixtureCommand, observeCopilotPreStop, type CopilotPreStopObservation, readCopilotDeniedEdit, copilotDenialSampleCursor, readCopilotDenialSettlement, observeCopilotFixtureCommand, readCopilotRemoteMarkerAfterRetirement, prepareCopilotRemoteAction, assertCopilotRemoteRetirement, assertCopilotRemoteAttached, copilotRemoteDeniedSample, copilotActionNotices, type CopilotRemoteBootstrap, type CopilotRemoteFixture, type CopilotRemoteSnapshot, countCopilotToolOrigins, countCopilotEditOriginsForTarget } from "./copilot-protection-evidence.js"; import type { LiveFixtureValues } from "./live-fixtures.js"; import type { MatrixExecution } from "./types.js"; type Row = Record; @@ -20,6 +23,7 @@ export async function settleCopilotDeniedRun(input: { load(): Promise<{ events: readonly unknown[]; run: Row; issue: Row; retired: boolean }>; afterDeniedEdit(): Promise; retainPreStop(receipt: CopilotPreStopObservation): Promise; + retainStopAcknowledgement?(receipt: CopilotStopAcknowledgement): Promise; afterSettlement(): Promise; }) { const cancellationRequestId = randomUUID(); @@ -63,20 +67,32 @@ export async function settleCopilotDeniedRun(input: { // this causal boundary, and a later replay must never manufacture it. await input.retainPreStop(preStop); if (Date.now() >= input.deadlineAt) throw new Error("Copilot denial deadline reached before Stop"); - assertBeforeStop(await input.load()); - if (Date.now() >= input.deadlineAt) throw new Error("Copilot denial deadline reached before Stop"); - const stopDispatchMonotonicNs = process.hrtime.bigint().toString(); - const stopped = await input.api.post(`/api/heartbeat-runs/${input.request.runId}/cancel`, { cancellationRequestId }); - if (stopped?.id !== input.request.runId || stopped?.resultJson?.nativeCancellation?.intentId !== `native-cancellation:${cancellationRequestId}`) { - throw new Error("Copilot denial Stop response has a foreign cancellation intent"); + let dispatchState: Awaited>; + while (true) { + dispatchState = await input.load(); + assertBeforeStop(dispatchState); + if (Date.now() >= input.deadlineAt) throw new Error("Copilot denial deadline reached before Stop"); + const observed = observeCopilotPreStop({ events: dispatchState.events, request: input.request, companyId: dispatchState.issue.companyId, cancellationRequestId }); + if (JSON.stringify(observed.terminal) === JSON.stringify(preStop.terminal)) break; + // Completion can arrive while the previous receipt is being persisted. + // Retain that actual pre-dispatch observation, then recheck for an earlier + // Stop after the write rather than discarding the newly observed terminal. + preStop = observed; + await input.retainPreStop(preStop); + if (Date.now() >= input.deadlineAt) throw new Error("Copilot denial deadline reached before Stop"); } + const stopDispatchMonotonicNs = process.hrtime.bigint().toString(); + const stopped = await input.api.post(`/api/heartbeat-runs/${input.request.runId}/cancel`); + const stopAcknowledgement = readCopilotStopAcknowledgement(stopped, + { companyId: preStop.companyId, issueId: dispatchState.issue.id, runId: input.request.runId, callerUserId: "local-board" }, cancellationRequestId, stopDispatchMonotonicNs); + await input.retainStopAcknowledgement?.(stopAcknowledgement); stopSent = true; await poll("correlated provider settlement and retired run", state => { if (!state.retired) return false; - readCopilotDenialSettlement({ ...state, request: input.request, preStop, stopDispatchMonotonicNs }); return true; + readCopilotDenialSettlement({ ...state, request: input.request, preStop, stopDispatchMonotonicNs, stopAcknowledgement }); return true; }); await input.afterSettlement(); - return readCopilotDenialSettlement({ ...await input.load(), request: input.request, preStop, stopDispatchMonotonicNs }); + return readCopilotDenialSettlement({ ...await input.load(), request: input.request, preStop, stopDispatchMonotonicNs, stopAcknowledgement }); } export async function runCopilotProtectionFlow(input: { @@ -94,7 +110,7 @@ export async function runCopilotProtectionFlow(input: { const checks: Check[] = []; let issue: Row = {}, runs: Row[] = [], runEvents: Row[] = []; let notices: CopilotToolNotice[] = []; const processObserver = remote ? undefined : observeRunProcesses(); - let processes: { captured: boolean; live: number[] } = processObserver?.sample() ?? { captured: false, live: [] }; + let processes: { captured: boolean; live: number[]; identityChanged?: boolean } = processObserver?.sample() ?? { captured: false, live: [] }; let remoteFixture: CopilotRemoteFixture | undefined, baseline: CopilotRemoteSnapshot | undefined, sealed: CopilotRemoteSnapshot | undefined; let remoteCommand: { command: string; commandSha256: string } | undefined; const remoteMarker = `${randomBytes(24).toString("hex")}\n`; @@ -120,7 +136,7 @@ export async function runCopilotProtectionFlow(input: { }; const watcher = localTarget?.watcher; const markerPath = join(workspacePath, `copilot-settlement-${nonce}.txt`); - const command = deny || remote ? undefined : await createAttachedCommandFixture(markerPath); + const command = deny || remote ? undefined : await createAttachedCommandFixture(markerPath, 4000, true); const exactCommand = () => remoteCommand ?? command; let watchReceipt: CopilotDeniedWriteEvidence["mutationObservation"] | undefined; const check = (id: string, passed: boolean, detail: string) => { checks.push({ id, passed, detail }); expect(passed, detail).toBe(true); }; @@ -133,7 +149,10 @@ export async function runCopilotProtectionFlow(input: { runEvents = runs[0] ? await collectRunEvents((afterSeq, limit) => api.get(`/api/heartbeat-runs/${runs[0]!.id}/events?afterSeq=${afterSeq}&limit=${limit}`)) : []; notices = runs[0] ? readCopilotToolEvidence(runEvents, runs[0].id) : []; const run = runs[0]; - if (processObserver) processes = processObserver.sample(run?.processPid ? { pid: run.processPid, groupId: run.processGroupId, startedAt: run.processStartedAt, runId: run.id } : undefined); + if (processObserver) { + processes = retainRunProcessIdentity(processes, processObserver.sample(run?.processPid ? { pid: run.processPid, groupId: run.processGroupId, startedAt: run.processStartedAt, runId: run.id } : undefined)); + if (processes.identityChanged) check("owned-process-identity", false, "A captured process identity changed; cleanup cannot be qualified"); + } return { issue, runs, runEvents, notices, processes }; } const wait = (label: string, accept: (state: Awaited>) => boolean) => pollUntil({ label, deadlineAt: input.deadlineAt, load, accept, intervalMs: 200, @@ -148,38 +167,44 @@ export async function runCopilotProtectionFlow(input: { }); if (deny && !remote) { await sample("before-request"); check("target-initially-absent", !fileObservations[0]!.exists, "The exact isolated target is absent before dispatch"); } const prompt = remote ? input.remoteBootstrap!.prompt(nonce) : `${localTarget ? bindDeniedTargetPrompt(execution.task.buildPrompt(nonce), targetName, target) : execution.task.buildPrompt(nonce)}${command ? `\nThe exact supplied command is:\n${command.command}\nDo not inspect or modify fixture code, fabricate its marker, or launch a substitute command.` : ""}`; - await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name, title: execution.task.buildTitle(nonce), prompt, workMode: "standard", projectName: project.name }); - const found = await pollUntil({ label: "browser-created Copilot protection task", deadlineAt: input.deadlineAt, load: async () => (await api.get(`/api/companies/${fixtures.company.id}/issues?limit=100`)).find(r => r.title === execution.task.buildTitle(nonce)), accept: Boolean }); + const createdTask = await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name, title: execution.task.buildTitle(nonce), prompt, workMode: "standard", projectName: project.name, requireExplicitTitle: true }); + const found = await pollUntil({ label: "browser-created Copilot protection task", deadlineAt: input.deadlineAt, load: async () => (await api.get(`/api/companies/${fixtures.company.id}/issues?limit=100`)).find(r => r.id === createdTask.issueId), accept: Boolean }); if (!found) throw new Error("Browser-created task was not found"); issue = found; await page.goto(`/${fixtures.company.issuePrefix}/issues/${issue.identifier ?? issue.id}`); - if (remote) { - await wait("exact remote bootstrap run", state => state.runs.length === 1 && state.runs[0]?.status === "running"); - const bound = await input.remoteBootstrap!.bindAndRelease({ issueId: issue.id, runId: runs[0]!.id, - targets: [deny ? target : `copilot-settlement-${nonce}.txt`], - actionPrompt: async fixture => { - remoteFixture = fixture; - const prepared = await prepareCopilotRemoteAction({ fixture, companyId: fixtures.company.id, environmentId: fixtures.environment.id, runId: runs[0]!.id, - target: deny ? target : `copilot-settlement-${nonce}.txt`, prompt: execution.task.buildPrompt(nonce), ...(deny ? {} : { markerText: remoteMarker }) }); - baseline = prepared.baseline; remoteCommand = prepared.command; - if (deny) { await sample("before-request"); check("target-initially-absent", !fileObservations[0]!.exists, "The actual remote target is absent before action publication"); } - await input.evidence("copilot-remote-baseline.json", baseline); - return prepared.prompt; - } }); - if (bound !== remoteFixture) throw new Error("Remote Copilot fixture changed during publication"); - input.registerBeforeEnvironmentTeardownAssertion!(async () => { - try { - const receipt = await sealRemote(); - if (deny) { - if (copilotRemoteDeniedSample(receipt, baseline!, target, "after-cleanup").exists) throw new Error("Remote denied target exists after retirement"); - } - else { - if (!await readCopilotRemoteMarkerAfterRetirement(remoteFixture!, baseline!, `copilot-settlement-${nonce}.txt`, remoteMarker)) throw new Error("Remote sealed marker changed or disappeared"); - } - await input.evidence("copilot-remote-pre-teardown.json", receipt); - return [{ id: "remote-sealed-retirement", passed: true, detail: "Exact remote run root and descendants retired; retained pre-deletion filesystem receipt validated" }]; - } finally { await remoteFixture!.close(); } - }); - } + await prepareCopilotContext({ + remoteSetup: remote ? async () => { + await wait("exact remote bootstrap run", state => state.runs.length === 1 && state.runs[0]?.status === "running"); + const bound = await input.remoteBootstrap!.bindAndRelease({ issueId: issue.id, runId: runs[0]!.id, + targets: [deny ? target : `copilot-settlement-${nonce}.txt`], + actionPrompt: async fixture => { + remoteFixture = fixture; + const prepared = await prepareCopilotRemoteAction({ fixture, companyId: fixtures.company.id, environmentId: fixtures.environment.id, runId: runs[0]!.id, + target: deny ? target : `copilot-settlement-${nonce}.txt`, prompt: execution.task.buildPrompt(nonce), ...(deny ? {} : { markerText: remoteMarker }) }); + baseline = prepared.baseline; remoteCommand = prepared.command; + if (deny) { await sample("before-request"); check("target-initially-absent", !fileObservations[0]!.exists, "The actual remote target is absent before action publication"); } + await input.evidence("copilot-remote-baseline.json", baseline); + return prepared.prompt; + } }); + if (bound !== remoteFixture) throw new Error("Remote Copilot fixture changed during publication"); + input.registerBeforeEnvironmentTeardownAssertion!(async () => { + try { + const receipt = await sealRemote(); + if (deny) { + if (copilotRemoteDeniedSample(receipt, baseline!, target, "after-cleanup").exists) throw new Error("Remote denied target exists after retirement"); + } + else { + if (!await readCopilotRemoteMarkerAfterRetirement(remoteFixture!, baseline!, `copilot-settlement-${nonce}.txt`, remoteMarker)) throw new Error("Remote sealed marker changed or disappeared"); + } + await input.evidence("copilot-remote-pre-teardown.json", receipt); + return [{ id: "remote-sealed-retirement", passed: true, detail: "Exact remote run root and descendants retired; retained pre-deletion filesystem receipt validated" }]; + } finally { await remoteFixture!.close(); } + }); + } : undefined, + approveContext: async () => { + if (deny) await approveCopilotContextThroughUi({ page, companyId: fixtures.company.id, deadlineAt: input.deadlineAt, + load: async () => { const state = await load(); return { run: state.runs[0] ?? {}, events: state.runEvents }; }, evidence: input.evidence }); + }, + }); if (deny) { await wait("exact native write permission", s => s.notices.some(n => n.stage === "permission_requested" && n.operation === "edit" && n.target === target && n.declineOffered && s.runEvents.some(r => r.eventType === "runtime_request.created" && r.payload?.prpEvent?.payload?.request?.requestId === n.requestId))); const request = notices.find(n => n.stage === "permission_requested" && n.operation === "edit" && n.target === target)!; @@ -190,8 +215,8 @@ export async function runCopilotProtectionFlow(input: { const activeRequests = runEvents.filter(r => r.eventType === "runtime_request.created" && r.payload?.prpEvent?.payload?.request && !retired.has(r.payload.prpEvent.payload.request.requestId)); check("only-one-pending-native-request", activeRequests.length === 1, "Only the exact denied edit is awaiting a decision"); await sample("pending"); await page.reload(); - const card = page.getByTestId("task-chat-runtime-request").filter({ visible: true }); - await expect(card).toHaveCount(1); await input.capture("permission-pending", "Copilot native write awaiting denial", "permission-pending.png"); + const card = copilotPendingPermissionCard(page, target); + await expect(card).toHaveCount(1); await expect(card).toContainText(target); await input.capture("permission-pending", "Copilot native write awaiting denial", "permission-pending.png"); const url = `/api/heartbeat-runs/${request.runId}/runtime-requests/${encodeURIComponent(request.requestId!)}/resolve`; const sent = page.waitForRequest(r => new URL(r.url()).pathname === url && r.method() === "POST"); const clickedAtMs = Date.now(); await card.getByRole("button", { name: "Deny", exact: true }).click(); @@ -205,6 +230,7 @@ export async function runCopilotProtectionFlow(input: { }, afterDeniedEdit: () => sample("after-decision"), retainPreStop: receipt => input.evidence("copilot-pre-stop-observation.json", receipt), + retainStopAcknowledgement: receipt => input.evidence("copilot-stop-acknowledgement.json", receipt), afterSettlement: async () => { if (remote) await sealRemote(); await sample("terminal"); await new Promise(resolve => setTimeout(resolve, 100)); await load(); await sample("after-cleanup"); @@ -229,8 +255,17 @@ export async function runCopilotProtectionFlow(input: { await input.evidence("copilot-denial-proof.json", { evidence, processes, notices }); const grade = gradeCopilotDeniedWrite(evidence); check("denial-without-side-effects", grade.passed, grade.failures.join(", ") || "Exact browser denial, explicit cancellation and absence through process cleanup"); check("negative-task-unfinished", issue.status === "in_progress", "The negative test does not claim the task is done"); - check("no-extra-native-operation", countCopilotToolOrigins(copilotActionNotices(notices, notices.find(n => n.stage === "tool" && n.toolCallId === request.toolCallId)!, remoteFixture ? { actionFile: remoteFixture.actionFile, events: runEvents } : undefined)) === 1, "No alternate native edit, command or delegated operation is permitted"); + check("no-extra-native-operation", countCopilotToolOrigins(copilotActionNotices(notices, notices.find(n => n.stage === "tool" && n.toolCallId === request.toolCallId)!, remoteFixture ? { actionFile: remoteFixture.actionFile, events: runEvents } : undefined, runEvents)) === 1, "No alternate native edit, command or delegated operation is permitted"); } else { + let finishOrigin: CopilotToolNotice | undefined; + await wait("native finish while the exact attached command is live", state => { + const matched = findCopilotFixtureCommand(state.notices, exactCommand()!.command); + if (!matched) return false; + finishOrigin = copilotAttachedFinishReady(state.notices, matched.call); + return Boolean(finishOrigin); + }); + const release = remote ? await remoteFixture!.releaseAttachedCommand() : command!.releaseAfterFinish(); + await input.evidence("copilot-attached-release.json", { finishOrigin, release, source: "fixture controller releases only its own finite child after the actual native finish receipt; final grading unchanged" }); await wait("attached command and task settlement", s => s.issue.status === "done" && s.runs[0]?.status === "succeeded" && (remote || (s.processes.captured && s.processes.live.length === 0))); // Preserve independent local proof before any command matcher can abort. const { external: localExternal, markerMatches: localMarkerMatches, afterCleanupMarkerMatches: localAfterCleanupMarkerMatches, matched } = @@ -241,10 +276,10 @@ export async function runCopilotProtectionFlow(input: { const call = matched?.call; check("single-exact-command", Boolean(matched), "Exactly one native execution matches the fixture command with at most eight leading ASCII SPACE/TAB bytes"); const semantic = readCopilotSemanticCompletion(runEvents, { companyId: fixtures.company.id, runId: call!.runId, turnId: call!.turnId, - nativeSessionId: call!.sessionId, command: call!, summary: execution.task.buildVisibleMarker(nonce) }); + nativeSessionId: call!.sessionId, command: call!, summary: execution.task.buildVisibleMarker(nonce), requireContextRead: true }); await input.evidence("copilot-semantic-completion.json", semantic); check("accepted-canonical-completion", true, "One exact native finish receipt matches the proposed and control-plane accepted result; transport success alone is insufficient"); - check("no-extra-native-operation", onlyCopilotAttachedOperations(copilotActionNotices(notices, call!, remoteFixture ? { actionFile: remoteFixture.actionFile, events: runEvents } : undefined), call!, semantic), "Only attested setup reads, the exact attached command/result, and one authoritatively correlated accepted finish are allowed"); + check("no-extra-native-operation", onlyCopilotAttachedOperations(copilotActionNotices(notices, call!, remoteFixture ? { actionFile: remoteFixture.actionFile, events: runEvents } : undefined, runEvents), call!, semantic), "Only attested context/setup reads, the exact attached command/result, and one authoritatively correlated accepted finish are allowed"); const started = notices.find(n => n.toolCallId === call!.toolCallId && n.shellState === "started"); const result = notices.find(n => n.commandToolCallId === call!.toolCallId && n.shellState === "completed"); const terminal = runEvents.find(r => r.eventType === "turn.completed" && r.payload?.prpEvent?.turnId === call!.turnId)?.payload.prpEvent; @@ -252,6 +287,7 @@ export async function runCopilotProtectionFlow(input: { const remoteAttached = remote ? assertCopilotRemoteAttached(sealed!, baseline!, terminal ? Date.parse(terminal.emittedAt) : NaN) : undefined; const external = remoteAttached ? { ...remoteAttached, childGone: true, clientGone: true, commandExit: { ...remoteAttached.commandExit!, ownedProcessIdentityVerified: true, commandSha256: exactCommand()!.commandSha256 } } : localExternal!; + check("finish-attempt-before-command-exit", copilotFinishAttemptBeforeCommandExit(semantic, external.commandExit?.observedAtMs ?? NaN), "The correlated finish attempt precedes the independently observed command exit"); check("trusted-command-exit", !external.failure && external.connections === 1 && external.childGone && external.clientGone, "Fixed controller-owned child exited and its native client is gone"); const markerMatches = remote ? sealed!.targets[`copilot-settlement-${nonce}.txt`]?.sha256 === `sha256:${createHash("sha256").update(remoteMarker).digest("hex")}` : localMarkerMatches!; check("native-client-before-terminal", Boolean(terminal) && external.clientExitedAtMs !== null && external.clientExitedAtMs < Date.parse(terminal.emittedAt), "Independent PID/start observation confirms native client retirement before turn completion"); diff --git a/tests/runner-e2e/copilot-protection-settlement.test.ts b/tests/runner-e2e/copilot-protection-settlement.test.ts index 3b4e910e2f9..71d63bfcf27 100644 --- a/tests/runner-e2e/copilot-protection-settlement.test.ts +++ b/tests/runner-e2e/copilot-protection-settlement.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { applyMainlineStopMetadata, stopAcknowledgementForTest } from "./copilot-stop-test-fixtures.js"; import { readCopilotToolEvidence } from "./copilot-evidence.js"; import { observeCopilotPreStop, copilotDenialSampleCursor, readCopilotDenialSettlement } from "./copilot-protection-evidence.js"; import { settleCopilotDeniedRun } from "./copilot-protection-flow.js"; @@ -30,18 +31,19 @@ function fixture(eventType = "turn.completed") { dispatchState: "acknowledged", reasonCode: "cancellation_run_only", effects: ["release_run_resources"], intentId: "native-cancellation:11111111-1111-4111-8111-111111111111", intentAuditId: "intent-audit", acknowledgementAuditId: "ack-audit", recordedAt: date(901), acknowledgedAt: date(909), } } }; + applyMainlineStopMetadata(run); const request = readCopilotToolEvidence(events, "run")[0]!; const preStop = observeCopilotPreStop({ events, request, companyId: "company", cancellationRequestId: "11111111-1111-4111-8111-111111111111" }); - return { events, request, run, issue: { id: "issue", companyId: "company", status: "in_progress" }, preStop, stopDispatchMonotonicNs: process.hrtime.bigint().toString() }; + const stopDispatchMonotonicNs = process.hrtime.bigint().toString(); + const stopAcknowledgement = stopAcknowledgementForTest(run, preStop.cancellationRequestId, stopDispatchMonotonicNs); + return { events, request, run, issue: { id: "issue", companyId: "company", status: "in_progress" }, preStop, stopDispatchMonotonicNs, stopAcknowledgement }; } const terminal = (f: ReturnType) => f.events[4]!.payload.prpEvent; const liveRun = (f: ReturnType) => ({ ...f.run, status: "running", resultJson: {} }); function stopPost(f: ReturnType, callback = () => {}) { - return vi.fn(async (_path: string, body: { cancellationRequestId: string }) => { + return vi.fn(async (_path: string) => { callback(); - f.run.resultJson.startupCancellation.cancellationRequestId = body.cancellationRequestId; - f.run.resultJson.nativeCancellation.intentId = `native-cancellation:${body.cancellationRequestId}`; return f.run; }); } @@ -54,6 +56,7 @@ describe("Copilot denial provider settlement and separate audited run Stop", () // Native/provider clock skew is irrelevant to the operator causal boundary. terminal(f).emittedAt = date(999); f.preStop = observeCopilotPreStop({ ...f, companyId: "company", cancellationRequestId: "11111111-1111-4111-8111-111111111111" }); f.stopDispatchMonotonicNs = process.hrtime.bigint().toString(); + f.stopAcknowledgement = stopAcknowledgementForTest(f.run, f.preStop.cancellationRequestId, f.stopDispatchMonotonicNs); expect(readCopilotDenialSettlement(f).branch).toBe("provider_completed_observed_before_stop"); }); it.each(["turn.cancelled", "turn.interrupted"])("retains the distinct observed %s branch", type => { @@ -66,6 +69,7 @@ describe("Copilot denial provider settlement and separate audited run Stop", () it.each(["equal", "later"])("accepts observed pre-Stop completion with %s transaction time, without ordering it against ack", kind => { const f = fixture(); f.events[4]!.createdAt = date(kind === "equal" ? 909 : 910); f.preStop = observeCopilotPreStop({ ...f, companyId: "company", cancellationRequestId: "11111111-1111-4111-8111-111111111111" }); f.stopDispatchMonotonicNs = process.hrtime.bigint().toString(); + f.stopAcknowledgement = stopAcknowledgementForTest(f.run, f.preStop.cancellationRequestId, f.stopDispatchMonotonicNs); expect(readCopilotDenialSettlement(f).branch).toBe("provider_completed_observed_before_stop"); }); it("rejects late normal completion even when its transaction began before Stop ack", () => { @@ -160,6 +164,37 @@ describe("Copilot denial provider settlement and separate audited run Stop", () await vi.advanceTimersByTimeAsync(200); expect((await result).branch).toBe("provider_cancelled_or_interrupted"); expect(afterSettlement).toHaveBeenCalledOnce(); } finally { vi.restoreAllMocks(); vi.useRealTimers(); } }); + it("retains completion first observed after the initial receipt write before dispatching Stop", async () => { + vi.useFakeTimers(); let mono = 100n; vi.spyOn(process.hrtime, "bigint").mockImplementation(() => ++mono); + try { + const f = fixture(); let completionVisible = false; + const receipts: Array> = []; + const post = stopPost(f, () => { expect(receipts).toHaveLength(2); expect(receipts[1]!.terminal?.eventType).toBe("turn.completed"); }); + const result = settleCopilotDeniedRun({ api: { post } as any, request: f.request, deadlineAt: Date.now() + 5000, + load: async () => ({ ...f, events: completionVisible ? f.events : f.events.filter((_, i) => i !== 4), run: post.mock.calls.length ? f.run : liveRun(f), retired: true }), + afterDeniedEdit: async () => {}, retainPreStop: async receipt => { receipts.push(receipt); completionVisible = true; }, afterSettlement: async () => {} }); + await vi.advanceTimersByTimeAsync(2000); + const settled = await result; + expect(settled.branch).toBe("provider_completed_observed_before_stop"); + expect(settled.preStop).toEqual(receipts[1]); expect(receipts[0]!.terminal).toBeNull(); expect(post).toHaveBeenCalledOnce(); + } finally { vi.restoreAllMocks(); vi.useRealTimers(); } + }); + it.each(["earlier-stop", "retention-failed"])("refuses dispatch when refreshed terminal retention encounters %s", async kind => { + vi.useFakeTimers(); let mono = 100n; vi.spyOn(process.hrtime, "bigint").mockImplementation(() => ++mono); + try { + const f = fixture(); let completionVisible = false, earlierStop = false, writes = 0; + const post = stopPost(f); + const result = settleCopilotDeniedRun({ api: { post } as any, request: f.request, deadlineAt: Date.now() + 5000, + load: async () => ({ ...f, events: completionVisible ? f.events : f.events.filter((_, i) => i !== 4), run: earlierStop ? f.run : liveRun(f), retired: true }), + afterDeniedEdit: async () => {}, retainPreStop: async () => { + writes++; completionVisible = true; + if (writes === 2) { if (kind === "retention-failed") throw new Error("refreshed receipt write failed"); earlierStop = true; } + }, afterSettlement: async () => {} }); + const rejected = expect(result).rejects.toThrow(kind === "earlier-stop" ? "earlier Stop" : "refreshed receipt write failed"); + await vi.advanceTimersByTimeAsync(2000); await rejected; + expect(writes).toBe(2); expect(post).not.toHaveBeenCalled(); + } finally { vi.restoreAllMocks(); vi.useRealTimers(); } + }); it("rejects normal completion first returned after Stop even with an earlier transaction timestamp", async () => { vi.useFakeTimers(); let mono = 100n; vi.spyOn(process.hrtime, "bigint").mockImplementation(() => ++mono); try { @@ -236,14 +271,14 @@ describe("Copilot denial provider settlement and separate audited run Stop", () })).rejects.toThrow("earlier Stop"); expect(post).not.toHaveBeenCalled(); }); - it.each(["conflict", "foreign-response"])("cannot pass a Stop racing after the last API read: %s", async kind => { - const f = fixture(); const post = vi.fn(async (_path: string, _body: { cancellationRequestId: string }) => { if (kind === "conflict") throw new Error("409 cancellation intent conflict"); return f.run; }); + it.each(["conflict", "foreign-response"])("rejects a conflicting or foreign-caller Stop API response: %s", async kind => { + const f = fixture(); const post = vi.fn(async (_path: string) => { if (kind === "conflict") throw new Error("409 cancellation intent conflict"); f.run.resultJson.cancelledByUserId = "foreign-caller"; return f.run; }); const sampled = vi.fn(async () => {}); await expect(settleCopilotDeniedRun({ api: { post } as any, request: f.request, deadlineAt: Date.now() + 1000, load: async () => ({ ...f, run: liveRun(f), retired: false }), afterDeniedEdit: async () => {}, retainPreStop: async () => {}, afterSettlement: sampled, - })).rejects.toThrow(kind === "conflict" ? "409" : "foreign cancellation intent"); + })).rejects.toThrow(kind === "conflict" ? "409" : "Stop acknowledgement"); expect(post).toHaveBeenCalledOnce(); expect(sampled).not.toHaveBeenCalled(); - expect(post.mock.calls[0]?.[1]).toMatchObject({ cancellationRequestId: expect.stringMatching(/^[a-f0-9-]{36}$/) }); + expect(post.mock.calls[0]).toEqual(["/api/heartbeat-runs/run/cancel"]); }); }); diff --git a/tests/runner-e2e/copilot-protection-tasks.ts b/tests/runner-e2e/copilot-protection-tasks.ts index 842beb8d6e1..808a79eb789 100644 --- a/tests/runner-e2e/copilot-protection-tasks.ts +++ b/tests/runner-e2e/copilot-protection-tasks.ts @@ -1,5 +1,6 @@ import type { RunnerTaskFixture } from "./types.js"; import { copilotProtectionCases } from "./copilot-protection-cases.js"; +import { contextOrientation } from "./copilot-active-stop-tasks.js"; export const copilotProtectionTasks: readonly RunnerTaskFixture[] = copilotProtectionCases.map(c => ({ id: c.id, label: c.id === "native-permission-deny-write" ? "Deny one native write, then stop safely" : "Attached command settles before completion", groups: [], workMode: "standard", flow: "copilot_protection", expectedRunCount: 1, @@ -7,7 +8,7 @@ export const copilotProtectionTasks: readonly RunnerTaskFixture[] = copilotProte expectedTerminalState: { issue: c.id === "native-permission-deny-write" ? "in_progress" : "done", run: c.id === "native-permission-deny-write" ? "cancelled" : "succeeded" }, buildTitle: nonce => `Copilot ${c.id} ${nonce}`, buildVisibleMarker: nonce => `COPILOT-PROTECTION-${c.id}-${nonce}`, - buildPrompt: nonce => c.id === "native-permission-deny-write" ? c.prompt(nonce) : [ + buildPrompt: nonce => c.id === "native-permission-deny-write" ? contextOrientation + c.prompt(nonce) : [ "Run the supplied exact command once using native bash with mode async and detach false.", "After starting it, attempt to finish immediately without calling read_bash or another waiting tool. The attached-process runtime must settle the command before ending the turn.", "Do not modify fixture code, manufacture its result, or start another command.", diff --git a/tests/runner-e2e/copilot-provider-death.test.ts b/tests/runner-e2e/copilot-provider-death.test.ts new file mode 100644 index 00000000000..119c4ce8bef --- /dev/null +++ b/tests/runner-e2e/copilot-provider-death.test.ts @@ -0,0 +1,80 @@ +import { createHash } from "node:crypto"; +import { bootstrapReadExecutionId } from "./native-bootstrap-read-proof.js"; +import { describe, expect, it } from "vitest"; +import { canonicalRemoteCopilotCommand, copilotDeathArguments, selectOwnedCopilotProcess } from "./copilot-provider-death.js"; + +describe("owned Copilot provider death selector", () => { + const root = { pid: 10, parent: 1 }, sidecar = { pid: 11, parent: 10 }, native = { pid: 12, parent: 11 }; + const argv = ["/private/paperclip-acpx-native-owned/distribution/copilot", ...copilotDeathArguments]; + it("selects only the native child of the live owned run", () => { + expect(selectOwnedCopilotProcess(10, [root, sidecar, native], [10, 11, 12], new Map([[12, argv]]))).toEqual(native); + }); + it.each([3, 7])("recognizes a verified inherited Linux descriptor %i", fd => { + const command = [`/proc/self/fd/${fd}`, ...copilotDeathArguments]; + const canonical = canonicalRemoteCopilotCommand(command, argv[0]!, { dev: "1", ino: "2" }, { path: argv[0]!, dev: "1", ino: "2" }); + expect(selectOwnedCopilotProcess(10, [root, sidecar, native], [10, 11, 12], new Map([[12, canonical]]))).toEqual(native); + }); + it("refuses descriptor aliases without matching executable identity", () => { + for (const [command, executable, descriptor] of [ + [["/proc/self/fd/8", ...copilotDeathArguments], argv[0], { path: argv[0], dev: "1", ino: "2" }], + [["/proc/self/fd/7", ...copilotDeathArguments], "/usr/bin/copilot", { path: "/usr/bin/copilot", dev: "1", ino: "2" }], + [["/proc/self/fd/7", ...copilotDeathArguments], argv[0], undefined], + [["/proc/self/fd/7", ...copilotDeathArguments], argv[0], { path: "/foreign/copilot", dev: "1", ino: "2" }], + [["/proc/self/fd/7", ...copilotDeathArguments], argv[0], { path: argv[0], dev: "2", ino: "2" }], + [["/proc/self/fd/7", ...copilotDeathArguments], argv[0], { path: argv[0], dev: "1", ino: "3" }], + ] as const) { + const canonical = canonicalRemoteCopilotCommand(command, executable!, { dev: "1", ino: "2" }, descriptor as any); + expect(() => selectOwnedCopilotProcess(10, [root, sidecar, native], [10, 11, 12], new Map([[12, canonical]]))).toThrow(); + } + }); + it("refuses foreign, retired, ambiguous and caller-selected processes", () => { + const commands = new Map([[12, argv]]); + expect(() => selectOwnedCopilotProcess(10, [root, sidecar, { ...native, parent: 99 }], [10, 11, 12], commands)).toThrow(); + expect(() => selectOwnedCopilotProcess(10, [root, sidecar, native], [11, 12], commands)).toThrow(); + expect(() => selectOwnedCopilotProcess(10, [root, sidecar, native], [10, 11], commands)).toThrow(); + expect(() => selectOwnedCopilotProcess(10, [root, sidecar, native, { pid: 13, parent: 11 }], [10, 11, 12, 13], new Map([[12, argv], [13, argv]]))).toThrow(); + for (const invalid of [["/usr/bin/copilot", ...copilotDeathArguments], [...argv, "--allow-all"], [argv[0]!, "--acp"]]) expect(() => selectOwnedCopilotProcess(10, [root, sidecar, native], [10, 11, 12], new Map([[12, invalid]]))).toThrow(); + }); +}); + +import { assertCopilotProviderDeath } from "./copilot-provider-death.js"; +import type { ActiveStopPending } from "./copilot-active-stop-evidence.js"; + +describe("provider-death independent evidence", () => { + const pending = { scope: { companyId: "company", runId: "run", issueId: "issue", provider: "copilot", target: "target.txt" }, requestId: "request", toolCallId: "tool", turnId: "turn", normalizedSessionId: "session" } as ActiveStopPending; + const row = (seq: number, eventType: string, payload: Record) => ({ companyId: "company", runId: "run", seq, eventType, protocolSchemaVersion: 1, payload: { prpEvent: { schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceKind: "runner", eventType, runId: "run", turnId: "turn", normalizedSessionId: "session", sourceInstanceId: "source", sourceSeq: seq, sourceEventId: `source:run:${seq}`, emittedAt: "2026-10-03T00:00:00Z", payload } } }); + const notice = row(1, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", provenance: { sessionId: "native-session", turnId: "turn", eventType: "permission_requested", method: "session/request_permission" }, details: Object.entries({ stage: "permission_requested", toolCallId: "tool", requestId: "request", operation: "edit", target: "target.txt", declineOffered: "true" }).map(([name, value]) => ({ name, value })) }); + const facts = () => ({ pending, run: { id: "run", companyId: "company", nativeIssueId: "issue", runtimeMode: "native", status: "failed" }, issue: { id: "issue", status: "blocked" }, events: [notice, row(2, "runtime_request.expired", { requestId: "request", turnId: "turn", requestKind: "permission_approval", reason: "provider_exit" }), row(3, "turn.failed", { error: { code: "AGENT_DISCONNECTED", message: "Provider exited" } })] }); + it("accepts callback expiry and an unfinished failed task with no replay", () => { + expect(assertCopilotProviderDeath(facts())).toMatchObject({ expired: true, mutationReplay: false }); + }); + it("exempts only an exactly attested completed remote instruction-file read", () => { + const actionFile = `.paperclip-eval-action-${"a".repeat(36)}.txt`, toolCallId = "instruction-read"; + const native = (seq: number, status: string) => row(seq, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", provenance: { sessionId: "native-session", turnId: "turn", eventType: "tool", method: "session/update" }, details: Object.entries({ stage: "tool", toolCallId, operation: "read", status, readTargetSha256: `sha256:${createHash("sha256").update(actionFile).digest("hex")}` }).map(([name, value]) => ({ name, value })) }); + const canonical = (seq: number, eventType: string, status: string) => row(seq, eventType, { schema: "paperclip.tool.execution.v1", transport: "builtin", operation: "read", target: actionFile, executionId: bootstrapReadExecutionId(toolCallId), status }); + const state = facts(); + const edit = row(5, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", provenance: { sessionId: "native-session", turnId: "turn", eventType: "tool", method: "session/update" }, details: Object.entries({ stage: "tool", toolCallId: pending.toolCallId, operation: "edit", status: "pending" }).map(([name, value]) => ({ name, value })) }); + state.events = [native(1, "pending"), canonical(2, "tool.execution.started", "running"), native(3, "completed"), canonical(4, "tool.execution.completed", "completed"), edit, row(6, notice.eventType, notice.payload.prpEvent.payload), row(7, "runtime_request.expired", { requestId: "request", turnId: "turn", requestKind: "permission_approval", reason: "provider_exit" }), row(8, "turn.failed", { error: { code: "AGENT_DISCONNECTED", message: "Provider exited" } })]; + expect(assertCopilotProviderDeath({ ...state, bootstrap: { actionFile, events: state.events } })).toMatchObject({ expired: true }); + expect(() => assertCopilotProviderDeath(state)).toThrow(/operation/); + expect(() => assertCopilotProviderDeath({ ...state, bootstrap: { actionFile: actionFile.replace("a", "b"), events: state.events } })).toThrow(/bootstrap read/); + }); + it.each(["execute", "read", "edit"])("refuses an additional %s operation after provider death", operation => { + const replay = facts(); + const extra = row(4, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", provenance: { sessionId: "native-session", turnId: "turn", eventType: "tool", method: "session/update" }, details: Object.entries({ stage: "tool", toolCallId: "extra-tool", operation, status: "completed" }).map(([name, value]) => ({ name, value })) }); + replay.events.push(extra); + expect(() => assertCopilotProviderDeath(replay)).toThrow(/replayed or completed an operation/); + }); + it("refuses an additional semantic operation without a file mutation", () => { + const replay = facts(); + replay.events.push(row(4, "provider.notice.recorded", { schema: "paperclip.provider.notice.v1", scope: "turn", category: "copilot_tool_evidence_v1", provenance: { sessionId: "native-session", turnId: "turn", eventType: "tool", method: "session/update" }, details: Object.entries({ stage: "tool", toolCallId: "semantic-extra", status: "completed", semanticOperationId: "issue.get", semanticCallIdentitySha256: "a".repeat(64), semanticInputSha256: "b".repeat(64), semanticNormalizedInputSha256: "null", semanticResultSha256: "c".repeat(64), semanticOutcome: "returned" }).map(([name, value]) => ({ name, value })) })); + expect(() => assertCopilotProviderDeath(replay)).toThrow(/replayed or completed an operation/); + }); + it("refuses missing expiry, stale resolution, success and foreign evidence", () => { + const missing = facts(); missing.events.splice(1, 1); expect(() => assertCopilotProviderDeath(missing)).toThrow(); + const resolved = facts(); resolved.events.push(row(4, "runtime_request.resolved", { requestId: "request" })); expect(() => assertCopilotProviderDeath(resolved)).toThrow(); + const succeeded = facts(); succeeded.run.status = "succeeded"; expect(() => assertCopilotProviderDeath(succeeded)).toThrow(); + const done = facts(); done.issue.status = "done"; expect(() => assertCopilotProviderDeath(done)).toThrow(); + const foreign = facts(); foreign.events[0]!.companyId = "foreign"; expect(() => assertCopilotProviderDeath(foreign)).toThrow(); + }); +}); diff --git a/tests/runner-e2e/copilot-provider-death.ts b/tests/runner-e2e/copilot-provider-death.ts new file mode 100644 index 00000000000..0900c613f36 --- /dev/null +++ b/tests/runner-e2e/copilot-provider-death.ts @@ -0,0 +1,71 @@ +import { createHash } from "node:crypto"; +import { COPILOT_LAUNCH_ARGUMENTS } from "../../packages/paperclip-runner/src/drivers/acpx/copilot-profile.js"; +import { isValidNativePrpEnvelope } from "./native-event-envelope.js"; +import { copilotEditOriginForPermission, readCopilotToolEvidence } from "./copilot-evidence.js"; +import { readCopilotContextRead } from "./copilot-context-evidence.js"; +import { copilotActionNotices } from "./copilot-protection-evidence.js"; +import type { BootstrapReadProof } from "./native-bootstrap-read-proof.js"; +import type { ActiveStopPending } from "./copilot-active-stop-evidence.js"; + +/** Closed selector shared by local and remote observers. It accepts no caller PID. + * Only a live native executable under an already owned run root is eligible. */ +export function selectOwnedCopilotProcess( + rootPid: number, journal: readonly T[], live: readonly number[], argv: ReadonlyMap, fixedArguments: readonly string[] = COPILOT_LAUNCH_ARGUMENTS, +): T { + const owned = new Map(journal.map(row => [row.pid, row])); + if (!owned.has(rootPid) || !live.includes(rootPid)) throw new Error("Copilot death requires a live owned run root"); + const candidates = journal.filter(row => { + if (row.pid === rootPid || !live.includes(row.pid)) return false; + const command = argv.get(row.pid); + if (!command || command.length !== fixedArguments.length + 1 || !/^\/[^\u0000-\u0020]*\/paperclip-acpx-native-[A-Za-z0-9_-]+\/distribution\/copilot$/u.test(command[0] ?? "") || !fixedArguments.every((arg, i) => command[i + 1] === arg)) return false; + const seen = new Set(); let parent = row.parent; + while (parent !== rootPid) { + if (seen.has(parent) || !live.includes(parent)) return false; + seen.add(parent); const ancestor = owned.get(parent); if (!ancestor) return false; parent = ancestor.parent; + } + return true; + }); + if (candidates.length !== 1) throw new Error("Copilot death requires exactly one owned native process"); + return candidates[0]!; +} +export const copilotDeathArguments = [...COPILOT_LAUNCH_ARGUMENTS]; + +/** Linux launches the held, verified executable descriptor rather than reopening + * its name. Canonicalize only that closed launcher shape, after the observer + * proves the live executable and inherited descriptor name the same inode. */ +export function canonicalRemoteCopilotCommand( + command: readonly string[], executable: string, + executableIdentity: { dev: string; ino: string }, + descriptor?: { path: string; dev: string; ino: string }, +): readonly string[] { + if (!/^\/[^\u0000-\u0020]*\/paperclip-acpx-native-[A-Za-z0-9_-]+\/distribution\/copilot$/u.test(executable)) return command; + if (command[0] === executable) return command; + if (!/^\/proc\/self\/fd\/(?:3|7)$/u.test(command[0] ?? "") || !descriptor + || descriptor.path !== executable || descriptor.dev !== executableIdentity.dev || descriptor.ino !== executableIdentity.ino) return command; + return [executable, ...command.slice(1)]; +} +export function copilotDeathCommandDigest(argv: readonly string[]) { + return `sha256:${createHash("sha256").update(JSON.stringify(argv)).digest("hex")}`; +} + +/** A failed run alone is insufficient: the pending callback must expire and its + * native operation must never complete or appear again after the stale answer. */ +export function assertCopilotProviderDeath(input: { pending: ActiveStopPending; run: Record; issue: Record; events: readonly any[]; bootstrap?: BootstrapReadProof }) { + const { pending, run, issue } = input; + if (run.id !== pending.scope.runId || run.companyId !== pending.scope.companyId || issue.id !== pending.scope.issueId || issue.status !== "blocked" || run.nativeIssueId !== pending.scope.issueId || run.runtimeMode !== "native" || run.status !== "failed") throw new Error("Provider death requires one failed unfinished run"); + const rows = input.events.filter(row => row.payload?.prpEvent); + if (!rows.length || rows.some(row => row.runId !== run.id || row.companyId !== run.companyId || !isValidNativePrpEnvelope(row.payload.prpEvent, row.protocolSchemaVersion))) throw new Error("Provider death requires canonical run evidence"); + const events = rows.map(row => row.payload.prpEvent); + const expiry = events.filter(event => event.eventType === "runtime_request.expired" && event.payload?.requestId === pending.requestId && event.turnId === pending.turnId && event.normalizedSessionId === pending.normalizedSessionId); + if (expiry.length !== 1 || events.some(event => event.eventType === "runtime_request.resolved" && event.payload?.requestId === pending.requestId) || events.some(event => event.eventType === "task.completed" || event.eventType === "turn.completed")) throw new Error("Provider death did not expire the unanswered callback"); + const allNotices = readCopilotToolEvidence(input.events, run.id); + const target = copilotEditOriginForPermission(allNotices, pending.scope.target, pending.toolCallId); + if (pending.scope.requireContextRead && !target) throw new Error("Provider death lacks the exact mutation origin"); + const context = target ? readCopilotContextRead(input.events, target, pending.scope.requireContextRead) : undefined; + const notices = target ? copilotActionNotices(allNotices, target, input.bootstrap, context ? input.events : undefined) : allNotices; + if (notices.filter(n => n.stage === "permission_requested").length !== 1 || notices.some(n => n.stage === "permission_delivered" && ["allow_once", "allow_always"].includes(n.outcome ?? "")) || notices.some(n => n.stage === "tool" && (n.toolCallId !== pending.toolCallId || (n.operation !== undefined && n.operation !== "edit") || n.status === "completed"))) throw new Error("Provider death replayed or completed an operation"); + const expirySeq = rows.find(row => row.payload.prpEvent === expiry[0])!.seq; + if (allNotices.some(n => n.stage === "tool" && n.seq >= expirySeq && !(n.toolCallId === pending.toolCallId && n.operation === "edit" && n.status === "failed"))) throw new Error("Provider death executed an operation after callback expiry"); + if (!events.some(event => ["turn.failed", "turn.interrupted"].includes(event.eventType) && event.turnId === pending.turnId)) throw new Error("Provider death lacks a failed provider turn"); + return { schema: "paperclip.e2e.copilot-provider-death.v1", runId: run.id, requestId: pending.requestId, expired: true, mutationReplay: false }; +} diff --git a/tests/runner-e2e/copilot-semantic-evidence.test.ts b/tests/runner-e2e/copilot-semantic-evidence.test.ts index 7abf1dc019a..4c04c023ac0 100644 --- a/tests/runner-e2e/copilot-semantic-evidence.test.ts +++ b/tests/runner-e2e/copilot-semantic-evidence.test.ts @@ -1,8 +1,9 @@ import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { describe, expect, it } from "vitest"; -import { onlyCopilotAttachedOperations, readCopilotSemanticCompletion } from "./copilot-semantic-evidence.js"; +import { copilotAttachedFinishReady, copilotFinishAttemptBeforeCommandExit, onlyCopilotAttachedOperations, readCopilotSemanticCompletion } from "./copilot-semantic-evidence.js"; import { readCopilotToolEvidence } from "./copilot-evidence.js"; +import { copilotActionNotices, findCopilotFixtureCommand } from "./copilot-protection-evidence.js"; import { validatePrpStructuredRunResult } from "../../packages/paperclip-runner/src/protocol/replay-contract.js"; import { runnerSuites, suiteDefinitionHash } from "./catalog.js"; const hash = (v: string) => createHash("sha256").update(v).digest("hex"); @@ -30,10 +31,47 @@ function fixture() { const frame = (r: any) => r.payload.prpEvent; function setField(r: any, key: string, value: string) { frame(r).payload.details.find((d: any) => d.name === key).value = value; } describe("Copilot semantic completion public-event oracle", () => { - it("versions the actual Copilot suite oracle without changing denial settlement", () => { + it("attests the retained v15 context read before independently accepting its exact finish", async () => { + const retained = JSON.parse(await readFile(new URL("./fixtures/copilot-context-attached-completion-v15.json", import.meta.url), "utf8")); + const notices = readCopilotToolEvidence(retained.rows, retained.rows[0].runId); + const command = notices.find(n => n.operation === "execute" && n.status === "pending")!; + const proof = readCopilotSemanticCompletion(retained.rows, { companyId: retained.rows[0].companyId, + runId: command.runId, turnId: command.turnId, nativeSessionId: command.sessionId, command, + summary: retained.summary, requireContextRead: true }); + expect(proof).toMatchObject({ schema: "paperclip.e2e.copilot-semantic-completion.v4", contextRead: { canonicalSeqs: expect.any(Array) } }); + expect(proof.contextRead!.completedSeq).toBeLessThan(command.seq); + expect(onlyCopilotAttachedOperations(copilotActionNotices(notices, command, undefined, retained.rows), command, proof)).toBe(true); + for (const invalid of ["missing", "duplicate", "error", "foreign", "late", "mutating", "wrong-input", "missing-canonical", "failed-canonical", "native-mutation", "canonical-mutation", "not-read-only", "source-sequence-drift", "extra-context-call"]) { + const rows = structuredClone(retained.rows); + const receipt = rows.find((r: any) => r.seq === proof.contextRead!.receiptSeq)!; + if (invalid === "missing") rows.splice(rows.indexOf(receipt), 1); + if (invalid === "duplicate") rows.push(structuredClone(receipt)); + if (invalid === "error") setField(receipt, "outcome", "error"); + if (invalid === "foreign") frame(receipt).sourceInstanceId = "foreign"; + if (invalid === "late") receipt.seq = command.seq + 1; + if (invalid === "mutating") setField(receipt, "operationId", "report_progress"); + if (invalid === "wrong-input") setField(receipt, "inputSha256", hash("foreign")); + const canonical = rows.find((r: any) => r.seq === proof.contextRead!.canonicalSeqs.at(-1))!; + if (invalid === "missing-canonical") rows.splice(rows.indexOf(canonical), 1); + if (invalid === "failed-canonical") frame(canonical).payload.status = "failed"; + if (invalid === "native-mutation") setField(rows.find((r: any) => r.seq === proof.contextRead!.pendingSeq), "operation", "edit"); + if (invalid === "canonical-mutation") frame(canonical).payload.operation = "edit"; + if (invalid === "not-read-only") frame(canonical).payload.readOnly = false; + if (invalid === "source-sequence-drift") { frame(canonical).sourceSeq = command.seq + 1; canonical.sourceSeq = command.seq + 1; } + if (invalid === "extra-context-call") rows.push(structuredClone(rows.find((r: any) => r.seq === proof.contextRead!.completedSeq))); + expect(() => readCopilotSemanticCompletion(rows, { companyId: rows[0].companyId, + runId: command.runId, turnId: command.turnId, nativeSessionId: command.sessionId, command, + summary: retained.summary, requireContextRead: true }), invalid).toThrow(); + } + }); + it("cannot qualify a historical finish without the now-required context read", () => { + const f = fixture(); + expect(() => readCopilotSemanticCompletion(f.rows, { ...f.expected, requireContextRead: true })).toThrow("context read"); + }); + it("versions semantic completion and current-mainline denial settlement independently", () => { const suite = runnerSuites.find(s => s.id === "copilot-protection")!; - expect(suite.definitionMetadata).toMatchObject({ version: 9, semanticCompletionEvidence: "paperclip.e2e.copilot-semantic-completion.v2", denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v3" }); - expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, version: 7 } })); + expect(suite.definitionMetadata).toMatchObject({ version: 23, titleCreation: "explicit-search-composer", taskBinding: "browser-creation-response-id", remoteBootstrapContext: "already-exposed-dedicated-tool-no-http-substitution", semanticCompletionEvidence: "paperclip.e2e.copilot-semantic-completion.v4", denialSettlementEvidence: "paperclip.e2e.copilot-denial-settlement.v4" }); + expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, version: 8 } })); }); it("joins exact native lifecycle, authoritative callback, proposed content and accepted control-plane result", () => { const { rows, expected } = fixture(); const proof = readCopilotSemanticCompletion(rows, expected); @@ -125,6 +163,35 @@ describe("Copilot semantic completion public-event oracle", () => { expect(f.notices.find(n => n.toolCallId === "shell-read" && n.status === "pending")?.commandToolCallId).toBeUndefined(); expect(onlyCopilotAttachedOperations(f.notices, f.command, f.proof)).toBe(true); }); + it("rejects waiting on read_bash before attempting finish", () => { + const f = attachedFixture(); + f.notices.find(n => n.toolCallId === "shell-read" && n.status === "pending")!.seq = 16; + expect(onlyCopilotAttachedOperations(f.notices, f.command, f.proof)).toBe(false); + }); + it("requires an independently observed exit strictly after the finish attempt", () => { + const f = attachedFixture(), at = f.proof.nativePendingObservedAtMs; + expect(copilotFinishAttemptBeforeCommandExit(f.proof, at + 1)).toBe(true); + for (const exitAt of [at - 1, at, NaN, Infinity]) expect(copilotFinishAttemptBeforeCommandExit(f.proof, exitAt)).toBe(false); + }); + it("releases the fixture only after a matching native finish receipt", () => { + const f = attachedFixture(); + expect(copilotAttachedFinishReady(f.notices, f.command)).toMatchObject({ toolCallId: f.proof.nativeToolCallId, status: "pending" }); + for (const changed of [{ turnId: "foreign" }, { sessionId: "foreign" }, { semanticOutcome: "error" }]) { + const partial = f.notices.map(n => n.semanticOperationId === "paperclip_finish" ? { ...n, ...changed } : n); + expect(copilotAttachedFinishReady(partial as any, f.command)).toBeUndefined(); + } + const incomplete = f.notices.filter(n => !(n.toolCallId === f.proof.nativeToolCallId && n.status === "pending")); + expect(() => copilotAttachedFinishReady(incomplete, f.command)).toThrow("matching finish origin"); + }); + it.each(["", " ", "\t ", " "])("releases an accepted whitespace-prefixed command through the same finish gate: %j", prefix => { + const f = attachedFixture(), command = "printf fixture"; + const nativeDigest = `sha256:${hash(prefix + command)}`; + for (const n of f.notices.filter(n => n.toolCallId === f.command.toolCallId)) n.commandSha256 = nativeDigest; + const matched = findCopilotFixtureCommand(f.notices, command); + expect(matched?.match.leadingWhitespace).toBe(prefix); + expect(copilotAttachedFinishReady(f.notices, matched!.call)?.toolCallId).toBe(f.proof.nativeToolCallId); + expect(findCopilotFixtureCommand(f.notices, command + " changed")).toBeNull(); + }); it("accepts bounded in-progress shell updates without inventing terminal command identity", () => { const f = attachedFixture(), pending = f.notices.find(n => n.toolCallId === "shell-read" && n.status === "pending")!; f.notices.push({ ...pending, status: "in_progress", seq: 53 }); @@ -169,7 +236,7 @@ describe("Copilot semantic completion public-event oracle", () => { it("keeps actual flow correlation before no-extra-operation gate and keeps visible marker independent", async () => { const source = await readFile(new URL("./copilot-protection-flow.ts", import.meta.url), "utf8"); expect(source.indexOf("readCopilotSemanticCompletion(runEvents")).toBeLessThan(source.indexOf('check("no-extra-native-operation", onlyCopilotAttachedOperations(')); - expect(source).toContain("undefined), call!, semantic)"); + expect(source).toContain("undefined, runEvents), call!, semantic)"); expect(source).toContain('check("exact-completion-marker", comments.filter'); expect(source).not.toContain('if (remote) check("no-extra-native-operation"'); }); diff --git a/tests/runner-e2e/copilot-semantic-evidence.ts b/tests/runner-e2e/copilot-semantic-evidence.ts index 029f09c7f93..2dad7976f33 100644 --- a/tests/runner-e2e/copilot-semantic-evidence.ts +++ b/tests/runner-e2e/copilot-semantic-evidence.ts @@ -1,5 +1,6 @@ import { createHash } from "node:crypto"; import { readCopilotToolEvidence, type CopilotToolNotice } from "./copilot-evidence.js"; +import { readCopilotContextRead, type CopilotContextReadProof } from "./copilot-context-evidence.js"; type Row = Record; const rec = (v: unknown): Row => v !== null && typeof v === "object" && !Array.isArray(v) ? v as Row : {}; @@ -10,15 +11,17 @@ const canonical = (v: unknown): string => Array.isArray(v) ? `[${v.map(canonical const digest = (v: unknown) => hash(canonical(v)); const receiptKeys = ["callIdentitySha256", "inputSha256", "normalizedInputSha256", "operationId", "outcome", "resultSha256", "schema", "stage"].sort().join(","); export interface CopilotSemanticCompletionProof { - schema: "paperclip.e2e.copilot-semantic-completion.v2"; + schema: "paperclip.e2e.copilot-semantic-completion.v2" | "paperclip.e2e.copilot-semantic-completion.v4"; + contextRead?: CopilotContextReadProof; runId: string; turnId: string; nativeSessionId: string; normalizedSessionId: string; sourceInstanceId: string; nativeToolCallId: string; callIdentitySha256: string; inputSha256: string; normalizedInputSha256: string; resultSha256: string; + nativePendingSeq: number; nativePendingObservedAtMs: number; proposedSeq: number; receiptSeq: number; nativeCompletedSeq: number; turnCompletedSeq: number; acceptedSeq: number; summarySha256: string; } /** Independent public-event oracle. A transport receipt alone never proves completion acceptance. */ export function readCopilotSemanticCompletion(rows: readonly unknown[], expected: { - companyId: string; runId: string; turnId: string; nativeSessionId: string; command: CopilotToolNotice; summary: string; + companyId: string; runId: string; turnId: string; nativeSessionId: string; command: CopilotToolNotice; summary: string; requireContextRead?: boolean; }): CopilotSemanticCompletionProof { const fail = (message: string): never => { throw new Error(`Copilot semantic completion: ${message}`); }; const all = rows.map(rec), notices = readCopilotToolEvidence(rows, expected.runId); @@ -45,7 +48,8 @@ export function readCopilotSemanticCompletion(rows: readonly unknown[], expected frame(rs[0]!, "runner"); } if (expected.command.runId !== expected.runId || expected.command.turnId !== expected.turnId || expected.command.sessionId !== expected.nativeSessionId) fail("foreign command"); - const authorityRows = all.filter(r => rec(rec(rec(r.payload).prpEvent).payload).category === "paperclip_semantic_tool_receipt_v2" || rec(rec(rec(r.payload).prpEvent).payload).category === "paperclip_semantic_tool_receipt_v1"); + const contextRead = readCopilotContextRead(rows, expected.command, expected.requireContextRead); + const authorityRows = all.filter(r => (rec(rec(rec(r.payload).prpEvent).payload).category === "paperclip_semantic_tool_receipt_v2" || rec(rec(rec(r.payload).prpEvent).payload).category === "paperclip_semantic_tool_receipt_v1") && ![contextRead?.receiptSeq, ...(contextRead?.discoveries.map(d => d.receiptSeq) ?? [])].includes(r.seq)); // This authored case asks for one finish, not arbitrary semantic actions. if (authorityRows.length !== 1) fail("expected one authoritative finish receipt"); const authorityRow = authorityRows[0]!, authority = frame(authorityRow, "runner"), payload = rec(authority.payload), origin = rec(payload.provenance); @@ -95,9 +99,10 @@ export function readCopilotSemanticCompletion(rows: readonly unknown[], expected && authorityRow.seq < native.seq && native.seq < terminals[0]!.seq && terminals[0]!.seq < acceptedRows[0]!.seq) || !(base.sourceSeq < pendingFrame.sourceSeq && pendingFrame.sourceSeq < proposed.sourceSeq && proposed.sourceSeq < authority.sourceSeq && authority.sourceSeq < completedFrame.sourceSeq && completedFrame.sourceSeq < terminal.sourceSeq)) fail("invalid causal order"); - return { schema: "paperclip.e2e.copilot-semantic-completion.v2", runId: expected.runId, turnId: expected.turnId, nativeSessionId: expected.nativeSessionId, + return { schema: expected.requireContextRead ? "paperclip.e2e.copilot-semantic-completion.v4" : "paperclip.e2e.copilot-semantic-completion.v2", ...(contextRead ? { contextRead } : {}), runId: expected.runId, turnId: expected.turnId, nativeSessionId: expected.nativeSessionId, normalizedSessionId: base.normalizedSessionId, sourceInstanceId: base.sourceInstanceId, nativeToolCallId: native.toolCallId, callIdentitySha256: fields.callIdentitySha256!, inputSha256: fields.inputSha256!, normalizedInputSha256: fields.normalizedInputSha256!, resultSha256: fields.resultSha256!, + nativePendingSeq: pending[0]!.seq, nativePendingObservedAtMs: pending[0]!.observedAtMs, proposedSeq: proposedRows[0]!.seq, receiptSeq: authorityRow.seq, nativeCompletedSeq: native.seq, turnCompletedSeq: terminals[0]!.seq, acceptedSeq: acceptedRows[0]!.seq, summarySha256: hash(expected.summary) }; } @@ -110,6 +115,16 @@ export function onlyCopilotAttachedOperations(notices: readonly CopilotToolNotic || new Set(notices.map(n => n.seq)).size !== notices.length) return false; const groups = new Map(); for (const n of notices) { const group = groups.get(n.toolCallId) ?? []; group.push(n); groups.set(n.toolCallId, group); } + if (proof.contextRead) { + const context = groups.get(proof.contextRead.toolCallId); + if (context && context.some(n => n.seq >= command.seq)) return false; + groups.delete(proof.contextRead.toolCallId); + for (const discovery of proof.contextRead.discoveries) { + const group = groups.get(discovery.toolCallId); + if (group && group.some(n => n.seq >= proof.contextRead!.pendingSeq)) return false; + groups.delete(discovery.toolCallId); + } + } // Exactly one command, one shell-result read, and the already-proven finish. // A completed-only extra read cannot borrow the original command's identity. if (groups.size !== 3 || !groups.has(proof.nativeToolCallId)) return false; @@ -131,9 +146,27 @@ export function onlyCopilotAttachedOperations(notices: readonly CopilotToolNotic const shellGroup = [...groups].find(([key]) => key !== command.toolCallId && key !== proof.nativeToolCallId)![1]; if (!complete(shellGroup)) return false; const terminal = shellGroup.at(-1)!; - return shellGroup[0]!.seq > started.seq && terminal.seq < proof.turnCompletedSeq + return shellGroup[0]!.seq > started.seq && shellGroup[0]!.seq > proof.nativePendingSeq && terminal.seq < proof.turnCompletedSeq && terminal.commandToolCallId === command.toolCallId && terminal.shellState === "completed" && terminal.exitCode === 0 && shellGroup.every(n => n.operation === "read" && n.shellId === started.shellId && n.target === undefined && n.readTargetSha256 === undefined && n.commandSha256 === undefined && n.mode === undefined && n.detach === undefined && semanticFree(n) && (n === terminal || (n.commandToolCallId === undefined && n.shellState === undefined && n.exitCode === undefined))); } + +/** The finish origin is producer-stamped on the same host as the independent child observer. */ +export function copilotFinishAttemptBeforeCommandExit(proof: CopilotSemanticCompletionProof, commandExitAtMs: number): boolean { + return Number.isFinite(proof.nativePendingObservedAtMs) && proof.nativePendingObservedAtMs >= 0 + && Number.isFinite(commandExitAtMs) && commandExitAtMs > proof.nativePendingObservedAtMs; +} + +/** Fixture scheduling only; final grading still verifies the complete durable receipt and acceptance. */ +export function copilotAttachedFinishReady(notices: readonly CopilotToolNotice[], command: CopilotToolNotice): CopilotToolNotice | undefined { + const matches = notices.filter(n => n.semanticOperationId === "paperclip_finish" && n.semanticOutcome === "returned" && n.stage === "tool" && n.status === "completed" + && n.runId === command.runId && n.turnId === command.turnId && n.sessionId === command.sessionId && n.seq > command.seq); + if (!matches.length) return undefined; + if (matches.length !== 1) throw new Error("Attached fixture has ambiguous finish receipts"); + const finish = matches[0]!, pending = notices.filter(n => n.toolCallId === finish.toolCallId && n.stage === "tool" && n.status === "pending" + && n.runId === command.runId && n.turnId === command.turnId && n.sessionId === command.sessionId && n.seq > command.seq && n.seq < finish.seq); + if (pending.length !== 1) throw new Error("Attached fixture lacks the matching finish origin"); + return pending[0]; +} diff --git a/tests/runner-e2e/copilot-stop-acknowledgement.test.ts b/tests/runner-e2e/copilot-stop-acknowledgement.test.ts new file mode 100644 index 00000000000..0fc0bab624a --- /dev/null +++ b/tests/runner-e2e/copilot-stop-acknowledgement.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest"; +import { retainedMainlineStop } from "./copilot-stop-test-fixtures.js"; +import { assertCopilotStopAcknowledgement, readCopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; + +const fixtureCorrelationId = "db8b16c1-aafa-4662-9cf4-0bb317db7751"; +function fixture() { + const run = structuredClone(retainedMainlineStop.run); + const scope = { companyId: run.companyId, runId: run.id, issueId: run.nativeIssueId, callerUserId: "local-board" }; + const read = () => readCopilotStopAcknowledgement(run, scope, fixtureCorrelationId, "100", "101"); + return { run, scope, read }; +} +describe("current-mainline Copilot Stop API acknowledgement", () => { + it("calibrates against the retained failed attempt without requiring an unsupported caller-owned intent", () => { + const f = fixture(), proof = f.read(); + expect(proof.intentId).not.toBe(`native-cancellation:${fixtureCorrelationId}`); + expect(proof).toMatchObject({ fixtureCorrelationId, intentId: "native-cancellation:88c9dd8f-92d2-407d-a989-a3495e8e6944" }); + expect(() => assertCopilotStopAcknowledgement(proof, f.run, f.scope, fixtureCorrelationId, "100")).not.toThrow(); + expect(retainedMainlineStop.outcome).toContain("calibration only"); + }); + it.each([ + ["foreign run", (r: any) => { r.id = "foreign"; }], + ["foreign company", (r: any) => { r.companyId = "foreign"; }], + ["foreign issue", (r: any) => { r.nativeIssueId = "foreign"; }], + ["failed run", (r: any) => { r.status = "failed"; }], + ["non-native run", (r: any) => { r.runtimeMode = "legacy"; }], + ["foreign API caller", (r: any) => { r.resultJson.cancelledByUserId = "other"; }], + ["agent caller", (r: any) => { r.resultJson.cancelledByActorType = "agent"; }], + ["foreign initiator", (r: any) => { r.resultJson.cancellation.initiator.id = "other"; }], + ["provider cancellation", (r: any) => { r.resultJson.cancellation.source = "provider"; }], + ["unexpected cancellation", (r: any) => { r.resultJson.cancellation.expected = false; }], + ["missing timestamp", (r: any) => { delete r.resultJson.startupCancellation.requestedAt; }], + ["startup-only cancellation", (r: any) => { r.resultJson.startupCancellation.beforeNativeSelection = true; }], + ["foreign acknowledgement", (r: any) => { r.resultJson.nativeCancellation.runId = "foreign"; }], + ["unacknowledged intent", (r: any) => { r.resultJson.nativeCancellation.dispatchState = "pending"; }], + ["undispatched intent", (r: any) => { r.resultJson.nativeCancellation.dispatched = false; }], + ["missing audit", (r: any) => { delete r.resultJson.nativeCancellation.intentAuditId; }], + ["same audit", (r: any) => { r.resultJson.nativeCancellation.intentAuditId = r.resultJson.nativeCancellation.acknowledgementAuditId; }], + ["unexpected effects", (r: any) => { r.resultJson.nativeCancellation.effects.push("replay"); }], + ["invalid intent", (r: any) => { r.resultJson.nativeCancellation.intentId = "native-cancellation:foreign"; }], + ] as const)("rejects %s", (_label, mutate) => { const f = fixture(); mutate(f.run); expect(f.read).toThrow(/acknowledgement/); }); + it.each(["100", "99", "0", "bad", "1".repeat(31)])("rejects response clock %s", clock => { + const f = fixture(); expect(() => readCopilotStopAcknowledgement(f.run, f.scope, fixtureCorrelationId, "100", clock)).toThrow(/acknowledgement/); + }); + it("requires retention of the actual response and rejects later replacement metadata", () => { + const f = fixture(), proof = f.read(); + expect(() => assertCopilotStopAcknowledgement(undefined as any, f.run, f.scope, fixtureCorrelationId, "100")).toThrow(/not retained/); + f.run.resultJson.nativeCancellation.intentId = "native-cancellation:11111111-2222-4333-8444-555555555555"; + expect(() => assertCopilotStopAcknowledgement(proof, f.run, f.scope, fixtureCorrelationId, "100")).toThrow(/changed/); + }); + it("rejects a replayed API response from another fixture dispatch", () => { + const f = fixture(), proof = f.read(); + expect(() => assertCopilotStopAcknowledgement(proof, f.run, f.scope, "11111111-2222-4333-8444-555555555555", "100")).toThrow(/changed/); + expect(() => assertCopilotStopAcknowledgement(proof, f.run, f.scope, fixtureCorrelationId, "99")).toThrow(/changed/); + }); +}); diff --git a/tests/runner-e2e/copilot-stop-acknowledgement.ts b/tests/runner-e2e/copilot-stop-acknowledgement.ts new file mode 100644 index 00000000000..c3b2e8409d1 --- /dev/null +++ b/tests/runner-e2e/copilot-stop-acknowledgement.ts @@ -0,0 +1,46 @@ +import { createHash } from "node:crypto"; +import { canonicalJson } from "../../packages/shared/src/portability-hash.js"; + +type Row = Record; +export interface CopilotStopScope { companyId: string; runId: string; issueId: string; callerUserId: string } +export interface CopilotStopAcknowledgement extends CopilotStopScope { + schema: "paperclip.e2e.copilot-stop-acknowledgement.v1"; + fixtureCorrelationId: string; dispatchMonotonicNs: string; apiResponseObservedMonotonicNs: string; + intentId: string; intentAuditId: string; acknowledgementAuditId: string; responseMetadataSha256: string; +} +const rec = (v: unknown): Row => v !== null && typeof v === "object" && !Array.isArray(v) ? v as Row : {}; +const id = (v: unknown): v is string => typeof v === "string" && v.length > 0 && v.length <= 240 && !/[\u0000-\u001f\u007f]/u.test(v); +const uuid = (v: unknown): v is string => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(v); +const mono = (v: unknown): v is string => typeof v === "string" && /^[1-9][0-9]{0,29}$/u.test(v); + +/** Current mainline's board-only cancel API generates the intent itself. Record + * its response after the retained pre-Stop observation; never invent support + * for a caller-supplied cancellation UUID or infer a response from later rows. */ +export function readCopilotStopAcknowledgement(runInput: unknown, scope: CopilotStopScope, fixtureCorrelationId: string, + dispatchMonotonicNs: string, apiResponseObservedMonotonicNs = process.hrtime.bigint().toString()): CopilotStopAcknowledgement { + const invalid = () => new Error("Copilot Stop acknowledgement lacks exact current-mainline caller and scope"); + const run = rec(runInput), result = rec(run.resultJson), stop = rec(result.nativeCancellation), startup = rec(result.startupCancellation), cancellation = rec(result.cancellation); + if (!Object.values(scope).every(id) || !uuid(fixtureCorrelationId) || !mono(dispatchMonotonicNs) || !mono(apiResponseObservedMonotonicNs) + || BigInt(apiResponseObservedMonotonicNs) <= BigInt(dispatchMonotonicNs) + || run.id !== scope.runId || run.companyId !== scope.companyId || run.nativeIssueId !== scope.issueId || run.runtimeMode !== "native" || run.status !== "cancelled" + || result.cancelledByActorType !== "user" || result.cancelledByUserId !== scope.callerUserId + || cancellation.source !== "operator" || cancellation.expected !== true || rec(cancellation.initiator).type !== "user" || rec(cancellation.initiator).id !== scope.callerUserId + || typeof startup.requestedAt !== "string" || !Number.isFinite(Date.parse(startup.requestedAt)) || startup.beforeNativeSelection !== false + || stop.schema !== "paperclip.native-cancellation.v1" || stop.runId !== scope.runId || stop.companyId !== scope.companyId || stop.issueId !== scope.issueId + || typeof stop.intentId !== "string" || !stop.intentId.startsWith("native-cancellation:") || !uuid(stop.intentId.slice("native-cancellation:".length)) + || stop.scope !== "run" || stop.dispatched !== true || stop.dispatchState !== "acknowledged" || stop.reasonCode !== "cancellation_run_only" + || !Array.isArray(stop.effects) || stop.effects.length !== 1 || stop.effects[0] !== "release_run_resources" + || !id(stop.intentAuditId) || !id(stop.acknowledgementAuditId) || stop.intentAuditId === stop.acknowledgementAuditId) throw invalid(); + const metadata = { id: run.id, companyId: run.companyId, nativeIssueId: run.nativeIssueId, runtimeMode: run.runtimeMode, status: run.status, + cancelledByActorType: result.cancelledByActorType, cancelledByUserId: result.cancelledByUserId, cancellation, startupCancellation: startup, nativeCancellation: stop }; + return { schema: "paperclip.e2e.copilot-stop-acknowledgement.v1", ...scope, fixtureCorrelationId, dispatchMonotonicNs, apiResponseObservedMonotonicNs, + intentId: stop.intentId, intentAuditId: stop.intentAuditId, acknowledgementAuditId: stop.acknowledgementAuditId, + responseMetadataSha256: `sha256:${createHash("sha256").update(canonicalJson(metadata)).digest("hex")}` }; +} + +export function assertCopilotStopAcknowledgement(proof: CopilotStopAcknowledgement, run: unknown, scope: CopilotStopScope, + fixtureCorrelationId: string, dispatchMonotonicNs: string): void { + if (!proof || proof.schema !== "paperclip.e2e.copilot-stop-acknowledgement.v1") throw new Error("Copilot Stop acknowledgement response was not retained"); + const current = readCopilotStopAcknowledgement(run, scope, fixtureCorrelationId, dispatchMonotonicNs, proof.apiResponseObservedMonotonicNs); + if (canonicalJson(proof) !== canonicalJson(current)) throw new Error("Copilot Stop acknowledgement changed after the API response"); +} diff --git a/tests/runner-e2e/copilot-stop-test-fixtures.ts b/tests/runner-e2e/copilot-stop-test-fixtures.ts new file mode 100644 index 00000000000..da5d349bfc6 --- /dev/null +++ b/tests/runner-e2e/copilot-stop-test-fixtures.ts @@ -0,0 +1,20 @@ +import { readFileSync } from "node:fs"; +import { readCopilotStopAcknowledgement } from "./copilot-stop-acknowledgement.js"; + +// Synthetic scope/times over a retained, sanitized current-mainline API response. +// This helper calibrates fixture assertions; it provides no live qualification. +export const retainedMainlineStop = JSON.parse(readFileSync(new URL("./fixtures/copilot-mainline-stop-ack-v15.json", import.meta.url), "utf8")); +export function applyMainlineStopMetadata(run: Record) { + const existing = run.resultJson ?? {}, template = structuredClone(retainedMainlineStop.run.resultJson); + const requestedAt = existing.startupCancellation?.requestedAt ?? template.startupCancellation.requestedAt; + run.runtimeMode = "native"; + run.resultJson = { ...existing, cancelledByActorType: "user", cancelledByUserId: "local-board", + cancellation: { ...template.cancellation, recordedAt: requestedAt }, + startupCancellation: { requestedAt, beforeNativeSelection: false }, + nativeCancellation: { ...template.nativeCancellation, ...existing.nativeCancellation, + intentId: template.nativeCancellation.intentId, companyId: run.companyId, runId: run.id, issueId: run.nativeIssueId } }; +} +export function stopAcknowledgementForTest(run: Record, fixtureCorrelationId: string, dispatchMonotonicNs: string) { + return readCopilotStopAcknowledgement(run, { companyId: run.companyId, runId: run.id, issueId: run.nativeIssueId, callerUserId: "local-board" }, + fixtureCorrelationId, dispatchMonotonicNs, (BigInt(dispatchMonotonicNs) + 1n).toString()); +} diff --git a/tests/runner-e2e/daytona-image-content.ts b/tests/runner-e2e/daytona-image-content.ts index 4ccda56aa2a..47e930a0675 100644 --- a/tests/runner-e2e/daytona-image-content.ts +++ b/tests/runner-e2e/daytona-image-content.ts @@ -41,7 +41,17 @@ export const DAYTONA_IMAGE_INPUT_PATHS = [ "packages/paperclip-runner/scripts/build-node-startup-timeout.mjs", "packages/paperclip-runner/scripts/candidate-provider-pack.mjs", "packages/paperclip-runner/scripts/build-copilot-distribution.mjs", + "packages/paperclip-runner/scripts/copilot-inner-distribution.mjs", + "packages/paperclip-runner/scripts/copilot-profile-identity.mjs", "packages/paperclip-runner/scripts/materialize-copilot-binary.mjs", + "packages/paperclip-runner/scripts/install-copilot-assets.mjs", + "packages/paperclip-runner/test/fixtures/copilot-profile-v12-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v14-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v21-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v24-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v25-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v34-identity.json", "packages/paperclip-runner/scripts/materialize-cursor-distribution.mjs", "packages/paperclip-runner/scripts/cursor-runtime-patch.mjs", "packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs", diff --git a/tests/runner-e2e/daytona-image.test.ts b/tests/runner-e2e/daytona-image.test.ts index 4c8af724c77..2ffd970e755 100644 --- a/tests/runner-e2e/daytona-image.test.ts +++ b/tests/runner-e2e/daytona-image.test.ts @@ -13,6 +13,29 @@ import { const repositoryRoot = path.resolve(import.meta.dirname, "../.."); describe("runner E2E Daytona image contract", () => { + it("retains the active Copilot identity declaration in the Docker context", async () => { + const [generator, dockerignore] = await Promise.all([ + readFile(path.join(repositoryRoot, "packages/paperclip-runner/scripts/copilot-profile-identity.mjs"), "utf8"), + readFile(path.join(repositoryRoot, ".dockerignore"), "utf8"), + ]); + const fixture = generator.match(/const fixture = resolve\(root, "([^"]+)"\)/)?.[1]; + expect(fixture).toBeDefined(); + const fixturePath = `packages/paperclip-runner/${fixture}`; + expect(DAYTONA_IMAGE_INPUT_PATHS).toContain(fixturePath); + await expect(readFile(path.join(repositoryRoot, fixturePath), "utf8")).resolves.toContain('"commandDigest"'); + // Docker's last matching rule wins, including the parent-directory exclusion. + const rules = dockerignore.split(/\r?\n/).filter(rule => rule && !rule.startsWith("#")); + const lastRule = rules.filter(rule => { + const pattern = rule.startsWith("!") ? rule.slice(1) : rule; + return path.matchesGlob(fixturePath, pattern) || fixturePath.startsWith(`${pattern}/`); + }).at(-1); + expect(lastRule?.startsWith("!")).toBe(true); + // The exception also covers the next version without exposing other live evidence. + const exception = lastRule!.slice(1); + expect(path.matchesGlob("packages/paperclip-runner/test/fixtures/copilot-profile-v999-identity.json", exception)).toBe(true); + expect(path.matchesGlob("packages/paperclip-runner/test/fixtures/copilot-live-denial-2026-09-28.json", exception)).toBe(false); + }); + it("keeps the qualified native Grok binary separate from the legacy command", async () => { const [dockerfile, packBuilder, runnerPackage] = await Promise.all([ readFile(path.join(repositoryRoot, "docker/daytona-runner/Dockerfile"), "utf8"), @@ -209,7 +232,17 @@ describe("runner E2E Daytona image contract", () => { "packages/paperclip-runner/package.json", "packages/paperclip-runner/scripts/candidate-provider-pack.mjs", "packages/paperclip-runner/scripts/build-copilot-distribution.mjs", + "packages/paperclip-runner/scripts/copilot-inner-distribution.mjs", + "packages/paperclip-runner/scripts/copilot-profile-identity.mjs", "packages/paperclip-runner/scripts/materialize-copilot-binary.mjs", + "packages/paperclip-runner/scripts/install-copilot-assets.mjs", + "packages/paperclip-runner/test/fixtures/copilot-profile-v12-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v14-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v21-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v24-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v25-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v26-identity.json", + "packages/paperclip-runner/test/fixtures/copilot-profile-v34-identity.json", "packages/paperclip-runner/scripts/materialize-cursor-distribution.mjs", "packages/paperclip-runner/scripts/cursor-runtime-patch.mjs", "packages/paperclip-runner/cursor-distributions.json", diff --git a/tests/runner-e2e/fixtures/copilot-attached-discovery-sequence-v15.json b/tests/runner-e2e/fixtures/copilot-attached-discovery-sequence-v15.json new file mode 100644 index 00000000000..f0c15d60823 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-attached-discovery-sequence-v15.json @@ -0,0 +1,2144 @@ +{ + "schema": "paperclip.e2e.retained-attached-bootstrap.v1", + "sourceRevision": "7dec03762eb8201f721b7a1c5116861c8ccd23b4", + "attempt": "local-2026-10-03T23-03-48-015Z", + "outcome": "failed singleton-discovery fixture; sanitized offline calibration only", + "rows": [ + { + "id": 126, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 126, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_XzscDouvZMLZAv70ZoUmw19L" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:21.364Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 101, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:101", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:101", + "sourceSeq": 101, + "sourcePayloadSha256": "327443d34f7486d9527c91f932ce92fcb699fa3704c0e68db0b5fadc9b487245", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:21.443Z" + }, + { + "id": 127, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 127, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-search_api", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-search_api (pending): get_task_context paperclip_finish", + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_XzscDouvZMLZAv70ZoUmw19L", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:21.364Z", + "eventType": "tool.execution.started", + "sourceSeq": 102, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:102", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:102", + "sourceSeq": 102, + "sourcePayloadSha256": "ea23416d7c167c3f1bb44a5782dc49dd7b0b5bf01c167eb817eb0e0827d11ce4", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:21.447Z" + }, + { + "id": 129, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 129, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "search_api" + }, + { + "name": "callIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "inputSha256", + "value": "3b6bda8aba9d22b72f36ba6540ff49364d1afc76ce566ab1ca58057c37138169" + }, + { + "name": "resultSha256", + "value": "ac93fbde983d6106f587cfac76e9383454a5891621ee9ea6df18535c2192cc75" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "null" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-2", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "semantic_result", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:21.811Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 104, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:104", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:104", + "sourceSeq": 104, + "sourcePayloadSha256": "41f31be7acb5fc3f042a998666e1e5c9f3d67121c71a0cf16c7edd57c5fd1b19", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:21.848Z" + }, + { + "id": 130, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 130, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_XzscDouvZMLZAv70ZoUmw19L" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "semanticOperationId", + "value": "search_api" + }, + { + "name": "semanticCallIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "semanticInputSha256", + "value": "3b6bda8aba9d22b72f36ba6540ff49364d1afc76ce566ab1ca58057c37138169" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "null" + }, + { + "name": "semanticResultSha256", + "value": "ac93fbde983d6106f587cfac76e9383454a5891621ee9ea6df18535c2192cc75" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-3", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:21.848Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 105, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:105", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:105", + "sourceSeq": 105, + "sourcePayloadSha256": "5c7cb1183c294e093c0a24623a3e5c8e424efca1f88a3c6f0a60542893491992", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:21.858Z" + }, + { + "id": 131, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 131, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-search_api", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_XzscDouvZMLZAv70ZoUmw19L", + "outputBytes": 4088, + "inputUpdated": false, + "outputDigest": "sha256:b2a383bd163b93566a922e3a816bb3fca45a8dfa8655ed1acec14fe01fd8391c", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:21.848Z", + "eventType": "tool.execution.completed", + "sourceSeq": 106, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:106", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:106", + "sourceSeq": 106, + "sourcePayloadSha256": "fa84f1538af3d54f7f60a38075514907cccd49adfbfd25dc5faf5f166e6f1a8b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:21.861Z" + }, + { + "id": 137, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 137, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_hjqKaXkXulaXfpHScGOhaMxw" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-4", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:22.984Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 111, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:111", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:111", + "sourceSeq": 111, + "sourcePayloadSha256": "3fb52f52ef8c86d42bcb9d75aedbbcf072621673e983d3b5c56527e803706afe", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:23.023Z" + }, + { + "id": 138, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 138, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-search_api", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-search_api (pending): get_task_context", + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_hjqKaXkXulaXfpHScGOhaMxw", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:22.984Z", + "eventType": "tool.execution.started", + "sourceSeq": 112, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:112", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:112", + "sourceSeq": 112, + "sourcePayloadSha256": "c4333f6631a54442000d4518415d386025de1a25218b928ace062b69d672858b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:23.028Z" + }, + { + "id": 140, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 140, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "search_api" + }, + { + "name": "callIdentitySha256", + "value": "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a" + }, + { + "name": "inputSha256", + "value": "c8b1f253cae1ef77b4a816d5b540c56dfa5ad2563db65187461bb6d48015eda5" + }, + { + "name": "resultSha256", + "value": "dc0a845335bae410a7a82291b58235ed824f9932a9c003810595c221b5542807" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "null" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-5", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "semantic_result", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:23.392Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 114, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:114", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:114", + "sourceSeq": 114, + "sourcePayloadSha256": "60f36957a713a9cb4b84d2c11cb1feeb542cb500e74c5194562607594d78b429", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:23.430Z" + }, + { + "id": 141, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 141, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_hjqKaXkXulaXfpHScGOhaMxw" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "semanticOperationId", + "value": "search_api" + }, + { + "name": "semanticCallIdentitySha256", + "value": "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a" + }, + { + "name": "semanticInputSha256", + "value": "c8b1f253cae1ef77b4a816d5b540c56dfa5ad2563db65187461bb6d48015eda5" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "null" + }, + { + "name": "semanticResultSha256", + "value": "dc0a845335bae410a7a82291b58235ed824f9932a9c003810595c221b5542807" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-6", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:23.430Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 115, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:115", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:115", + "sourceSeq": 115, + "sourcePayloadSha256": "5eccf15c98fdf98172448b037f01af02c77744de64fea2ca96c14d704faa53f8", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:23.433Z" + }, + { + "id": 142, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 142, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-search_api", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_hjqKaXkXulaXfpHScGOhaMxw", + "outputBytes": 4070, + "inputUpdated": false, + "outputDigest": "sha256:87f4a92f6177165ccd3a58e67102bd241944b10c178e1661b8bf097d72c23a4f", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:23.430Z", + "eventType": "tool.execution.completed", + "sourceSeq": 116, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:116", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:116", + "sourceSeq": 116, + "sourcePayloadSha256": "6ad8ca6eac93e45edb627277a70831ee942fd0988748dcc8c00d9664da281c3c", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:23.439Z" + }, + { + "id": 232, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 232, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_KsNEXEdro6ZSODFED42sL7wV" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-7", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:27.700Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 205, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:205", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:205", + "sourceSeq": 205, + "sourcePayloadSha256": "759b1b237f9f11875f65aafa6e8fcf0c1bbe84971d38d853667b1b62820abf55", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:27.753Z" + }, + { + "id": 233, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 233, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-get_task_context", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-get_task_context (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_KsNEXEdro6ZSODFED42sL7wV", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:27.700Z", + "eventType": "tool.execution.started", + "sourceSeq": 206, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:206", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:206", + "sourceSeq": 206, + "sourcePayloadSha256": "30c9bf1f2e6eb79f76909e2d7a62c70034e14da39c00f94121d1b56ba9c909e1", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:27.760Z" + }, + { + "id": 235, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 235, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "get_task_context" + }, + { + "name": "callIdentitySha256", + "value": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d" + }, + { + "name": "inputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "resultSha256", + "value": "b45b9b45929721267935275a465c4848f493bc224f59f3713ec675764ca51aa3" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "null" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-8", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "semantic_result", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:28.118Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 208, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:208", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:208", + "sourceSeq": 208, + "sourcePayloadSha256": "18efd9bbe9d51de5991996a2256bec40aa09609ced76efd92a54be8057225193", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:28.157Z" + }, + { + "id": 236, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 236, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_KsNEXEdro6ZSODFED42sL7wV" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "operation", + "value": "read" + }, + { + "name": "semanticOperationId", + "value": "get_task_context" + }, + { + "name": "semanticCallIdentitySha256", + "value": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d" + }, + { + "name": "semanticInputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "null" + }, + { + "name": "semanticResultSha256", + "value": "b45b9b45929721267935275a465c4848f493bc224f59f3713ec675764ca51aa3" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-9", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:28.157Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 209, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:209", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:209", + "sourceSeq": 209, + "sourcePayloadSha256": "5c91cce45d607e331aee9df0d3b30eecebc0f90dcc10d50cfa699aed5c38fc7a", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:28.165Z" + }, + { + "id": 237, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 237, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-get_task_context", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_KsNEXEdro6ZSODFED42sL7wV", + "outputBytes": 4110, + "inputUpdated": false, + "outputDigest": "sha256:0fe2163dcd0f08aa86a3c60bd144280c58625c2c5585b8a17d15f405f70040a5", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:28.157Z", + "eventType": "tool.execution.completed", + "sourceSeq": 210, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:210", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:210", + "sourceSeq": 210, + "sourcePayloadSha256": "acd4ff59e68cbfe929d7c93413769719ec6976d40846d67ba08f1d30a441a70e", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:28.169Z" + }, + { + "id": 315, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 315, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_KvAAXBIT5hgUJQhGBZuOnwm0" + }, + { + "name": "operation", + "value": "execute" + }, + { + "name": "commandSha256", + "value": "sha256:23db1284e7667a1d48324371c982492886426609e468aa808d609c6b8c27a6a2" + }, + { + "name": "mode", + "value": "async" + }, + { + "name": "detach", + "value": "false" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-10", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:32.467Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 288, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:288", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:288", + "sourceSeq": 288, + "sourcePayloadSha256": "5d220749611cbc23141f97bc9b4119b6a424a6154049acce32dd628a6c535d3e", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:32.528Z" + }, + { + "id": 316, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 316, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "Run supplied settlement fixture command", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "Run supplied settlement fixture command (pending): '/private/tmp/paperclip-pi-v5-node-20260928/darwin-arm64/node' '/tmp/pc-copilot-WNx3b9/client.cjs' '/tmp/pc-copilot-WNx3b9/private/socket' '1a7c8079769fc96d4255a468bbd2343b'", + "readOnly": false, + "namespace": null, + "operation": "execute", + "transport": "builtin", + "durationMs": null, + "executionId": "call_KvAAXBIT5hgUJQhGBZuOnwm0", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:32.467Z", + "eventType": "tool.execution.started", + "sourceSeq": 289, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:289", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:289", + "sourceSeq": 289, + "sourcePayloadSha256": "39652f7a616f87ba989584511e9f0a7422b56b64ef38c1f84d843361dd5ac716", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:32.531Z" + }, + { + "id": 330, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 330, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_KvAAXBIT5hgUJQhGBZuOnwm0" + }, + { + "name": "operation", + "value": "execute" + }, + { + "name": "commandSha256", + "value": "sha256:23db1284e7667a1d48324371c982492886426609e468aa808d609c6b8c27a6a2" + }, + { + "name": "mode", + "value": "async" + }, + { + "name": "detach", + "value": "false" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "commandToolCallId", + "value": "call_KvAAXBIT5hgUJQhGBZuOnwm0" + }, + { + "name": "shellState", + "value": "started" + } + ], + "summary": "Copilot started an attached command.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-11", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:32.467Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 303, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:303", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:303", + "sourceSeq": 303, + "sourcePayloadSha256": "204c0efccb0162f38d7eb441f52c3f98683be462a0914a7e69cb578f2c52d3d2", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:32.572Z" + }, + { + "id": 331, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 331, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "Run supplied settlement fixture command", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": false, + "namespace": null, + "operation": "execute", + "transport": "builtin", + "durationMs": null, + "executionId": "call_KvAAXBIT5hgUJQhGBZuOnwm0", + "outputBytes": 129, + "inputUpdated": false, + "outputDigest": "sha256:418ba56ba3cfd65410012e6cc4589133eb340c1e54d4b4be38692c4af082ef8f", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:32.467Z", + "eventType": "tool.execution.completed", + "sourceSeq": 304, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:304", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:304", + "sourceSeq": 304, + "sourcePayloadSha256": "fbe70aba112814ff141d7dc40dd465c22fd375fad2ab2cab0627888b70572171", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:32.575Z" + }, + { + "id": 408, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 408, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_5gzZTL4x2r7YXRdqHTwRqEvc" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-12", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:35.583Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 381, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:381", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:381", + "sourceSeq": 381, + "sourcePayloadSha256": "7a8a224d9295c9fa655be5b2b2a7cbeba74feee1453915d338a59f16a0f8b83c", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:35.596Z" + }, + { + "id": 409, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 409, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-paperclip_finish", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-paperclip_finish (pending)", + "readOnly": false, + "namespace": null, + "operation": "execute", + "transport": "builtin", + "durationMs": null, + "executionId": "call_5gzZTL4x2r7YXRdqHTwRqEvc", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:35.583Z", + "eventType": "tool.execution.started", + "sourceSeq": 382, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:382", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:382", + "sourceSeq": 382, + "sourcePayloadSha256": "d41ccfd58b14c50da52a8b5db7d975e8cbcf381f99f9b1ad08cbafe4d4e1cb6b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:35.631Z" + }, + { + "id": 410, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 410, + "eventType": "tool.execution.progressed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": null, + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "tool call", + "readOnly": false, + "namespace": null, + "operation": "unknown", + "transport": "builtin", + "durationMs": null, + "executionId": "call_KvAAXBIT5hgUJQhGBZuOnwm0", + "outputBytes": 4, + "inputUpdated": false, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:35.583Z", + "eventType": "tool.execution.progressed", + "sourceSeq": 383, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:383", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:383", + "sourceSeq": 383, + "sourcePayloadSha256": "2391c6e034a3e86a3ec8ef6401c12554ffe306fe2d382c290636543a0b09d41b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:35.636Z" + }, + { + "id": 423, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 423, + "eventType": "run.result.proposed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "6", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "schema": "paperclip.run_result.v1", + "summary": "COPILOT-PROTECTION-attached-async-settlement-9913c6aa6ec8-1", + "evidence": [], + "artifacts": [], + "verification": [], + "completionClaim": { + "criteria": [ + { + "status": "satisfied", + "criterionId": "objective", + "evidenceRefs": [] + } + ], + "remainingWork": [], + "contractRevision": "1", + "objectiveSatisfied": true + }, + "attentionRequests": [], + "reportedWorkDisposition": "done" + }, + "priority": 0, + "emittedAt": "2026-10-03T23:04:35.597Z", + "eventType": "run.result.proposed", + "sourceSeq": 396, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:396", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:396", + "sourceSeq": 396, + "sourcePayloadSha256": "4447bb92556ef97e9a9f72f2af7be0240b26913849995e5f6c6ff2f74fea344e", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:35.699Z" + }, + { + "id": 424, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 424, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "paperclip_finish" + }, + { + "name": "callIdentitySha256", + "value": "e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683" + }, + { + "name": "inputSha256", + "value": "9bf089cba8478e2e91971ab388d8fd03408b7ffadca18c8befa1d7a53630d27e" + }, + { + "name": "resultSha256", + "value": "ed09c64c04798ab9e6108f46c431bbaa3abc4876057b72b8ee421a44c05e3587" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "0051f5d3863c6d3ebcc843ac32d4f90b5fa5a601b250c91532e7e5683ed44207" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-13", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "semantic_result", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.093Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 397, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:397", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:397", + "sourceSeq": 397, + "sourcePayloadSha256": "68528a4e87a24f2d087441b2562e9d810cae244eccde34c50b8d8b0cc0f4614d", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.189Z" + }, + { + "id": 425, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 425, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_5gzZTL4x2r7YXRdqHTwRqEvc" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "semanticOperationId", + "value": "paperclip_finish" + }, + { + "name": "semanticCallIdentitySha256", + "value": "e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683" + }, + { + "name": "semanticInputSha256", + "value": "9bf089cba8478e2e91971ab388d8fd03408b7ffadca18c8befa1d7a53630d27e" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "0051f5d3863c6d3ebcc843ac32d4f90b5fa5a601b250c91532e7e5683ed44207" + }, + { + "name": "semanticResultSha256", + "value": "ed09c64c04798ab9e6108f46c431bbaa3abc4876057b72b8ee421a44c05e3587" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-14", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 398, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:398", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:398", + "sourceSeq": 398, + "sourcePayloadSha256": "654346cb0e8d37314ffa5dad3eed4cde99c276575a1b6ab11c8020c25da4a96d", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.199Z" + }, + { + "id": 426, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 426, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "paperclip-paperclip_finish", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": false, + "namespace": null, + "operation": "execute", + "transport": "builtin", + "durationMs": null, + "executionId": "call_5gzZTL4x2r7YXRdqHTwRqEvc", + "outputBytes": 2470, + "inputUpdated": false, + "outputDigest": "sha256:c233767b330e5cbf090b93281f22eb6ecc516587dd19a041c365112d045bc042", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "tool.execution.completed", + "sourceSeq": 399, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:399", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:399", + "sourceSeq": 399, + "sourcePayloadSha256": "ebf8a1ffa07d8b11a33f54cc72d78cc12e778e3ddce1fc57c4f09b498dec1bf2", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.203Z" + }, + { + "id": 430, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 430, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "680a8ed9-dc0d-4068-92a4-e0b26e40be96" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-15", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 403, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:403", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:403", + "sourceSeq": 403, + "sourcePayloadSha256": "15adcf3af8d095c0deeaf12cbab22f78d8384547c03da481a55eaf6f6eee6a9f", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.243Z" + }, + { + "id": 431, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 431, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "Reading shell output", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "Reading shell output (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "680a8ed9-dc0d-4068-92a4-e0b26e40be96", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "tool.execution.started", + "sourceSeq": 404, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:404", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:404", + "sourceSeq": 404, + "sourcePayloadSha256": "0b717074d11726c39bad2f108d549771c5fc549462dfc08ab8554e6b05aa97f8", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.247Z" + }, + { + "id": 432, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 432, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "680a8ed9-dc0d-4068-92a4-e0b26e40be96" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "operation", + "value": "read" + }, + { + "name": "commandToolCallId", + "value": "call_KvAAXBIT5hgUJQhGBZuOnwm0" + }, + { + "name": "shellState", + "value": "completed" + }, + { + "name": "exitCode", + "value": "0" + } + ], + "summary": "Copilot reported command completion with exit code 0.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-c23fbff431f43ac046ac58f9-16", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "eventType": "tool", + "sessionId": "91510691-ffc0-4edd-b373-7a35b312fcbd" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 405, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:405", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:405", + "sourceSeq": 405, + "sourcePayloadSha256": "38969933d243fb3c8d6c02181fc8dd69214df72ba65dcf29235fa578dd36454d", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.251Z" + }, + { + "id": 433, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 433, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "itemId": "item-f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "name": "Reading shell output", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "680a8ed9-dc0d-4068-92a4-e0b26e40be96", + "outputBytes": 117, + "inputUpdated": false, + "outputDigest": "sha256:fbfb07f2103d12470f73d193b2864c215c2db47851a66d824288ff785a83762f", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:36.189Z", + "eventType": "tool.execution.completed", + "sourceSeq": 406, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:406", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:406", + "sourceSeq": 406, + "sourcePayloadSha256": "269fef0e28b252d6f2be1395bfcde0d93aab6129bee32c3e467a746779710b26", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:36.255Z" + }, + { + "id": 466, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 466, + "eventType": "turn.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "error": null, + "status": "completed" + }, + "priority": 1, + "emittedAt": "2026-10-03T23:04:38.040Z", + "eventType": "turn.completed", + "sourceSeq": 439, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:439", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:f93aae13-d84e-496a-a986-717f4cf079cc:439", + "sourceSeq": 439, + "sourcePayloadSha256": "45c557e0b5f249986d28407027fdf3accdf976a6049c96fe99492a22dbc21f9d", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:38.049Z" + }, + { + "id": 469, + "companyId": "73879fd2-bd85-4d5c-873e-f53d1a493ad3", + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "agentId": "23b98756-c577-4492-bbcf-e42f60e772a9", + "seq": 469, + "eventType": "run.result.accepted", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "f93aae13-d84e-496a-a986-717f4cf079cc", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_935e5397ac7f4b609a3379e40f2e0999", + "payload": { + "result": { + "schema": "paperclip.run_result.v1", + "summary": "COPILOT-PROTECTION-attached-async-settlement-9913c6aa6ec8-1", + "evidence": [], + "artifacts": [], + "verification": [], + "completionClaim": { + "criteria": [ + { + "status": "satisfied", + "criterionId": "objective", + "evidenceRefs": [] + } + ], + "remainingWork": [], + "contractRevision": "1", + "objectiveSatisfied": true + }, + "attentionRequests": [], + "reportedWorkDisposition": "done" + } + }, + "priority": 0, + "emittedAt": "2026-10-03T23:04:38.059Z", + "eventType": "run.result.accepted", + "sourceSeq": 1, + "sourceKind": "control_plane", + "schemaVersion": 1, + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:control:f93aae13-d84e-496a-a986-717f4cf079cc:1", + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:control", + "normalizedSessionId": "cd1a1202-07d1-4850-86b4-ccc9430db7de" + } + }, + "sourceInstanceId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:control", + "sourceEventId": "79d45364-42a2-4e2b-9ad9-df89bd63a8b6:control:f93aae13-d84e-496a-a986-717f4cf079cc:1", + "sourceSeq": 1, + "sourcePayloadSha256": "909b95eb037edd80021a7edf466ce5bfe1f77b3aa7fead3bad68577076fb96ed", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:04:38.059Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-context-attached-completion-v15.json b/tests/runner-e2e/fixtures/copilot-context-attached-completion-v15.json new file mode 100644 index 00000000000..402af388113 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-context-attached-completion-v15.json @@ -0,0 +1,1115 @@ +{ + "schema": "paperclip.e2e.copilot-context-completion-fixture.v1", + "provenance": { + "paperclipRevision": "e7e23797589333694fa2f51adcf6fc203268bb7a", + "profileVersion": 15, + "attempt": "local-2026-10-03T20-41-36-004Z", + "originalDisposition": "candidate_failure", + "originalError": "expected one authoritative finish receipt", + "selection": "Only public durable context/command/finish notices and canonical context/completion rows; no provider reasoning or credentials." + }, + "summary": "COPILOT-PROTECTION-attached-async-settlement-1b6ca90f2748-1", + "rows": [ + { + "id": 113, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 113, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_2mh33WksABzS95skV80cUjsc" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:13.299Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 88, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:88", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:88", + "sourceSeq": 88, + "sourcePayloadSha256": "a81b21f85514ebfb6516c6529f973a49fccce17a83e4b23f9d1d297d6c66850d", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:13.355Z" + }, + { + "id": 114, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 114, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "name": "paperclip-get_task_context", + "output": "null", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-get_task_context (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_2mh33WksABzS95skV80cUjsc", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:13.299Z", + "eventType": "tool.execution.started", + "sourceSeq": 89, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:89", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:89", + "sourceSeq": 89, + "sourcePayloadSha256": "c3afda298c3cfeda74a63940cfccd6856a7b6032d3cdf3b85c54a50b92690afb", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:13.360Z" + }, + { + "id": 116, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 116, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "get_task_context" + }, + { + "name": "callIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "inputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "resultSha256", + "value": "491c7c90831af3856eed247c429da34ee0ab38788d1295c7de318b38f9d8c5ed" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "null" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-2", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "semantic_result", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:13.733Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 91, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:91", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:91", + "sourceSeq": 91, + "sourcePayloadSha256": "3379b4a76a4fe26c9cd47198a14f3150ea3148431377d08720beb8dbae0d1568", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:13.769Z" + }, + { + "id": 117, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 117, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_2mh33WksABzS95skV80cUjsc" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "operation", + "value": "read" + }, + { + "name": "semanticOperationId", + "value": "get_task_context" + }, + { + "name": "semanticCallIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "semanticInputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "null" + }, + { + "name": "semanticResultSha256", + "value": "491c7c90831af3856eed247c429da34ee0ab38788d1295c7de318b38f9d8c5ed" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-3", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:13.769Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 92, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:92", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:92", + "sourceSeq": 92, + "sourcePayloadSha256": "e3a4fb35087df2e0c9be552ab4fdfccac71540b94e7f55cb46a558a647c1201b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:13.773Z" + }, + { + "id": 118, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 118, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "name": "paperclip-get_task_context", + "output": "{\"content\":\"{\\\"acceptedPlan\\\":null,\\\"activeTask\\\":{\\\"assigneeAgentId\\\":\\\"f4c7cef4-5228-4815-9880-3db2042dbcf3\\\",\\\"companyId\\\":\\\"6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c\\\",\\\"description\\\":\\\"Run the supplied exact command once using native bash with mode async and detach false.\\\\nAfter starting it, attempt to finish immediately without calling read\\\\\\\\_bash or another waiting tool. The attached-process runtime must settle the command before ending the turn.\\\\nDo not modify fixture code, manufacture its result, or start another command.\\\\nThe fixture-owned marker is a private diagnostic sentinel, not a requested file deliverable. Do not publish or attach it.\\\\nCall paperclip\\\\\\\\_finish with reportedWorkDisposition done, summary COPILOT-PROTECTION-attached-async-settlement-1b6ca90f2748-1, current completion contract revision, satisfied objective criterion, no remaining work, evidence \\\\\\\\[], verification \\\\\\\\[]. After acceptance reply with that exact summary.\\\\nThe exact supplied command is:\\\\n'/private/tmp/paperclip-pi-v5-node-20260928/darwin-arm64/node' '/tmp/pc-copilot-mt2vtc/client.cjs' '/tmp/pc-copilot-mt2vtc/private/socket' '22a1e053ebaa01263678e1942d9bfbfb'\\\\nDo not inspect or modify fixture code, fabricate its marker, or launch a substitute command.\\\",\\\"executionRunId\\\":\\\"63f9b9e9-4797-43eb-bf3d-4449d784c574\\\",\\\"goalId\\\":null,\\\"id\\\":\\\"d1c7dfa0-d11c-45d1-8632-88a8c91eadc6\\\",\\\"identifier\\\":\\\"RUN-1\\\",\\\"parentId\\\":null,\\\"priority\\\":\\\"medium\\\",\\\"projectId\\\":\\\"7c4c67a8-aa32-41e4-aa80-6517c6947b49\\\",\\\"status\\\":\\\"in_progress\\\",\\\"statusVersion\\\":1,\\\"title\\\":\\\"Copilot attached-async-settlement 1b6ca90f2748-1\\\",\\\"titleNeedsGeneration\\\":false,\\\"url\\\":null,\\\"workMode\\\":\\\"standard\\\"},\\\"actor\\\":{\\\"capabilities\\\":\\\"Completes deterministic standard, planning, and ask-mode runner acceptance tasks.\\\",\\\"companyId\\\":\\\"6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c\\\",\\\"id\\\":\\\"f4c7cef4-5228-4815-9880-3db2042dbcf3\\\",\\\"name\\\":\\\"Runner E2E runner-acpx-copilot 1b6ca90f2748-1\\\",\\\"reportsTo\\\":null,\\\"role\\\":\\\"qa\\\",\\\"status\\\":\\\"running\\\",\\\"title\\\":\\\"Paid full-stack runner acceptance fixture\\\"},\\\"childReviewOutcomes\\\":[],\\\"childTasks\\\":[],\\\"childTasksTruncated\\\":false,\\\"company\\\":{\\\"id\\\":\\\"6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c\\\"},\\\"connectionGuidance\\\":\\\"Connection tools:\\\\n- When the user asks to connect a service, call `connections_search` before any service tool, even if already installed. Also search when a task needs a service and usable access is uncertain. Search by its name or capability and follow the returned `instruction`.\\\\n- Use the returned service identifiers and connection tools for setup. Respect recorded user choices; never invent access or ask for credentials in comments.\\\\n- When waiting for user action, finish independent work, then yield without retrying or polling. On continuation, follow the recorded outcome and use the installed connection.\\\\n- Do not use connection tools for arbitrary MCP URLs or unrelated work.\\\",\\\"delegationGuidance\\\":\\\"Reuse existing child tasks for delegated work. Inspect a completed child's comments and documents before creating another task for the same deliverable. Use search_tasks if the child task list is truncated.\\\",\\\"run\\\":{\\\"id\\\":\\\"63f9b9e9-4797-43eb-bf3d-4449d784c574\\\",\\\"invocationSource\\\":\\\"assignment\\\",\\\"status\\\":\\\"running\\\"},\\\"sourcePlanApproval\\\":null}{\\\"schema\\\":\\\"paperclip.semantic_tool_receipt.v2\\\",\\\"operationId\\\":\\\"get_task_context\\\",\\\"callIdentitySha256\\\":\\\"4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce\\\",\\\"inputSha256\\\":\\\"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\\\",\\\"resultSha256\\\":\\\"491c7c90831af3856eed247c429da34ee0ab38788d1295c7de318b38f9d8c5ed\\\",\\\"outcome\\\":\\\"returned\\\",\\\"normalizedInputSha256\\\":null}\",\"detailedContent\":\"{\\\"acceptedPlan\\\":null,\\\"activeTask\\\":{\\\"assigneeAgentId\\\":\\\"f4c7cef4-5228-4815-9880-3db2042dbcf3\\\",\\\"companyId\\\":\\\"6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c\\\",\\\"description\\\":\\\"Run the supplied exact command once using native bash with mode async and detach false.\\\\nAfter starting it, attempt to finish immediately without calling read\\\\\\\\_b\u2026[truncated]", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_2mh33WksABzS95skV80cUjsc", + "outputBytes": 4110, + "inputUpdated": false, + "outputDigest": "sha256:20689370ad16807828eccd73f515ebac1b39919d8d95013892b669735f18952e", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:13.769Z", + "eventType": "tool.execution.completed", + "sourceSeq": 93, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:93", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:93", + "sourceSeq": 93, + "sourcePayloadSha256": "0696ce4b13d44bc38ffe4ee0af0a5697505362e965371c659c21787a11f020ec", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:13.779Z" + }, + { + "id": 122, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 122, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_AYt2DjYP97SNsHfpePOYFoIL" + }, + { + "name": "operation", + "value": "execute" + }, + { + "name": "commandSha256", + "value": "sha256:a35aa2bad106c54363592042c708ffac425e4d7ab7c72e025e0c4c4b76aa8183" + }, + { + "name": "mode", + "value": "async" + }, + { + "name": "detach", + "value": "false" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-4", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:16.104Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 97, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:97", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:97", + "sourceSeq": 97, + "sourcePayloadSha256": "1945f203d9e7e32c1c38a3a4439cb754fbbae0ac11c578ffbdad325f8ffdf1c6", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:16.108Z" + }, + { + "id": 137, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 137, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_AYt2DjYP97SNsHfpePOYFoIL" + }, + { + "name": "operation", + "value": "execute" + }, + { + "name": "commandSha256", + "value": "sha256:a35aa2bad106c54363592042c708ffac425e4d7ab7c72e025e0c4c4b76aa8183" + }, + { + "name": "mode", + "value": "async" + }, + { + "name": "detach", + "value": "false" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "commandToolCallId", + "value": "call_AYt2DjYP97SNsHfpePOYFoIL" + }, + { + "name": "shellState", + "value": "started" + } + ], + "summary": "Copilot started an attached command.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-5", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:16.104Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 112, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:112", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:112", + "sourceSeq": 112, + "sourcePayloadSha256": "e5aa2d0f513e13214ab293c2dbb15a3d992c0d32b472c3c96f2fabcd248a51dd", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:16.168Z" + }, + { + "id": 225, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 225, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_dv0cqVImwdZSXe5vgHKqjKTU" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-6", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:20.406Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 200, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:200", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:200", + "sourceSeq": 200, + "sourcePayloadSha256": "7c1089d4531aaffe09178e8e2ff515ceb31c17b0e35de983d629fea29040db66", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:20.442Z" + }, + { + "id": 240, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 240, + "eventType": "run.result.proposed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "4", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "schema": "paperclip.run_result.v1", + "summary": "COPILOT-PROTECTION-attached-async-settlement-1b6ca90f2748-1", + "evidence": [], + "artifacts": [], + "verification": [], + "completionClaim": { + "criteria": [ + { + "status": "satisfied", + "criterionId": "objective", + "evidenceRefs": [] + } + ], + "remainingWork": [], + "contractRevision": "1", + "objectiveSatisfied": true + }, + "attentionRequests": [], + "reportedWorkDisposition": "done" + }, + "priority": 0, + "emittedAt": "2026-10-03T20:42:20.419Z", + "eventType": "run.result.proposed", + "sourceSeq": 215, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:215", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:215", + "sourceSeq": 215, + "sourcePayloadSha256": "a8877c6fc8faae6419262e2f025b59364720a59d01085918a8870d2934bbf170", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:20.525Z" + }, + { + "id": 241, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 241, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "paperclip_finish" + }, + { + "name": "callIdentitySha256", + "value": "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a" + }, + { + "name": "inputSha256", + "value": "f4588361bd294c1231771137b32ec8eb4dec247a37b8cf75846641f3a1fb06f1" + }, + { + "name": "resultSha256", + "value": "ed09c64c04798ab9e6108f46c431bbaa3abc4876057b72b8ee421a44c05e3587" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "94ce39ee0fa76a7ad75d6e6433f6653ab43e0d368a605915b0b0d491fd9d2c58" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-7", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "semantic_result", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:20.916Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 216, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:216", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:216", + "sourceSeq": 216, + "sourcePayloadSha256": "f167c8c72089cc8bf9b803dd1407f61315e9e3031eacef292ed8dca9aab98e09", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:21.014Z" + }, + { + "id": 242, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 242, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_dv0cqVImwdZSXe5vgHKqjKTU" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "semanticOperationId", + "value": "paperclip_finish" + }, + { + "name": "semanticCallIdentitySha256", + "value": "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a" + }, + { + "name": "semanticInputSha256", + "value": "f4588361bd294c1231771137b32ec8eb4dec247a37b8cf75846641f3a1fb06f1" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "94ce39ee0fa76a7ad75d6e6433f6653ab43e0d368a605915b0b0d491fd9d2c58" + }, + { + "name": "semanticResultSha256", + "value": "ed09c64c04798ab9e6108f46c431bbaa3abc4876057b72b8ee421a44c05e3587" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-8", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:21.014Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 217, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:217", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:217", + "sourceSeq": 217, + "sourcePayloadSha256": "707b798fa36c5909b75ef64a5323d952777f8ee0adb5a7b33ecac3f4520f94b4", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:21.043Z" + }, + { + "id": 247, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 247, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "885d9297-6411-4e75-a53a-04a602e4ad5e" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-9", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:21.014Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 222, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:222", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:222", + "sourceSeq": 222, + "sourcePayloadSha256": "e90defa85694e079782ac8243f93654b4b375752d379ebb2fcb6026dc01469d4", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:21.084Z" + }, + { + "id": 249, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 249, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "itemId": "item-63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "885d9297-6411-4e75-a53a-04a602e4ad5e" + }, + { + "name": "shellId", + "value": "0" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "operation", + "value": "read" + }, + { + "name": "commandToolCallId", + "value": "call_AYt2DjYP97SNsHfpePOYFoIL" + }, + { + "name": "shellState", + "value": "completed" + }, + { + "name": "exitCode", + "value": "0" + } + ], + "summary": "Copilot reported command completion with exit code 0.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-ae440745f9c60b9710ca3cff-10", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "eventType": "tool", + "sessionId": "0c94e846-c188-4830-8107-868c912340d1" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:21.014Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 224, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:224", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:224", + "sourceSeq": 224, + "sourcePayloadSha256": "06a5b556a4dd0d9b5023620e6ea094d1cae7e600536fafd8e83e0a7d4e2aaf28", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:21.093Z" + }, + { + "id": 283, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 283, + "eventType": "turn.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "error": null, + "status": "completed" + }, + "priority": 1, + "emittedAt": "2026-10-03T20:42:23.360Z", + "eventType": "turn.completed", + "sourceSeq": 258, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:258", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:63f9b9e9-4797-43eb-bf3d-4449d784c574:258", + "sourceSeq": 258, + "sourcePayloadSha256": "f1eafafef92aa1e448c839827428404d66ed6d3e4328803148312b339765414e", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:23.371Z" + }, + { + "id": 285, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 285, + "eventType": "turn.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "error": null, + "status": "completed", + "prpSourceSeq": 258, + "prpSourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c" + }, + "sourceInstanceId": null, + "sourceEventId": null, + "sourceSeq": null, + "sourcePayloadSha256": null, + "protocolSchemaVersion": null, + "createdAt": "2026-10-03T20:42:23.378Z" + }, + { + "id": 286, + "companyId": "6a051e4f-7ace-4ec9-8ab1-dc3b025d9c1c", + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "agentId": "f4c7cef4-5228-4815-9880-3db2042dbcf3", + "seq": 286, + "eventType": "run.result.accepted", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "63f9b9e9-4797-43eb-bf3d-4449d784c574", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_13cc1481467246b2965da1fee3b0c73f", + "payload": { + "result": { + "schema": "paperclip.run_result.v1", + "summary": "COPILOT-PROTECTION-attached-async-settlement-1b6ca90f2748-1", + "evidence": [], + "artifacts": [], + "verification": [], + "completionClaim": { + "criteria": [ + { + "status": "satisfied", + "criterionId": "objective", + "evidenceRefs": [] + } + ], + "remainingWork": [], + "contractRevision": "1", + "objectiveSatisfied": true + }, + "attentionRequests": [], + "reportedWorkDisposition": "done" + } + }, + "priority": 0, + "emittedAt": "2026-10-03T20:42:23.382Z", + "eventType": "run.result.accepted", + "sourceSeq": 1, + "sourceKind": "control_plane", + "schemaVersion": 1, + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:control:63f9b9e9-4797-43eb-bf3d-4449d784c574:1", + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:control", + "normalizedSessionId": "2c865203-a948-4631-afdf-c4c2367139cb" + } + }, + "sourceInstanceId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:control", + "sourceEventId": "e3bbeb49-9769-4d33-b5ec-de7505f6782c:control:63f9b9e9-4797-43eb-bf3d-4449d784c574:1", + "sourceSeq": 1, + "sourcePayloadSha256": "becd28fec8b37c9b2ce562882aeb25cff6caadb7f5334b0252a8151b83937898", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T20:42:23.382Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v15.json b/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v15.json new file mode 100644 index 00000000000..73d04e4f512 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v15.json @@ -0,0 +1,296 @@ +{ + "schema": "paperclip.e2e.retained-copilot-context-discovery.v1", + "sourceRevision": "90b14b00b7a56d48a6d203545d04753750f864d7", + "retainedResult": "local-2026-10-03T22-19-31-840Z/copilot-protection/runner-acpx-copilot/local/native-permission-deny-write/attempt-1", + "outcome": "failed; unanswered read-only discovery card; calibration only", + "rows": [ + { + "id": 38, + "companyId": "578f9e01-20ff-4db1-885d-2671eafdcb50", + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "agentId": "f74461b5-4959-46e7-8361-b7df67a15aa7", + "seq": 38, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "payload": { + "request": { + "type": "permission", + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "prompt": "search_api", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "reason": "search_api", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-a0dcc0a913a8deb4f50f5f0c0ceb407243221e79c368a416df3af9231902b1b3" + }, + "requestId": "acpx-input-0074c32d9558181355984bac", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-03T22:20:03.585Z", + "eventType": "runtime_request.created", + "sourceSeq": 15, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:15", + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "normalizedSessionId": "fe6fb523-12e2-4f05-964d-ca0ff2b535e0" + } + }, + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:15", + "sourceSeq": 15, + "sourcePayloadSha256": "42162563ef331950dbf0dab8738b4b954f5e8842b179504f9810ea0c691cf94e", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T22:20:03.594Z" + }, + { + "id": 39, + "companyId": "578f9e01-20ff-4db1-885d-2671eafdcb50", + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "agentId": "f74461b5-4959-46e7-8361-b7df67a15aa7", + "seq": 39, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_5TMnOe8eC08O6s2DT93LNT6B" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-7f339408bfe222bbcdca74ee-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "eventType": "tool", + "sessionId": "54158a5d-0061-4aee-85f2-aa4d755ea54e" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T22:20:03.585Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 16, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:16", + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "normalizedSessionId": "fe6fb523-12e2-4f05-964d-ca0ff2b535e0" + } + }, + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:16", + "sourceSeq": 16, + "sourcePayloadSha256": "e93010406ca30fa0d533dd102c61cdf32d773ca8a4919d4b620ba41f2bb16b3f", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T22:20:03.601Z" + }, + { + "id": 40, + "companyId": "578f9e01-20ff-4db1-885d-2671eafdcb50", + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "agentId": "f74461b5-4959-46e7-8361-b7df67a15aa7", + "seq": 40, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "payload": { + "name": "paperclip-search_api", + "output": "null", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-search_api (pending): get_task_context", + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_5TMnOe8eC08O6s2DT93LNT6B", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T22:20:03.585Z", + "eventType": "tool.execution.started", + "sourceSeq": 17, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:17", + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "normalizedSessionId": "fe6fb523-12e2-4f05-964d-ca0ff2b535e0" + } + }, + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:17", + "sourceSeq": 17, + "sourcePayloadSha256": "bea6445ec3425ba5aad574749cee68e17d0745a23d7037b8f29e1b4ccc7dd4a1", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T22:20:03.605Z" + }, + { + "id": 42, + "companyId": "578f9e01-20ff-4db1-885d-2671eafdcb50", + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "agentId": "f74461b5-4959-46e7-8361-b7df67a15aa7", + "seq": 42, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "8f63e1b0-749c-483e-8f46-a1d112d27682", + "itemId": "item-8f63e1b0-749c-483e-8f46-a1d112d27682", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "call_5TMnOe8eC08O6s2DT93LNT6B" + }, + { + "name": "requestId", + "value": "acpx-input-0074c32d9558181355984bac" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-7f339408bfe222bbcdca74ee-2", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_e9c47d3e1de54cdc84bc319b205ad2bd", + "eventType": "permission_requested", + "sessionId": "54158a5d-0061-4aee-85f2-aa4d755ea54e" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T22:20:03.585Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 18, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:18", + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "normalizedSessionId": "fe6fb523-12e2-4f05-964d-ca0ff2b535e0" + } + }, + "sourceInstanceId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523", + "sourceEventId": "7e5b6c31-f5d5-4edb-aa44-f18d8aa7c523:8f63e1b0-749c-483e-8f46-a1d112d27682:18", + "sourceSeq": 18, + "sourcePayloadSha256": "adad7b07f2129c3ca09b3722fafc9ea5fc6b4ff7bdbc3525c71434b8a37eed0b", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T22:20:03.617Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v17.json b/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v17.json new file mode 100644 index 00000000000..237afb01dc2 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-context-discovery-permission-v17.json @@ -0,0 +1,296 @@ +{ + "schema": "paperclip.e2e.retained-copilot-context-discovery.v1", + "sourceRevision": "e00fe7ba5952533f6036f7abb883d110992889f8", + "retainedResult": "tests/runner-e2e/results/local-2026-10-04T00-21-23-068Z/copilot-provider-death/runner-acpx-copilot/local/pending-permission-provider-death/attempt-1", + "outcome": "failed before provider-death dispatch; unanswered read-only discovery card; calibration only", + "rows": [ + { + "id": 38, + "companyId": "61e76084-01a0-4f2b-bfb7-fd5591473107", + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "agentId": "3a7f1016-cbea-4767-96a7-e0e6b6120345", + "seq": 38, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "payload": { + "request": { + "type": "permission", + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "prompt": "search_api", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "reason": "search_api", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-3dc0e2121b9b0232adf0e633c60d6190ef9047753210ca9a51ef02830a4af613" + }, + "requestId": "acpx-input-5dab1f28c05b3e9ae25de1ec", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-04T00:21:55.035Z", + "eventType": "runtime_request.created", + "sourceSeq": 15, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:15", + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "normalizedSessionId": "9aaa3f9d-8a7d-4d73-8704-b0efd500c4fc" + } + }, + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:15", + "sourceSeq": 15, + "sourcePayloadSha256": "717355680d3d00818585dab3af3f5b3a8c47871a9f1586f0287d9519c79ab742", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-04T00:21:55.043Z" + }, + { + "id": 39, + "companyId": "61e76084-01a0-4f2b-bfb7-fd5591473107", + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "agentId": "3a7f1016-cbea-4767-96a7-e0e6b6120345", + "seq": 39, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_fVtZbx73Gy42MZEv3TJroCQH" + }, + { + "name": "status", + "value": "pending" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-06be8b16b6645f5f69de0fb6-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "eventType": "tool", + "sessionId": "e735fdac-c742-4b28-8d36-02ee5f4cbec0" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-04T00:21:55.035Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 16, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:16", + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "normalizedSessionId": "9aaa3f9d-8a7d-4d73-8704-b0efd500c4fc" + } + }, + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:16", + "sourceSeq": 16, + "sourcePayloadSha256": "734364491d39e51a78538fc6d55e668d8660831427d7ba8dcdf1b2f4e753c126", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-04T00:21:55.047Z" + }, + { + "id": 40, + "companyId": "61e76084-01a0-4f2b-bfb7-fd5591473107", + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "agentId": "3a7f1016-cbea-4767-96a7-e0e6b6120345", + "seq": 40, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "payload": { + "name": "paperclip-search_api", + "output": "null", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-search_api (pending): get task context", + "readOnly": true, + "namespace": null, + "operation": "search", + "transport": "builtin", + "durationMs": null, + "executionId": "call_fVtZbx73Gy42MZEv3TJroCQH", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-04T00:21:55.035Z", + "eventType": "tool.execution.started", + "sourceSeq": 17, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:17", + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "normalizedSessionId": "9aaa3f9d-8a7d-4d73-8704-b0efd500c4fc" + } + }, + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:17", + "sourceSeq": 17, + "sourcePayloadSha256": "50614b7c76d807802cbc9a39afab04930c01129112a31c0647d1937eb752c282", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-04T00:21:55.050Z" + }, + { + "id": 42, + "companyId": "61e76084-01a0-4f2b-bfb7-fd5591473107", + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "agentId": "3a7f1016-cbea-4767-96a7-e0e6b6120345", + "seq": 42, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "975a7c7e-6a92-4907-afc7-aee73203f469", + "itemId": "item-975a7c7e-6a92-4907-afc7-aee73203f469", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "call_fVtZbx73Gy42MZEv3TJroCQH" + }, + { + "name": "requestId", + "value": "acpx-input-5dab1f28c05b3e9ae25de1ec" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-06be8b16b6645f5f69de0fb6-2", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_61222317c2304f13ab1e4fca31b8d578", + "eventType": "permission_requested", + "sessionId": "e735fdac-c742-4b28-8d36-02ee5f4cbec0" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-04T00:21:55.035Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 18, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:18", + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "normalizedSessionId": "9aaa3f9d-8a7d-4d73-8704-b0efd500c4fc" + } + }, + "sourceInstanceId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9", + "sourceEventId": "51f1666e-be8e-4da4-9812-4cad3f37d9d9:975a7c7e-6a92-4907-afc7-aee73203f469:18", + "sourceSeq": 18, + "sourcePayloadSha256": "7b6546f06f2ab8d6aeada545cca4b515ae387b98a1b05c93fefaeebf40737cad", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-04T00:21:55.061Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-context-permission-v15.json b/tests/runner-e2e/fixtures/copilot-context-permission-v15.json new file mode 100644 index 00000000000..c5505672d61 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-context-permission-v15.json @@ -0,0 +1,303 @@ +{ + "schema": "paperclip.e2e.copilot-context-permission-fixture.v1", + "provenance": { + "source": "b41e8e2b6aa97a734ddfc363cd2466831a5b9895", + "attempt": "local-2026-10-03T21-21-10-776Z", + "originalGrade": "failed: timed out while initial context permission was unanswered", + "selection": "Public permission and read-origin rows only; no reasoning or credentials." + }, + "rows": [ + { + "id": 38, + "companyId": "acfabb15-ad54-40b3-b288-13ec06abe4b8", + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "agentId": "efc5ae8f-9876-4c1a-bad0-21772e849d90", + "seq": 38, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "payload": { + "request": { + "type": "permission", + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "prompt": "get_task_context", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "reason": "get_task_context", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-ad8774ab01e9c0d2f68bdd24378679c2ae71490f9bf41e3096d5708331765383" + }, + "requestId": "acpx-input-77b887edc8c38070824692b3", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-03T21:21:41.558Z", + "eventType": "runtime_request.created", + "sourceSeq": 15, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:15", + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "normalizedSessionId": "8e0f95e7-8073-4453-b639-b9d5daf3d7c4" + } + }, + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:15", + "sourceSeq": 15, + "sourcePayloadSha256": "f6b7ec5dc043205cfb96191a4bb7cb6067348bfee95201f62a01c6b9349815b4", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T21:21:41.571Z" + }, + { + "id": 39, + "companyId": "acfabb15-ad54-40b3-b288-13ec06abe4b8", + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "agentId": "efc5ae8f-9876-4c1a-bad0-21772e849d90", + "seq": 39, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_tIdO9gSPTCuzpKlMPlIa7kNT" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-aee0303132a18925ffaebb35-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "eventType": "tool", + "sessionId": "cd67831d-ce2e-4d4d-b16a-55ae787e707e" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T21:21:41.558Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 16, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:16", + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "normalizedSessionId": "8e0f95e7-8073-4453-b639-b9d5daf3d7c4" + } + }, + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:16", + "sourceSeq": 16, + "sourcePayloadSha256": "bae2a1d20364263627919f037a766ec72821b0a63b29f2fd59eeba82fb0624cb", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T21:21:41.575Z" + }, + { + "id": 40, + "companyId": "acfabb15-ad54-40b3-b288-13ec06abe4b8", + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "agentId": "efc5ae8f-9876-4c1a-bad0-21772e849d90", + "seq": 40, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "payload": { + "name": "paperclip-get_task_context", + "output": "null", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-get_task_context (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_tIdO9gSPTCuzpKlMPlIa7kNT", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T21:21:41.558Z", + "eventType": "tool.execution.started", + "sourceSeq": 17, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:17", + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "normalizedSessionId": "8e0f95e7-8073-4453-b639-b9d5daf3d7c4" + } + }, + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:17", + "sourceSeq": 17, + "sourcePayloadSha256": "6e895fbc42b0858d1f0cc354ea6f32e93c75584ce687d1ed53c43ba69e08beee", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T21:21:41.578Z" + }, + { + "id": 42, + "companyId": "acfabb15-ad54-40b3-b288-13ec06abe4b8", + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "agentId": "efc5ae8f-9876-4c1a-bad0-21772e849d90", + "seq": 42, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "34412568-43a8-47ed-8c91-91a2974562d2", + "itemId": "item-34412568-43a8-47ed-8c91-91a2974562d2", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "call_tIdO9gSPTCuzpKlMPlIa7kNT" + }, + { + "name": "requestId", + "value": "acpx-input-77b887edc8c38070824692b3" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-aee0303132a18925ffaebb35-2", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_e3bf9964656b47bdb71b6cf1ba78912d", + "eventType": "permission_requested", + "sessionId": "cd67831d-ce2e-4d4d-b16a-55ae787e707e" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T21:21:41.558Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 18, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:18", + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "normalizedSessionId": "8e0f95e7-8073-4453-b639-b9d5daf3d7c4" + } + }, + "sourceInstanceId": "59b8d1e1-cd19-447b-b60f-00a03acc4584", + "sourceEventId": "59b8d1e1-cd19-447b-b60f-00a03acc4584:34412568-43a8-47ed-8c91-91a2974562d2:18", + "sourceSeq": 18, + "sourcePayloadSha256": "61bbc7742a510fe8a62a795b1c16a9ea3b481467d88ebbdff55ea648ff865f35", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T21:21:41.588Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-context-permission-v26.json b/tests/runner-e2e/fixtures/copilot-context-permission-v26.json new file mode 100644 index 00000000000..729e9bd36db --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-context-permission-v26.json @@ -0,0 +1,306 @@ +{ + "schema": "paperclip.e2e.copilot-context-permission-fixture.v1", + "provenance": { + "source": "f43b483517f952121c426cc58e6fb4c361577602", + "runtimeSource": "3073c062d47891cc275072de4eaaf049f5837839", + "attempt": "copilot-v26-local-denial-01", + "originalGrade": "failed: current sidecar Copilot origin rejected by historical context fixture", + "selection": "Exact public context permission and read-origin rows only; no model text or credentials.", + "apiStateSha256": "9d8981ee034c07744c87894e8939bdd7ed3a4882fc2954dd7bbbdd78f702e6cd" + }, + "rows": [ + { + "id": 38, + "companyId": "22f78234-c769-4fd7-b216-99dc12f088f2", + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "agentId": "ad45a4b4-067a-4c59-94de-db99c4674c54", + "seq": 38, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_ZURUpVXGfBtUt9Ev2V9mqCyN" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-550fe5ba6f88ad9c180bb3a6-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "eventType": "tool", + "sessionId": "bf102626-34e9-4597-b440-6ccb851fc6e0" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-08T00:41:20.073Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 15, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:15", + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "normalizedSessionId": "ad5649d3-696e-45d4-8910-8ddb61439340" + } + }, + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:15", + "sourceSeq": 15, + "sourcePayloadSha256": "b829c1d45c49fa61cd0143fa224095b0cf79fdae4b94372496f87c7c1f5e7eb6", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-08T00:41:20.086Z" + }, + { + "id": 39, + "companyId": "22f78234-c769-4fd7-b216-99dc12f088f2", + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "agentId": "ad45a4b4-067a-4c59-94de-db99c4674c54", + "seq": 39, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "payload": { + "name": "paperclip-get_task_context", + "output": "null", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-get_task_context (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_ZURUpVXGfBtUt9Ev2V9mqCyN", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-08T00:41:20.086Z", + "eventType": "tool.execution.started", + "sourceSeq": 16, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:16", + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "normalizedSessionId": "ad5649d3-696e-45d4-8910-8ddb61439340" + } + }, + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:16", + "sourceSeq": 16, + "sourcePayloadSha256": "05a9ff0a2e6e2708604dc44ad4516ce82880c8bb0b50b92c73f0d1fe4c2af503", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-08T00:41:20.094Z" + }, + { + "id": 41, + "companyId": "22f78234-c769-4fd7-b216-99dc12f088f2", + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "agentId": "ad45a4b4-067a-4c59-94de-db99c4674c54", + "seq": 41, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "call_ZURUpVXGfBtUt9Ev2V9mqCyN" + }, + { + "name": "requestId", + "value": "acpx-input-7ea2516111a915df66eb8c03" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-550fe5ba6f88ad9c180bb3a6-2", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "eventType": "permission_requested", + "sessionId": "bf102626-34e9-4597-b440-6ccb851fc6e0" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-08T00:41:20.086Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 17, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:17", + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "normalizedSessionId": "ad5649d3-696e-45d4-8910-8ddb61439340" + } + }, + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:17", + "sourceSeq": 17, + "sourcePayloadSha256": "8142d6ca2b08865eda6b2eef8a721973ccfec164dc5546d941e185fb9c9391a8", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-08T00:41:20.129Z" + }, + { + "id": 42, + "companyId": "22f78234-c769-4fd7-b216-99dc12f088f2", + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "agentId": "ad45a4b4-067a-4c59-94de-db99c4674c54", + "seq": 42, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "payload": { + "request": { + "type": "permission", + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "copilot" + }, + "prompt": "get_task_context", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-5c1ddc98-5f3c-4469-ad1b-ae825af1fa44", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "copilot" + }, + "reason": "get_task_context", + "turnId": "turn_lab_73aaed4624a44a448bd3d3fe0d0bdb8a", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-80e180f467cfbdfe68e90384027c4b1fe0f65dc94991b7f6a67321043b66f912", + "toolCallId": "call_ZURUpVXGfBtUt9Ev2V9mqCyN" + }, + "requestId": "acpx-input-7ea2516111a915df66eb8c03", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-08T00:41:20.088Z", + "eventType": "runtime_request.created", + "sourceSeq": 18, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:18", + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "normalizedSessionId": "ad5649d3-696e-45d4-8910-8ddb61439340" + } + }, + "sourceInstanceId": "70076d7e-5eed-4d98-869b-44a49e167abf", + "sourceEventId": "70076d7e-5eed-4d98-869b-44a49e167abf:5c1ddc98-5f3c-4469-ad1b-ae825af1fa44:18", + "sourceSeq": 18, + "sourcePayloadSha256": "a1705b655a8c7831a2458b74a3d433d0948e14bb8494f174c7b43afaa98bdff1", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-08T00:41:20.138Z" + } + ] +} diff --git a/tests/runner-e2e/fixtures/copilot-mainline-stop-ack-v15.json b/tests/runner-e2e/fixtures/copilot-mainline-stop-ack-v15.json new file mode 100644 index 00000000000..b2b3a5947dc --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-mainline-stop-ack-v15.json @@ -0,0 +1,55 @@ +{ + "schema": "paperclip.e2e.retained-mainline-stop.v1", + "sourceRevision": "2dd62ad52b8b4348d286c59e11e36144305bca6c", + "outcome": "failed fixture expected unsupported caller-owned cancellation ID; calibration only", + "run": { + "id": "be098444-7780-4afc-976a-4cfbc9565eb2", + "companyId": "045e3eb6-1f10-47dd-9931-7f4e0396d682", + "nativeIssueId": "7f425402-9ec8-4011-9e37-6f6d4d3a7153", + "runtimeMode": "native", + "status": "cancelled", + "resultJson": { + "stopReason": "cancelled", + "cancellation": { + "reason": "Cancelled by a board operator", + "source": "operator", + "expected": true, + "initiator": { + "id": "local-board", + "type": "user" + }, + "recordedAt": "2026-10-03T22:42:57.243Z" + }, + "timeoutFired": false, + "timeoutSource": "config", + "cancelledByUserId": "local-board", + "timeoutConfigured": true, + "nativeCancellation": { + "runId": "be098444-7780-4afc-976a-4cfbc9565eb2", + "scope": "run", + "schema": "paperclip.native-cancellation.v1", + "effects": [ + "release_run_resources" + ], + "issueId": "7f425402-9ec8-4011-9e37-6f6d4d3a7153", + "intentId": "native-cancellation:88c9dd8f-92d2-407d-a989-a3495e8e6944", + "companyId": "045e3eb6-1f10-47dd-9931-7f4e0396d682", + "decisionId": null, + "dispatched": true, + "reasonCode": "cancellation_run_only", + "recordedAt": "2026-10-03T22:42:57.247Z", + "dispatchState": "acknowledged", + "intentAuditId": "ceab3cf4-3bec-4133-a73b-15101f3035b2", + "acknowledgedAt": "2026-10-03T22:42:57.249Z", + "acknowledgementAuditId": "87b5cd2d-6c5e-4beb-91e7-3392d0868036", + "priorCoordinatorDecisionId": null + }, + "effectiveTimeoutSec": 120, + "startupCancellation": { + "requestedAt": "2026-10-03T22:42:57.243Z", + "beforeNativeSelection": false + }, + "cancelledByActorType": "user" + } + } +} diff --git a/tests/runner-e2e/fixtures/copilot-patch-permission-target-v15.json b/tests/runner-e2e/fixtures/copilot-patch-permission-target-v15.json new file mode 100644 index 00000000000..a9d2fd8c419 --- /dev/null +++ b/tests/runner-e2e/fixtures/copilot-patch-permission-target-v15.json @@ -0,0 +1,964 @@ +{ + "schema": "paperclip.e2e.retained-patch-permission.v1", + "sourceRevision": "7f6238747165ba2b420fff48ec3a71f6ca594f21", + "outcome": "failed fixture required notification path and redundant card ID; sanitized pre-Stop calibration only", + "scope": { + "provider": "copilot", + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "issueId": "af0fd9f5-9bdd-42f4-a214-328919e315f7", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "target": "pc-denied-dUYoVS/active-stop-80fcac4754d6-1.txt", + "requireContextRead": true + }, + "rows": [ + { + "id": 119, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 119, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "request": { + "type": "permission", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "prompt": "get_task_context", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "reason": "get_task_context", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-bb9ec77ae62affebe9269afb211e8cff11b30059ee6155586f1ba46ef3758033" + }, + "requestId": "acpx-input-5f82c73c88b9dec922b29e72", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:25.534Z", + "eventType": "runtime_request.created", + "sourceSeq": 95, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:95", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:95", + "sourceSeq": 95, + "sourcePayloadSha256": "eda0b599abce54f82f848325581fb142f53059e4e316d23362a0d1f46c8ffb46", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:25.547Z" + }, + { + "id": 120, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 120, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "read" + } + ], + "summary": "Copilot tool operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-1", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "tool", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:25.534Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 96, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:96", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:96", + "sourceSeq": 96, + "sourcePayloadSha256": "b9878e600359fa61e28676ef597326323afe30bc50bdd7aece097c49598fa138", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:25.558Z" + }, + { + "id": 121, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 121, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "name": "paperclip-get_task_context", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "paperclip-get_task_context (pending)", + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:25.534Z", + "eventType": "tool.execution.started", + "sourceSeq": 97, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:97", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:97", + "sourceSeq": 97, + "sourcePayloadSha256": "0c4214a7e47ecf29e5bf2296dad0a06e4d8b028c3dd32ea9756bf26287033e8c", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:25.562Z" + }, + { + "id": 122, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 122, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF" + }, + { + "name": "requestId", + "value": "acpx-input-5f82c73c88b9dec922b29e72" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-2", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "permission_requested", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:25.534Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 98, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:98", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:98", + "sourceSeq": 98, + "sourcePayloadSha256": "9da4e3c45baa601b100f472e3859ba1b0e00e80f685a4e3c5ba2653c3b9bfd78", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:25.566Z" + }, + { + "id": 123, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 123, + "eventType": "runtime_request.resolved", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "action": "accept", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "requestId": "acpx-input-5f82c73c88b9dec922b29e72", + "requestKind": "permission_approval" + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:26.074Z", + "eventType": "runtime_request.resolved", + "sourceSeq": 99, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:99", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:99", + "sourceSeq": 99, + "sourcePayloadSha256": "05f1aa32fceb8ae5a3ce2c3cfbc7315d2c658d302bb866c81dc0cf8fcdcea066", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:26.075Z" + }, + { + "id": 124, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 124, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_delivered" + }, + { + "name": "toolCallId", + "value": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF" + }, + { + "name": "requestId", + "value": "acpx-input-5f82c73c88b9dec922b29e72" + }, + { + "name": "declineOffered", + "value": "true" + }, + { + "name": "outcome", + "value": "allow_once" + } + ], + "summary": "Copilot received your permission decision.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-3", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "permission_delivered", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:26.664Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 100, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:100", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:100", + "sourceSeq": 100, + "sourcePayloadSha256": "fe0d38bdaf1e6e14678679ae7af4192f54c43ea1b505ac0d4e88d6a274d28bd5", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:26.675Z" + }, + { + "id": 128, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 128, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "semantic_result" + }, + { + "name": "schema", + "value": "paperclip.semantic_tool_receipt.v2" + }, + { + "name": "operationId", + "value": "get_task_context" + }, + { + "name": "callIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "inputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "resultSha256", + "value": "8b849d7fd1959bdad785ac51b7e6cfd6ba9fa42e986c3dfdb17ad32b0ec212eb" + }, + { + "name": "outcome", + "value": "returned" + }, + { + "name": "normalizedInputSha256", + "value": "null" + } + ], + "summary": "Paperclip returned a semantic tool result.", + "category": "paperclip_semantic_tool_receipt_v2", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-4", + "severity": "info", + "provenance": { + "method": "paperclip/semantic_tool_result", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "semantic_result", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:27.096Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 103, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:103", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:103", + "sourceSeq": 103, + "sourcePayloadSha256": "cbedf804f3e26eeae5afbc431bff2db3acdbecf7fd521fc988a682a465f642b1", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:27.139Z" + }, + { + "id": 129, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 129, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF" + }, + { + "name": "status", + "value": "completed" + }, + { + "name": "operation", + "value": "read" + }, + { + "name": "semanticOperationId", + "value": "get_task_context" + }, + { + "name": "semanticCallIdentitySha256", + "value": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce" + }, + { + "name": "semanticInputSha256", + "value": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + { + "name": "semanticNormalizedInputSha256", + "value": "null" + }, + { + "name": "semanticResultSha256", + "value": "8b849d7fd1959bdad785ac51b7e6cfd6ba9fa42e986c3dfdb17ad32b0ec212eb" + }, + { + "name": "semanticOutcome", + "value": "returned" + } + ], + "summary": "Copilot tool operation: completed.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-5", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "tool", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:27.139Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 104, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:104", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:104", + "sourceSeq": 104, + "sourcePayloadSha256": "809ae9305e5de108d9e2ff4131e62ea6bb8dc96402ddfbe59f49463ad9eec80a", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:27.151Z" + }, + { + "id": 130, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 130, + "eventType": "tool.execution.completed", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "name": "paperclip-get_task_context", + "schema": "paperclip.tool.execution.v1", + "status": "completed", + "target": null, + "exitCode": null, + "progress": null, + "readOnly": true, + "namespace": null, + "operation": "read", + "transport": "builtin", + "durationMs": null, + "executionId": "call_Qtd5aWLn5Mv1dNLnNEo7dkgF", + "outputBytes": 4110, + "inputUpdated": false, + "outputDigest": "sha256:aea3a374b88ed64d03eb099029c1c5c3ae244ba1e38daa7b8a5aa15d53e048a0", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:27.139Z", + "eventType": "tool.execution.completed", + "sourceSeq": 105, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:105", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:105", + "sourceSeq": 105, + "sourcePayloadSha256": "4f7b8bcdefe69f0981507f1a3c11ec013337e8b0c0e1b23207a5d6ece3d8f0dd", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:27.155Z" + }, + { + "id": 134, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 134, + "eventType": "runtime_request.created", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "request": { + "type": "permission", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "method": "session/request_permission", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "prompt": "Change file: pc-denied-dUYoVS/active-stop-80fcac4754d6-1.txt", + "schema": "paperclip.runtime_request.v2", + "status": "pending", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "details": { + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "origin": { + "method": "session/request_permission", + "adapter": "acpx-runtime-sidecar", + "provider": "acpx" + }, + "reason": "Change file: pc-denied-dUYoVS/active-stop-80fcac4754d6-1.txt", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "choices": [ + { + "key": "accept", + "label": "Allow once" + }, + { + "key": "accept_for_session", + "label": "Allow for session" + }, + { + "key": "decline", + "label": "Deny" + }, + { + "key": "cancel", + "label": "Cancel" + } + ], + "threadId": "paperclip-bb9ec77ae62affebe9269afb211e8cff11b30059ee6155586f1ba46ef3758033" + }, + "requestId": "acpx-input-ccaa35c8d2130be28a27e1d3", + "requestKind": "permission_approval" + } + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:28.325Z", + "eventType": "runtime_request.created", + "sourceSeq": 109, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:109", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:109", + "sourceSeq": 109, + "sourcePayloadSha256": "11628254fc5e512ff7734b3ef9416fd9f223ec78a756e86a2a4673fbc02ccb75", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:28.333Z" + }, + { + "id": 135, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 135, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "tool" + }, + { + "name": "toolCallId", + "value": "custom_call_jyQm34Kq61JjSxkZUXvPSNrh" + }, + { + "name": "status", + "value": "pending" + }, + { + "name": "operation", + "value": "edit" + } + ], + "summary": "Copilot file operation: pending.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-6", + "severity": "info", + "provenance": { + "method": "session/update", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "tool", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:28.325Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 110, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:110", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:110", + "sourceSeq": 110, + "sourcePayloadSha256": "6e50e44f381af9824aa37cde550765cc90f851260c5b9956de4e1d242a31e3a8", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:28.337Z" + }, + { + "id": 136, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 136, + "eventType": "tool.execution.started", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "name": "apply_patch", + "schema": "paperclip.tool.execution.v1", + "status": "running", + "target": null, + "exitCode": null, + "progress": "apply_patch (pending): *** Begin Patch\n*** Add File: pc-denied-dUYoVS/active-stop-80fcac4754d6-1.txt\n+MUST_NOT_EXIST\n*** End Patch\n", + "readOnly": false, + "namespace": null, + "operation": "edit", + "transport": "builtin", + "durationMs": null, + "executionId": "custom_call_jyQm34Kq61JjSxkZUXvPSNrh", + "outputBytes": 4, + "inputUpdated": true, + "outputDigest": "sha256:74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "outputTruncated": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:28.325Z", + "eventType": "tool.execution.started", + "sourceSeq": 111, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:111", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:111", + "sourceSeq": 111, + "sourcePayloadSha256": "d99db3d323a1814c586fb7eebc0568648bcd4fc361cbfc785068c327d45df398", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:28.340Z" + }, + { + "id": 137, + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "agentId": "1d6760b4-ebe7-4198-9f3d-5d4568b997c6", + "seq": 137, + "eventType": "provider.notice.recorded", + "stream": "system", + "level": "info", + "color": null, + "message": null, + "payload": { + "prpEvent": { + "runId": "861387cc-31c6-4761-bebb-24fde8539f4b", + "itemId": "item-861387cc-31c6-4761-bebb-24fde8539f4b", + "schema": "paperclip.prp.event.v1", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "payload": { + "scope": "turn", + "schema": "paperclip.provider.notice.v1", + "details": [ + { + "name": "stage", + "value": "permission_requested" + }, + { + "name": "toolCallId", + "value": "custom_call_jyQm34Kq61JjSxkZUXvPSNrh" + }, + { + "name": "requestId", + "value": "acpx-input-ccaa35c8d2130be28a27e1d3" + }, + { + "name": "target", + "value": "pc-denied-dUYoVS/active-stop-80fcac4754d6-1.txt" + }, + { + "name": "operation", + "value": "edit" + }, + { + "name": "declineOffered", + "value": "true" + } + ], + "summary": "Copilot requested permission.", + "category": "copilot_tool_evidence_v1", + "noticeId": "copilot-evidence-312ceb146a219ca749ed7bba-7", + "severity": "info", + "provenance": { + "method": "session/request_permission", + "turnId": "turn_lab_71c34ac179ea42ac9e5a65138c15e2b7", + "eventType": "permission_requested", + "sessionId": "64118500-7098-4903-830f-58adc1333b66" + }, + "recoverable": true, + "userActionable": false + }, + "priority": 1, + "emittedAt": "2026-10-03T23:15:28.325Z", + "eventType": "provider.notice.recorded", + "sourceSeq": 112, + "sourceKind": "runner", + "schemaVersion": 1, + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:112", + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "normalizedSessionId": "456cb78e-0362-4f76-9133-5de358b4bc7f" + } + }, + "sourceInstanceId": "11a08fb7-ec9f-4496-9550-cc4decdeed09", + "sourceEventId": "11a08fb7-ec9f-4496-9550-cc4decdeed09:861387cc-31c6-4761-bebb-24fde8539f4b:112", + "sourceSeq": 112, + "sourcePayloadSha256": "068b266a639517b20a5babe71b6e61f8dc6777e2f0b234860fa5b22a326c1d8f", + "protocolSchemaVersion": 1, + "createdAt": "2026-10-03T23:15:28.343Z" + } + ], + "run": { + "id": "861387cc-31c6-4761-bebb-24fde8539f4b", + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "nativeIssueId": "af0fd9f5-9bdd-42f4-a214-328919e315f7", + "runtimeMode": "native", + "status": "running", + "resultJson": {} + }, + "issue": { + "id": "af0fd9f5-9bdd-42f4-a214-328919e315f7", + "companyId": "6ce27423-9b7a-4423-9171-8d4d40e6c346", + "status": "in_progress" + } +} diff --git a/tests/runner-e2e/harness-env.ts b/tests/runner-e2e/harness-env.ts index 52bbf8bbbc6..1202a4c57f4 100644 --- a/tests/runner-e2e/harness-env.ts +++ b/tests/runner-e2e/harness-env.ts @@ -123,7 +123,7 @@ export function buildRunnerE2EProcessEnvironment( || (execution.suite.id === "pi-native" && agent === "pi") || (execution.suite.id === "pi-controls" && agent === "pi") || (["cursor-native", "native-provider-loss"].includes(execution.suite.id) && agent === "cursor") - || (execution.suite.id === "copilot-protection" && agent === "copilot") + || (["copilot-protection", "copilot-provider-death"].includes(execution.suite.id) && agent === "copilot") || (execution.suite.id === "native-active-stop" && (agent === "cursor" || agent === "copilot")); if (!admittedSuite || !execution.suite.manualOnly) { throw new Error("Candidate qualification requires an explicit provider qualification suite"); diff --git a/tests/runner-e2e/live-fixtures.ts b/tests/runner-e2e/live-fixtures.ts index 07a8397cbae..3ffaba95f34 100644 --- a/tests/runner-e2e/live-fixtures.ts +++ b/tests/runner-e2e/live-fixtures.ts @@ -41,7 +41,7 @@ interface AgentRecord { interface ManagedAccountFixture { connectionId: string; binding: { - provider: "openai" | "anthropic" | "openrouter"; + provider: "openai" | "anthropic" | "github" | "openrouter"; method: "api_key"; mode: "responsible_user"; }; @@ -242,14 +242,15 @@ export async function setupLiveFixtures(input: { }); const managedHiring = isManagedHiringCase(execution.suite.id, execution.task.id); - if (managedHiring) { + const savedCopilot = execution.profile.qualificationCandidate === "copilot"; + if (managedHiring || savedCopilot) { registry.register({ id: "ai-connection", - dependencies: ["company"], + dependencies: ["company", ...(savedCopilot ? ["environment"] : [])], async setup(resolved) { const company = value(resolved, "company"); - const key = execution.profile.credential; - const provider = key === "ANTHROPIC_API_KEY" ? "anthropic" + const key = savedCopilot ? "COPILOT_GITHUB_TOKEN" : execution.profile.credential; + const provider = savedCopilot ? "github" : key === "ANTHROPIC_API_KEY" ? "anthropic" : key === "OPENROUTER_API_KEY" ? "openrouter" : key === "OPENAI_API_KEY" ? "openai" : null; if (!provider) throw new Error(`Unsupported managed hiring credential ${key}`); @@ -262,6 +263,7 @@ export async function setupLiveFixtures(input: { method: "api_key", name: `Runner E2E account ${input.executionNonce}`, ownership: "personal", + ...(savedCopilot ? { environmentId: value(resolved, "environment").id } : {}), apiKey, agentIds: [], allAgents: false, @@ -282,7 +284,7 @@ export async function setupLiveFixtures(input: { "secrets", "environment", ...(grokSubscription ? ["subscription-login"] : []), - ...(managedHiring ? ["ai-connection"] : []), + ...(managedHiring || savedCopilot ? ["ai-connection"] : []), ], async setup(resolved) { const company = value(resolved, "company"); @@ -299,7 +301,7 @@ export async function setupLiveFixtures(input: { || (execution.suite.id === "everyday-workflows" && ["hire-reuse", "delegate-feedback"].includes(execution.task.id))) { agent.budgetMonthlyCents = 1_000; } - if (managedHiring) { + if (managedHiring || savedCopilot) { const account = value(resolved, "ai-connection"); const config = agent.adapterConfig as Record; delete config.env; diff --git a/tests/runner-e2e/native-active-stop.test.ts b/tests/runner-e2e/native-active-stop.test.ts index 90520c321c9..7456f2e0195 100644 --- a/tests/runner-e2e/native-active-stop.test.ts +++ b/tests/runner-e2e/native-active-stop.test.ts @@ -376,7 +376,7 @@ describe("explicit active Stop discovery", () => { expect(cells).toHaveLength(4); expect(suite.manualOnly).toBe(true); expect(cells.map(e => `${e.profile.qualificationCandidate}/${e.environment.id}`).sort()).toEqual(["copilot/daytona", "copilot/local", "cursor/daytona", "cursor/local"]); expect(cells.every(e => e.task.expectedRunCount === 1 && e.task.flow === "native_active_stop" && e.task.expectedTerminalState?.run === "cancelled")).toBe(true); - expect(suite.definitionMetadata).toMatchObject({ version: 4, evidence: "paperclip.e2e.native-active-stop-settlement.v2", normalCompletionAccepted: false, providerDeath: "not-covered" }); + expect(suite.definitionMetadata).toMatchObject({ version: 20, evidence: "paperclip.e2e.native-active-stop-settlement.v2", normalCompletionAccepted: false, providerDeath: "not-covered" }); expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, version: 3 } })); expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, normalCompletionAccepted: true } })); expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(e => e.suite.id === suite.id)).toBe(false); diff --git a/tests/runner-e2e/native-bootstrap-read-proof.test.ts b/tests/runner-e2e/native-bootstrap-read-proof.test.ts index f13e4ce5240..7114c870e46 100644 --- a/tests/runner-e2e/native-bootstrap-read-proof.test.ts +++ b/tests/runner-e2e/native-bootstrap-read-proof.test.ts @@ -87,6 +87,7 @@ it.each(["cursor", "copilot"] as const)("correlates actual %s passive notices th const { readCursorToolEvidence } = await import("./cursor-native-evidence.js"); const { readCopilotToolEvidence } = await import("./copilot-evidence.js"); const create = provider === "cursor" ? createCursorToolEvidence : createCopilotToolEvidence; + const readEvidence = provider === "cursor" ? readCursorToolEvidence : readCopilotToolEvidence; for (const variant of ["exact", "wrong-path", "multi-path", "ambiguous-update"]) { const rows: any[] = []; const projector = create({ sessionId: "session", turnId: "turn", workingDirectory: "/workspace", active: () => true, @@ -98,7 +99,7 @@ it.each(["cursor", "copilot"] as const)("correlates actual %s passive notices th projector.tool({ type: "tool_call", tag: "tool_call", toolCallId: "bootstrap", kind: "read", status: "pending", rawInput: variant === "multi-path" ? { paths: [path, "private.txt"] } : { path }, locations: [{ path }] }); projector.tool({ type: "tool_call", tag: "tool_call_update", toolCallId: "bootstrap", status: "failed", ...(variant === "ambiguous-update" ? { rawInput: { path, paths: [path, "private.txt"] } } : {}) }); const evaluate = () => { - const readNotices = provider === "cursor" ? readCursorToolEvidence(rows, "run") : readCopilotToolEvidence(rows, "run"); + const readNotices = readEvidence(rows, "run"); const started = receipt("started", "running", 2), end = receipt("completed", "failed", 4); Object.assign(end.payload.prpEvent.payload, { target: null, operation: "unknown" }); return withoutProvenBootstrapReads([...readNotices, origin], origin, { actionFile, events: [...rows, started, end] }); diff --git a/tests/runner-e2e/playwright-support.config.ts b/tests/runner-e2e/playwright-support.config.ts index 6d2d2976a5e..bdaefb27292 100644 --- a/tests/runner-e2e/playwright-support.config.ts +++ b/tests/runner-e2e/playwright-support.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "@playwright/test"; /** Browser-only harness regressions: no Paperclip instance or provider credentials. */ export default defineConfig({ testDir: ".", - testMatch: ["screenshot-readiness.spec.ts", "service-worker-reload.spec.ts", "lost-send.spec.ts", "chat-restart.spec.ts", "settings-toggle.spec.ts", "browser-bootstrap-diagnostics.spec.ts", "service-worker-module-loading.spec.ts", "browser-bootstrap-recovery.spec.ts", "blocker-input.spec.ts", "native-blocker-visible.spec.ts"], + testMatch: ["screenshot-readiness.spec.ts", "service-worker-reload.spec.ts", "lost-send.spec.ts", "chat-restart.spec.ts", "settings-toggle.spec.ts", "browser-bootstrap-diagnostics.spec.ts", "service-worker-module-loading.spec.ts", "browser-bootstrap-recovery.spec.ts", "blocker-input.spec.ts", "native-blocker-visible.spec.ts", "copilot-permission-card.spec.ts"], workers: 1, retries: 0, timeout: 10_000, diff --git a/tests/runner-e2e/process-tree-owner.test.ts b/tests/runner-e2e/process-tree-owner.test.ts index 17dcafc07eb..f53b9d4340f 100644 --- a/tests/runner-e2e/process-tree-owner.test.ts +++ b/tests/runner-e2e/process-tree-owner.test.ts @@ -27,6 +27,17 @@ it.skipIf(process.platform === "win32")("revalidates PID/start before signaling } finally { f.owner.stopObserving(); } }); +it.skipIf(process.platform === "win32")("keeps surviving peer identities when a descendant traversal ends at a short-lived child", async () => { + const f = fixture([row(100, process.pid, 100), row(101, 100, 100), row(200, 100, 200), row(201, 200, 200)]); + try { + await f.owner.observe(); + await f.owner.observe(); + f.replace([row(100, process.pid, 100), row(200, 100, 200)]); + await f.owner.signal("SIGKILL"); + expect(f.signals).toEqual([[200, "SIGKILL"], [100, "SIGKILL"]]); + } finally { f.owner.stopObserving(); } + }); + it.skipIf(process.platform === "win32")("admits replacement group members only through a still-owned ancestor", async () => { const f = fixture([row(100, process.pid, 100), row(200, 100, 200)]); try { @@ -39,6 +50,19 @@ it.skipIf(process.platform === "win32")("admits replacement group members only t } finally { f.owner.stopObserving(); } }); +it.skipIf(process.platform === "win32")("keeps surviving peers after the launcher exits without admitting an unobserved group", async () => { + const f = fixture([row(100, process.pid, 100), row(200, 100, 200), row(201, 200, 200)]); + try { + await f.owner.observe(); + f.root.exitCode = 0; + f.replace([row(200, 1, 200), row(201, 200, 200)]); + await f.owner.observe(); + f.replace([row(200, 1, 200), row(999, 1, 999)]); + await f.owner.signal("SIGTERM"); + expect(f.signals).toEqual([[200, "SIGTERM"]]); + } finally { f.owner.stopObserving(); } + }); + it.skipIf(process.platform === "win32")("refuses replacement groups containing any unowned live member", async () => { const f = fixture([row(100, process.pid, 100), row(200, 100, 200)]); try { diff --git a/tests/runner-e2e/process-tree-owner.ts b/tests/runner-e2e/process-tree-owner.ts index ba5dbb0fdfb..86b44902bcc 100644 --- a/tests/runner-e2e/process-tree-owner.ts +++ b/tests/runner-e2e/process-tree-owner.ts @@ -76,7 +76,15 @@ export function createProcessTreeOwner(root: ChildProcess, options: { for (const pid of anchors) { for (const group of observeDescendantProcessTree(next, pid).groups) { if (group.processGroupId !== callerGroup) { - byGroup.set(group.processGroupId, group); + // A descendant traversal can cover only part of an already + // validated group. Keep every observed peer as an identity anchor. + const previous = byGroup.get(group.processGroupId); + const members = new Map(previous?.members.map(member => [member.pid, member]) ?? []); + for (const member of group.members) members.set(member.pid, member); + byGroup.set(group.processGroupId, { ...group, + depth: Math.max(group.depth, previous?.depth ?? group.depth), + members: [...members.values()], + }); if (covered.has(next.find(row => row.pid === pid)!.processGroupId)) covered.add(group.processGroupId); } } diff --git a/tests/runner-e2e/remote-native-bootstrap.test.ts b/tests/runner-e2e/remote-native-bootstrap.test.ts index c4fddd65c8c..41cfad852c1 100644 --- a/tests/runner-e2e/remote-native-bootstrap.test.ts +++ b/tests/runner-e2e/remote-native-bootstrap.test.ts @@ -49,6 +49,16 @@ it("keeps negated bootstrap instructions out of the production file-delivery con expect(explicitlyRequestsFileOutput(`${prompt}\nCreate a downloadable report.txt file.`)).toBe(true); }); +it("delivers the required direct Copilot context tool guidance before publishing native actions", () => { + const h = harness(); + const prompt = createRemoteNativeBootstrap({ ...h.input, requireCopilotTaskContext: true }, h.bind).prompt("copilot"); + expect(prompt).toContain("get_task_context tool is already available directly"); + expect(prompt).toContain("get_task_context exactly once"); + expect(prompt).toContain("Do not call search_api or use call_api or an HTTP route as a substitute"); + expect(explicitlyRequestsFileOutput(prompt)).toBe(false); + expect(h.bootstrap.prompt("other-provider")).not.toContain("get_task_context"); +}); + it("keeps async completion immediate with a private sentinel and no delivery wait", () => { const task = copilotProtectionTasks.find(task => task.id === "attached-async-settlement")!; const action = task.buildPrompt("nonce"); diff --git a/tests/runner-e2e/remote-native-bootstrap.ts b/tests/runner-e2e/remote-native-bootstrap.ts index 7edcf680706..f85eaee235d 100644 --- a/tests/runner-e2e/remote-native-bootstrap.ts +++ b/tests/runner-e2e/remote-native-bootstrap.ts @@ -63,6 +63,7 @@ export async function createRemoteFixtureClient(apiKey: string): Promise; }, bind = bindRemoteNativeFixture): RemoteNativeBootstrap { if (!/^sha256:[a-f0-9]{64}$/u.test(input.nodeSha256) || !/^sha256:[a-f0-9]{64}$/u.test(input.runnerdSha256) || !/@sha256:[a-f0-9]{64}$/u.test(input.image)) { @@ -77,6 +78,10 @@ export function createRemoteNativeBootstrap(input: { return [ `Your task instructions will be published by the operator in the workspace file ${path}.`, "Use your native file-read tool to read that exact relative file in the current execution workspace. If it is not present yet, retry the read for up to 20 seconds, then report the setup failure.", + ...(input.requireCopilotTaskContext ? [ + "The dedicated Paperclip get_task_context tool is already available directly. Read this setup file first, then invoke get_task_context exactly once and wait for its successful returned context before performing the supplied native command or edit.", + "No discovery is needed. search_api discovers HTTP routes, not semantic tools. Do not call search_api or use call_api or an HTTP route as a substitute for get_task_context.", + ] : []), "Before reading those instructions: do not infer the task. Do not run shell commands. Do not create or modify any file.", "Do not ask replacement questions. Do not mark work complete. Do not create the missing instruction file.", "After reading the complete file, perform precisely the supplied task. Treat it as the operator's continuation of this task.", @@ -185,6 +190,7 @@ export function createRemoteNativeBootstrap(input: { fixture = await bind({ api: input.api, daytona: input.daytona, sdkVersion: "0.203.0", nodeSha256: input.nodeSha256, runnerdSha256: input.runnerdSha256, authority: { companyId: input.companyId, environmentId: input.environmentId, runId: request.runId, leaseId: lease.id, sandboxId: lease.providerLeaseId, image: input.image }, targets: [...request.targets], crossRoot: request.crossRoot, actionFile: setup.path, deadlineAt: input.deadlineAt, + retainTerminalDiagnostics: diagnostics => input.evidence(`remote-native-terminal-diagnostics-${request.runId}.json`, diagnostics), }); } catch (error) { if (lastReadError !== undefined && lastState.phase === "lease_admission") { diff --git a/tests/runner-e2e/remote-native-fixtures.test.ts b/tests/runner-e2e/remote-native-fixtures.test.ts index 116d7a8d37e..0209318959f 100644 --- a/tests/runner-e2e/remote-native-fixtures.test.ts +++ b/tests/runner-e2e/remote-native-fixtures.test.ts @@ -1,6 +1,7 @@ import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { createHash } from "node:crypto"; +import { copilotDeathArguments } from "./copilot-provider-death.js"; import { EventEmitter } from "node:events"; import { mkdtemp, rename, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -246,6 +247,40 @@ describe("remote native lease admission", () => { if (type === "bad-file") end.targets["result.txt"] = { ...end.targets["result.txt"], absent: false, sha256: hash("missing") }; h.resolveTerminal(end); await expect(f.finish()).rejects.toThrow(); }); + it("retains bounded incomplete retirement diagnostics while rejecting the receipt", async () => { + const h = harness(), retain = vi.fn(async () => {}); + const f = await bindRemoteNativeFixture({ ...h.options, retainTerminalDiagnostics: retain }); + await f.publishAction("action.txt", "task"); + const end: any = { ...structuredClone(h.current), complete: false, incompleteReasons: ["workspace_directory_added"], processes: { ...h.current.processes, live: [] }, files: {} }; + h.resolveTerminal(end); + await expect(f.finish()).rejects.toThrow("terminal_evidence_incomplete"); + expect(retain).toHaveBeenCalledExactlyOnceWith({ failureCodes: [], actionPublished: true, setupPublished: true, complete: false, watcherComplete: true, targetMutationCount: 0, workspaceMutationCount: 0, processRootCaptured: true, liveProcessCount: 0, incompleteReasons: ["workspace_directory_added"] }); + await expect(f.readFile("result.txt")).rejects.toThrow(); + }); + it("rejects arbitrary diagnostic text before retaining terminal metadata", async () => { + const h = harness(), retain = vi.fn(async () => {}); + const f = await bindRemoteNativeFixture({ ...h.options, retainTerminalDiagnostics: retain }); + await f.publishAction("action.txt", "task"); + h.resolveTerminal({ ...structuredClone(h.current), incompleteReasons: ["untrusted provider content"], processes: { ...h.current.processes, live: [] }, files: {} } as any); + await expect(f.finish()).rejects.toThrow("incomplete_reasons_shape"); + expect(retain).not.toHaveBeenCalled(); + }); + it("reports only closed terminal failure reasons without weakening retirement proof", async () => { + const h = harness(), retain = vi.fn(async () => {}), f = await bindRemoteNativeFixture({ ...h.options, retainTerminalDiagnostics: retain }); + await f.publishAction("action.txt", "task"); + const end = { ...structuredClone(h.current), complete: false, + processes: { ...h.current.processes, live: [] }, files: {}, + failureCodes: ["process_pid_reused", "secret-provider-payload", "process_pid_reused", { arbitrary: "payload" }] }; + h.resolveTerminal(end); + const error = await f.finish().catch(error => error); + expect(error.message).toContain("terminal_evidence_incomplete:published=true,complete=false"); + expect(error.message).toContain(":process_pid_reused"); + expect(error.message).not.toContain("secret-provider-payload"); + expect(error.message).not.toContain("arbitrary"); + expect(retain).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ failureCodes: ["process_pid_reused"] })); + expect(JSON.stringify(retain.mock.calls)).not.toContain("secret-provider-payload"); + expect(remoteNativeFixtureDiagnostics(error)).toEqual([{ phase: "wait", code: "terminal_evidence_incomplete" }]); + }); it("retains a validated incomplete terminal receipt without raw file or RPC content", async () => { const h = harness(), f = await bindRemoteNativeFixture(h.options); await f.publishAction("action.txt", "task"); @@ -253,7 +288,7 @@ describe("remote native lease admission", () => { processes: { ...h.current.processes, live: [] }, files: { "private.txt": "PRIVATE RPC CONTENT" }, untrustedDump: "PRIVATE RPC CONTENT" }; h.resolveTerminal(end); const error = await f.finish().catch(error => error); - expect(error.message).toBe("remote_native_fixture:terminal_evidence_incomplete"); + expect(error.message).toContain("remote_native_fixture:terminal_evidence_incomplete"); const retained = remoteNativeIncompleteTerminalEvidence(error)!; expect(retained.complete).toBe(false); expect(retained.watcher.complete).toBe(false); expect(retained.processes.liveCount).toBe(0); @@ -392,10 +427,15 @@ describe("cell deadline and cleanup bounds", () => { }); describe("independent filesystem and process observations", () => { - it("retains transient create/delete events and detects same-path parent replacement", async () => { + // This real filesystem probe qualifies the Linux Daytona observer. + it.runIf(process.platform === "linux")("retains transient create/delete events and detects same-path parent replacement", async () => { const dir = await mkdtemp(join(tmpdir(), "remote-watch-")); const watcher = createRemoteTargetWatch(dir, "denied.txt"); try { - await writeFile(join(dir, "denied.txt"), "forbidden"); await rm(join(dir, "denied.txt")); + await writeFile(join(dir, "denied.txt"), "forbidden"); + // macOS coalesces a create/remove pair before delivering fs.watch. Wait + // for the real create observation, then prove it survives removal. + await vi.waitFor(() => expect(watcher.snapshot().mutationCount).toBeGreaterThan(0)); + await rm(join(dir, "denied.txt")); await vi.waitFor(() => expect(watcher.snapshot().mutationCount).toBeGreaterThan(0)); expect(watcher.snapshot().complete).toBe(true); await rename(dir, `${dir}-old`); await writeFile(dir, "replacement"); @@ -437,6 +477,7 @@ describe("actual generated observer state machine", () => { const missing = () => { throw Object.assign(new Error("missing"), { code: "ENOENT" }); }; const fds = new Map(); let nextFd = 50, runtimeInode = 4n; const symbolicLinks = new Set(), directoryInodes = new Map(); + const processExecutables = new Map(); const directories = new Set(["/tmp", "/workspace", "/workspace/.paperclip-runtime", "/workspace/.paperclip-runtime/paperclip-runner", "/workspace/.paperclip-runtime/paperclip-runner/sessions"]); if (!deferStartup) directories.add(config.root); const listeners = new Map void>(); @@ -462,7 +503,8 @@ describe("actual generated observer state machine", () => { if (!directory && !files.has(path) && !symbolicLinks.has(path)) return missing(); return { dev: 1n, ino: directoryInodes.get(path) ?? (path === config.root ? 2n : path === "/workspace/.paperclip-runtime/paperclip-runner" ? runtimeInode : 3n), mtimeNs: 4n, ctimeNs: 5n, isDirectory: () => directory, isFile: () => !directory, isSymbolicLink: () => symbolicLinks.has(path), size: files.get(path)?.length ?? 0 }; }, - realpathSync: (path: string) => path, + realpathSync: (path: string) => processExecutables.get(path)?.path ?? (/^\/proc\/\d+\/exe$/u.test(path) ? proc.get(Number(path.split("/")[2]))?.argv[0] : path), + statSync(path: string) { const identity = processExecutables.get(path); if (!identity) return missing(); return identity; }, readdirSync(path: string) { if (path === "/proc") return [...proc.keys()].map(String); if (path.startsWith("/workspace/.paperclip-runtime/paperclip-runner")) throw new Error("excluded runtime must not be traversed"); return [...new Set([...files.keys(), ...directories, ...symbolicLinks].filter(p => p.startsWith(path + "/") && !p.slice(path.length + 1).includes("/")).map(p => p.slice(path.length + 1)))]; }, watch(path: string, options: unknown, callback?: (_kind: string, name: string | null) => void) { const entry = { path, callback: (callback ?? options) as (_kind: string, name: string | null) => void, closed: false }; watches.push(entry); @@ -492,8 +534,8 @@ describe("actual generated observer state machine", () => { const net = { createServer(fn: (socket: any) => void) { handlers.push(fn); return server; } }; const faultSpawn = vi.fn(() => ({ status: 0, stdout: JSON.stringify(piFaultReceipt()) })); const context = { - require(name: string) { if (name === "node:fs") return fs; if (name === "node:net") return net; if (name === "node:child_process") return { spawnSync: faultSpawn, spawn: vi.fn(() => { const child = Object.assign(new EventEmitter(), { pid: 88, exitCode: null, signalCode: null, kill: vi.fn() }); children.push(child); return child; }) }; if (name === "node:path") return { join: (...paths: string[]) => paths.join("/"), dirname: (path: string) => path.slice(0, path.lastIndexOf("/")), basename: (path: string) => path.slice(path.lastIndexOf("/") + 1) }; if (name === "node:crypto") return { createHash }; throw new Error("unexpected module"); }, - process: { argv: ["node", `${config.root}/observer.cjs`, Buffer.from(JSON.stringify(config)).toString("base64")], execPath: "/node", hrtime: { bigint: () => 12345n }, exit: vi.fn() }, + require(name: string) { if (name === "node:fs") return fs; if (name === "node:net") return net; if (name === "node:child_process") return { spawnSync: faultSpawn, spawn: vi.fn((_node: string, argv: string[]) => { const child = Object.assign(new EventEmitter(), { argv, stdin: { end: vi.fn() }, pid: 88, exitCode: null, signalCode: null, kill: vi.fn() }); children.push(child); return child; }) }; if (name === "node:path") return { join: (...paths: string[]) => paths.join("/"), dirname: (path: string) => path.slice(0, path.lastIndexOf("/")), basename: (path: string) => path.slice(path.lastIndexOf("/") + 1) }; if (name === "node:crypto") return { createHash }; throw new Error("unexpected module"); }, + process: { argv: ["node", `${config.root}/observer.cjs`, Buffer.from(JSON.stringify(config)).toString("base64")], execPath: "/node", hrtime: { bigint: () => 12345n }, exit: vi.fn(), kill: vi.fn() }, Buffer, __filename: `${config.root}/observer.cjs`, setInterval(fn: () => void) { intervals.push(fn); return 1; }, clearInterval: vi.fn(), setTimeout(fn: () => void, ms: number) { timers.push({ fn, ms }); return { unref() {} }; }, @@ -504,7 +546,7 @@ describe("actual generated observer state machine", () => { const replies: any[] = [], socket = Object.assign(new EventEmitter(), { end: (value: string) => replies.push(JSON.parse(value)), destroy: vi.fn() }); handlers[0]!(socket); socket.emit("data", Buffer.from(JSON.stringify({ op, nonce: config.nonce, ...args }) + "\n")); return replies; } - return { request, proc, files, watches, fs, intervals, timers, config, handlers, children, faultSpawn, symbolicLinks, directoryInodes, context, server, directories, listeners, install: h.calls.find(c => c.request.op === "install")!, replaceRuntimeRoot() { runtimeInode = 999n; } }; + return { request, proc, files, watches, fs, intervals, timers, config, handlers, children, faultSpawn, symbolicLinks, directoryInodes, processExecutables, context, server, directories, listeners, install: h.calls.find(c => c.request.op === "install")!, replaceRuntimeRoot() { runtimeInode = 999n; } }; } it("keeps existing watched directory notifications complete without losing nested mutations", async () => { const o = await observerHarness(true); @@ -733,6 +775,25 @@ describe("actual generated observer state machine", () => { o.proc.set(1, { ppid: 0, group: 1, ticks: "1", argv: ["/sbin/init"] }); expect(o.request("snapshot")[0].result.processes.captured).toBe(true); }); + it.each(["valid", "wrong-inode", "foreign-executable", "ambiguous", "wrong-arguments"])("dispatches generated provider death only for verified descriptor ownership: %s", async variant => { + const o = await observerHarness(); + o.request("snapshot"); o.request("publish", { path: "action.txt", text: "do task" }); + const executable = "/tmp/paperclip-acpx-native-owned/distribution/copilot"; + o.proc.set(22, { ppid: 21, group: 21, ticks: "200", argv: ["/node"] }); + o.proc.set(23, { ppid: 22, group: 21, ticks: "300", argv: ["/proc/self/fd/7", ...copilotDeathArguments] }); + o.processExecutables.set("/proc/23/exe", { path: executable, dev: 1n, ino: 40n }); + o.processExecutables.set("/proc/23/fd/7", { path: executable, dev: 1n, ino: 40n }); + if (variant === "wrong-inode") o.processExecutables.get("/proc/23/fd/7")!.ino = 41n; + if (variant === "foreign-executable") { o.processExecutables.get("/proc/23/exe")!.path = "/usr/bin/copilot"; o.processExecutables.get("/proc/23/fd/7")!.path = "/usr/bin/copilot"; } + if (variant === "wrong-arguments") o.proc.get(23)!.argv.push("--allow-all"); + if (variant === "ambiguous") o.proc.set(24, { ppid: 22, group: 21, ticks: "400", argv: [executable, ...copilotDeathArguments] }); + const replies = o.request("provider-death"); + if (variant === "valid") { + expect(replies[0]).toMatchObject({ ok: true, result: { schema: "paperclip.e2e.copilot-owned-provider-death.v1", signal: "SIGKILL", child: { pid: 23 } } }); + expect(o.context.process.kill).toHaveBeenCalledExactlyOnceWith(23, "SIGKILL"); + expect(o.request("provider-death")[0].ok).toBe(false); + } else { expect(replies[0].ok).toBe(false); expect(o.context.process.kill).not.toHaveBeenCalled(); } + }); it("arms exact remote root then seals drained no-live proof and retained bytes", async () => { const o = await observerHarness(); expect(o.request("snapshot")[0].result.processes.captured).toBe(true); const wait = o.request("wait"); expect(wait).toHaveLength(0); @@ -771,6 +832,25 @@ describe("actual generated observer state machine", () => { expect(o.children).toHaveLength(0); expect(socket.destroy).toHaveBeenCalled(); expect(o.request("snapshot")[0].result.attached.failure).toBe("client_rejected"); }); + it("releases a gated remote child once, through the owned controller after publication", async () => { + const o = await observerHarness(); o.request("snapshot"); + const config = { marker: "result.txt", markerText: "settled", delayMs: 100, clientNonce: "test-gated-client", waitForFinishAttempt: true }; + const fixture = o.request("attached", config)[0].result; + expect(o.request("release-attached")[0].ok).toBe(false); + o.request("publish", { path: o.config.actionFile, text: "exact action" }); + expect(o.request("release-attached")[0].ok).toBe(false); + o.proc.set(23, { ppid: 21, group: 21, ticks: "300", argv: ["/node", fixture.clientScript, fixture.clientSocket, config.clientNonce] }); + const replies: any[] = [], socket = Object.assign(new EventEmitter(), { end: (value: string) => replies.push(JSON.parse(value)), destroy: vi.fn() }); + o.handlers[1]!(socket); socket.emit("data", Buffer.from(JSON.stringify({ nonce: config.clientNonce, pid: 23 }) + "\n")); + expect(o.children).toHaveLength(1); expect(o.files.has("/workspace/result.txt")).toBe(false); + expect(() => new Script(o.children[0].argv[1])).not.toThrow(); + expect(o.request("release-attached")[0]).toMatchObject({ ok: true, result: { releasedAtMs: expect.any(Number) } }); + expect(o.children[0].stdin.end).toHaveBeenCalledExactlyOnceWith("release\n"); + expect(o.request("release-attached")[0].ok).toBe(false); + expect(replies).toHaveLength(0); + o.children[0].exitCode = 0; o.children[0].emit("exit", 0, null); + expect(replies).toEqual([{ code: 0 }]); expect(o.files.get("/workspace/result.txt")?.toString()).toBe("settled"); + }); it("scopes actual runtime symlinks/state churn out while retaining sentinel and sibling coverage", async () => { const o = await observerHarness(); o.symbolicLinks.add("/workspace/.paperclip-runtime/paperclip-runner/provider-pack"); diff --git a/tests/runner-e2e/remote-native-fixtures.ts b/tests/runner-e2e/remote-native-fixtures.ts index 0160f5c2c62..f965528d3f9 100644 --- a/tests/runner-e2e/remote-native-fixtures.ts +++ b/tests/runner-e2e/remote-native-fixtures.ts @@ -1,3 +1,4 @@ +import { canonicalRemoteCopilotCommand, copilotDeathArguments, selectOwnedCopilotProcess } from "./copilot-provider-death.js"; import { createHash, randomBytes } from "node:crypto"; import { watch, lstatSync, readFileSync } from "node:fs"; import { posix } from "node:path"; @@ -33,8 +34,8 @@ function relative(value: string): string { } // Only closed diagnostic enums cross the remote boundary; SDK errors and output // are never retained. These diagnostics explain failed evidence, not qualification. -const RPC_PHASES = ["runtime-ready", "install", "wait", "close", "arm", "publish", "snapshot", "attached", "read", "pi-provider-death"] as const; -const RPC_CODES = ["node_identity", "sentinel_type", "sentinel", "cwd", "runtime_root_identity", "runtime_binary_identity", "proc_bound", "ambiguous_run_root", "runtime_not_ready", "runtime_identity_changed", "invalid_proc_identity", "invalid_proc_fields", "socket_error", "socket_timeout", "output_bound", "rpc_deadline", "remote_unknown", "transport_failure", "invalid_response", "readiness_deadline", "pi_provider_identity_or_signal_failed"] as const; +const RPC_PHASES = ["runtime-ready", "install", "wait", "close", "arm", "publish", "snapshot", "attached", "release-attached", "read", "inject-loss", "provider-death", "pi-provider-death"] as const; +const RPC_CODES = ["node_identity", "sentinel_type", "sentinel", "cwd", "runtime_root_identity", "runtime_binary_identity", "proc_bound", "ambiguous_run_root", "runtime_not_ready", "runtime_identity_changed", "invalid_proc_identity", "invalid_proc_fields", "socket_error", "socket_timeout", "output_bound", "rpc_deadline", "remote_unknown", "transport_failure", "invalid_response", "readiness_deadline", "terminal_evidence_incomplete", "pi_provider_identity_or_signal_failed"] as const; type RpcPhase = typeof RPC_PHASES[number]; type RpcCode = typeof RPC_CODES[number]; interface RpcDiagnostic { phase: RpcPhase; code: RpcCode } @@ -51,7 +52,8 @@ export function remoteNativeFixtureDiagnostics(error: unknown): RpcDiagnostic[] export interface RemoteNativeAuthority { companyId: string; environmentId: string; runId: string; leaseId: string; sandboxId: string; image: string } export interface RemoteNativeBinding extends RemoteNativeAuthority { remoteCwd: string } export interface RemoteProcessIdentity { pid: number; ppid: number; startTicks: string; bootId: string } -const INCOMPLETE_REASONS = ["multiple_run_roots", "run_root_changed", "process_identity_reused", "process_sample_failed", "process_read_io", "process_stat_shape", "process_runtime_binding", "process_terminal_seal_failed", "unknown_watch_name", "setup_file_changed", "workspace_event_bound", "workspace_symlink", "unwatched_directory", "directory_identity_changed", "watch_stat_failed", "workspace_watch_error", "target_watch_incomplete", "receipt_byte_bound", "attached_process_live", "control_request_bound", "observer_ttl"] as const; +const TERMINAL_FAILURE_CODES = ["ambiguous_run_root", "run_root_changed", "process_pid_reused", "workspace_watch_unknown_entry", "setup_file_changed", "setup_file_unreadable", "workspace_watch_entry_bound", "workspace_watch_new_directory", "workspace_watch_read_failure", "workspace_watch_io_failure", "receipt_output_bound", "attached_child_live", "terminal_snapshot_failed", "process_sample_failed"] as const; +const INCOMPLETE_REASONS = ["multiple_run_roots", "run_root_changed", "process_identity_reused", "workspace_watch_incomplete", "setup_file_changed", "workspace_event_bound", "workspace_directory_added", "retirement_snapshot_failed", "process_sample_failed", "control_bound", "observer_ttl", "process_read_io", "process_stat_shape", "process_runtime_binding", "process_terminal_seal_failed", "unknown_watch_name", "workspace_symlink", "unwatched_directory", "directory_identity_changed", "watch_stat_failed", "workspace_watch_error", "target_watch_incomplete", "receipt_byte_bound", "attached_process_live", "control_request_bound"] as const; type IncompleteReason = typeof INCOMPLETE_REASONS[number]; const FILESYSTEM_INCOMPLETE_REASONS: readonly IncompleteReason[] = ["unknown_watch_name", "setup_file_changed", "workspace_event_bound", "workspace_symlink", "unwatched_directory", "directory_identity_changed", "watch_stat_failed", "workspace_watch_error", "target_watch_incomplete"]; export interface RemoteNativeSnapshot { @@ -74,9 +76,9 @@ export interface RemoteNativeSnapshot { setup: { path: string; sha256: string | null; published: boolean }; attached: { connections: number; failure: string | null; commandExit: { code: number; observedAtMs: number; observedMonotonicNs: string } | null; markerWrittenAtMs: number | null; markerWrittenMonotonicNs: string | null; clientExitedAtMs: number | null; clientExitedMonotonicNs: string | null } | null; } -class IncompleteRemoteTerminalEvidenceError extends Error { - constructor(readonly snapshot: RemoteNativeSnapshot) { - super("remote_native_fixture:terminal_evidence_incomplete"); +class IncompleteRemoteTerminalEvidenceError extends RemoteFixtureError { + constructor(readonly snapshot: RemoteNativeSnapshot, flags: string[] = [], codes: string[] = []) { + super(`remote_native_fixture:terminal_evidence_incomplete${flags.length ? ":" + flags.join(",") + ":" + codes.join(",") : ""}`, { phase: "wait", code: "terminal_evidence_incomplete" }); } } /** Retain only a shape-validated public receipt, without raw RPC text or files. */ @@ -160,14 +162,16 @@ const OBSERVER = String.raw` const fs=require('node:fs'),path=require('node:path'),crypto=require('node:crypto'),net=require('node:net'),cp=require('node:child_process'); const config=JSON.parse(Buffer.from(process.argv[2],'base64').toString()); const parseStat=PARSE_STAT;const runRoot=RUN_ROOT;const vanished=PROC_ENTRY_DISAPPEARED;const watchTarget=WATCH_TARGET;const PI_FAULT_SOURCE=PI_FAULT_PROGRAM,PI_CLOSURE_PIN=PI_CLOSURE_DIGEST; +const selectCopilot=SELECT_COPILOT;const canonicalCopilot=CANONICAL_COPILOT;const copilotArgs=COPILOT_ARGS;let providerDeathDispatched=false; const hash=x=>'sha256:'+crypto.createHash('sha256').update(x).digest('hex'); const cwdStat=fs.lstatSync(config.binding.remoteCwd,{bigint:true}),rootStat=fs.lstatSync(config.root,{bigint:true}),scriptStat=fs.lstatSync(__filename,{bigint:true}),scriptHash=hash(fs.readFileSync(__filename)); const runtimeRoot=path.join(config.binding.remoteCwd,config.runtimeRelative),runtimeStat=fs.lstatSync(runtimeRoot,{bigint:true});if(!runtimeStat.isDirectory()||runtimeStat.isSymbolicLink()||fs.realpathSync(runtimeRoot)!==runtimeRoot)throw Error('runtime_root_identity');let observedPrpEnvironmentLeaseId=null; const boot=fs.readFileSync('/proc/sys/kernel/random/boot_id','utf8').trim(); const targets=new Map();let complete=true,sealed=false,root=null,attached=null,child=null,client=null,childTimer=null; -const incompleteReasons=new Set();function incomplete(reason){complete=false;incompleteReasons.add(reason)} +const incompleteReasons=new Set(),failureCodes=new Set();function incomplete(reason){complete=false;incompleteReasons.add(reason);failureCodes.add(reason)}function markIncomplete(reason){incomplete(reason)} const journal=new Map(),sockets=new Set(),waiters=new Set(),armWaiters=new Set();let observedRootCount=0,publishedHash=null,finalReceipt=null,retiring=false,piFaultAttempted=false; function identity(pid){return parseStat(pid,fs.readFileSync('/proc/'+pid+'/stat','utf8'),boot)} +function deathCommand(pid){const command=fs.readFileSync('/proc/'+pid+'/cmdline').toString().split('\0').filter(Boolean);if(!/^\/proc\/self\/fd\/(?:3|7)$/.test(command[0]??''))return command;const executable=fs.realpathSync('/proc/'+pid+'/exe'),s=fs.statSync('/proc/'+pid+'/exe',{bigint:true}),fd='/proc/'+pid+'/fd/'+command[0].split('/').at(-1),d=fs.statSync(fd,{bigint:true});return canonicalCopilot(command,executable,{dev:String(s.dev),ino:String(s.ino)},{path:fs.realpathSync(fd),dev:String(d.dev),ino:String(d.ino)})} function table(){const ids=fs.readdirSync('/proc').filter(x=>/^\d+$/.test(x)&&Number(x)>1);if(ids.length>4096)throw Error('process_bound');return ids.flatMap(x=>{try{return [identity(Number(x))]}catch(e){if(vanished(e,()=>fs.lstatSync('/proc/'+x)))return [];throw e}})} function sample(){ const all=table();const candidates=[]; @@ -188,7 +192,7 @@ function guard(){const rs=fs.lstatSync(runtimeRoot,{bigint:true});if(!rs.isDirec function readSafe(p){const fd=fs.openSync(p,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW);try{const before=fs.fstatSync(fd,{bigint:true});if(!before.isFile()||before.size>65536n)throw Error('file_bound_or_type');const bytes=fs.readFileSync(fd),after=fs.fstatSync(fd,{bigint:true}),named=fs.lstatSync(p,{bigint:true});if(before.dev!==named.dev||before.ino!==named.ino||named.isSymbolicLink()||before.size!==after.size||before.mtimeNs!==after.mtimeNs||BigInt(bytes.length)!==before.size)throw Error('file_changed');return bytes}finally{fs.closeSync(fd)}} function file(p){try{const s=fs.lstatSync(p);if(s.isSymbolicLink()||!s.isFile()||s.size>65536)throw Error('file_bound_or_type');return {absent:false,sha256:hash(readSafe(p))}}catch(e){if(e.code==='ENOENT')return {absent:true,sha256:null};throw e}} function workspace(){const result={};let count=0,bytes=0;function visit(dir,prefix){for(const name of fs.readdirSync(dir).sort()){if(++count>512)throw Error('workspace_entry_bound');const full=path.join(dir,name),rel=prefix+name,s=fs.lstatSync(full);if(rel===config.runtimeRelative)continue;if(rel===config.actionFile){if(!publishedHash||file(full).sha256!==publishedHash)throw Error('setup_file_changed');continue;}if(s.isSymbolicLink())throw Error('workspace_symlink');if(s.isDirectory()){result[rel]='directory';visit(full,rel+'/')}else if(s.isFile()){bytes+=s.size;if(s.size>65536||bytes>4194304)throw Error('workspace_byte_bound');result[rel]=hash(readSafe(full))}else throw Error('workspace_special_file')}}visit(config.binding.remoteCwd,'');return result} -function snapshot(){guard();verifyWorkspaceWatchRoots();const processes=sample(),out={},reasons=new Set(incompleteReasons);let watchComplete=complete,total=0;for(const [name,t] of targets){const status=t.watch.snapshot();out[name]={...file(t.path),...status};watchComplete&&=status.complete;if(!status.complete)reasons.add('target_watch_incomplete');total+=status.mutationCount}return {binding:config.binding,observedAtMs:Date.now(),observedMonotonicNs:process.hrtime.bigint().toString(),complete:complete&&watchComplete,incompleteReasons:[...reasons].sort(),workspace:workspace(),targets:out,watcher:{complete:watchComplete,targetMutationCount:total,workspaceMutationCount},processes,scope:{kind:'user_workspace',excludedRuntime:{relativePath:config.runtimeRelative,absolutePath:runtimeRoot,dev:String(runtimeStat.dev),ino:String(runtimeStat.ino),runnerExecutableSha256:config.runnerdSha256},observedPrpEnvironmentLeaseId,prpEnvironmentLeaseIdVerified:false},setup:{path:config.actionFile,sha256:publishedHash,published:publishedHash!==null},attached}} +function snapshot(){guard();verifyWorkspaceWatchRoots();const processes=sample(),out={},reasons=new Set(incompleteReasons);let watchComplete=complete,total=0;for(const [name,t] of targets){const status=t.watch.snapshot();out[name]={...file(t.path),...status};watchComplete&&=status.complete;if(!status.complete)reasons.add('target_watch_incomplete');total+=status.mutationCount}return {binding:config.binding,observedAtMs:Date.now(),observedMonotonicNs:process.hrtime.bigint().toString(),complete:complete&&watchComplete,failureCodes:[...failureCodes],incompleteReasons:[...reasons].sort(),workspace:workspace(),targets:out,watcher:{complete:watchComplete,targetMutationCount:total,workspaceMutationCount},processes,scope:{kind:'user_workspace',excludedRuntime:{relativePath:config.runtimeRelative,absolutePath:runtimeRoot,dev:String(runtimeStat.dev),ino:String(runtimeStat.ino),runnerExecutableSha256:config.runnerdSha256},observedPrpEnvironmentLeaseId,prpEnvironmentLeaseIdVerified:false},setup:{path:config.actionFile,sha256:publishedHash,published:publishedHash!==null},attached}} for(const name of config.targets){const p=path.join(config.binding.remoteCwd,name);if(fs.realpathSync(path.dirname(p))!==path.dirname(p))throw Error('target_parent_symlink');targets.set(name,{path:p,watch:watchTarget(path.dirname(p),path.basename(p),{watch:fs.watch,lstatSync:fs.lstatSync})})} if(config.crossRoot){const p=path.join(config.root,'cross-root-target');fs.writeFileSync(p,config.crossRoot.initialText,{flag:'wx',mode:0o600});targets.set('@cross-root',{path:p,watch:watchTarget(config.root,'cross-root-target',{watch:fs.watch,lstatSync:fs.lstatSync})})} let workspaceMutationCount=0;const directoryWatches=[]; @@ -217,14 +221,25 @@ const server=net.createServer(socket=>{sockets.add(socket);socket.on('close',()= const childResult=cp.spawnSync('/usr/bin/python3',['-I','-c',PI_FAULT_SOURCE,Buffer.from(JSON.stringify(request)).toString('base64')],{env:{PATH:'/usr/bin:/bin'},timeout:8000,maxBuffer:32768,encoding:'utf8'}); if(childResult.status!==0||childResult.error)throw Error('pi_provider_identity_or_signal_failed');result=JSON.parse(childResult.stdout); } - else if(r.op==='attached'){if(attached||sealed)throw Error('attached_already_configured');if(!config.targets.includes(r.marker)||!Number.isInteger(r.delayMs)||r.delayMs<100||r.delayMs>8000)throw Error('attached_bounds'); - attached={connections:0,failure:null,commandExit:null,markerWrittenAtMs:null,markerWrittenMonotonicNs:null,clientExitedAtMs:null,clientExitedMonotonicNs:null}; + else if(r.op==='attached'){if(attached||sealed)throw Error('attached_already_configured');if(!config.targets.includes(r.marker)||!Number.isInteger(r.delayMs)||r.delayMs<100||r.delayMs>8000||(r.waitForFinishAttempt!==undefined&&typeof r.waitForFinishAttempt!=='boolean'))throw Error('attached_bounds'); + attached={waitForFinishAttempt:r.waitForFinishAttempt===true,releasedAtMs:null,connections:0,failure:null,commandExit:null,markerWrittenAtMs:null,markerWrittenMonotonicNs:null,clientExitedAtMs:null,clientExitedMonotonicNs:null}; const clientScript=path.join(config.root,'client.cjs'),clientSocket=path.join(config.root,'attached.sock'); - fs.writeFileSync(clientScript,ATTACHED_CLIENT,{flag:'wx',mode:0o400}); + fs.writeFileSync(clientScript,ATTACHED_CLIENT.replace('15000',attached.waitForFinishAttempt?'45000':'15000'),{flag:'wx',mode:0o400}); const srv=net.createServer(s=>{sockets.add(s);s.on('close',()=>sockets.delete(s));s.on('error',()=>{});let b='';s.on('data',d=>{b+=d;if(b.length>1024){s.destroy();attached.failure='client_bound';return}if(!b.includes('\n'))return;s.removeAllListeners('data');try{const q=JSON.parse(b);attached.connections++;if(q.nonce!==r.clientNonce||attached.connections!==1)throw Error('client_identity');client=identity(q.pid);sample();if(journal.get(q.pid)?.startTicks!==client.startTicks)throw Error('client_not_owned_by_run');const argv=fs.readFileSync('/proc/'+q.pid+'/cmdline').toString().split('\0');if(argv[1]!==clientScript||argv[2]!==clientSocket||argv[3]!==r.clientNonce)throw Error('client_argv'); - child=cp.spawn(process.execPath,['-e','setTimeout(()=>process.exit(0),'+r.delayMs+')'],{env:{PATH:'/usr/bin:/bin'},stdio:'ignore'});child.once('error',()=>{attached.failure='child_start';s.destroy()});child.once('exit',(code,signal)=>{attached.commandExit={code:code??-1,observedAtMs:Date.now(),observedMonotonicNs:process.hrtime.bigint().toString()};if(code!==0||signal){attached.failure='child_failed';s.destroy();return}try{fs.writeFileSync(path.join(config.binding.remoteCwd,r.marker),r.markerText,{flag:'wx'});attached.markerWrittenAtMs=Date.now();attached.markerWrittenMonotonicNs=process.hrtime.bigint().toString();s.end(JSON.stringify({code:0})+'\n')}catch{attached.failure='marker_failed';s.destroy()}}); + const childScript=attached.waitForFinishAttempt?"const deadline=setTimeout(()=>process.exit(3),30000);process.stdin.once('data',b=>{if(b.toString()!=='release\\n')process.exit(2);clearTimeout(deadline);setTimeout(()=>process.exit(0),"+r.delayMs+")});":'setTimeout(()=>process.exit(0),'+r.delayMs+')';child=cp.spawn(process.execPath,['-e',childScript],{env:{PATH:'/usr/bin:/bin'},stdio:attached.waitForFinishAttempt?['pipe','ignore','ignore']:'ignore'});child.once('error',()=>{attached.failure='child_start';s.destroy()});child.once('exit',(code,signal)=>{attached.commandExit={code:code??-1,observedAtMs:Date.now(),observedMonotonicNs:process.hrtime.bigint().toString()};if(code!==0||signal){attached.failure='child_failed';s.destroy();return}try{fs.writeFileSync(path.join(config.binding.remoteCwd,r.marker),r.markerText,{flag:'wx'});attached.markerWrittenAtMs=Date.now();attached.markerWrittenMonotonicNs=process.hrtime.bigint().toString();s.end(JSON.stringify({code:0})+'\n')}catch{attached.failure='marker_failed';s.destroy()}}); }catch{attached.failure='client_rejected';s.destroy()}})});srv.listen(clientSocket);attached.server=srv;result={clientScript,clientSocket}; } + else if(r.op==='release-attached'){if(sealed||!publishedHash||!attached?.waitForFinishAttempt||attached.releasedAtMs!==null||!child?.stdin||child.exitCode!==null||child.signalCode!==null)throw Error('attached_release_boundary');attached.releasedAtMs=Date.now();child.stdin.end('release\n');result={releasedAtMs:attached.releasedAtMs};} + else if(r.op==='provider-death'){ + if(providerDeathDispatched||!publishedHash||sealed)throw Error('death_boundary'); + const observed=sample();if(!complete||!observed.captured)throw Error('death_owner'); + const argv=new Map(observed.live.map(pid=>[pid,deathCommand(pid)])); + const selected=selectCopilot(root.pid,observed.journal.map(p=>({...p,parent:p.ppid})),observed.live,argv,copilotArgs); + const current=identity(selected.pid),currentRoot=identity(root.pid),command=deathCommand(selected.pid); + if(current.bootId!==selected.bootId||current.startTicks!==selected.startTicks||currentRoot.startTicks!==root.startTicks||JSON.stringify(command)!==JSON.stringify(argv.get(selected.pid))||fs.realpathSync('/proc/'+selected.pid+'/exe')!==command[0])throw Error('death_identity'); + providerDeathDispatched=true;const dispatchedMonotonicNs=process.hrtime.bigint().toString();process.kill(selected.pid,'SIGKILL'); + result={schema:'paperclip.e2e.copilot-owned-provider-death.v1',root,child:current,signal:'SIGKILL',commandSha256:hash(JSON.stringify(command)),dispatchedMonotonicNs}; + } else if(r.op==='inject-loss'){const p=sample();if(sealed||!complete||!publishedHash||!root||!p.live.includes(root.pid))throw Error('loss_owner_missing');const original=journal.get(root.pid);if(!original||original.startTicks!==root.startTicks||original.bootId!==root.bootId)throw Error('loss_owner_changed'); const program="import os,signal,sys\npid=int(sys.argv[1]);fd=os.pidfd_open(pid)\ntry:\n s=open('/proc/'+str(pid)+'/stat').read();ticks=s[s.rfind(') ')+2:].split()[19];boot=open('/proc/sys/kernel/random/boot_id').read().strip()\n if ticks!=sys.argv[2] or boot!=sys.argv[3]:raise RuntimeError('identity changed')\n signal.pidfd_send_signal(fd,signal.SIGKILL)\nfinally:os.close(fd)"; const killed=cp.spawnSync('/usr/bin/python3',['-c',program,String(root.pid),root.startTicks,root.bootId],{env:{PATH:'/usr/bin:/bin'},stdio:'pipe',timeout:3000});if(killed.status!==0)throw Error('loss_not_injected');result={root:original,signal:'SIGKILL',observedMonotonicNs:process.hrtime.bigint().toString()};} @@ -241,7 +256,7 @@ server.listen(path.join(config.root,'control.sock'));setTimeout(()=>{incomplete( const ATTACHED_CLIENT = String.raw`const net=require('node:net');const s=net.connect(process.argv[2]);let b='';s.setTimeout(15000,()=>process.exit(3));s.on('error',()=>process.exit(4));s.on('connect',()=>s.write(JSON.stringify({nonce:process.argv[3],pid:process.pid})+'\n'));s.on('data',x=>{b+=x;if(b.length>1024)process.exit(6);if(b.includes('\n')){const r=JSON.parse(b);s.end();process.exit(r.code===0?0:5)}});`; function observerSource() { return OBSERVER.replace("PI_FAULT_PROGRAM", () => JSON.stringify(PI_FAULT_SOURCE)).replace("PI_CLOSURE_DIGEST", () => JSON.stringify(PI_DISTRIBUTION_CLOSURE_SHA256["linux-x64"])).replace("PARSE_STAT", () => parseRemoteProcStat.toString()).replace("RUN_ROOT", () => isRemoteRunRoot.toString()) - .replace("PROC_ENTRY_DISAPPEARED", () => remoteProcEntryDisappeared.toString()).replace("WATCH_TARGET", () => createRemoteTargetWatch.toString()).replace("ATTACHED_CLIENT", () => JSON.stringify(ATTACHED_CLIENT)); + .replace("SELECT_COPILOT", () => selectOwnedCopilotProcess.toString()).replace("CANONICAL_COPILOT", () => canonicalRemoteCopilotCommand.toString()).replace("COPILOT_ARGS", () => JSON.stringify(copilotDeathArguments)).replace("PROC_ENTRY_DISAPPEARED", () => remoteProcEntryDisappeared.toString()).replace("WATCH_TARGET", () => createRemoteTargetWatch.toString()).replace("ATTACHED_CLIENT", () => JSON.stringify(ATTACHED_CLIENT)); } const RPC = String.raw`const fs=require('node:fs'),net=require('node:net'),cp=require('node:child_process'),crypto=require('node:crypto');const r=JSON.parse(Buffer.from(process.argv[1],'base64').toString());const hash=x=>'sha256:'+crypto.createHash('sha256').update(x).digest('hex'); const startedAt=Date.now();if(!Number.isInteger(r.timeoutMs)||r.timeoutMs<1000||r.timeoutMs>300000)throw Error('rpc_deadline');setTimeout(()=>{failure('rpc_deadline');process.exit(2)},r.timeoutMs).unref(); @@ -291,7 +306,9 @@ export interface RemoteNativeFixture { readFile(path: string): Promise; publishAction(path: string, text: string): Promise; injectOwnedRunLoss(): Promise>; - setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number }): Promise<{ command: string; commandSha256: string }>; + killOwnedCopilot(): Promise>; + setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number; waitForFinishAttempt?: boolean }): Promise<{ command: string; commandSha256: string }>; + releaseAttachedCommand(): Promise<{ releasedAtMs: number }>; /** Consumes the host-held terminal receipt; never queries a deleted lease. */ finish(): Promise; /** Only fixture-owned observer cleanup. Never deletes a sandbox or lease. */ @@ -309,6 +326,7 @@ export interface RemoteNativeFixtureOptions { /** Cell deadline; receipt collection stops 15s earlier for public teardown. */ deadlineAt: number; crossRoot?: { initialText: string }; + retainTerminalDiagnostics?(diagnostics: Record): Promise; } const sha = (value: unknown): value is string => typeof value === "string" && /^sha256:[a-f0-9]{64}$/u.test(value); function readSnapshot(value: unknown, binding: RemoteNativeBinding, names: string[], actionFile: string, runnerdSha256: string): RemoteNativeSnapshot { @@ -545,6 +563,12 @@ export async function bindRemoteNativeFixture(options: RemoteNativeFixtureOption const ack = record(await rpc({ op: "publish", path, text })); fail(ack.published === true && ack.path === path && ack.sha256 === digest(text), "publish_ack"); }, + async killOwnedCopilot() { + fail(!closed && published && !finished, "death_boundary"); + const result = record(await rpc({ op: "provider-death" })); + fail(result.schema === "paperclip.e2e.copilot-owned-provider-death.v1" && result.signal === "SIGKILL" && /^sha256:[a-f0-9]{64}$/u.test(String(result.commandSha256)) && /^\d+$/u.test(String(result.dispatchedMonotonicNs)), "death_ack"); + return result; + }, async setupAttachedCommand(input) { fail(!closed && !published && targets.includes(input.marker) && Number.isInteger(input.delayMs) && input.delayMs >= 100 && input.delayMs <= 8000 && typeof input.markerText === "string" && Buffer.byteLength(input.markerText) <= 4096, "attached_bound"); @@ -554,6 +578,12 @@ export async function bindRemoteNativeFixture(options: RemoteNativeFixtureOption const command = `${quote(NODE)} ${quote(result.clientScript as string)} ${quote(result.clientSocket as string)} ${quote(clientNonce)}`; return { command, commandSha256: digest(command) }; }, + async releaseAttachedCommand() { + fail(!closed && published && !finished, "attached_release_boundary"); + const result = record(await rpc({ op: "release-attached" })); + fail(Number.isSafeInteger(result.releasedAtMs) && (result.releasedAtMs as number) >= 0, "attached_release_ack"); + return { releasedAtMs: result.releasedAtMs as number }; + }, async finish() { if (finished) return finished; const receipt = await terminal; @@ -566,8 +596,15 @@ export async function bindRemoteNativeFixture(options: RemoteNativeFixtureOption const knownFilesystemGap = result.incompleteReasons !== undefined && result.incompleteReasons.length > 0 && result.incompleteReasons.every(reason => FILESYSTEM_INCOMPLETE_REASONS.includes(reason)); if (result.processes.captured && result.processes.live.length === 0 && (result.complete || knownFilesystemGap)) retiredTerminal = result; + const allowed = new Set(TERMINAL_FAILURE_CODES); + const reasons = record(receipt.value).failureCodes; + const codes = Array.isArray(reasons) ? [...new Set(reasons.filter((code): code is string => typeof code === "string" && allowed.has(code)))].slice(0, allowed.size) : []; + await options.retainTerminalDiagnostics?.({ failureCodes: codes, actionPublished: published, setupPublished: result.setup.published, complete: result.complete, watcherComplete: result.watcher.complete, targetMutationCount: result.watcher.targetMutationCount, workspaceMutationCount: result.watcher.workspaceMutationCount, processRootCaptured: result.processes.captured, liveProcessCount: result.processes.live.length, incompleteReasons: result.incompleteReasons ?? [] }); if (!(published && result.setup.published && result.complete && result.watcher.complete && result.processes.captured && result.processes.live.length === 0)) { - throw new IncompleteRemoteTerminalEvidenceError(result); + // Closed flags explain missing evidence without retaining SDK output, + // provider text, file contents or opaque observer control credentials. + const flags = [`published=${published && result.setup.published}`, `complete=${result.complete}`, `watcher=${result.watcher.complete}`, `captured=${result.processes.captured}`, `live=${result.processes.live.length}`]; + throw new IncompleteRemoteTerminalEvidenceError(result, flags, codes); } const files = record(record(receipt.value).files); for (const name of names) { diff --git a/tests/runner-e2e/runner.spec.ts b/tests/runner-e2e/runner.spec.ts index 980c5ebff27..6094da4cf71 100644 --- a/tests/runner-e2e/runner.spec.ts +++ b/tests/runner-e2e/runner.spec.ts @@ -1,3 +1,5 @@ +import { runNativeActiveStopFlow as runCopilotActiveStopFlow } from "./copilot-active-stop-flow.js"; +import { nativeActiveStopTasks as copilotActiveStopTasks } from "./copilot-active-stop-tasks.js"; import type { RestartRunnerIdentity } from "./process-tree-owner.js"; import { runNativeActiveStopFlow } from "./native-active-stop-flow.js"; import { runPiControlsFlow } from "./pi-controls-flow.js"; @@ -923,6 +925,7 @@ for (const execution of executions) { nodeSha256: process.env.PAPERCLIP_E2E_DAYTONA_NODE_SHA256 ?? "", runnerdSha256: process.env.PAPERCLIP_E2E_DAYTONA_RUNNERD_SHA256 ?? "", deadlineAt: startedAtMs + deadlineMs, + requireCopilotTaskContext: execution.profile.qualificationCandidate === "copilot", evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets), }) : undefined; @@ -1043,14 +1046,23 @@ for (const execution of executions) { issue = story.issue as IssueRecord; selectedRuns = story.runs as RunRecord[]; matcherResults = story.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `nativeProviderLoss.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })); } else if (execution.task.flow === "native_active_stop") { - const story = await runNativeActiveStopFlow({ - page, api, fixtures, execution, nonce, workspacePath, deadlineAt: startedAtMs + deadlineMs, + const story = await (execution.profile.qualificationCandidate === "copilot" ? runCopilotActiveStopFlow : runNativeActiveStopFlow)({ + page, api, fixtures, execution: execution.profile.qualificationCandidate === "copilot" && execution.task.id === "pending-permission-stop" ? { ...execution, task: copilotActiveStopTasks[0]! } : execution, nonce, workspacePath, deadlineAt: startedAtMs + deadlineMs, observe: (currentIssue, currentRuns) => { issue = currentIssue as IssueRecord; selectedRuns = currentRuns as RunRecord[]; }, capture: captureScreenshot, evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets), remoteBootstrap, registerCleanupAssertion, registerBeforeEnvironmentTeardownAssertion, }); issue = story.issue as IssueRecord; selectedRuns = story.runs as RunRecord[]; matcherResults = story.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `nativeActiveStop.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })); + } else if (execution.task.flow === "copilot_protection") { + const story = await runCopilotProtectionFlow({ + page, api, fixtures, execution, nonce, workspacePath, deadlineAt: startedAtMs + deadlineMs, + observe: (currentIssue, currentRuns) => { issue = currentIssue as IssueRecord; selectedRuns = currentRuns as RunRecord[]; }, + capture: captureScreenshot, evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets), + remoteBootstrap, registerBeforeEnvironmentTeardownAssertion, + }); + issue = story.issue as IssueRecord; selectedRuns = story.runs as RunRecord[]; + matcherResults = story.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `copilotProtection.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })); } else if (execution.task.flow === "cursor_native") { const story = await runCursorNativeFlow({ page, api, fixtures, execution, nonce, workspacePath, deadlineAt: startedAtMs + deadlineMs, @@ -1072,16 +1084,6 @@ for (const execution of executions) { }); issue = story.issue as IssueRecord; selectedRuns = story.runs as RunRecord[]; matcherResults = story.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `piNative.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })); - } else if (execution.task.flow === "copilot_protection") { - const story = await runCopilotProtectionFlow({ - page, api, fixtures, execution, nonce, workspacePath, deadlineAt: startedAtMs + deadlineMs, - observe: (currentIssue, currentRuns) => { issue = currentIssue as IssueRecord; selectedRuns = currentRuns as RunRecord[]; }, - capture: captureScreenshot, - evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets), - remoteBootstrap, registerBeforeEnvironmentTeardownAssertion, - }); - issue = story.issue as IssueRecord; selectedRuns = story.runs as RunRecord[]; - matcherResults = story.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `copilotProtection.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })); } else if (execution.task.flow === "instruction_persistence") { const story = await runInstructionPersistenceFlow({ page, api, fixtures, execution, nonce, secrets, deadlineAt: startedAtMs + deadlineMs, diff --git a/tests/runner-e2e/warm-managed-files.test.ts b/tests/runner-e2e/warm-managed-files.test.ts index 2cbc0a9cbd3..82bff9d4a76 100644 --- a/tests/runner-e2e/warm-managed-files.test.ts +++ b/tests/runner-e2e/warm-managed-files.test.ts @@ -5,7 +5,8 @@ describe("managed warm file oracle", () => { save: { state: "saved", contract: "agent_files", checkpointStats: { copiedBytes: 16, copiedFiles: 1, hashedBytes: 16 } } }; it("accepts independently saved bytes with an incremental receipt", () => expect(gradeWarmManagedFiles(good).passed).toBe(true)); it.each([ - { content: "T1-test\n" }, { binary: Buffer.from("claimed image") }, { deletedStatus: 200 }, { save: undefined }, + { content: "T1-test\n" }, { content: "T1-test\\nT2-test\\n" }, + { binary: Buffer.from("claimed image") }, { deletedStatus: 200 }, { save: undefined }, { save: { ...good.save, checkpointStats: { copiedBytes: 8388624, copiedFiles: 2, hashedBytes: 8388624 } } }, ])("rejects missing, stale, deleted, or fully recopied data", wrong => expect(gradeWarmManagedFiles({ ...good, ...wrong }).passed).toBe(false)); });