diff --git a/.agents/skills/verify-paperclip/SKILL.md b/.agents/skills/verify-paperclip/SKILL.md index b570578d092..3bdc6e759e2 100644 --- a/.agents/skills/verify-paperclip/SKILL.md +++ b/.agents/skills/verify-paperclip/SKILL.md @@ -189,6 +189,26 @@ For Paperclip-assigned work, attach important proof through the evidence directory or issue, not in the map. The generated `maps/generated/coverage.json` lists page references, not live test health. +### Preserve failures through acceptance + +A successful retry establishes that a later attempt succeeded. Retain earlier +material failures in the verification record, including the failed requirement, +relevant logs, and what changed before the retry. Use a focused comparison or +reproduction to determine whether the change caused the failure. When useful, +check the unchanged baseline under comparable conditions. If the cause remains +unknown, state that uncertainty; do not infer an infrastructure fault from a +passing retry alone. + +Report recovery and resolution separately. A later passing journey can show +recovery without explaining the earlier failure. Do not describe acceptance as +complete while a relevant requirement remains unverified. Record the remaining +gap and the next discriminating check instead of repeating retries until green. + +Apply this to failures that bear on the changed behavior or requested acceptance +criteria. Independently established infrastructure failures can be tracked +separately, with supporting evidence and any checks they prevented still marked +blocked or not run. This does not require investigating every unrelated failure. + ## Cleanup Close the browser context you created (the smoke does this in `finally`). Leave diff --git a/.github/scripts/tests/ci-routing.test.mjs b/.github/scripts/tests/ci-routing.test.mjs index a9703feb747..b6cf5ff2b3e 100644 --- a/.github/scripts/tests/ci-routing.test.mjs +++ b/.github/scripts/tests/ci-routing.test.mjs @@ -29,7 +29,7 @@ test("routing exports both placement and stack scope to callers", () => { assert.match(jobs, /^ {6}full_ci: \$\{\{ steps\.scope\.outputs\.full_ci \}\}$/m); }); -const githubStub = "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nendpoint=\"${*: -1}\"\ncase \"$endpoint\" in\n */pulls/*)\n jq -cn \\\n --argjson author_id \"${TEST_PR_AUTHOR_ID:-34892728}\" \\\n --argjson repository_id \"${TEST_REPOSITORY_ID:-1170821064}\" \\\n --arg base_ref \"${TEST_BASE_REF:-master}\" \\\n --arg base_sha \"${TEST_PR_BASE_SHA:-$EVENT_BASE_SHA}\" \\\n --arg head_sha \"${TEST_PR_HEAD_SHA:-1111111111111111111111111111111111111111}\" \\\n --arg merge_sha \"${TEST_PR_MERGE_SHA:-4444444444444444444444444444444444444444}\" '{\n state: \"open\",\n user: {id: $author_id},\n base: {repo: {id: $repository_id}, ref: $base_ref, sha: $base_sha},\n head: {sha: $head_sha},\n merge_commit_sha: $merge_sha\n }'\n ;;\n */git/ref/heads/*)\n jq -cn \\\n --arg sha \"${TEST_LIVE_BASE_REF_SHA:-$EVENT_BASE_SHA}\" '{object: {sha: $sha}}'\n ;;\n */compare/*)\n jq -cn \\\n --arg merge_base_sha \"${TEST_BASE_COMPARISON_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\" \\\n --arg status \"${TEST_BASE_COMPARISON_STATUS:-ahead}\" '{\n merge_base_commit: {sha: $merge_base_sha},\n status: $status\n }'\n ;;\n */git/commits/*)\n requested_sha=\"${endpoint##*/}\"\n live_merge_sha=\"${TEST_PR_MERGE_SHA:-4444444444444444444444444444444444444444}\"\n synthetic_base_merge_sha=\"${TEST_SYNTHETIC_BASE_MERGE_SHA:-}\"\n if [[ \"$requested_sha\" == \"$EVENT_MERGE_SHA\" ]]; then\n tree_sha=\"${TEST_EVENT_MERGE_TREE_SHA:-5555555555555555555555555555555555555555}\"\n base_sha=\"${TEST_EVENT_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\"\n head_sha=\"${TEST_EVENT_MERGE_HEAD_SHA:-$EVENT_HEAD_SHA}\"\n elif [[ \"$requested_sha\" == \"$live_merge_sha\" ]]; then\n tree_sha=\"${TEST_LIVE_MERGE_TREE_SHA:-5555555555555555555555555555555555555555}\"\n base_sha=\"${TEST_LIVE_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\"\n head_sha=\"${TEST_LIVE_MERGE_HEAD_SHA:-$EVENT_HEAD_SHA}\"\n elif [[ -n \"$synthetic_base_merge_sha\" && \"$requested_sha\" == \"$synthetic_base_merge_sha\" ]]; then\n tree_sha=\"${TEST_SYNTHETIC_BASE_MERGE_TREE_SHA:-8888888888888888888888888888888888888888}\"\n base_sha=\"${TEST_SYNTHETIC_MASTER_PARENT_SHA:-9999999999999999999999999999999999999999}\"\n head_sha=\"${TEST_SYNTHETIC_BASE_REF_PARENT_SHA:-${TEST_LIVE_BASE_REF_SHA:-$EVENT_BASE_SHA}}\"\n else\n echo \"unexpected merge commit: $requested_sha\" >&2\n exit 1\n fi\n jq -cn \\\n --arg sha \"$requested_sha\" \\\n --arg tree_sha \"$tree_sha\" \\\n --arg base_sha \"$base_sha\" \\\n --arg head_sha \"$head_sha\" '{\n sha: $sha,\n tree: {sha: $tree_sha},\n parents: [{sha: $base_sha}, {sha: $head_sha}]\n }'\n ;;\n */actions/runs/*)\n jq -cn \\\n --argjson actor_id \"${TEST_TRIGGERING_ACTOR_ID:-34892728}\" \\\n --argjson repository_id \"${TEST_REPOSITORY_ID:-1170821064}\" \\\n --arg event_name \"${TEST_EVENT_NAME:-pull_request}\" '{\n triggering_actor: {id: $actor_id},\n repository: {id: $repository_id},\n event: $event_name\n }'\n ;;\n *)\n echo \"unexpected gh endpoint: $endpoint\" >&2\n exit 1\n ;;\nesac"; +const githubStub = "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nendpoint=\"${*: -1}\"\ncase \"$endpoint\" in\n */pulls/*)\n jq -cn \\\n --argjson author_id \"${TEST_PR_AUTHOR_ID:-34892728}\" \\\n --argjson repository_id \"${TEST_REPOSITORY_ID:-1170821064}\" \\\n --arg base_ref \"${TEST_BASE_REF:-master}\" \\\n --arg base_sha \"${TEST_PR_BASE_SHA:-$EVENT_BASE_SHA}\" \\\n --arg head_sha \"${TEST_PR_HEAD_SHA:-1111111111111111111111111111111111111111}\" \\\n --arg merge_sha \"${TEST_PR_MERGE_SHA:-4444444444444444444444444444444444444444}\" '{\n state: \"open\",\n user: {id: $author_id},\n base: {repo: {id: $repository_id}, ref: $base_ref, sha: $base_sha},\n head: {sha: $head_sha},\n merge_commit_sha: $merge_sha\n }'\n ;;\n */git/ref/heads/*)\n jq -cn \\\n --arg sha \"${TEST_LIVE_BASE_REF_SHA:-$EVENT_BASE_SHA}\" '{object: {sha: $sha}}'\n ;;\n */compare/*)\n [[ -z \"${TEST_COMPARE_FAIL:-}\" ]] || exit 1\n default_files='[{\"filename\":\"server/src/index.ts\"}]'\n jq -cn \\\n --arg merge_base_sha \"${TEST_BASE_COMPARISON_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\" \\\n --arg status \"${TEST_BASE_COMPARISON_STATUS:-ahead}\" \\\n --argjson files \"${TEST_COMPARE_FILES:-$default_files}\" '{\n merge_base_commit: {sha: $merge_base_sha},\n status: $status,\n files: $files\n }'\n ;;\n */git/commits/*)\n requested_sha=\"${endpoint##*/}\"\n live_merge_sha=\"${TEST_PR_MERGE_SHA:-4444444444444444444444444444444444444444}\"\n synthetic_base_merge_sha=\"${TEST_SYNTHETIC_BASE_MERGE_SHA:-}\"\n if [[ \"$requested_sha\" == \"$EVENT_MERGE_SHA\" ]]; then\n tree_sha=\"${TEST_EVENT_MERGE_TREE_SHA:-5555555555555555555555555555555555555555}\"\n base_sha=\"${TEST_EVENT_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\"\n head_sha=\"${TEST_EVENT_MERGE_HEAD_SHA:-$EVENT_HEAD_SHA}\"\n elif [[ \"$requested_sha\" == \"$live_merge_sha\" ]]; then\n tree_sha=\"${TEST_LIVE_MERGE_TREE_SHA:-5555555555555555555555555555555555555555}\"\n base_sha=\"${TEST_LIVE_MERGE_BASE_SHA:-$EVENT_BASE_SHA}\"\n head_sha=\"${TEST_LIVE_MERGE_HEAD_SHA:-$EVENT_HEAD_SHA}\"\n elif [[ -n \"$synthetic_base_merge_sha\" && \"$requested_sha\" == \"$synthetic_base_merge_sha\" ]]; then\n tree_sha=\"${TEST_SYNTHETIC_BASE_MERGE_TREE_SHA:-8888888888888888888888888888888888888888}\"\n base_sha=\"${TEST_SYNTHETIC_MASTER_PARENT_SHA:-9999999999999999999999999999999999999999}\"\n head_sha=\"${TEST_SYNTHETIC_BASE_REF_PARENT_SHA:-${TEST_LIVE_BASE_REF_SHA:-$EVENT_BASE_SHA}}\"\n else\n echo \"unexpected merge commit: $requested_sha\" >&2\n exit 1\n fi\n jq -cn \\\n --arg sha \"$requested_sha\" \\\n --arg tree_sha \"$tree_sha\" \\\n --arg base_sha \"$base_sha\" \\\n --arg head_sha \"$head_sha\" '{\n sha: $sha,\n tree: {sha: $tree_sha},\n parents: [{sha: $base_sha}, {sha: $head_sha}]\n }'\n ;;\n */actions/runs/*)\n jq -cn \\\n --argjson actor_id \"${TEST_TRIGGERING_ACTOR_ID:-34892728}\" \\\n --argjson repository_id \"${TEST_REPOSITORY_ID:-1170821064}\" \\\n --arg event_name \"${TEST_EVENT_NAME:-pull_request}\" '{\n triggering_actor: {id: $actor_id},\n repository: {id: $repository_id},\n event: $event_name\n }'\n ;;\n *)\n echo \"unexpected gh endpoint: $endpoint\" >&2\n exit 1\n ;;\nesac"; function stepScript(name) { const start = workflow.indexOf(` - name: ${name}\n`); @@ -137,12 +137,82 @@ const scopeCases = [ for (const [name, stack, base, expected] of scopeCases) { test(`scope decision: ${name}`, () => { - assert.equal(executeStep("Select stacked PR CI scope", { + assert.equal(executeStep("Select PR CI scope", { STACK_JSON: JSON.stringify(stack), PR_BASE_REF: base, }).full_ci, expected); }); } +const files = (...paths) => JSON.stringify(paths.map((filename) => ({ filename }))); +const manyReleaseNotes = JSON.stringify(Array.from({ length: 300 }, (_, index) => ({ + filename: `releases/beta/v2026.1012.${index}-beta.0.md`, +}))); + +const pathScopeCases = [ + ["stable release notes", { TEST_COMPARE_FILES: files("releases/v2026.1015.0.md") }, "false"], + ["beta release notes draft", { TEST_COMPARE_FILES: files("releases/beta/v2026.1012.1-beta.0.md") }, "false"], + ["stable and beta notes", { TEST_COMPARE_FILES: files("releases/v2026.1015.0.md", "releases/beta/v2026.1012.1-beta.0.md") }, "false"], + ["removed release notes", { TEST_COMPARE_FILES: JSON.stringify([{ filename: "releases/beta/v2026.1006.0-beta.0.md", status: "removed" }]) }, "false"], + ["release notes and code", { TEST_COMPARE_FILES: files("releases/v2026.1015.0.md", "server/src/index.ts") }, "true"], + ["code renamed into releases", { TEST_COMPARE_FILES: JSON.stringify([{ filename: "releases/v2026.1015.0.md", previous_filename: "scripts/release.sh" }]) }, "true"], + ["non-markdown file in releases", { TEST_COMPARE_FILES: files("releases/beta/v2026.1012.1-beta.0.json") }, "true"], + ["nested releases directory", { TEST_COMPARE_FILES: files("releases/beta/drafts/v2026.1012.1-beta.0.md") }, "true"], + ["markdown outside releases", { TEST_COMPARE_FILES: files("doc/RELEASING.md") }, "true"], + ["file entry without a name", { TEST_COMPARE_FILES: JSON.stringify([{ status: "modified" }]) }, "true"], + ["empty diff", { TEST_COMPARE_FILES: "[]" }, "true"], + ["possibly truncated file list", { TEST_COMPARE_FILES: manyReleaseNotes }, "true"], + ["compare lookup failure", { TEST_COMPARE_FAIL: "1", TEST_COMPARE_FILES: files("releases/v2026.1015.0.md") }, "true"], + ["non-PR event", { EVENT_NAME: "workflow_dispatch", TEST_COMPARE_FILES: files("releases/v2026.1015.0.md") }, "true"], + ["malformed head SHA", { EVENT_HEAD_SHA: "invalid", TEST_COMPARE_FILES: files("releases/v2026.1015.0.md") }, "true"], +]; + +for (const [name, env, expected] of pathScopeCases) { + test(`path scope decision: ${name}`, () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: "null", PR_BASE_REF: "master", ...env, + }).full_ci, expected); + }); +} + +test("path scope keeps full CI for the top PR in a stack that touches code", () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: JSON.stringify({ position: 11, size: 11, base: { ref: "master" } }), + PR_BASE_REF: "stack-10", + }).full_ci, "true"); +}); + +test("path scope keeps full CI for release notes on top of code-changing stacked PRs", () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: JSON.stringify({ position: 11, size: 11, base: { ref: "master" } }), + PR_BASE_REF: "stack-10", + TEST_COMPARE_FILES: files("releases/v2026.1015.0.md"), + }).full_ci, "true"); +}); + +test("path scope keeps full CI for release notes with malformed stack metadata", () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: JSON.stringify({ position: 0, size: 2, base: { ref: "master" } }), + PR_BASE_REF: "master", + TEST_COMPARE_FILES: files("releases/v2026.1015.0.md"), + }).full_ci, "true"); +}); + +test("path scope skips full CI for the lowest PR in a stack that only touches release notes", () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: JSON.stringify({ position: 1, size: 3, base: { ref: "master" } }), + PR_BASE_REF: "master", + TEST_COMPARE_FILES: files("releases/v2026.1015.0.md"), + }).full_ci, "false"); +}); + +test("path scope skips full CI for a single-PR stack that only touches release notes", () => { + assert.equal(executeStep("Select PR CI scope", { + STACK_JSON: JSON.stringify({ position: 1, size: 1, base: { ref: "master" } }), + PR_BASE_REF: "master", + TEST_COMPARE_FILES: files("releases/v2026.1015.0.md"), + }).full_ci, "false"); +}); + function routingPlacement(vars = {}, github = {}) { const source = "[34892728]"; diff --git a/.github/workflows/ci-routing.yml b/.github/workflows/ci-routing.yml index 5ad3e1e11e3..a146b834bd2 100644 --- a/.github/workflows/ci-routing.yml +++ b/.github/workflows/ci-routing.yml @@ -215,10 +215,14 @@ jobs: echo "runner=$aws_runner" >> "$GITHUB_OUTPUT" echo '::notice title=AWS CI routing::Using an ephemeral RunsOn Fleet runner' - - name: Select stacked PR CI scope + - name: Select PR CI scope id: scope shell: bash env: + GH_TOKEN: ${{ github.token }} + EVENT_NAME: ${{ github.event_name }} + EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }} + EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} STACK_JSON: ${{ toJSON(github.event.pull_request.stack) }} PR_BASE_REF: ${{ github.event.pull_request.base.ref }} run: | @@ -226,17 +230,53 @@ jobs: full_ci='true' reason='ordinary pull request' + # True when the PR's own diff is everything it adds to the stack's + # base branch: no stack, or the lowest unmerged PR in one. Above + # that, the diff omits lower PRs' code that this run must still test. + diff_covers_stack='true' + + # Release notes PRs touch only releases/v*.md or releases/beta/v*.md, + # which no build, test, or e2e lane reads. The diff is pinned to the + # event's base and head SHAs, so a later push cannot change the + # decision for this run. Any lookup failure keeps full CI. + release_notes_only() { + [[ "${EVENT_NAME:-}" == 'pull_request' ]] || return 1 + [[ "${EVENT_BASE_SHA:-}" =~ ^[0-9a-f]{40}$ ]] || return 1 + [[ "${EVENT_HEAD_SHA:-}" =~ ^[0-9a-f]{40}$ ]] || return 1 + + local comparison + comparison="$(gh api \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "/repos/paperclipai/paperclip/compare/$EVENT_BASE_SHA...$EVENT_HEAD_SHA" 2>/dev/null)" \ + || return 1 + + # The compare API lists at most 300 files, so a full page may be + # truncated. Renames count both the old and the new path. + jq -e ' + (.files | type == "array") and + (.files | length > 0 and length < 300) and + all(.files[]; + (.filename | type == "string") and + ([.filename, .previous_filename] | map(select(. != null)) | + all(.[]; type == "string" and test("^releases/(beta/)?v[^/]+\\.md$")))) + ' <<< "$comparison" >/dev/null 2>&1 + } if jq -e 'type == "object"' <<< "$STACK_JSON" >/dev/null 2>&1; then stack_position="$(jq -r '.position // empty' <<< "$STACK_JSON")" stack_size="$(jq -r '.size // empty' <<< "$STACK_JSON")" stack_base_ref="$(jq -r '.base.ref // empty' <<< "$STACK_JSON")" + if [[ -z "$stack_base_ref" || "$stack_base_ref" != "$PR_BASE_REF" ]]; then + diff_covers_stack='false' + fi if [[ ! "$stack_position" =~ ^[1-9][0-9]*$ ]] || [[ ! "$stack_size" =~ ^[1-9][0-9]*$ ]] || (( stack_position > stack_size )) || [[ -z "$stack_base_ref" ]]; then reason='malformed stack metadata; defaulting to full CI' + diff_covers_stack='false' elif (( stack_position == stack_size )); then reason='top pull request in stack' elif [[ "$stack_base_ref" == "$PR_BASE_REF" ]]; then @@ -247,6 +287,12 @@ jobs: fi fi + if [[ "$full_ci" == 'true' && "$diff_covers_stack" == 'true' ]] && + release_notes_only; then + full_ci='false' + reason='release notes only' + fi + echo "full_ci=$full_ci" >> "$GITHUB_OUTPUT" - echo "::notice title=Stacked PR CI scope::$reason; full_ci=$full_ci" + echo "::notice title=PR CI scope::$reason; full_ci=$full_ci" diff --git a/.greptile/files.json b/.greptile/files.json index 6794bccb306..ccf54ad800c 100644 --- a/.greptile/files.json +++ b/.greptile/files.json @@ -16,6 +16,17 @@ "path": "rules/product-harness-responsibility.mdc", "description": "Product requirements and review criteria for harness responsibility, minimal user-facing internals, truthful progress, and human decisions." }, + { + "path": "rules/runtime-dependency-boundaries.mdc", + "description": "Dependency cost, responsiveness, and verification requirements for changed server, adapter, and runner execution paths.", + "scope": [ + "server/src/**", + "packages/adapter-utils/src/**", + "packages/adapters/*/src/**", + "packages/paperclip-runner/src/**", + "packages/paperclip-runner/runner/**" + ] + }, { "path": "rules/chat-channel-platform-boundaries.mdc", "description": "Architecture requirements and review criteria for separating chat channel platform implementations from shared orchestration.", diff --git a/cli/src/__tests__/runtime-info.test.ts b/cli/src/__tests__/runtime-info.test.ts index eea58f3a050..9098942ebcd 100644 --- a/cli/src/__tests__/runtime-info.test.ts +++ b/cli/src/__tests__/runtime-info.test.ts @@ -33,12 +33,6 @@ function fixture(): { filePath: string; info: PaperclipRuntimeInfo } { } describe("runtime info", () => { - it("writes and reads the selected runtime endpoint", () => { - const { filePath, info } = fixture(); - writeRuntimeInfo(info, filePath); - expect(readRuntimeInfo("default", filePath)).toEqual(info); - }); - it("does not remove runtime info owned by a replacement process", () => { const { filePath, info } = fixture(); writeRuntimeInfo(info, filePath); diff --git a/cli/src/__tests__/zip-codec.test.ts b/cli/src/__tests__/zip-codec.test.ts deleted file mode 100644 index 03788e88070..00000000000 --- a/cli/src/__tests__/zip-codec.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { bytesToPortableFileEntry, isBlobStorePath, readZipArchive } from "../commands/client/zip.js"; -import { createStoredZipArchive } from "./helpers/zip.js"; - -describe("isBlobStorePath", () => { - it("matches blobs/ entries at the archive root and under a package root", () => { - expect(isBlobStorePath("blobs/4f2d1c9a")).toBe(true); - expect(isBlobStorePath("paperclip-demo/blobs/4f2d1c9a")).toBe(true); - expect(isBlobStorePath("tasks/pap-1/TASK.md")).toBe(false); - expect(isBlobStorePath("blobs/nested/file")).toBe(false); - }); -}); - -describe("bytesToPortableFileEntry", () => { - it("keeps blobs/ entries as base64 octet streams regardless of extension", () => { - const bytes = new Uint8Array([0x00, 0x01, 0x80, 0xfe, 0xff]); - expect(bytesToPortableFileEntry("blobs/4f2d1c9a", bytes)).toEqual({ - encoding: "base64", - data: Buffer.from(bytes).toString("base64"), - contentType: "application/octet-stream", - }); - }); - - it("falls back to base64 when bytes are not valid UTF-8", () => { - const invalidUtf8 = new Uint8Array([0x68, 0x69, 0xff, 0xfe, 0xc0]); - expect(bytesToPortableFileEntry("tasks/pap-1/raw-notes", invalidUtf8)).toEqual({ - encoding: "base64", - data: Buffer.from(invalidUtf8).toString("base64"), - contentType: "application/octet-stream", - }); - }); - - it("decodes valid UTF-8 entries to text", () => { - const bytes = new TextEncoder().encode("# Notes\n\ncafé ✅\n"); - expect(bytesToPortableFileEntry("tasks/pap-1/TASK.md", bytes)).toBe("# Notes\n\ncafé ✅\n"); - }); -}); - -describe("readZipArchive", () => { - it("round-trips blob and invalid UTF-8 entries byte-exactly", async () => { - const blobBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x00, 0xff]); - const invalidUtf8 = new Uint8Array([0x68, 0x69, 0xff, 0xfe, 0xc0]); - const archive = createStoredZipArchive( - { - "COMPANY.md": "# Company\n", - "blobs/4f2d1c9a": blobBytes, - "notes/raw": invalidUtf8, - }, - "paperclip-demo", - ); - - await expect(readZipArchive(archive)).resolves.toEqual({ - rootPath: "paperclip-demo", - files: { - "COMPANY.md": "# Company\n", - "blobs/4f2d1c9a": { - encoding: "base64", - data: Buffer.from(blobBytes).toString("base64"), - contentType: "application/octet-stream", - }, - "notes/raw": { - encoding: "base64", - data: Buffer.from(invalidUtf8).toString("base64"), - contentType: "application/octet-stream", - }, - }, - }); - }); -}); diff --git a/design/pap-14557-monitor-visibility/.paperclip-page/state.json b/design/pap-14557-monitor-visibility/.paperclip-page/state.json deleted file mode 100644 index 1e3344d4d97..00000000000 --- a/design/pap-14557-monitor-visibility/.paperclip-page/state.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bucket": "pages.paperclip.ing", - "prefix": "pap-14557-monitor-visibility/", - "slug": "pap-14557-monitor-visibility", - "url": "https://pages.paperclip.ing/pap-14557-monitor-visibility/", - "baseUrl": "https://pages.paperclip.ing", - "publishedAt": "2026-07-17T15:51:31Z", - "sourceHash": "4d276364eb4e122d782247da6f1f0996763c12175ec0d7efa8ac7a0568fb6f2c", - "version": 1 -} diff --git a/design/pap-14557-monitor-visibility/index.html b/design/pap-14557-monitor-visibility/index.html deleted file mode 100644 index 28963350dd0..00000000000 --- a/design/pap-14557-monitor-visibility/index.html +++ /dev/null @@ -1,420 +0,0 @@ - - - - - -PAP-14557 — Monitor visibility wireframes - - - -
-
- - - PAP-14557 · Monitor visibility
- Jump to a screen -
- -
- -
- -
-
-
PAP-14557 · Wireframes
-

Make a waiting monitor obvious

-

When an issue has a monitor armed, nothing on the page says so unless you squint at a truncated properties row. These wireframes surface the same monitor state in three places — a banner at the top of the task page, a strip directly above the reply composer, and a readable two-line properties row with a hover tooltip for full details. Every surface shows both the exact time and how far away it is from now.

-
- 4 screens + 2 mobile - Desktop + mobile - Lo-fi · monochrome - Click any wireframe to zoom -
-
- -
-
Flow
-

One monitor state, three surfaces

-

The banner, the composer strip, and the properties row are all fed by the same state + copy rules (screen 04), so they can never disagree. Red dashed marks are annotation callouts, not UI.

-
-
flow.svg1280×304
- Flow diagram of the three monitor surfaces -
-
- -
-
Step 1 · Task page top
-

1.Monitor banner under the title

-

A full-width banner sits between the issue title and the description whenever a monitor is scheduled, due, or overdue. Headline leads with the relative time ("resumes in 2h 12m"); the second line gives the exact local time, the attempt count, and what is being watched. "Check now" fires the monitor immediately; "Edit" opens the existing monitor editor.

-
-
-
01-issue-top.svg1280×800 · desktop
- Task page top with monitor banner (desktop) wireframe -
-
-
mobile375×812
- Task page with monitor banner (mobile) wireframe -
-
-
-

Annotations

-
    -
  • 1 — Monitor banner: bold relative time first, exact time + attempt + watched service second, actions on the right. On mobile the actions drop below the text.
  • -
  • 2 — Properties Monitor row switches to the two-line layout (detail on screen 3). On mobile (callouts 2–3 on the phone frame) the collapsed properties bar carries a compact "in 2h 12m" chip and the strip repeats above the composer.
  • -
-
Why this changes: today the only always-visible signal is the truncated properties row; the existing "Monitor scheduled" card renders in the description area where it scrolls away and is easy to miss. The banner is pinned to the page header area so the very first glance answers "why is nothing happening, and when will it?"
-
-
-
-
- -
-
Step 2 · Thread bottom
-

2.Strip above the reply composer

-

The bottom of the thread is where you go to ask "what's happening?" — so the answer sits right there. A compact strip directly above the composer repeats the resume time (exact + relative + attempt) with a "Check now" action, and a one-line hint under the composer explains that replying wakes the agent before the scheduled check.

-
-
-
02-composer.svg1280×800 · desktop
- Monitor strip above composer (desktop) wireframe -
-
-
mobile375×812
- Monitor strip above composer (mobile) wireframe -
-
-
-
Referencecurrent thread bottom
- Current thread bottom with no monitor indication -
-
-

Annotations

-
    -
  • 1 — Monitor strip: anchored to the composer (scrolls with it), not part of the comment list. Same copy system as the banner.
  • -
  • 2 — Wake hint under the composer: sets the expectation that sending a message preempts the scheduled check.
  • -
-
Why this changes: in the reference screenshot the last comment says a monitor is armed only in prose ("issue monitor fires 16:08Z"), and once more comments land even that disappears. The strip keeps the resume time visible at the exact spot where a human decides whether to wait or to nudge.
-
-
-
-
- -
-
Step 3 · Properties pane
-

3.Readable Monitor row + details tooltip

-

The properties row stops cramming label, date, and attempt onto one colliding line. Collapsed: two lines — bold relative time, then muted "absolute time · Attempt N". Hover (tap on mobile) opens a details card with the full timestamp incl. timezone, watched service, notes, last-trigger info, and quick actions. Click still opens the existing editor popover.

-
-
-
03-properties-monitor.svg1280×800 · component sheet
- Properties monitor row redesign wireframe -
-
-
-
Referencecurrent properties row
- Current cramped monitor properties row -
-
-

Annotations

-
    -
  • 1 — Current failure: "Next check" truncates to "Ne…", the date shows the year but no time, and "Attempt 1" wraps into a two-line crumb.
  • -
  • 2 — Proposed collapsed row: relative time is the headline; absolute time and attempt live on a second muted line sized for the 352px pane.
  • -
  • 3 — Hover/tap tooltip: exact timestamp with timezone, watched service, notes, last-trigger state, and Check now / Edit / Clear without opening the editor.
  • -
-
Why this changes: the row currently tries to show four facts in one 200px slot, so none of them are readable. Two lines fit the pane; everything else moves to a tooltip you summon only when you care about specifics.
-
-
-
-
- -
-
Step 4 · System
-

4.State matrix and copy rules

-

All three surfaces share one formatter. Relative time always leads and uses two units under 24h ("in 2h 12m", not "in 2h" — today's single-unit rounding is why the current UI feels vague). Overdue and due-now states get explicit copy instead of silently showing a past date.

-
-
-
04-states.svg1280×800 · component sheet
- Monitor state matrix wireframe -
-
-
-

States covered

-
    -
  • Scheduled — under 1h / under 24h / beyond today.
  • -
  • Retrying — attempt count > 1 gets visual weight.
  • -
  • Due now / overdue — explicit copy, never a stale-looking past date.
  • -
  • Cleared / none — muted, no clock icon, banner and strip hidden.
  • -
-
Why this changes: a single copy system means the banner, strip, and row can never disagree about when the task resumes, and edge states (overdue, cleared) stop looking like bugs.
-
-
-
-
- -
-
For reviewer
-

Open questions

-
-
    -
  • Banner vs. existing card — there is already an "IssueMonitorActivityCard" that renders in the description area. Proposal: promote/replace it with the pinned banner (screen 1) rather than showing both. OK?
  • -
  • Strip stickiness — should the composer strip scroll with the thread (inline, as drawn) or stick to the composer even mid-scroll? Drawn as anchored to the composer.
  • -
  • Relative-time precision — adopt the two-unit format ("in 2h 12m") everywhere, including the existing scheduled-retry row which uses the same formatter?
  • -
  • Hover vs. click — the properties row currently opens the editor on click. Proposal keeps click = editor, hover/tap = read-only details tooltip. Fine, or should tap open the editor directly on mobile?
  • -
  • Live countdown — should the relative time tick live (re-render every minute) or is on-load accuracy enough?
  • -
-
-
- - -
-
- - - - - - diff --git a/design/pap-14557-monitor-visibility/screenshots/current-properties-monitor.png b/design/pap-14557-monitor-visibility/screenshots/current-properties-monitor.png deleted file mode 100644 index f3255e33fec..00000000000 Binary files a/design/pap-14557-monitor-visibility/screenshots/current-properties-monitor.png and /dev/null differ diff --git a/design/pap-14557-monitor-visibility/screenshots/current-thread-bottom.png b/design/pap-14557-monitor-visibility/screenshots/current-thread-bottom.png deleted file mode 100644 index 9e87ca95462..00000000000 Binary files a/design/pap-14557-monitor-visibility/screenshots/current-thread-bottom.png and /dev/null differ diff --git a/design/pap-14557-monitor-visibility/wireframes/01-issue-top-mobile.svg b/design/pap-14557-monitor-visibility/wireframes/01-issue-top-mobile.svg deleted file mode 100644 index aeb3f5f8511..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/01-issue-top-mobile.svg +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - PAP-14557 - - - - Nightly model comparison sweep - - - In Progress - - - - - - - - - Waiting on monitor — in 2h 12m - Thu Jul 17, 4:08 PM · Attempt 1 - - - Check now - - - - - - - - - - - - - - - Senior Engineer Fable - 15m ago - - - - - - - - - Properties - - - - in 2h 12m - - - - - - - - - - Resumes in 2h 12m - Jul 17, 4:08 PM · Attempt 1 - Check now - - - - - - Reply… - - - - - - - Send - - - - - - - - - 1 - - - - - 2 - - - - - 3 - - - diff --git a/design/pap-14557-monitor-visibility/wireframes/01-issue-top.svg b/design/pap-14557-monitor-visibility/wireframes/01-issue-top.svg deleted file mode 100644 index cbf09bab1ed..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/01-issue-top.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - Issues / - PAP-14557 - - - - - - - Nightly model comparison sweep - - - In Progress - - - - - - - - - - Waiting on monitor — resumes in 2h 12m - Thu Jul 17, 4:08 PM (your time) · Attempt 1 · Watching: vercel-deploy - - - Check now - - - - Edit - - - - - - - - - - - - Activity - - - - - - Senior Engineer Fable - 15m ago - - - - - - ⌄ thread continues — see screen 02 for the composer - - - PROPERTIES - Status - In Progress - Priority - Medium - Assignee - - Fable - Approvers - None - - - Monitor - - - - In 2h 12m - Jul 17, 4:08 PM · Attempt 1 - - Watchdog - None - - - - - - - 1 - - - - - 2 - - - diff --git a/design/pap-14557-monitor-visibility/wireframes/02-composer-mobile.svg b/design/pap-14557-monitor-visibility/wireframes/02-composer-mobile.svg deleted file mode 100644 index 895d8be5260..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/02-composer-mobile.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - PAP-14557 - - - ⌃ earlier comments - - - - Senior Engineer Fable - 15m ago - - - - Next action (armed): - - - - - - - - Previous · PAP-14544 - Phase 3 — rescore… - - - - - - - - - Resumes in 2h 12m - Thu Jul 17, 4:08 PM · Attempt 1 - - - Check now - - - - - - - Reply… - - - - - - - Agent mode - - - - - Send - - - - - - - - - 1 - - - diff --git a/design/pap-14557-monitor-visibility/wireframes/02-composer.svg b/design/pap-14557-monitor-visibility/wireframes/02-composer.svg deleted file mode 100644 index cc417d0f538..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/02-composer.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - Issues / - PAP-14557 - - - - - PROPERTIES - Status - In Progress - Monitor - - - - In 2h 12m - Jul 17, 4:08 PM · Attempt 1 - - - ⌃ earlier comments - - - - Senior Engineer Fable - 15m ago - - - - Next action (armed): - - - - - - - - - Previous · PAP-14544 - Phase 3 — rescore + gap re-runs… - - - - - - - - - Resumes in 2h 12m - Thu Jul 17, 4:08 PM · Attempt 1 · Watching: vercel-deploy - - - Check now - - - - - - - Reply… - - - - - - - Agent mode - - - - - Senior Engineer Fable - - - - Send - - - - - Sending a reply wakes the agent now — before the scheduled check. - - - - - - - 1 - - - - - 2 - - - diff --git a/design/pap-14557-monitor-visibility/wireframes/03-properties-monitor.svg b/design/pap-14557-monitor-visibility/wireframes/03-properties-monitor.svg deleted file mode 100644 index bb39e5f302a..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/03-properties-monitor.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Properties pane — Monitor row redesign - Side pane fragment, 352px wide. The same row is reused in the mobile properties sheet. - - - A · Current — cramped, truncated - - - Approvers - None - - Monitor - - - - Ne… - Jul 17, 2026 - Attempt - 1 - - Watchdog - None - - Label truncates to “Ne…”, date is ambiguous (which year matters, - not the time?), attempt badge wraps into two lines. - - - B · Proposed — two-line row - - - Approvers - None - - Monitor - - - - In 2h 12m - Jul 17, 4:08 PM · Attempt 1 - - Watchdog - None - - Relative time leads and is bold; absolute time + attempt get their - own muted line. Nothing truncates at 352px. Row grows to 2 lines. - - - C · Hover / tap — full details - - - - Monitor - - - - In 2h 12m - Jul 17, 4:08 PM · Attempt 1 - - - - - - Monitor - Attempt 1 - - - Next check - Thu, Jul 17, 2026 · 4:08 PM PDT - in 2h 12m - - Watching - vercel-deploy - - Notes - Verify completion, commit ledger / - scores, extend comparison site. - - Last triggered - — not yet triggered - - - - - Check now - - - - Edit - - - - Clear - - - - Hover on desktop, tap on mobile. Exact timestamp with timezone, - service, notes and quick actions — no digging into the edit popover. - - - - - - - 1 - - - - - 2 - - - - - 3 - - - diff --git a/design/pap-14557-monitor-visibility/wireframes/04-states.svg b/design/pap-14557-monitor-visibility/wireframes/04-states.svg deleted file mode 100644 index 105dbc4b840..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/04-states.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Monitor row — state matrix - One copy system across banner, composer strip and properties row. Relative time always leads. - - - - - SCHEDULED · UNDER 1 HOUR - Monitor - - - - In 24m - Today, 2:20 PM - - - - - - SCHEDULED · UNDER 24 HOURS - Monitor - - - - In 2h 12m - Today, 4:08 PM - - - - - - SCHEDULED · BEYOND TODAY - Monitor - - - - In 3d - Mon Jul 20, 9:00 AM - - - - - - RETRYING · ATTEMPT > 1 - Monitor - - - - In 2h 12m - Today, 4:08 PM · - Attempt 3 - - - - - - DUE NOW - Monitor - - - - Due now - checking momentarily… - - - - - - OVERDUE - Monitor - - - - Overdue by 18m - Jul 17, 1:38 PM · fires on next tick - - - - - - CLEARED - Monitor - Cleared - 2h ago · after attempt 2 - - - - - - NONE - Monitor - None - - - - Copy rules - < 1h → “In 24m” · < 24h → “In 2h 12m” (two units, not “in 2h”) · beyond → “In 3d” + weekday date · past due → “Overdue by …”. - Absolute time says “Today” when it is today; adds the year only when it is not the current year. Banner + strip render only in scheduled / due / overdue states. - diff --git a/design/pap-14557-monitor-visibility/wireframes/flow.svg b/design/pap-14557-monitor-visibility/wireframes/flow.svg deleted file mode 100644 index 3e6d9403c21..00000000000 --- a/design/pap-14557-monitor-visibility/wireframes/flow.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - One monitor state, three surfaces — banner (top), strip (composer), row (properties) — all fed by the same copy rules (04). - - - - - - - - - - - - - 01 · Task page top — banner - - - - - - - - - - - - - - - 02 · Above the composer — strip - - - - - - - - - - - - - - - - - - 03 · Properties row + tooltip - - - - - - - - - - - - - 04 · State matrix + copy rules - diff --git a/doc/DATABASE.md b/doc/DATABASE.md index 4ae992bc87e..d684b1c7e13 100644 --- a/doc/DATABASE.md +++ b/doc/DATABASE.md @@ -36,6 +36,13 @@ secret redaction still run on the bounded result set. Agent detail links fetch an older selected run by ID when it is outside the history page, and verify that it belongs to the current company and agent. +Saved-run policy and parent-report lookups read only the `executionPolicy` +value or the legacy `issueId` and `taskId` values from a run context. PostgreSQL projects +those JSON values before it sends the row to the controller. Unused prompt and +launch context does not travel with each policy or parent-report check. JSON +types, task-key precedence, and company and agent identity checks are preserved. +Each decision still reads current data; this is not an authorization cache. + To compare the real summary and full history queries with an earlier revision: ```sh @@ -45,6 +52,30 @@ pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-run-history.ts - The fixture has 1,400 synthetic runs with 192 KiB contexts. Both queries return the same 200 complete rows. This benchmark excludes HTTP and redaction latency. +Workspace reads also compare retained task IDs as guarded, canonical UUIDs. +This lets PostgreSQL check each source task by primary key before it evaluates +task privacy. Casting the indexed task ID to text can instead evaluate privacy +across company history for each workspace. Repository admission repeats these +reads while holding its company provenance lock, so that plan can delay other +agent launches. Missing, malformed, noncanonical, and foreign-company source +IDs still deny access. + +The subsequent source-task enumeration also keeps `issues.id` as a UUID. Its +OR predicate combines the workspace binding index with the task primary key. +Casting that ID to text forces another scan of company history even after the +workspace privacy query has passed. The workspace authorization regression +suite checks both plans and verifies the attached and retained source results. + +To compare this query on a disposable fixture with 13,429 tasks: + +```sh +pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-workspace-authorization.ts --baseline-ref=5c7a025032 +``` + +The benchmark loads the baseline authorization module with current dependencies, +compares complete workspace rows, and prints five warm samples and query plans. +It measures SQL execution, not the full repository admission or agent startup. + ## 1. Embedded PostgreSQL — zero config If you don't set `DATABASE_URL`, the server automatically starts an embedded PostgreSQL instance and manages a local data directory. @@ -851,6 +882,8 @@ Migration [0337_exotic_proemial_gods.sql](../packages/db/src/migrations/0337_exo Migration [0338_absurd_vengeance.sql](../packages/db/src/migrations/0338_absurd_vengeance.sql) adds internal `execution_workspace_repository_access` provenance. Company/canonical-root source entries retain execution-workspace and source-project identities; reader entries retain the exact admitted run, task, agent and responsible user. New repository receipts also record their root at creation; legacy receipts receive no inferred origin. Shared project-source entries retain which physical owners require workspace write authority. Current admission, selection, requests and publication check the current task’s writer against those owners; retained past readers and file APIs still require only read authority. Genuine isolated copies retain source read access without requiring shared-folder write access. An existing linked worktree can recover a missing isolated-copy proof only after Git registration and its captured source owner are verified. Configured local sibling copies retain deny-only task/project source ownership on the bound task root before materialization; ordinary repository clones create no isolation proof. The shared root fence checks every reader against every source before admission and future publication, including tasks without repository requests. File APIs apply the same source privacy without writing provenance. These entries grant no authority and have no workspace or run deletion cascade; unavailable retained identity fails closed. Automatic provenance release is deferred, so a newly incompatible permission scope requires a separate physical root. +Repository admission loads retained workspace and task sources in bounded batches. Readers share source rows and privacy evaluation for identical principals, but each retained run receives its own trust and responsible-user decision. The snapshot lasts only for one read batch; later admission and publication reload current authority. Keep source-loading query counts bounded as both workspace history and retained reader counts grow. + Git-source capture checks the actual common Git storage directory and verified checkout owners. Separately stored metadata does not make its parent a checkout. When a linked worktree cannot identify its primary checkout, the service verifies diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 1f73f433b9d..98995749210 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -49,6 +49,15 @@ workflow reads GitHub metadata without checking out or executing PR code. Its au repository, head, and merge validation still decide whether tests can use AWS. Unapproved test workloads continue to use GitHub-hosted runners. +The routing workflow also selects the CI scope. Middle PRs in a stack and PRs +whose diff changes only release notes (`releases/v*.md` or +`releases/beta/v*.md`) skip the build, test, and e2e lanes. The `policy` job +and the `verify` and `e2e` aggregate checks still run. The release-notes check +reads the diff between the event's base and head commits. If that lookup +fails, or the diff is empty or may be truncated, the PR runs full CI. A +notes-only PR stacked above other PRs also runs full CI, because its own diff +does not include the lower PRs' code. + The routing Fleet has two warm On-Demand runners and a four-runner ceiling. Test fleets cannot consume its runner slots. Hosted test queues therefore do not block AWS runner selection. A manual dispatch of `ci-routing.yml` on master @@ -670,6 +679,18 @@ Activity-only reorderings wait for one second without further activity changes; new and removed tasks appear immediately. Titles, status, and live indicators stay current during that delay. +### Local Linux runner qualification + +Before serving requests, the server reads `getconf GNU_LIBC_VERSION` once with +an asynchronous subprocess, a one-second timeout, and a 1 KiB output limit. +Runner discovery reuses those immutable platform facts. This check does not +generate a Node diagnostic report or resolve socket hostnames. + +If `getconf` is missing, fails, times out, or returns an unrecognized version, +the local libc remains unknown and automatic GNU/Linux runner selection stays +disabled. Qualified Linux targets still require GNU libc 2.39 or newer. Existing +saved execution choices remain unchanged. + ### OpenCode native runtime Paperclip Runner resolves the pinned OpenCode platform package installed with @@ -1074,6 +1095,20 @@ Workspace preparation resolves an existing root symlink before reading the snaps Execution-workspace close-readiness and branch-reconciliation status checks allow up to 32 MiB of buffered output. This accommodates nested task worktrees while preserving exact untracked-file counts. Larger output fails the scan and blocks destructive cleanup. Other buffered scan bounds stay unchanged. +Heartbeat configuration, environment selection, and finalization read persisted +workspace records through the company-scoped `getStoredById` service method. +Those reads do not inspect Git, refresh runtime service health, or resolve PR +delivery. Workspace detail responses still load live display state, and the +execution and cleanup boundaries retain their live branch and cleanliness +guards. Close readiness derives ancestry from its existing ahead/behind +comparison, avoiding a second history walk with `merge-base`. + +Measure the avoided reads on synthetic workspaces with +`pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-workspace-record-reads.ts`. +The benchmark checks identical persisted fields and reports wall time and actual +Git command counts. Use `--resident-mib=1536` to exercise a large Node process on +Linux. It measures workspace reads, not end-to-end agent latency. + The cache intentionally trades up to a few seconds of changed-file freshness for stable server latency. The file browser retains an explicit refresh action, does not start its query while the panel or browser tab is hidden, and presents overloads as retryable failures rather than an empty workspace. A full queue returns `503` with code `workspace_git_scan_saturated`; a scan exceeding its wall-clock limit returns `504` with code `workspace_git_scan_timeout`. Both responses include `Retry-After: 1`. Sandbox Git sync treats only the selected repository root as a clone source. A selected subfolder uses directory sync within that folder, applies the enclosing repository's ignore rules, and does not transfer parent files or Git history. @@ -1426,6 +1461,10 @@ as they are available, so later setup failures still clean up acquired resources Busy-account deferral returns explicitly before session/workspace preparation. Public-path tests are in `server/src/__tests__/heartbeat-execution-configuration.test.ts`. +Browser Use skill assignment first checks for an enabled, active Browser Use +connection in the run's company. With none, it skips full tool discovery. +With one, the existing task, tool-policy and credential-grant checks still apply. + Session preparation is in `server/src/services/heartbeat/session-preparation.ts`. It selects fresh or resumable sessions from effective config and managed-account identity, then resolves adapter session identity and compaction after workspace @@ -1813,6 +1852,21 @@ that classification finishes. waits for the previous executor to finish releasing its task resources. - A verified dead runner starts a replacement from the same durable root and resumes the same provider checkpoint. Only the operating-system PID changes. +- A prepared remote workspace interrupted before the executor's first claim + can continue on the same run without a provider checkpoint. Under the recovery + locks, Paperclip requires attempt and controller generation zero, no prior + controller or lease owner, and no session, process, launch, or provider evidence. + Any existing durable runner root must still prove an untouched bootstrap before + execution starts. A prior claim or contradictory evidence retains the hold. + A server-owned preparation protocol marker is required. It is recorded only + when fresh native identities are created by a server with the ownership fence. + Older unmarked preparations remain held during rolling upgrades; revisiting an + existing run never upgrades that marker from caller-supplied profile data. +- If the original preparation resumes after another execution claimed the run, + it yields without failing the shared run or cleaning up the new owner. An + expired foreign lease still requires normal recovery before takeover. + Recovery leaves this server's active preparation with its current execution. + A detached execution also preserves the current owner's run-scoped gateway tokens. - A runner that died before its first authenticated connection can restart on the same run only when its durable root proves that no provider authority or checkpoint exists. Paperclip quarantines the incomplete root first. @@ -1884,6 +1938,10 @@ automatically alongside it; see [Agent commentary](agent-commentary.md). Other runtime skills remain controlled by `paperclipSkillSync.desiredSkills`. The native `paperclip_runner` does not use this legacy default because its protocol supplies the control-plane contract. +An empty native runtime selection returns before company skill inventory work. +An explicit empty stored key set does the same. Nonempty selections still use +the current inventory, version and access checks. Legacy automatic selections +still resolve their operational and feedback skills. Validate the catalog without writing the manifest: @@ -2031,9 +2089,16 @@ Environment overrides: - `PAPERCLIP_WORKSPACE_REAPER_COOLDOWN_DAYS=` sets how long the terminal-workspace reaper waits after an issue tree becomes terminal before it archives the execution workspace and deletes the worktree. A person can reopen - the work inside this window. The default is `7`. A value of `0` disables the + the work inside this window. The default is `1`. A value of `0` disables the cooldown and restores immediate reaping. A negative or non-numeric value falls back to the default. +- `PAPERCLIP_WORKSPACE_COMPACTION_DELAY_HOURS=` sets how long the reaper + waits after an issue tree becomes terminal before it removes regenerable files + from the workspace (see "Terminal workspace compaction" below). The default is + `1`. A value of `0` compacts on the next sweep. A negative, empty, or + non-numeric value falls back to the default. +- `PAPERCLIP_WORKSPACE_COMPACTION_ENABLED=false` turns compaction off. Any other + value keeps it on. Without `PAPERCLIP_DB_BACKUP_ALERT_FILE`, health checks look for `db-backup-to-s3.failure` in the backup directory, beside the backup directory, @@ -2343,6 +2408,57 @@ fields before disabling a connection. Verify workspace ownership, active runs, Git state, and runtime-service readiness before closing a workspace. A missing URL or old workspace timestamp alone does not prove that a row is disposable. +### Terminal workspace compaction + +A finished task often keeps its worktree for a long time: the reaper archives a +worktree only when its delivery is merged and its files are clean, and then only +after the cooldown. Most of a worktree's size is installed dependencies and build +output, not work. Compaction removes those files and keeps everything else, so a +finished task does not hold gigabytes while it waits for the reaper (or forever, +when it has no merged pull request or has leftover files). + +The reaper compacts a workspace once per terminal cycle, after +`PAPERCLIP_WORKSPACE_COMPACTION_DELAY_HOURS`. Server startup waits for the +reaper's first sweep, so that sweep never compacts; later sweeps do. A workspace +qualifies when all of these hold: + +- the workspace is an isolated linked Git worktree (never a shared workspace or a + project's main checkout); +- every issue in its tree is done or cancelled; +- no queued or running heartbeat run works on the tree or the workspace; +- no runtime service runs from the workspace. + +Compaction removes only directories that have a regenerable name +(`node_modules`, `dist`, `build`, `coverage`, `storybook-static`, `target`, +`.cache`, `.next`, `.nuxt`, `.parcel-cache`, `.svelte-kit`, `.turbo`, `.vite`), +that the repository's own `.gitignore` files ignore (a negated pattern, a global +excludes file, or `.git/info/exclude` does not count), and that hold no tracked +file. A directory with such a name +that does not qualify (for example a tracked `build/`) is searched in turn, so +ignored dependencies inside it still go. It never enters `.git` or +`.paperclip`, and it never follows a symlink. It checks each directory's ignore +status on its own, so dependencies below an ignored parent (for example +`vendor/tool/node_modules`) are found. Its Git reads have a deadline and stop +when the sweep stops. Tracked files, uncommitted changes, +untracked files, other ignored files (such as `.env` or local data), the branch, +and its commits all stay. The workspace row stays active. + +The removal is a rename into `.paperclip-compaction-trash` beside the worktree, +made while the reaper holds the workspace lifecycle lock. The reaper checks the +conditions above before the rename and again after it, in the same transaction. +If the task reopened or a run started in between, the directories move back. +Otherwise the trash is deleted. Trash that an interrupted compaction or a failed +delete leaves behind is deleted by a later sweep once it is more than an hour +old. The sweep checks each worktree parent directory at most every 30 minutes, +whether or not anything is left to compact. + +A compacted worktree is in the same state as a freshly created one, so when the +task resumes, whatever produced those files the first time produces them again: +reuse runs the workspace provision command (or `scripts/provision-worktree.sh`), +which reinstalls dependencies when `node_modules` is missing. Repositories without +a provision command rely on the agent's own install step, as they do for a new +worktree. + ### Browser realtime connection recovery If a browser cannot construct a WebSocket, the live-update and run transcript diff --git a/doc/MCP-RUNTIME-OPERATIONS.md b/doc/MCP-RUNTIME-OPERATIONS.md index 9a05b0157a6..011c5c6fbbc 100644 --- a/doc/MCP-RUNTIME-OPERATIONS.md +++ b/doc/MCP-RUNTIME-OPERATIONS.md @@ -36,6 +36,18 @@ delivery path. Managed gateway discovery excludes historical native assignments. Their stored rows remain available for existing runs. Explicit company gateways continue to use their configured scopes. +## Client-side tool-call deadline + +The gateway bounds each provider call, but the agent's MCP client also needs a +bound. Without one, a gateway request that never answers holds the agent turn +until the adapter execution timeout ends the run. Claude's default MCP tool +timeout is far longer than a run, and ACP `mcpServers` entries have no +per-server timeout field. So when an ACP Claude run receives runtime MCP +servers, the ACPX engine sets `MCP_TOOL_TIMEOUT` to 15 minutes. A call that +passes this deadline returns an MCP error to the model, and the turn +continues. An `MCP_TOOL_TIMEOUT` value in the adapter `env` configuration +takes priority. + ## Metrics The board runtime health API summarizes one-hour event windows plus current durable slot state: diff --git a/doc/PRODUCT.md b/doc/PRODUCT.md index 0ed960f9926..4a5277ddfe4 100644 --- a/doc/PRODUCT.md +++ b/doc/PRODUCT.md @@ -111,10 +111,12 @@ default branch marked. People can also enter a branch name that is not listed. Saving checks GitHub access and the selected ref before changing tracking; failed saves keep the selection and installed content. Project refs remain owned by project configuration so changing a source cannot redirect another project's tracking. -If a tracked repository grows beyond the supported package size, its installed -repository skills and shared dependencies remain available until a safe refresh -can succeed. Initial oversized imports can use self-contained skill packages; -packages with omitted dependencies are invalid and are not installed. +If a repository exceeds the supported package size, Paperclip first packages its +skill folders and referenced repository files, preserving their original paths. +Missing references are advisory: a valid selected skill can still be imported. +Unavailable files are listed, and existing installed dependencies are retained +when a bounded refresh cannot preserve them. File limits and content validation +still apply; importing never executes repository code. Project-derived skills inherit current project access. Access through any originating project is sufficient, including the responsible-user restrictions on agent execution. diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 48f91825806..658e1859a17 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -1355,6 +1355,8 @@ The current app also exposes V1-supporting surfaces for: derive `deliveryState` as `merged_via_pr | merged_by_ancestry | unmerged | unknown`; terminal issue trees with a merged delivery and no active checkout run become cleanup-eligible with reason `issue_terminal` and are archived through the workspace cleanup path. Reopening the source issue records activity but does not restore that workspace. + Before that, terminal isolated worktrees are compacted: ignored, untracked dependency, build, and cache directories + are removed while the workspace, its branch, and all source changes stay, and reuse regenerates them. - task watchdog configuration and reusable watchdog issue orchestration for explicitly watched issue subtrees - routines and scheduled/API/webhook triggers - plugin installation, configuration, state, jobs, logs, webhooks, and plugin database namespace migration @@ -2108,11 +2110,15 @@ unavailable. Preserve current ownership and newer-work fences. See be paused independently of selection. Project sources select future additions unless excluded; existing manually imported sources retain their selections. Legacy projects adopt through an explicit company-scoped, project-authorized endpoint, never a GET. -- Repository size fallback must not discard installed shared dependencies or - publish packages with detected omitted dependencies. Retain affected versions - and report the size problem. Self-contained packages can still update through - fallback. Project sources retry complete repository packaging and return to it - when supported; selections and exclusions remain unchanged. +- Repository size fallback first retains skill folders and transitively referenced + repository files within the existing file/byte limits, preserving original paths. + If that set is still too large, bounded skill folders remain importable. Missing + references are advisory and do not invalidate otherwise valid selected skills. + Web routes and inline Markdown examples are not package dependencies. Preserve + installed versions if fallback would omit their previously installed shared files. + Project sources retry complete packaging; selections and exclusions remain unchanged. + Refresh reinspects cached dependency-rejection errors once even at an unchanged + upstream commit. Content validation and company/project access remain enforced. - Unattended refresh stores setup identity and connection/grant identifiers, never token values. Current membership, connection authorization and skill policy are revalidated. Unrecoverable legacy setup identities require explicit connection repair. Retries are diff --git a/doc/SPEC.md b/doc/SPEC.md index c56c8fd8f47..302ce8c641a 100644 --- a/doc/SPEC.md +++ b/doc/SPEC.md @@ -722,6 +722,9 @@ the runtime. A declarative manifest can suggest public entrypoints and setup req new imports keep repository files together and select all discovered skills. Missing references, setup problems and omitted files produce persistent warnings. Files rejected for unsafe execution or secret disclosure are excluded without blocking other files. +Oversized repositories use a bounded package of skill folders and referenced files +with their original paths. Missing references do not block a valid selected skill; +fallback preserves previously installed shared dependencies or retains the old version. The installed file viewer exposes saved repository dependencies. Importing never runs setup or configures host tools. New upstream skills require reviewed selection; removed or deselected skills remain diff --git a/doc/dependency-wake-recovery.md b/doc/dependency-wake-recovery.md new file mode 100644 index 00000000000..0187b99c6f3 --- /dev/null +++ b/doc/dependency-wake-recovery.md @@ -0,0 +1,19 @@ +# Dependency wakes and deliberate stops + +A task can remain blocked after all recorded dependencies finish. Its assignee +may be waiting for an external action. Blocking its own task does not request +another dependency wake. The periodic recovery sweep preserves this decision, +including native status decisions attributed to the assignee's bound run. + +Title, monitor, and other unrelated edits do not change this wait. A later +assignment, status, or blocker-set update permits reconciliation again. A blocker +that completes or finishes workspace export after the wait also permits a wake. +The export must name the blocker issue. An export from the waiting task or an +unattributed operation in the same shared workspace does not remove its wait. +Normal readiness, duplicate-wake, interaction, pause, and execution gates still +apply. A board transition into blocked keeps the existing recovery behavior. + +Cancelling a run or pausing its agent suppresses immediate stranded-work recovery. +A queued user message remains a separate continuation request. The release path +still preserves independently authorized deferred input; stopping the old run +does not discard that input or grant permission to replay its old tools. diff --git a/doc/evals.md b/doc/evals.md index cad0bd73769..35623eb9e16 100644 --- a/doc/evals.md +++ b/doc/evals.md @@ -157,6 +157,11 @@ permission, and requester scope decisions through saved browser interactions. ## Validation ladder +The native Codex Daytona `recover-controller` cell in Everyday Workflows +requires active process and lease continuity, native receipt continuity and +workspace finalization. See the [restart evidence contract](../tests/runner-e2e/EVERYDAY-WORKFLOWS.md). +Catalog discovery and deterministic calibration do not establish live recovery. + The explicit-only [public MCP suite](../tests/runner-e2e/PUBLIC-MCP.md) evaluates paid assistant delegation, later retrieval, feedback, review, uncertain retries and permission boundaries. It uses the Product E2E fixtures, launcher, evidence diff --git a/doc/execution-semantics.md b/doc/execution-semantics.md index cc05e150bfc..fe9ee0c9615 100644 --- a/doc/execution-semantics.md +++ b/doc/execution-semantics.md @@ -212,6 +212,13 @@ The active-lock lifecycle is part of the checkout contract: Stale-lock recovery is crash recovery, not a retry loop. Paperclip must not clear or adopt locks held by non-terminal runs. After stale cleanup, a checkout `409` should mean a real live owner, status/assignee mismatch, unresolved blocker, or active gate still prevents checkout. Agents must treat that `409` as an ownership conflict and stop rather than retrying the same checkout. +Stopping a queued legacy run before runtime selection clears only its own task +locks. It preserves deferred messages without promoting another run. The stop +must have a recorded, expected operator cancellation and no runtime selection, +start timestamp, or process identity. A later eligible instruction can adopt the +saved messages through normal admission. This rule does not prove a dispatched +provider has stopped or change native cancellation acknowledgement. + ### Known execution waits at admission A known execution hold is a waiting condition, not a new execution attempt. Every issue wake must read the current effective reconciliation hold under the issue admission lock before creating a run. Resolved recovery bookkeeping can still carry a no-replay hold; only clearing the effective hold makes admission eligible again. The final dispatch gate remains required for changes after admission. @@ -528,6 +535,15 @@ Warm sandbox reuse must match the current host Git tip and branch as well as the file snapshot and saved stamp, including managed nested repositories. A history or branch mismatch restages the host before the next run begins. +Disk-backed native sandbox workspace snapshots run in at most two worker threads per +controller process. Each capture retains the same file hashes, modes, symlinks, +exclusions and ignored-path manifest as an inline capture. A worker seals its +private manifest before handing it to the controller; ordinary snapshot disposal +removes that worker's temporary directory. A failed capture removes its temporary +state before releasing its worker slot. This scheduling boundary does not replace +source authorization, warm-stamp validation, Git identity checks or restore +conflict detection. Legacy adapter snapshots remain inline. + ### Remote Codex model compatibility Fresh remote Codex Runner runs check the selected image CLI before saving the @@ -841,6 +857,15 @@ On startup and on the periodic recovery loop, Paperclip performs the following r The stranded-work pass closes the gap where issue state survives a crash but the wake/run path does not. The silent-run scan covers the separate case where a live process exists but has stopped producing observable output. +Startup health (`/api/health`) reports `starting` until these recovery passes and +the other startup steps that serving requests depends on finish. Housekeeping +that every scheduler tick repeats does not delay it: the custom-image setup +cleanup, tool connection health, decision expiry, adapter and setup-token login +reapers, environment lease cleanup, and the retention sweep run after readiness. +A failed housekeeping step is logged as `startup housekeeping step failed` and +the next step still runs. The heartbeat scheduler interval starts after +housekeeping, so the reapers still finish before the first timer tick. + The stale-lock sweep leaves native runs with their finalization coordinator while same-session recovery or accepted-result finalization is pending. A provider can exit before workspace copy-back, assessment, or arbitration completes. An expired @@ -1124,6 +1149,25 @@ recording the proof and successor lineage. Retained provider files are not edite Bootstrap retries, exact-checkpoint resumes, and fresh replacement sessions share three total provider attempts, including the original attempt. Linked run IDs, controller restarts, and duplicate wakes do not reset this budget. Automatic attempts retain the 30-second delay. Replacement scheduling and predecessor lineage commit together, with one successor per predecessor and admission through the normal task locks, authorization, pause, approval, and budget gates. +A prepared remote workspace interrupted before the executor's first durable +claim may resume its untouched bootstrap on the same run. This requires attempt +and controller generation zero, no prior owner, and no process, provider session, +checkpoint, or launch evidence. The executor still verifies any durable runner +root before starting. Missing evidence after a prior claim does not qualify. +Classification preserves the provider attempt count; execution consumes the +first attempt through the normal claim and admission gates. + +This path also requires a server-owned preparation fencing marker created with +fresh native run identities. Unmarked preparations from older servers remain +held during rolling upgrades, because those servers cannot safely yield to a +recovery owner. Revisiting an existing preparation cannot add that marker. + +A preparation that resumes after another execution acquired the coordinator +lease yields through the controller-detachment path. It does not fail the shared +run, release its environment, or start another provider. Lease expiry alone does +not authorize that stale preparation to take ownership; normal recovery must +establish a new claim first. + An exact local Codex restart can restore the conversation after losing its active turn. Runnerd records this as `provider_turn_lost_on_restore`, with no invented task result. The admitted restart attempt may send one continuation in that same @@ -1265,6 +1309,13 @@ Examples: The recovery action stays source-scoped by default. Stranded-task escalation is board-owned and records the cause, evidence, next action, source and return owner, `routingPolicy: board_escalation_no_takeover_v1`, and wake or monitor policy in the source thread/detail surface. +The recovery-actions read API also lists settled records whose automatic recovery +receipt still blocks replay. A resolved recovery record does not by itself prove +that work resumed. The task's stopped-run notice lets authorized board users +inspect the matching decision under Recovery details, even when there is no +active recovery action. Existing run inspection, verified continuation, new +messages, and required-file repair remain the supported recovery paths. + The board owns the recovery decision, not the source deliverable. Automatic recovery must preserve both source assignee fields. Only an explicit operator decision or applicable serious-failure policy may transfer the deliverable. An upgrade may encounter an already-active agent-owned recovery action. Paperclip keeps that record readable and resolvable for compatibility, but periodic reconciliation does not enqueue another takeover wake from it. diff --git a/doc/experimental/issue-worktree-support.md b/doc/experimental/issue-worktree-support.md index e52e620b6ce..19614a3c36f 100644 --- a/doc/experimental/issue-worktree-support.md +++ b/doc/experimental/issue-worktree-support.md @@ -33,8 +33,10 @@ Explicit project policies and issue workspace settings still take precedence. A Projects and individual issues can set `sharedWorkspaceConcurrency` in their execution workspace policy/settings: - `auto` (the default when absent): allow concurrent shared-workspace runs on `local` and `ssh` environments, and serialize runs on `sandbox` and `plugin` environments. An instance forced to Kubernetes always serializes in `auto` mode. -- `serialize`: on `sandbox` and `plugin` environments, defer a run while another live run holds the same project workspace, using the `workspace_busy` retry path. Local and SSH folders remain concurrent, even when an existing project or issue policy requests serialization. -- `allow`: dispatch alongside a live holder on every environment. +- `serialize`: on `sandbox` and `plugin` environments, defer a run while another live run holds the same project workspace or the same reused shared execution workspace, using the `workspace_busy` retry path. Local and SSH folders remain concurrent, even when an existing project or issue policy requests serialization. +- `allow`: dispatch alongside a live holder on `local` and `ssh` environments. Other environments cannot place concurrent agents in one physical shared folder, so a run with a live holder fails with `configuration_incomplete` (`shared_realization_unsupported`) instead of waiting. + +A reused shared execution workspace follows the same rules. For example, a child task can inherit the shared execution workspace of a parent whose run is still live. In `auto` or `serialize` mode on a sandbox, the child waits for the parent run to finish and then continues automatically. A parent run that is still running but has produced no output for more than an hour still holds the folder. In that case the child fails with `configuration_incomplete` (`shared_realization_unsupported`). It does not share the folder with the parent, and it does not wait without limit. Issue settings override the project policy, which overrides the default `auto`. The final execution environment determines whether workspace serialization applies; an instance forced to Kubernetes is a sandbox even if the agent normally runs locally. Local and SSH folder runs never wait for exclusive workspace ownership. Task checkout and per-agent concurrency limits still apply. When concurrency is allowed and a live holder exists, Paperclip adds the holder run and issue to the dispatched task context so agents can coordinate concurrent mutations through commits. The setting is optional JSON policy data, so existing databases require no migration. diff --git a/doc/grok-bot-agent.md b/doc/grok-bot-agent.md new file mode 100644 index 00000000000..952fbace5a0 --- /dev/null +++ b/doc/grok-bot-agent.md @@ -0,0 +1,79 @@ +# Grok Bot agents (experimental) + +Grok Bot is an externally managed Paperclip agent. It uses Grok Bot's existing +apps and persistent context. It is separate from the Grok Build coding runtime. +Paperclip coordinates tasks through the managed Runner. Grok usage and spending +are not reported. + +From **Agents → New agent → Invite external agent → Grok Bot**, name the agent +and select its role and manager. Follow the connection instructions in Grok Bot. +Approve the fixed Paperclip company and agent in the browser. Copy the one-use +pairing code with **Copy code**; enter it only on the authorization page. +Grok plugins are account-wide, so connecting a Bot does not isolate credentials +between Bots on that Grok account. + +Copy the routine instructions into the Bot's chat. Open its desktop routine's +Webhook panel and paste the URL and key into Paperclip's secure configuration. +Use a webhook-only routine with no schedule. **Verify connection** must show both +assignment delivery and an authenticated reply before **Done** becomes available. +Use **Save & exit** to preserve unfinished setup. See the official +[routine instructions](https://cursor.com/help/grok-bot/routines). + +Manage the Bot from its agent's **Runtime** settings. This shows connection +health, routine configuration, and verification. Model selection, local paths, +and runtime tests are managed outside Paperclip; use **Verify connection** to +check both directions. Use **Reconnect agent access** to restore the same agent's +connection. Select **Copy code** beside the masked one-use pairing code, then +paste it into Paperclip's authorization page. Keep it out of Bot chat. + +Assign ordinary tasks to the agent. Read its progress, answer its question cards, +and review its documents and files in the task thread. The Bot can initiate a +new task from its Grok conversation through the admitted work-session tools. +An immediate proactive claim does not send the same assignment back through the +routine. + +## Files and completion + +Use `paperclip_grok_get_upload_url` for an accepted current assignment. Compute +the exact file's SHA-256 and size, then PUT those bytes with Grok's own HTTP/file +tools. The successful response includes the attachment and its verified +`publication.evidenceRef`. Cite that reference in the completion report. No +Paperclip filesystem is exposed, and no separate upload completion call is needed. +Repeat the same request ID after a lost response. Concurrent or repeated uploads +retain one attachment and one publication receipt. Person MCP uploads remain +person uploads; they do not gain agent execution or publication authority. + +The upload checks current grant, company, agent, task, work session and work mode. +Publication also verifies stored filename, byte size and SHA-256. Normal Runner +completion verifies that receipt against the upload and admitted assignment. +A visible attachment without that verified publication does not satisfy a task +that requires a file output. + +## Feedback and pending operations + +An assignment wake starts a Grok routine. Once the Bot accepts that work, +Paperclip keeps new comments and asynchronous operation receipts durable without +starting another routine for that session. Silence does not mean the Bot is idle. +Acceptance and the final webhook handoff are serialized. If acceptance finishes +while credentials are loading, the unsent wake is cancelled. Stop and +authenticated acknowledgements remain independent of a pending HTTP response. +Human-question answers and resolved durable waits resume through normal Runner +admission and a new assignment wake. + +If a tool returns pending, poll `paperclip_grok_operation_status` with the same +assignment and request IDs for its durable outcome. Do not repeat it under a new +ID or perform dependent work first. An unknown outcome requires reconciliation. At safe boundaries and before finishing, +the Bot reads its inbox and current task history for queued feedback. Current +assignment reads include these instructions, including for existing connections. + +## Recovery and stop + +Inspect a rejected delivery before using **Retry rejected delivery**. An uncertain +outcome needs inspection and explicit recovery; it is never sent again automatically. +Saved work remains on the task if execution fails. Reverify after clearing an +inspected recovery hold before admitting another assignment. + +**Stop Paperclip work** immediately revokes the session's Paperclip authority and +stops future dispatch. Remote Grok execution may continue. Open the Grok Bot app +to inspect it. Paperclip shows reported activity, not a complete remote transcript +or computer recording. diff --git a/doc/idle-sleep-safety.md b/doc/idle-sleep-safety.md index ec87d70985e..82f3652fbc7 100644 --- a/doc/idle-sleep-safety.md +++ b/doc/idle-sleep-safety.md @@ -74,6 +74,41 @@ Verify a full scheduled sleep/wake/execution cycle on staging before enabling this host capability. The [tests](../server/src/__tests__/idle-sleep-safety.test.ts) cover ownership, future deadlines, changed generations and conservative fallback. +## Passive issues and scheduled monitors + +Signed Cloud control callers can request `scheduledWake=2` to include issue +monitoring alongside routines. Earlier hosts requesting `scheduledWake=1` +retain the routine-only rules, including the conservative open-issue check. +Browser administrators and tenant credentials cannot request either extension. + +With the second capability, backlog tasks, unassigned todo tasks, explicit +blocked tasks and human-owned tasks may be passive records. Their presence +alone does not require compute. An assigned onboarding first task is also +passive with an idle assignee before its first run, user-authored comment or user-resolved opening +card. This uses the same awaiting-user predicate as recovery; a seeded greeting +does not start work. User input still enters through the host wake path. +Execution locks, automated pending review, +conversation state, unknown state and dependency repair remain blockers. A +blocked task whose blockers have all finished stays awake for reconciliation. +Enabled task watchdogs and unsupported recovery keep their existing checks. +Incoming changes still require the hosting admission and wake path. + +An active issue monitor needs a saved future `monitor_next_check_at` matching +its validated execution policy, a company-scoped agent, no outstanding dispatch +claim, and remaining attempts and timeout. Due, malformed, quota-recovery, +unowned and inconsistent monitors keep the workspace awake. Inspection does +not clear schedules, advance attempts or dispatch an agent. + +The combined report uses `version: 2` and a scheduled proof with `version: 1` +and `kind: "routines_and_issue_monitors"`. Its deadline is the earliest required +routine or monitor time across all companies. Its generation binds the current +process, idle owner and inspected catalogs. Hosts must acknowledge this known +proof kind, persist the alarm and validate the unchanged owned report before +stopping compute. Existing hosts reject the new kind. Monitor dispatch and +startup catch-up continue through the existing scheduler and atomic claims; +self-hosted scheduling is unchanged. This capability does not cover all future +work in the instance: every other safety check continues to apply. + ## Retry history Retry promotion keeps `scheduled_retry_at` on a run for inspection. That date diff --git a/doc/native-workspace-finalization-recovery.md b/doc/native-workspace-finalization-recovery.md index a9a6e6365f1..3d861bb82a9 100644 --- a/doc/native-workspace-finalization-recovery.md +++ b/doc/native-workspace-finalization-recovery.md @@ -1,5 +1,21 @@ # Native workspace finalization ownership and recovery +A native provider can fail before it submits a result while its workspace still +contains unpublished edits. A prepared workspace reference is not an export +receipt. Terminal provider failure pins the exact bound sandbox and records a +file-recovery obligation in the same transaction as the failed run. Both setup +failure handlers record that obligation before their terminal writer and provider +cleanup run. This includes descriptor reads that fail inside runtime selection. Only its exact live recovery claim can settle an owned +preparation failure; another owner or a stale claim prevents cleanup. Settling +that claim and recording file retention share a transaction. + +The recovery card names the original run and requires evidence that its exact +allocation stopped and its files were recovered. Recording that evidence does +not retry the old turn, certify unknown external actions, change the current task +owner or status, or require an accepted native result. Accepted-result export +recovery and same-run provider recovery keep their existing paths. An export +that has already finalized does not create this file obligation. + Native workspace export and merge acquire a PostgreSQL advisory lock scoped to company and run before the first physical copyback. The live heartbeat and the reconciler share that lock. Recovery skips a busy owner without recording another @@ -35,6 +51,14 @@ leave tar/Git children alive, so incomplete copyback requires operator stop verification even on the same host. PID reuse is checked against its recorded start time rather than trusted by PID alone. +When PostgreSQL configures an idle-transaction timeout, the owner probes its +dedicated lock transaction at one third of that interval, capped at +30 seconds. The probe stops and joins before the transaction is released. This +keeps a live copyback from losing its lock only because filesystem work has no +database statements. It does not disable the database timeout or reconnect a +failed lock session. Publication still requires the physical connection and +the exact durable owner token. A disabled timeout adds no periodic probe. + ## Automatic cloud recovery New Railway copyback receipts record the exact project, environment, service and diff --git a/doc/observability.md b/doc/observability.md index 36d2fc2554b..741dced29a7 100644 --- a/doc/observability.md +++ b/doc/observability.md @@ -175,7 +175,7 @@ GitHub identity selection, secret resolution, managed AI runtime preparation, skill inventory, connector assignments, connector skill preparation and delivery, connection instructions, and agent identity. Their names are `heartbeat.prepare.configuration_`, using the closed names in -`server/src/services/heartbeat/preparation-timing.ts`. These intervals are inside +`server/src/services/execution-preparation-timing.ts`. These intervals are inside `execution_configuration`; do not add them to its duration. They remain available when a later setup step fails before the provider lifecycle trace starts. @@ -194,6 +194,52 @@ traces; the provider lifecycle trace is created later and can have a different trace ID. Without an endpoint, tracing remains a no-op. These spans add no first-party Telemetry events or database run-log writes. +Workspace preparation also records `heartbeat.prepare.workspace_` spans +for source resolution, provisioning policy, base validation, source access, +provisioning, retained source access, workspace record creation and updates, +task binding, operation binding, configuration records, repository admission, +project repositories, and recovery-root reservation. These intervals are inside +`heartbeat.prepare.workspace`. Repeated bindings or updates have separate spans. +Do not add these intervals to the containing workspace duration. They use the +same run hash and contain no workspace paths, commands, or returned records. + +The `paperclip.native-controller` tracer records separate +`native.controller.` root spans around GitHub access setup, wake binding, +backend construction, connector assignments, assigned MCP discovery, tool +authority, companion selection and Runner binary resolution. It also records +GitHub shutdown, coordinator release, instruction checkpoints and collection, +and warm-session release. These intervals can occur between the existing native +preparation, execution and settlement spans. They are controller work, not model +turn time, and carry only the existing run hash. Nested phases overlap their +containing backend or lifecycle intervals; do not add both durations. + +All preparation timing uses the endpoint gate, even when another library has +registered a tracer provider. Tracer lookup, callback, status or ending failures +cannot repeat work or replace its result or error. Automatic database children +are not guaranteed: an aggregate workspace trace can contain just one span. +If a process exits before an operation settles, its open span may not be exported. + +With an endpoint, `paperclip.controller-startup` records independent +`controller.startup.` root spans for tool reviews, gateway sessions, +delivery-worker readiness, accounting, plugin-worker readiness, native recovery, +hot-restart adoption, orphan reaping, scheduled retries, queued runs, and goal +recovery. These spans have no custom attributes. Each ends when its awaited +phase settles, including failures. They separate controller readiness from +provider startup; they are not agent-run durations. A disabled endpoint takes +the direct work path without a tracer lookup or additional log messages. +Tracing failures cannot repeat recovery or replace its result or error. + +Native recovery and orphan reaping each run a native finalization +reconciliation pass; the periodic recovery loop runs it on every scheduler tick. +With an endpoint, a pass that takes at least one second records, after it ends, +a `controller.native_reconciliation` root span from `paperclip.native-reconciliation` +with one child span per phase: `legacy_workspace_exports`, +`obsolete_policy_reviews`, `automatic_completion_reviews`, `candidate_scan` and +`candidates`. The pass span carries `paperclip.native_reconciliation.scoped` and, +once known, `paperclip.native_reconciliation.candidates` (a count). A failed +pass marks the pass span and the phase that was running with an error status. +Faster passes record nothing. + ## Native Runner Trace Spans Paperclip Runner task runs emit a single foldable OpenTelemetry trace. This is @@ -591,6 +637,14 @@ whether remote work may have been accepted. identifiers, untyped terminal failures, and timeouts after accepted work remain reportable. Stream/poll errors retain their existing retry and logging behavior. +A Codex ACP model capacity failure also stays local. The Codex ACP classifier +must match a typed `service` terminal failure that reports the model at capacity +or the server overloaded. It records `codex_transient_upstream` with the +`transient_upstream` family, and the bounded transient retry owns recovery. The +report must come after setup and carry no signal. Other `service` failures, +untyped capacity text, and Codex CLI transient upstream failures remain reportable. +The run keeps its failed state and its terminal diagnostic. + Expected pre-provider waits for a shared project workspace, a credential rotation lock, or an exhausted AI connection pool carry a server-owned expected cancellation receipt. They retain existing retry and lock behavior without a @@ -746,6 +800,18 @@ only for these labels; its text and stack are not copied, because they bypass th executor's identity redaction. These observations do not change close, quarantine or recovery behavior. +Remote runner reattachment failures also record `remoteRunnerRecoveryStage` +and `remoteRunnerRecoveryReason` from the saved run. When reattachment fails +before the coordinator saves its result, the report reads the same labels from +the caught recovery error instead. These fields contain +closed labels for marker reads and process verification. They distinguish +missing or unsafe markers, invalid or missing fingerprints, changed or +unavailable process identity, and failed or timed-out probes. Paths, PIDs, +nonces, command lines and transport output are excluded. The existing +`runner_remote_process_identity_unavailable` error message and native failure +classification remain unchanged. The coordinator also saves these labels in +its failure detail and recovery evidence. A later failure clears stale labels. + ACP turns record `acpLastEventAgeMs`, `acpObservedEventCount`, `acpPendingToolCount`, and `acpToolInventoryComplete` at finalization, before usage reads, error logging, and cleanup. The age measures time since the last diff --git a/doc/plans/2026-10-09-repository-skill-packages.md b/doc/plans/2026-10-09-repository-skill-packages.md index 77ef830edeb..918bcbdd56c 100644 --- a/doc/plans/2026-10-09-repository-skill-packages.md +++ b/doc/plans/2026-10-09-repository-skill-packages.md @@ -27,3 +27,11 @@ The importer saves available repository files without asking for repair decision The production dialog shows every skill by default, selects all discovered skills, and collapses individual package files. It uses one collapsed warnings disclosure and leaves Import repository available. The importer has no packaging option: new imports keep repository files together. Storybook covers omitted files, warning details, missing references, setup problems, warnings after import, and mobile review. Focused scanner/delivery/PostgreSQL tests and UI tests verify saved warnings and available bytes; browser fixtures exercise the production UI. This revision has not rerun the paid local/Daytona campaigns above. Final acceptance also found and fixed duplicate saved warnings and an installed-file viewer that did not expose repository dependencies. The viewer now lists repository files and reads their saved bytes without GitHub access. Content errors for unsafe execution or secret disclosure omit the affected files with warnings; the remaining repository can still import. Database and scanner regressions verify these exclusions and file-reader company isolation. + +## October 14: dependency read batches + +Oversized repository imports read each Markdown dependency frontier through the existing bounded Git batch reader. The next frontier contains newly found references. The scanner retains its file and byte limits, content audits, reference order, and fallback for snapshot readers without batch support. This avoids one Git child process for each Markdown file during a source refresh. + +The regression checks a real oversized Git repository with 64 skills, transitive references, and a reference cycle. It compares every retained file's bytes and requires four batch reads with no individual reads. A local comparison of the old and final dependency walkers on 500 Markdown files and two shared references returned identical entries: three old samples took 4.9–6.2 seconds with 502 child processes, while the new samples took 62–85 milliseconds with three batches. These measurements exclude provider execution and do not establish agent response latency. + +The frontier uses a queue of newly added Markdown files. A regression at the 9,990-file limit retains every file, terminates a reference cycle, and bounds path visits. The earlier frontier rescan made 50,014,936 path visits in a local comparison. The queue makes 89,911 visits and retains the same output order. diff --git a/doc/plans/2026-10-09-task-workspaces.md b/doc/plans/2026-10-09-task-workspaces.md index c12873ddc10..fdfb1e2dad7 100644 --- a/doc/plans/2026-10-09-task-workspaces.md +++ b/doc/plans/2026-10-09-task-workspaces.md @@ -67,7 +67,7 @@ Preserve simultaneous local/SSH tasks in an explicitly configured identical phys Concurrent remote sharing requires proven common physical placement, separate session authority and coherent snapshot capability. Unsupported new concurrent sharing returns a typed unavailable result; do not instantiate separate clones and label them shared. Already-admitted descriptors remain recoverable unchanged. Local authoritative roots need no replica copyback; SSH retains its actual existing persistence contract. -Sparse filesystem checkpoints use disk-backed full manifests, changed bytes/deletions, safe-link and existing ignore semantics, and separate nested Git history. Capture still enumerates and hashes files with stable-read validation; a metadata hash cache is deferred. No-change sends zero file-content payload where capability is supported; manifest metadata still transfers; unsupported transport uses explicit confined full fallback. Sparse capture capacity failures before outbound data is read also use the existing confined full transfer, so a tiny output can recover when the sandbox cannot preserve the sparse manifest reserve. Returned manifest, path, digest and payload validation failures remain errors and never trigger fallback. Full checkpoint exports validate the completed staging root before merge, reject unsupported entries, and omit absolute or escaping links without following them; safe relative links remain supported. Reuse immutable durable seed generations, not only warm sandbox contents; reuse still verifies archive digests. Existing run references pin seeds/deltas until all admitted runs/recovery obligations release them. Retain the final host rescan because the run ownership fence does not exclude external writers to shared folders; do not reuse a stale post-merge manifest. +Sparse filesystem checkpoints use disk-backed full manifests, changed bytes/deletions, safe-link and existing ignore semantics, and separate nested Git history. Capture still enumerates and hashes files with stable-read validation; a metadata hash cache is deferred. No-change sends zero file-content payload where capability is supported; manifest metadata still transfers; unsupported transport uses explicit confined full fallback. Sparse capture capacity failures before outbound data is read also use the existing confined full transfer, so a tiny output can recover when the sandbox cannot preserve the sparse manifest reserve. Returned manifest, path, digest and payload validation failures remain errors and never trigger fallback. Full checkpoint exports validate the completed staging root before merge, reject unsupported entries, and omit absolute or escaping links without following them; safe relative links remain supported. Reuse immutable durable seed generations, not only warm sandbox contents; reuse still verifies archive digests. If warm identity validation requires a host-current transfer, that fallback retains the same company/workspace seed-cache scope, so a verified generation can supply the transfer without a second Git clone and archive rebuild. Missing or invalid generations still rebuild from the host. When the generation cache declines a new snapshot, warm fallback may replay the current preparation's private durable seed after its existing file-type and digest checks. It does not raise the cache quotas or reuse a prior run's seed. Missing or changed current-run archives retain the host rebuild path. Existing run references pin seeds/deltas until all admitted runs/recovery obligations release them. Retain the final host rescan because the run ownership fence does not exclude external writers to shared folders; do not reuse a stale post-merge manifest. A returned sparse snapshot must describe a coherent tree before any host mutation. Every nested entry requires an immediate parent marked as a directory in that same returned snapshot, including baseline-unchanged files. Checking every directory also verifies recursive ancestry. Top-level entries use the implicit root. Invalid trees fail without overwriting retained host files and keep recovery evidence. The check uses the existing indexed manifest interface; it adds no host-path scans or unbounded in-memory inventory. diff --git a/doc/plans/2026-10-12-heartbeat-disposition-test-audit-evidence.json b/doc/plans/2026-10-12-heartbeat-disposition-test-audit-evidence.json new file mode 100644 index 00000000000..1a1fe951658 --- /dev/null +++ b/doc/plans/2026-10-12-heartbeat-disposition-test-audit-evidence.json @@ -0,0 +1,5664 @@ +{ + "status": "HISTORICAL_PROVEN_EXACT_PARENT", + "head": "8e955181d8a3776acc86e4662de3c77933231a76", + "runtimeDelta": { + "baseline": 398, + "candidate": 396, + "removed": [ + "heartbeat orphaned process recovery re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "heartbeat orphaned process recovery repairs missing disposition independently of the productive diagnostic label" + ], + "added": [] + }, + "model": "OpenAI gpt-6.1-sol", + "contextWindow": "host does not expose it", + "timingClaim": "None. Local recorded durations are noisy diagnostics only.", + "testFile": "server/src/__tests__/heartbeat-process-recovery.test.ts", + "retired": [ + { + "number": 202, + "line": 13446, + "endLine": 13471, + "name": "repairs missing disposition independently of the productive diagnostic label", + "assertions": [ + "expect(result.continuationRequeued).toBe(1)", + "expect(result.escalated).toBe(0)", + "expect(result.issueIds).toEqual([issueId])", + "expect( retryRun?.contextSnapshot as Record | undefined, ).toMatchObject({ issueId, retryReason: \"issue_disposition_repair\", source: \"issue.deliberate_wait_disposition_repair\", })" + ] + }, + { + "number": 207, + "line": 13710, + "endLine": 13749, + "name": "re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "assertions": [ + "expect(result.continuationRequeued).toBe(1)", + "expect(result.productiveContinuationObserved).toBe(0)", + "expect(result.successfulContinuationObserved).toBe(0)", + "expect(result.escalated).toBe(0)", + "expect(result.issueIds).toEqual([issueId])", + "expect(issue?.status).toBe(\"in_progress\")", + "expect(comments).toHaveLength(0)", + "expect(runs).toHaveLength(2)", + "expect( retryRun?.contextSnapshot as Record | undefined, ).toMatchObject({ issueId, taskId: issueId, retryReason: \"issue_disposition_repair\", retryOfRunId: runId, source: \"issue.deliberate_wait_disposition_repair\", })", + "expect( retryRun?.contextSnapshot as Record, ).not.toHaveProperty(\"modelProfile\")", + "expect(wakeups).toHaveLength(2)" + ] + } + ], + "keeper": { + "number": 239, + "line": 15632, + "endLine": 15706, + "name": "does not treat a productive terminal run as healthy when in-progress work has no live path", + "sourceSHA256": "5c17238acb74acc2cf775abed83fd9269adfb75c6a6fc19f2c48b3264df78295", + "assertions": [ + "expect(sourceIssue).toMatchObject({ status: \"in_progress\", assigneeAgentId: agentId, assigneeUserId: null, executionRunId: null, })", + "expect(activeRuns).toHaveLength(0)", + "expect(liveWakeups).toHaveLength(0)", + "expect(result.productiveContinuationObserved).toBe(0)", + "expect(result.continuationRequeued + result.escalated).toBe(1)", + "expect(result.issueIds).toEqual([issueId])", + "expect(comments).toHaveLength(0)", + "expect(recoveryIssues).toHaveLength(0)", + "expect(followupRuns).toHaveLength(2)", + "expect( retryRun?.contextSnapshot as Record | undefined, ).toMatchObject({ issueId, taskId: issueId, retryReason: \"issue_disposition_repair\", retryOfRunId: runId, source: \"issue.deliberate_wait_disposition_repair\", })", + "expect( retryRun?.contextSnapshot as Record, ).not.toHaveProperty(\"modelProfile\")" + ] + }, + "coverageMap": { + "fixture": "seedStrandedIssueFixture({ status: \"in_progress\", runStatus: \"succeeded\", livenessState: \"advanced\" })", + "sharedAction": "heartbeatService(db).reconcileStrandedAssignedIssues() once", + "additionalKeeperPreconditions": "Pure reads verify in-progress source ownership, no active company runs, no queued/deferred company wakes before reconcile. They do not mutate fixture or authorize another run.", + "uniqueChecksTransferred": [ + "successfulContinuationObserved=0", + "continuationRequeued=1", + "escalated=0", + "post-reconcile issue.status=in_progress", + "two agent wakeup rows", + "conditional waitForRunToSettle on exact retryRun.id", + "two agent runs across all companies via original selectRecoveryRunsByAgent query, alongside original keeper company+agent count" + ], + "allSharedChecksPreserved": [ + "productiveContinuationObserved=0", + "exact issueIds", + "zero issue comments", + "two agent runs", + "context.issueId/taskId/retryReason/retryOfRunId/source", + "no modelProfile" + ], + "keeperExtraChecksPreserved": [ + "no stranded recovery children", + "precondition in_progress/assigneeAgentId/assigneeUserId/executionRunId", + "no active company runs and no live company wakes", + "continuationRequeued+escalated=1" + ], + "diff": { + "added": 13, + "deleted": 68, + "netTestLOC": -55, + "registrationsRetired": 2, + "declarationsBefore": 248, + "declarationsAfter": 246, + "expectedCasesBefore": 398, + "expectedCasesAfter": 396 + }, + "hashes": { + "original.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "candidate.test.ts": "b9acb0ab58861f791fd63376e7c1b0fa9fa17b2174d997fa74e946430f413de0", + "original.service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db", + "mutated-source.service.ts": "3eb31aa0ea1af37f0f68421873c70fb84ee188ce5959b61ce14a0c3aede05530", + "mutated-counter.service.ts": "f206e54f03c37667204b39eb48da1bfbb09f5e90b7b9b691a2c1a122838a017c", + "candidate.patch": "e5274e628a02e753b19e6a1cad0ba6341cff9262e160852540c451be69d9ea98" + }, + "mutationMap": [ + { + "owner": "server/src/services/recovery/service.ts:3163", + "kind": "wrong persisted disposition-repair context source", + "retiredDeclaration": 202, + "expectedOracle": "contextSnapshot.source toMatchObject", + "patch": "mutation-source.patch" + }, + { + "owner": "server/src/services/recovery/service.ts:4513", + "kind": "incorrect successfulContinuationObserved increment in queued legacy disposition repair branch", + "retiredDeclaration": 207, + "expectedOracle": "result.successfulContinuationObserved toBe(0)", + "patch": "mutation-counter.patch" + } + ], + "oracleLocations": { + "original-source": { + "title": "repairs missing disposition independently of the productive diagnostic label", + "oracleLine": 13463, + "expectedLog": "issue.wrong_disposition_repair" + }, + "original-counter": { + "title": "re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "oracleLine": 13721, + "expectedLog": "successfulContinuationObserved" + }, + "candidate-source": { + "title": "does not treat a productive terminal run as healthy when in-progress work has no live path", + "oracleLine": 15635, + "expectedLog": "issue.wrong_disposition_repair" + }, + "candidate-counter": { + "title": "does not treat a productive terminal run as healthy when in-progress work has no live path", + "oracleLine": 15608, + "expectedLog": "successfulContinuationObserved" + } + } + }, + "controls": [ + { + "phase": "baseline", + "exitCode": 0, + "tests": 398, + "passed": 398, + "failed": 0, + "pending": 0, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db" + } + }, + { + "phase": "candidate", + "exitCode": 0, + "tests": 396, + "passed": 396, + "failed": 0, + "pending": 0, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "b9acb0ab58861f791fd63376e7c1b0fa9fa17b2174d997fa74e946430f413de0", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db" + } + }, + { + "phase": "revert", + "exitCode": 0, + "tests": 398, + "passed": 398, + "failed": 0, + "pending": 0, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db" + } + }, + { + "phase": "mutation-source-original", + "exitCode": 1, + "tests": 398, + "passed": 0, + "failed": 1, + "pending": 397, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "server/src/services/recovery/service.ts": "3eb31aa0ea1af37f0f68421873c70fb84ee188ce5959b61ce14a0c3aede05530" + } + }, + { + "phase": "mutation-source-keeper", + "exitCode": 1, + "tests": 396, + "passed": 0, + "failed": 1, + "pending": 395, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "b9acb0ab58861f791fd63376e7c1b0fa9fa17b2174d997fa74e946430f413de0", + "server/src/services/recovery/service.ts": "3eb31aa0ea1af37f0f68421873c70fb84ee188ce5959b61ce14a0c3aede05530" + } + }, + { + "phase": "mutation-counter-original", + "exitCode": 1, + "tests": 398, + "passed": 0, + "failed": 1, + "pending": 397, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "server/src/services/recovery/service.ts": "f206e54f03c37667204b39eb48da1bfbb09f5e90b7b9b691a2c1a122838a017c" + } + }, + { + "phase": "mutation-counter-keeper", + "exitCode": 1, + "tests": 396, + "passed": 0, + "failed": 1, + "pending": 395, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "b9acb0ab58861f791fd63376e7c1b0fa9fa17b2174d997fa74e946430f413de0", + "server/src/services/recovery/service.ts": "f206e54f03c37667204b39eb48da1bfbb09f5e90b7b9b691a2c1a122838a017c" + } + }, + { + "phase": "restoration", + "exitCode": 0, + "tests": 396, + "passed": 396, + "failed": 0, + "pending": 0, + "hashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "b9acb0ab58861f791fd63376e7c1b0fa9fa17b2174d997fa74e946430f413de0", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db" + } + } + ], + "cleanSourceHashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db", + "server/src/services/heartbeat.ts": "9ac217e97e5319c850924f083df32be43df11f33f8970ff8efb4a5f776a0c1d9", + "server/src/services/heartbeat/retries.ts": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a" + }, + "ownerMutationKills": [ + { + "phase": "mutation-source-original", + "failedCase": "heartbeat orphaned process recovery repairs missing disposition independently of the productive diagnostic label", + "expected": "issue.deliberate_wait_disposition_repair", + "actual": "issue.wrong_disposition_repair", + "note": "Source mutation changes the persisted retry source; the source oracle expects the canonical value and observes the corrupted value." + }, + { + "phase": "mutation-source-keeper", + "failedCase": "heartbeat orphaned process recovery does not treat a productive terminal run as healthy when in-progress work has no live path", + "expected": "issue.deliberate_wait_disposition_repair", + "actual": "issue.wrong_disposition_repair", + "note": "Source mutation changes the persisted retry source; the source oracle expects the canonical value and observes the corrupted value." + }, + { + "phase": "mutation-counter-original", + "failedCase": "heartbeat orphaned process recovery re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "expected": 0, + "actual": 1, + "note": "Counter mutation increments successfulContinuationObserved; the keeper counter oracle expects 0 and observes 1." + }, + { + "phase": "mutation-counter-keeper", + "failedCase": "heartbeat orphaned process recovery does not treat a productive terminal run as healthy when in-progress work has no live path", + "expected": 0, + "actual": 1, + "note": "Counter mutation increments successfulContinuationObserved; the keeper counter oracle expects 0 and observes 1." + } + ], + "cleanup": { + "trackedSourceClean": true, + "pinnedSourceHashesMatch": true, + "ownedMatchingProcesses": [], + "globalPostgresMainProcessesAtSnapshot": [], + "globalVitestProcessesAtSnapshot": [ + { + "pid": 11493, + "parentPID": 11483, + "executable": "node" + } + ] + }, + "declarationLedger": [ + { + "number": 1, + "sourceLines": [ + 965, + 1044 + ], + "name": "does not reap active adapter executions started by another heartbeat service instance", + "kind": "ordinary", + "sourceSHA256": "e57a2403b82378c9f5c1b7f57ba97ae61f0c1dc55c42fd6de66430a953dfc242", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reap active adapter executions started by another heartbeat service instance", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 2, + "sourceLines": [ + 1601, + 1644 + ], + "name": "keeps unresolved-ref recovery and reporting while persisting only owned diagnostics: %s", + "kind": "each", + "sourceSHA256": "bdddddecb4a01f20b41b178555570bd4f5794fff5cd822e4e3cdda832d2cf52c", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "keeps unresolved-ref recovery and reporting while persisting only owned diagnostics: owned", + "status": "passed" + }, + { + "title": "keeps unresolved-ref recovery and reporting while persisting only owned diagnostics: forged", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 3, + "sourceLines": [ + 1646, + 1672 + ], + "name": "keeps a repair notice on the latest run when an unresolved branch fails twice", + "kind": "ordinary", + "sourceSHA256": "3af5cd2dc3d38a60e54111b8385bb993d47319a98cded003f5699412c699a675", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a repair notice on the latest run when an unresolved branch fails twice", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 4, + "sourceLines": [ + 1674, + 1726 + ], + "name": "preserves AI configuration recovery and reports only unexpected causes: %s", + "kind": "each", + "sourceSHA256": "572be6457e17bbcf895114aadf0bd2df92ca30ba06bcf28491ed8e9e0e69658b", + "runtimeCaseCount": 6, + "runtimeCases": [ + { + "title": "preserves AI configuration recovery and reports only unexpected causes: missing_default", + "status": "passed" + }, + { + "title": "preserves AI configuration recovery and reports only unexpected causes: credential_not_shared", + "status": "passed" + }, + { + "title": "preserves AI configuration recovery and reports only unexpected causes: database_error", + "status": "passed" + }, + { + "title": "preserves AI configuration recovery and reports only unexpected causes: unmarked_http_error", + "status": "passed" + }, + { + "title": "preserves AI configuration recovery and reports only unexpected causes: unmarked_forbidden", + "status": "passed" + }, + { + "title": "preserves AI configuration recovery and reports only unexpected causes: provider_error", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 5, + "sourceLines": [ + 1728, + 1896 + ], + "name": "settles explicit retry admission through real executor cleanup: %s", + "kind": "each", + "sourceSHA256": "6239aa8fdeb1648b2c3df940f7e0598b891e4eceb22965847f7693a62fedad62", + "runtimeCaseCount": 12, + "runtimeCases": [ + { + "title": "settles explicit retry admission through real executor cleanup: timeout", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: upstream", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: cleanup_pending", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: cleanup_pending_edited", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: cleanup_pending_exhausted", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: edited", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: exhausted", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: new_message", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: no_claim", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: reassigned", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: superseded", + "status": "passed" + }, + { + "title": "settles explicit retry admission through real executor cleanup: stopped", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 6, + "sourceLines": [ + 1898, + 1927 + ], + "name": "does not reuse a completed explicit turn's receipt for a missing-comment follow-up", + "kind": "ordinary", + "sourceSHA256": "b92c4ef7c39d30c16bed6bfa5da498fde89769f2f806f2465a87660b9d7fde87", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reuse a completed explicit turn's receipt for a missing-comment follow-up", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 7, + "sourceLines": [ + 1929, + 1973 + ], + "name": "persists the normalized failure without permanently blocking the conversation", + "kind": "ordinary", + "sourceSHA256": "e707c43c9852c6673a3e2fba75f9c7447163525a4163e499358b9699d66e5a60", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "persists the normalized failure without permanently blocking the conversation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 8, + "sourceLines": [ + 1975, + 2001 + ], + "name": "cancels obsolete continuation setup: %s", + "kind": "each", + "sourceSHA256": "f4caf6aa405e96200d446c0edec47b84043be626b34d938dd70cb2e6e6a889df", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "cancels obsolete continuation setup: continuation_task_ownership_changed", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 9, + "sourceLines": [ + 2003, + 2035 + ], + "name": "retains untyped continuation setup failures: %s", + "kind": "each", + "sourceSHA256": "d4430934f5fe4cebc3aff6b4ed0f0224bf635ca0887b48b5312f71cf7b3ecc27", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "retains untyped continuation setup failures: continuation_source_context_missing", + "status": "passed" + }, + { + "title": "retains untyped continuation setup failures: continuation_user_authorization_missing", + "status": "passed" + }, + { + "title": "retains untyped continuation setup failures: continuation_task_ownership_changed", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 10, + "sourceLines": [ + 2037, + 2108 + ], + "name": "does not immediately continue a low-trust preflight setup failure", + "kind": "ordinary", + "sourceSHA256": "aeeca6713628b9c0484a717d41c800bfe8a5d02c4cf846e424a02e27ecc7c7f0", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not immediately continue a low-trust preflight setup failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 11, + "sourceLines": [ + 2110, + 2139 + ], + "name": "does not queue immediate recovery when the failed run's issue is hidden", + "kind": "ordinary", + "sourceSHA256": "ea82af0bdb42de543b03e2fe570a7b44a0bc3a5f2b7ed9a0105791b3a11f45c2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not queue immediate recovery when the failed run's issue is hidden", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 12, + "sourceLines": [ + 2141, + 2149 + ], + "name": "does not relabel a lost process run when its agent changes to a conversation adapter", + "kind": "ordinary", + "sourceSHA256": "f085754bd7bc8bf67ab5f505ef4725f72caa870e5eb591e3bd21083e9eafe68e", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not relabel a lost process run when its agent changes to a conversation adapter", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 13, + "sourceLines": [ + 2151, + 2180 + ], + "name": "waits for a live terminal predecessor before a new turn (historical hold: %s)", + "kind": "each", + "sourceSHA256": "b819cba7f939868aa9b3bb5f12d4624f336003c181fd63b38fd4f9434875d231", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "waits for a live terminal predecessor before a new turn (historical hold: false)", + "status": "passed" + }, + { + "title": "waits for a live terminal predecessor before a new turn (historical hold: true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 14, + "sourceLines": [ + 2182, + 2198 + ], + "name": "waits for a terminal predecessor's environment lease to be released", + "kind": "ordinary", + "sourceSHA256": "ad23ec2ed42f8c36b38f3f60ace72227f715cae99810ff5f51a0e58774cb5c6e", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "waits for a terminal predecessor's environment lease to be released", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 15, + "sourceLines": [ + 2200, + 2220 + ], + "name": "keeps an unsafe Stop blocked when recovery sees a deferred human comment", + "kind": "ordinary", + "sourceSHA256": "fe6f24ec60e4d853cfa906818daf1708542f53d08503af286dfc98c01a85a333", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps an unsafe Stop blocked when recovery sees a deferred human comment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 16, + "sourceLines": [ + 2222, + 2236 + ], + "name": "leaves hidden issues out of stranded-issue reconciliation", + "kind": "ordinary", + "sourceSHA256": "4a95510fe77d0d6b58cb0f95ae541c88558f376fb6a1b2418f6690e30e3b74a7", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "leaves hidden issues out of stranded-issue reconciliation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 17, + "sourceLines": [ + 2275, + 2320 + ], + "name": "preserves native finalization source and board hold after restart with %s owner", + "kind": "each", + "sourceSHA256": "5677dbe2fa9eea6d271990da61f880d2a593ac2ff13a0e37c617cd050c2e79fb", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "preserves native finalization source and board hold after restart with foreign owner", + "status": "passed" + }, + { + "title": "preserves native finalization source and board hold after restart with malformed owner", + "status": "passed" + }, + { + "title": "preserves native finalization source and board hold after restart with expired_controller owner", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 18, + "sourceLines": [ + 2322, + 2342 + ], + "name": "rejects a stale orphan candidate when native finalization commits before owner acquisition", + "kind": "ordinary", + "sourceSHA256": "3a370e7a9473dd630cc843c02378dd0dd3cdb3cf3e80dd621886796ab383cbe6", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rejects a stale orphan candidate when native finalization commits before owner acquisition", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 19, + "sourceLines": [ + 2344, + 2394 + ], + "name": "fences a native finalization publisher that commits while the orphan UPDATE waits on its row lock", + "kind": "ordinary", + "sourceSHA256": "043aa82f1ea321a292aef74989bc5249a167a46af55d8d8164eae753eb07bc3a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "fences a native finalization publisher that commits while the orphan UPDATE waits on its row lock", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 20, + "sourceLines": [ + 2396, + 2414 + ], + "name": "does not invent native finalization authority from %s", + "kind": "each", + "sourceSHA256": "babe25914fba9d87c38673d00362aa265771f0a658ab1adea6558f91f8fa8fec", + "runtimeCaseCount": 4, + "runtimeCases": [ + { + "title": "does not invent native finalization authority from phase_only", + "status": "passed" + }, + { + "title": "does not invent native finalization authority from rejected_result", + "status": "passed" + }, + { + "title": "does not invent native finalization authority from terminal_failure", + "status": "passed" + }, + { + "title": "does not invent native finalization authority from unknown_phase", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 21, + "sourceLines": [ + 2416, + 2438 + ], + "name": "resolves the exact native finalization hold after verified workspace settlement without replay", + "kind": "ordinary", + "sourceSHA256": "67d1e35cf84c0b95a04030f50b8d49529b7ae7a149aa25759325cc109976ac98", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "resolves the exact native finalization hold after verified workspace settlement without replay", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 22, + "sourceLines": [ + 2440, + 2452 + ], + "name": "rejects foreign result, company, and issue bindings for native finalization", + "kind": "ordinary", + "sourceSHA256": "bcbb500ee7c0b767f72fefd59bfe5fb6c0211c2f3490f758a6080a6d11b5c05d", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rejects foreign result, company, and issue bindings for native finalization", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 23, + "sourceLines": [ + 2454, + 2460 + ], + "name": "does not extend native finalization authority to a legacy runtime", + "kind": "ordinary", + "sourceSHA256": "4ef85999c9f679eb7f007b0b711dc3119f55611c29ab978653cb7fb4a623f259", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not extend native finalization authority to a legacy runtime", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 24, + "sourceLines": [ + 2462, + 2475 + ], + "name": "does not clear another run's native finalization hold on the same task", + "kind": "ordinary", + "sourceSHA256": "a51a446fcd34b7fcb86a2a06d0501ec6ae0620e182a26643c85085d0dd16ddfd", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not clear another run's native finalization hold on the same task", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 25, + "sourceLines": [ + 2477, + 2489 + ], + "name": "keeps existing terminal-task disposition for a native finalization hold", + "kind": "ordinary", + "sourceSHA256": "3250ef406661d80d8129f8f0cb9398fe37e3750e35bf191796ae00f47546eb7a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps existing terminal-task disposition for a native finalization hold", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 26, + "sourceLines": [ + 2491, + 2741 + ], + "name": "joins startup and reap retained cleanup, keeps recovery live, and drains its %s operation", + "kind": "each", + "sourceSHA256": "ea47cc6dba980f53003c80c604e5d3e50fa789b4f2ab7d75eded136ea3d08065", + "runtimeCaseCount": 4, + "runtimeCases": [ + { + "title": "joins startup and reap retained cleanup, keeps recovery live, and drains its settled operation", + "status": "passed" + }, + { + "title": "joins startup and reap retained cleanup, keeps recovery live, and drains its rejected operation", + "status": "passed" + }, + { + "title": "joins startup and reap retained cleanup, keeps recovery live, and drains its late_callback operation", + "status": "passed" + }, + { + "title": "joins startup and reap retained cleanup, keeps recovery live, and drains its provider_transport_failed operation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 27, + "sourceLines": [ + 2743, + 2768 + ], + "name": "keeps a local run active when the recorded pid is still alive", + "kind": "ordinary", + "sourceSHA256": "a3f34ca212685f45e5bee0e8feaeacf50f543f397444fffd0ccbfefb07f76f1a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a local run active when the recorded pid is still alive", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 28, + "sourceLines": [ + 2770, + 2811 + ], + "name": "keeps a native run active without granting legacy retry or signal authority", + "kind": "ordinary", + "sourceSHA256": "ab8e0983cd07d3e536287ad9dc5dcca67588e668d85fc6e4f26d1d68b6002e2a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a native run active without granting legacy retry or signal authority", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 29, + "sourceLines": [ + 2813, + 2961 + ], + "name": "retains authentication-blocked ownership after restart with coordinator phase %s", + "kind": "each", + "sourceSHA256": "da3dc2ef29358251bc532150bb5934e2c2bc6cc8a2178691ddc58065977a2778", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "retains authentication-blocked ownership after restart with coordinator phase terminal_failure", + "status": "passed" + }, + { + "title": "retains authentication-blocked ownership after restart with coordinator phase retryable_failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 30, + "sourceLines": [ + 2963, + 3003 + ], + "name": "keeps a live native run owned by the current controller out of ambiguous recovery", + "kind": "ordinary", + "sourceSHA256": "2d031c5b9774f5257f83e683d8e30c9c98c6db2c4b64f2495460434378c72c92", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a live native run owned by the current controller out of ambiguous recovery", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 31, + "sourceLines": [ + 3005, + 3050 + ], + "name": "does not reap a retryable native run while its same-run recovery path owns it", + "kind": "ordinary", + "sourceSHA256": "37a1897441425fd47227840b5b2de6afa75fa228f0aa2d50c728163896b6f02b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reap a retryable native run while its same-run recovery path owns it", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 32, + "sourceLines": [ + 3052, + 3079 + ], + "name": "does not grant a dead native run legacy retry authority after adapter reassignment", + "kind": "ordinary", + "sourceSHA256": "a3423bf197972fc193f7dd0ea7470dcf0a6906664e9b00f76fa7bb3bf2e92b79", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not grant a dead native run legacy retry authority after adapter reassignment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 33, + "sourceLines": [ + 3081, + 3113 + ], + "name": "skips generic timer wakes without invoking an adapter when no assigned work is actionable", + "kind": "ordinary", + "sourceSHA256": "a21e1de612f12c998438e8f51ffc2982b426d202a2318e8fa4c3d3f8c06ec6f3", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "skips generic timer wakes without invoking an adapter when no assigned work is actionable", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 34, + "sourceLines": [ + 3115, + 3127 + ], + "name": "recovers legacy startup before adapter.invoke using the claimed adapter identity", + "kind": "ordinary", + "sourceSHA256": "95d604588811b52e3db7ef1f4ebfbea112c237c7196af01f78d6151f193cca5a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers legacy startup before adapter.invoke using the claimed adapter identity", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 35, + "sourceLines": [ + 3129, + 3217 + ], + "name": "schedules one conversation continuation after losing the provider", + "kind": "ordinary", + "sourceSHA256": "d00299ec60227dcaeb21234ed5b64c3b5bff4085224a3f99cea17dc8f7812209", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "schedules one conversation continuation after losing the provider", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 36, + "sourceLines": [ + 3219, + 3244 + ], + "name": "requires reconciliation for a lost monitor whose provider outcomes are unknown", + "kind": "ordinary", + "sourceSHA256": "1270da179a1c1a1d4ca1d242e9050f3d90d003ff8cf1750e41740b1f9fefa915", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "requires reconciliation for a lost monitor whose provider outcomes are unknown", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 37, + "sourceLines": [ + 3246, + 3275 + ], + "name": "does not retry a lost monitor dispatch while another monitor wake remains scheduled", + "kind": "ordinary", + "sourceSHA256": "fdb49058cbd631b9a3afc8fbb01c57df3db70094becda1526a12d6167cdd057b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not retry a lost monitor dispatch while another monitor wake remains scheduled", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 38, + "sourceLines": [ + 3310, + 3339 + ], + "name": "fences native selection when cancellation wins during preparation", + "kind": "ordinary", + "sourceSHA256": "c17e6273c1b46897b12bfded1868ce6c4c792dcbf25487388b5e5d25be9a9335", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "fences native selection when cancellation wins during preparation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 39, + "sourceLines": [ + 3341, + 3371 + ], + "name": "does not dispatch when cancellation wins after native selection", + "kind": "ordinary", + "sourceSHA256": "20c18bd0a5955507b7e2153011f14cb7db3197df6d05b3801e3afc3033430e61", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not dispatch when cancellation wins after native selection", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 40, + "sourceLines": [ + 3373, + 3428 + ], + "name": "dispatches local native external chat inside the server-selected task root", + "kind": "ordinary", + "sourceSHA256": "ad7c2d1a910089b2feeb5259e28fe4b5fc1411953ff81b0e622cecb90570caa2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "dispatches local native external chat inside the server-selected task root", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 41, + "sourceLines": [ + 3430, + 3473 + ], + "name": "carries the ordinary initial description provenance through native heartbeat assembly", + "kind": "ordinary", + "sourceSHA256": "1dd6edc04a9cd916db7e401b0d30943b2125feecce656072c3fb61fa85a99382", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "carries the ordinary initial description provenance through native heartbeat assembly", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 42, + "sourceLines": [ + 3475, + 3594 + ], + "name": "holds admitted native chat with a legacy shared cwd without replacing input or releasing ownership", + "kind": "ordinary", + "sourceSHA256": "46c91235ae91d2cf4ca48c9042f72901d01bd6c078354e2587c39c99bb0d2784", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "holds admitted native chat with a legacy shared cwd without replacing input or releasing ownership", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 43, + "sourceLines": [ + 3596, + 3603 + ], + "name": "checkpoints idle warm sessions even when no hot restart was requested", + "kind": "ordinary", + "sourceSHA256": "ccdc4fdde6f0f584d1172c2ce67b0cafb0fe67b6919dbe3eab4594ed6062c9fc", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "checkpoints idle warm sessions even when no hot restart was requested", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 44, + "sourceLines": [ + 3605, + 3669 + ], + "name": "captures a hot-restart shutdown snapshot without interrupting running runs", + "kind": "ordinary", + "sourceSHA256": "1ade63648a871e12ae7a5cfbbbb3b3a403bd26245b590f86363ec995b44631e8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "captures a hot-restart shutdown snapshot without interrupting running runs", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 45, + "sourceLines": [ + 3671, + 3756 + ], + "name": "snapshots and drains a server-stdio ACP run before embedded database shutdown", + "kind": "ordinary", + "sourceSHA256": "385241dbc58de228443b60c91f1a8efba861e589aecce13606727b0b63469347", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "snapshots and drains a server-stdio ACP run before embedded database shutdown", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 46, + "sourceLines": [ + 3758, + 3815 + ], + "name": "reports a selectively drained ACP run as lost when terminal persistence fails", + "kind": "ordinary", + "sourceSHA256": "48bea9460320bf4ed1c89a4890d31be9a9f3dc75fa156b99413d10daf00345d5", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reports a selectively drained ACP run as lost when terminal persistence fails", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 47, + "sourceLines": [ + 3817, + 3916 + ], + "name": "drains only server-stdio runs and preserves detached CLI adoption in a mixed restart", + "kind": "ordinary", + "sourceSHA256": "81e30096dcb94125866db7069fb2df024b03e7ee1735add62038c8f237ac7956", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "drains only server-stdio runs and preserves detached CLI adoption in a mixed restart", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 48, + "sourceLines": [ + 3918, + 3987 + ], + "name": "adopts an old-server legacy snapshot written for a new instance-scoped marker", + "kind": "ordinary", + "sourceSHA256": "bdcc8bfda3a1704d781bce88ce2936f09e1800e0ab570f46dd2b6c384120f02b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "adopts an old-server legacy snapshot written for a new instance-scoped marker", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 49, + "sourceLines": [ + 3989, + 4024 + ], + "name": "reports preflight live runs as lost when the shutdown snapshot is missing", + "kind": "ordinary", + "sourceSHA256": "1e5ea4f6885f0f353a12ff051d9636317e6b54ca807e815600e57bde106b6878", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reports preflight live runs as lost when the shutdown snapshot is missing", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 50, + "sourceLines": [ + 4026, + 4073 + ], + "name": "reports a preflight run that finished before snapshot capture as finalized", + "kind": "ordinary", + "sourceSHA256": "22b93d0e1195f5fc524ba9c32078bd857847c6f7c7c92ff33562c219a39a1029", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reports a preflight run that finished before snapshot capture as finalized", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 51, + "sourceLines": [ + 4075, + 4208 + ], + "name": "persists codex_local spawn identity before hot restart and never loses the live run for missing metadata", + "kind": "ordinary", + "sourceSHA256": "4a31bea8e215c098ad2ea853e13b6bb77b5301023db7f1ade7b7e302090ddd7c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "persists codex_local spawn identity before hot restart and never loses the live run for missing metadata", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 52, + "sourceLines": [ + 4210, + 4280 + ], + "name": "reports adopted hot-restart runs before startup reap can mark them process_lost", + "kind": "ordinary", + "sourceSHA256": "141b74fe80c4edf294c4b8cae9f35e6f2e7478e7088ea11d48bed9f0baaf04ad", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reports adopted hot-restart runs before startup reap can mark them process_lost", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 53, + "sourceLines": [ + 4282, + 4340 + ], + "name": "keeps process-group-only hot-restart adoptions out of process_lost reaping", + "kind": "skipIf", + "sourceSHA256": "e50f256fb720ad0030256447f615a5d5f5bebbc5c8ac734148ce013dac2712e0", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps process-group-only hot-restart adoptions out of process_lost reaping", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 54, + "sourceLines": [ + 4342, + 4386 + ], + "name": "terminalizes an unsupported legacy session on shutdown without speculative replay", + "kind": "ordinary", + "sourceSHA256": "64e6a4df6b4111cf25d0d0f82509f14c39932aafbb03ce3f2608d386196cc302", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "terminalizes an unsupported legacy session on shutdown without speculative replay", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 55, + "sourceLines": [ + 4388, + 4444 + ], + "name": "suspends native Paperclip Runner ownership on graceful restart without cancelling or creating a retry run", + "kind": "ordinary", + "sourceSHA256": "25aea5766be0f0cae6be0240010610349c96f7f9472b6ad3bcb1d2d50232fa93", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "suspends native Paperclip Runner ownership on graceful restart without cancelling or creating a retry run", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 56, + "sourceLines": [ + 4446, + 4487 + ], + "name": "does not overwrite a run that is no longer running during graceful shutdown drain", + "kind": "ordinary", + "sourceSHA256": "2a53ef98dfa994dd560062f28730ff706797830c98be6cd2e4c2b3fc31d3f83b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not overwrite a run that is no longer running during graceful shutdown drain", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 57, + "sourceLines": [ + 4489, + 4504 + ], + "name": "does not duplicate a legacy reconciliation action across repeated shutdowns", + "kind": "ordinary", + "sourceSHA256": "462a23a44f9bf15755dd8214231867c1de2be676883bdbc306b85fe6df2dbc89", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not duplicate a legacy reconciliation action across repeated shutdowns", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 58, + "sourceLines": [ + 4506, + 4545 + ], + "name": "does not reset an exhausted incident budget on server restart", + "kind": "ordinary", + "sourceSHA256": "667d1ae7a4f05536bc464f147a728796625f6b816ef4136e8487993a03ae36ee", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reset an exhausted incident budget on server restart", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 59, + "sourceLines": [ + 4547, + 4569 + ], + "name": "releases active environment leases when an orphaned run is reaped", + "kind": "ordinary", + "sourceSHA256": "d54ba1e81f61f796410ed9c6d375b00f2cbc78fc1bc7d75cd515942a5e6619be", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "releases active environment leases when an orphaned run is reaped", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 60, + "sourceLines": [ + 4571, + 4603 + ], + "name": "does not signal an unowned persisted process group after the tracked parent exits", + "kind": "skipIf", + "sourceSHA256": "ef3a35c36c7ccf57bee4a097cd93666a8802b8a8764fe2ee8a157af5270dc8df", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not signal an unowned persisted process group after the tracked parent exits", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 61, + "sourceLines": [ + 4605, + 4627 + ], + "name": "does not bypass unknown process outcomes through immediate continuation recovery", + "kind": "ordinary", + "sourceSHA256": "922793188732d925f03e3f2186ed776bf10846306b997440fd95cec4c3a7ded4", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not bypass unknown process outcomes through immediate continuation recovery", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 62, + "sourceLines": [ + 4629, + 4706 + ], + "name": "blocks failed recovery work in place during immediate terminal-run cleanup", + "kind": "ordinary", + "sourceSHA256": "19773eb58b2f99b210c66cdb62cda591fcc101282d3b8aa28e1f137c47d8e788", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks failed recovery work in place during immediate terminal-run cleanup", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 63, + "sourceLines": [ + 4708, + 4743 + ], + "name": "does not block paused-tree work when immediate continuation recovery is suppressed by the hold", + "kind": "ordinary", + "sourceSHA256": "ed18a016d0147f4614d8d4e203693209cf0e4b95087eb22a212d2304ae305c41", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not block paused-tree work when immediate continuation recovery is suppressed by the hold", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 64, + "sourceLines": [ + 4745, + 4780 + ], + "name": "allows conversation continuation after a transient remote-compaction failure", + "kind": "ordinary", + "sourceSHA256": "9eda0fd96c117d1253192511b9a33759510b02635a321c38861e15e469f12887", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "allows conversation continuation after a transient remote-compaction failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 65, + "sourceLines": [ + 4782, + 4962 + ], + "name": "schedules bounded retries for failed accepted interaction continuation wakes", + "kind": "ordinary", + "sourceSHA256": "573e70126bc707ad7e773d3b9dfd01e5859f936d4b7ceed6829b92b1c7ded4d1", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "schedules bounded retries for failed accepted interaction continuation wakes", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 66, + "sourceLines": [ + 4964, + 5137 + ], + "name": "schedules an infra retry for a setup failure caused by a transient sandbox provider worker restart", + "kind": "ordinary", + "sourceSHA256": "cda9ed6e83f766d8c55983eaa7d58ed437b398f7e040b4b91597a514e6ab8003", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "schedules an infra retry for a setup failure caused by a transient sandbox provider worker restart", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 67, + "sourceLines": [ + 5139, + 5172 + ], + "name": "classifies only the installed-but-not-ready sandbox provider plugin message as a configuration gap", + "kind": "ordinary", + "sourceSHA256": "b35e64856a22a435231c1ec41af47ce432665d9bdcc90c464cf4f0d04a150cb7", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "classifies only the installed-but-not-ready sandbox provider plugin message as a configuration gap", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 68, + "sourceLines": [ + 5174, + 5308 + ], + "name": "blocks the issue instead of re-dispatching when the sandbox provider plugin is stuck in error", + "kind": "ordinary", + "sourceSHA256": "fff8a612aa1f3b89734631f8d4667adc15f2186f76190dd96de686b90e68b0d4", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks the issue instead of re-dispatching when the sandbox provider plugin is stuck in error", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 69, + "sourceLines": [ + 5310, + 5481 + ], + "name": "escalates (does not retry) an accepted-interaction-continuation setup failure whose message matches neither retryable pattern", + "kind": "ordinary", + "sourceSHA256": "b66d1202ae1eebbb6e0e56dc5dd4b4390b0b0ee098c1be00dbdb330032fed2a3", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates (does not retry) an accepted-interaction-continuation setup failure whose message matches neither retryable pattern", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 70, + "sourceLines": [ + 5483, + 5600 + ], + "name": "escalates exhausted plan approval resume failures with a system comment and recovery action", + "kind": "ordinary", + "sourceSHA256": "fcd524d0082212bcb7e724f4a1906c25e7c08debb7e5d29fdcc0bc567767607c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates exhausted plan approval resume failures with a system comment and recovery action", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 71, + "sourceLines": [ + 5604, + 5744 + ], + "name": "retries a plan-approval continuation lost as process_lost before agent start as an infrastructure failure", + "kind": "ordinary", + "sourceSHA256": "959ce379297f6271275d27ce5074deffc852fff70e8fc7bfdda1b52c3532bf97", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries a plan-approval continuation lost as process_lost before agent start as an infrastructure failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 72, + "sourceLines": [ + 5746, + 5863 + ], + "name": "blocks a git-sensitive local adapter before launch when a project-workspace-linked issue is missing its project id", + "kind": "ordinary", + "sourceSHA256": "3696b26df78b9e4983fa18887f4a71dc6c313af5dd946e4af0bea15d39d6be4c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks a git-sensitive local adapter before launch when a project-workspace-linked issue is missing its project id", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 73, + "sourceLines": [ + 5865, + 5913 + ], + "name": "keeps an explicit local-path/worktree conflict blocked with a board action and no Sentry event", + "kind": "ordinary", + "sourceSHA256": "79ae93700d32eadb7822ecdd25358e1f6398734883f6f74a302de2c08b43eb23", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps an explicit local-path/worktree conflict blocked with a board action and no Sentry event", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 74, + "sourceLines": [ + 5915, + 6010 + ], + "name": "preserves failed workspace recovery while classifying managed Git connections: $name", + "kind": "each", + "sourceSHA256": "3a7509ebb2f0fff4a94318a9988bf265bf6d204a6ced8df78033b8f1f791556b", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "preserves failed workspace recovery while classifying managed Git connections: HTTPS authentication", + "status": "passed" + }, + { + "title": "preserves failed workspace recovery while classifying managed Git connections: SSH alias DNS", + "status": "passed" + }, + { + "title": "preserves failed workspace recovery while classifying managed Git connections: mixed recognized and unknown candidates", + "status": "passed" + }, + { + "title": "preserves failed workspace recovery while classifying managed Git connections: mixed local and authentication diagnost\u2026", + "status": "passed" + }, + { + "title": "preserves failed workspace recovery while classifying managed Git connections: unknown clone failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 75, + "sourceLines": [ + 6012, + 6086 + ], + "name": "keeps a direct additional-repository connection failure visible without reporting it to Sentry", + "kind": "ordinary", + "sourceSHA256": "693146c4f6e2c904b55ca122e37e0f098c342c26a84bcf2dfa6680e785046cf6", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a direct additional-repository connection failure visible without reporting it to Sentry", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 76, + "sourceLines": [ + 6088, + 6118 + ], + "name": "redacts opaque environment-bound credentials from Sentry diagnostics: %s", + "kind": "each", + "sourceSHA256": "70237ea6963af7874d471855deffa70725379b821793859c43be6fecca57a068", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "redacts opaque environment-bound credentials from Sentry diagnostics: throw", + "status": "passed" + }, + { + "title": "redacts opaque environment-bound credentials from Sentry diagnostics: result", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 77, + "sourceLines": [ + 6120, + 6235 + ], + "name": "blocks before dispatch when a declared secret ref has no binding instead of emitting an opaque setup failure", + "kind": "ordinary", + "sourceSHA256": "1ee13a9a395cc8dd03dc1b31eb1504e38323a00ee26fc611ba308b09a0e55bc9", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks before dispatch when a declared secret ref has no binding instead of emitting an opaque setup failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 78, + "sourceLines": [ + 6237, + 6282 + ], + "name": "queues one finish-handoff wake when a successful run leaves in-progress work without a next action", + "kind": "ordinary", + "sourceSHA256": "24e3deca25de6d2ce048ec00c740097856a4a41c84568c6dab2f27e9ac776da5", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "queues one finish-handoff wake when a successful run leaves in-progress work without a next action", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 79, + "sourceLines": [ + 6284, + 6351 + ], + "name": "requeues a missing-disposition handoff when the previous corrective wake was cancelled", + "kind": "ordinary", + "sourceSHA256": "28eec6f524297e20cc5284c2a0e282d371a5487c030e7a8ae1fc47b0764e68ce", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "requeues a missing-disposition handoff when the previous corrective wake was cancelled", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 80, + "sourceLines": [ + 6353, + 6441 + ], + "name": "queues one missing-disposition handoff for artifact-producing successful runs left in progress", + "kind": "ordinary", + "sourceSHA256": "a5063adf2ee3270aa5a6ab34168d7d181f5febcf99c75d7ed2826f33b1ddc515", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "queues one missing-disposition handoff for artifact-producing successful runs left in progress", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 81, + "sourceLines": [ + 6443, + 6497 + ], + "name": "redacts secret-bearing successful-run detected progress before handoff disclosure", + "kind": "ordinary", + "sourceSHA256": "7f471b4ff1189b909199368fb8c03ff9257c86a8f30a71f37a85fa7f681c099a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "redacts secret-bearing successful-run detected progress before handoff disclosure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 82, + "sourceLines": [ + 6499, + 6622 + ], + "name": "escalates an exhausted failed successful-run handoff without using generic continuation recovery first", + "kind": "ordinary", + "sourceSHA256": "9c1a6e4e8c0fc52a34ba1591f36c849c917a5841928a802d5b5d274b5bc20445", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an exhausted failed successful-run handoff without using generic continuation recovery first", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 83, + "sourceLines": [ + 6624, + 6703 + ], + "name": "retries an interrupted corrective successful-run handoff instead of escalating it", + "kind": "ordinary", + "sourceSHA256": "de33c60cd1eb76f96c329494af7b3d4bd1053a6088eb811cbe533047dce6bc4a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries an interrupted corrective successful-run handoff instead of escalating it", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 84, + "sourceLines": [ + 6705, + 6773 + ], + "name": "escalates an interrupted corrective successful-run handoff once its transient retry budget is spent", + "kind": "ordinary", + "sourceSHA256": "5ae489cd7532719f0660df8e326da190f73f334d56daa0417a146a810e987c61", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an interrupted corrective successful-run handoff once its transient retry budget is spent", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 85, + "sourceLines": [ + 6775, + 6846 + ], + "name": "escalates an in_progress issue whose interrupted run has spent its transient retry budget", + "kind": "ordinary", + "sourceSHA256": "886df06108228b693f2024216fb21a43c473354739a4df424084492e77558db8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an in_progress issue whose interrupted run has spent its transient retry budget", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 86, + "sourceLines": [ + 6848, + 6890 + ], + "name": "keeps retrying an interrupted run while its transient retry budget remains", + "kind": "ordinary", + "sourceSHA256": "7a01901aae32879a8186f61886a83747907d8cf4f466cc82c54ca7851375c2fe", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps retrying an interrupted run while its transient retry budget remains", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 87, + "sourceLines": [ + 6892, + 6926 + ], + "name": "escalates an assigned todo issue whose lost dispatch has spent its transient retry budget", + "kind": "ordinary", + "sourceSHA256": "f2d48e3ebd3436f8a4503ca9914010c1c5ca84c8f27aabf54f20c802bf4829d8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an assigned todo issue whose lost dispatch has spent its transient retry budget", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 88, + "sourceLines": [ + 6928, + 6963 + ], + "name": "escalates an exhausted successful handoff run that still leaves no disposition", + "kind": "ordinary", + "sourceSHA256": "8d84e16efe472ff6f9d03f72471ab6a864745a8d970267073a9e6b1342860c7b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an exhausted successful handoff run that still leaves no disposition", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 89, + "sourceLines": [ + 6965, + 7100 + ], + "name": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: %s)", + "kind": "each", + "sourceSHA256": "ef55d0d4603c2ad7a6a4a1d36007d1612e722ce5a1d47d498f2a31070614357b", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: false)", + "status": "passed" + }, + { + "title": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: receipt)", + "status": "passed" + }, + { + "title": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: launch)", + "status": "passed" + }, + { + "title": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: provider)", + "status": "passed" + }, + { + "title": "converts a continuation parked for review into a dependency wait on its open sub-tasks (dispatch receipt: cleanup)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 90, + "sourceLines": [ + 7102, + 7179 + ], + "name": "converts a continuation parked for review into a dependency wait on its existing blockers", + "kind": "ordinary", + "sourceSHA256": "516389e977e82f73cc5f63833ae8e905568e5ec5b669a9a308f0277e7d86e4f5", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "converts a continuation parked for review into a dependency wait on its existing blockers", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 91, + "sourceLines": [ + 7181, + 7248 + ], + "name": "repairs the PAP-16986 deliberate wait through the original owner when no target exists", + "kind": "ordinary", + "sourceSHA256": "644cb575e0600b72440a2736e7116e7258e80f194760a67b5955bfe2b06eef35", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "repairs the PAP-16986 deliberate wait through the original owner when no target exists", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 92, + "sourceLines": [ + 7250, + 7323 + ], + "name": "folds a persisted disposition-repair action when a current typed wait appears", + "kind": "ordinary", + "sourceSHA256": "b6c15bfd33ade4e6950ded21ce09e25a2065da6b35a3bc3a4b8efd9be9d304aa", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "folds a persisted disposition-repair action when a current typed wait appears", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 93, + "sourceLines": [ + 7325, + 7409 + ], + "name": "reschedules an expired persisted disposition repair without duplicating the retry", + "kind": "ordinary", + "sourceSHA256": "9983c7e62875869883025f6766300ef4dff5ced54127ceed6bf1b53f8fa7e7fc", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reschedules an expired persisted disposition repair without duplicating the retry", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 94, + "sourceLines": [ + 7411, + 7498 + ], + "name": "atomically deduplicates concurrent disposition-repair reconciliation", + "kind": "ordinary", + "sourceSHA256": "09d0135c34796fa1ff1e3e0687180136e05ae6b599c8374bac6d22dd6c87005c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "atomically deduplicates concurrent disposition-repair reconciliation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 95, + "sourceLines": [ + 7500, + 7567 + ], + "name": "does not reset disposition repair for prose but does reset for durable source state", + "kind": "ordinary", + "sourceSHA256": "10b47d9628f7dc91163a7173cc253723a7a8eba8a0605779abc0874a2d27f15f", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reset disposition repair for prose but does reset for durable source state", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 96, + "sourceLines": [ + 7569, + 7667 + ], + "name": "escalates exhausted source-owner repair to the board without a substitute wake", + "kind": "ordinary", + "sourceSHA256": "5fe8d6031fefa017aa95bddd855b72d5b6c7ec1321d3296aa3a88ccac55b6516", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates exhausted source-owner repair to the board without a substitute wake", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 97, + "sourceLines": [ + 7669, + 7702 + ], + "name": "routes a non-invokable source owner to recovery without reassigning the source", + "kind": "ordinary", + "sourceSHA256": "37a6b49b53b95fa0e5b80039692dc6e5368ed68d1df97fe0924cb46f16afd995", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "routes a non-invokable source owner to recovery without reassigning the source", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 98, + "sourceLines": [ + 7704, + 7759 + ], + "name": "keeps a legacy agent-owned recovery action readable without scheduling another takeover wake", + "kind": "ordinary", + "sourceSHA256": "f0d2a209ca464787a91dbbeab57b0353690e29ba1c87c7dc59bc3c1dd962ea5c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps a legacy agent-owned recovery action readable without scheduling another takeover wake", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 99, + "sourceLines": [ + 7761, + 7800 + ], + "name": "does not consume a disposition-repair attempt when on-demand wakes are disabled", + "kind": "ordinary", + "sourceSHA256": "22272b5de0ba13fded27a8d63a7e95276107c2b1c7d18fe6cb8350da65f6de2c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not consume a disposition-repair attempt when on-demand wakes are disabled", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 100, + "sourceLines": [ + 7802, + 7866 + ], + "name": "preserves a $providerOutcome provider outcome when instruction cleanup times out ($cleanupAt, warning failure: $warningFails)", + "kind": "each", + "sourceSHA256": "010aaccad5b933b2393dfd977568c6cd48d0402f83843f39fa439571259ad09b", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "preserves a succeeded provider outcome when instruction cleanup times out (inner, warning failure: false)", + "status": "passed" + }, + { + "title": "preserves a failed provider outcome when instruction cleanup times out (inner, warning failure: false)", + "status": "passed" + }, + { + "title": "preserves a throws provider outcome when instruction cleanup times out (inner, warning failure: false)", + "status": "passed" + }, + { + "title": "preserves a succeeded provider outcome when instruction cleanup times out (outer, warning failure: false)", + "status": "passed" + }, + { + "title": "preserves a succeeded provider outcome when instruction cleanup times out (inner, warning failure: true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 101, + "sourceLines": [ + 7868, + 7894 + ], + "name": "persists a failed capture before heartbeat failure finalization settles an older receipt", + "kind": "ordinary", + "sourceSHA256": "266860ba691b77b9bae8be944a39d262a391b8586cca54fea4eadba7c5d5c4e7", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "persists a failed capture before heartbeat failure finalization settles an older receipt", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 102, + "sourceLines": [ + 7896, + 7933 + ], + "name": "stops controller renewal and releases execution controls when teardown deadline cleanup throws", + "kind": "ordinary", + "sourceSHA256": "ece3387e840470a0630ebe0ce742b928c682c156951cffbeeb105b8e9fb724d3", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "stops controller renewal and releases execution controls when teardown deadline cleanup throws", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 103, + "sourceLines": [ + 7935, + 7964 + ], + "name": "dispatches interrupted CLI input and cleans up early fixture exits (%s)", + "kind": "each", + "sourceSHA256": "b7ddd172af97353bad02da1e3d8a84110b29bb8f8e0b9d488b6cd7fbf62d504d", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "dispatches interrupted CLI input and cleans up early fixture exits (false)", + "status": "passed" + }, + { + "title": "dispatches interrupted CLI input and cleans up early fixture exits (true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 104, + "sourceLines": [ + 7966, + 8001 + ], + "name": "retries durable queue interruption after a promotion failure on a fresh service", + "kind": "ordinary", + "sourceSHA256": "158620c28bd59a41636dbc1ae3548e21c847fde2c933ef3dd7eaf30e0497fc99", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries durable queue interruption after a promotion failure on a fresh service", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 105, + "sourceLines": [ + 8003, + 8062 + ], + "name": "resumes only the authorized %s queue after an acknowledged legacy interrupt", + "kind": "each", + "sourceSHA256": "72cf5ce024c249d37082a9a177cc753a59aa2511637b09ce0945a2e139b364b2", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "resumes only the authorized pending queue after an acknowledged legacy interrupt", + "status": "passed" + }, + { + "title": "resumes only the authorized discarded queue after an acknowledged legacy interrupt", + "status": "passed" + }, + { + "title": "resumes only the authorized wrong queue queue after an acknowledged legacy interrupt", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 106, + "sourceLines": [ + 8064, + 8087 + ], + "name": "preserves deferred input on a clean Stop and adopts it once on the next explicit comment", + "kind": "ordinary", + "sourceSHA256": "e9ca73c55eff541ad217d7a2132c859d28370aca10222d91a238e67f804009c9", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves deferred input on a clean Stop and adopts it once on the next explicit comment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 107, + "sourceLines": [ + 8089, + 8229 + ], + "name": "does not adopt unrelated queued comments for a %s after Stop", + "kind": "each", + "sourceSHA256": "9a94087a55011d4665af4add2f044e453578ae2780bad18bc57cc84469152068", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "does not adopt unrelated queued comments for a dedicated deferred donor after Stop", + "status": "passed" + }, + { + "title": "does not adopt unrelated queued comments for a non-coalescing recipient after Stop", + "status": "passed" + }, + { + "title": "does not adopt unrelated queued comments for a persistent agent conversation after Stop", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 108, + "sourceLines": [ + 8231, + 8242 + ], + "name": "releases holds for real process bootstrap failures: %j", + "kind": "each", + "sourceSHA256": "c293ecbb5aa37aaff3315bedc0975230139f40bc2e1f850422298276ba346ac8", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "releases holds for real process bootstrap failures: {}", + "status": "passed" + }, + { + "title": "releases holds for real process bootstrap failures: {\"command\":\"/nonexistent-paperclip-bootstrap-command\"}", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 109, + "sourceLines": [ + 8244, + 8259 + ], + "name": "releases the dispatch reservation after a proven pre-provider adapter failure", + "kind": "ordinary", + "sourceSHA256": "3b9587d198ef6382322f211869c5d313125a43276473e0c2a697a9e06015b326", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "releases the dispatch reservation after a proven pre-provider adapter failure", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 110, + "sourceLines": [ + 8261, + 8411 + ], + "name": "fences adapter registration while an earlier no-owner %s waits to commit", + "kind": "each", + "sourceSHA256": "9c07ddfa64788a4e07953263e7c7e4f1df3f56f77cdf86375c2fb1398b948ab1", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "fences adapter registration while an earlier no-owner single Stop waits to commit", + "status": "passed" + }, + { + "title": "fences adapter registration while an earlier no-owner agent pause waits to commit", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 111, + "sourceLines": [ + 8413, + 8437 + ], + "name": "signals an embedded adapter and waits for its cleanup before returning Stop", + "kind": "ordinary", + "sourceSHA256": "49fa07b38bec00d970aa836a047332d974a7987c80940114655ec870bbae80f0", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "signals an embedded adapter and waits for its cleanup before returning Stop", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 112, + "sourceLines": [ + 8439, + 8494 + ], + "name": "preserves a copy-back obligation recorded after Stop reads the running adapter", + "kind": "ordinary", + "sourceSHA256": "ec5efacd35e3212dd7b583cfc994ac27aceb77f070d8d1aa059a6564ecf6d63b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a copy-back obligation recorded after Stop reads the running adapter", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 113, + "sourceLines": [ + 8496, + 8512 + ], + "name": "clears the detached warning when the run reports activity again", + "kind": "ordinary", + "sourceSHA256": "5af1ca124c4da0b1b9dd91c2acb35542e7cf4265d861937df22fb5d69331f6e0", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "clears the detached warning when the run reports activity again", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 114, + "sourceLines": [ + 8514, + 8530 + ], + "name": "tracks the first heartbeat with the agent role instead of adapter type", + "kind": "ordinary", + "sourceSHA256": "c85a3a31a94d15231273bbb93dcddb58116693ede279a87b995f84c72a9d0116", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "tracks the first heartbeat with the agent role instead of adapter type", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 115, + "sourceLines": [ + 8532, + 8553 + ], + "name": "preserves first-heartbeat telemetry after a timer interval claim", + "kind": "ordinary", + "sourceSHA256": "f73c786d73128179bd1910b7290c7df5292816f73c24b4a451a8e4a0cc133ab1", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves first-heartbeat telemetry after a timer interval claim", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 116, + "sourceLines": [ + 8555, + 8737 + ], + "name": "settles an owned $adapterType Stop before classifying its $mode", + "kind": "each", + "sourceSHA256": "f76884fc418b1e11c4ea80a147edee1cacfa31e615db869d8da2662c1f49f6f6", + "runtimeCaseCount": 16, + "runtimeCases": [ + { + "title": "settles an owned process Stop before classifying its signal", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its signal", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its graceful exit", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its graceful exit", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its adapter exception", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its adapter exception", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its termination error", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its termination error", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its cancellation write error", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its cancellation write error", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its graceful termination error", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its graceful termination error", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its graceful cancellation write error", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its graceful cancellation write error", + "status": "passed" + }, + { + "title": "settles an owned process Stop before classifying its late graceful cancellation write error", + "status": "passed" + }, + { + "title": "settles an owned codex_local Stop before classifying its late graceful cancellation write error", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 117, + "sourceLines": [ + 8739, + 8817 + ], + "name": "preserves exact restore sources through late process Stop metadata with an oversized result", + "kind": "ordinary", + "sourceSHA256": "6e46f2ee027ebf8e11f12999c7737cae4f67a1d22eabaf5808e4e6b2e7103787", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves exact restore sources through late process Stop metadata with an oversized result", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 118, + "sourceLines": [ + 8819, + 8868 + ], + "name": "keeps an independent process $mode distinct from Stop", + "kind": "each", + "sourceSHA256": "ff99ea7f55cd808b93319f68451adb652adc6657a64f9951e83b745361a22ae1", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "keeps an independent process clean exit distinct from Stop", + "status": "passed" + }, + { + "title": "keeps an independent process unrequested signal distinct from Stop", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 119, + "sourceLines": [ + 8870, + 8942 + ], + "name": "retries a settled failed Stop while its exact active process remains alive", + "kind": "ordinary", + "sourceSHA256": "24bae060afb4833b5e032a6af1532daec38847fd2134768eded58b382e2a6c7b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries a settled failed Stop while its exact active process remains alive", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 120, + "sourceLines": [ + 8944, + 8968 + ], + "name": "retains the exact owned child when process Stop cannot verify termination", + "kind": "ordinary", + "sourceSHA256": "ca3ec5f52b31aeb766fd5d379522bb99bee03092e42642cc49bdfec666781868", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retains the exact owned child when process Stop cannot verify termination", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 121, + "sourceLines": [ + 8970, + 8999 + ], + "name": "terminates the in-memory process before persisting cancellation status", + "kind": "ordinary", + "sourceSHA256": "d6ffb79af51d5e4da082719a4134ccb84c8065497d1c7dfbb1eec4e1428bf66f", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "terminates the in-memory process before persisting cancellation status", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 122, + "sourceLines": [ + 9001, + 9029 + ], + "name": "uses a bounded per-cancel process grace for an external-chat successor", + "kind": "ordinary", + "sourceSHA256": "f0e56383e86345de3ba6cba9423fe57801bb96835e4d492a127d35b795185ebe", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "uses a bounded per-cancel process grace for an external-chat successor", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 123, + "sourceLines": [ + 9031, + 9068 + ], + "name": "does not overwrite a run that finishes while cancellation is stopping its process", + "kind": "ordinary", + "sourceSHA256": "549cf1b05874fccf0b55a3be17651b265f52f4cd3f08c909cdb2b707b6b773fa", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not overwrite a run that finishes while cancellation is stopping its process", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 124, + "sourceLines": [ + 9070, + 9082 + ], + "name": "does not signal an unowned persisted process during manual cancellation", + "kind": "ordinary", + "sourceSHA256": "3609249f588354803d814b322ea68de17cd6b1b04d44fccc4a87c0b8dda0f4f7", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not signal an unowned persisted process during manual cancellation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 125, + "sourceLines": [ + 9084, + 9096 + ], + "name": "does not signal an unowned persisted process during graceful shutdown", + "kind": "ordinary", + "sourceSHA256": "a7cdd1de3f24a7545d99949e98993ce362e133c9ceb57df208a295f1e58206ab", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not signal an unowned persisted process during graceful shutdown", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 126, + "sourceLines": [ + 9098, + 9110 + ], + "name": "does not signal an unowned persisted process during agent-wide cancellation", + "kind": "ordinary", + "sourceSHA256": "5c54f27e9af63501ac2c382b2ddafb6199f6bf50bb7d3ef811232b8178d8a167", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not signal an unowned persisted process during agent-wide cancellation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 127, + "sourceLines": [ + 9112, + 9131 + ], + "name": "signals and clears an owned process during graceful shutdown", + "kind": "ordinary", + "sourceSHA256": "b5f8312936a0e0971399f6219adfdc5099ab8e8a5c097a17c6c19baae94be0e2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "signals and clears an owned process during graceful shutdown", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 128, + "sourceLines": [ + 9133, + 9152 + ], + "name": "signals and clears an owned process during agent-wide cancellation", + "kind": "ordinary", + "sourceSHA256": "5e73e11a269f8c1a6374d5c49b9e1b0fc6ff6cdc55a95dc2f81440af6bac03cb", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "signals and clears an owned process during agent-wide cancellation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 129, + "sourceLines": [ + 9154, + 9169 + ], + "name": "records manual cancellation stop metadata", + "kind": "ordinary", + "sourceSHA256": "5c9e6298109e48dc4ee93e2e40889628bf11cbcdc989720ed9d202cfa552579b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "records manual cancellation stop metadata", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 130, + "sourceLines": [ + 9171, + 9221 + ], + "name": "records operator interrupt cancellation metadata without changing terminal status", + "kind": "ordinary", + "sourceSHA256": "7ff37a68f321a6cce8e71918c85663b1c3d7c1c7100525b896d3ff4fde07b527", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "records operator interrupt cancellation metadata without changing terminal status", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 131, + "sourceLines": [ + 9223, + 9283 + ], + "name": "dispatches assigned todo work with no prior run as a normal assignment wake", + "kind": "ordinary", + "sourceSHA256": "7e3b07c4e6bfa5799680ec5512042b4cafe29650fa1c187bfa781854958ef858", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "dispatches assigned todo work with no prior run as a normal assignment wake", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 132, + "sourceLines": [ + 9285, + 9313 + ], + "name": "leaves the onboarding first task idle until the user comments", + "kind": "ordinary", + "sourceSHA256": "553cb96e8d4712dd0c52138c701e10576fa0aadd0f77df273ec64620665574f9", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "leaves the onboarding first task idle until the user comments", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 133, + "sourceLines": [ + 9315, + 9356 + ], + "name": "keeps the onboarding first task idle while the seeded opening card is unanswered", + "kind": "ordinary", + "sourceSHA256": "84c2761d59534841f3e8d4160befed97f88373c3521bea9b464e8c032045dc18", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps the onboarding first task idle while the seeded opening card is unanswered", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 134, + "sourceLines": [ + 9358, + 9410 + ], + "name": "dispatches the onboarding first task once the user answered the opening card", + "kind": "ordinary", + "sourceSHA256": "a01f6e8e1a183f8ab1253240b51b958b4b5380872a2deffe170f5f836e1748b1", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "dispatches the onboarding first task once the user answered the opening card", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 135, + "sourceLines": [ + 9412, + 9438 + ], + "name": "dispatches the onboarding first task once a user comment exists", + "kind": "ordinary", + "sourceSHA256": "4d126e36c1ae7bcd74358d53241599fe94673ad14a257a08d3df260cbe5cc164", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "dispatches the onboarding first task once a user comment exists", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 136, + "sourceLines": [ + 9440, + 9466 + ], + "name": "does not duplicate initial assigned todo dispatch when a queued wake already exists", + "kind": "ordinary", + "sourceSHA256": "660a9a6d0619b2a4ecd2a25fdd1430544fbc28fcd254794acf4a29b492c1ef41", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not duplicate initial assigned todo dispatch when a queued wake already exists", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 137, + "sourceLines": [ + 9468, + 9549 + ], + "name": "does not bypass a %s durable inbound-chat wake intent with generic assignment recovery", + "kind": "each", + "sourceSHA256": "5106c3a3b5be09b87d9e6ad973ec12587c573b3a8b894c0338348b3e1933b2cb", + "runtimeCaseCount": 4, + "runtimeCases": [ + { + "title": "does not bypass a preparing durable inbound-chat wake intent with generic assignment recovery", + "status": "passed" + }, + { + "title": "does not bypass a issued durable inbound-chat wake intent with generic assignment recovery", + "status": "passed" + }, + { + "title": "does not bypass a processing durable inbound-chat wake intent with generic assignment recovery", + "status": "passed" + }, + { + "title": "does not bypass a failed durable inbound-chat wake intent with generic assignment recovery", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 138, + "sourceLines": [ + 9551, + 9644 + ], + "name": "creates a board recovery action for budget-blocked assigned work and continues the sweep", + "kind": "ordinary", + "sourceSHA256": "c8186df54e720a87addf5a01427c30011efa6e1290520534ee1a6f5c8176009e", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "creates a board recovery action for budget-blocked assigned work and continues the sweep", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 139, + "sourceLines": [ + 9646, + 9700 + ], + "name": "routes paused assigned work to the board without waking available executives", + "kind": "ordinary", + "sourceSHA256": "137c01a8dc7a5d2707e6bcf4c212922b4e8549766f986a9838066c934933eddd", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "routes paused assigned work to the board without waking available executives", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 140, + "sourceLines": [ + 9702, + 9732 + ], + "name": "re-enqueues assigned todo work when the last issue run died and no wake remains", + "kind": "ordinary", + "sourceSHA256": "18ec73a20a0bd03f5fbd9fd55a172045638aec9adca1c0751d83ea27b5825dfc", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "re-enqueues assigned todo work when the last issue run died and no wake remains", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 141, + "sourceLines": [ + 9734, + 9786 + ], + "name": "re-enqueues handed-back todo work when its resolving run succeeded but the wake was lost", + "kind": "ordinary", + "sourceSHA256": "d8af6ae61dda1766f3e9c74d077f44bd9b60f956d0919206a9ce9d3358f52d24", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "re-enqueues handed-back todo work when its resolving run succeeded but the wake was lost", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 142, + "sourceLines": [ + 9788, + 9850 + ], + "name": "does not let an active chat conversation suppress stranded execution-review participant recovery", + "kind": "ordinary", + "sourceSHA256": "99b754ff0504029d9a52f97ea0b11484c3785660286b371a025f3bb6e85e41be", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not let an active chat conversation suppress stranded execution-review participant recovery", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 143, + "sourceLines": [ + 9852, + 9905 + ], + "name": "escalates an execution-review participant whose interrupted run has spent its transient retry budget", + "kind": "ordinary", + "sourceSHA256": "998bdf38b47726132c1ba879657c65f200a660a2541a296e0c4fe79dda971cd8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates an execution-review participant whose interrupted run has spent its transient retry budget", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 144, + "sourceLines": [ + 9907, + 9972 + ], + "name": "does not block an issue whose review participant spent its retry budget while another agent is still working it", + "kind": "ordinary", + "sourceSHA256": "3089b7d050a1f81ad25abaa1fa7531f817a869c63478b4fb31f1375a28c04864", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not block an issue whose review participant spent its retry budget while another agent is still working it", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 145, + "sourceLines": [ + 9974, + 10045 + ], + "name": "re-enqueues a stranded execution-review participant when another agent has the latest issue run", + "kind": "ordinary", + "sourceSHA256": "b21cfbda2bdb9f8022b06ebf423895f4ad8d50aa30edc82aa422681905e2c5df", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "re-enqueues a stranded execution-review participant when another agent has the latest issue run", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 146, + "sourceLines": [ + 10047, + 10098 + ], + "name": "re-enqueues a stranded execution-review participant when another agent has a queued issue wake", + "kind": "ordinary", + "sourceSHA256": "56b2dc821acf3615bde7137e04cf52a28ab270f5e362bba0cb3f56e7318c9ca0", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "re-enqueues a stranded execution-review participant when another agent has a queued issue wake", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 147, + "sourceLines": [ + 10100, + 10140 + ], + "name": "retries a pending execution-review participant when another agent has an active issue run", + "kind": "ordinary", + "sourceSHA256": "4b5a41db39da0047fb37fa506953c46f21649eaa8f252a651ec82415a21d0c4b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries a pending execution-review participant when another agent has an active issue run", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 148, + "sourceLines": [ + 10142, + 10166 + ], + "name": "does not immediately recover a generic on-demand run used for an in-review agent API update", + "kind": "ordinary", + "sourceSHA256": "f1a0d452fbe2c0f02d8be96e82107ca57245bfb4b1e66bdfed9a08199f7c0e31", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not immediately recover a generic on-demand run used for an in-review agent API update", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 149, + "sourceLines": [ + 10168, + 10271 + ], + "name": "retries a pending execution-review participant once before blocking with a recovery action", + "kind": "ordinary", + "sourceSHA256": "76c7289981ac8f7afbd7f61b79f66b743a07be209e31e269c1e5c2a463e0d090", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retries a pending execution-review participant once before blocking with a recovery action", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 150, + "sourceLines": [ + 10273, + 10358 + ], + "name": "blocks failed execution-review recovery under the reviewer when the source assignee differs", + "kind": "ordinary", + "sourceSHA256": "891e2835ca9f8ebd12037049186254a4db8d872810193ba694b578262f45e5b2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks failed execution-review recovery under the reviewer when the source assignee differs", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 151, + "sourceLines": [ + 10360, + 10409 + ], + "name": "re-enqueues stranded in-progress work after a %s/%s run before escalating", + "kind": "each", + "sourceSHA256": "b9caffcd9e2873dfb34bb826aab684c1a9782434c5819657d94ffad5b760b1d7", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "re-enqueues stranded in-progress work after a failed/adapter_failed run before escalating", + "status": "passed" + }, + { + "title": "re-enqueues stranded in-progress work after a failed/process_lost run before escalating", + "status": "passed" + }, + { + "title": "re-enqueues stranded in-progress work after a timed_out/adapter_timed_out run before escalating", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 152, + "sourceLines": [ + 10411, + 10439 + ], + "name": "skips stranded recovery when a pending %s interaction exists", + "kind": "each", + "sourceSHA256": "112099b7e1abdbc7e071e842c2177813d5db73351258bda5a1624a61457e255a", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "skips stranded recovery when a pending wake_assignee interaction exists", + "status": "passed" + }, + { + "title": "skips stranded recovery when a pending wake_assignee_on_accept interaction exists", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 153, + "sourceLines": [ + 10472, + 10493 + ], + "name": "recovers an answered question with its interaction-specific continuation context", + "kind": "ordinary", + "sourceSHA256": "d74456861e1cd9d9915efac303d27b1400eacbc5b8d574ed24d298acb484884c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers an answered question with its interaction-specific continuation context", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 154, + "sourceLines": [ + 10495, + 10576 + ], + "name": "does not requeue an answered interaction after native recovery is board-owned", + "kind": "ordinary", + "sourceSHA256": "4c74fb45a4fc9a5f2a5d3262b534c0164cc14d4435aae63f14087e3acadaccdd", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not requeue an answered interaction after native recovery is board-owned", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 155, + "sourceLines": [ + 10578, + 10655 + ], + "name": "counts five historical review-park cancellations against the upgraded disposition-repair ceiling", + "kind": "ordinary", + "sourceSHA256": "d0878b0c90103457bcbcaaaa67b63ad80a5d9049d15e806ac91cd76035792b3b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "counts five historical review-park cancellations against the upgraded disposition-repair ceiling", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 156, + "sourceLines": [ + 10657, + 10694 + ], + "name": "skips accepted interaction recovery after its continuation succeeds", + "kind": "ordinary", + "sourceSHA256": "a4e1b6d7925f0b36d6e439cc6939359711f4e278cf6d04ad202fd37d918b4acd", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "skips accepted interaction recovery after its continuation succeeds", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 157, + "sourceLines": [ + 10696, + 10740 + ], + "name": "requeues accepted interaction continuations even when a later successful run is unrelated", + "kind": "ordinary", + "sourceSHA256": "5761d5d6c47e40b8e0aa5e7c20e84f2937a8d5bfe964fa7703cbd44a9e7953a5", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "requeues accepted interaction continuations even when a later successful run is unrelated", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 158, + "sourceLines": [ + 10747, + 10802 + ], + "name": "recovers a plan approval whose accept-time continuation wake enqueue was silently dropped", + "kind": "ordinary", + "sourceSHA256": "7bf06a9406817a958375244f95dfaa12524263a918bc654d68dc68055612141f", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers a plan approval whose accept-time continuation wake enqueue was silently dropped", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 159, + "sourceLines": [ + 10808, + 10898 + ], + "name": "promotes a scheduled plan-approval continuation retry after a simulated server restart", + "kind": "ordinary", + "sourceSHA256": "f7edcc0a5c233f6cd4bd79dbe98426ff402ba66f49cfccf0b66e4d6e80270bae", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "promotes a scheduled plan-approval continuation retry after a simulated server restart", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 160, + "sourceLines": [ + 10900, + 10938 + ], + "name": "still re-enqueues stranded assigned todo recovery when an old queued wake exists", + "kind": "ordinary", + "sourceSHA256": "11ece7d43c75a00ffe3e795fafb958f5e5b6216bfab9989f034ad8d36cf84a7d", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "still re-enqueues stranded assigned todo recovery when an old queued wake exists", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 161, + "sourceLines": [ + 10940, + 11012 + ], + "name": "blocks assigned todo work after the one automatic dispatch recovery was already used", + "kind": "ordinary", + "sourceSHA256": "474e6ec20dfc5c2a4513059e4ed18b7c29ebbf32df2aed4cac866de524f909fa", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks assigned todo work after the one automatic dispatch recovery was already used", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 162, + "sourceLines": [ + 11014, + 11099 + ], + "name": "blocks an already stranded recovery issue without creating a recovery child", + "kind": "ordinary", + "sourceSHA256": "7fd50c4c3c1d8d54257115c2614af8816410945ee9109f20f121e87b46b9e66c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks an already stranded recovery issue without creating a recovery child", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 163, + "sourceLines": [ + 11101, + 11195 + ], + "name": "assigns open unassigned blockers back to their creator agent", + "kind": "ordinary", + "sourceSHA256": "6ab0cda88531aa3a11f245900f95b63f2332cf5a39482953da85e32a5b0df043", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "assigns open unassigned blockers back to their creator agent", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 164, + "sourceLines": [ + 11197, + 11228 + ], + "name": "does not run generic continuation recovery for a paused unfinished session goal", + "kind": "ordinary", + "sourceSHA256": "7ad3b12d99299d8f27932ab2c8e857bae974778c3726d272b329069abd80de5a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not run generic continuation recovery for a paused unfinished session goal", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 165, + "sourceLines": [ + 11230, + 11266 + ], + "name": "does not continue seeded in-progress work that has no run linkage", + "kind": "ordinary", + "sourceSHA256": "fdc7295de604df689e01a6aee52c0b0826fc70fea4dcc5be3bd6f7c6ef2bbfc2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not continue seeded in-progress work that has no run linkage", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 166, + "sourceLines": [ + 11268, + 11359 + ], + "name": "enqueues one bounded plan-only continuation with legacy productivity review present: %s", + "kind": "each", + "sourceSHA256": "4717194c9b41525776332a8fc0443ac6c50bf5445a6088f83712b1f518e0f14a", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "enqueues one bounded plan-only continuation with legacy productivity review present: false", + "status": "passed" + }, + { + "title": "enqueues one bounded plan-only continuation with legacy productivity review present: true", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 167, + "sourceLines": [ + 11361, + 11433 + ], + "name": "treats a plan document update as progress and does not enqueue liveness continuation", + "kind": "ordinary", + "sourceSHA256": "601ecf14fd24d893297e7beef3e748ad47fee82c194edad155769c2d7c273a77", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "treats a plan document update as progress and does not enqueue liveness continuation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 168, + "sourceLines": [ + 11434, + 11482 + ], + "name": "blocks stranded in-progress work after the continuation retry was already used", + "kind": "ordinary", + "sourceSHA256": "a053d0aa981e563533f9ac96a3d05eabf4ef3cf1cdca67c54a91e0f28dc1d678", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks stranded in-progress work after the continuation retry was already used", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 169, + "sourceLines": [ + 11484, + 11524 + ], + "name": "redacts error-code-only stranded recovery failures in issue copy", + "kind": "ordinary", + "sourceSHA256": "96bcff68841be1e2bbca83fe4c7157b24170d4aac3a20e60cae75bdd9af5fae7", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "redacts error-code-only stranded recovery failures in issue copy", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 170, + "sourceLines": [ + 11526, + 11557 + ], + "name": "keeps retrying transient adapter_failed continuation runs before the cap", + "kind": "ordinary", + "sourceSHA256": "5bab0572cabfad9a0eac00aa82ace50dccbe5ed56efb5bcb9ac3506bebbc5cc8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps retrying transient adapter_failed continuation runs before the cap", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 171, + "sourceLines": [ + 11559, + 11624 + ], + "name": "escalates after repeated adapter_failed continuation retries with the cause in the comment", + "kind": "ordinary", + "sourceSHA256": "4a7b233946207541e00dd79e4b8949320c629306eccedaee3b03d160f50f1d2a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates after repeated adapter_failed continuation retries with the cause in the comment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 172, + "sourceLines": [ + 11626, + 11651 + ], + "name": "counts different failure causes against the same incident budget", + "kind": "ordinary", + "sourceSHA256": "093257b06ebc554dfe76615a66d7190fd5962a403188e0d0ae5854905b6d2389", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "counts different failure causes against the same incident budget", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 173, + "sourceLines": [ + 11653, + 11696 + ], + "name": "escalates non-retryable continuation failures immediately without enqueuing another retry", + "kind": "ordinary", + "sourceSHA256": "5d873365c1cac7c1070c92256e215ba43b540cbafc786a16254a24a3c26cb562", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates non-retryable continuation failures immediately without enqueuing another retry", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 174, + "sourceLines": [ + 11698, + 11727 + ], + "name": "does not recover a finished native chat while its response publication is pending", + "kind": "ordinary", + "sourceSHA256": "344d528eb00ce0c06aef33fff94a017b7916a075541b9b0b47f1b29d478ae771", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not recover a finished native chat while its response publication is pending", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 175, + "sourceLines": [ + 11729, + 11764 + ], + "name": "does not turn a pre-adapter setup failure into a duplicate continuation run", + "kind": "ordinary", + "sourceSHA256": "b2cb8affc130ea83c2e06f0b73ef3a0859369ab9d78031d72139adf07d652003", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not turn a pre-adapter setup failure into a duplicate continuation run", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 176, + "sourceLines": [ + 11766, + 11794 + ], + "name": "leaves a successful external-chat turn idle while its conversation is %s", + "kind": "each", + "sourceSHA256": "f4fbc3040240ee972fae83c96109e3385b63494dbf3ef05d84bdf955943198ef", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "leaves a successful external-chat turn idle while its conversation is active", + "status": "passed" + }, + { + "title": "leaves a successful external-chat turn idle while its conversation is waiting", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 177, + "sourceLines": [ + 12013, + 12039 + ], + "name": "does not automatically resume a source after its committed authorized /%s receipt", + "kind": "each", + "sourceSHA256": "232101fb98a200dd87f1681850134345981e7a4e54d85ac6a1cf04d8d1b47d97", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "does not automatically resume a source after its committed authorized /close receipt", + "status": "passed" + }, + { + "title": "does not automatically resume a source after its committed authorized /new receipt", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 178, + "sourceLines": [ + 12107, + 12122 + ], + "name": "recognizes the real generic control delivery event kind %s", + "kind": "each", + "sourceSHA256": "6f48d52381f3c419e453fd29532d68b9355afc19c122d177ad359128d8e3bdfe", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "recognizes the real generic control delivery event kind message", + "status": "passed" + }, + { + "title": "recognizes the real generic control delivery event kind direct_message", + "status": "passed" + }, + { + "title": "recognizes the real generic control delivery event kind mention", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 179, + "sourceLines": [ + 12124, + 12176 + ], + "name": "does not infer chat close authority from a mismatched %s receipt", + "kind": "each", + "sourceSHA256": "9102bc31b5f9471e9ec57e623b56ab00b64d2125cc7b792fbf843d8126d3d71d", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "does not infer chat close authority from a mismatched authorization receipt", + "status": "passed" + }, + { + "title": "does not infer chat close authority from a mismatched outbound receipt", + "status": "passed" + }, + { + "title": "does not infer chat close authority from a mismatched principal receipt", + "status": "passed" + }, + { + "title": "does not infer chat close authority from a mismatched command receipt", + "status": "passed" + }, + { + "title": "does not infer chat close authority from a mismatched generation receipt", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 180, + "sourceLines": [ + 12178, + 12240 + ], + "name": "orders chat close against original admitted sources, not a later failed retry action", + "kind": "ordinary", + "sourceSHA256": "e197924bf94df32c9c27cadef8696f4d6daf8cb410b1a847feacfc9f99073ade", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "orders chat close against original admitted sources, not a later failed retry action", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 181, + "sourceLines": [ + 12242, + 12330 + ], + "name": "preserves a genuinely later coalesced inbound source instead of selecting only the old canonical message", + "kind": "ordinary", + "sourceSHA256": "c3941e0a3724021cd033b38897131bf0bb258c1a686cd517f3bdb6ecdadbf2f8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a genuinely later coalesced inbound source instead of selecting only the old canonical message", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 182, + "sourceLines": [ + 12332, + 12366 + ], + "name": "follows exact automatic ancestry despite rewritten context and stops only that unused child", + "kind": "ordinary", + "sourceSHA256": "7bb7c3737f53b2ef895a8c831a887e329b8ad53d12aff44ca9380b9edbd06c2a", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "follows exact automatic ancestry despite rewritten context and stops only that unused child", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 183, + "sourceLines": [ + 12368, + 12432 + ], + "name": "honors close committed at the %s boundary without a provider call", + "kind": "each", + "sourceSHA256": "bd807872585ac813343ce7a59ca290d6e4b07fe92933dc5987f7028220867b60", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "honors close committed at the claim boundary without a provider call", + "status": "passed" + }, + { + "title": "honors close committed at the dispatch boundary without a provider call", + "status": "passed" + }, + { + "title": "honors close committed at the queued_comments boundary without a provider call", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 184, + "sourceLines": [ + 12434, + 12526 + ], + "name": "rechecks admission after transient database contention: %s", + "kind": "each", + "sourceSHA256": "d1105eda2d5b54b7891e9b82baf0c5d18126f81285942bc60cf2ee006f3fd0dc", + "runtimeCaseCount": 6, + "runtimeCases": [ + { + "title": "rechecks admission after transient database contention: issue", + "status": "passed" + }, + { + "title": "rechecks admission after transient database contention: wake", + "status": "passed" + }, + { + "title": "rechecks admission after transient database contention: run", + "status": "passed" + }, + { + "title": "rechecks admission after transient database contention: native", + "status": "passed" + }, + { + "title": "rechecks admission after transient database contention: close", + "status": "passed" + }, + { + "title": "rechecks admission after transient database contention: cancel", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 185, + "sourceLines": [ + 12528, + 12580 + ], + "name": "defers unresolved automatic ancestry at claim and records a distinct nonretrying failure after claim", + "kind": "ordinary", + "sourceSHA256": "1e8fd9865e1642d16de7315d4c0521f1949170c3083b10251f5114b60dd62238", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "defers unresolved automatic ancestry at claim and records a distinct nonretrying failure after claim", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 186, + "sourceLines": [ + 12582, + 12624 + ], + "name": "does not confuse inherited warm-runner metadata with dispatch of the new child", + "kind": "ordinary", + "sourceSHA256": "faa34bf56f93f9d31571180c62ac192a91d4bce27ab2b2a790e6ba4c036a48de", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not confuse inherited warm-runner metadata with dispatch of the new child", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 187, + "sourceLines": [ + 12626, + 12699 + ], + "name": "keeps the claimed admission marker through legacy preparation: %s", + "kind": "each", + "sourceSHA256": "04d5935192e31b37140a173dc7438dce8fea3dd65aea3720b9814140ba0e03e9", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "keeps the claimed admission marker through legacy preparation: intact", + "status": "passed" + }, + { + "title": "keeps the claimed admission marker through legacy preparation: lost", + "status": "passed" + }, + { + "title": "keeps the claimed admission marker through legacy preparation: copied", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 188, + "sourceLines": [ + 12757, + 12797 + ], + "name": "rechecks only unadmitted native bootstrap recovery after a committed close: %s", + "kind": "each", + "sourceSHA256": "0616760b3148ce7108dad2621207829c47a39acf4859c436e44dd354d93e8f58", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "rechecks only unadmitted native bootstrap recovery after a committed close: required", + "status": "passed" + }, + { + "title": "rechecks only unadmitted native bootstrap recovery after a committed close: admitted", + "status": "passed" + }, + { + "title": "rechecks only unadmitted native bootstrap recovery after a committed close: historical", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 189, + "sourceLines": [ + 12799, + 12855 + ], + "name": "does not acquire a new chat gate lock for protected native recovery: %s", + "kind": "each", + "sourceSHA256": "ba9697a09f56f477b3d2b3801f13172646195155629a3503adbb74ee5d707215", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "does not acquire a new chat gate lock for protected native recovery: admitted", + "status": "passed" + }, + { + "title": "does not acquire a new chat gate lock for protected native recovery: historical", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 190, + "sourceLines": [ + 12857, + 12909 + ], + "name": "native preparation preserves only the current run's reserved admission evidence: %s", + "kind": "each", + "sourceSHA256": "7e0c4ea2563346d130769cb6724ea5c40f971ba07eac292b7c1d621def4caf5f", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "native preparation preserves only the current run's reserved admission evidence: required", + "status": "passed" + }, + { + "title": "native preparation preserves only the current run's reserved admission evidence: historical", + "status": "passed" + }, + { + "title": "native preparation preserves only the current run's reserved admission evidence: invalid", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 191, + "sourceLines": [ + 12911, + 12972 + ], + "name": "retains closed chat ancestry and current retry authority: %s", + "kind": "each", + "sourceSHA256": "a72ba392795e8dd09e2b1d92cf6c03c6825b09938d854de2a138bf416cadfc2e", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "retains closed chat ancestry and current retry authority: native", + "status": "passed" + }, + { + "title": "retains closed chat ancestry and current retry authority: legacy_pre_provider", + "status": "passed" + }, + { + "title": "retains closed chat ancestry and current retry authority: legacy_unknown", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 192, + "sourceLines": [ + 12974, + 12999 + ], + "name": "defers exhausted automatic ancestry without claiming a close or launching work", + "kind": "ordinary", + "sourceSHA256": "f3af55715586941940dae74b00642714402ec1ba9c27e03f94e2d35cb9bf41a1", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "defers exhausted automatic ancestry without claiming a close or launching work", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 193, + "sourceLines": [ + 13001, + 13135 + ], + "name": "binds a native status continuation to its applied decision and exact delivered effect (mismatch=%s)", + "kind": "each", + "sourceSHA256": "60a5491931ea965e9f49cbb4d6c4d1151155779e0eca80471d9d122e8108fe1f", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "binds a native status continuation to its applied decision and exact delivered effect (mismatch=false)", + "status": "passed" + }, + { + "title": "binds a native status continuation to its applied decision and exact delivered effect (mismatch=true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 194, + "sourceLines": [ + 13137, + 13172 + ], + "name": "allows fresh Board work and coalesced user causes after a committed chat close", + "kind": "ordinary", + "sourceSHA256": "a77e829dec1808fb5b22f2868ed26fed40535b88702f2b1c9a25eb3c428a6b7c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "allows fresh Board work and coalesced user causes after a committed chat close", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 195, + "sourceLines": [ + 13174, + 13267 + ], + "name": "defers a contended chat close proof without starving another queued Board task", + "kind": "ordinary", + "sourceSHA256": "b1eb96941888fa2691181c5bf661641564d1de2664e7aafbbbf546d953114327", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "defers a contended chat close proof without starving another queued Board task", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 196, + "sourceLines": [ + 13269, + 13310 + ], + "name": "requires the exact native close target generation (mismatch=%s)", + "kind": "each", + "sourceSHA256": "05178872fd9d55806b052e0b3ebe0d730c477cf84258bee09607b5f27cad2360", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "requires the exact native close target generation (mismatch=false)", + "status": "passed" + }, + { + "title": "requires the exact native close target generation (mismatch=true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 197, + "sourceLines": [ + 13312, + 13344 + ], + "name": "does not let an old agent's closed-chat stop suppress reassigned ordinary work", + "kind": "ordinary", + "sourceSHA256": "6c2799344925c83f85ba92025c768d40ee4146fe7fb3b94e41bc0d81139d9843", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not let an old agent's closed-chat stop suppress reassigned ordinary work", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 198, + "sourceLines": [ + 13346, + 13375 + ], + "name": "recovers productive chat work after its conversation is completed", + "kind": "ordinary", + "sourceSHA256": "09f24b72a6b82e67517194a481d548b3d4f23851080710b04ffafdea11791421", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers productive chat work after its conversation is completed", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 199, + "sourceLines": [ + 13377, + 13406 + ], + "name": "recovers a non-chat productive run even when its issue has an active chat conversation", + "kind": "ordinary", + "sourceSHA256": "64466e41505f28940ea8d1733de6cecd9c45cf55c8fc89b78c4efaf9ad4beab2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers a non-chat productive run even when its issue has an active chat conversation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 200, + "sourceLines": [ + 13408, + 13427 + ], + "name": "lets a child waiting for the shared workspace run before recovering its lead", + "kind": "ordinary", + "sourceSHA256": "df1595325cb7581588a7fe5705920c92c9dfe1111747310b2b4aa865fe31da20", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "lets a child waiting for the shared workspace run before recovering its lead", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 201, + "sourceLines": [ + 13429, + 13444 + ], + "name": "resumes a shared-workspace lead when its child needs review", + "kind": "ordinary", + "sourceSHA256": "2bef91113729decb240906c0c49286f3f279aadbd65c64abc00740ba26958071", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "resumes a shared-workspace lead when its child needs review", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 202, + "sourceLines": [ + 13446, + 13471 + ], + "name": "repairs missing disposition independently of the productive diagnostic label", + "kind": "ordinary", + "sourceSHA256": "b9a65b8f02be55a40b3986244d684176148dec646bc47cf82575eea8736bac60", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "repairs missing disposition independently of the productive diagnostic label", + "status": "passed" + } + ], + "decision": "retire" + }, + { + "number": 203, + "sourceLines": [ + 13473, + 13504 + ], + "name": "reuses the raced stranded recovery issue when duplicate active recovery creation conflicts", + "kind": "ordinary", + "sourceSHA256": "b3549fe02e26a6a4da9677316fd63a18071d61e7345db0ff924e403b86a4f722", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "reuses the raced stranded recovery issue when duplicate active recovery creation conflicts", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 204, + "sourceLines": [ + 13506, + 13572 + ], + "name": "blocks stranded recovery issues in place instead of creating nested recovery issues", + "kind": "ordinary", + "sourceSHA256": "f2edf1f89a009ba115b9ccd579aa1ed1b0133556c2920a60e6344e38e876e125", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks stranded recovery issues in place instead of creating nested recovery issues", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 205, + "sourceLines": [ + 13574, + 13666 + ], + "name": "keeps repeated recovery failures on the same canonical recovery issue", + "kind": "ordinary", + "sourceSHA256": "1fd61bdc05c24375a0e8475f7922fc7104bbf04a0b2f6dec6fc3039bdb398b61", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps repeated recovery failures on the same canonical recovery issue", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 206, + "sourceLines": [ + 13668, + 13708 + ], + "name": "does not escalate paused-tree recovery when the automatic continuation retry was cancelled by the hold", + "kind": "ordinary", + "sourceSHA256": "d2b39f6975cb4edd260d37eba0d90ae5360df14fa6f6f93c6bfc00401691e161", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not escalate paused-tree recovery when the automatic continuation retry was cancelled by the hold", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 207, + "sourceLines": [ + 13710, + 13749 + ], + "name": "re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "kind": "ordinary", + "sourceSHA256": "6c44d1f55304e113cdc5a427499cfd148de628d052457d8bfacf1a185ba1d6ac", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "re-enqueues recovery when the latest in-progress continuation made progress but left no live path", + "status": "passed" + } + ], + "decision": "retire" + }, + { + "number": 208, + "sourceLines": [ + 13751, + 13787 + ], + "name": "does not accept unmanaged local-background wait evidence as a live continuation path", + "kind": "ordinary", + "sourceSHA256": "fe34a43b1ae834fbcdabbd94c1859ebc0a0007902aef10442207183d9aab248f", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not accept unmanaged local-background wait evidence as a live continuation path", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 209, + "sourceLines": [ + 13789, + 13830 + ], + "name": "escalates repeated unmanaged local-background waits instead of retrying forever", + "kind": "ordinary", + "sourceSHA256": "1959524d6cf052a7b06c107c958ca1c7b8b124f87926b0f85166c9f5eb363fa8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "escalates repeated unmanaged local-background waits instead of retrying forever", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 210, + "sourceLines": [ + 13832, + 13860 + ], + "name": "preserves a persisted issue monitor as the durable external-wait path", + "kind": "ordinary", + "sourceSHA256": "769ab8c93966108c19d6eecf6bdf249423dc78efd0730f769596cfcf009b767c", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a persisted issue monitor as the durable external-wait path", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 211, + "sourceLines": [ + 13862, + 13902 + ], + "name": "preserves a delegated blocker edge as the durable external-wait path", + "kind": "ordinary", + "sourceSHA256": "6f373ce024b77747957d430b267ef41cb875897943fee3bd3b0ee625c3af72cf", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a delegated blocker edge as the durable external-wait path", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 212, + "sourceLines": [ + 13904, + 13939 + ], + "name": "blocks stranded in-progress work after a productive continuation retry was already used", + "kind": "ordinary", + "sourceSHA256": "bb4eddebb03468043593274e6356c426d1ffc978c8751269876eb4f82782c1bf", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "blocks stranded in-progress work after a productive continuation retry was already used", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 213, + "sourceLines": [ + 14068, + 14116 + ], + "name": "commits a native passive Board response without manufacturing immediate work (%s)", + "kind": "each", + "sourceSHA256": "adda6caaa7ac30d59b1d64a8e889ce2b0d36d10c796f16dca3a36ce20e239e1b", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "commits a native passive Board response without manufacturing immediate work (issue_commented)", + "status": "passed" + }, + { + "title": "commits a native passive Board response without manufacturing immediate work (issue_reopened_via_comment)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 214, + "sourceLines": [ + 14118, + 14127 + ], + "name": "rejects an unfinished native response wait while the finish report is still correctable", + "kind": "ordinary", + "sourceSHA256": "06ee441a7b86656e67af84e148a59dd3b8939f1d5a4b960a76257394b35904d9", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rejects an unfinished native response wait while the finish report is still correctable", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 215, + "sourceLines": [ + 14129, + 14150 + ], + "name": "bounds unfinished native response wait repair across repeated finalization (used=%s)", + "kind": "each", + "sourceSHA256": "649fec5a8dbfe04c1c5bef614e75924987a080f3db351192be276f20df8841ab", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "bounds unfinished native response wait repair across repeated finalization (used=false)", + "status": "passed" + }, + { + "title": "bounds unfinished native response wait repair across repeated finalization (used=true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 216, + "sourceLines": [ + 14152, + 14180 + ], + "name": "preserves a real wait when an unfinished native response reports %s", + "kind": "each", + "sourceSHA256": "3c63056bfa9be8262eb800e974f0c09c117c24b6e23392747b690e08348b50a1", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "preserves a real wait when an unfinished native response reports question", + "status": "passed" + }, + { + "title": "preserves a real wait when an unfinished native response reports approval", + "status": "passed" + }, + { + "title": "preserves a real wait when an unfinished native response reports dependency", + "status": "passed" + }, + { + "title": "preserves a real wait when an unfinished native response reports pause", + "status": "passed" + }, + { + "title": "preserves a real wait when an unfinished native response reports conversation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 217, + "sourceLines": [ + 14182, + 14205 + ], + "name": "reconsiders a legacy committed unfinished Board wait (superseded=%s)", + "kind": "each", + "sourceSHA256": "bc7662ce6d08b5cf2dccaf76fb6cdee4c9ec7ea87683ff3d43e8c41152a3a378", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "reconsiders a legacy committed unfinished Board wait (superseded=false)", + "status": "passed" + }, + { + "title": "reconsiders a legacy committed unfinished Board wait (superseded=true)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 218, + "sourceLines": [ + 14207, + 14215 + ], + "name": "does not repair unfinished native work after its Board comment is superseded", + "kind": "ordinary", + "sourceSHA256": "a23fa5aeef21b94e6ab2c3da0033ea5117e29cea4f2b1f2f5098de0bc558f64d", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not repair unfinished native work after its Board comment is superseded", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 219, + "sourceLines": [ + 14329, + 14379 + ], + "name": "preserves the exact %s passive receipt across repeated paused restart sweeps", + "kind": "each", + "sourceSHA256": "36cbfe046ef727f67ac065f796f9f4d50318e2f24b93828edfc83ec602a1bf84", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "preserves the exact board passive receipt across repeated paused restart sweeps", + "status": "passed" + }, + { + "title": "preserves the exact chat passive receipt across repeated paused restart sweeps", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 220, + "sourceLines": [ + 14381, + 14516 + ], + "name": "keeps ordinary escalation when a paused chat wait is not current (%s)", + "kind": "each", + "sourceSHA256": "fc3b6a35c5373bb715ceed454024be13f8932d75ad9258682c285dafb33ff74a", + "runtimeCaseCount": 11, + "runtimeCases": [ + { + "title": "keeps ordinary escalation when a paused chat wait is not current (uncommitted)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (superseded_decision)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (newer_request)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (edited_source)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (failed_run)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (terminated_agent)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (reassigned)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (revoked_destination)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (malformed_source)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (pending_interaction)", + "status": "passed" + }, + { + "title": "keeps ordinary escalation when a paused chat wait is not current (pending_approval)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 221, + "sourceLines": [ + 14518, + 14534 + ], + "name": "preserves existing error-agent passive wait behavior without treating it as paused", + "kind": "ordinary", + "sourceSHA256": "3c7d270821a47354acd6a6d79c3b42e37e6ba3f61231eadaaeefda7b129a764b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves existing error-agent passive wait behavior without treating it as paused", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 222, + "sourceLines": [ + 14536, + 14566 + ], + "name": "does not use an obsolete Board wait to suppress paused recovery (%s)", + "kind": "each", + "sourceSHA256": "aa067fbc91f1993b09ba8c91ba26fcd7edb90d0f14f3d313de4fb9808af029e6", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "does not use an obsolete Board wait to suppress paused recovery (edited_source)", + "status": "passed" + }, + { + "title": "does not use an obsolete Board wait to suppress paused recovery (newer_request)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 223, + "sourceLines": [ + 14569, + 14609 + ], + "name": "materializes the accepted native passive Board answer privately in the decision commit", + "kind": "ordinary", + "sourceSHA256": "537a3590c9722a43c66283f47816cd2b254da9507c626c0899399c080bd5b8a5", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "materializes the accepted native passive Board answer privately in the decision commit", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 224, + "sourceLines": [ + 14611, + 14647 + ], + "name": "rolls the native passive Board answer back with its rejected decision, then recovers once", + "kind": "ordinary", + "sourceSHA256": "6d9e307ad25595f3b278e31785fd5dc1d162898658d65cd5d11c81beee80c4f2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rolls the native passive Board answer back with its rejected decision, then recovers once", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 225, + "sourceLines": [ + 14649, + 14730 + ], + "name": "does not use stale native passive Board authority after %s", + "kind": "each", + "sourceSHA256": "6fdd6c4e263ab8ece2bf17f0e25c8318f954d1265ad0b3b1263e4ea70c61394d", + "runtimeCaseCount": 10, + "runtimeCases": [ + { + "title": "does not use stale native passive Board authority after source_edit", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after source_delete", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after different_user", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after wake_actor", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after wake_reason", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after wake_run", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after native_issue", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after new_comment", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after reassignment", + "status": "passed" + }, + { + "title": "does not use stale native passive Board authority after pending_question", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 226, + "sourceLines": [ + 14732, + 14766 + ], + "name": "preserves productive native Board %s continuations", + "kind": "each", + "sourceSHA256": "1a76a186dc54b862242c7110b8964784188320753ef789cf504ef736fd7d3583", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "preserves productive native Board same_agent continuations", + "status": "passed" + }, + { + "title": "preserves productive native Board retry continuations", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 227, + "sourceLines": [ + 14768, + 14850 + ], + "name": "rechecks native Board source inside the status transaction ($reasonCode, $change)", + "kind": "each", + "sourceSHA256": "4fe91cdf41b79a783af1f141ef007bc153dd1ce577966e064ef486d96a28e5be", + "runtimeCaseCount": 12, + "runtimeCases": [ + { + "title": "rechecks native Board source inside the status transaction (board_response_waiting, edited)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (board_response_waiting, deleted)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (board_response_waiting, newer_comment)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (board_response_waiting, wake_actor)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (completion_evidence_incomplete, edited)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (completion_evidence_incomplete, deleted)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (completion_evidence_incomplete, newer_comment)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (completion_evidence_incomplete, wake_actor)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (prior_status_preserved_no_live_path, edited)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (prior_status_preserved_no_live_path, deleted)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (prior_status_preserved_no_live_path, newer_comment)", + "status": "passed" + }, + { + "title": "rechecks native Board source inside the status transaction (prior_status_preserved_no_live_path, wake_actor)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 228, + "sourceLines": [ + 14852, + 14882 + ], + "name": "does not let a native passive Board wait suppress a fresh request or reassignment", + "kind": "ordinary", + "sourceSHA256": "f3883229770253d6e1c87ea5944e4410642fabc7fca26c765eea1a3bc0c31f30", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not let a native passive Board wait suppress a fresh request or reassignment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 229, + "sourceLines": [ + 14884, + 14947 + ], + "name": "preserves the native passive Board origin after %s without presenting a stale answer", + "kind": "each", + "sourceSHA256": "f5b63331ee3c54bcff2db371f7b573831a0bce32896eeef2decbeb9abed3ca67", + "runtimeCaseCount": 3, + "runtimeCases": [ + { + "title": "preserves the native passive Board origin after edited without presenting a stale answer", + "status": "passed" + }, + { + "title": "preserves the native passive Board origin after deleted without presenting a stale answer", + "status": "passed" + }, + { + "title": "preserves the native passive Board origin after newer_comment without presenting a stale answer", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 230, + "sourceLines": [ + 14949, + 15026 + ], + "name": "admits a genuine new Board wake for %s after a native passive Board wait", + "kind": "each", + "sourceSHA256": "d9615075ddeddf203a5781864b610d882570e6c136c13b47195d80c4b187347e", + "runtimeCaseCount": 2, + "runtimeCases": [ + { + "title": "admits a genuine new Board wake for same_agent after a native passive Board wait", + "status": "passed" + }, + { + "title": "admits a genuine new Board wake for reassigned_agent after a native passive Board wait", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 231, + "sourceLines": [ + 15028, + 15088 + ], + "name": "defers a native passive Board decision under a held source lock without partial presentation", + "kind": "ordinary", + "sourceSHA256": "cf38ae2c9c146977e7d31de0e8a4e27562f1f12c52d68d99c49e866fed0fe648", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "defers a native passive Board decision under a held source lock without partial presentation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 232, + "sourceLines": [ + 15090, + 15119 + ], + "name": "recovers the native passive Board wait from its receipt, not mutable result or wake presentation", + "kind": "ordinary", + "sourceSHA256": "a7fba02bcd00c8c16a88e9e4430d3afae7d6de62b090af93b780c8902ca57a40", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "recovers the native passive Board wait from its receipt, not mutable result or wake presentation", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 233, + "sourceLines": [ + 15246, + 15305 + ], + "name": "preserves a native blocked Board request instead of recovering the old task title", + "kind": "ordinary", + "sourceSHA256": "a70a724b9c449e80700d3728b2edf0194bdba8a12a8b0c8ba256aa4b1826122b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a native blocked Board request instead of recovering the old task title", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 234, + "sourceLines": [ + 15307, + 15431 + ], + "name": "does not clobber a newer %s while native blocker repair waits on the issue lock", + "kind": "each", + "sourceSHA256": "15bdb4a6b9dc52a337295fae534bddad99b96594e4a08a790923f45c993bf719", + "runtimeCaseCount": 6, + "runtimeCases": [ + { + "title": "does not clobber a newer status while native blocker repair waits on the issue lock", + "status": "passed" + }, + { + "title": "does not clobber a newer assignee while native blocker repair waits on the issue lock", + "status": "passed" + }, + { + "title": "does not clobber a newer comment while native blocker repair waits on the issue lock", + "status": "passed" + }, + { + "title": "does not clobber a newer wake while native blocker repair waits on the issue lock", + "status": "passed" + }, + { + "title": "does not clobber a newer run while native blocker repair waits on the issue lock", + "status": "passed" + }, + { + "title": "does not clobber a newer attention while native blocker repair waits on the issue lock", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 235, + "sourceLines": [ + 15433, + 15501 + ], + "name": "preserves a later routed native attention decision after its wake was coalesced", + "kind": "ordinary", + "sourceSHA256": "131eb214c183b45b56637ca848d0c71ad59f6a71977b308d653524c34f45b1e8", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves a later routed native attention decision after its wake was coalesced", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 236, + "sourceLines": [ + 15503, + 15536 + ], + "name": "does not grant a legacy summary authority to bind a native blocked wait", + "kind": "ordinary", + "sourceSHA256": "d5de2751265a7455cd44533e9a0be829be41634f4bae9ed1d190bd0f7f8b4d48", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not grant a legacy summary authority to bind a native blocked wait", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 237, + "sourceLines": [ + 15538, + 15599 + ], + "name": "rolls back the native blocked wait when its durable notice cannot be persisted", + "kind": "ordinary", + "sourceSHA256": "45a94f2d1f316c6d267c06e99ccb92588397d666cfcfa429b41f2f00cdb31e18", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rolls back the native blocked wait when its durable notice cannot be persisted", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 238, + "sourceLines": [ + 15601, + 15630 + ], + "name": "allows one productive-terminal recovery after regular continuation recovery made progress", + "kind": "ordinary", + "sourceSHA256": "8d13e7fa81d638db9dc1d3e3045d72b77900495dd78634c0f5200a9e30e62d2b", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "allows one productive-terminal recovery after regular continuation recovery made progress", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 239, + "sourceLines": [ + 15632, + 15706 + ], + "name": "does not treat a productive terminal run as healthy when in-progress work has no live path", + "kind": "ordinary", + "sourceSHA256": "5c17238acb74acc2cf775abed83fd9269adfb75c6a6fc19f2c48b3264df78295", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not treat a productive terminal run as healthy when in-progress work has no live path", + "status": "passed" + } + ], + "decision": "keeper" + }, + { + "number": 240, + "sourceLines": [ + 15708, + 15732 + ], + "name": "does not replenish exhausted legacy repair from a recent comment", + "kind": "ordinary", + "sourceSHA256": "6adbcf29c73304e24f08a8db238d14feb48758353a40784f215aaf38a10450cb", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not replenish exhausted legacy repair from a recent comment", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 241, + "sourceLines": [ + 15734, + 15761 + ], + "name": "still escalates stranded-recovery work when the recent comment is older than the exemption window (GGU-809)", + "kind": "ordinary", + "sourceSHA256": "42001e22686e98dfd5032b481ef1202ec3e1642dd439411f02f54a5915a12d86", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "still escalates stranded-recovery work when the recent comment is older than the exemption window (GGU-809)", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 242, + "sourceLines": [ + 15763, + 15784 + ], + "name": "does not reconcile user-assigned work through the agent stranded-work recovery path", + "kind": "ordinary", + "sourceSHA256": "09b2d28707a4bf1d29a4c339e6bf5be5666b058610d7beaca8018bc4426e054d", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "does not reconcile user-assigned work through the agent stranded-work recovery path", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 243, + "sourceLines": [ + 15843, + 15880 + ], + "name": "keeps an accepted Cursor plan passive across restart, future configuration changes, and paused recovery", + "kind": "ordinary", + "sourceSHA256": "a3830a3dc49feb1d8d83d5a3e9e5d02ec07cc0c458e6369108f42fcd3d575ff2", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "keeps an accepted Cursor plan passive across restart, future configuration changes, and paused recovery", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 244, + "sourceLines": [ + 15882, + 15941 + ], + "name": "retains an exact historical Cursor6 committed wait across more than 1000 ignored progress rows without allowing new unbound admission", + "kind": "ordinary", + "sourceSHA256": "f8b7b3081188da6b61d518aac4ff8479b2b2269cf491a4b63ccf177bb4232cfa", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "retains an exact historical Cursor6 committed wait across more than 1000 ignored progress rows without allowing new unbound admission", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 245, + "sourceLines": [ + 15943, + 15968 + ], + "name": "admits a later ordinary user message after an accepted Cursor plan without changing Plan mode or replaying its run", + "kind": "ordinary", + "sourceSHA256": "7735971a76608aea40d1706d028d66885973646be9c01254fe84f3754589e481", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "admits a later ordinary user message after an accepted Cursor plan without changing Plan mode or replaying its run", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 246, + "sourceLines": [ + 15970, + 15991 + ], + "name": "revokes an accepted Cursor plan passive wait after %s changes", + "kind": "each", + "sourceSHA256": "373e109d98368bfec2e6c3d313f9ea3a32a23034960f6f285ad71587390c12c5", + "runtimeCaseCount": 5, + "runtimeCases": [ + { + "title": "revokes an accepted Cursor plan passive wait after delivery changes", + "status": "passed" + }, + { + "title": "revokes an accepted Cursor plan passive wait after assignment changes", + "status": "passed" + }, + { + "title": "revokes an accepted Cursor plan passive wait after admission changes", + "status": "passed" + }, + { + "title": "revokes an accepted Cursor plan passive wait after user_request changes", + "status": "passed" + }, + { + "title": "revokes an accepted Cursor plan passive wait after result_or_decision changes", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 247, + "sourceLines": [ + 15993, + 16005 + ], + "name": "rechecks accepted Cursor plan delivery under the status transaction before presentation or effects", + "kind": "ordinary", + "sourceSHA256": "e32cc8d86d63f086558fb3e42855c860007992c107c62e99853458bd63522a07", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "rechecks accepted Cursor plan delivery under the status transaction before presentation or effects", + "status": "passed" + } + ], + "decision": "retain" + }, + { + "number": 248, + "sourceLines": [ + 16007, + 16013 + ], + "name": "preserves explicit semantic finish priority over accepted Cursor plan evidence", + "kind": "ordinary", + "sourceSHA256": "dec8ed05f2e1f2f1b58ef55b5049591db4af5b8bc0eebfda52eb3954c1de3194", + "runtimeCaseCount": 1, + "runtimeCases": [ + { + "title": "preserves explicit semantic finish priority over accepted Cursor plan evidence", + "status": "passed" + } + ], + "decision": "retain" + } + ], + "helpersAndHooks": [ + { + "name": "commentMetadataRows", + "line": 291, + "endLine": 299, + "sha256": "06b22b7bb750636eb458aaa330ecbb959d04530084e915a10feef58828a569a5" + }, + { + "name": "spawnAliveProcess", + "line": 301, + "endLine": 305, + "sha256": "aadbc1ae3473817b3e2ab7c3eaee7eb6808b13d35a6f3a97a83e6af73fe94b65" + }, + { + "name": "isPidAlive", + "line": 307, + "endLine": 316, + "sha256": "31aecc99b653a38bd8519722b1129fd7732a383e8b1fa92eb587796e17e4d49d" + }, + { + "name": "waitForPidExit", + "line": 318, + "endLine": 325, + "sha256": "83198fce90e5869534c0876859b32ad6bc858a213f21e9e4e97a09dffcc5cacb" + }, + { + "name": "waitForRunToSettle", + "line": 327, + "endLine": 340, + "sha256": "b6cc93978ab341b654fd2bb21c008ec9c6c3c497949de0646dfcdb1be1ae6a26" + }, + { + "name": "waitForValue", + "line": 342, + "endLine": 354, + "sha256": "8018a75f6094e87eea687f0a3c4066e5cc4f35e7206ab2623859100ed73cde52" + }, + { + "name": "waitForHeartbeatIdle", + "line": 356, + "endLine": 374, + "sha256": "dfc0b125f9fbd15d274bed22de91f70e9c6457894cef2e7c639f3bfd4030b511" + }, + { + "name": "stopRegisteredProcessesForCleanup", + "line": 376, + "endLine": 390, + "sha256": "2b1c14655fda08c82578ce4b266b033609b6cdf19303d756c21379f993440f10" + }, + { + "name": "cancelActiveRunsForCleanup", + "line": 392, + "endLine": 448, + "sha256": "7faae892d17b9ad59c886a8127275ee39b357ebcabf153c15c88df2dbcb289d3" + }, + { + "name": "spawnOrphanedProcessGroup", + "line": 450, + "endLine": 490, + "sha256": "03ad97a27a50ae6df337c3f1062ec1294a6de4752ca6807bad604832c119f5e2" + }, + { + "name": "recoveryCompanyRow", + "line": 500, + "endLine": 511, + "sha256": "e2701b5368a92fe7de4770607a4f24406ea7f9b73ea8db839944283dcf5dc4c4" + }, + { + "name": "recoveryAgentRow", + "line": 513, + "endLine": 531, + "sha256": "0b8c1a235bdba36572e288aa40f68c6b4007cb7a523d18feea0407afc3d5f016" + }, + { + "name": "selectRecoveryRunsByAgent", + "line": 533, + "endLine": 538, + "sha256": "8aa2f51e620520a7b89f7e8c053fadad201471535dc07f5df8ceda95612e7af9" + }, + { + "name": "selectRecoveryIssueById", + "line": 540, + "endLine": 546, + "sha256": "fc099866facf1a97b80952e53cf2e654e54118d45bf5534d2010e9864b74337c" + }, + { + "name": "selectRecoveryCommentsForIssue", + "line": 548, + "endLine": 553, + "sha256": "8df15af2150a247faacf3b4140073dfa833c5eacc090c4161d691172553d8b4d" + }, + { + "name": "selectRecoveryActionForIssue", + "line": 555, + "endLine": 569, + "sha256": "a6f625a0c7fc538259c23b3992990e4fad0198b59c4d9eaeec99933a36d2727a" + }, + { + "name": "selectStrandedRecoveryIssuesForCompany", + "line": 571, + "endLine": 581, + "sha256": "4e2398c5a97e7aea11ed206afd386f29fbd8a6adca7ea76add620b1981eedd90" + }, + { + "name": "selectRecoveryActionsForIssue", + "line": 583, + "endLine": 588, + "sha256": "7dfc114ee82a56d060f0bc865d93bfa8e36dc9dd52e338b708294ca71587f2d8" + }, + { + "name": "selectRecoveryWakesByAgent", + "line": 590, + "endLine": 595, + "sha256": "e83f00e10e88c5fd21aed7ebe211e3616b6b78f844d12b899f7d1a7a40615f75" + }, + { + "name": "selectStrandedRecoveryIssuesForSource", + "line": 597, + "endLine": 611, + "sha256": "5f69265a40faaab735397642530faf814a0bc6d05162425907c5f59be4ca65d3" + }, + { + "name": "beforeAll", + "line": 613, + "endLine": 631, + "sha256": "a396faa14dd00410f93d870fd7767f0e082d1332c6b47d7a2d6bc2a47e6d4740" + }, + { + "name": "afterEach", + "line": 633, + "endLine": 805, + "sha256": "681fcdfe64c056848fb0d48b7ac0640ad0395f2a0147d42e7d5f4c6ce5d71511" + }, + { + "name": "afterAll", + "line": 807, + "endLine": 825, + "sha256": "9c545b21ec1ea3078523788ce47d270d25459e17e380b42bf92d55b6ff943f03" + }, + { + "name": "seedRunFixture", + "line": 827, + "endLine": 920, + "sha256": "c74a9d5d8bd4485174c270c5e6785a12eb0c68e04519427ad2a6dc6dc752d583" + }, + { + "name": "seedEnvironmentLeaseFixture", + "line": 922, + "endLine": 963, + "sha256": "6e33996754f6df58c05109ae7eef312fc3dc136420bd5d234eb937bab0ff38af" + }, + { + "name": "seedStrandedIssueFixture", + "line": 1046, + "endLine": 1194, + "sha256": "df0ae6435b397e1f0724200c7defa7736caaf67b94aacd3f5caa0d590bf86dfe" + }, + { + "name": "bindChatConversation", + "line": 1196, + "endLine": 1246, + "sha256": "c120b8a5c75bee80474c295ecee2ad08e43799cbc4218241860dd0d3c276f5a1" + }, + { + "name": "seedInReviewParticipantRunFixture", + "line": 1248, + "endLine": 1329, + "sha256": "9b184e1ba125ebbd0b030582df6b130ede774cb1134ef77f629d22b07ec8996f" + }, + { + "name": "seedAssignedTodoNoRunFixture", + "line": 1331, + "endLine": 1367, + "sha256": "cb7c86a89e50f9912c32710a4a1e9ba5a93859f9f288190347717ee9f35ac4c9" + }, + { + "name": "seedIdleTimerAgentFixture", + "line": 1369, + "endLine": 1401, + "sha256": "728e2a1e8e8094ce9bd94899fe632d5460a0a7e6d4c61e42db8ae326fafd1993" + }, + { + "name": "expectSourceScopedStrandedRecoveryAction", + "line": 1403, + "endLine": 1498, + "sha256": "f4f92bac6261fca2d0449ba7dfdcff7eae0c1ccec3cbe6ec8b54d54a6648c57a" + }, + { + "name": "sourceBlockerIssueIds", + "line": 1500, + "endLine": 1515, + "sha256": "8159db401baab08f08e11b2751e7ead225b36f45ce78fb91f3eba59b25765920" + }, + { + "name": "seedQueuedIssueRunFixture", + "line": 1517, + "endLine": 1599, + "sha256": "6bd605c427351a7338ff7ba0602216b84b2a9c07b89c121c47403b9202073786" + }, + { + "name": "seedPendingNativeFinalization", + "line": 2238, + "endLine": 2273, + "sha256": "8048c038f89e61c098ec11f83e34d342c292dcb1892cec22920044c1e2eee43f" + }, + { + "name": "makePort", + "line": 2254, + "endLine": 2257, + "sha256": "e31c07a82fd719527c1407e4dacfcb20c0b58cc20b65b5ed79bd8f98db8aa675" + }, + { + "name": "complete", + "line": 2261, + "endLine": 2262, + "sha256": "6fa3a72c0ffc6b41be0c19203e17d9cae59c8a37fdea9a5d705173e11c7795f9" + }, + { + "name": "withTempPaperclipHome", + "line": 3277, + "endLine": 3308, + "sha256": "c1983186ccc78b1aa0d1adae8b949632b5c30438cad698fdad9a3dbaf1459dd4" + }, + { + "name": "makeDirectoriesWritable", + "line": 3292, + "endLine": 3304, + "sha256": "56e2ab5e7617d781f92b9f66406bb015408455af42de7b03152c9f579254c303" + }, + { + "name": "seedResolvedInteractionRecoveryFixture", + "line": 10442, + "endLine": 10470, + "sha256": "e87f4f2f41fed5bae85ae552a78e17487fa721f495652e34a554581db117f1b9" + }, + { + "name": "seedCommittedChatControlStop", + "line": 11796, + "endLine": 12011, + "sha256": "59f1fe06690997b8701d3118c934367d82f72548bbcdfbf1aee92d2ff7849054" + }, + { + "name": "seedChatAutomaticChild", + "line": 12041, + "endLine": 12095, + "sha256": "f47d9f70f74368f3d1113dd8394db03c9051769b5f9db584cd4d048400fe60f1" + }, + { + "name": "chatStopScope", + "line": 12097, + "endLine": 12105, + "sha256": "9f94faea69c7cf7af88bb2e67ef7429cbdbd1f5597f6d8138d32106c128f4e22" + }, + { + "name": "seedPreparedChatRecovery", + "line": 12701, + "endLine": 12755, + "sha256": "f77fec5f03f04539ec6329d9db4e92b01f7b48ce54aba229018b13e8655924c9" + }, + { + "name": "seedNativePassiveBoardResponse", + "line": 13941, + "endLine": 14066, + "sha256": "29242e26d3841f673cc1d4a06f526a601f705b6199be5dc1b66bda5c895f7646" + }, + { + "name": "seedPassive", + "line": 14218, + "endLine": 14327, + "sha256": "cb79438ceb55728e93d299dcb80905f936d894448543111cf1d7c09c3a5aaac7" + }, + { + "name": "seedNativeBlockedBoardRequest", + "line": 15121, + "endLine": 15244, + "sha256": "b0998dfbb5ac9dc285a951089730c6874ba56025e43be336465c28570e3c40c9" + }, + { + "name": "beforeEach", + "line": 15787, + "endLine": 15789, + "sha256": "067007d953738331f0371209048ba6f8db801c1ff34b4e652d81e634ae37f6ae" + }, + { + "name": "afterEach", + "line": 15790, + "endLine": 15790, + "sha256": "97fa9145884474de6de5b4b3b890309bc7b4f4f3c5cfd22d6942cfe54236014b" + }, + { + "name": "seedAcceptedCursorPlanWait", + "line": 15792, + "endLine": 15841, + "sha256": "276cb3b641a8f120825efeaadf1f3ce68e7f2b40f178fa6c2aef9b1a3d59ce2e" + } + ], + "evidenceScope": "Pre-integration controls recorded on the historical head, not current-head runtime validation." +} diff --git a/doc/plans/2026-10-12-heartbeat-disposition-test-audit.md b/doc/plans/2026-10-12-heartbeat-disposition-test-audit.md new file mode 100644 index 00000000000..fb2991a5893 --- /dev/null +++ b/doc/plans/2026-10-12-heartbeat-disposition-test-audit.md @@ -0,0 +1,89 @@ +# Heartbeat disposition test audit — 2026-10-12 + +Two duplicate fixture-and-recovery executions are retired. Test registrations fall from 398 to 396. Test source changes by +13/−68, net−55 lines. No execution-time or CI-time benefit is claimed. + +The historical pre-integration proof parent was #16099 at `8e955181d8a3776acc86e4662de3c77933231a76`, branch `codex/test-audit-daytona-pack`. The complete 248-declaration, 398-runtime-case ledger and 49 helper/hook hashes are in the companion JSON evidence file. All 52 matrix declarations remain. All 245 declarations outside the keeper and the two retirements retain their source hashes. + +## Retired and keeper map + +| Role | Original lines | Case | +| --- | --- | --- | +| Retired case 202 |13446–13471| `repairs missing disposition independently of the productive diagnostic label` | +| Retired case 207 |13710–13749| `re-enqueues recovery when the latest in-progress continuation made progress but left no live path` | +| Keeper case 239 |15632–15706| `does not treat a productive terminal run as healthy when in-progress work has no live path` | + +Each seeds `seedStrandedIssueFixture({status: "in_progress", runStatus: "succeeded", livenessState: "advanced"})`, creates one heartbeat service, and reconciles once. No optional fixture input differs. The keeper's extra precondition queries are reads. They confirm the source owner and the absence of active company runs and live company wakes. + +The keeper retains its complete original checks. It receives the exact successful/continuation/escalation counters, post-reconcile issue state, two wakes, the original agent-only run count, and the conditional settlement wait. Both the agent-only and company+agent query boundaries remain. Full retry issue/task/source/ancestor identity and the absent modelProfile remain. Crash, ownership, persistence, restart, cancellation, error and distinct liveness scenarios outside this exact fixture are unchanged. + +## Historical sequential exact-parent controls + +| Phase | Passed | Failed | Skipped | +| --- | --- | --- | --- | +| baseline | 398 | 0 | 0 | +| candidate | 396 | 0 | 0 | +| revert | 398 | 0 | 0 | +| mutation-source-original | 0 | 1 | 397 | +| mutation-source-keeper | 0 | 1 | 395 | +| mutation-counter-original | 0 | 1 | 397 | +| mutation-counter-keeper | 0 | 1 | 395 | +| restoration | 396 | 0 | 0 | + +The four mutation runs select one intended case each. Their unselected skips do not count as coverage. The real recovery owner runs against isolated embedded Postgres; adapter mocks are unchanged. + +The source mutation changes `server/src/services/recovery/service.ts:3163` from the canonical persisted context source to `issue.wrong_disposition_repair`. It fails retired case 202 at original line 13463 and the keeper at candidate line 15635, at the context source oracle. + +The independent counter mutation adds `successfulContinuationObserved += 1` in the active queued legacy-repair branch at `server/src/services/recovery/service.ts:4513`. It fails retired case 207 at original line 13721 and the keeper at candidate line 15608, where the counter expects 0 and receives 1. Other earlier liveness checks pass before this guard. + +Restoring the owner returns the full candidate to 396/396 with zero failures/skips. Test and owner source are then restored byte for byte to the exact parent before the final patch is applied. The process snapshot has no owned remaining test/Postgres process. No global main Postgres process is present at that snapshot. One unrelated Vitest process in another checkout remains. It is not part of this proof. No global process is terminated by this audit. + +## Integrated revision checks + +The counts, source hashes, matrix/declaration ledger and mutation line numbers +below and in the companion JSON were recorded before integration. They do not +claim a passing run or matching whole-file hashes on today's PR head. + +The integrated code revisions are parent +`ef04ba1592a5932d2d971e09b09467f878572d7a` and candidate +`1daaa7496b67c9d38eb7ee4f3eec9e1a3e27cd86`. These immutable public revisions +precede this documentation clarification; the clarification changes no tests or +production source. Fresh static checks confirm the candidate still changes only ++13/-68 lines in `heartbeat-process-recovery.test.ts`, and the production owner +has identical bytes in both revisions. + +| Integrated file | Parent SHA-256 | Candidate SHA-256 | +| --- | --- | --- | +| `heartbeat-process-recovery.test.ts` | `66c6fcfd0a798bc690355eef86306c3b02dead31341e14e27a27bf3f7e5ca061` | `e1d787440f7a24cde1601da41a7c2d78897f0e0b9b31c88657479332d3ff3310` | +| `recovery/service.ts` | `6f23f16a963eb4fe743daad39bc3a51373b72799c851ac9b367ebf119c2b706c` | `6f23f16a963eb4fe743daad39bc3a51373b72799c851ac9b367ebf119c2b706c` | + +```sh +base=ef04ba1592a5932d2d971e09b09467f878572d7a +candidate=1daaa7496b67c9d38eb7ee4f3eec9e1a3e27cd86 +git fetch https://github.com/paperclipai/paperclip.git "$base" "$candidate" +git diff --check "$base" "$candidate" +git diff --numstat "$base" "$candidate" -- server/src/__tests__/heartbeat-process-recovery.test.ts +git show "$base":server/src/__tests__/heartbeat-process-recovery.test.ts | shasum -a 256 +git show "$candidate":server/src/__tests__/heartbeat-process-recovery.test.ts | shasum -a 256 +git diff --exit-code "$base" "$candidate" -- server/src/services/recovery/service.ts +``` + +To validate the integrated owner, create separate isolated checkouts at these +revisions, install the pinned dependencies and run the owner command below in +each. Record actual current counts; do not substitute the historical 398/396 +counts. Current-head CI must validate the integrated change before merge. This +clarification records static checks, not a new local runtime or mutation run. + +## Reproduce historical controls + +Use isolated PAPERCLIP_HOME, PAPERCLIP_CONFIG and TMPDIR values and the checked-in root/server Vitest configuration: + +```sh +node node_modules/vitest/vitest.mjs run --project @paperclipai/server \ + server/src/__tests__/heartbeat-process-recovery.test.ts --exclude '**/dist/**' +``` + +Run baseline, candidate, full revert, each original/keeper mutation independently, then the full restored candidate. Restore source after proof. Mutation patches are described above and are never committed. + +## Limits + +This is a narrow test change. Local controls cover the entire owner. Repository-wide typecheck/test/build and review gates must be checked on the new PR head. No timing or broader live-product acceptance claim is made. diff --git a/doc/plans/2026-10-12-test-audit-r10-db.md b/doc/plans/2026-10-12-test-audit-r10-db.md new file mode 100644 index 00000000000..8dd3aaf25a2 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r10-db.md @@ -0,0 +1,30 @@ +# Round 10 database schema test audit + +This test-only change is stacked on #16075 at `3432307f95f679e20b991c6a250934c2114b7aaf`. The original matched baseline and control ran on its prior head `e86a747fc028069d2bb4d699bd814e0d82fad558`. The parent correction changes documentation only; the rebased complete owner replay passed 6/6 with no failures or skips. It removes one registered case and six net test lines. It makes no measured CI time claim. + +| Retired case | Keeper | Preserved observations | +| --- | --- | --- | +| `keeps the owner principal non-null and drops the environment from the key` | `serializes the active slot on company, owner, and adapter` | Copy the exact `started_by_user_id` non-null assertion. The keeper's exact index column list already proves that the owner is present and the environment is absent. | + +Both cases inspect the same `adapterAuthSessions` table and active index. The keeper still checks index presence, uniqueness, the exact company/owner/adapter columns, and the partial predicate. The public-session identity, claim marker, active states, secret-column exclusion, and migration SQL cases remain separate. No fixture, helper, schema, migration, or caller changes. + +The direct owner is `packages/db/src/schema/adapter_auth_sessions.ts`. Its owner column prevents a null principal from escaping the active unique index. Local login, device login, and setup-token services use this company/owner/adapter scope. The DB project runs in the default `general-workspaces-c` CI group. + +Complete-file command: + +```sh +pnpm exec vitest run packages/db/src/adapter-auth-sessions-schema.test.ts +``` + +| Run on the exact stacked base | Passed | Failed | Skipped | +| --- | ---: | ---: | ---: | +| Baseline | 7 | 0 | 0 | +| Candidate | 6 | 0 | 0 | +| Nullability control | 5 | 1 | 0 | +| Restored-owner candidate | 6 | 0 | 0 | + +The deliberate control removes only `.notNull()` from `startedByUserId` in the production schema. The complete file fails only the keeper at the copied non-null assertion (`false` instead of `true`). The other five cases pass. Restore the schema before the positive replay. + +A whole-program byte comparison proves that the only test changes are one retired statement and one exact copied assertion. Production schema bytes are restored with SHA-256 `ada8f01da2171306b97e5cb2b14b52dd53f6a7d58dd9dd51414d381c56821235`. Candidate test SHA-256 is `9719104e3f6d79d868dee36b1778ce758da8829ffc4485019cad41a128bcff6f`. The earlier independent proof at `77408fa6c3d7c916d7160760eae2144aaa0f8fe7` is preserved separately. Root review approved the keeper map before this PR. + +Risk: the index keeper now reports both index and owner-nullability defects. An earlier index failure can stop the later assertion, but still fails the scenario. Each distinct schema and migration contract remains covered. diff --git a/doc/plans/2026-10-12-test-audit-r11-corpus.md b/doc/plans/2026-10-12-test-audit-r11-corpus.md new file mode 100644 index 00000000000..8d6c29aa71c --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r11-corpus.md @@ -0,0 +1,113 @@ +# 2026-10-12 native status corpus preservation map + +The native status-authority corpus keeps one complete execution of its 53 real fixtures. The comparison-negative loop uses those same actual observations. It no longer creates another set of issues, runs, decisions, effects, and wakes only to change expected comparison fields. + +The change is stacked on public PR #16076 at parent commit `9abd9b23bde1da5fa1f11bfb9ce9a0d5d4c5a8dd`. The dedicated branch is `codex/test-audit-r11-corpus`. + +## Change and retained proof + +| Item | Before | After | +|---|---:|---:| +| Full real-corpus passes in the two affected declarations | 2 | 1 | +| Real fixture executions in those passes | 106 | 53 | +| Corpus fixture definitions | 53 | 53 | +| Joined contract matrix rows | 70 | 70 | +| Negative comparison field categories | 11 | 11 | +| Registered owner and sibling cases | 62 | 61 | +| Corpus test source lines | 2,981 | 2,976 | + +No production source changes. All later live-entrypoint negative controls and storage, status-lock, lineage, reviewer, child-delivery, and monitor tests remain. Those controls still run their targeted scenarios separately. + +The first declaration at `server/src/__tests__/native-status-arbiter-corpus.test.ts:1821` keeps all real consumer execution and matrix checks. It absorbs the pure negative-comparison loop from the former declaration at line 1870. The loop body is byte-identical. The two original full passes had identical fixture lists and `executeFixture` calls. The moved loop changes expected fields and local comparison objects. It does not write to the database or change the original observations. + +All 53 fixture behavior rows are distinct when comparing mode, given facts, and expected behavior. No fixture definitions were removed. + +## Exact-parent verification + +| Phase | Result | +|---|---| +| Baseline at the parent head | 62/62 passed | +| Candidate | 61/61 passed | +| Comparator owner mutation | Keeper failed inside the moved negative assertion | +| Comparator restored to candidate bytes | 61/61 passed | + +Mutation: remove the `reasonCode` comparison from the test-local `comparisonFailures` owner at original line 358. Correct production results still satisfy the positive comparison. The moved negative check fails with `native-mechanical-completion:reasonCode: expected [] to not deeply equal []` at mutated candidate line 1889. This is a comparator/harness-owner regression, not a production status-policy mutation. The comparator was restored byte for byte before the final full owner and sibling replay. + +Run from `server/`: + +```sh +node ../node_modules/vitest/vitest.mjs run --config vitest.config.ts src/__tests__/native-status-arbiter-corpus.test.ts src/services/native-runtime/status-arbiter.test.ts +``` + +The mutation replay selects the merged keeper with `-t 'executes all 53 fixtures, joins all 70 matrix rows'`. + +Repo-wide typecheck, the full Vitest suite, and build were not run locally for this narrow test-only change. The draft PR checks must pass before a merge-ready handoff. + +## Source identity and local cost + +- Baseline corpus SHA-256: `1dffa2f852ff2a9c5c6b174cfb3ef21746b65d124e4593d00401588923bbdcf0`. +- Candidate corpus SHA-256: `e4974c65b704ea3aac7887cddf2707f53c686578fe5c0b1b2aeab42fa10c9d38`. +- Local fixture-test subtotals: baseline `1897.437 ms`; candidate `967.448 ms`; comparator-restored candidate `985.561 ms`. + +These are local Vitest replay samples, not CI timings or a controlled benchmark. The evidence supports removal of the repeated pass. It does not claim a fixed CI time reduction. + +## Declaration ledger + +The full-file review covers the corpus test (31 declarations) and the sibling policy test (17 declarations). Each table row below records one declaration. All parameter rows stay unless the ledger says otherwise. + +| File | Parent line | Test declaration | Mark and retained contract | +|---|---:|---|---| +| native-status-arbiter-corpus.test.ts | 1821 | executes all 53 fixtures in their production consumers and joins all 70 matrix rows | R: Primary real-consumer receipt owner; catches incorrect decisions/effects, missing persisted targets, replay duplication, and incomplete matrix joins. Absorbs the comparator-negative loop. | +| native-status-arbiter-corpus.test.ts | 1870 | fails independently when any asserted field category is mutated | C: Keep every comparison-negative assertion, but use the first declaration’s 53 real observations; this declaration currently executes exactly the same fixture list a second time. | +| native-status-arbiter-corpus.test.ts | 1900 | fails mapped fixtures when live entrypoints or owning actions are removed despite matching policy labels | R: Distinct live-entrypoint negative controls; policy labels alone must not masquerade as cancellation, attention, workspace reconciliation, rollout, or effect delivery. | +| native-status-arbiter-corpus.test.ts | 2066 | retires a proven automatic review and applies the current successful completion exactly once | R: Automatic-review retirement must produce exactly one superseding completion and remain idempotent. | +| native-status-arbiter-corpus.test.ts | 2081 | automatic review reconciliation preserves unread child results (%s); rows: queued, claimed, deferred_issue_execution, current_run, current_intent, consumed | R: Distinct six durable wake delivery states govern whether unread child results hold completion; preserve all rows. | +| native-status-arbiter-corpus.test.ts | 2113 | rechecks child delivery under the status lock during automatic review reconciliation | R: A child wake arriving between initial assessment and status lock must block a stale completion commit. | +| native-status-arbiter-corpus.test.ts | 2138 | new evidence cannot complete reconciliation before a pending child result is consumed | R: Reconciliation policy must prioritize pending child results and emit the release-checkout effect, which the storage-level sibling does not assert. | +| native-status-arbiter-corpus.test.ts | 2146 | new-evidence reconciliation respects %s child delivery; rows: queued, consumed | R: Actual queued/consumed wake rows must select the new-evidence reconciliation path and persisted task status; preserve both rows. | +| native-status-arbiter-corpus.test.ts | 2164 | completes a new merge run after an old CI review and bounds repeated incomplete results | R: New merge runs may retire old CI reviews, while repeated incomplete reports must terminate bounded repair without manufacturing approvals. | +| native-status-arbiter-corpus.test.ts | 2195 | rejects wrong, missing, and duplicate criterion IDs before accepting completion | R: Completion feedback validates exact criterion ID membership/cardinality and remains read-only. | +| native-status-arbiter-corpus.test.ts | 2206 | keeps genuine approval actionable in the finish response and in finalization | R: Genuine human approval remains pending; finish feedback escapes prompt-injection titles and rejects failed verification. | +| native-status-arbiter-corpus.test.ts | 2231 | preserves answered cards, explicit attention, stronger authority, and later task edits | R: Six independent guard facts prevent unsafe automatic review retirement: resolved response, explicit attention, authority, later status, newer contract, failed workspace. | +| native-status-arbiter-corpus.test.ts | 2251 | rejects an empty needs_review report with actionable feedback without inventing an approval | R: Empty needs-review reports fail with actionable feedback and leave review retirement to its owning reconciler. | +| native-status-arbiter-corpus.test.ts | 2262 | withdraws obsolete policy reviews, restores the prior status, and is idempotent | R: Obsolete-policy cleanup restores prior status/version while retaining decision history, then becomes idempotent. | +| native-status-arbiter-corpus.test.ts | 2276 | leaves an already %s review untouched; rows: accepted, rejected | R: Accepted and rejected interactions are both authoritative responses and must remain untouched; preserve both rows. | +| native-status-arbiter-corpus.test.ts | 2286 | preserves real completion reviews and limits cleanup to the requested runs | R: Genuine reviews and unrelated run-scoped cards must not be retired. | +| native-status-arbiter-corpus.test.ts | 2296 | dismisses the obsolete card without undoing a later %s status; rows: blocked, done, cancelled | R: Blocked, done, and cancelled later states have distinct cancellation/expiry behavior and must not be overwritten; preserve all rows. | +| native-status-arbiter-corpus.test.ts | 2306 | does not restore status after a newer decision or a status change back to review | R: Changed decision pointer and a round-trip status edit independently invalidate restoration authority. | +| native-status-arbiter-corpus.test.ts | 2321 | keeps review status when a separate request still needs a response | R: Another pending interaction retains review liveness even after obsolete-card retirement. | +| native-status-arbiter-corpus.test.ts | 2332 | isolates a failed cleanup candidate and retries it on the next pass | R: One failed cleanup transaction must not stop other candidates; a later pass retries the failed candidate. | +| native-status-arbiter-corpus.test.ts | 2354 | keeps committed cleanup and continues publication after a live event fails | R: Live-event publication failure must not roll back committed cleanup or prevent later publication. | +| native-status-arbiter-corpus.test.ts | 2374 | continues native finalization when the obsolete-card lookup fails | R: Obsolete-card lookup failure must not halt native finalization. | +| native-status-arbiter-corpus.test.ts | 2388 | preserves a later authoritative status during reconciliation | R: Reconciliation reducer preserves externally advanced blocked state and its explicit authority reason. | +| native-status-arbiter-corpus.test.ts | 2399 | supersedes the exact committed coordinator decision and sequences preserve decisions independently of issue status versions | R: Semantic decision sequencing is independent of statusVersion changes and supersedes the exact committed coordinator decision. | +| native-status-arbiter-corpus.test.ts | 2469 | keeps assessment lineage run-local while superseding an issue decision from another run | R: Cross-run issue decision supersession must preserve current-run assessment ancestry and durable effect payload linkage. | +| native-status-arbiter-corpus.test.ts | 2634 | preserves an intermediate same-run assessment when the issue decision points to an older ancestor | R: An intermediate same-run assessment must not be replaced with the older issue-decision ancestor. | +| native-status-arbiter-corpus.test.ts | 2741 | ignores historical committed finalizations superseded by a newer authoritative decision | R: Historical committed finalization must remain untouched after a newer authoritative decision wins. | +| native-status-arbiter-corpus.test.ts | 2811 | records run-scoped superseding assessment lineage with %s predecessor; rows: none, same_run, other_run | R: No predecessor, same-run predecessor, and other-run predecessor must all preserve run-local assessment lineage; preserve all rows. | +| native-status-arbiter-corpus.test.ts | 2893 | binds a tools-refresh continuation to its existing durable wake without a duplicate | R: Tools-refresh continuation must bind its existing wake exactly once, rather than insert a duplicate. | +| native-status-arbiter-corpus.test.ts | 2910 | revalidates a %s monitor under the final disposition lock; rows: scheduled, cleared, replaced, reassigned | R: Scheduled, cleared, replaced, and reassigned monitor authority is rechecked under the status lock; preserve all rows. | +| native-status-arbiter-corpus.test.ts | 2943 | fails the transaction closed for an unknown status effect | R: Unknown effect kinds fail the complete transaction closed, leaving no decision/effect/status/coordinator mutation. | +| status-arbiter.test.ts | 47 | keeps a verified accepted Cursor plan passive without completing or replaying work | R: Accepted plan wait is passive only with verified authority and still yields to failed terminals, cancelled issues, and governance. | +| status-arbiter.test.ts | 68 | a reviewer finishes its decision without completing rejected or still-reviewed work | R: A resolved reviewer action must preserve worker/review task status and avoid retrying the worker assignment. | +| status-arbiter.test.ts | 76 | routes an unfinished reviewer action to bounded recovery instead of retrying the worker task | R: Pending reviewer actions require bounded reviewer recovery without generic continuation/retry. | +| status-arbiter.test.ts | 85 | treats only the authorized Board response_wake as passive and preserves governance | R: Board response-wait authority, origin, governance, and non-response continuations select distinct policy branches. | +| status-arbiter.test.ts | 138 | repairs unfinished response waits once using the server continuation budget | R: Unfinished response waits must use bounded server repair while preserving pause, dependency, governance, external-chat, and conversation authority. | +| status-arbiter.test.ts | 172 | marks done only from successful finalization and complete durable evidence | R: Durable evidence and accepted low-risk policy are separate completion authorities; missing evidence alone cannot complete standard work. | +| status-arbiter.test.ts | 245 | creates explicit liveness paths for review, continuation, cancellation, and governance | R: Reviewless needs-review reports, retry continuations, external-chat authority, run cancellation, and governance have distinct liveness/effect contracts. | +| status-arbiter.test.ts | 379 | accepts low-risk completion claims with unrun verification caveats | R: Explicit unrun-verification caveats are permitted under accepted low-risk completion policy without treating them as failed verification. | +| status-arbiter.test.ts | 424 | keeps failed verification with the agent and routes explicit attention to its owner | R: Failed verification overrides low-risk claim acceptance; explicit attention separately creates review authority. | +| status-arbiter.test.ts | 485 | blocks only for a task-wide blocker with a named owner and action | R: Task-wide blockers bind named unblock ownership and send the required owner notification. | +| status-arbiter.test.ts | 513 | waits for an explicit unblock instead of inventing another productive track | R: Current-track blockers bind explicit unblock actions without inventing a runnable alternate track; policy version is independently guarded. | +| status-arbiter.test.ts | 554 | blocks a current-track result when a durable dependency already gates the issue | R: Existing durable dependencies override provider current-track labels without an immediate wake. | +| status-arbiter.test.ts | 581 | lets a durable dependency override an optimistic done result | R: Durable dependency blockers also override an optimistic done report with policy claim acceptance. | +| status-arbiter.test.ts | 598 | does not create a duplicate review after this run's review was already accepted | R: Accepted human review authority suppresses a second review card for needs-review disposition. | +| status-arbiter.test.ts | 626 | does not create a duplicate review after this run's planning confirmation was already accepted | R: Accepted planning approval must also suppress a second card when disposition is blocked; this exercises a distinct allowed disposition. | +| status-arbiter.test.ts | 654 | preserves authoritative terminal statuses | R: Authoritative done task status must not be overwritten or produce new liveness effects. | +| status-arbiter.test.ts | 663 | routes each explicit request to its own reviewer | R: Mixed human/agent attention requests preserve separate reviewer routing and ownership. | + +## History and stopping point + +The executable corpus and comparison-negative control arrived in #12616. The corpus grew to 53 fixtures in #13038. The empty-effect forbidden-effects negative was corrected in #13266; this change keeps that correction verbatim. + +The remaining declarations protect distinct policy, lifecycle, storage, atomicity, routing, and authority contracts. No further cuts are proposed in this lane. diff --git a/doc/plans/2026-10-12-test-audit-r12-runtime-auth-controls.json b/doc/plans/2026-10-12-test-audit-r12-runtime-auth-controls.json new file mode 100644 index 00000000000..ca7ebe52090 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r12-runtime-auth-controls.json @@ -0,0 +1,432 @@ +{ + "baseSHA": "e12e8de07b7cbd0c1b37a921d6db432bbb8d32c5", + "testPatchSHA256": "f339d62b12317218d4206eafa1434725a50ded2610285193802c6379afa44506", + "tests": [ + { + "file": "cli/src/__tests__/runtime-info.test.ts", + "originalSHA256": "ab601b9fef15f75e5685d362a35750a891c0c8b7db466ff7d047010ff9e51714", + "candidateSHA256": "a8beaa462ce1220221b6c2628116ccd3f45152e4177ecb3fb75410ac8a28e7c4" + }, + { + "file": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts", + "originalSHA256": "501bb3720e431a49f360d60b80e9b92184825920109858077bd5f2ae53ad26fa", + "candidateSHA256": "7c00cd14422f2657056a7fd760b4fb5dc66c07e3abcda3abe6356edb8c29731a" + }, + { + "file": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts", + "originalSHA256": "4af358ecdac53c1b22b0288939565be015decaefcfa8502bcd2fadb9c8a14c52", + "candidateSHA256": "39c4d03eeed86d16184df9173ad0df7c46a2c3eb1fe58f2a2125c965e64e8d0b" + }, + { + "file": "packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts", + "originalSHA256": "19e37c4f9e470239408d3710a4ed1ca2414170d6ec83d50f3431593bda79019a", + "candidateSHA256": "3be8594756d8d23ff28361e88c5175c4dc043d3ec08a64d802f5c9373c97070d" + } + ], + "owners": [ + { + "file": "cli/src/runtime-info.ts", + "originalSHA256": "b051153fd3553d71cadccff7d79edc85f6dc74fd87de5563fe91424f05eb90ec", + "finalSHA256": "b051153fd3553d71cadccff7d79edc85f6dc74fd87de5563fe91424f05eb90ec", + "productionRestored": true + }, + { + "file": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.cjs", + "originalSHA256": "86d7323e0eb4af87718c48efa4af28fb18b5c925ca42c91cd10337e696b45557", + "finalSHA256": "86d7323e0eb4af87718c48efa4af28fb18b5c925ca42c91cd10337e696b45557", + "productionRestored": true + }, + { + "file": "packages/adapters/codex-local/src/server/adapter-auth-promotion.ts", + "originalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "finalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "productionRestored": true + }, + { + "file": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.cjs", + "originalSHA256": "573b115eebbb2ccf93dcbae415f1f182862f196a8d10ca3b1c3aafe5ced7ec64", + "finalSHA256": "573b115eebbb2ccf93dcbae415f1f182862f196a8d10ca3b1c3aafe5ced7ec64", + "productionRestored": true + } + ], + "completeFileRuns": [ + { + "label": "baseline", + "exitCode": 0, + "passed": 177, + "failed": 0, + "skipped": 0, + "files": [ + { + "name": "packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts", + "status": "passed", + "cases": 28 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-cache.test.ts", + "status": "passed", + "cases": 31 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-copyback.test.ts", + "status": "passed", + "cases": 17 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts", + "status": "passed", + "cases": 14 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-home.test.ts", + "status": "passed", + "cases": 53 + }, + { + "name": "cli/src/__tests__/runtime-info.test.ts", + "status": "passed", + "cases": 3 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-copyback.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-home.test.ts", + "status": "passed", + "cases": 9 + } + ], + "reportSHA256": "a2a46938d83607e37c114e7b4da42068230185e787fbd08ad97101f30820acec", + "logSHA256": "79fc48066d2f3f285d8a270b163121d73c99885c2708bc862c3ba8aa042abec0" + }, + { + "label": "candidate", + "exitCode": 0, + "passed": 170, + "failed": 0, + "skipped": 0, + "files": [ + { + "name": "packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts", + "status": "passed", + "cases": 25 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-cache.test.ts", + "status": "passed", + "cases": 31 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-copyback.test.ts", + "status": "passed", + "cases": 17 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts", + "status": "passed", + "cases": 12 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-home.test.ts", + "status": "passed", + "cases": 53 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-copyback.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts", + "status": "passed", + "cases": 10 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-home.test.ts", + "status": "passed", + "cases": 9 + }, + { + "name": "cli/src/__tests__/runtime-info.test.ts", + "status": "passed", + "cases": 2 + } + ], + "reportSHA256": "4604c4120f3a892eda54fa9836a62e1805ee5c48fbfb4e9086eaf3ae8870c782", + "logSHA256": "084c1048d51859ee32c514672fd0a4a0828eeea0ca2dc42bf8310d2de46e215c" + }, + { + "label": "revert", + "exitCode": 0, + "passed": 177, + "failed": 0, + "skipped": 0, + "files": [ + { + "name": "packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts", + "status": "passed", + "cases": 28 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-cache.test.ts", + "status": "passed", + "cases": 31 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-copyback.test.ts", + "status": "passed", + "cases": 17 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts", + "status": "passed", + "cases": 14 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-home.test.ts", + "status": "passed", + "cases": 53 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-copyback.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-home.test.ts", + "status": "passed", + "cases": 9 + }, + { + "name": "cli/src/__tests__/runtime-info.test.ts", + "status": "passed", + "cases": 3 + } + ], + "reportSHA256": "b60e9b8b949acb1fb603100c59bf9c36cc564858426ebe2576fae3c11a8ab3e7", + "logSHA256": "0395a74d8c069e3a8963c297599d94655e3768ccc5c8c6f2ab108cb4c8a3b450" + }, + { + "label": "candidate-after-restoration", + "exitCode": 0, + "passed": 170, + "failed": 0, + "skipped": 0, + "files": [ + { + "name": "packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts", + "status": "passed", + "cases": 25 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-cache.test.ts", + "status": "passed", + "cases": 31 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-copyback.test.ts", + "status": "passed", + "cases": 17 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts", + "status": "passed", + "cases": 12 + }, + { + "name": "packages/adapters/codex-local/src/server/codex-home.test.ts", + "status": "passed", + "cases": 53 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-copyback.test.ts", + "status": "passed", + "cases": 11 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts", + "status": "passed", + "cases": 10 + }, + { + "name": "packages/adapters/grok-local/src/server/grok-home.test.ts", + "status": "passed", + "cases": 9 + }, + { + "name": "cli/src/__tests__/runtime-info.test.ts", + "status": "passed", + "cases": 2 + } + ], + "reportSHA256": "d2d363ab57d607633f12d75eaea17265010e834c7d92bed588334a39e6550867", + "logSHA256": "21cfea7bd04b674fccbe2226aebfba9b19dc12b022df995d7c35e779122c01bf" + } + ], + "mutations": [ + { + "label": "mutation-runtime-write", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 1, + "reportSHA256": "5271aa251ce2c804eaea9fb345854c61674247c5cf91aaf3f2c043f1177fde69", + "logSHA256": "68f6c2f446ef0f1717f1f676f65e999657f9455954279890566a2a777dade6da", + "originalSHA256": "b051153fd3553d71cadccff7d79edc85f6dc74fd87de5563fe91424f05eb90ec", + "mutatedSHA256": "4bab7a0eb48f9ec37c7912640c544a460e498ecc806200fb086c7f9c23c24490", + "owner": "cli/src/runtime-info.ts", + "keeper": "runtime info does not remove runtime info owned by a replacement process", + "mutationId": "runtime-write", + "assertion": "AssertionError: expected null to deeply equal { schemaVersion: 1, \u2026(6) }", + "edit": { + "from": " fs.renameSync(temporaryPath, filePath);", + "to": "omit the atomic rename statement" + } + }, + { + "label": "mutation-codex-default-absent", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 11, + "reportSHA256": "5441f2d038e120fdd62f40fbf0adad3711f5bee66ac214a2f188ad90b798a22a", + "logSHA256": "d52cd9f252f1f9ebf4da23f86cacd9682fc7137aef7ec64d311c14074aed2226", + "originalSHA256": "86d7323e0eb4af87718c48efa4af28fb18b5c925ca42c91cd10337e696b45557", + "mutatedSHA256": "c2140ce41b07a007adc6232b1db6ee308db14f5adfd7e31aa17d509970afa97f", + "owner": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.cjs", + "keeper": "codex-auth-merge-decision predicate seed mode the leading positional --seed-if-dest-absent flag is parsed before the two path arguments; a default two-path call never enters seed mode", + "mutationId": "codex-default-absent", + "assertion": "AssertionError: expected 10 to be 20 // Object.is equality", + "edit": { + "from": " seedIfDestAbsent &&", + "to": " true &&" + } + }, + { + "label": "mutation-codex-seed-absent", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 11, + "reportSHA256": "0784aed1b34cb2dfa13d7c93ac10d5ef8a9f38da153eebce234c762fab6cb628", + "logSHA256": "02e7b7367bdecc722e3a57bffb8974dee0afc2c0304f23dcea76db3a13ce8ad6", + "originalSHA256": "86d7323e0eb4af87718c48efa4af28fb18b5c925ca42c91cd10337e696b45557", + "mutatedSHA256": "867730de34972c627b235a1499aee126ab428dd899098e5b1d30d450dd764113", + "owner": "packages/adapters/codex-local/src/server/codex-auth-merge-decision.cjs", + "keeper": "codex-auth-merge-decision predicate seed mode the leading positional --seed-if-dest-absent flag is parsed before the two path arguments; a default two-path call never enters seed mode", + "mutationId": "codex-seed-absent", + "assertion": "AssertionError: expected 20 to be 10 // Object.is equality", + "edit": { + "from": " return sourceIsImplausible ? IMPLAUSIBLE_LAST_REFRESH : USE_SOURCE;", + "to": " return KEEP_DESTINATION;" + } + }, + { + "label": "mutation-grok-iso-expiry", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 9, + "reportSHA256": "3aa14c8f3dc9a4720743926fb0f62601aba8a7cc862d154064ab32b17cc3e2fd", + "logSHA256": "f7835d91b6d8bbdd039ca56cc83205de798b82b4ce791b33a546a2d7cde7bb25", + "originalSHA256": "573b115eebbb2ccf93dcbae415f1f182862f196a8d10ca3b1c3aafe5ced7ec64", + "mutatedSHA256": "20a815272eff6fefe592f09e55d9d690ca3eee41e98145b4447fcdac9828d856", + "owner": "packages/adapters/grok-local/src/server/grok-auth-merge-decision.cjs", + "keeper": "grok-auth-merge-decision predicate reads an ISO-8601 expiry, an epoch-seconds expiry, and an epoch-milliseconds expiry", + "mutationId": "grok-iso-expiry", + "assertion": "AssertionError: ISO-8601: expected 20 to be 10 // Object.is equality", + "edit": { + "from": " const ms = Date.parse(raw);", + "to": " const ms = 0;" + } + }, + { + "label": "mutation-promotion-company-home", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 24, + "reportSHA256": "aca6aa3dc842e1f1ba1c956b245393de4d2ee993d8da156b960b0af6fd69c2bc", + "logSHA256": "0fa22f697fbc4b042cb58ad1efe6d8db0459a6fe129e584b4666cfc421cad8f8", + "originalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "mutatedSHA256": "cf54fc5ca674868f4b98f8af0700800153837fc5db26066aec99eb530127f06e", + "owner": "packages/adapters/codex-local/src/server/adapter-auth-promotion.ts", + "keeper": "device-login credential promotion a user login seeds this account's own home and the company default home", + "mutationId": "promotion-company-home", + "assertion": "AssertionError: expected 'wrong-account' to be 'acct-42' // Object.is equality", + "edit": { + "from": " sourceBytes: authBytes,\n destinationPath: companyHomeAuthPath,", + "to": " sourceBytes: Buffer.from(JSON.stringify({ ...JSON.parse(authBytes.toString()), tokens: { ...JSON.parse(authBytes.toString()).tokens, account_id: \"wrong-account\" } })),\n destinationPath: companyHomeAuthPath," + } + }, + { + "label": "mutation-promotion-account-id", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 24, + "reportSHA256": "73d41f793829a226a0aee3cbb12b66702353f9d83bbc7e3d146f72486f193a43", + "logSHA256": "4deb6470827ab1cd43e3761b43ec4f73b16dbd8a6941bbf92b50c7f527d7b549", + "originalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "mutatedSHA256": "d1b3f413f0c9e5a0c2f3fd57e37f4eaf38254884c4adb894b8e2fb03605272c0", + "owner": "packages/adapters/codex-local/src/server/adapter-auth-promotion.ts", + "keeper": "device-login credential promotion a user login seeds this account's own home and the company default home", + "mutationId": "promotion-account-id", + "assertion": "AssertionError: expected 'wrong-account' to be 'acct-42' // Object.is equality", + "edit": { + "from": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId,", + "to": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId: \"wrong-account\"," + } + }, + { + "label": "mutation-promotion-account-home", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 24, + "reportSHA256": "2202e3a9350c8cccbbf880e728055c5703c594d1480a62525858842db7052f25", + "logSHA256": "daa89ab14e44be704e8a6926bda850d0c2e214bf602cff161bc00c5b3fa930b7", + "originalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "mutatedSHA256": "b03b3fac62ac26b179b13505268fbfd59cbf63213f10da87835f33fd5c54843d", + "owner": "packages/adapters/codex-local/src/server/adapter-auth-promotion.ts", + "keeper": "device-login credential promotion a user login seeds this account's own home and the company default home", + "mutationId": "promotion-account-home", + "assertion": "AssertionError: expected 'wrong-home' to be the exact independently resolved account-home path", + "edit": { + "from": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId,\n accountHomeDir,", + "to": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId,\n accountHomeDir: \"wrong-home\"," + } + }, + { + "label": "mutation-promotion-created-receipt", + "exitCode": 1, + "passed": 0, + "failed": 1, + "skipped": 24, + "reportSHA256": "3ded03074b1f22efa84f940fbc17a91465b4f65434010e0b2b8ccc9fb16a3fa8", + "logSHA256": "c12316bc00a075f589b03d2b6686120d39cd8ec89763d22599546801fcf4900f", + "originalSHA256": "6065e6e64c53965709acec0fd21431c1b08799bef8a9f2a9fba92f4c367a740a", + "mutatedSHA256": "3d13658dcc9bfeab21ace2d5f44d30bd6490e73bdacb18810ab3415822bc4956", + "owner": "packages/adapters/codex-local/src/server/adapter-auth-promotion.ts", + "keeper": "device-login credential promotion a user login seeds this account's own home and the company default home", + "mutationId": "promotion-created-receipt", + "assertion": "AssertionError: expected false to be true // Object.is equality", + "edit": { + "from": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId,\n accountHomeDir,\n accountHomeCreated,", + "to": " outcome: accountHomeOutcome === \"written\" ? \"promoted\" : \"kept\",\n accountId,\n accountHomeDir,\n accountHomeCreated: false," + } + } + ] +} diff --git a/doc/plans/2026-10-12-test-audit-r12-runtime-auth.md b/doc/plans/2026-10-12-test-audit-r12-runtime-auth.md new file mode 100644 index 00000000000..53a1f2389d1 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r12-runtime-auth.md @@ -0,0 +1,57 @@ +# Test audit R12: runtime and credential owner coverage + +Date: 2026-10-12. Base: `e12e8de07b7cbd0c1b37a921d6db432bbb8d32c5`, the head of [R11](https://github.com/paperclipai/paperclip/pull/16078). Branch: `codex/test-audit-r12-runtime-auth`. + +This change removes **seven redundant registered executions and 76 net test lines** across four files. It changes no production code. Each retired case repeats an existing owner invocation. Three result assertions move into the existing stronger first-login case. No wall-time improvement is inferred from these counts. + +## Exact retirement map + +| File | Retired exact case | Retained keeper and observation | +| --- | --- | --- | +| `cli/src/__tests__/runtime-info.test.ts` | `writes and reads the selected runtime endpoint` | `does not remove runtime info owned by a replacement process`: same fresh fixture, write and exact read; wrong-PID preservation and matching-PID removal also remain. | +| `packages/adapters/codex-local/src/server/codex-auth-merge-decision.test.ts` | `default mode keeps destination when destination is absent (host-default fail-closed)` | `the leading positional --seed-if-dest-absent flag is parsed before the two path arguments; a default two-path call never enters seed mode`: identical subscription bytes and absent destination, actual subprocess, exit 20. | +| Same Codex decision file | `seed mode uses source when destination is absent and source is a usable subscription credential` | Same leading-flag keeper: identical opt-in call, actual subprocess, exit 10. | +| `packages/adapters/grok-local/src/server/grok-auth-merge-decision.test.ts` | `uses the source when the identity matches and the source expiry is later` | `reads an ISO-8601 expiry, an epoch-seconds expiry, and an epoch-milliseconds expiry`: ISO row retains identical source/destination credential shape, markers and later-expiry relation through actual files and subprocess; all encodings remain. | +| `packages/adapters/codex-local/src/server/adapter-auth-promotion.test.ts` | `promotion returns the account id and the account home path` | `a user login seeds this account's own home and the company default home`: identical fresh-root input and callbacks. Exact account ID and account-home path assertions move unchanged into this keeper. | +| Same promotion file | `promotion reports accountHomeCreated true for a first login of an account` | Same first-login keeper gains the exact true receipt. Repeat-login and concurrent-created receipts remain separate. | +| Same promotion file | `promotion seeds the company default home when it holds no usable credential` | Same first-login keeper already reads the company default home's actual account ID and also reads the account home, checks promoted outcome and forbids instance-global writes. | + +The retired invocations comprise one runtime-info write/read scenario, two Codex decision subprocesses, one Grok decision subprocess and three fresh-login promotions. Table registration and internal case execution are unchanged in the keepers. This is reduced duplicate execution, not only shorter test setup. + +Net test lines: runtime-info 6; Codex decision 17; Grok decision 7; promotion 46, including five added assertion lines. Total: 81 deletions and five additions. The evidence files are excluded from those savings. + +## Preserved contracts + +The runtime file's malformed-input and process-ownership guards remain. Both decision suites retain identity isolation, unusable credentials, strict freshness and ties, exact clock bounds and encoding rejection. Promotion retains readiness, user-initiated/sole-owner gates, company isolation, account separation, repeat and concurrent creation, persistence failures, symlink replacement and secret redaction. No API, schema, migration, platform, lifecycle or security contract is retired. + +## Replay on the exact stacked base + +Nine complete existing files ran through the repository's real Vitest project configuration with isolated home, config, instance ID and temporary paths. The four owner files contain 56 original or 49 candidate cases. Five unchanged auth-cache, copyback and home siblings contain 121 cases. + +| Phase | Passed | Failed | Skipped | +| --- | ---: | ---: | ---: | +| Original baseline | 177 | 0 | 0 | +| Candidate | 170 | 0 | 0 | +| Reverted original tests | 177 | 0 | 0 | +| Candidate after owner restoration | 170 | 0 | 0 | + +Run counts overlap and must not be added. Source-only filtering excludes build output. Existing pinned external dependencies are read-only; workspace dependency links point into this managed checkout. The initial discovery export was not used as PR qualification. + +## Owner mutation controls + +Eight deliberate changes to the real production owners each failed exactly one retained keeper at its intended assertion. Each owner was restored byte for byte in `finally` before the next run. + +| Deliberate owner regression | Failed observation | +| --- | --- | +| Omit atomic runtime-file publication | Exact persisted runtime object: null instead of the written object. | +| Seed an absent destination without the Codex opt-in flag | Default call: exit 10 instead of 20. | +| Keep an absent destination despite the Codex opt-in flag | Seed call: exit 20 instead of 10. | +| Erase the decoded Grok ISO expiry | ISO row: exit 20 instead of 10. | +| Write the wrong identity to the company default home | Actual stored account ID differs. | +| Return the wrong account ID after real promotion | Migrated result account ID differs. | +| Return the wrong account-home path after real promotion | Migrated exact result path differs. | +| Report false after first account-home creation | Migrated creation receipt is false instead of true. | + +Name-filtered mutation runs skip the unrelated cases in their complete owner file. Those intentional filter skips are separate from the four complete-file controls, which have zero skips. The harness counts one failed case, rather than assuming Vitest's nested suite count equals one. No timeout, mock result, fixture receipt or setup error supplies a mutation failure. + +[The adjacent controls record](2026-10-12-test-audit-r12-runtime-auth-controls.json) retains exact original/candidate test hashes, restored owner hashes, run/log digests, production edits and actual failing keeper assertions. Compile, build and current-head CI/review results are reported separately on the draft PR. diff --git a/doc/plans/2026-10-12-test-audit-r13-git-credentials.md b/doc/plans/2026-10-12-test-audit-r13-git-credentials.md new file mode 100644 index 00000000000..e8f009943cc --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r13-git-credentials.md @@ -0,0 +1,93 @@ +# 2026-10-12 Git credential proof ownership map + +The Git credential owner now holds all 14 existing redaction input/output assertions. The workspace suite no longer repeats that direct helper contract through a compatibility re-export. Runtime workspace code already imports the canonical helper directly, so the obsolete heartbeat import/re-export shim can be removed. + +Parent: public PR #16079 at `2e17a5ed43ecb0002f7f0e2fe5c1017f35d7ffab`. Branch: `codex/test-audit-r13-git-credentials`. + +## Scope and retained contract + +| Item | Before | After | +|---|---:|---:| +| Direct redaction input/output assertions | 14 | 14 | +| Direct redaction helper calls | 14 | 14 | +| Registered keeper/workspace cases | 206 | 201 | +| Test source lines in the changed files | 3,705 | 3,687 | +| Obsolete heartbeat shim source lines | 6 | 0 | + +The net reduction is 18 test source lines and six production shim lines. This is proof ownership and seam cleanup. It does not remove helper execution or establish a CI time reduction. + +Removed workspace declarations at parent lines 949–986 preserve their complete bodies inside the four existing keeper declarations at `server/src/__tests__/git-credentials.test.ts:282`. Their five wrappers and the heartbeat import are removed. The six-line import/comment/re-export block at `server/src/services/heartbeat.ts:323–328` is the only production change. + +All original input/output pairs were compared as a multiset after resolving the local string constants. Both sides contain exactly 14 pairs and have this canonical SHA-256: `78ff3b460616782e2380217c22ba00dbe757203a9e34d1c38d1ea294a4c5a9e0`. + +## Assertion relocation map + +| Original file and assertion line | Candidate keeper assertion line | Protected case | +|---|---:|---| +| git-credentials.test.ts:284 | 284 | HTTPS userinfo | +| git-credentials.test.ts:290 | 296 | SSH userinfo | +| git-credentials.test.ts:293 | 299 | scp-style remote remains unchanged | +| git-credentials.test.ts:299 | 311 | URL query with token and reference | +| git-credentials.test.ts:302 | 314 | arbitrary query parameter name | +| git-credentials.test.ts:308 | 326 | credential-free error text | +| heartbeat-workspace-session.test.ts:951 | 287 | quoted HTTPS userinfo in a Git error | +| heartbeat-workspace-session.test.ts:957 | 290 | bare username and multiple URLs | +| heartbeat-workspace-session.test.ts:963 | 302 | SSH and git+ssh userinfo in one error | +| heartbeat-workspace-session.test.ts:966 | 305 | scp-style remote with surrounding text | +| heartbeat-workspace-session.test.ts:972 | 317 | quoted query in a Git error | +| heartbeat-workspace-session.test.ts:975 | 320 | query with trailing error text | +| heartbeat-workspace-session.test.ts:982 | 328 | credential-free URL error | +| heartbeat-workspace-session.test.ts:984 | 330 | plain text with no URL | + +## Scoped declaration ledger + +| Parent declaration | Mark | Keeper and retained contract | +|---|---|---| +| git-credentials.test.ts:283 — masks URL userinfo | R | Keeps HTTPS userinfo and absorbs quoted errors, bare usernames, and multiple URLs. | +| git-credentials.test.ts:289 — non-HTTP userinfo and scp remotes | R | Keeps SSH/scp cases and absorbs git+ssh and surrounding-message cases. | +| git-credentials.test.ts:298 — complete query masking | R | Keeps arbitrary query keys and absorbs quote/trailing-message boundaries. | +| git-credentials.test.ts:307 — credential-free text | R | Keeps plain errors and absorbs credential-free URL/text examples. | +| heartbeat-workspace-session.test.ts:950 — URL userinfo | C | Move its unchanged assertion into the existing userinfo keeper. | +| heartbeat-workspace-session.test.ts:956 — bare usernames and multiple URLs | C | Move its unchanged assertion into the existing userinfo keeper. | +| heartbeat-workspace-session.test.ts:962 — non-HTTP userinfo and scp remotes | C | Move both unchanged assertions into the non-HTTP keeper. | +| heartbeat-workspace-session.test.ts:971 — complete query masking | C | Move both unchanged assertions into the query keeper. | +| heartbeat-workspace-session.test.ts:980 — credential-free text | C | Move both unchanged assertions and their local constants into the plain-text keeper. | +| heartbeat.ts:323–328 — scrubber re-export shim | D | No runtime importer remains through heartbeat. Workspace code uses the canonical Git credential owner. | + +Other keeper and workspace declarations stay unchanged. This is a scoped ledger, not a claim that all 206 cases were deletion candidates. + +## Production callers and history + +The only production callers are in `server/src/services/heartbeat/workspaces.ts`: import at line 47 and calls at lines 602, 3545, and 3553. These callers already import `scrubGitCredentialText` from `git-credentials.ts`; their code stays unchanged. Repository search found no other heartbeat re-export consumer after the workspace test import was removed. + +#10719 introduced the workspace redaction proof. #10720 established the Git credential owner. #10722 removed a duplicate implementation and retained the heartbeat re-export so older imports, including the workspace tests, still worked. The canonical implementation and its production callers now remain; the obsolete test-only import path is removed. + +## Exact-parent verification + +| Phase | Result | +|---|---| +| Keeper/workspace baseline | 206/206 passed | +| Candidate | 201/201 passed | +| Production owner mutation | 24 passed; two intended keeper failures | +| Production owner restored byte for byte | 201/201 passed | +| Real managed-checkout caller sibling | 19/19 passed | + +Mutation: change the userinfo mask regex from `/gi` to `/i` in `server/src/services/git-credentials.ts:111`. The helper leaks credentials in later URLs. The keeper fails in the two migrated multi-URL assertions at candidate lines 290 and 302, whose assertions finish at lines 292 and 304. The production helper was restored to its exact parent bytes before the final replay. + +Run from `server/`: + +```sh +node ../node_modules/vitest/vitest.mjs run --config vitest.config.ts src/__tests__/git-credentials.test.ts src/__tests__/heartbeat-workspace-session.test.ts +node ../node_modules/vitest/vitest.mjs run --config vitest.config.ts src/__tests__/heartbeat-managed-clone-credentials.test.ts +``` + +Repo-wide typecheck, the full Vitest suite, and build were not run locally for this narrow owner consolidation. Draft PR checks must pass before a merge-ready handoff. + +## Source hashes + +| File | Parent SHA-256 | Candidate SHA-256 | +|---|---|---| +| server/src/__tests__/git-credentials.test.ts | `e236377145742b8074be8d61ff11b50812815436c5d188133f95475cd731384d` | `29e12200d45e8a17098bcc8571d82fc2ea3f68422b7cc04271a79b52b4f32ae0` | +| server/src/__tests__/heartbeat-workspace-session.test.ts | `ad26f3a88c25da94502889fd3f0f158b81bd1bed03332880a383a45437703761` | `93480b2a883bb0ad075cbf143abe302225ddfd01e333431999c95e378cd8a6c3` | +| server/src/services/heartbeat.ts | `0fb791d721262d024ef2935b3045f4a5489adfa5853d2d54dab15c639d26fe54` | `9ac217e97e5319c850924f083df32be43df11f33f8970ff8efb4a5f776a0c1d9` | +| server/src/services/git-credentials.ts | `0e8bc7031cc7000db3d9828eb84fbd2c22d19657bfcdc5e287f95cc01e05934f` | `0e8bc7031cc7000db3d9828eb84fbd2c22d19657bfcdc5e287f95cc01e05934f` | diff --git a/doc/plans/2026-10-12-test-audit-r15-environment-runtime.md b/doc/plans/2026-10-12-test-audit-r15-environment-runtime.md new file mode 100644 index 00000000000..baa0a125523 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r15-environment-runtime.md @@ -0,0 +1,62 @@ +# Environment runtime test audit — 2026-10-12 + +Parent: #16083 (`codex/test-audit-r14-browser`), exact head +`1113d4c9eaa49311c9b269c96b79bbae06d42e25`. + +## Change and keeper + +`server/src/__tests__/environment-runtime.test.ts:1405–1420` repeats the local +projectless acquisition already executed by the lifecycle test at1371. Both +use the same `seedEnvironment()` call, runtime, suite hooks and acquisition +input. The seed/acquire statements are AST-identical. Both tests originated +in `70679a33216` (Add sandbox environment support, #4415). + +Move the unique `lease.executionWorkspaceId` null assertion into the keeper +at1383. Keep the exact `leaseContext` equality, which already checks its two +null fields. Every original keeper statement remains unchanged and ordered. +The keeper also releases the lease and checks the durable released row. + +One file: +2/-17 test lines, net15 fewer lines. One registered case and one +redundant seed/acquisition/setup/cleanup cycle disappear (162→161 cases). +No production behavior changes. Provider, SSH, company access, secret +resolution/redaction, timeout precedence, session and recovery cases remain. + +## Exact-parent proof + +| Phase | Passed | Failed | Filtered/pending | +|---|---|---|---| +| Full baseline | 162 | 0 | 0 | +| Full candidate | 161 | 0 | 0 | +| Unmutated keeper control | 1 | 0 | 160 | +| Production mutation, intended keeper | 0 | 1 | 160 | +| Owner restored, full candidate | 161 | 0 | 0 | +| All source reverted, full parent | 162 | 0 | 0 | + +Proof-only production mutation at `environment-runtime.ts:4047`: acquisition +returns `lease: { ...lease, executionWorkspaceId: lease.executionWorkspaceId +?? input.environment.id }`. This real return-mapping fault leaves context and +DB setup intact. The relocated assertion at1383:49 rejects the environment +UUID because a projectless lease must return null. Production bytes were +restored before the full candidate replay. The mutation is not in this PR. + +The first reverted run had a suite-level startup failure (3 pure tests passed, +159 DB cases pending, no assertion failure). JSON-only output omitted the +hook diagnostic. An unchanged-source rerun with the default reporter passed +162/162. The failed attempt remains in the temporary receipt. No CI speedup +is claimed from the single local samples. + +## Hashes and receipts + +- Parent test SHA-256: `a7370481526c91044a020cf0e56a5ab4f27a7289c6ee8bcf94e414d7b70ab35a`. +- Candidate test SHA-256: `39069f6d8261886b9c9521a7e3e5026051d90287b5e14c471d589fde384832e7`. +- Production owner SHA-256: `77da04770ef8e87dd868f90a65c10ae2322a95ad1d8d78e73facb1be3443c0f8`. +- Identical seed/acquisition AST SHA-256: `d25af395dc9775474276cefbedd2d736ddfca7c57263db5543c603e52b6f96d3`. + +Full read: 7,223 lines, 100 declarations, 162 cases, two suites and four hooks. +The full ledger, case mapping, patch, preservation proof and JSON/log receipts +are in `/private/tmp/test-audit-environment-runtime/`. + +Local verification uses the full owner with the unchanged server Vitest +configuration. Full repository test/build preflight is deferred to isolated +CI because this audit worktree shares dependency symlinks and preflight +rewrites workspace package links. CI must pass before a green handoff. diff --git a/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery-controls.json b/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery-controls.json new file mode 100644 index 00000000000..6179db6be79 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery-controls.json @@ -0,0 +1,97 @@ +{ + "parentPR": 16086, + "parentBranch": "codex/test-audit-r15-environment-runtime", + "parentHead": "c0414ac02e625a1e635857895b74581c37201500", + "result": "PROVEN", + "registrationsBefore": 399, + "registrationsAfter": 398, + "declarationsBefore": 249, + "declarationsAfter": 248, + "retiredCase": "heartbeat orphaned process recovery re-enqueues continuation for stranded in-progress work with no active run", + "keeper": "heartbeat orphaned process recovery re-enqueues stranded in-progress work after a failed/process_lost run before escalating", + "deletedTestLOC": 31, + "movedAssertionLOC": 5, + "netTestLOCDelta": -26, + "candidateTestSHA256": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6", + "parentSourceHashes": { + "server/src/__tests__/heartbeat-process-recovery.test.ts": "bf7c888eaba44a56d12823bb948026d5db44f2249a75a6cf083343edbd7c0673", + "server/src/services/heartbeat/retries.ts": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a", + "server/src/services/heartbeat.ts": "9ac217e97e5319c850924f083df32be43df11f33f8970ff8efb4a5f776a0c1d9", + "server/src/services/recovery/service.ts": "c414df876e7df64e701a752513db8e5cf7365589294d50da0e1396788a9207db" + }, + "sourceRestoredBeforeBranchCreation": true, + "controls": [ + { + "phase": "baseline", + "exitCode": 0, + "tests": 399, + "passed": 399, + "failed": 0, + "pending": 0, + "failures": [], + "ownerSHA256": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a", + "testSHA256": "bf7c888eaba44a56d12823bb948026d5db44f2249a75a6cf083343edbd7c0673" + }, + { + "phase": "candidate", + "exitCode": 0, + "tests": 398, + "passed": 398, + "failed": 0, + "pending": 0, + "failures": [], + "ownerSHA256": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a", + "testSHA256": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6" + }, + { + "phase": "revert", + "exitCode": 0, + "tests": 399, + "passed": 399, + "failed": 0, + "pending": 0, + "failures": [], + "ownerSHA256": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a", + "testSHA256": "bf7c888eaba44a56d12823bb948026d5db44f2249a75a6cf083343edbd7c0673" + }, + { + "phase": "mutation-keeper", + "exitCode": 1, + "tests": 398, + "passed": 0, + "failed": 1, + "pending": 397, + "failures": [ + { + "name": "heartbeat orphaned process recovery re-enqueues stranded in-progress work after a failed/process_lost run before escalating", + "messages": [ + "AssertionError: expected { \u2026(78) } to match object { status: 'scheduled_retry', \u2026(1) }\n(84 matching properties omitted from actual)\n at Proxy. (file:///node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2050:10)\n at Proxy. (file:///node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at server/src/__tests__/heartbeat-process-recovery.test.ts:10386:24\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at file:///node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + ], + "ownerSHA256": "cff836de7a98e47e845ad7f7393e4c98fcfea3fecb0ba5bdb9fcc92fc9673c84", + "testSHA256": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6" + }, + { + "phase": "restoration", + "exitCode": 0, + "tests": 398, + "passed": 398, + "failed": 0, + "pending": 0, + "failures": [], + "ownerSHA256": "9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a", + "testSHA256": "645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6" + } + ], + "temporaryMutation": { + "owner": "server/src/services/heartbeat/retries.ts", + "line": 1049, + "before": "scheduledRetryAttempt: schedule.attempt", + "after": "scheduledRetryAttempt: schedule.attempt + 1", + "keeperAssertionLine": 10386, + "selection": "Only exact failed/process_lost keeper selected; all other cases are unselected.", + "expectedAttempt": 1, + "observedAttempt": 2 + } +} diff --git a/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery.md b/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery.md new file mode 100644 index 00000000000..5174228c42f --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r16-heartbeat-recovery.md @@ -0,0 +1,84 @@ +# Heartbeat recovery test audit — 2026-10-12 + +Parent: #16086 (`codex/test-audit-r15-environment-runtime`), exact head +`c0414ac02e625a1e635857895b74581c37201500`. + +## Change and keeper + +The standalone `re-enqueues continuation for stranded in-progress work with no active run` +case at `heartbeat-process-recovery.test.ts:11192–11221` repeats the existing +`re-enqueues stranded in-progress work after a failed/process_lost run before escalating` +table row at 10360. + +Both call `seedStrandedIssueFixture` with `status: in_progress` and +`runStatus: failed`. The standalone test omits `runErrorCode`; the helper at 1132–1136 +persists `process_lost` as its default. The table supplies that same value. +Every other supplied argument, default, and owner action matches. Generated +record IDs vary between independent fixture runs. + +Move five unique assertion lines into the keeper: a truthy retry ID and +`{ status: scheduled_retry, scheduledRetryAttempt: 1 }`. Existing keeper checks +retain zero dispatch, one continuation, no escalation, exact issue IDs, two +agent runs, `transient_failure`, and absent `modelProfile`. The keeper also +checks source task/run binding, no recovery children, and eventual settlement. +The failed/adapter_failed and timed_out/adapter_timed_out rows remain and gain +the durable retry assertions. + +One test file: +5/−31 lines, net 26 fewer test lines. One registered execution +is retired (399→398). This is execution retirement, with no production change. +Crash/restart, ownership, cancellation, persistence, retry-budget, and failure +isolation contracts remain. No CI time saving is claimed from local samples. + +## Exact-parent sequential proof + +| Phase | Passed | Failed | Filtered/pending | +|---|---|---|---| +| Full baseline | 399 | 0 | 0 | +| Full candidate | 398 | 0 | 0 | +| Full revert | 399 | 0 | 0 | +| Temporary production mutation, exact keeper | 0 | 1 | 397 | +| Owner restored, full candidate | 398 | 0 | 0 | + +The mutation changes the real scheduler write at `heartbeat/retries.ts:1049` +from `schedule.attempt` to `schedule.attempt + 1`. The exact keeper fails at +the moved retry-state assertion, candidate line 10386, expecting attempt 1 and receiving 2. Only that keeper is +selected; other cases are filtered. The persisted attempt feeds durable +failure-retry accounting in `execution-recovery-attempt.ts:40–49`. +The recovery/scheduler owner is not mocked. The mutation is absent from this PR. +After restoring the owner, all 398 candidate cases pass. Both source files were +then restored byte-for-byte and the checkout was clean before branch creation. +The final patch is the exact candidate bytes that passed restoration. + +Runtime-title comparison removes only the standalone registration and adds +no cases. The original full-file ledger contains 249 declarations / 399 runtime +cases; the candidate has 248 / 398. All declarations were reviewed. Near-overlaps +with different queued wakes, timer claims, continuation ancestry, or crash points +were retained. + +```sh +PAPERCLIP_HOME=/private/tmp/r16-hbr/h \ +PAPERCLIP_CONFIG=/private/tmp/r16-hbr/h/config.json \ +PAPERCLIP_INSTANCE_ID=heartbeat-recovery-audit TMPDIR=/private/tmp/r16-hbr/t NODE_ENV=test \ +node node_modules/vitest/vitest.mjs run --project @paperclipai/server \ +server/src/__tests__/heartbeat-process-recovery.test.ts --exclude '**/dist/**' +``` + +Full repository typecheck/build/test preflight is delegated to isolated CI, +as in the parent audit. The local worktree uses shared external dependency +symlinks, and preflight rewrites workspace links. Every CI gate must pass +before a green handoff. The full owner uses the checked-in server configuration +and its real embedded Postgres. + +## Hashes and receipts + +- Parent test SHA-256: `bf7c888eaba44a56d12823bb948026d5db44f2249a75a6cf083343edbd7c0673`. +- Candidate test SHA-256: `645fd53b52e513e97c83f1e074dc1bded0749be18fb086138d154c19ea18d1d6`. +- Restored production retry owner SHA-256: `9f29feb6d986f281cbfd849e0152f49cf56e18974c6df1bc801010dcce4c877a`. + +Machine receipts are in `2026-10-12-test-audit-r16-heartbeat-recovery-controls.json`. +Full JSON/log output, patch, exact keeper map, and whole-file ledger are in +`/private/tmp/r16-heartbeat-recovery/`. The earlier read-only discovery is in +`/private/tmp/heartbeat-recovery-full-audit/`. This parent replay uses fresh +sequential controls; it does not reuse the earlier parent proof. + +Model: OpenAI `gpt-6.1-sol`; the host does not expose its context-window size. diff --git a/doc/plans/2026-10-12-test-audit-r17-issue-detail.md b/doc/plans/2026-10-12-test-audit-r17-issue-detail.md new file mode 100644 index 00000000000..1f18c495285 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r17-issue-detail.md @@ -0,0 +1,41 @@ +# IssueDetail test audit — 2026-10-12 + +Parent: #16088, `codex/test-audit-r16-heartbeat-recovery`, exact head +`a454638931ee8a2e40347aef1ab4fbf17d8acf9f`. + +## Retained contracts + +- The empty-subtask keeper retains the default task thread checks after the + first flush, and default file-viewer/plan-query checks after the second. + Its explicit empty list matches the shared beforeEach default. +- The first-task properties-button keeper uses the same first-task input. + It now checks closePanel before the toggle, then still proves opt-in opening. +- Three repeated renders disappear: 140→137 cases, 109→106 declarations, + +14/-57 test lines (43 fewer). All 17 parameterized declarations remain. + The full ledger and AST proof remain in temporary receipts, not this PR. + +## Exact-parent proof + +| Full owner phase | Passed | Failed | +|---|---|---| +| Baseline | 140 | 0 | +| Candidate | 137 | 0 | +| Reverted test | 140 | 0 | +| Default thread forced to legacy | 108 | 29 | +| File viewer forced on | 136 | 1 | +| Plan panel forced to render/query | 136 | 1 | +| Panel close paths removed | 135 | 2 | +| Production restored, candidate | 137 | 0 | + +Each production mutation fails its intended moved keeper assertion at lines +2463, 2470, 2473 or 4299. Each owner restoration is byte-for-byte. No cases +are skipped. `pnpm check:token-gates`, UI typecheck and diff-check pass. +The normal command from ui is `pnpm exec vitest run src/pages/IssueDetail.test.tsx`. + +Parent test SHA-256: `06b026c4bd296848011d1cc30f89711453a9db0c1e4c743fa22ae653b3e942d7`. +Candidate SHA-256: `530b6046ce0c15ece4304bc49f711af6fc95ebd62ef5b0aa0515b96ce2d94995`. +Restored owner SHA-256: `17b28ad871c2f00fa2d1153d50f151ebd0bed68c85d5a0f2a50164a2f1a612b2`. +Receipts: `/private/tmp/test-audit-r17-issue-detail-replay/receipt.json`. +No production change is included. No CI speedup is claimed from local samples. +Full repository preflight stays in isolated CI because local dependency links +are shared; it would rewrite those links. CI must pass before green handoff. diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig-controls.json b/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig-controls.json new file mode 100644 index 00000000000..c8b438ce094 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig-controls.json @@ -0,0 +1,530 @@ +{ + "audit": { + "file": "ui/src/components/AgentConfigForm.render.test.tsx", + "base": "e3effbb9e5c66b838f67fd487987c8bf1007e413", + "originalSha256": "57282ae3afe8c55ac118f3a658152d8df500a87b15383c8f6b0530a7fdcfb8a0", + "candidateSha256": "132112f3d2bb74d0844673a39ce31ffca1306e0b82d9846f3e0cafaf83e81708", + "allOtherProgramAstUnchanged": true, + "allOtherSourceBytesUnchanged": true, + "newHelpers": 0, + "registeredInvocationsRemoved": 4, + "rootRendersRemoved": 4, + "netTestLinesRemoved": 44, + "migrations": [ + { + "retired": "keeps secret access out of the main Configuration content", + "keeper": "hides the environment override when Local is the only configured environment", + "exactSetupStatements": 2, + "sameEnclosingHooks": true, + "exactCleanupStatements": 0, + "migratedGuards": [ + "expect(result.container.textContent).not.toContain(\"Secret access\");" + ], + "exactDuplicateGuardAlreadyKept": [], + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1 + }, + { + "retired": "starts a login session for the effective sandbox and shows the code and the authentication URL", + "keeper": "shows a Cancel affordance while a login is active and cancels the session", + "exactSetupStatements": 5, + "sameEnclosingHooks": true, + "exactCleanupStatements": 0, + "migratedGuards": [ + "expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(\"company-1\", \"codex_local\", {\n environmentId: \"sandbox-1\",\n });", + "expect(result.container.textContent).toContain(\"WXYZ-1234\");", + "expect(result.container.textContent).toContain(\"https://auth.example.test/device\");" + ], + "exactDuplicateGuardAlreadyKept": [], + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1 + }, + { + "retired": "shows the authorization URL and a browser-code input for a Claude sandbox", + "keeper": "cancels an active Claude login and returns to its start state", + "exactSetupStatements": 6, + "sameEnclosingHooks": true, + "exactCleanupStatements": 0, + "migratedGuards": [ + "expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith(\"company-1\", {\n environmentId: \"sandbox-1\",\n });", + "expect(result.container.textContent).toContain(\"https://claude.example.test/authorize\");", + "expect(\n result.container.querySelector('input[aria-label=\"Browser code\"]'),\n ).toBeTruthy();", + "expect(result.container.textContent).not.toContain(\"not encrypted\");" + ], + "exactDuplicateGuardAlreadyKept": [], + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1 + }, + { + "retired": "keeps the non-path ACP controls visible when the policy hides the engine choice", + "keeper": "hides the host path and execution-engine fields for claude_local when the policy is on", + "exactSetupStatements": 6, + "sameEnclosingHooks": true, + "exactCleanupStatements": 0, + "migratedGuards": [ + "expect(labels).toContain(\"ACP session mode\");", + "expect(labels).toContain(\"ACP non-interactive permissions\");" + ], + "exactDuplicateGuardAlreadyKept": [], + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1 + } + ] + }, + "verifierSha256": "084bdbc359e510397ef64daeb24e259efbc62c8b5d2c56d3e09d7f2534f155aa", + "mutationVerifierSha256": "ca8463047109d38a447d93fb94a19bb8efe9b1df440e0abf91ab31a8c0a358a2", + "runs": [ + { + "kind": "baseline", + "total": 99, + "passed": 99, + "failed": 0, + "skipped": 0, + "jsonSha256": "16a733e7e1de263ce88f1fbd89b94d4844aab7b6e7eca1fd670899fd670e263f", + "logSha256": "3f6f04966d8971e70a782681fccde2afd1aee5099fd8f376197109ed2babd2e5" + }, + { + "kind": "candidate", + "total": 95, + "passed": 95, + "failed": 0, + "skipped": 0, + "jsonSha256": "7fdabb69061f6d6f072c17224832171a225ac9a992d53c94d050c277eb467120", + "logSha256": "2717b82c2589f41dad5d9e926a7a37dd4fd79594acc1e9e6d9614a89ae2b5841" + } + ], + "mutations": [ + { + "slug": "secrets", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "hides the environment override when Local is the only configured environment", + "mutation": { + "before": "if (!isCreate && props.content === \"secrets\")", + "after": "if (!isCreate && (props.content === \"secrets\" || props.content === undefined))", + "replacementCount": 1 + }, + "intendedGuard": "expect(result.container.textContent).not.toContain(\"Secret access\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "24cf0c3e870ddc964fe327426491a4bbdfde043206191df578686ffb089d2f8f", + "logSha256": "10c8a0e3b2aafe33ab63fce0881fdf2b066d1cee507ccc589db6076701da4161", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "a9af939b306874f9bc7ef93c6954f3a234aafbb252c14672097aa33732c86f90", + "logSha256": "7fa30fef5ccdfa2cb797df92c4b207dff1eac29c99b9dcefa7a977278a4c3593", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'Secret accessSecrets this agent can r\u2026' not to contain 'Secret access'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:822:46\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "codex-company", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "shows a Cancel affordance while a login is active and cancels the session", + "mutation": { + "before": "agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId, aiConnection })", + "after": "agentsApi.startAdapterAuthLogin(\"wrong-company\", adapterType, { environmentId, aiConnection })", + "replacementCount": 1 + }, + "intendedGuard": "expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "fabccb095c86b4151cb80e0446d83e60074a4da76e7f0a36d4fef0246a483696", + "logSha256": "3397bb075079467542110406669cb26177cbbcdee8c6e6c56a5f52ccaaafd30f", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "332cf3fce408da458c33289abaf343fbf76ffedc68d37ee532a5c43ffc09f93e", + "logSha256": "81892e052e638ba655789fcac2b9bf95b243d1bd628c86a72053ebd7c596a432", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected \"vi.fn()\" to be called with arguments: [ Array(3) ]\n\nReceived:\n\n 1st vi.fn() call:\n\n [\n- \"company-1\",\n+ \"wrong-company\",\n \"codex_local\",\n {\n+ \"aiConnection\": undefined,\n \"environmentId\": \"sandbox-1\",\n },\n ]\n\n\nNumber of calls: 1\n\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2233:10)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:1593:49\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "codex-adapter", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "shows a Cancel affordance while a login is active and cancels the session", + "mutation": { + "before": "agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId, aiConnection })", + "after": "agentsApi.startAdapterAuthLogin(companyId, \"wrong-adapter\", { environmentId, aiConnection })", + "replacementCount": 1 + }, + "intendedGuard": "expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "3acb41404a916f7bc11b1ff698bfbaaa22017c67835907e59283b7b1df5ea07c", + "logSha256": "6530c744b0788868f9123b062307dfd1fac3f40740e193943a81da360fb19f3a", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "b3cbd3bfee156e965bed6924beefc3f0e6009785f3dcade569f03b88fd5cba55", + "logSha256": "90eeb1ecbbce2406915a5123c24d1311016fc1fe3d878af795781da3c352a071", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected \"vi.fn()\" to be called with arguments: [ Array(3) ]\n\nReceived:\n\n 1st vi.fn() call:\n\n [\n \"company-1\",\n- \"codex_local\",\n+ \"wrong-adapter\",\n {\n+ \"aiConnection\": undefined,\n \"environmentId\": \"sandbox-1\",\n },\n ]\n\n\nNumber of calls: 1\n\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2233:10)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:1593:49\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "codex-environment", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "shows a Cancel affordance while a login is active and cancels the session", + "mutation": { + "before": "agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId, aiConnection })", + "after": "agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId: \"wrong-sandbox\", aiConnection })", + "replacementCount": 1 + }, + "intendedGuard": "expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "3afd47d7705415ceb4ad8b7ce1415bd61b0b202d65732881660d75e75910c536", + "logSha256": "874b11b544fc038fba5e9ff57b156229acbbefb1db8410bf1bebc6794d1e5f20", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "2750457c4f575d786dade8a250f50520e08b1e351c95d2562900a06ffc9ab769", + "logSha256": "845ff3e01a9b6640496b2f895ca31e169639a65e3bf8e94bb3a383026e232b8b", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected \"vi.fn()\" to be called with arguments: [ Array(3) ]\n\nReceived:\n\n 1st vi.fn() call:\n\n [\n \"company-1\",\n \"codex_local\",\n {\n- \"environmentId\": \"sandbox-1\",\n+ \"aiConnection\": undefined,\n+ \"environmentId\": \"wrong-sandbox\",\n },\n ]\n\n\nNumber of calls: 1\n\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2233:10)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:1593:49\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "codex-code", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "shows a Cancel affordance while a login is active and cancels the session", + "mutation": { + "before": "{prompt.code}", + "after": "{\"missing code\"}", + "replacementCount": 1 + }, + "intendedGuard": "expect(result.container.textContent).toContain(\"WXYZ-1234\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "f985ab64892d06589b60759cb68183f52c9128056b79c93e69880cb5ff8c3ad1", + "logSha256": "dba2f7e6b14ed30859608119e88ac17f20c3c6de3ad93727a357d3860dc5a592", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "3a7ecc1e9c5e40c93c8e92f9e3930a4d0a01a2f84f01ffed53541c3403be4737", + "logSha256": "77c403024242dedc9cfa765484486337c97a35133cea348ed4ec57caf26a760d", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'IdentityNameTitleReports toChoose man\u2026' to contain 'WXYZ-1234'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:1596:42\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "codex-url", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "shows a Cancel affordance while a login is active and cancels the session", + "mutation": { + "before": "{prompt.url}", + "after": "{\"missing url\"}", + "replacementCount": 1 + }, + "intendedGuard": "expect(result.container.textContent).toContain(\"https://auth.example.test/device\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "0a11ca18e53fda28ea9a2956ee7254ab1b9f40f32f772b530df169adb06569b9", + "logSha256": "1772bbd6667cd1f32383f0671a186bc263054ef434de07933bac4feda1e3d4b6", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "3cca5f95b518a31622ebf3f17758c4617fa40476623210da56eee539c5a1efb6", + "logSha256": "1bac45eb7f9b150cdc6a99067d62e588baeca3af41d148fe897d0a0fa5949332", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'IdentityNameTitleReports toChoose man\u2026' to contain 'https://auth.example.test/device'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:1597:42\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "claude-company", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "cancels an active Claude login and returns to its start state", + "mutation": { + "before": "agentsApi.startClaudeSetupTokenLogin(companyId, {", + "after": "agentsApi.startClaudeSetupTokenLogin(\"wrong-company\", {", + "replacementCount": 1 + }, + "intendedGuard": "expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "40ec576f564d239009569ca0033715f58f5b255f76ef22da20b134c2e490218f", + "logSha256": "075d5fe2d62f1b80f60d63e0ce37e540fffdf2e666b01a21740264e6d4075478", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "0bf3dff32509866e86a80d67029ee81a1437d335045a91cb6cdf7f6ce59b5982", + "logSha256": "7ae4dcd73616fa424d373204986a05e798f573b5659daef4ac58bc6f7b9d9fdf", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected \"vi.fn()\" to be called with arguments: [ 'company-1', \u2026(1) ]\n\nReceived:\n\n 1st vi.fn() call:\n\n [\n- \"company-1\",\n+ \"wrong-company\",\n {\n+ \"aiConnection\": undefined,\n \"environmentId\": \"sandbox-1\",\n },\n ]\n\n\nNumber of calls: 1\n\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2233:10)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:2782:54\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "claude-environment", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "cancels an active Claude login and returns to its start state", + "mutation": { + "before": "agentsApi.startClaudeSetupTokenLogin(companyId, {\n environmentId,", + "after": "agentsApi.startClaudeSetupTokenLogin(companyId, {\n environmentId: \"wrong-sandbox\",", + "replacementCount": 1 + }, + "intendedGuard": "expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "4250b6cc6d66dc4df5753f403ef6894ceacdbc94bbf7812c5788ae143201d617", + "logSha256": "b76c385cbee9d1e26130b8e0c8c5f61fb2b31523e2ca65e2fbbf7303037ff179", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "7dfc3e5efa6eeb0c719d717b901b31fca6ca4a5cff1eb817382fa932a90f983b", + "logSha256": "56c3a19a6f86d4cc69918c47afd81bc4b4d887f788c135c7b956ea4fc3c0903c", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected \"vi.fn()\" to be called with arguments: [ 'company-1', \u2026(1) ]\n\nReceived:\n\n 1st vi.fn() call:\n\n [\n \"company-1\",\n {\n- \"environmentId\": \"sandbox-1\",\n+ \"aiConnection\": undefined,\n+ \"environmentId\": \"wrong-sandbox\",\n },\n ]\n\n\nNumber of calls: 1\n\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2233:10)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:2782:54\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "claude-url", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "cancels an active Claude login and returns to its start state", + "mutation": { + "before": "{authorizationUrl}", + "after": "{\"missing authorization url\"}", + "replacementCount": 1 + }, + "intendedGuard": "expect(result.container.textContent).toContain(\"https://claude.example.test/authorize\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "8ee00e28a8630374d9bfe8c85d96a44121121377b89b7f845ee4b32d659833f5", + "logSha256": "35255166875e6184b0a10b1abc4c6f42a281d4217eeac6ba2d1bbda83620aaf9", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "b2aca78cbef836af30a95188def37939d9ea2d0af743392abe804ae1bf118605", + "logSha256": "bb383cfabd0dc16036f31324b47ad392159a9eb7db75c622e2c2e132c2d1ee63", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'IdentityNameTitleReports toChoose man\u2026' to contain 'https://claude.example.test/authorize'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:2787:42\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "claude-input", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "cancels an active Claude login and returns to its start state", + "mutation": { + "before": "aria-label=\"Browser code\"", + "after": "aria-label=\"Missing browser input\"", + "replacementCount": 1 + }, + "intendedGuard": "result.container.querySelector('input[aria-label=\"Browser code\"]')", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "cf8cb1ded0e9999bb966c94c1c38aa7a147d217a06ff4ebe647c4f03ed3e550f", + "logSha256": "dc8c2389ba9b411989fc0d60a27e489829b21de1001c94b908555c337dfc685e", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "1bffa0d67d82af2e69b851eea8a1d4b2c319f85cad4b9b9fdf36dc95c143bde5", + "logSha256": "13f2b561842f8803f084be9723c7042abf5c0be74b625c99effb180f0c57dc4d", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected null to be truthy\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:2790:7\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "claude-advisory", + "owner": "ui/src/components/AgentConfigForm.tsx", + "sourceSha256": "bf999102f39bfc762747ce5d598e1e6ec2f926ac5fb28de55fa1baf5493f28b1", + "title": "cancels an active Claude login and returns to its start state", + "mutation": { + "before": "{transportInsecure && (", + "after": "{true && (", + "replacementCount": 2 + }, + "intendedGuard": "expect(result.container.textContent).not.toContain(\"not encrypted\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "ce03b65cc51ee03992fec6dfa438798f57053b914aca0cb56a791a43817a04b7", + "logSha256": "1e6753c737dd67dafe5f8cc4f7ec40be3be0875a4b97f0668e8d457a31462d4f", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "7f3ed2737dd31b0caf900567545c59d50c2cbf9aca876fd35080f13c87fb8757", + "logSha256": "aca8606f2a3f8176814074b81c913ec3f702e44141cd2131e28c14de2ce695c7", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'IdentityNameTitleReports toChoose man\u2026' not to contain 'not encrypted'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:2793:46\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "acp-mode", + "owner": "ui/src/adapters/claude-local/config-fields.tsx", + "sourceSha256": "c7e5aa4093bf30d31d5d3b1de092abe767965b7c79143965f92f8c250bf916f6", + "title": "hides the host path and execution-engine fields for claude_local when the policy is on", + "mutation": { + "before": "label=\"ACP session mode\"", + "after": "label=\"Missing ACP mode\"", + "replacementCount": 1 + }, + "intendedGuard": "expect(labels).toContain(\"ACP session mode\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "f6011ba4b3d6f34b47bf64dd76424cfb674b81a6663a58df2dd3994b9f87fca7", + "logSha256": "0174f7e7bcb488d93a15306214bb579be7049bfba08e3a048c3183f79360b6b0", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "294e714633d60e3121afe528b1c461d32bf13e4407cbfbd0da2581fc6d22f1ad", + "logSha256": "90a2ade05e125113eabeb530edec54a30e21dabd097407bb285139f4d87a35af", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected [ Array(17) ] to include 'ACP session mode'\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2077:15)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:3599:20\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "acp-permissions", + "owner": "ui/src/adapters/claude-local/config-fields.tsx", + "sourceSha256": "c7e5aa4093bf30d31d5d3b1de092abe767965b7c79143965f92f8c250bf916f6", + "title": "hides the host path and execution-engine fields for claude_local when the policy is on", + "mutation": { + "before": "label=\"ACP non-interactive permissions\"", + "after": "label=\"Missing ACP permissions\"", + "replacementCount": 1 + }, + "intendedGuard": "expect(labels).toContain(\"ACP non-interactive permissions\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "f859cc875b6f5c83be73f63cf9122d2fe928ad225c6d815a9ae29f6fed365766", + "logSha256": "06269344f536044578526368812f6c89c02f8208b9258beb773d14043859720b", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "b8b76b1c053101b532aec230f5356cbea28900d0c40fb012e3a8cc9b93a1ff2d", + "logSha256": "590f6908806038ca072a41f61516323dcba661886afbab66c0e1cf280c596060", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected [ Array(17) ] to include 'ACP non-interactive permissions'\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:2077:15)\n at Proxy. (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/index.C4tKZ0yW.js:1944:14)\n at Proxy.methodWrapper (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js:1737:25)\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AgentConfigForm.render.test.tsx:3600:20\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + } + ], + "priorEvidenceUnchanged": [ + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appboundary-controls.json", + "sha256": "bad49e7bc92dea919df6b980bfa45e7452984cf31a6fd4655b355ba0444ff9a6" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appboundary.md", + "sha256": "cd6f173584db0d485004af6f85b1ce78fa65b19d8c6ef582b061f0d232c6c1ed" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appdetail-controls.json", + "sha256": "7906fde0d62e14a7511b5cf45f864c9386a4c377922333eefc7f7e703c832f6a" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appdetail.md", + "sha256": "715f23cd27502c92d0f5d1b83b91bcac03f254c73ab63b44181ae76939805229" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-attachments-controls.json", + "sha256": "c4ff66a3226e1edafd286cad0ca45c9445795b46944cf70d606c59a754913f3a" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-attachments.md", + "sha256": "d38715393c95306d10e871a5a2c9a32f105fa1452871cd7ad030d86dbbdb7416" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-controls.json", + "sha256": "0d445f6f066032f2d9cd724525a8d7d2290246cbfeee1f6ab4b44818467af2a7" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-route-controls.json", + "sha256": "0a72a7770ec749f2c0507a74d395e65d70b722d0b9d2a767a2c066a67ed46382" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-route.md", + "sha256": "9b5e1543163d6c9d0958aa3e377a593ec458f6674ce1ddb2bf3ca158786f09c4" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-sidebar-controls.json", + "sha256": "ae9f24746e67432cf6247c5da130b9c1bba46fc85112c0610d3f18545dfce326" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-sidebar.md", + "sha256": "5e267441c43aa8375fa5c54edcbd2879428d657708be9a2ab372aa66e4792886" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-timeline-controls.json", + "sha256": "6a86b07770f1cd8d758334fb61c46254197a7f533d24c056fc9a52c317488291" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-timeline.md", + "sha256": "612dc3ec4c3d435888055ba20a7f28230ce89ea8b071b8c38d1ea2c147074a96" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-transcript-controls.json", + "sha256": "9e4dbdc33806d6fcc912dc3aacc8691a77939e2ce2403032b2a8127c0e407e94" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-transcript.md", + "sha256": "9e4ac8c92a3a9eca727c29a77ae428702d19ecdee0cab0e3dfcda0a36bf004db" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui.md", + "sha256": "896b9928af85a4c7d588dfca93484980aadb238bd79d8142e671c62f0b191fa8" + } + ], + "ownerRestoredToBaseGitBytes": true, + "productionLinesChanged": 0 +} diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig.md b/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig.md new file mode 100644 index 00000000000..84eaf0b9da3 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-agentconfig.md @@ -0,0 +1,47 @@ +# Test audit, Round 7 UI — Agent configuration follow-up + +This separate packet removes **4 registered executions, 4 root renders and 44 net test lines** from `ui/src/components/AgentConfigForm.render.test.tsx`. It adds no helper and changes no production lines. Audit base: `e3effbb9e5c66b838f67fd487987c8bf1007e413`. Prior committed evidence remains byte-identical. Render/execution savings are per complete-suite invocation; no measured runtime or CI-speed claim is made. + +## Exact retirement map + +| Retired registration | Keeper | Observations retained | +|---|---|---| +| `keeps secret access out of the main Configuration content` | `hides the environment override when Local is the only configured environment` | expect(result.container.textContent).not.toContain("Secret access"); | +| `starts a login session for the effective sandbox and shows the code and the authentication URL` | `shows a Cancel affordance while a login is active and cancels the session` | expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith("company-1", "codex_local", { environmentId: "sandbox-1", });; expect(result.container.textContent).toContain("WXYZ-1234");; expect(result.container.textContent).toContain("https://auth.example.test/device"); | +| `shows the authorization URL and a browser-code input for a Claude sandbox` | `cancels an active Claude login and returns to its start state` | expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith("company-1", { environmentId: "sandbox-1", });; expect(result.container.textContent).toContain("https://claude.example.test/authorize");; expect( result.container.querySelector('input[aria-label="Browser code"]'), ).toBeTruthy();; expect(result.container.textContent).not.toContain("not encrypted"); | +| `keeps the non-path ACP controls visible when the policy hides the engine choice` | `hides the host path and execution-engine fields for claude_local when the policy is on` | expect(labels).toContain("ACP session mode");; expect(labels).toContain("ACP non-interactive permissions"); | + +Each of four pairs uses identical beforeEach/afterEach hooks and render/helper input expressions. The Local-only configuration keeper absorbs the Secret access exclusion. The Codex and Claude cancellation keepers retain their respective original provider-specific start API and displayed-code/submitted-browser-code observations before any Cancel click; their providers and lifecycle remain separate. Real runTest/startLogin helpers click Test/Sign in under the original QueryClient/Toast/Tooltip tree with fresh clients and roots. The managed-sandbox-only keeper absorbs both visible non-path ACP labels after the same Advanced/Advanced Run Policy toggles. AgentConfigForm.tsx passes the policy into real ClaudeLocalAdvancedFields from adapters/claude-local/config-fields.tsx, which retains non-path mode/permission fields while host paths are hidden. Existing enabled/disabled/loading, stored-token, terminal-state, submit, cancel-error and timeout cases remain unchanged. Two retired login cases avoid repeated environment-test/start-login API paths; polling request counts and time savings are not claimed. + +The AST verifier checks exact setup, shared enclosing hooks and matched cleanup where present, reconstructs candidate text solely from the declared registrations/assertion migrations, and requires exact byte equality plus full-program AST equality outside those edits. No fixture, helper, provider, action order, wait, assertion strength, timer or teardown changes. The IssueChatThread keeper is an existing complete subset proof; InlineEditor’s identical not-null guard is retained once. Every other case stays byte-identical. Default CI includes this file in `@paperclipai/ui` under `general-workspaces-a`, once across the two file shards; workflow/config unchanged. + +## Complete owner-file results + +| Run | Registered | Passed | Failed | Skipped | +|---|---:|---:|---:|---:| +| Baseline | 99 | 99 | 0 | 0 | +| Candidate | 95 | 95 | 0 | 0 | + +From `ui/`: `pnpm exec vitest run src/components/AgentConfigForm.render.test.tsx`. + +All 13 isolated positive controls passed. All 13 deliberate behavior regressions failed at their intended retained/migrated assertion, rather than setup, timeout or a different guard: + +| Regression | Intended guard | +|---|---| +| secrets | `expect(result.container.textContent).not.toContain("Secret access")` | +| codex-company | `expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(` | +| codex-adapter | `expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(` | +| codex-environment | `expect(mockAgentsApi.startAdapterAuthLogin).toHaveBeenCalledWith(` | +| codex-code | `expect(result.container.textContent).toContain("WXYZ-1234")` | +| codex-url | `expect(result.container.textContent).toContain("https://auth.example.test/device")` | +| claude-company | `expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith(` | +| claude-environment | `expect(mockAgentsApi.startClaudeSetupTokenLogin).toHaveBeenCalledWith(` | +| claude-url | `expect(result.container.textContent).toContain("https://claude.example.test/authorize")` | +| claude-input | `result.container.querySelector('input[aria-label="Browser code"]')` | +| claude-advisory | `expect(result.container.textContent).not.toContain("not encrypted")` | +| acp-mode | `expect(labels).toContain("ACP session mode")` | +| acp-permissions | `expect(labels).toContain("ACP non-interactive permissions")` | + +Exactly one production owner was temporarily changed per control. Every owner was restored byte for byte after each mutation and matched pinned Git bytes. Filtered controls select one keeper; unselected tests do not count as full-suite evidence. [Controls JSON](2026-10-12-test-audit-r7-ui-agentconfig-controls.json) records exact owner/source hashes, migration/input proof, complete-file run/log digests, mutation bodies, intended assertion failures and restoration receipts. Temporary raw verifiers and logs are identified by digests rather than committed in full. + +The earlier broad `pnpm test:run` was intentionally stopped without completion and is not claimed as passed. Separate packet results are focused proof, not a completed combined delivery-head replay or pending full CI. No push, rebase, PR or merge is included here. diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-appboundary-controls.json b/doc/plans/2026-10-12-test-audit-r7-ui-appboundary-controls.json new file mode 100644 index 00000000000..cafb0f6a273 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-appboundary-controls.json @@ -0,0 +1,137 @@ +{ + "audit": { + "file": "ui/src/components/AppErrorBoundary.test.tsx", + "base": "c39262305b6b48a5decd564af8369083cd972b33", + "originalSha256": "1cc53b4a6978c3c5e98761467208d8e2e9a165163956a64a33bb84cea2778327", + "candidateSha256": "51fce35eb3b3b2dd0e1aacdd09f14dcd208e2e6b557ab0e4de9a16606fa3b778", + "allOtherProgramAstUnchanged": true, + "allOtherSourceBytesUnchanged": true, + "newHelpers": 0, + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1, + "netTestLinesRemoved": 20, + "migrations": [ + { + "retired": "renders a reload prompt instead of a blank page when the shell throws in render", + "keeper": "reports one captured error and keeps the reload prompt", + "setupAstEqual": true, + "setupBindingNormalization": null, + "guards": [ + "expect(container.textContent).toContain(\"Paperclip hit an error\");", + "expect(container.textContent).toContain(\"Maximum update depth exceeded\");" + ], + "registeredInvocationsRemoved": 1 + } + ] + }, + "verifierSha256": "0f1b173c8a399bb8c3e831edac6805634304a749faffcfff7b8b1900da3d34a3", + "mutationVerifierSha256": "a05f5ba9071c0ce7ce8eac1bc0f6c7dd83401c4e720816389d779f8919312064", + "runs": [ + { + "kind": "baseline", + "jsonSha256": "c1a2a5f2da21b5a730f174b628b23c1b01e682b2b8cc10ab8f332dbcede339bd", + "logSha256": "475145ce3b58595c9e76cbed628598ecc97a3d5cc835b1e374a10507e72ba9f7", + "total": 6, + "passed": 6, + "failed": 0, + "skipped": 0 + }, + { + "kind": "candidate", + "jsonSha256": "b27bec00f8765cc28254633e2752f96f15d6f0734cd59b4e0c10267ba138ff46", + "logSha256": "bba83ddd4f7aae20381ef90bfd2f17f6b44818e71994c5f716ea17df8c10c057", + "total": 5, + "passed": 5, + "failed": 0, + "skipped": 0 + } + ], + "mutations": [ + { + "slug": "title", + "owner": "ui/src/components/AppErrorBoundary.tsx", + "sourceSha256": "72bfc237fd0760e8432a9948e3423b6cab6881507f28815240f585f46e3ceb7c", + "title": "reports one captured error and keeps the reload prompt", + "mutation": { + "before": "Paperclip hit an error", + "after": "Unexpected title", + "replacementCount": 1, + "additionalPreClosingTagReplacement": false + }, + "intendedGuard": "expect(container.textContent).toContain(\"Paperclip hit an error\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "85d36ca8369f85ceb5e9173db5670b2d5ccf3c843cf26b3b1fb0ddb73a59a029", + "logSha256": "088a6c3c7dafb9546d8c32db0829797a2917cba4ce2e78c0215529c99d9096f2", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "b82c5bc57625cae251acafe46aa64021129848550e43cb66c19996b10f7ced0f", + "logSha256": "5f8f8ad95575b5920ae4574c4c04d7d7a6b3db3262c293973953b4f56f2f6604", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'Unexpected titleSomething went wrong \u2026' to contain 'Paperclip hit an error'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AppErrorBoundary.test.tsx:53:35\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:1628:35\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:26\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3321:20\n at new Promise ()\n at runWithCancel (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3316:10)\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3301:20\n at new Promise ()\n at runWithTimeout (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3259:10)\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3882:64" + ] + } + }, + { + "slug": "detail", + "owner": "ui/src/components/AppErrorBoundary.tsx", + "sourceSha256": "72bfc237fd0760e8432a9948e3423b6cab6881507f28815240f585f46e3ceb7c", + "title": "reports one captured error and keeps the reload prompt", + "mutation": { + "before": "{error.message}", + "after": "{\"hidden detail\"}", + "replacementCount": 1, + "additionalPreClosingTagReplacement": false + }, + "intendedGuard": "expect(container.textContent).toContain(\"Maximum update depth exceeded\")", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "a11dcafca2602e4baed9feff4925dcca4c09ceb46a36b5baf782742274fe467d", + "logSha256": "23301c06f7a3c46599867040f2fe947f36fe6bf9c33c66e6c0d465c9b7a97746", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "3d0fdd9f0f6fa788decca2bdf816bee3bcbe4f2d5836ba6582e8e2860369d974", + "logSha256": "31c8775a1049760ef218a4d1bb14df2dde621652cd58f1b53c24651a4dfa0d3a", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected 'Paperclip hit an errorSomething went \u2026' to contain 'Maximum update depth exceeded'\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/AppErrorBoundary.test.tsx:54:35\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:1628:35\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:26\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3321:20\n at new Promise ()\n at runWithCancel (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3316:10)\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3301:20\n at new Promise ()\n at runWithTimeout (file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3259:10)\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:3882:64" + ] + } + } + ], + "priorEvidenceUnchanged": [ + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appdetail-controls.json", + "sha256": "7906fde0d62e14a7511b5cf45f864c9386a4c377922333eefc7f7e703c832f6a" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-appdetail.md", + "sha256": "715f23cd27502c92d0f5d1b83b91bcac03f254c73ab63b44181ae76939805229" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-controls.json", + "sha256": "0d445f6f066032f2d9cd724525a8d7d2290246cbfeee1f6ab4b44818467af2a7" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-transcript-controls.json", + "sha256": "9e4dbdc33806d6fcc912dc3aacc8691a77939e2ce2403032b2a8127c0e407e94" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui-transcript.md", + "sha256": "9e4ac8c92a3a9eca727c29a77ae428702d19ecdee0cab0e3dfcda0a36bf004db" + }, + { + "file": "doc/plans/2026-10-12-test-audit-r7-ui.md", + "sha256": "896b9928af85a4c7d588dfca93484980aadb238bd79d8142e671c62f0b191fa8" + } + ], + "ownerRestoredToBaseGitBytes": true, + "productionLinesChanged": 0 +} diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-appboundary.md b/doc/plans/2026-10-12-test-audit-r7-ui-appboundary.md new file mode 100644 index 00000000000..a6780b611f0 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-appboundary.md @@ -0,0 +1,33 @@ +# Test audit, Round 7 UI — App error boundary follow-up + +This isolated follow-up removes **1 registered executions, 1 root renders and 20 net test lines** from `ui/src/components/AppErrorBoundary.test.tsx`. It changes no production lines and adds no helper. The pinned audit base is `c39262305b6b48a5decd564af8369083cd972b33`. Prior UI proof remains unchanged. Registration/render reductions are per complete-suite invocation; no measured runtime or CI-speed claim is made. + +## Exact migrations + +| Retired registration | Keeper | Original observations retained | +|---|---|---| +| `renders a reload prompt instead of a blank page when the shell throws in render` | `reports one captured error and keeps the reload prompt` | expect(container.textContent).toContain("Paperclip hit an error");; expect(container.textContent).toContain("Maximum update depth exceeded"); | + +main.tsx wraps the app in AppErrorBoundary. The paired tests create the same root and render the same BoomRender throw under the real boundary, with identical hooks/unmount. Move title and original error detail before retained capture/count/metadata/reload-button checks. The retired reload-button expression already matches the retained expression exactly. Effect errors, healthy children and DOM insertion/translation errors remain distinct and unchanged. + +The verifier reconstructs the exact candidate text from only the declared removals and insertions, requires byte equality, and compares the entire program AST after the same migrations. Every unrelated source byte, hook, mock, test body and binding remains unchanged, outside the audit receipt’s explicitly declared duplicate-title or blank-line cleanup where applicable. Setup ASTs are equal except the explicitly normalized attachment callback binding described above where applicable. No tests are skipped, softened, suppressed or added. Each file remains included in `@paperclipai/ui` under CI `general-workspaces-a`; Vitest file sharding selects it once across the two shards, with no workflow/config change. + +## Complete-file evidence + +| Run | Registered | Passed | Failed | Skipped | +|---|---:|---:|---:|---:| +| Baseline | 6 | 6 | 0 | 0 | +| Candidate | 5 | 5 | 0 | 0 | + +From `ui/`: `pnpm exec vitest run src/components/AppErrorBoundary.test.tsx`. + +All 2 separately selected positive keeper controls passed. Every deliberate behavior mutation failed at the corresponding intended keeper assertion, not setup, timeout or another guard: + +| Mutation | Intended assertion | +|---|---| +| title | `expect(container.textContent).toContain("Paperclip hit an error")` | +| detail | `expect(container.textContent).toContain("Maximum update depth exceeded")` | + +Every mutated production owner was restored byte for byte after each mutation and checked against pinned Git bytes. Name-filtered controls select exactly one keeper; their unselected tests are not full-suite evidence. [Compact controls](2026-10-12-test-audit-r7-ui-appboundary-controls.json) preserve source/owner hashes, exact migration map, body verifier digests, complete run/log digests, intended failure receipts and restoration checks. Temporary AST/receipt scripts initially needed corrections for chained assertions or statement position; those verifier attempts produced no accepted test evidence. + +The earlier broad local `pnpm test:run` was intentionally stopped before completion; it is not claimed as a pass. These focused runs do not substitute for pending delivery-head CI. No push, rebase, pull request or merge is included in this packet. diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-appdetail-controls.json b/doc/plans/2026-10-12-test-audit-r7-ui-appdetail-controls.json new file mode 100644 index 00000000000..86e3b3a6fac --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-appdetail-controls.json @@ -0,0 +1,103 @@ +{ + "audit": { + "file": "ui/src/pages/apps/AppDetail.test.tsx", + "base": "81cd7ab0ca0bdc55ea0927bc44fcf6e764a530ed", + "originalSha256": "35c4d4828daa80d37b8319fa1dfd4c049c03e26a780c6ce3b1e64990f32be9c6", + "candidateSha256": "f41c56cc4bae6e496067c93b58419ed3acfc6938f7a1ea7169ac2b1fa9ae3e61", + "retired": "removes the separate always-installed controls from Permissions", + "keeper": "renders searchable action groups with three-way permission toggles", + "identicalInitialSetup": true, + "migratedExactAssertions": [ + "expect(container.textContent).toContain(\"Which agents can use this connection?\");", + "expect(container.textContent).not.toContain(\"Always installed\");", + "expect(putConnectionInstallsMock).not.toHaveBeenCalled();" + ], + "allOtherSourceBytesUnchanged": true, + "allOtherProgramAstUnchanged": true, + "newHelpers": 0, + "registeredInvocationsRemoved": 1, + "completeRenderCallsRemoved": 1, + "netTestLinesRemoved": 7 + }, + "verifierSha256": "8252a8caeedbbbc3b6b630376b8207b4f78021b719ac601e061d394535ecea0e", + "runs": [ + { + "kind": "baseline", + "jsonSha256": "74eb47f37400e2409108c4a88bbbd1f0c25d12d0e7f9c8576aad598758472ea3", + "total": 78, + "passed": 78, + "failed": 0, + "skipped": 0 + }, + { + "kind": "candidate", + "jsonSha256": "50a8a2e03f8521f125011f82c0ee816b55e1bd1d751e9d3fd2fe3fd71fad2314", + "total": 77, + "passed": 77, + "failed": 0, + "skipped": 0 + } + ], + "mutations": [ + { + "slug": "appdetail-old-install-copy", + "owner": "ui/src/pages/apps/app-detail/PermissionsPanel.tsx", + "title": "renders searchable action groups with three-way permission toggles", + "sourceSha": "b3b96c2e2fbafee17e0b01c9127c30bd4a0a787f2481a264001fd6c2bce8e379", + "replacements": 1, + "mutation": { + "before": ">Which agents can use this connection?", + "after": ">Which agents can use this connection?Always installed" + }, + "restoredByteForByte": true, + "positive": { + "exit": 0, + "caseStatus": "passed", + "jsonSha256": "a97f5dd88c5febcf4fe906f34ea7efd0672781b95b2757aeb9cbcb0bd9247edf", + "logSha256": "26c7c2d6e1c6edca6e56e0f21ecbe3897c28c3f7ad1300dd07886fe905d0c025" + }, + "negative": { + "exit": 1, + "caseStatus": "failed", + "intendedAssertionFailure": "AssertionError: expected 'GitHubConnected2 actions availableUnv\u2026' not to contain 'Always installed'", + "jsonSha256": "a444c1fbc181dabecbcf67ce1ea7da5659d2bf3cc85a5983fc02fd49418ea66d", + "logSha256": "feef1c4c056f2bba80e2bda438edc27e099da834f11d3c5b1ccaf1e13da73b0d" + } + }, + { + "slug": "appdetail-install-on-mount", + "owner": "ui/src/pages/apps/AppDetail.tsx", + "title": "renders searchable action groups with three-way permission toggles", + "sourceSha": "ee8ab6e844ce11bf5d13954f0a73de3e42de61a933deb01ad9e40cb76e3b93d1", + "replacements": 1, + "mutation": { + "before": " const activeTab: AppTabKey | null = isAppTabKey(tab) ? tab : null;", + "after": " useEffect(() => { void toolsApi.putConnectionInstalls(connectionId, []); }, [connectionId]);\n const activeTab: AppTabKey | null = isAppTabKey(tab) ? tab : null;" + }, + "restoredByteForByte": true, + "positive": { + "exit": 0, + "caseStatus": "passed", + "jsonSha256": "b87e88f3d6a624d21ce9795889d002fb0bb8a08ad7b27bd5cad6420b03ec6f04", + "logSha256": "0d0d9d88fd6da2c652fb4119fb2ed3cef7c624514e8b0501f3daf62578ec99ac" + }, + "negative": { + "exit": 1, + "caseStatus": "failed", + "intendedAssertionFailure": "AssertionError: expected \"vi.fn()\" to not be called at all, but actually been called 1 times", + "jsonSha256": "7d04f5db02f84793fe53914e4f8cfc98f7644766146861857afcf586809b6d16", + "logSha256": "cc98a64e983162d6330263ea4433cc6b17c4925b3b9806794996dafb9b7acd0c" + } + } + ], + "allProductionOwnersRestored": true, + "productionLinesChanged": 0, + "priorUiProofUnchanged": { + "commit": "81cd7ab0ca0bdc55ea0927bc44fcf6e764a530ed", + "reportSha256": "896b9928af85a4c7d588dfca93484980aadb238bd79d8142e671c62f0b191fa8", + "controlsSha256": "0d445f6f066032f2d9cd724525a8d7d2290246cbfeee1f6ab4b44818467af2a7" + }, + "ciInclusion": "@paperclipai/ui project in general-workspaces-a; complete file remains included; no routing changes", + "netCombinedTestLinesRemoved": 371, + "combinedRegisteredInvocationsRemoved": 2 +} diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-appdetail.md b/doc/plans/2026-10-12-test-audit-r7-ui-appdetail.md new file mode 100644 index 00000000000..91ceee0a2af --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-appdetail.md @@ -0,0 +1,47 @@ +# Test audit, Round 7 UI — AppDetail follow-up + +This follow-up removes **one duplicate registered invocation, one complete render, and seven net test lines** from `ui/src/pages/apps/AppDetail.test.tsx`. It adds no helper and changes zero production lines. Combined with the [approved first UI batch](2026-10-12-test-audit-r7-ui.md), the branch removes **371 net test lines and two registered invocations**. The original UI report, controls and test bytes remain unchanged. + +The follow-up audit base is local UI commit `81cd7ab0ca0bdc55ea0927bc44fcf6e764a530ed`. This file has the same original bytes at Round 6 parent `e868e6645ce0d598d756d3458f0b3d71afd6db4a`. + +## Exact retirement and keeper + +Retired: `removes the separate always-installed controls from Permissions`. + +Keeper: `renders searchable action groups with three-way permission toggles`. + +Both cases use the same enclosing beforeEach, connection, catalog, profiles, grants, install state and spies. Their two pre-assertion statements are byte-identical: select the permissions tab, then await the same complete AppDetail render. Neither case changes a fixture or takes a user action before its assertions. Both permission observations were updated by [PR #12802](https://github.com/paperclipai/paperclip/pull/12802). + +All three retired observations move unchanged into the keeper, immediately after render and before its existing assertions: + +- The agent-access question is visible. +- The old Always installed copy is absent. +- The connection-install mutation API has not been called. + +The keeper also retains its original fixed action-group counts, action names, review prompt, search input, selected Allowed/Ask first radio states, two Test actions, old-copy exclusions and card-layout exclusion. Viewer gates, picked-agent mode, required installed agents, access mutations, AI credential routing, task-grant removal and failure paths remain separate cases. + +App.tsx mounts AppDetail at `apps/:connectionId/:tab`. AppDetail's actual permissions path renders AgentAccessSection and ActionsSection in PermissionsPanel. AgentAccessSection supplies the question and access controls. The install API is used by the explicit AI-connection save path, so a no-write-on-read check is a separate observation from absent UI copy. Both fixed guards remain in the keeper. The grouped action-control sibling suite remains unchanged. + +The test stays in the `@paperclipai/ui` project and the `general-workspaces-a` lane. This cut removes a collected test and its full query-client/root render; it changes no suite inclusion or CI routing. No wall-time improvement is claimed. + +## Proof + +| Complete AppDetail file | Registered cases | Passed | Failed | Skipped | +|---|---:|---:|---:|---:| +| Baseline | 78 | 78 | 0 | 0 | +| Candidate | 77 | 77 | 0 | 0 | + +Run from `ui/`: `pnpm exec vitest run src/pages/apps/AppDetail.test.tsx`. + +The body audit checks the two identical setup statements, copies the three exact assertion ASTs, and compares full original/candidate programs outside the declared removal and insertion. It also reconstructs the exact candidate text from only those source edits and requires byte equality. Every other source byte remains unchanged, including all mock factories, beforeEach, render helper, spies, waits, cleanup and original keeper assertions. No withPermissions helper exists in these pinned file bytes; no such setup was added or changed. + +Both positive keeper controls passed. Two deliberately broken production owners failed at the intended migrated guard: + +| Production regression | Direct keeper failure | +|---|---| +| Reintroduce Always installed copy beside the access question in PermissionsPanel | Fixed negative old-copy assertion. | +| Write connection installs from an AppDetail mount effect | Fixed no-install-API-call assertion. | + +Each negative control selected only the keeper. Its unselected cases do not count as complete-file proof. Neither mutation failed on setup, timeout or an unrelated action assertion. Both production owners were restored byte for byte and checked against pinned Git source. + +[Follow-up controls](2026-10-12-test-audit-r7-ui-appdetail-controls.json) retain exact original/candidate hashes, the unchanged prior proof hashes, body-audit digest, complete-file results, mutation descriptions, intended failures, run/log digests and source-restoration receipts. Raw verifiers and logs remain temporary audit packets; their digests do not retain complete payloads. Latest delivery-head replay, typecheck, token gates, build and CI status belong in the PR. The broad local default run was stopped before completion as recorded by the coordinator; it is not a full-suite pass. diff --git a/doc/plans/2026-10-12-test-audit-r7-ui-attachments-controls.json b/doc/plans/2026-10-12-test-audit-r7-ui-attachments-controls.json new file mode 100644 index 00000000000..31b88e75810 --- /dev/null +++ b/doc/plans/2026-10-12-test-audit-r7-ui-attachments-controls.json @@ -0,0 +1,168 @@ +{ + "audit": { + "file": "ui/src/components/IssueAttachmentsSection.test.tsx", + "base": "c39262305b6b48a5decd564af8369083cd972b33", + "originalSha256": "938aead11f9ccd2246049fe7ebe82800c3bdc8ff7c17fc20017d03ecc659d73a", + "candidateSha256": "e7cc76d20708169e8eadba692d6f9bc9e7ba32cb18075e72c8aaceb420705a94", + "allOtherProgramAstUnchanged": true, + "allOtherSourceBytesUnchanged": true, + "newHelpers": 0, + "registeredInvocationsRemoved": 1, + "rootRendersRemoved": 1, + "netTestLinesRemoved": 22, + "migrations": [ + { + "retired": "renders video attachments through the same player used for artifact outputs", + "keeper": "lets video attachments open the shared media gallery", + "setupAstEqual": false, + "setupBindingNormalization": "Inline fresh vi.fn becomes existing keeper fresh named vi.fn; provider/attachment/delete callback/flush unchanged", + "guards": [ + "const video = container.querySelector(\"video\");", + "expect(video?.getAttribute(\"src\")).toBe(\"/api/attachments/video-attachment/content\");", + "expect(video?.getAttribute(\"controls\")).not.toBeNull();", + "expect(fetchSpy).not.toHaveBeenCalled();" + ], + "registeredInvocationsRemoved": 1 + } + ] + }, + "verifierSha256": "0f1b173c8a399bb8c3e831edac6805634304a749faffcfff7b8b1900da3d34a3", + "mutationVerifierSha256": "a05f5ba9071c0ce7ce8eac1bc0f6c7dd83401c4e720816389d779f8919312064", + "runs": [ + { + "kind": "baseline", + "jsonSha256": "41996a845a818368c4fe0a7a4b31474ddc1b35c56c9aff524dcfae3218a1112c", + "logSha256": "cf1b9e033afd854462df31d4d65f4e604f317f6473c1f75c25b91ccf46e3e92c", + "total": 8, + "passed": 8, + "failed": 0, + "skipped": 0 + }, + { + "kind": "candidate", + "jsonSha256": "e9db5edd68d2e89898848aa90dabf75957fb703813c429472f80fbae92d194b4", + "logSha256": "67fe4596626962ac86f547b251f992158d5a28063a2c62962816433c3e837ed2", + "total": 7, + "passed": 7, + "failed": 0, + "skipped": 0 + } + ], + "mutations": [ + { + "slug": "source", + "owner": "ui/src/components/IssueAttachmentsSection.tsx", + "sourceSha256": "2ba7827325ed63d57fad24f31144e54c48c0618584c0cd013b26acf93e7f525b", + "title": "lets video attachments open the shared media gallery", + "mutation": { + "before": "", + "after": "", + "replacementCount": 1, + "additionalPreClosingTagReplacement": false + }, + "intendedGuard": "expect(video?.getAttribute(\"src\")).toBe(", + "restoredByteForByte": true, + "positive": { + "exit": 0, + "jsonSha256": "26bb673ede3119522a715a31928f8066c41c6538e78139e5a1dde1e879964fd5", + "logSha256": "5f31a93058981f8a4a0b9c109b851aad78153f734eb723cc04998fb3ffca555b", + "caseStatus": "passed" + }, + "negative": { + "exit": 1, + "jsonSha256": "41c490f0613aaf4958ad6ccc613dcf31053f2f528b1c7b2a252230470bc7d8ae", + "logSha256": "984bfe91fe92dcb4518504ae6fe21205466e3c775c2dde6806934471d73ab7d5", + "caseStatus": "failed", + "intendedFailureMessages": [ + "AssertionError: expected '/wrong-video.webm' to be '/api/attachments/video-attachment/con\u2026' // Object.is equality\n at /Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/ui/src/components/IssueAttachmentsSection.test.tsx:206:40\n at file:///Users/dotta/.codex/worktrees/test-audit-r7-ui/paperclip/node_modules/.pnpm/vitest@5.0.3_@opentelemetry+api@1.9.1_@types+node@24.13.3_jsdom@30.0.1_@noble+hashes@2.4.0__v_wr5mxfuu6kgge6st623q4v5egm/node_modules/vitest/dist/chunks/run.BTlFXlnv.js:2783:20" + ] + } + }, + { + "slug": "controls", + "owner": "ui/src/components/IssueAttachmentsSection.tsx", + "sourceSha256": "2ba7827325ed63d57fad24f31144e54c48c0618584c0cd013b26acf93e7f525b", + "title": "lets video attachments open the shared media gallery", + "mutation": { + "before": "", + "after": "