Commit Graph
7 Commits
Author SHA1 Message Date
DottaandPaperclip 965ca91dd0 Capture legacy workspace cohorts under one source fence (#19873)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Workspace migration preserves local files as authenticated portable
snapshots.
> - Workspaces that share Git storage require one complete source fence.
> - Independent imports contend on that fence and cannot safely run as a
batch.
> - This change adds an opt-in cohort owner that captures members
concurrently and publishes them atomically.
> - Existing source, recovery, snapshot and publication checks remain
authoritative.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The administrative remote-workspace migration owner for workspaces in
one shared physical source component. Related changes: #19688, #19854
and #19855.

**Current behavior**

Each independent import acquires the complete source fence. Parallel
independent calls therefore contend instead of overlapping archive work.
A caller cannot use a surrounding transaction and assume that a
successful savepoint confirms the outer commit.

**Proposed behavior**

An explicit `migrateRemoteWorkspaceCohort` call selects one to eight
leaves from one proven component. It owns the full
source/lifecycle/copyback fence, uses the original pool for bounded
archive work, validates every member, publishes serially, and returns
success only after the outer commit.

**Reason and benefit**

This supplies a correctness-gated path for a controlled two-member
hosted qualification. It makes no wall-clock speedup claim. No running
app call site or scheduler selects cohorts automatically.

**Breaking changes**

The existing single-import API retains its guards and rejects a journal
owned by a cohort. New cohort journals require the same complete member
set on resume.

## What Changed

- Extract the existing reservation, capture, validation and publication
stages for reuse by both owners.
- Add an opt-in cohort API with default concurrency two and a maximum of
eight. Every member must share the same complete physical/Git/alias
component; selected roots must be leaves relative to each other.
- Persist exact cohort and operation membership in the existing journal.
Compare all selected fingerprints with the aggregate proof before the
first reservation under the complete fence, and recheck each member when
reserving its journal. Refuse foreign journals, incomplete or mixed
replay sets, lost fences and changed sources. Keep all authorities and
activity events unpublished if the outer commit fails.
- Share the merged nested-child verifier inside one complete fence.
Queue its prepare calls, retain its two-child bound and authenticated
immutable proof, and recheck local bytes and database bindings on every
pass.
- Document the API, exact-membership resume protocol, resource bounds
and required hosted qualification.

## Verification

- Integration head `fce1896010`: 32/32 PostgreSQL/filesystem cases
across the cohort owner, the complete merged nested-verifier suite, and
five existing parent-import cases.
- Final source-proof repair: 19/19 cohort-owner cases pass, including
real first- and last-member metadata changes before reservation,
changed-source partial-resume refusal, mixed local/imported refusal with
storage forbidden, cancelled/failed/finished admission, a late finished
timestamp, complete fences, queued child-proof reuse, cold restore,
cancellation, commit failure and exact replay. All local database checks
use the canonical owned wrapper, one Vitest worker and Node 24.22.2 on
macOS arm64/PostgreSQL 18.1.
- Final rebased head is based on
`fbe689938c3bf8c5849d2cd56b9da90029db9389`, including current
source-owner, database-schema and Runner contract changes. The 19-case
owner suite, full server layered source/tests/test-tree typechecks,
module boundaries and diff checks are rerun on that base; required CI
supplies the native Runner qualification.
- Selection-only `node scripts/local-verify.mjs --base
68db7ed9e3 --explain` exceeded its
60-second observation bound before producing a plan. It supplies no test
pass; required CI must supply broad coverage.
- Genuine replay RED: a committed two-member cohort accepted a
one-member replay. The repaired owner requires its full
succeeded-journal/snapshot membership.
- Genuine terminal-journal RED: read-only preflight accepted a
cancelled, finished saved member, and apply created two pending
snapshots and the missing peer journal before refusing later. The repair
rejects that state before preflight eligibility or snapshot work and
rechecks unfinished state before publication.
- Genuine source-proof race REDs: early workspace metadata drift could
strand a partial journal. A first repair checked each member only when
reserving it; an additional last-member regression showed that an
earlier journal could still be written before noticing the changed
member. The final owner compares the complete preimage in the existing
fenced preflight before the first reservation and retains
per-reservation checks. Both first- and last-member cases now leave zero
journals/snapshots and permit a fresh attempt. Changed sources after a
genuine partial reservation still refuse.
- Coverage includes parallel uploads, cold restore with originals
removed, complete source/copyback fences, exact partial-reservation
resume, actual read-only preflight with storage forbidden,
foreign/changed journals, peer failure and cancellation, fence
connection loss, and a real deferred outer-commit failure.
- Prior head `70bc2740d0` passed all CI after one explicitly bounded
same-head retry of server shard 4. Its first attempt observed an
unrelated heartbeat/plugin test still running at a five-second wait;
automatic test retry then hit the fixed-name environment uniqueness
constraint. The test and 1,100 relevant owner files matched the nearest
successful base gate. The one job retry passed the target test in
3,491ms and all 3,045 shard tests. Original failures remain recorded;
their initial cause is unknown. This does not supply CI acceptance for
the later repair head.
- Earlier prototype evidence remains historical: 286 PostgreSQL controls
and twelve cold restores passed on its prior base. Six macOS timing runs
were inconclusive under contention, including slower cohort runs. They
are not speedup or hosted acceptance evidence.
- Hosted acceptance remains a two-member same-component leaf canary with
no imported children, followed by same-host timing, peak-memory and
scratch-headroom observations before considering a larger batch.

Run the focused owner file from `server/` with the canonical temporary
PostgreSQL owner:

```sh
node ../cli/node_modules/tsx/dist/cli.mjs ../packages/db/src/test-postgres-command.ts -- node ../node_modules/vitest/vitest.mjs run src/services/remote-workspace-cohort-migration.test.ts --fsModuleCache --maxWorkers 1 --hookTimeout 90000
```

Final head `112ffeb20c272c6ef5e1e0956be889c85511f0d0` passed [required
CI](https://github.com/paperclipai/paperclip/actions/runs/42402942287)
with 56 successful checks and four skips; the aggregate is green.
[Greptile reviewed this exact head at
5/5](https://github.com/paperclipai/paperclip/pull/19873#issuecomment-6710780943),
and a fresh paginated review-thread read has zero unresolved findings.

## Risks

- Concurrent archive work increases CPU, memory, scratch space, database
and storage load. Admission must count logical member workers and up to
two nested-child workers, not Node processes. Eight-member use remains
unqualified.
- The complete cohort fence lasts until all started work joins. Slow
callbacks can delay cancellation and associated source writers.
- The exact journal exception is internal and binds company, cohort,
workspace and operation IDs. Wrong or changed membership fails closed;
operators must inspect unknown outcomes before resuming the same full
set.
- Existing remote recovery holds, provider obligations, source files and
history remain preserved. A successful import does not settle those
independent obligations.

## Model Used

OpenAI GPT-6 (Codex), with reasoning, code execution and repository
tools. This runtime does not expose a more specific backend model ID or
context-window size.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-07 13:02:54 -06:00
DottaandPaperclip 54b83b15a3 Reuse authenticated nested workspace verification (#19854)
## Thinking Path

> - Paperclip preserves the work that its agents produce.
> - Portable workspace migration can save nested workspaces before their
parent.
> - The parent must prove that a committed child snapshot covers any
files it omits.
> - The existing owner downloads and restores the same immutable child
archive before and after parent capture.
> - The fresh local-file checks are required, but the repeated immutable
archive check is avoidable within one live source fence.
> - This pull request reuses only an authenticated archive proof with an
unchanged reference and provider identity.
> - It also bounds independent child branches at two workers while
retaining every fresh local and Git check.

## Linked Issues or Issue Description

Refs #19805 and #19688. A duplicate search found no separate fix for
repeated child archive verification.

**What existing behavior does this improve?**

Online Git parent migration after nested workspaces have committed
portable snapshots.

**Subsystem affected**

server/ — workspace migration and nested snapshot coverage.

**Current behavior**

Each parent verification pass downloads, authenticates and restores
every child archive, then captures the child's current local tree. The
second pass repeats the immutable download and restore. Independent
child branches also run in sequence. A large parent can exhaust its
existing budget before parent capture begins.

**Proposed behavior**

One private verifier belongs to the existing live source-owner
transaction. It can reuse a completed archive proof only when the full
snapshot reference, operation, child closure, ETag and byte length still
match. Missing or weak ETags and non-cancellation HEAD errors keep full
verification. Caller cancellation still stops work. A changed identity
forces fresh authenticated readback. The owner still captures current
local bytes and Git state in both passes.

**Reason and benefit**

Remove duplicate immutable work without treating an old local-file check
as current authority. Two independent topmost branches can overlap.
Recursive descendants stay serial within those branches, so the bound
does not grow with depth.

## What Changed

- Add a private per-fence verifier to the existing tree owner. It clears
its proof map at the end of the import and refuses a different owner
transaction.
- Reuse only completed ciphertext, GCM, plaintext, and canonical restore
verification with a matching fresh provider identity.
- Retain all current database, operation, recursive coverage,
local-file, Git and manifest checks on each pass.
- Bound independent branches at two. Abort queued work on failure and
await active peers before scratch cleanup or fence release.
- Use the verifier only in the existing online Git importer. Keep the
offline full-verification path and all capture deadlines unchanged.
- Document resource admission, fallback behavior and the hosted
acceptance limit.

## Verification

- Genuine baseline RED through the real parent importer: child GET
counts 2 + 2, canonical restores 4, maximum overlapping readbacks 1. The
four fresh local child captures already pass.
- Exact positive GREEN: child GET counts 1 + 1, canonical restores 2,
maximum overlapping readbacks 2. All four fresh local child captures
remain.
- The fixture uses real encrypted archives and the canonical S3
provider's local HTTP HEAD parsing with actual payload-derived ETags. It
uses fake fixture credentials only.
- Final focused suite: **13/13 PASS**, 148.36 seconds, at `eaf17d3340`.
This includes recursive max-two completion, failure/cancellation
joining, identity/ref/content changes, same-fence alias reuse and
stale-owner refusal.
- Both affected existing migration suites: **256/256 PASS**, 201.84
seconds, at initial head `eaf17d3340`.
- The HEAD-failure review repair has genuine two-case RED evidence and
three passing failure/cancellation controls. Full layered
source/test-tree typecheck and module boundaries pass at repaired head
`763648adad`. Final exact-head controls: **21/21 PASS**, 86.63 seconds
(all 16 new controls plus five existing parent cases).
- The first extended recursive fixture correctly failed because it
created a Git branch after taking an earlier snapshot. The fixture now
creates all branches before any import; the source-change guard remains
intact.
- Exact-head CI: 56 success, four expected skips, and one neutral
Paperclip Review. Required gates pass; Greptile is fresh 5/5. Its
optional aggregate-handler cleanup recommendation is resolved with the
intentional fail-closed rationale.
- Local verification is macOS arm64 / Node26.4.0. The observed hosted
parent still needs to complete within its unchanged budget after normal
delivery. No hosted timing improvement is claimed here.

## Risks

- Two simultaneous child verifications can use more I/O, memory and
scratch space. Operators must count logical verifications, even in one
process.
- HEAD availability and provider identity are required for proof reuse.
Providers without a usable ETag perform the full check.
- Reuse lasts only for one live transaction. A retry, changed reference,
changed provider identity, process exit or owner change requires fresh
verification.
- Faster verification does not waive changed child bytes or guarantee a
specific hosted completion time. No provider turn, authority marker,
source deletion or larger timeout is introduced.

## Model Used

OpenAI GPT-6 through Codex, with repository and code-execution tools.
The exact serving model revision and context-window size are not exposed
to this agent.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-07 10:45:17 -06:00
DottaandPaperclip d5c4f7dd02 fix: preserve shared Git sources during online workspace migration (#19688)
## Thinking Path

> - Paperclip preserves the work produced by its agents.
> - Remote workspace migration must preserve legacy Git history and
local edits.
> - Linked checkouts can share administration and objects with other
tasks.
> - A task lock alone cannot exclude writers of a shared or borrowed
store.
> - The online source owner must derive and retain the full physical
ownership closure.
> - This change reuses task, project, lifecycle and finalizer locks for
that closure.
> - Idle legacy Git work can then migrate while unrelated remote turns
continue.

## Linked Issues or Issue Description

Refs: #19642.

**What happened?**

Online migration keeps legacy Git sources blocked because several task
checkouts can share a project base or a private Git cache. A read-only
source observation found 160 pending Git records, 120 distinct paths and
nine common stores. Its largest project cohort contained 126 records and
110 task owners. An ordinary-directory fence cannot preserve these
sources by itself.

**Expected behavior**

Derive the physical source owners from stable Git metadata. Hold their
existing source and lifecycle keys through publication. Retain all
history and recovery obligations. Reject foreign aliases, unknown stores
and changed sources. Let unrelated object-backed work continue.

**Steps to reproduce**

1. Create a managed project checkout and linked task worktrees.
2. Keep a settled prepared recovery source on a sibling task.
3. Keep unrelated remote work running.
4. Request an online import of an idle leaf checkout.
5. The existing owner keeps the shared source blocked.

**Paperclip version or commit**

This branch builds on merged #19642 (`7ac99718a4`) and fresh master
`fb3cf1a072`. It adds the Git closure. It does not add a writers-stopped
shortcut.

**Deployment mode**

Hosted native Runner with remote workspace storage. Local qualification
uses owned synthetic Git repositories, disposable PostgreSQL databases
and the actual archive and storage owners.

## What Changed

- Read bounded stable Git pointers, common-directory metadata and local
alternates. Derive the complete physical source ownership cohort.
Preserve all object stores.
- Retain common and borrowed store owners in preparation, lifecycle,
runtime, expiration and actual reopen paths. Keep company boundaries and
the existing 128-key bound.
- Hold exact sibling native recovery proofs under their existing
physical finalizer keys. Recheck descriptors, allocations, recovery
actions and source identities before capture and publication. Preserve
original states and warnings.
- Require leaf-first nested imports. Reuse the existing immutable child
download, restore and source comparison before parent exclusion.
- Bind the self-contained archive owner to the held Git namespaces. Keep
its three walks, history normalization, fsck, bytes and digest format.
Reject bare, detached, network, unknown or changed Git storage.
- Check both foreign path fields. Keep foreign metadata private.
Document the proof, bounds, source observation and remaining eligibility
limits.

## Verification

- Final revision `2934e41c923b93e16508b978c849b747c86be645`: all checks
are terminal, with 56 successful and four skipped, zero failures and
zero pending. The [required CI
aggregate](https://github.com/paperclipai/paperclip/actions/runs/42326464408/job/128266279210)
passed. Build, types, all server/Runner/serialized suites, end-to-end
aggregate and Snyk passed. [Fresh exact-head Greptile
review](https://github.com/paperclipai/paperclip/pull/19688#issuecomment-6699760104)
is 5/5 with no open threads. GitHub reports MERGEABLE and CLEAN.

- Review then found two correctness gaps: an ordinary managed directory
inside an unrelated host repository was blocked, and a Git parent could
miss a completed nested operation after its child snapshot became
missing or deleted. Three actual-owner regressions reproduced both gaps
in 9.70 seconds. The corrected owner passes 12 focused cases in 17.08
seconds. It imports and restores only the ordinary root bytes, preserves
the outer repository, rejects both missing/deleted child receipts before
a parent journal, and retains valid/changed child coverage and
private-cache contention. Its wrapper exited 0 and cleanup completed.
The first bootstrap attempt failed before tests ran because the host
exhausted its 32 shared-memory segment limit; no shared services were
stopped and no pass is claimed for that attempt. The final qualified
source/test type graph, module boundaries and diff checks pass.
- Receipt scans keep canonical path resolution. A lexical SQL prefix can
omit children reached through symlinked ancestors. The observed full
metadata closure completed in 1.17 seconds; no measured bottleneck
justifies weakening that alias proof. Writer admission still rejects
uncertain Git ownership before mutation. Expiration returns its typed
skip, and reopen returns its structured failure. Runtime and lifecycle
do not bypass an unknown source. No broad error-contract change was
added.

- Full CI at `720104ea975e` exposed five existing boundary cases across
three server shards. Four exact cases reproduced locally as failures in
25.98 seconds. Reopen now preserves its structured failure when source
inspection rejects the path, without reconstructing it. Runtime
recognizes only the exact regular ignore-only `.git/info/exclude`
fragment; migration still rejects that fragment. The original
arbitrary-path and file-URL import/delete-host/resume fixtures keep both
supported input forms and use the genuine offline setup path after their
sole writer finishes. The production mode gate is unchanged. A new
real-owner control proves the offline flag still rejects a running
legacy database writer.
- The final focused run passed all ten cases across four suites in 22.75
seconds, including all five CI failures, invalid-pointer admission,
fragment rejection, offline writer exclusion and actual nested reopen
lock ordering. Its wrapper exited 0 and cleanup completed. The known
invalid-pointer case remains fail-closed; the separate unexpected-I/O
diagnostic retains safe error metadata without command text or
credentials. Existing Git stderr redaction coverage remains intact. The
final metadata reader suite passed 20 cases in 2.68 seconds. The
qualified server/test source type graph, module boundaries and diff
checks pass. No production assertions or timeout defaults were weakened.
Fresh CI and review are required for the repair revision.

- A later review found that the ordinary early return still used only
one foreign path field. The actual ordinary providerRef-only regression
failed as incorrectly eligible. One source-path owner now enumerates
both local locators for company/foreign ordinary and Git scans, layout
checks, early admission, lifecycle/reopen/runtime and
expiration/move/preparation retention. Capture and display keep their
original preferred path. The actual owner canary passed 49 cases across
two suites in 109.15 seconds. It covers
cwd-only/ref-only/different/symlink locators, same-company mismatches,
foreign owners, lifecycle ordering, Git/recovery imports and
expiration/reopen. Three selected-locator fallback cases also failed
before their guard; the final eight-case control passed in 31.05
seconds, including the configured alias and valid import/private-child
controls. All wrappers exited 0 and completed cleanup. The
metadata/locator suite passed 19 cases. The final source/test type
graph, module boundaries and diff checks pass.

- Review found two P1 gaps. Both configured-directory symlink cases
failed before canonicalization. The actual parent import lost an
already-imported child's unique private cache and failed its archive
fence instead of retaining that owner. The corrected real-owner canary
passed four cases in 20.78 seconds: configured symlink runtime
provisioning, private-cache contention before the parent journal,
successful parent publication after release, and unchanged/changed child
coverage. The metadata suite passed 15 cases in 4.14 seconds. Both
wrappers exited 0 and cleanup completed.
- A fresh source read-only observation found 160 pending Git records and
nine common stores. An additional exact receipt query found 1,243
completed migration receipts and 557 nested source paths under pending
Git roots. All 126 records in the largest project were present in the
captured Git metadata set. For all 110 present candidate workspace
records, the derived closure fits the unchanged 128-key cap. The worst
case needs 110 issue keys and one project key, with 17 keys of headroom.
Its 173 imported children introduce no physical issue owner outside
those 110 logical task owners. This checks the observed key bound only.
It does not claim migration eligibility or a coherent snapshot.
- The initial stacked CI did not run four Runner test lanes: trusted
master passed new --package-lane/--native-server options to the old base
lane script. The branch is now rebased onto actual merged master and
retargeted before synchronization. It includes the canonical current
lane parser without an unrelated Runner patch. Fresh new-head CI is
required.

- The actual importer/source owner run passed 36 cases across three
suites in 187.75 seconds. It covers sibling prepared recovery, unrelated
running remote work, common-cache contention, changed
coordinator/allocation/path state during upload, exact nested coverage,
dirty unpushed work, and existing expiration/reopen owners. Wrapper exit
0 and cleanup completed.
- A separate previously hanging lifecycle-worker restore passed in 17.33
seconds with the unchanged 15-second test-body limit. An earlier run
timed out in three expiration/reopen cases and blocked cleanup; its
wrapper later exited 1. These failures remain recorded. They are not
counted as passes.
- Two boundary controls first failed: a foreign providerRef-only alias
was incorrectly eligible, and actual reopen did not admit its distinct
cache owner at the initial source boundary. The narrow guards now
inspect both foreign locators and derive physical Git keys before the
raw lifecycle transaction. The foreign alias and existing real nested
reopen contention passed. The private-cache control first retained
contradictory recorded source provenance and stayed fail-closed after
release. The corrected present legacy-worktree fixture passed in 12.61
seconds. These failed attempts remain recorded; they are not passes.
- The metadata reader and serialized/local archive controls use actual
Git repositories. They compare archive bytes and digests, remove every
original source, restore staged and unstaged work, and run fsck. The
metadata reader passed 13 cases in 2.85 seconds. The local/serialized
archive controls passed nine cases in 9.27 seconds. All processes exited
0. An additional actual importer control changed a relative commondir
pointer to the absolute name of the same store during upload.
Publication was rejected, the uploaded journal remained available, and
original files stayed intact (one case, 5.86 seconds; wrapper exit 0 and
cleanup complete).
- The final rebased full local server/test type graph passes with
db/shared/Runner source aliases because reused dependency declarations
predate new master exports. The first rebased attempt reported those
stale declaration fields/tables, and is not counted as a pass. Module
boundaries and diff checks pass. Standard package builds, types and
broad regression checks remain required in CI.
- No hosted import, admission hold, cancellation, provider mutation,
source filesystem write or shared-cache Git command was performed for
this change. The source observation is read-only and is not a locked
eligibility proof.

## Risks

- A missing writer owner could make physical source exclusion
incomplete. Existing mutation owners retain the same
company/task/project keys, and uncertain metadata fails closed.
- A project can contain a large cohort. Limits remain finite. A bound
failure keeps the source blocked.
- Bare, detached, unowned or network Git stores remain blocked online.
Missing archived sources retain their existing authenticated
reconstruction path.
- Exact recovery proofs preserve authority and obligations. They do not
infer provider termination or mark a prepared descriptor finalized.
- This change does not claim all observed Git records are immediately
eligible. Existing preparation, provider, recovery, runtime and
source-change blockers remain.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use and code execution.
The host does not expose a more specific backend model ID or
context-window size.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 20:08:19 -06:00
DottaandPaperclip 7ac99718a4 fix(workspaces): fence legacy sources for online migration (#19642)
## Thinking Path

> - Paperclip coordinates AI agents and preserves their saved work.
> - Remote workspace storage replaces legacy host directories with
durable snapshots.
> - Several legacy workspace records can name one physical project
directory.
> - A task lock alone cannot exclude a new task that selects that
directory.
> - Terminal recovery records can retain valid sources after physical
writers settle.
> - This change fences the source through its real preparation and
lifecycle owners.
> - Idle ordinary sources can then migrate while unrelated remote work
continues.

## Linked Issues or Issue Description

**What happened?**

Online migration rejects ordinary legacy directories when several
workspace records name the same path. A source inspection found 45
aliases across three project roots with no root Git marker or previously
imported nested child. The instance also had unrelated running remote
work. Requiring the whole instance to drain prevented useful migration
of these idle sources.

Terminal native sources and superseded finalization attempts also remain
blocked after their exact physical and durable owners settle. Their
files, provider allocations and recovery records must remain available
through the change of file authority.

**Expected behavior**

Migrate an idle ordinary source under a derived physical source fence.
Let unrelated object-backed turns continue. Preserve provider and
recovery obligations. Keep unknown writers, shared Git stores and
uncertain source state blocked.

**Steps to reproduce**

1. Create several local workspace records for one managed project
directory.
2. Give the aliases different source tasks. Leave their local writers
idle.
3. Keep a valid remote workspace turn running on another source.
4. Inspect or apply migration to an alias. The old owner returns
`overlapping_workspace_requires_maintenance`.
5. Retain a terminal native prepared source with a null coordinator
owner, or an old finalize attempt with an exact later committed receipt.
The old online path keeps these sources blocked.

**Paperclip version or commit**

Current tested head: `7478c817f09f7eed396922c0f7831af07ae6ff14`, based
on master `8621bcf53ee2e4bbefb7d193add42dd544ca06e8`. Related recovery
work: #19131.

**Deployment mode**

Hosted native Runner with remote workspace storage. Qualification uses
disposable local PostgreSQL databases, actual archive/storage owners and
synthetic source trees. The source observations were read-only.

## What Changed

- Retain the existing company/task source keys and add one physical
managed-project key. Heartbeat source selection, materialization, reopen
and lifecycle owners share it. Configured aliases of canonical task
directories retain their original task key.
- Fence runtime provision and each configured service CWD through both
startup owners. Check all unsettled service CWDs for physical source
overlap, even when workspace IDs differ. Runs with no services keep
their filesystem-free path.
- Derive a bounded ordinary alias closure. Lock every alias task and
lifecycle key. Reject foreign companies, changed membership, unproven
descendants, included Git metadata and other unresolved writer
obligations.
- Commit remote authority on the connection that holds source exclusion.
Refresh a stale selected local workspace after admission if migration
published remote authority. Publish activity after commit.
- Admit exact terminal native recovery sources under the existing
physical finalizer keys. Bind descriptors, allocations and owner
checkpoints to journaled fingerprints. Recheck them before capture and
publication.
- Add a separate company-scoped online entry for exact superseded
finalization receipts. Keep the offline entry's deployment prerequisites
and keep inventory read-only.
- Document the proof and the remaining Git/common-store extension. The
original files stay in place. No new scheduler, deployment action or
database migration is added.

## Verification

- Review found that the real reopen path could hold lifecycle exclusion
while waiting for a project source. The new actual nested-reopen
regression failed before the repair: lifecycle lock count was 1 while
another connection held the project fence. The repair acquires declared
and physical project sources before reconstruction and lifecycle. The
contention case, ordinary-fetch restore and runtime nested restore
passed. The lifecycle-worker restore first completed in 16.64 seconds
but exceeded its unchanged 15-second body limit; a bounded warm rerun
passed at that same limit. Both wrappers exited and completed cleanup.
The source type graph, module-boundary check and diff check pass. Final
7478 CI passed: 58 successful jobs, three skipped, one neutral and a
successful Snyk context. Fresh Greptile is 5/5 for exact7478, with no
unresolved threads. [Final
CI](https://github.com/paperclipai/paperclip/actions/runs/42318150314/job/128240631573).

- Full CI on `e4283b3452` passed the other server, build and type
checks. One exposure owner case failed: two execution workspaces
received one preview pair after source admission serialized their
starts. The first local seven-case run reproduced that failure; adding
the owner argument alone did not change the hash payload. The completed
repair binds run-scoped reuse to its execution-workspace owner. All
seven existing real-process/fake-broker cases then passed in 13.81
seconds, with wrapper exit 0 and cleanup complete. Public scope IDs and
explicit project/agent sharing are unchanged. The final7478 head passed
all required CI and fresh review gates.

- The service/source/restore owner canary passed 25 cases across four
suites before the final no-service fast-path guard (41.33 seconds). This
includes actual project service processes with a different project ID
and no execution-workspace ID. Their startup waits for source ownership,
migration stays blocked while they run, and the source becomes eligible
after their verified stop. The final three-case runtime canary passed at
the final source inputs (18.70 seconds). It covers that fast path and
both service-CWD variants. Both wrappers exited 0 and completed cleanup.
- The boundary-repair canary passed 9 cases across four suites at
`7c18f1c170`. It covers the real rehome/conversion gateway, project
command provisioning, future task selection, stale imported authority,
foreign-company binding, and existing standalone filesystem callers. The
shared PostgreSQL wrapper exited 0 and completed cleanup. Duration:
94.15 seconds.
- The P1 diagnostic passed 46 cases across five suites at the pre-rebase
P1 revision. It covers the actual importer with an export warning
created by `recordNativeFinalizationFailure`, strict
prepared-source/action bindings, finalizer locks, source/lifecycle
races, unrelated remote execution, and bare Git with external
alternates. It uses the repository shared PostgreSQL wrapper,
`--fsModuleCache`, and a local-only 90-second hook allowance. Duration:
245.44 seconds. No product timeout changed.
- The canonical retained-directory compatibility check passed 22 cases
across two suites. Missing archived task directories still import from
authenticated restore points; they do not need a present-directory
source proof.
- Both initial P1 regressions were observed before the fix. The real
finalizer warning made the importer ineligible. The old archive owner
accepted a nested bare repository borrowing external objects. The new
cases pass and preserve the original warning, prepared descriptor,
allocations and files.
- Prior attempts remain non-passes: the first 23-case canary passed on
an earlier head; its exact-head rerun hit 30-second database
setup/cleanup limits. A read of the owned PostgreSQL server found a
template clone waiting on `DataFileExtend` for 58 seconds. A broader
owner run was intentionally interrupted during slow existing heartbeat
setup. One diagnostic invocation used an invalid CLI option and executed
no tests. A later review canary passed 21 cases and failed the new real
restore contention case: cwd-only admission missed the declared project
of an external worktree. Its fixture also omitted the required
file-repository authorization callback and produced an unhandled
rejection after the failed assertion. The repaired run passed 22 cases,
exited 0 and completed cleanup. These earlier failures are not reported
as passes.
- The recovery owner passed 37 PostgreSQL cases, including 14 new and 23
existing finalized-recovery cases. The operation reconciliation owner
passed 40 cases. The initial archive run passed 103 cases and found a
missing closed diagnostic entry; the corrected diagnostic and expanded
Git rejection cases pass in the P1 run.
- The full server type graph passes with the Runner source exports
mapped locally because this checkout has no built Runner declarations.
The latest module-boundary check and `git diff --check` pass. Standard
package build/typecheck and broad final-head regression coverage remain
required in CI.
- The initial CI exposed missing admission scopes for standalone
filesystem APIs and a changed foreign-company error. The latest repair
keeps those APIs independent of a database and holds physical fences at
their actual database-backed mutation owners. The branch also includes
the existing master fix for unchanged Runner default-model assertions.
New review findings and new-head CI are being checked.
- The local canary command and bounded source selection are documented
in `doc/plans/2026-11-06-online-ordinary-source-fence.md`. Source
observations only read data. A fresh bounded metadata probe found 254
pending legacy records and zero unsettled local-process services. No
live import, hold, cancellation, provider action or source filesystem
write was performed for this change.

## Risks

- Run-scoped reuse now includes execution-workspace ownership in its
private key. Explicit project/agent sharing keeps its previous identity.
- A missing writer owner could make a source proof unsafe. Admission
derives physical aliases, includes future project selection and retains
all existing source/lifecycle/finalizer locks. Unknown and changed state
fail closed.
- Cross-company physical alias checks use bounded minimal-path pages.
Lexical-prefix filtering cannot safely replace them because a symlink
can hide a foreign alias. Unknown paths or more than 10,240 foreign
records leave the source explicitly unproven. More than 1,000
same-company local records also fail closed.
- The project key can serialize local preparation within one project. A
first project waits; additional project acquisition fails on contention
to avoid a lock cycle. Remote authority keeps its remote preparation
path.
- Recovery preservation does not prove provider termination or
successful file recovery. Existing provider allocations, checkpoints,
task intent and recovery actions remain authoritative.
- This slice does not finish shared Git migration. Git
administrative/common-object/alternates closure and exact nested
imported-child coverage remain the next owned extension. No global
writers-stopped shortcut is used online.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use and code execution.
The host does not expose a more specific backend model ID or
context-window size.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 18:42:45 -06:00
DottaandPaperclip f5f5184f23 fix(workspaces): preserve retained branches and private commit sources (#19147)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Ephemeral web servers must preserve existing task files before
volume removal.
> - An old private checkout can be absent while its recorded branch
still exists.
> - A deleted branch can also retain its exact commit in the original
private object source.
> - The importer currently rejects these surviving sources.
> - This pull request saves the verified committed tree and records its
exact source proof.
> - It keeps task state, branch ownership and recovery obligations
unchanged.

## Linked Issues or Issue Description

Refs #19138, #19144, #18826.

**What happened?**

Existing Git workspace records can have missing checkout folders and
retained branch objects. The importer returns
`local_source_unsafe_or_missing`. A successful deletion receipt can also
have a valid private object source that the importer overlooks in favor
of a missing or changed project cache.

**Expected behavior**

Save the surviving committed tree from the exact recorded branch or
deletion receipt. Reject competing owners, live worktree registrations,
changed branches and invalid source identities. Do not use the current
default branch as a substitute.

**Steps to reproduce**

1. Retain an isolated workspace branch in its managed project cache.
2. Remove its clean checkout but keep the branch and original workspace
record.
3. Attempt workspace migration.
4. Alternatively, retain a deleted commit in its recorded private source
while its shared source is unavailable.

## What Changed

- Pin a retained branch in its verified company and project Git cache.
- Require the recorded private path, no competing workspace owner and no
surviving Git worktree registration or index authority.
- For a user-owned branch, require its exact successful unforced removal
receipt after archival. Preserve the branch ownership metadata.
- Prefer a verified recorded private object source for successful
deletion receipts when that source still exists.
- Recheck branch and receipt proof before publication. Preserve
compatibility with existing migration journals.
- Allow missing active records only through the existing offline writer
fence. Keep their status unchanged.
- Add byte-restoration and rejected-authority tests. Document the source
limits.

- Revalidate and honor interrupted journals' original Git source path.
Reject detached registrations and retained orphan index data. Reuse the
cleanup producer command formatter for paths containing spaces.

## Verification

- 172 focused migration, concurrency and isolated-source tests pass with
real PostgreSQL and Git repositories.
- The previous importer fails the retained-branch and offline-active
restoration cases. The previous source selection also fails the
private-cache and clean user-owned receipt cases.
- Tests remove the old home and restore the pinned commit and task file
bytes. New shared default-branch work is excluded.
- Tests reject competing owners, registered worktrees, missing branch
ownership, deleted branches, changed branch tips, forced removal and
revoked receipts.
- Read-only re-inventory of 79 actual failed source records makes 78
eligible with committed owner overlays. One older local test repository
remains blocked. This is source-overlay qualification, not full
released-image acceptance.
- A separate bounded archive replay completed six imports before its
25-minute deadline. That timing failure remains open; it does not
establish complete migration.
- Local server typecheck has no changed-file diagnostics. Stale linked
declarations prevent a clean local whole-server result. Full clean CI
remains required.
- Serving enrollment and empty-home recovery remain separate acceptance
gates.

- Latest rebased head 9f42901683 passes 172 focused
migration/archive/concurrency tests. Four review regressions fail on the
previous source; the revoked journal source already rejected correctly.
The real cleanup fixture uses an unforced removal under a home path
containing spaces. The orphan-index fixture is locked to keep Git
maintenance from deleting its synthetic retained state before
inspection.
- The prerebase layered server typecheck passed. Current-head full CI
remains required after integrating master. The local environment reset
removed earlier temporary logs; the current red/green logs and source
now live in persistent worktree folders.

- Current-head full CI passes on run 41965901512 after one bounded
failed-shard retry. The first run failed an unrelated status-card
fake-clock deadline assertion by 150 ms; the same source passed the
retry. Its cause remains unestablished, and the first failure log
remains in Actions and persistent local evidence. No migration-related
test failed.

## Risks

- A retained branch saves committed data that still exists. It cannot
certify recovery of files that were already absent.
- User-owned branches remain user-owned. A clean removal receipt grants
preservation authority, not deletion authority.
- Any retained registration, conflicting record or changed proof keeps
migration blocked.
- This PR does not resolve the remaining source, recovery or migration
timing gates.

## Model Used

OpenAI GPT-6.1 Codex, exact model ID `gpt-6.1-sol`. Used for reasoning,
code editing, shell tools and test execution. The runtime does not
expose its context-window size.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 22:35:55 -06:00
Dotta 82dd3bb702 Preserve unstarted queued turns during offline enrollment (#18887)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Existing-volume enrollment moves server files to durable object
storage.
> - Enrollment stops application deployments before it changes file
authority.
> - A task drain keeps unstarted turns queued in the database.
> - The migration rejected every queued row, so a busy instance could
never finish enrollment without discarding requests.
> - This change preserves unstarted queue intent and continues to reject
running or previously started executions.
> - Users keep their pending turns and saved files through the
conversion.

## Linked Issues or Issue Description

**What happened?**

A preserving enrollment found queued turns after task admission stopped.
The workspace, instruction-file, and historical-log guards treated these
rows as live writers. The drain also prevented the rows from starting,
so waiting could not satisfy the migration guard.

**Expected behavior**

After every old deployment has stopped, maintenance can move files while
unstarted queued turns remain unchanged. The replacement claims those
turns through normal admission. Actual executions and retained execution
ownership continue to block migration.

**Steps to reproduce**

1. Queue a turn behind another task.
2. Start a task drain and let the current task finish.
3. Stop the application deployments and run offline enrollment.
4. Observe `active_execution_requires_drain` for the unstarted queued
turn.

**Paperclip version or commit**

Reproduced on the migration contract through #18864. This branch starts
at `ab2d2098a2764c511978d1c3e7a3fc5dfcef07f4`.

Related: #18826, #18835, #18841, #18864. Searches for queued enrollment
and queued migration found no duplicate fix.

## What Changed

- Share one offline execution predicate across workspace, agent-file,
and historical-log migration.
- Reuse the existing pre-dispatch evidence predicate; queued turns with
execution markers, saved checkpoints, source events, coordinators, or
environment leases remain blockers.
- Keep task bindings and normal live-migration restrictions unchanged.
- Exercise normal replacement admission after deleting the old
filesystem: the same queued run reads restored workspace bytes and
instructions, completes its original wake, and cannot execute twice.
- Document the offline boundary and queued-turn behavior.

## Verification

- **111 tests passed** across the final 110-case migration suite and
assembled queued-turn heartbeat proof, with real PostgreSQL and local
S3. This includes workspace/log migration, queued ownership blockers,
ordinary agent-file controls, and the actual bundled maintenance
process.
- The maintenance process preserves the queued row while archiving
historical output. The new assembled heartbeat test creates a normal
wake under drain, migrates without changing either row, deletes the
original filesystem, and resumes through normal admission. It reads
saved workspace bytes and the restored instruction bundle, persists one
successful native result, completes the same wake, and verifies a second
sweep does not duplicate execution. Only sandbox transport/provider
execution are scripted; no live model or Daytona calls.
- An earlier optional run-evidence worker integration invocation was
skipped without its dedicated environment; it is outside the changed
offline path.
- Server typecheck, server build, and runtime bundle passed.
- The first maintenance test exposed the separate agent-file guard; that
guard now uses the same offline predicate and the test passes.
- Initial local typecheck attempts lacked CLI dependency links in this
isolated checkout. Restoring dependencies fixed setup without source
changes.
- Full repository checks run in CI. The first head lost spot runners in
Build and serialized tests; one unchanged idle-deadline fixture crossed
UTC midnight and failed its timestamp assertion. Final-head CI passed on
run 41744678994 after retrying a spot-runner shutdown. Greptile reviewed
the exact final head at 5/5; all inline findings are resolved. The
separate Paperclip Review app remains queued. Hosted enrollment remains
pending release qualification and compatibility verification for the
existing provider configuration.

## Risks

The offline predicate relies on Cloud stopping every old application
deployment. It does not make online migration safe. Running executions
always block. Requeued executions with retained ownership also block. No
queued request, task binding, or recovery state is removed or rewritten.
No database schema changes.

## Model Used

OpenAI Codex, GPT-6 based, with reasoning and tool-assisted code
execution. The exact deployed model identifier and context-window size
are not exposed in this session.

## 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
2026-11-03 18:33:41 -06:00
DottaandPaperclip 2a16dcf629 fix(workspaces): preserve shared directory trees during enrollment (#18835)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A volume-free web server needs existing task files in durable object
storage.
> - Historical workspace records can share a physical folder or contain
other task folders.
> - A task-level migration hold cannot fence every alias, and capturing
a parent recursively duplicates nested task data.
> - Offline enrollment imports children first and verifies their durable
coverage before omitting them from a parent archive.
> - Snapshot retention preserves those child versions for as long as a
covering parent depends on them.
> - Existing files, task bindings, and archived states remain intact
through the storage migration.

## Linked Issues or Issue Description

Builds on merged #18826.

**What happened?**

The per-workspace importer could capture a shared project folder while
another workspace record still owned the same path. It also recursively
included runtime-created nested task workspaces in each parent import.
Packaged enrollment rejected existing workspace data instead of invoking
its migration owner.

**Expected behavior**

Require offline maintenance for overlapping paths. Import nested tasks
before their parents, verify every excluded directory against its saved
snapshot, preserve dependencies through retention, and resume
interrupted imports without replacing existing remote authority.

**Steps to reproduce**

Create a project workspace and another task workspace below its
`.paperclip/worktrees/` directory. Try importing either while ordinary
writers are running, then run offline enrollment. Interrupt a parent
import after upload and resume it.

## What Changed

- Add physical-overlap admission checks and an offline entry-point
guard, including global running and queued executions.
- Import pending records in leaf-first pages through the existing
migration owner.
- Verify nested snapshots by downloading, restoring, and comparing the
original source before and after parent upload; check transitive
dependencies.
- Publish parent dependency receipts under child snapshot row locks.
Retention pins those children and revisits them when the parent is
deleted.
- Index imported-child dependency lookups with a partial GIN index; a
PostgreSQL query-plan regression covers 2,500 unrelated snapshots.
- Propagate cancellation through capture and object storage operations
and preserve existing retry journals.
- Wire workspace migration into packaged maintenance and document its
preservation contract.

## Verification

- The original overlap and duplicate-parent regressions failed before
implementation.
- 73 migration and retention tests pass against real PostgreSQL and
files, including interrupted uploads, resumed batches, equal-path
aliases, changed or missing child data, nested descendants, and active
or queued executions.
- Six S3-compatible storage integration tests pass, including migration
through the maintenance owner and restoration of both parent and child
after deleting the original filesystem.
- The standalone process case was initially skipped because the worktree
had no bundle. After rebuilding the server and runtime bundle, that test
passed against PostgreSQL and S3: authenticated ready/export receipts,
preserved decision secret, blocked application routes, and clean SIGTERM
shutdown.
- Server typecheck, server build, runtime bundle build, and
module-boundary checks passed. Full repository CI passed on
97136a20b9 after targeted retries of
infrastructure-interrupted jobs.
- Review fixes resolve physical paths through symlinked ancestors and
persist each parent journal’s original capture scope, including
pre-change journals. All three new regressions failed before their
fixes. An initial abort/retry test exposed JSON object-key ordering in
the saved scope; deep equality fixes that case, and the full migration
suite passed.
- Three new fixtures initially omitted required workspace fields; one
also omitted its task binding. The fixtures now satisfy the existing
contract; production validation was not relaxed.
- The parent PR's full local suite stopped on a chat retry-notice
timeout and a following fake-timer failure. All four relevant cases
passed separately without source changes; this is not a claim that the
full local suite passed.
- No staging enrollment or live rolling-deploy result is claimed by this
PR.

## Risks

The offline flag is an internal maintenance contract: Cloud must
actually stop every predecessor deployment before starting the process.
Deep or large trees require additional object downloads and source
comparisons and may exceed one maintenance window; import journals
support retry. Dependency lookups are company-scoped and event-driven,
with no added idle polling. Unavailable archived sources and unresolved
recovery/copyback still block migration and require separate owner work.
Unsupported local providers and cleanup_failed workspaces deliberately
remain enrollment blockers: filtering them from both the batch and
eligibility would abandon local files when the volume is detached. Two
regression tests prove that rejected records keep their files and do not
acquire remote authority. Original folders and the original volume
remain retained. The additional review request to ignore parallel-write
policy, stale task ownership, or a missing task binding in offline mode
is intentionally not applied: stopping deployments does not establish
those semantics. #18841 documents the separate owner actions and adds
three offline regressions preserving their files and blocking detach.
The batch reports the first failed workspace and supports journaled
retry; it does not promise aggregate diagnostics.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. This session does not expose a more specific serving model ID
or context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with available 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 described the issue in-PR using the bug template
- [x] I have not referenced internal or instance-local issues or links
- [x] My branch name describes the change and contains no internal task
ID
- [x] I have run the focused tests locally and they pass
- [x] I have added tests for the changed behavior
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address reviewer findings before merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-03 14:50:14 -06:00