Files
PaperClipAI/maps/agent-access-map/workspaces.md
T
DottaandPaperclip 6bec153410 fix(runtime): retain cancelled workspaces before reconstruction (#16700)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Native runs must preserve files when Stop ends a turn before its
result is saved.
> - The cancellation path could skip file retention and leave a prepared
remote workspace active.
> - An older controller could also admit an extra run while an earlier
lost-source hold was unresolved.
> - Rejecting every later run then prevented recovery even after that
extra run was stopped and its files were reconciled.
> - This change retains cancelled workspaces and records the exact proof
for a safely reconciled intervening cancellation.
> - Recovery can proceed without changing cancelled history, replaying
actions, or weakening execution gates.

## Linked Issues or Issue Description

Refs: #16583, #16690, #16696.

**What happened?**

Stop could acknowledge a native turn without recording its unsaved file
obligation. Reconstruction also rejected a later cancelled run after its
exact environment and files had been reconciled. The task could remain
inactive with an `in_progress` status, which the reconstruction endpoint
rejected.

**Expected behavior**

Retain unsaved files after Stop. Permit a board operator to reconstruct
from saved sources only after each intervening cancellation has a
confirmed stop and a file-only repair record. Preserve the original
failed export and the cancelled run.

**Steps to reproduce**

1. Prepare a native workspace and cancel the turn before a structured
result exists.
2. Inspect its file retention and remote lease state.
3. For a task with an older destroyed export, reconcile the later
cancelled run's exact files and unperformed actions.
4. Request reconstruction from verified same-task attachments.

## What Changed

- Pin prepared native files before cancellation settlement. Defer
teardown if pinning fails. Use the existing stop-only cleanup worker.
- Repair active warm and ephemeral sources, plus stopping and
confirmed-stopped historical cancellations in bounded startup and
periodic batches. Preserve the existing exact-source checks. Bound
row-lock waits and advance past a busy source.
- Accept only same-owner native run-only cancellations with no result or
current coordinator, confirmed stopped leases, and board file-only
repair with `not_performed` action outcomes.
- Save hashes of each intervening repair and recheck them at queue claim
and final dispatch. Reject changed evidence or another later run.
- Expose saved-source reconstruction in the issue API for inactive
`blocked` and `in_progress` tasks, and hide it when an execution or
checkout owner exists. Permit the same inactive source at submission.
Keep the source, owner, version, attachment, budget, pause and retry
checks.
- Update the shared evidence type, recovery runbook and capability maps.
Add an optional cancelled-native no-action confirmation to file
recovery, resetting evidence when its source changes. No migration, new
endpoint, permission or dependency.

## Verification

- The new fixture fails on the original validator with
`workspace_reconstruction_stale` after valid cancelled-run file repair.
- Focused source and Stop retention tests: 94 passed before the final
contention case. The subsequent three-suite run passed 123 tests,
including real PostgreSQL lock contention and public
startup/API/final-dispatch paths. The final narrow-query rerun passed
all 123 tests. Review correction: all 143 tests passed across four
suites, including real lock contention through the teardown boundary and
stopping/stopped allocations.
- Six broader cancellation, executor, heartbeat and restart suites: 831
passed, 9 existing skips.
- Final correction: all 145 backend tests and all 36 recovery UI tests
passed. Real contention covers both warm reusable and ephemeral sources.
Optional no-action confirmation defaults to uncertified, and changing
source clears evidence. Workspace `pnpm -r typecheck` and token gates
passed on these sources.
- Maps, module boundaries and Git whitespace checks: passed. Full `pnpm
build` passed on the final `4e8c0e73ac` revision as well as the prior
reviewed heads. A full local run of the preceding fix completed with
21,724 passes, 109 skips and three failures in webhook body limits, a
status-card timeout, and a port-allocation fixture. All three suites
then passed unchanged in an isolated rerun (209 tests). The full local
run remains recorded as failed; the final head's full CI passed (55
completed checks, no failures). A fresh full local run is also in
progress on the frozen reviewed head.
- Preserved failures: the first attempt lacked a built plugin SDK; after
building prerequisites, the regression reached the expected validator
rejection. Treatment tests caught the file-only action's deliberately
null continuation owner. A real lock test then caught the database
driver's wrapped timeout error. Both defects were corrected and
retested. Independent review then found a teardown race after a failed
retention write. A second review found active warm sources excluded by
the historical policy filter; the regression reproduced both warm
failures before correcting the query. Its real-row-lock fixture
reproduced the failure; the fixed release guard and historical
stopping/stopped cases pass. No checks were weakened.
- A pre-deployment walkthrough caught the issue API still hiding
reconstruction for an inactive `in_progress` task. The status/capability
baseline reproduced three failures, including inappropriate capability
flags for execution/checkout owners. The fix uses the same
inactive-state requirements as submission; its positive fixture reads
the assembled issue API before reconstructing. All 117 tests in those
three recovery suites pass, and all 28 heartbeat-recovery tests pass
separately. Full workspace typecheck passed again. Full build passed
with the exact `4e8c0e73ac` stamp; latest-head independent review is 5/5
with no open findings. All 55 latest-head checks completed with no
failures.
- Live tenant acceptance remains pending. No successful reconstruction,
export or warm-process proof is claimed by these local tests.

