Files
PaperClipAI/server
DottaandClaude Opus 5.5 bf0d45a4d2 feat(recovery): settle teardown claims of dead claimants on the shared ownership path (#19966)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A sandbox teardown writes a claim on its environment lease that
fences other cleanup for 15 minutes
> - After a restart, the cleanup sweep ended the fence of a claim whose
claimant had exited, with its own process check
(`releaseLostPendingCleanupClaims`)
> - Phase 2 of the durable ownership plan moves each in-progress record
kind onto one shared owner proof and deletes its bespoke check
> - This pull request registers a settle for
`environment_teardown_claim` and calls the shared path where the sweep
called the bespoke release
> - The benefit is one proof for every kind: a local claimant proves
death by its stamp; another host stays a fence handoff

## Linked Issues or Issue Description

Refs #19965
Refs #19964
Refs #19933

**What happened?**
The teardown claim had its own liveness check
(`controllerProcessExited`) in the pending-cleanup sweep. Each kind with
its own check can drift from the shared rules (PID space, PID reuse,
current boot).

**Expected behavior**
The shared prover judges the claimant stamped for this exact attempt. On
gone, the fence ends now and a counted sweep attempt is refunded once. A
claim held as a fence (another claimant or another host) waits for its
deadline.

**Steps to reproduce**
1. Start a sandbox teardown so the lease holds an in-flight claim.
2. Kill the server.
3. Restart. The claim must be released by the shared ownership path, not
by a separate check.

## What Changed

- `environment-teardown-claim-settlement.ts` (new): compare-and-swap
settle on the exact attempt and claimant. It ends the fence, refunds a
counted attempt, and is single-use. It never acts on a fence owner.
- `recovery/ownership-registry.ts`: `environment_teardown_claim`
registers the settle.
- `heartbeat/recovery.ts`: the sweep calls `settleOwnableKind(db,
"environment_teardown_claim")`. Deleted
`releaseLostPendingCleanupClaims` and its use of
`controllerProcessExited`.
- `environment-cleanup-state.ts`: the attempt cap, attempt key and their
SQL readers move here, so the settle and the sweep share one reader.
- `heartbeat-pending-cleanup-sweep.test.ts`: the overlap barrier now
holds the shared prover instead of the deleted check.

## Verification

- `cd server && npx vitest run
src/__tests__/heartbeat-pending-cleanup-sweep.test.ts
src/__tests__/environment-lease-owner-stamps.test.ts
src/__tests__/ownership-reconciliation-report.test.ts` (embedded
Postgres).
- The existing lost-claim matrix stays green on the shared path: retry
now; no failed-attempt charge for a dead server; one refund when sweeps
overlap; keep a live claim; keep a newer claim that kept an older
record; keep another host's claim; keep a claim without identity; page
past fenced claims.
- New, red before and green here (written after the code): the
reconciler pass ends a dead claimant's fence and refunds its counted
attempt.
- New counter-cases (green): a read-only pass never settles; a live
claimant, another PID space, and an unstamped fence claim keep their
fence.
- Full typecheck is left to CI (local memory limit).

## Risks

- Low risk. The settle keeps the previous release semantics (fence ends,
lease stays `pending_cleanup` and in flight until the sweep claims it
again).
- The stop-only claim still does not renew its fence; a live stop that
runs longer than 15 minutes can still be taken over. This change does
not alter that.
- Stacked on #19965 and #19964. Merge those first.

> 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 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edit, embedded Postgres tests).

## 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-07 23:48:43 -06:00
..