Finish Copilot production qualification on current master

Preserve the reviewed candidate source and mainline provider behavior.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-09 10:54:14 -05:00
1 parent aaa65bbfbf
commit 6f256f3e2a
76 files changed
+12006 -206

No files matched your search

+6
View File
@@ -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/
+36 -9
View File
@@ -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 }}
+3
View File
@@ -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/
@@ -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, {
@@ -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.
File diff suppressed because it is too large. Load diff
@@ -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"
}
@@ -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
}
}
@@ -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."
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
+1 -1
View File
@@ -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()')" \
@@ -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<void>;
onSpawn?: (meta: { pid: number; startedAt: string }) => Promise<void>;
}) => {
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,
@@ -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);
}
});
@@ -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
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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("");
+9 -2
View File
@@ -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.
+22
View File
@@ -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
+13 -3
View File
@@ -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);
+34 -19
View File
@@ -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;
@@ -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<string, any>;
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<number>(), source = new Set<string>();
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<string, number>();
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<ActiveStopScope, "companyId" | "runId" | "target">; 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<typeof readActiveStopRemoteRetirement>[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 };
}
@@ -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<string, any>;
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<typeof localActiveStopProcessAuthority>, 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<ActiveStopState>;
retain(receipt: ActiveStopPending): Promise<void>;
retainAcknowledgement?(receipt: CopilotStopAcknowledgement): Promise<void>;
stop(runId: string, cancellationRequestId: string): Promise<Row>;
}) {
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<Check[]>): void;
registerBeforeEnvironmentTeardownAssertion(callback: () => Promise<Check[]>): void;
observe(issue: Row, runs: Row[]): void;
capture(id: string, label: string, file: string): Promise<void>;
evidence(name: string, value: unknown): Promise<void>;
}) {
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<typeof assertCopilotProviderDeath> | 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<string>();
const remoteObservations: ActiveStopRemoteObservation[] = [];
let retirement: ReturnType<typeof assertActiveStopRetirement> | undefined;
const samples: Array<{ phase: string; absent: boolean }> = [];
let fixture: CopilotRemoteFixture | undefined, baseline: CopilotRemoteSnapshot | undefined, sealed: CopilotRemoteSnapshot | undefined;
let completed: Awaited<ReturnType<typeof stopAtPendingPermission>> | 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<ActiveStopState> => {
if (issue.id) issue = await api.get<Row>(`/api/issues/${issue.id}`);
const list = await api.get<Row[]>(`/api/companies/${fixtures.company.id}/heartbeat-runs?limit=100`);
runs = await Promise.all(list.map(r => api.get<Row>(`/api/heartbeat-runs/${r.id}`)));
if (runs.length > 1) throw new Error("Native active Stop dispatched an extra run");
events = runs[0] ? await collectRunEvents<Row>((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<Row>(`/api/agents/${fixtures.agent.id}`);
const configured = await api.patch<Row>(`/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<Row>(`/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<Row[]>(`/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 });
}
}
@@ -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: () => [],
}];
@@ -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<string, any>;
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<typeof observeActiveStopPending> | 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<typeof fixture>) => { f.events.splice(1, 1); }],
["missing canonical start", (f: ReturnType<typeof fixture>) => { f.events.splice(2, 1); }],
["wrong native tool", (f: ReturnType<typeof fixture>) => { payload(f.events[1]!).details.find((d: Row) => d.name === "toolCallId").value = "other"; }],
["wrong canonical tool", (f: ReturnType<typeof fixture>) => { payload(f.events[2]!).executionId = "other"; }],
["wrong command or path", (f: ReturnType<typeof fixture>) => { payload(f.events[1]!).details.find((d: Row) => d.name === "target").value = "other.txt"; }],
["wrong source", (f: ReturnType<typeof fixture>) => { frame(f.events[2]!).sourceInstanceId = "other"; frame(f.events[2]!).sourceEventId = "other:run:3"; }],
["wrong turn", (f: ReturnType<typeof fixture>) => { frame(f.events[2]!).turnId = "other"; }],
["wrong request", (f: ReturnType<typeof fixture>) => { payload(f.events[0]!).request.requestId = "other"; }],
["replayed canonical start", (f: ReturnType<typeof fixture>) => { f.events.push(f.row(5, "tool.execution.started", payload(f.events[2]!))); }],
["replayed native origin", (f: ReturnType<typeof fixture>) => { f.events.push(f.row(5, "provider.notice.recorded", payload(f.events[1]!))); }],
["answered permission", (f: ReturnType<typeof fixture>) => { f.events.push(f.notice(5, "permission_delivered", { requestId: "request", outcome: "cancel" })); }],
["terminal operation", (f: ReturnType<typeof fixture>) => { 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<typeof withSessionPrefix>) => { frame(f.events[1]!).schemaVersion = 1; }],
["row/envelope mismatch", (f: ReturnType<typeof withSessionPrefix>) => { f.events[1]!.protocolSchemaVersion = 1; }],
["unknown schema", (f: ReturnType<typeof withSessionPrefix>) => { frame(f.events[1]!).schema = "paperclip.prp.event.v3"; frame(f.events[1]!).schemaVersion = f.events[1]!.protocolSchemaVersion = 3; }],
["foreign session source", (f: ReturnType<typeof withSessionPrefix>) => { 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<typeof fixture>) => { f.run.resultJson.startupCancellation = { cancellationRequestId }; }],
["answered callback", (f: ReturnType<typeof fixture>) => { f.events.push(f.notice(5, "permission_delivered", { requestId: "request", outcome: "cancel" })); }],
["already settled provider", (f: ReturnType<typeof fixture>) => { f.events.push(f.row(5, "turn.completed", { status: "completed" })); }],
["already closed request", (f: ReturnType<typeof fixture>) => { f.events.push(f.row(5, "runtime_request.resolved", {})); }],
["failed operation", (f: ReturnType<typeof fixture>) => { 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<typeof settled>) => { f.events[5]!.eventType = frame(f.events[5]!).eventType = "turn.completed"; payload(f.events[5]!).status = "completed"; }],
["interrupted terminal", ({ f }: ReturnType<typeof settled>) => { f.events[5]!.eventType = frame(f.events[5]!).eventType = "turn.interrupted"; payload(f.events[5]!).status = "interrupted"; }],
["failed terminal", ({ f }: ReturnType<typeof settled>) => { payload(f.events[5]!).error = { code: "failure" }; }],
["duplicate terminal", ({ f }: ReturnType<typeof settled>) => { f.events.push(f.row(7, "turn.cancelled", payload(f.events[5]!))); }],
["late same-tool replay", ({ f }: ReturnType<typeof settled>) => { f.events.push(f.notice(7, "tool", { status: "in_progress" })); }],
["wrong closed item", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).itemId = "other"; }],
["reordered source stream", ({ f }: ReturnType<typeof settled>) => { frame(f.events[5]!).sourceSeq = 1; frame(f.events[5]!).sourceEventId = "source:run:1"; }],
["missing terminal", ({ f }: ReturnType<typeof settled>) => { f.events.pop(); }],
["wrapper type mismatch", ({ f }: ReturnType<typeof settled>) => { f.events[5]!.eventType = "turn.completed"; }],
["foreign normalized session", ({ f }: ReturnType<typeof settled>) => { frame(f.events[5]!).normalizedSessionId = "native-session"; }],
["foreign source", ({ f }: ReturnType<typeof settled>) => { frame(f.events[5]!).sourceInstanceId = "foreign"; }],
["foreign turn", ({ f }: ReturnType<typeof settled>) => { frame(f.events[5]!).turnId = "other-turn"; }],
["foreign company", ({ f }: ReturnType<typeof settled>) => { f.events[5]!.companyId = "other"; }],
["foreign request", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).requestId = "other"; }],
["expired rather than cancelled", ({ f }: ReturnType<typeof settled>) => { f.events[4]!.eventType = frame(f.events[4]!).eventType = "runtime_request.expired"; }],
["provider death", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).reason = "provider_process_lost"; }],
["raw backend closure is not the Product contract", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).reason = "explicit_cancellation"; }],
["answered closure", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).action = "accept"; }],
["resolved request", ({ f }: ReturnType<typeof settled>) => { f.events[4]!.eventType = frame(f.events[4]!).eventType = "runtime_request.resolved"; }],
["duplicate closure", ({ f }: ReturnType<typeof settled>) => { f.events.push(f.row(7, "runtime_request.cancelled", payload(f.events[4]!))); }],
["foreign closure turn", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).turnId = "other"; }],
["missing acknowledgement", ({ f }: ReturnType<typeof settled>) => { delete f.run.resultJson.nativeCancellation.acknowledgementAuditId; }],
["unacknowledged intent", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.nativeCancellation.dispatchState = "pending"; }],
["replay permitted", ({ f }: ReturnType<typeof settled>) => { payload(f.events[4]!).replayAllowed = true; }],
["tampered pending row", ({ f }: ReturnType<typeof settled>) => { payload(f.events[3]!).request.itemId = "changed"; }],
["missing pending receipt", (s: ReturnType<typeof settled>) => { s.pending.requestRowSha256 = ""; }],
["legacy v1 pending receipt", (s: ReturnType<typeof settled>) => { (s.pending as Row).schema = "paperclip.e2e.native-active-stop-pending.v1"; }],
["missing native origin binding", (s: ReturnType<typeof settled>) => { delete (s.pending as Row).toolOriginRowSha256; }],
["missing tool start binding", (s: ReturnType<typeof settled>) => { delete (s.pending as Row).toolStartedRowSha256; }],
["changed native origin", ({ f }: ReturnType<typeof settled>) => { frame(f.events[0]!).emittedAt = "2026-01-01T00:00:01Z"; }],
["changed tool start", ({ f }: ReturnType<typeof settled>) => { frame(f.events[1]!).emittedAt = "2026-01-01T00:00:01Z"; }],
["wrong origin sequence", (s: ReturnType<typeof settled>) => { s.pending.toolOriginSourceSeq += 1; }],
["wrong start sequence", (s: ReturnType<typeof settled>) => { s.pending.toolStartedSourceSeq += 1; }],
["missing caller", ({ f }: ReturnType<typeof settled>) => { delete f.run.resultJson.cancelledByUserId; }],
["null caller", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.cancelledByUserId = null; }],
["foreign caller", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.cancellation.initiator.id = "another-board-user"; }],
["agent caller", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.cancelledByActorType = "agent"; }],
["unbound caller receipt", (s: ReturnType<typeof settled>) => { (s.pending as Row).caller = null; }],
["foreign intent", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.nativeCancellation.intentId = "native-cancellation:other"; }],
["foreign ack scope", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.nativeCancellation.companyId = "other"; }],
["undispatched cancellation", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.nativeCancellation.dispatched = false; }],
["same audit twice", ({ f }: ReturnType<typeof settled>) => { f.run.resultJson.nativeCancellation.acknowledgementAuditId = "audit-intent"; }],
["finished task", ({ f }: ReturnType<typeof settled>) => { f.issue.status = "done"; }],
["extra semantic operation", ({ f }: ReturnType<typeof settled>) => { f.events.push(f.row(7, "tool.execution.started", { executionId: "other" })); }],
["extra native operation", ({ f }: ReturnType<typeof settled>) => { f.events.push(f.notice(7, "tool", { toolCallId: "other", status: "pending" })); }],
["native session switched", ({ f }: ReturnType<typeof settled>) => { payload(f.events[0]!).provenance.sessionId = "other"; }],
["target operation completed", ({ f }: ReturnType<typeof settled>) => { 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<typeof proof>) => { p.processes.live.push(42); }],
["unobserved process", (p: ReturnType<typeof proof>) => { p.processes.captured = false; }],
["replaced root", (p: ReturnType<typeof proof>) => { p.identityChanged = true; }],
["incomplete watch", (p: ReturnType<typeof proof>) => { p.watcher.complete = false; }],
["transient write", (p: ReturnType<typeof proof>) => { p.watcher.targetMutationCount = 1; }],
["written target", (p: ReturnType<typeof proof>) => { p.samples[2]!.absent = false; }],
["missing final sample", (p: ReturnType<typeof proof>) => { p.samples.pop(); }],
["missing settlement", (p: ReturnType<typeof proof>) => { 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<typeof remoteRetirementProof>) => { p.observations[2]!.source = "live-snapshot"; }],
["seal replayed as pending", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot = p.observations[2]!.snapshot; }],
["after-UI seal replay", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations.push({ ...p.observations[2]!, phase: "after-cleanup" as any }); }],
["missing pending", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations.splice(1, 1); }],
["all foreign run snapshots", (p: ReturnType<typeof remoteRetirementProof>) => { for (const o of p.observations) o.snapshot.binding.runId = "other"; }],
["all foreign company snapshots", (p: ReturnType<typeof remoteRetirementProof>) => { for (const o of p.observations) o.snapshot.binding.companyId = "other"; }],
["foreign lease", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.binding.leaseId = "other"; }],
["foreign run", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot.binding.runId = "other"; }],
["replacement root", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.root = { ...p.observations[2]!.snapshot.processes.root!, startTicks: "300" }; }],
["live child", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.live = [51]; }],
["lost child journal", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.journal.pop(); }],
["unobserved pending root", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot.processes.live = []; }],
["transient target mutation", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.targetMutationCount = 1; }],
["workspace mutation", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.workspaceMutationCount = 1; }],
["late target present", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.targets["target.txt"]!.absent = false; }],
["incomplete watcher", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.complete = false; }],
["changed action", (p: ReturnType<typeof remoteRetirementProof>) => { 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 }));
});
});
@@ -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<string, any>;
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();
});
});
@@ -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<string, any>;
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<string, string> = {};
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 } : {}) };
}
@@ -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<void>(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);
});
});
@@ -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<string, any>;
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<void>;
}) {
const label = "initial Copilot context permission", decided = new Set<string>();
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<void>;
approveContext(): Promise<void>;
}): Promise<void> {
await input.remoteSetup?.();
await input.approveContext();
}
@@ -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/);
});
});
+17
View File
@@ -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];
}
+84 -13
View File
@@ -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<T extends { captured: boolean; live: number[]; identityChanged?: boolean }>(previous: T, current: T): T & { identityChanged: boolean } {
return { ...current, identityChanged: previous.identityChanged === true || current.identityChanged === true };
}
export function observeRunProcesses() {
const owned = new Map<number, ProcessIdentity>(); let root: ProcessIdentity | undefined;
const owned = new Map<number, ProcessIdentity>(); 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<Socket>();
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;
@@ -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<void>(resolve => child.once("exit", () => resolve()));
try {
await new Promise<void>((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/<pid> 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);
});
});
@@ -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('<div data-testid="task-chat-runtime-request">get_task_context — resolved</div><div data-testid="task-chat-runtime-request">Change file: pc-denied-A/target.txt<button>Deny</button><button>Allow once</button></div>');
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('<div data-testid="task-chat-runtime-request">Change file: expected.txt — resolved</div><div data-testid="task-chat-runtime-request">Change file: foreign.txt<button>Deny</button></div>');
await expect(copilotPendingPermissionCard(page, "expected.txt")).toHaveCount(0);
await page.setContent('<div data-testid="task-chat-runtime-request">Change file: expected.txt<button>Deny</button></div><div data-testid="task-chat-runtime-request">Change file: expected.txt<button>Deny</button></div>');
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('<div data-testid="task-chat-runtime-request">search_api: get_task_context — resolved</div><div data-testid="task-chat-runtime-request">get_task_context<button>Deny</button><button>Allow once</button></div>');
await expect(copilotPendingPermissionCard(page, "get_task_context")).toHaveCount(1);
await expect(copilotPendingPermissionCard(page, "search_api")).toHaveCount(0);
});
@@ -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 }) });
}
@@ -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();
});
});
@@ -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"] },
@@ -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"]);
+28 -13
View File
@@ -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<CopilotRemoteSnapshot>;
setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number }): Promise<{ command: string; commandSha256: string }>;
killOwnedCopilot(): Promise<Record<string, unknown>>;
setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number; waitForFinishAttempt?: boolean }): Promise<{ command: string; commandSha256: string }>;
releaseAttachedCommand(): Promise<{ releasedAtMs: number }>;
finish(): Promise<CopilotRemoteSnapshot>; readFile(relative: string): Promise<Buffer>; close(): Promise<void>;
}
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) },
@@ -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<number | null>(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 () => {
+85 -49
View File
@@ -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<string, any>;
@@ -20,6 +23,7 @@ export async function settleCopilotDeniedRun(input: {
load(): Promise<{ events: readonly unknown[]; run: Row; issue: Row; retired: boolean }>;
afterDeniedEdit(): Promise<void>;
retainPreStop(receipt: CopilotPreStopObservation): Promise<void>;
retainStopAcknowledgement?(receipt: CopilotStopAcknowledgement): Promise<void>;
afterSettlement(): Promise<void>;
}) {
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<Row>(`/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<ReturnType<typeof input.load>>;
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<Row>(`/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<Row>((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<ReturnType<typeof load>>) => 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<Row[]>(`/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<Row[]>(`/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");
@@ -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<typeof fixture>) => f.events[4]!.payload.prpEvent;
const liveRun = (f: ReturnType<typeof fixture>) => ({ ...f.run, status: "running", resultJson: {} });
function stopPost(f: ReturnType<typeof fixture>, 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<ReturnType<typeof observeCopilotPreStop>> = [];
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"]);
});
});
+2 -1
View File
@@ -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.",
@@ -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<string, unknown>) => ({ 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();
});
});
@@ -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<T extends { pid: number; parent: number }>(
rootPid: number, journal: readonly T[], live: readonly number[], argv: ReadonlyMap<number, readonly string[]>, 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<number>(); 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<string, any>; issue: Record<string, any>; 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 };
}
@@ -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"');
});
+38 -5
View File
@@ -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<string, any>;
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<string, CopilotToolNotice[]>();
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];
}
@@ -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/);
});
});
@@ -0,0 +1,46 @@
import { createHash } from "node:crypto";
import { canonicalJson } from "../../packages/shared/src/portability-hash.js";
type Row = Record<string, any>;
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");
}
@@ -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<string, any>) {
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<string, any>, 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());
}
+10
View File
@@ -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",
+33
View File
@@ -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",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
}
}
@@ -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"
}
}
+1 -1
View File
@@ -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");
+9 -7
View File
@@ -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<ManagedAccountFixture>({
id: "ai-connection",
dependencies: ["company"],
dependencies: ["company", ...(savedCopilot ? ["environment"] : [])],
async setup(resolved) {
const company = value<CompanyRecord>(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<EnvironmentRecord>(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<CompanyRecord>(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<ManagedAccountFixture>(resolved, "ai-connection");
const config = agent.adapterConfig as Record<string, unknown>;
delete config.env;
+1 -1
View File
@@ -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);
@@ -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] });
@@ -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,
@@ -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 {
+9 -1
View File
@@ -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);
}
}
@@ -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");
@@ -63,6 +63,7 @@ export async function createRemoteFixtureClient(apiKey: string): Promise<RemoteF
export function createRemoteNativeBootstrap(input: {
api: RemoteFixtureApi; daytona: RemoteFixtureDaytona; companyId: string; environmentId: string;
agentId: string; image: string; nodeSha256: string; runnerdSha256: string; deadlineAt: number;
requireCopilotTaskContext?: boolean;
evidence(name: string, data: unknown): Promise<void>;
}, 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") {
@@ -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<number, string>(); let nextFd = 50, runtimeInode = 4n;
const symbolicLinks = new Set<string>(), directoryInodes = new Map<string, bigint>();
const processExecutables = new Map<string, { path: string; dev: bigint; ino: bigint }>();
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<string, (socket: any) => 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");
+52 -15
View File
@@ -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<Buffer>;
publishAction(path: string, text: string): Promise<void>;
injectOwnedRunLoss(): Promise<Record<string, unknown>>;
setupAttachedCommand(input: { marker: string; markerText: string; delayMs: number }): Promise<{ command: string; commandSha256: string }>;
killOwnedCopilot(): Promise<Record<string, unknown>>;
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<RemoteNativeSnapshot>;
/** 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<string, boolean | number | readonly string[]>): Promise<void>;
}
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<string>(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) {
+14 -12
View File
@@ -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,
+2 -1
View File
@@ -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));
});