## Risks

- Stop retains unfinished work, so storage remains until file recovery
and explicit cleanup complete.
- Reconstruction remains held for unknown or performed external actions,
foreign owners, live or rebound environments, accepted results, changed
evidence, and unrelated later runs.
- The sweep examines at most five candidates per pass. It uses a fair
cursor and bounded transaction waits. It performs no provider call and
starts no new turn.
- Intervening verification reads only the needed JSON fields. The source
transaction still serializes recovery for the same task.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, tool use and code execution.
The session does not expose an exact 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-10-18 01:33:11 -05:00

253 lines
14 KiB
Markdown

# Execution workspaces, services, and files — agent access
Product map: [Execution workspaces, services, and files](../feature-map/workspaces.md).
Lost-source reconstruction adds the board-only
`POST /api/issues/{id}/recovery-actions/reconstruct-workspace` action. It requires
`runtime:manage` and ordinary task visibility. Agents cannot authorize it. The
result is a durable fresh-session admission request, subject to current execution
gates. It grants no additional tool access, budget, work window or permission to
repeat completed actions. Attachments must belong to the same task and company.
The original failed run and lost-export evidence remain unchanged.
Later native run-only cancellations can coexist only with confirmed lease stops,
board file-only repair and an external-action outcome of `not_performed`.
Their immutable evidence is checked again at dispatch. Historical cancellation
retention runs internally and grants no new agent operation or authority.
Review date: 2026-10-12. Source commit: `58e16dbddfc9c8d0cf60867f9d9847f0feda055b`.
Native REST transport reconciled in this change; see the
[shared transport contract](README.md#native-rest-transport).
This is a source-reviewed baseline of representative operations and known gaps.
Each capability needs operation-level review before its entire scope can be
qualified. No live agent execution is claimed. Follow the [map contract](../README.md)
for format, permission checks, evidence and maintenance.
## `binding`
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/issues/{id}/heartbeat-context`.
- `GET /api/execution-workspaces/{id}`.
- `GET /api/issues/{id}/workspace` inspects the durable task binding.
- `PUT /api/issues/{id}/workspace` requests the next binding.
- `GET /api/companies/{companyId}/execution-workspaces?summary=true&selectableForTask=true`
lists authorized task-selectable workspaces.
Agent access: `available` — Native get_task_context / REST / CLI / MCP workspace read.
The task-bound native tools `get_workspace`, `list_workspaces`, and
`select_workspace` use the ordinary authenticated APIs. Legacy agents can call
those APIs with their admitted run token. These paths do not require a project.
Policy: Company/project/workspace visibility and runtime authority; active run, trust
preset and task ownership where required.
Task workspace writes require `issue:mutate`, an active owned task/run for agents,
and current source visibility. Shared physical roots also require source-project
workspace write authority. Accepting a new pending choice advances its binding
revision; superseded retries fail while current-choice retries remain idempotent.
Binding revisions and request keys fence retries;
selection applies at the next normal admission without interrupting a live run.
Fresh standard-trust tasks with an implicit repository default can use an owned
remote clone when the shared default source fails the restricted-task workspace
guard. This does not grant access to the private local checkout or its Git metadata.
The normal admitted Git identity must already authorize the configured remote.
Creation of a fallback source is unavailable for explicit, parent/channel, reused
or low-trust selections, or branch templates without a task-derived placeholder;
an existing verified owned source remains reusable. Literal branch selections
retain the original denial without cloning or replacing their unpublished commits.
New implicit Git defaults record project-source provenance. Untyped explicit,
inherited and older ambiguous checkout bindings retain the denial; admission reads
the current company-scoped binding before considering the fallback.
A retained denial blocks the task as `configuration_incomplete`
(`workspace_source_restricted`) before dispatch; it grants no access and names the
owner action on the task.
Local paths, unsupported transports and credential-bearing URLs are not fallback
sources. Company/task/remote bindings, real paths, Git storage and the exact origin
are revalidated for reuse, cleanup and reopening. A failed save removes only the
owned task worktree. The [isolated-source service](../../server/src/services/isolated-remote-workspace-source.ts)
and its local regression tests establish source and lifecycle boundaries; live
agent/provider qualification remains separate.
Reconstructable Git expiration adds no agent endpoint or permission. It records
company/workspace/repository identity and an exact commit before removing owned
cache files. Existing task/workspace reopening retains current company, source,
and trust checks. Missing upstream access returns a retryable error; it does not
choose a newer commit or grant access to another company's source. Shared project
bases and unresolved recovery remain protected. Local lifecycle evidence is in
[expiration tests](../../server/src/__tests__/git-workspace-expiration.test.ts).
Writable aliases must also be compatible with current and retained physical
source projects' trust boundaries. Incompatible choices are rejected before
dispatch; a projectless alias cannot bypass required sandbox containment.
Source-copy checks apply the same trust boundary before provisioning. The
authoritative sandbox or SSH selection suppresses configured and built-in
controller worktree and runtime provisioning regardless of task trust, source
write grants or board sponsorship. Genuine isolated worktrees still permit
authorized read-only source copying. Source write permission does not authorize
controller execution. Explicit, channel, inherited, foreign or nondefault isolated
source choices remain copy-only for controller provisioning even on local runs.
Their deny-only workspace marker survives metadata and binding changes; a board
reopen does not turn it into approval. Implicit trusted local defaults and explicit
SSH remote provisioning retain their existing placement; no new sandbox provision transport
is claimed. The implicit default follows task creation: configured policy pointer,
then primary source, creation time and ID. A missing or foreign configured source
does not authorize controller hooks. This precedence neither changes existing
creation validation nor rotates bindings or clears retained copy-only markers.
Closed-worktree reopening checks current task, agent, organizational and source policies without assuming a future driver. Effective low-trust reopens suppress configured and built-in host scripts. Returned checkpoints must have directory parents for every nested entry before merging, including unchanged entries; invalid output leaves retained host files intact and keeps recovery evidence.
Skipping the host scan for a fully unchanged returned snapshot grants no new
access. The merge still holds its canonical writer lock and runs both authority
and publication hooks. Denials still propagate, and checkpoint/archive validation
is unchanged. [Regression coverage](../../packages/adapter-utils/src/workspace-restore-unchanged.test.ts)
is source-level evidence only.
Child creation by an admitted agent checks an explicit prospective-task context before the child is saved. The creator run must have an authoritative saved policy and a verified company/agent/parent lineage. Current parent, prospective child and physical source policies must fit that boundary. This creation path does not relax exact task/run checks for existing-task writes. The [child trust regression](../../server/src/__tests__/task-workspace-child-trust.test.ts) is automated source-level evidence, not live agent qualification.
Source review for these additions: PR #15749 and
[task workspace routes](../../server/src/routes/task-workspaces.ts),
[native authority](../../server/src/services/native-runtime/paperclip-runner-tool-authority.ts),
and [semantic transport](../../server/src/services/task-workspace-tools.ts).
Evidence: `source-only` —
[implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the
reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Run/task context identifies its actual realized workspace; record existence is not
a running service.
## `provisioning`
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/execution-workspaces/{id}`.
- `GET /api/execution-workspaces/{id}/workspace-operations`.
- `POST /api/issues/{id}/workspace/repositories` requests repository preparation.
Agent access: `partial` — REST / native call_api reads; task/project assignment triggers
server provisioning.
Native `prepare_repository` and admitted legacy REST callers can request a
catalog repository or validated repository URL for their current task.
Preparation occurs at normal admission; the agent cannot supply a host path or
controller identity. Current source permissions, responsible-user intersection,
trust restrictions, immutable receipts, and retained physical-root ownership are
checked before acquisition and publication. Low-trust callers cannot trigger a
new controller-side Git acquisition. Evidence remains source-only here; focused
[repository tests](../../server/src/__tests__/task-workspace-repositories-trust.test.ts)
are separate from live agent qualification.
Policy: Company/project/workspace visibility and runtime authority; active run, trust
preset and task ownership where required.
Evidence: `source-only` —
[implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the
reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Observing readiness exists. Creating host commands or arbitrary provision targets
is a separate protected operation.
## `services`
Review: `baseline`.
API: `present` — representative operation IDs:
- `POST /api/execution-workspaces/{id}/runtime-services/{action}`.
Agent access: `available` — Authorized agent REST / native `call_api` / CLI runtime
service controls and existing stdio MCP controls.
Policy: Company/project/workspace visibility and runtime authority; active run, trust
preset and task ownership where required.
Evidence: `source-only` —
[implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the
reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: No dedicated production control_workspace_service binding. Generic native
calls retain runtime trust and workspace/task scope checks. Optional MCP availability
must be recorded.
## `files-git`
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/issues/{issueId}/file-resources/content`.
- `GET /api/issues/{issueId}/file-resources/list`.
Agent access: `partial` — Authorized workspace filesystem tools; Dot workspace bridge
when enabled; board file-resource HTTP UI.
Policy: Company/project/workspace visibility and runtime authority; active run, trust
preset and task ownership where required.
Evidence: `source-only` —
[implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the
reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Files panel HTTP requires board. Agents can use their admitted workspace file
tools; remote/Dot scope and Git command availability differ.
File reads also enforce retained source privacy after local copy, configuration
move/removal, reopen, and project deletion. A task assignment or repository
receipt does not grant access to private files from another physical owner.
Authorized Git-source capture also retains ownership on the actual shared Git
storage. Source checks use verified checkout identities, including company-known
owners when separate metadata has no primary-checkout registration. Removing that
checkout does not release its private committed files in surviving worktrees.
## `closure`
Review: `baseline`.
API: `present` — representative operation IDs:
- `PATCH /api/execution-workspaces/{id}`.
- `GET /api/execution-workspaces/{id}/close-readiness`.
- `POST /api/execution-workspaces/{id}/reconcile-branch`.
Agent access: `partial` — REST / native fallback for permitted update; CLI workspace;
reconcile-branch requires board.
Policy: Company/project/workspace visibility and runtime authority; active run, trust
preset and task ownership where required.
Evidence: `source-only` —
[implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the
reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Closure/readiness exists; not every workspace can be reopened, and branch
reconciliation has a board boundary.