Commit Graph
6 Commits
Author SHA1 Message Date
DottaandClaude Opus 5.5 61fa13c6d8 test(chaos): add S1, a board Stop across a restart then a continue comment (#20161)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness drives real user actions across
control-plane restarts: comments, Retry, approvals and budget resumes
> - The board's Stop button on a running task was not exercised. The
cancel route stamps the stop as operator-initiated so that recovery
stands down instead of re-waking the agent
> - A restart right after a Stop is where recovery could wrongly restart
work the operator stopped, or lose the operator's later "continue"
direction
> - This pull request adds S1: stop the running run, fire a fault right
after, then comment to continue
> - The benefit is a graded check that a Stop holds across a restart and
that a later continue comment finishes the task

## Linked Issues or Issue Description

Refs #20071

**What is missing?**
The campaign's scenario list includes mid-run stops. C1 covers a mid-run
comment (a steer), but no workload clicks Stop. Without one, a
regression where recovery restarts operator-stopped work, or drops the
continue comment, would go unseen.

**Expected behavior**
- After the board stops a run and a restart follows, the stopped run
stays cancelled.
- Nothing runs until the operator comments.
- The continue comment then finishes the task.

## What Changed

- S1 workload ("Stop while running, then continue"): a brief task. At
its follow-up time, while a run is running, the observer posts `POST
/api/heartbeat-runs/:id/cancel` (the board's Stop) and records `stop`.
- New `stop` trigger, for S1 only: the planned fault fires 0–2 s after
the stop.
- The continue comment is posted only after the stop has settled: at
least 20 s after the stop, with no run running or queued and no fault in
progress. It asks the agent to continue and add a `CONTINUE-<nonce>`
line.
- The oracle requires:
  - status done;
  - the brief with the chaos and continue markers;
  - `stop_took_effect`: the stopped run ended `cancelled`;
- `nothing_ran_after_stop`: no run was created between the stop and the
continue comment.
- S1 is not a default workload. There is a README section.

## Verification

- `npx vitest run` in `tests/restart-chaos`: 205 passed. New tests
cover:
- the S1 grade (pass, a run restarted after the stop, the stop not
taking effect, no stop);
  - the trigger-to-workload pairing;
- an observer test that clicks Stop once and posts the continue comment
only after the 20 s settle.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`: clean.
- A live S1 wave runs after the current chaos wave. Its result will be
posted here before merge.

## Risks

- If the product moves the stopped run to a status other than
`cancelled`, `stop_took_effect` fails. That check is intentional.
- The continue comment waits for the stop to settle, so a run stuck
`queued` delays it. R5 and the wave deadline still bound the wait.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:52:48 -06:00
DottaandClaude Opus 5.5 0502cc9fac test(chaos): retry a blocked M1 subtask and check the parent's stall wake (#20147)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness grades the M1 scenario: a parent task
delegates two subtasks across restarts
> - When a subtask's run failed recovery, the harness only clicked Retry
on the parent. The parent's Retry is correctly held behind the blocked
subtask (#20109), so the task waited until its deadline
> - A real board user would also click the subtask's Retry. #20098 also
added a wake for a parent whose subtask stalls, and the grade did not
check it
> - This pull request clicks the board Retry on a blocked subtask
(graded as a needed Retry) and checks that the parent got exactly one
stall wake per stall
> - The benefit is that M1 grades the full user path and proves the
stall wake from the product's own activity record

## Linked Issues or Issue Description

Refs #20098, #20109, #20094

**What happened?**
In wave `w-20261109T003126-s9701`, the M1 parent t05 rested blocked
behind a blocked codex subtask. The harness clicked Retry on the parent.
The product held that Retry ("waiting on a blocked task") as designed.
Nothing retried the subtask, and the task waited 38 minutes until the
wave deadline. The wave also could not show whether the parent got the
new `issue_blocker_stalled` wake.

**Expected behavior**
The harness acts like the board: it clicks Retry on a blocked subtask
that offers one, and it counts that as a needed Retry. The M1 oracle
checks the parent's activity for one `issue.blocker_stalled_wake` per
stalled subtask.

## What Changed

- `observer.ts`:
- For M1, the observer checks the parent's subtasks every 10 s while the
parent is open, independent of the parent's rest window.
- Each blocked subtask is recorded as a stall: the subtask plus its
latest recovery action, from `GET /api/issues/:id/recovery-actions`.
- When a blocked subtask's interruption offers Retry, the harness posts
the board Retry. It clicks once per subtask; a click that was not
accepted can be tried again on a later check.
- The parent's `issue.blocker_stalled_wake` activity entries are
collected as `stallWakes` through `GET /api/issues/:id/activity`.
- `grader.ts`: R3 fails with `retry_needed_on_subtask_<n> (<ids>)` when
a subtask needed a board Retry, unless the parent's own Retry already
failed R3.
- `workloads.ts`: the new M1 check `parent_woken_once_per_stall` is
added when a subtask stalled. Each (subtask, recovery action) stall must
have exactly one matching wake. A stall with an unknown action matches
any wake for that subtask.

## Verification

- `npx vitest run` in `tests/restart-chaos`: 203 passed. New tests
cover:
- an observer test: a blocked subtask gets one Retry click, and the
parent is still watched;
  - the R3 subtask-retry grade;
- the stall-wake check: woken once, not woken, woken twice, two stalls
of one subtask with only one woken, and no stall.
- G1, which also has subtasks, is unaffected: the existing governance
tests pass.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`: clean.

## Risks

- The harness now acts on subtasks, which it did not before. It clicks
once per subtask and only when the product offers Retry.
- The stall-wake check reads activity through the board API. If the API
omits these entries for a board user, the check fails visibly instead of
passing.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:12:03 -06:00
DottaandClaude Opus 5.5 89265f7312 test(chaos): add R1, a code change in a fresh clone of a real repository (#20111)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness proves that real agent tasks survive
control-plane restarts
> - Its coding workloads run in a tiny chaoscli fixture. Real work
happens in large repositories, where workspace setup, copy and restore
cost more and fail differently
> - The campaign asks for real coding tasks in a fresh Paperclip clone,
run under restarts
> - This pull request adds the smallest support for that: a
`--workspace-repo` option and an R1 workload
> - The benefit is restart coverage for code changes in a real
repository, graded by running the agent's own test and a hidden case

## Linked Issues or Issue Description

Refs #20071, #20089

**What is missing?**
The harness can only seed the generated chaoscli fixture as the project
workspace. There is no way to run restart chaos against a code change in
a real repository.

**Expected behavior**
An operator can name a git repository. The harness clones it fresh as
the workspace, and a workload asks for a real code change in it with an
oracle that runs the result.

## What Changed

- `--workspace-repo <git repository>`: `seedChaosLab` clones it
(shallow, from committed HEAD, `--no-hardlinks`) as the project
workspace instead of the chaoscli fixture. Local only.
- R1 workload: the agent adds
`packages/shared/src/chaos/slug-<nonce>.ts` with `chaosSlug` and a
`node:test` file, and runs it. The nonce names keep concurrent tasks
from colliding in one repository.
- The R1 oracle runs the agent's test with `node --test` and a hidden
case against the module through Node's type stripping.
- Collect commands can take an `argv` function of the nonce.
- Config rules: R1 and `--workspace-repo` go together. B1 and B5 (which
need the chaoscli fixture) do not combine with it. R1 is not a default
workload.
- README section "Code change in a real repository".

## Verification

- `npx vitest run` in `tests/restart-chaos`: 195 passed, including new
tests for the config pairing, R1 grading with the nonce argv, and a
shallow clone of a fixture repository.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`: clean.
- I checked the oracle commands by hand on Node 26: `node --test` on a
`.ts` test and `node --input-type=module -e 'import … from "./…ts"'`
both work.
- A live R1 wave against a Paperclip clone runs after the current chaos
wave frees the harness slot. Its result will be posted here before
merge.

## Risks

- A shallow clone of a large repository adds seconds to wave seeding.
That is one clone per wave.
- The oracle uses Node's built-in type stripping. A runtime without it
fails `node_test` and `hidden_case`, which shows as an oracle failure,
not a false pass.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:55:02 -06:00
DottaandClaude Opus 5.5 c3c226d165 test(restart-chaos): governance (approval, budget hard stop) and upgrade mid-run scenarios (#20089)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness kills and restarts the control plane
during real agent tasks and grades every task
> - It covers run lifecycle, comments, delegation and long tools, but
not two areas that must also survive a restart: governance (approvals
and budget hard stops) and an upgrade in the middle of a run
> - An approval that disappears, runs twice or runs after a rejection,
or a budget pause that dispatches work, breaks governance. An upgrade
that cannot resume saved state breaks the upgrade-safe-saved-state rule
> - This pull request adds the harness support for both scenarios, using
only the paths a real board user and agent use
> - The benefit is that the next chaos waves can test these boundaries
and report failures with evidence

## Linked Issues or Issue Description

Refs #20084, #20067

**What happened?**
The restart chaos harness had no scenario for an approval-gated action
pending across a restart, for a budget hard stop crossed mid-run with a
restart, or for a server upgrade in the middle of a run. These
boundaries were untested.

**Expected behavior**
- **G1:** a pending approval survives K1, K2 or K4. Approving it after
the restart runs the gated action exactly once; rejecting it never runs
it.
- **H1:** a budget hard stop crossed mid-run pauses the company, nothing
dispatches while paused (also across a restart), and resuming continues
the work.
- **K14:** a wave started on an older build and upgraded mid-turn
resumes old-saved state on the new build with no Retry, no lost comments
and no stuck records.

**Steps to reproduce**
1. Run the harness commands in `tests/restart-chaos/README.md`
("Governance across restarts" and "Upgrade mid-run").
2. Each task is graded R1–R6 plus its oracle.

**Paperclip version or commit**
master at 4f7eee10c2.

## What Changed

- **G1 workload: approval across a restart.**
- The agent requests board approval through its `call_api` tool (`POST
/api/companies/{companyId}/approvals`, `request_board_approval`, linked
to its task), blocks, and ends its turn.
- A new `approval` trigger fires the fault once the observer sees the
approval pending.
- After the restart, with the server ready, the observer acts as the
board through `POST /api/approvals/:id/approve` or `/reject` (rejection
also cancels the task). The decision is fixed by the nonce.
- The oracle checks one request, still pending when decided after the
restart, the decision, and the gated subtask exactly once on approval
and never after rejection.
- R1 and R3 grade a rejected task against `cancelled`
(`expectedFinalStatus`).
- **H1 workload: budget hard stop across a restart.**
- Before launch the observer sets the company budget to observed spend
plus one cent through `PATCH /api/companies/:id/budgets`.
- A new `paused` trigger fires the fault once the company shows `paused`
/ `budget`.
- After the restart the pause must hold 30 s with the server ready. The
observer records runs that started while paused, then resolves the
incident with `raise_budget_and_resume`.
- The oracle checks the brief, the pause, no dispatch while paused, the
resume after the fault, and an active company at rest.
- H1 runs alone, at concurrency 1, and not with `--rc06-masked` (config
errors).
- **K14 fault: upgrade mid-run, with `--upgrade-from <older
checkout>`.**
  - The server starts from the older checkout.
- The first K14 stops it with SIGTERM and starts this checkout on the
same database, instance directory, port and secrets. The runner binary,
pinned Codex and source dependencies are prepared per checkout.
  - The receipt names both commits.
- K14 is local-only and needs `--upgrade-from` (and the reverse). It is
excluded from storm defaults. Preflight refuses an older checkout that
is not prepared.
- G1 and H1 are opt-in: they are not in the default workloads.
- README sections for both scenarios.

## Verification

- `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts`:
every new and changed test passes; 188 of 189 pass in total. The one
failure, `wave-speed.test.ts` "judges time to first agent activity…",
also fails on master: #20077 gave the local TTFT a budget after #20071's
test expected none. This PR does not touch `wave-speed` or the speed
budgets. The new `governance.test.ts` covers:
- the board decides only after the fault the pending approval armed,
with the server ready, for approve and reject;
  - a blocked task waiting on its approval is not counted as resting;
- G1 and H1 oracle pass and fail cases (duplicate request, action twice
or after rejection, decision or resume that crossed no restart, dispatch
while paused);
- H1 budget set, pause hold and incident resolve, against a fake board
API;
- trigger gating, opt-in workloads, H1 exclusivity, and K14 option
pairing.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`:
passed.
- Not run yet: a live wave of G1, H1 or K14. Phase 3 holds the harness
slot, and the smoke runs are requested there. Their results will be
added here.

## Risks

- Test harness only; no product code changes.
- G1 relies on the agent following the instructions (request, block, act
only when approved). A task where the agent does not follow them fails
the oracle visibly.
- K14 seeds the company with this checkout's harness code while the
older server serves. This works for recent builds; a much older build
could reject a seed call.
- Live behavior is not proven until the smoke runs.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with tool use
(shell, file edits, test execution, a read-only research subagent).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 13:40:39 -06:00
DottaandClaude Opus 5.5 a65626a2a2 test(chaos): fail waves on a needed Retry and on speed regressions (outage removed); add M1 multi-agent workload (#20071)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness (tests/restart-chaos) kills and restarts
the control plane during real agent tasks, and grades every task on
reliability rules R1–R6 plus a task oracle
> - Dotta's bar for today's campaign: restarts must continue on their
own (Retry is only a fallback), the system must stay fast (speed budgets
in `native-speed-budgets.ts`), and realistic scenarios must be covered
> - The harness graded a task that needed a user Retry as a pass, graded
speed only in crash-point mode, and counted a K4 controller hold as slow
TTFT. It also had no multi-agent scenario
> - This pull request closes those four gaps
> - The benefit is that every chaos wave now fails on a needed Retry and
on a real speed regression, but not on the fault's own outage

## Linked Issues or Issue Description

Refs #19831 (durable ownership plan) and the restart chaos plan
(`doc/plans/2026-11-06-control-plane-restart-chaos.md`). Used by today's
campaign waves:
- w-20261108T134237-s9401 re-graded to 104/107 under the Retry rule;
- w-20261108T153612-s9401: 100/101, with a Speed section showing 6
breaches after outage removal.

## What Changed

- **R3 Retry rule** (`grader.ts`): a task that needed a Retry fails R3
as `retry_needed (<failed runs' error codes>)`, and a second click fails
as `retry_needed_<n>_clicks`. The harness still clicks Retry so the task
can finish and be graded.
- **Per-wave speed grade** (new `wave-speed.ts`, wired in `main.ts`):
- It reads every task run's run log through the API and reports TTFT
(from run created) and agent boot p50/p95 per adapter and start kind,
restart recovery (ready → the run's next provider event), and single-run
`run.performance.budget_exceeded` events, against the local budgets.
  - A breach fails the wave.
- Output goes to `speed.json` and a Speed section in `SUMMARY.md`.
Remote environments report numbers with no budget.
- **Outage removal**: each performance span is shortened by its overlap
with fault outage windows (fault start to replacement server ready)
before it is judged. The summary reports how many spans and how much
time were removed.
- **M1 workload** (multi-agent delegation):
- The parent task creates two subtasks, part 1 delegated to the other
Chaos Lab agent.
- Each subtask saves a marked document, and the parent writes a summary
of both before it completes.
- The observer collects the subtasks and their documents, and the oracle
checks the parent links, assignees, statuses and markers.

## Verification

- `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts`:
14 files, 163 tests pass. New tests cover the M1 grade, the R3 Retry
rule (one click and two clicks), the wave speed grade (budget judgement,
restart recovery, remote no-budget, fault→run attribution) and outage
removal.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos` passes.
- Real waves:
  - The R3 rule re-graded w-20261108T134237-s9401 to 104/107.
- w-20261108T153612-s9401 produced the Speed section: 923 spans
adjusted, 2,452 s of outage removed, 6 breaches.
- M1 has not yet run in a live wave. It's next in the campaign rotation.

## Risks

- Waves that passed before can now fail: on a needed Retry, or on a
speed breach. That is the intended bar.
- Speed is judged at wave concurrency on a shared host, which is noisier
than the idle baseline the budgets came from. Breaches are reported per
row, so a reviewer can see the margin.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:27:39 -06:00
DottaandClaude Opus 5.5 871a9b3180 test(restart-chaos): add Phase 1 control-plane restart chaos harness (#19477)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The control plane owns task execution, recovery and Retry for every
agent run
> - Server restarts were the largest remaining source of stalled tasks
in the staging soak campaign
> - The soak could only observe restarts that staging happened to
perform, so a failure could not be repeated
> - This pull request adds a script harness that causes the restarts on
purpose and grades each task on fixed rules
> - The benefit is a repeatable restart failure with a replay command
and an evidence bundle for each fixer

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The restart test coverage. Today the runner-e2e restart cells test one
fixed restart point per cell. They cannot run many tasks at once, cannot
choose a random fault time, and cannot replay a failure.

**Subsystem affected**
Test tooling (`tests/restart-chaos/`). It reuses the runner-e2e process
code in `tests/runner-e2e/`. Product code does not change.

**Current behavior**
Restart robustness is checked through a small set of fixed cells. A
random restart during a real task has no defined pass rule.

**Proposed behavior**
A harness starts an isolated control plane and seeds a "Chaos Lab"
company. It launches seeded workloads, injects K1, K2 or K4 faults at
seeded times, and grades each task on rules R1 to R6 plus a
deterministic task oracle. `--replay <wave>/<task>` repeats one task.

**Reason and benefit**
Fixers get a replay command and an evidence bundle for each failure
signature. The first smoke wave already found three product signatures
(dead Retry after a restart, a false over-budget stop after restarts,
and an OpenCode turn killed mid-turn that ends as a provider failure).

**Breaking changes**
None. The change adds new test files, one entry in the test-project
typecheck list and a gitignore entry.

## What Changed

- Add `tests/restart-chaos/`:
- `supervisor.ts` owns one isolated server. It reuses
`replaceController`, the process-tree stopper, the environment isolation
helpers, the config seed, the port reservation, the wrapper-owned
Postgres and `server-entry.ts` from `tests/runner-e2e/`. It does not
copy them. Faults: K1 (SIGKILL, immediate restart), K2 (SIGTERM,
restart) and K4 (SIGKILL, 2 to 5 minute hold). Postgres and the
controller runtime cache stay across a fault.
- `seed.ts` creates the company, encrypted provider secrets, native
Codex, ACPX Claude and OpenCode agents from the runner-e2e catalog, and
the `chaos-sandbox` project. The project is a git repository with a
Python CLI, one failing test and a four-minute `bench.sh`.
- `scheduler.ts` makes a seeded plan of workload, adapter, fault, fire
time, hold and nonce for each task. It saves the plan so that `--replay`
can run one task again.
- `observer.ts` polls each issue and its runs. When a task stops and
offers Retry, it sends `POST /api/issues/:id/retry` one time, with the
same body as `useTaskRetry.ts`.
- `grader.ts` grades R1 to R6 as pure functions. `workloads.ts` defines
workloads A1, B1, B5 and C1 and their oracles.
- `evidence.ts` and `ledger.ts` write an evidence bundle for each
failure, a JSONL ledger and `SUMMARY.md`. All results go to the
gitignored `tests/restart-chaos/results/`. Known provider key values are
redacted.
- Unit tests: `scheduler.test.ts` (determinism, replay and CLI parsing)
and `grader.test.ts` (R1 to R6 and the oracles on fixture timelines).
These tests need no credentials.
- Add `tests/restart-chaos` to the test-project typecheck list
(`scripts/typecheck-test-projects.mjs`), with a `tsconfig.layers.json`
like `tests/runner-e2e`. The harness runs through direct script
invocations, not `package.json` scripts.
- After the first Greptile review:
- A failed fault or an unexpected server exit now gets one bounded
recovery. If the recovery fails, the wave fails and does not hang.
- Incomplete cleanup is now a harness error, and the harness keeps the
instance.
- After the C1 follow-up comment, the observer needs a fresh rest window
before it grades the task.
- `--replay` now fires the recorded faults that crossed the task, at the
same offsets, including the faults that other tasks requested.
- A failed workspace-operation read is now a sweep error. It cannot give
a clean rest.
- R2 now checks that workspace files from before the first fault still
exist at rest.
- B5 now needs the completion record that `bench.sh` writes for each
task.
- After the second round of reviews:
- An idle `todo` or `in_progress` task with no live run and no named
wait is no longer treated as resting. The observer keeps watching it
until the task resumes or passes the R5 stall limit.
  - A failed injected fault now fails the wave with exit 2.
- T-oracle commands run agent-editable code. They now run under a macOS
Seatbelt sandbox with a scratch `HOME` and no network, and they cannot
read the operator's home directory. On other platforms these commands
are refused unless the operator explicitly allows them.
- R4 now counts every active run. A wake comment may be served again
only after a run that did not succeed.
- Evidence redaction now covers every environment variable name that
ends in `KEY`, `TOKEN`, `SECRET` or `PASSWORD`.
- If the pre-fault snapshot fails, only R2 for that task is left
unchecked. The wave continues.
- B5 also needs bench progress that the harness observed while it
polled.
- Add `doc/plans/2026-11-06-control-plane-restart-chaos.md` and its
campaign ledger.

## Verification

- `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts`:
38 tests pass. The tests include observer tests with a fake API, a real
`bench.sh` run, and a sandbox test that cannot read a canary secret.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`:
passes.
- `server/scripts/typecheck-layers.test.mjs`: 45 tests pass, including
"covers every project under tests/".
- A supervisor check with no credentials passed. K1 was ready again in
about 6 seconds. K2 with a 3-second hold was ready again in about 9
seconds. Company data stayed readable.
- One paid smoke wave ran: `--env local --adapters codex,claude,opencode
--workloads A1,B1,C1 --faults K1 --tasks 3 --concurrency 2 --seed 7`.
The harness worked from start to end, and all three tasks crossed a
restart. The 0/3 result comes from product failures, which the campaign
ledger records. A scan found no provider key in the results.
- To run a wave:

  ```sh
  pnpm --filter @paperclipai/paperclip-runner build:runner-binaries
  set -a; source ~/.secrets; set +a
node cli/node_modules/tsx/dist/cli.mjs tests/restart-chaos/main.ts --env
local --adapters codex,claude,opencode --concurrency 4 --tasks 8
--faults K1,K2,K4 --seed 42
node cli/node_modules/tsx/dist/cli.mjs tests/restart-chaos/main.ts
--replay <wave>/<task>
  ```

## Risks

- Low product risk. The change adds test tooling only and does not
change product code.
- A wave spends provider money. The company budget hard cap
(`--budget-cents`, default 3000) is the only spend limit. The plan's
per-wave and per-night refusal is not implemented yet.
- Known gaps:
- `--env daytona` is reserved. It fails with a TODO in `seed.ts`. The
Daytona sandbox sweep for R6 is not implemented yet.
- The project requests isolated git-worktree workspaces. A B1 task still
ran in the shared fixture repository. Concurrent B1 and B5 tasks can
share files. The oracles use marker files for each task.
- Only K1, K2 and K4 at seeded random times are available. Boundary
triggers (B1 to B16), other faults and the UI probe come in Phase 2.
  - The paid smoke did not exercise B5 or K4.
- On macOS, B1 oracle commands need `python3` outside the operator's
home directory, for example `/usr/bin` or Homebrew. On Linux they are
refused until a sandbox is added.
- The B5 proof is best-effort against a deliberately adversarial agent,
because that agent can write to the workspace.
- Startup phase timings come from polling `/api/ready`. The server
records its own timings only as OpenTelemetry spans.
- `pnpm verify` and the CI Vitest jobs do not select these unit tests.
The root Vitest projects cover workspace packages only. CI typechecks
the files, but the tests run only through the vitest command above.
- After the smoke, rest detection and the review fixes changed. A
recovery action that the board owns now counts as a rest, and R6 reports
it. A paid wave did not run again after these changes.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used in
Claude Code as an agent with tool use (shell, file edits) and extended
thinking.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:45:52 -06:00