Merge workspace isolation fix and reject unsupported linked Codex homes

This commit is contained in:
Dotta committed 2026-10-13 15:02:50 -05:00
commit 1d09569b05
23 files changed
+639 -68

No files matched your search

+23 -1
View File
@@ -44,7 +44,29 @@ authored IDs, both review histories and all four incoming recovery states. Its
27 existing controls, maintained catalog check and35-area/175-ID map check pass.
Only JavaScript dependencies are prepared locally; native/image builds stay remote.
Next action: finish focused combined-source checks and freeze one candidate. Require fresh
The combined736 source passes368 focused checks across10 files with zero final
skips. The initial sandbox listener failures are preserved separately; unchanged
support probes and owned loopback execution reach the affected tests successfully.
Incoming master `c7f4b74111d16f0b57112fa6d0b6a59c2d63bfe8` then adds fresh-task
workspace source isolation. Its eleven server files remain exact master, and281
affected source/trust/preparation/runtime controls pass without repeating the368.
The catalog retains263 suites/2503 IDs and both histories.
An actual task bootstrap check finds a narrow compatibility mismatch: an explicitly
configured or operator `CODEX_HOME` with linked auth can pass setup while task
isolation omits that link. Ordinary new defaults create no such link; regular host
logins and server-selected managed account homes seed correctly. Keep the general
no-follow guard. Use existing server-owned managed credential provenance to reject
the unbound linked case with explicit Legacy/managed-account guidance, while
retaining checked setup seeding for resolved accounts. The existing fake bootstrap
stops before provider launch; it is not live account proof. The existing trusted
outer managed credential-home context closes this mismatch without a new schema
or permission field. Four linked cases reproduce false setup success before the
guard;81 final setup controls, server no-emit and independent source review pass.
Regular homes, API-key precedence and resolved-account setup aliases remain
supported. Raw configuration cannot provide the trusted provenance.
Next action: freeze and publish the combined candidate. Require fresh
CI/review, affected installed-package and managed first-task proof before ready
or merge. Historical receipts keep their literal sources. The following
checkpoints are retained history, not the current merge state.
+20
View File
@@ -36,6 +36,26 @@ See [Project Repository Checkouts](DEVELOPING.md#project-repository-checkouts) f
ignore rules, and reuse behavior. Responsible-user credential rules still apply.
A repository selection never delegates credentials.
Fresh tasks using the default repository workspace can clone the same authorized
remote into a company- and task-scoped source directory when an existing project
checkout contains another task's restricted workspace. The clone uses the run's
Git connection and keeps separate Git metadata. Source access checks still apply
before the task workspace is published. Low-trust review runs cannot use this
persistent clone fallback. This fallback does not copy unpublished
local changes or replace reused workspaces, explicit workspace selections, pinned
branches, or repositories that only exist on the local filesystem.
Configured branch templates must derive the branch from the task identifier, ID,
title, or slug. Literal or task-independent templates retain the source denial,
so a fresh clone cannot replace a selected local branch with a new empty branch.
New implicit Git workspace defaults record their project-source provenance.
An untyped checkout binding from an older task is ambiguous and keeps the denial,
including explicit checkout IDs and inherited parent sources. Admission rechecks
the current binding rather than inferring a default from the selected checkout ID.
Startup validation of these recorded defaults keeps the task's effective workspace
strategy. It does not substitute the agent's shared strategy or grant project writes.
Workspace close and reopen use the recorded isolated source. Both operations
verify its company, task, repository URL, and separate Git storage before use.
## Discovery and setup
The server checks company membership, grant ownership/status, and organization-grant
+5 -2
View File
@@ -91,8 +91,11 @@ or Legacy CLI check cannot replace that runtime evidence. Missing dependencies,
credentials, invalid models and incompatible settings fail without silently
changing runners. Account login remains board/owner-bound.
For a linked Codex subscription, setup checks the resolved credential owner and
stages a private probe copy before local or remote isolation. The general native
For a linked Codex subscription with server-resolved managed credential provenance,
setup checks the resolved owner and stages a private probe copy before local or
remote isolation. An unbound linked `CODEX_HOME` is rejected with explicit Legacy
or managed-account guidance. Caller configuration cannot supply that provenance.
The general native
home materializer retains its no-follow symlink guard. Refresh still targets the
checked account source, and confirmed bounded retirement removes its private
copy. Unconfirmed retirement retains the existing cleanup block and owned state.
+18
View File
@@ -39,6 +39,24 @@ 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.
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.
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.
+7 -4
View File
@@ -59,10 +59,13 @@ existing agent, make an unrelated edit, and confirm both its persisted runner an
next run stay unchanged. Use [hiring/import preservation](./agents.md#hire-agent)
for approval and portability controls.
Test native Codex with a saved subscription whose auth file is a managed link,
as well as a regular file and an API-key account. Setup must seed the isolated
probe from the checked account without exposing credentials or changing its
saved runner. Dangling or incorrectly owned credentials must fail clearly. The
Test native Codex with a saved subscription whose setup auth file is a managed
link, as well as a regular file and an API-key account. Setup must seed the
isolated probe from the checked account without exposing credentials or changing
its saved runner. An unbound configured `CODEX_HOME` with linked credentials must
fail setup with managed-account or explicit Legacy guidance; it must not claim
readiness when task isolation would omit the file. Dangling or incorrectly owned
credentials must also fail clearly. The
private probe copy must be removed after confirmed retirement following success,
failure or cancellation. Unconfirmed shutdown remains a cleanup failure.
Existing setup controls use fake credentials; real account execution remains
+19
View File
@@ -34,6 +34,25 @@ Automated: [workspace card](../../ui/src/components/IssueWorkspaceCard.test.tsx)
Manual: Run a task that writes a small file, follow the bound workspace, and inspect the file and revision. Reload and verify links still refer to the same workspace, not another checkout or a stale run.
For fresh standard-trust tasks using an implicit repository default, create a
disposable project checkout whose shared Git storage overlaps a private task
workspace. Verify that source admission creates a separate checkout from the
configured remote, with no private local branches, commits or shared Git storage.
Reusing, closing and reopening that task must retain the company/task-bound source;
a failed workspace save must clean up only its owned checkout. Explicit workspace,
parent/channel selections, literal or task-independent branch templates, low-trust
tasks, and local or credential-bearing remote
URLs must not use this fallback. A selected checkout requires recorded default
provenance. Create tasks
through the task service with an explicit checkout ID or an inherited parent
source but no typed selection; verify denial, no clone or environment acquisition,
and preservation of unpublished branch commits. Older ambiguous bindings and
source choices changed during preparation must retain the denial. The
[remote-source tests](../../server/src/services/isolated-remote-workspace-source.test.ts)
and [preparation tests](../../server/src/services/heartbeat/workspace-preparation.test.ts)
exercise the real Git isolation and lifecycle guards. They do not qualify live
remote credentials or a provider deployment.
For task workspace selection, use the authorized task workspace API to choose a
task directory, project source, or existing workspace. Verify that a change
made during a run stays pending until the next normal admission. Submit another
+14 -14
View File
@@ -80,7 +80,7 @@ Creation, assignment, lists, properties, comments, and completion.
| [Tasks and conversations/Task creation and lifecycle/External Objects](../../ui/storybook/stories/tasks/external-objects.stories.tsx) | `tasks/properties` | unreviewed / production / fixtures | 8 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation visibility now defaults to hiding automated independent tasks, with a show agent-created tasks control and inbox participation exceptions. Source alignment reviewed; these fixture stories do not establish live server pagination or authorization. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Tasks and conversations/Task creation and lifecycle/Product/Issue Management](../../ui/storybook/stories/tasks/issue-management.stories.tsx) | `tasks/properties`, `tasks/organize` | unreviewed / production / fixtures | 11 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation visibility now defaults to hiding automated independent tasks, with a show agent-created tasks control and inbox participation exceptions. Source alignment reviewed; these fixture stories do not establish live server pagination or authorization. Focused creation-visibility stories mount the production collection with simulated hierarchy reads and assert hidden roots, opt-in inclusion, and the unchecked filter control. Creation visibility examples and fixture setup now live in a support module so the surface matrix stays below 1000 lines; exported story IDs are unchanged. Creation visibility now shares the subscribed preference across modern and legacy controls; hierarchy child reads share a bounded rendering budget. Source alignment inspected; server behavior is tested separately. Hierarchy fetch and rendering budgets are separate; loaded context remains during paging. The large-tree UI regression exercises delayed reads in both list interfaces. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Tasks and conversations/Task creation and lifecycle/Components/Entity pickers/Mobile](../../ui/storybook/stories/tasks/mobile-entity-pickers.stories.tsx) | `tasks/create` | unreviewed / production / fixtures | 3 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Tasks and conversations/Task creation and lifecycle/Composer/New task](../../ui/storybook/stories/tasks/new-task-composer.stories.tsx) | `tasks/create`, `workspaces/binding` | current / production / fixtures | 40 / 25 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. WorktreePicker, ReuseWorktree, CreateWithReusedWorktree and NewWorktreeAfterReuse exercise workspace selection and submitted fixture request values. Actual admission/binding and execution remain outside this story. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Tasks and conversations/Task creation and lifecycle/Composer/New task](../../ui/storybook/stories/tasks/new-task-composer.stories.tsx) | `tasks/create`, `workspaces/binding` | current / production / fixtures | 40 / 25 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. WorktreePicker, ReuseWorktree, CreateWithReusedWorktree and NewWorktreeAfterReuse exercise workspace selection and submitted fixture request values. Actual admission/binding and execution remain outside this story. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Tasks and conversations/Task creation and lifecycle/Tasks/Runner activity preview](../../ui/storybook/stories/tasks/runner-activity.stories.tsx) | `tasks/discussion`, `runs-adapters/runs` | current / production / fixtures | 14 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The managed-link credential and confirmed probe-retirement recipe was inspected; fixture responses do not verify a real account or cleanup. |
| [Tasks and conversations/Task creation and lifecycle/Paperclip/Successful Run Handoff](../../ui/storybook/stories/tasks/successful-run-handoff.stories.tsx) | `tasks/completion`, `recovery/diagnosis` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Master reconciliation inspected assignment-history recipient suggestions and manual-selection retention; existing stories do not establish the new assignment-history behavior. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents source-only retained-file repair; existing fixtures do not establish live provider recovery. |
| [Tasks and conversations/Task creation and lifecycle/Tasks/Task chat review fixes](../../ui/storybook/stories/tasks/task-chat-chain-of-thought.stories.tsx) | `tasks/discussion`, `runs-adapters/runs` | unreviewed / production / fixtures | 4 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The managed-link credential and confirmed probe-retirement recipe was inspected; fixture responses do not verify a real account or cleanup. |
@@ -191,9 +191,9 @@ Versioned documents, annotations, files, outputs, and artifact library.
| [Tasks and conversations/Documents, attachments, and work products/Product/Document Comments](../../ui/storybook/stories/documents-artifacts/document-comments.stories.tsx) | `documents-artifacts/annotations` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. |
| [Tasks and conversations/Documents, attachments, and work products/Document revisions](../../ui/storybook/stories/documents-artifacts/document-revisions.stories.tsx) | `documents-artifacts/documents` | current / production / fixtures | 9 / 4 | IssueDocumentsSection renders current/edit/historical preview and append-only restore with mutation error. Read/loading failures retain shipped fallback rendering; no persisted concurrency or authorization proof. |
| [Tasks and conversations/Documents, attachments, and work products/Components/File Viewer Sheet](../../ui/storybook/stories/documents-artifacts/file-viewer.stories.tsx) | `documents-artifacts/attachments` | unreviewed / production / fixtures | 13 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview](../../ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace](../../ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey](../../ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview](../../ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace](../../ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey](../../ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Tasks and conversations/Documents, attachments, and work products/Task Chat/Rich Work Product Cards](../../ui/storybook/stories/documents-artifacts/rich-work-product-cards.stories.tsx) | `documents-artifacts/work-products` | unreviewed / production / fixtures | 9 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. |
| [Tasks and conversations/Documents, attachments, and work products/Tasks/Artifact Gallery](../../ui/storybook/stories/documents-artifacts/task-artifact-gallery.stories.tsx) | `documents-artifacts/library`, `documents-artifacts/work-products` | unreviewed / production / fixtures | 8 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. |
| [Tasks and conversations/Documents, attachments, and work products/Tasks/Text file tabs](../../ui/storybook/stories/documents-artifacts/task-text-files.stories.tsx) | `documents-artifacts/attachments` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. |
@@ -296,9 +296,9 @@ Project lifecycle, task context, repository configuration, and defaults.
| Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note |
| --- | --- | --- | --- | --- |
| [Projects and execution/Projects and repositories/Proposals/Project repos/New project](../../ui/storybook/stories/projects/new-project-repos.stories.tsx) | `projects/repositories` | proposed / mixed / fixtures | 18 / 5 | RepositoryEditor is shipped, but NewProjectPrototype composes its own creation flow. Preserve proposal until route parity is checked. |
| [Projects and execution/Projects and repositories/Workspaces/Project runtime provision command](../../ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx) | `projects/defaults`, `workspaces/provisioning` | unreviewed / production / fixtures | 2 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Projects and repositories/Workspaces/Project runtime provision command](../../ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx) | `projects/defaults`, `workspaces/provisioning` | unreviewed / production / fixtures | 2 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Projects and repositories/Proposals/Project repos/Configuration](../../ui/storybook/stories/projects/project-repos-configuration.stories.tsx) | `projects/repositories` | proposed / mixed / fixtures | 12 / 0 | Production properties plus local configuration page composition; repository interaction is simulated. |
| [Projects and execution/Projects and repositories/Product/Projects Goals Workspaces](../../ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx) | `projects/lifecycle`, `goals/hierarchy`, `workspaces/binding` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Projects and repositories/Product/Projects Goals Workspaces](../../ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx) | `projects/lifecycle`, `goals/hierarchy`, `workspaces/binding` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
### Goals and work alignment
@@ -318,14 +318,14 @@ Provisioning, task bindings, services/logs, Git/files, and closure.
| Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note |
| --- | --- | --- | --- | --- |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity](../../ui/storybook/stories/workspaces/browser-use-activity.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer](../../ui/storybook/stories/workspaces/browser-use-footer.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 21 / 2 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel](../../ui/storybook/stories/workspaces/browser-use.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 28 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. |
| [Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker](../../ui/storybook/stories/workspaces/searchable-select.stories.tsx) | `workspaces/binding` | unreviewed / production / fixtures | 9 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Workspaces, services, and files/Close readiness](../../ui/storybook/stories/workspaces/workspace-close.stories.tsx) | `workspaces/closure` | current / production / fixtures | 7 / 7 | Readiness dialog with loading, inspection failure/403, running-task block and pending/accepted archive response. No real workspace cleanup, Git removal or service shutdown is performed. |
| [Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser](../../ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx) | `workspaces/files-git` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status](../../ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx) | `workspaces/provisioning` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Workspaces, services, and files/Workspaces/Service control bar](../../ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx) | `workspaces/services` | unreviewed / production / fixtures | 16 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity](../../ui/storybook/stories/workspaces/browser-use-activity.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer](../../ui/storybook/stories/workspaces/browser-use-footer.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 21 / 2 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel](../../ui/storybook/stories/workspaces/browser-use.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 28 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker](../../ui/storybook/stories/workspaces/searchable-select.stories.tsx) | `workspaces/binding` | unreviewed / production / fixtures | 9 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Close readiness](../../ui/storybook/stories/workspaces/workspace-close.stories.tsx) | `workspaces/closure` | current / production / fixtures | 7 / 7 | Readiness dialog with loading, inspection failure/403, running-task block and pending/accepted archive response. No real workspace cleanup, Git removal or service shutdown is performed. The separate remote-source recipe requires Git isolation and preservation of unpublished task work; these close-readiness fixtures do not exercise repository cloning or reconstruction. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser](../../ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx) | `workspaces/files-git` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status](../../ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx) | `workspaces/provisioning` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
| [Projects and execution/Workspaces, services, and files/Workspaces/Service control bar](../../ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx) | `workspaces/services` | unreviewed / production / fixtures | 16 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings. |
### Execution environments
+21
View File
@@ -855,3 +855,24 @@ visual baseline, provider call or live acceptance is claimed.
The maintained catalog/index checker, all 27 catalog/generator/map controls and
the 35-area/175-capability structural map check pass. No unowned generated
artifacts or authored story files changed during this reconciliation.
## Fresh-task isolation recipe reconciliation — 2026-10-13
Reconciled candidate `736d32682290fe9d6c3157e47a36b31a431fb45c` with master
`c7f4b74111d16f0b57112fa6d0b6a59c2d63bfe8`. The auto-merged catalog retains
all 263 suites and 2,503 authored story IDs, both review histories, the Codex
suite and all four retained-file recovery states. Regenerated the conflicted
index from that combined catalog. Classifications and simulated-data boundaries
remain unchanged.
Inspected the incoming workspace recipe: implicit repository defaults must use
a separately admitted remote checkout when shared Git storage overlaps private
task work, with lifecycle and current-default provenance guards. Its catalog
notes explicitly leave cloning, admission and live recovery outside these UI
fixtures. The incoming reviewed fingerprints already match the merged source;
none were silently refreshed. This is source inventory reconciliation only,
not new browser, provider or production acceptance.
The maintained catalog/index checker, all 27 catalog/generator/map controls and
the 35-area/175-capability structural map check pass. No authored story or
application source was changed by this reconciliation.
+1 -1
View File
@@ -1,6 +1,6 @@
# Verification routes for Storybook capabilities
Updated 2026-10-13. Source inventory is reconciled from candidate `12046f522b` and master `393d1c4108`; the authorized draft reload fix, retained-file recovery states and inspected recipe changes are recorded in the catalog fingerprints.
Updated 2026-10-13. Source inventory is reconciled from candidate `736d326822` and master `c7f4b74111`; the prior authorized draft reload and retained-file recovery reviews are preserved alongside the inspected fresh-task source-isolation recipe.
The catalog has 263 authored suites, 2,503 authored story IDs and 175 feature-map capabilities. The 37 new production-subject suites add 257 scenarios and associate all 49 UI capabilities identified in step 2. **11 capabilities remain without authored examples: 8 CLI/operator operations and 3 real-browser concerns.** Generated Overview pages and guides do not count as examples. Existing suites and story IDs are retained. The four retained-file recovery states from master `393d1c4108` are preserved alongside the Codex suite and examples previously merged from master `c8efdbfba43`; this inventory update does not claim an additional production verification run.
+28 -28
View File
@@ -1248,7 +1248,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/tasks.md",
@@ -1259,7 +1259,7 @@
"ui/storybook/fixtures/browser-use.tsx",
"ui/storybook/stories/workspaces/browser-use-activity.stories.tsx"
],
"fingerprint": "f0e77b99e99a69532f45d58546a2324893543911771e850a47ca3452818bcf3d",
"fingerprint": "00c08cb6566e12b7fd9dce179296397813ea3e2ab4ee47d27cd1ca948496ca15",
"stableId": "tasks-browser-use-task-activity",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity"
},
@@ -1273,7 +1273,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/tasks.md",
@@ -1283,7 +1283,7 @@
"ui/storybook/fixtures/browser-use.tsx",
"ui/storybook/stories/workspaces/browser-use-footer.stories.tsx"
],
"fingerprint": "b60b13c6ff934b038616eee7da6aeeaac0f595aeb7c8d80d2a2f787c7bb0dbac",
"fingerprint": "fda7ec26101f5b6fe68da1f7b6a96c364878c5f14884d639e406412e8341959f",
"stableId": "tasks-browser-use-footer",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer"
},
@@ -1319,7 +1319,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/tasks.md",
@@ -1330,7 +1330,7 @@
"ui/storybook/fixtures/paperclipData.ts",
"ui/storybook/stories/workspaces/browser-use.stories.tsx"
],
"fingerprint": "7dea88462587a256933271c48cfef7ff339f55acd1f77a38bf89ad537c63f3ae",
"fingerprint": "dafe2721334717ad745e72100e23577dbe3816a3b9ee9224a114ff84fbbb4729",
"stableId": "tasks-browser-use-panel",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel"
},
@@ -2732,7 +2732,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/documents-artifacts.md",
@@ -2741,7 +2741,7 @@
"ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx",
"ui/storybook/support/html-artifacts/fixtures.ts"
],
"fingerprint": "9f96f1e4abf214d663906e05b0a8ce231f06217fc5f022415a84df33c1d0ac96",
"fingerprint": "404690079b8b85bf7aaab867266a14096f7d57bea2665032efba2f82617008df",
"stableId": "html-artifacts-01-preview",
"sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview"
},
@@ -2755,7 +2755,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/documents-artifacts.md",
@@ -2765,7 +2765,7 @@
"ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx",
"ui/storybook/support/html-artifacts/fixtures.ts"
],
"fingerprint": "47c33fce5f296031d43f31d7f099abcef40393cfe51e565db542b7e8230a00a3",
"fingerprint": "4ced4be164f64cb1dd0158cf3afc9939e63e4447a3911b33e664544277693f0b",
"stableId": "html-artifacts-02-workspace",
"sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace"
},
@@ -2779,7 +2779,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/documents-artifacts.md",
@@ -2792,7 +2792,7 @@
"ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx",
"ui/storybook/support/html-artifacts/fixtures.ts"
],
"fingerprint": "470396b938981d10ddc74d03cec0808aaa1fb305dcdeb8056b308c2752f7cea7",
"fingerprint": "c5cb892dd95ec005e52e6c61de414aedb5fe2a443a729038767028edd3c52b6d",
"stableId": "html-artifacts-03-task-journey",
"sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey"
},
@@ -3254,7 +3254,7 @@
"status": "current",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. WorktreePicker, ReuseWorktree, CreateWithReusedWorktree and NewWorktreeAfterReuse exercise workspace selection and submitted fixture request values. Actual admission/binding and execution remain outside this story. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. WorktreePicker, ReuseWorktree, CreateWithReusedWorktree and NewWorktreeAfterReuse exercise workspace selection and submitted fixture request values. Actual admission/binding and execution remain outside this story. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/tasks.md",
@@ -3264,7 +3264,7 @@
"ui/storybook/stories/tasks/new-task-composer.stories.tsx",
"maps/feature-map/workspaces.md"
],
"fingerprint": "db3b18dcf012cfd2174900456dd8f8189b2f0b4287d84fbb62562bb0325a0001",
"fingerprint": "efb21d7fdb67867b886668295e55ac1e05f67c71029fb85b7f341c9662217705",
"stableId": "composer-new-task",
"sidebarTitle": "Tasks and conversations/Task creation and lifecycle/Composer/New task"
},
@@ -4037,7 +4037,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/projects.md",
@@ -4046,7 +4046,7 @@
"ui/storybook/fixtures/paperclipData.ts",
"ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx"
],
"fingerprint": "618a389f8aa005657fc633c9a0ebdc34e0fc77df6d1cc8d0474330590d4c081c",
"fingerprint": "89c541df010f3ea0dafa6ed29e02882ffbbecca1aaa90a2606176ffe6b8f9f4d",
"stableId": "workspaces-project-runtime-provision-command",
"sidebarTitle": "Projects and execution/Projects and repositories/Workspaces/Project runtime provision command"
},
@@ -4090,7 +4090,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/goals.md",
@@ -4106,7 +4106,7 @@
"ui/storybook/fixtures/paperclipData.ts",
"ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx"
],
"fingerprint": "83a6815f2ceea9c18eb9145f2abf7f90588dafe833c20abcd93050ee1c66114a",
"fingerprint": "e7330baf164a462aaef557989a6334fd61e9b1f378dd6674f34f4e7bbe8f03bb",
"stableId": "product-projects-goals-workspaces",
"sidebarTitle": "Projects and execution/Projects and repositories/Product/Projects Goals Workspaces"
},
@@ -4790,14 +4790,14 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/workspaces.md",
"ui/src/components/SearchableSelect.tsx",
"ui/storybook/stories/workspaces/searchable-select.stories.tsx"
],
"fingerprint": "524803af7c926b60e8c636031f703d4bb4abe45cd4832c7985f81c8f4d0cb035",
"fingerprint": "e34c07642d243f0011ef4c589cfd061f1b20477f10ee3e299f5c197982acee3c",
"stableId": "components-searchableselect-workspace-picker",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker"
},
@@ -5665,7 +5665,7 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/workspaces.md",
@@ -5673,7 +5673,7 @@
"ui/src/components/IssueWorkspaceCard.tsx",
"ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx"
],
"fingerprint": "980fcd9a6905ac1eae10efeb7979dd8b3769c2cc7d625d4e0ba4b091f4b4d614",
"fingerprint": "a37dfdde49f89e907842859bfebde7d3c88a4ebabce4d003a9e4f722b73bb098",
"stableId": "issue-workspace-file-browser",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser"
},
@@ -5686,14 +5686,14 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/workspaces.md",
"ui/src/pages/ExecutionWorkspaceDetail.tsx",
"ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx"
],
"fingerprint": "f8a7316fb6637151d8da32b637136408cd6fd9d505fe2a2a47f3c0d670b0eeec",
"fingerprint": "4a4e9dc99829632e221a1c558f54502bbba8b1547768d0fdce97cad3c0c5fc02",
"stableId": "workspaces-runtime-provisioning-status",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status"
},
@@ -5706,14 +5706,14 @@
"status": "unreviewed",
"implementation": "production",
"boundary": "fixtures",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.",
"notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. The added verification recipe documents remote-source isolation and lifecycle guards; existing fixtures do not establish live provider recovery. The source recipe now excludes literal and task-independent branch templates; these fixtures do not execute that backend admission guard. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/workspaces.md",
"ui/src/components/WorkspaceServiceControlBar.tsx",
"ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx"
],
"fingerprint": "b9cd51c58b31ab445e2997b9f2a23c7a7b3f4ecd7b6dd39fc0f35ae0779311df",
"fingerprint": "01ffe1c604d850af606be5ebd4a4ba652716ef6b1ca833d6aa6f8d564ced64c1",
"stableId": "workspaces-service-control-bar",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Workspaces/Service control bar"
},
@@ -5875,7 +5875,7 @@
"status": "current",
"implementation": "production",
"boundary": "fixtures",
"notes": "Readiness dialog with loading, inspection failure/403, running-task block and pending/accepted archive response. No real workspace cleanup, Git removal or service shutdown is performed.",
"notes": "Readiness dialog with loading, inspection failure/403, running-task block and pending/accepted archive response. No real workspace cleanup, Git removal or service shutdown is performed. The separate remote-source recipe requires Git isolation and preservation of unpublished task work; these close-readiness fixtures do not exercise repository cloning or reconstruction. The remote fallback requires current default provenance; these examples do not prove admission for explicit or inherited checkout bindings.",
"reviewedAt": "2026-10-13",
"sources": [
"maps/feature-map/workspaces.md",
@@ -5887,7 +5887,7 @@
],
"stableId": "feature-workspace-close",
"sidebarTitle": "Projects and execution/Workspaces, services, and files/Close readiness",
"fingerprint": "b3d1d32d7f1f51f837ecd04f64ca1b6b7e8b36c921d54b638898ef06512617cc"
"fingerprint": "08b1e604b188130184296c8aee286f8dde12c8026654f0760388bd492e1ffe5b"
},
{
"file": "ui/storybook/stories/budgets-costs/budget-resolution.stories.tsx",
@@ -26,6 +26,7 @@ import { instanceSettingsService } from "../services/instance-settings.ts";
import { environmentRuntimeService } from "../services/environment-runtime.js";
import { findNativeChatWorkspaceScope } from "../services/native-runtime/native-chat-workspace.js";
import { drainHeartbeatRunsToQuiescence } from "./helpers/drain-heartbeat-runs.js";
import { executionWorkspaceService } from "../services/execution-workspaces.js";
const execute = vi.hoisted(() => vi.fn(async (_input: any) => ({ exitCode: 0, signal: null, timedOut: false })));
vi.mock("../adapters/index.js", () => ({
@@ -149,6 +150,7 @@ suite("heartbeat physical workspace source trust", () => {
["configured", "both", "local_default"], ["builtin", "both", "local_default"],
["configured", "both", "local_new_default"], ["builtin", "both", "local_new_default"],
["configured", "both", "local_new_primary"], ["builtin", "both", "local_new_primary"],
["configured", "agent", "local_effective_strategy"], ["builtin", "agent", "local_effective_strategy"],
] as const)("places standard source %s provisioning with %s grants on %s", async (provisionKind, grants, placement) => {
const implicitDefault = placement.startsWith("local_");
const newerDefault = placement === "local_new_default" || placement === "local_new_primary";
@@ -171,16 +173,18 @@ suite("heartbeat physical workspace source trust", () => {
} });
if (newerDefault) await db.insert(projectWorkspaces).values({ id: randomUUID(), companyId, projectId, name: "Older source", sourceType: "local_path", cwd: path.join(root, companyId, "older-source"), isPrimary: placement === "local_new_default", createdAt: new Date(0) });
await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Source", sourceType: "local_path", cwd, repoUrl: pathToFileURL(cwd).href, isPrimary: placement !== "local_new_default" });
await db.insert(agents).values({ id: agentId, companyId, name: "Reader", status: "idle", adapterType: "codex_local", adapterConfig: { fullAuto: grants === "both" }, permissions: {} });
await db.insert(agents).values({ id: agentId, companyId, name: "Reader", status: "idle", adapterType: "codex_local", adapterConfig: { fullAuto: grants === "both",
...(placement === "local_effective_strategy" ? { workspaceStrategy: { type: "project_primary" } } : {}) }, permissions: {} });
await db.insert(companyMemberships).values({ companyId, principalType: "agent", principalId: agentId, status: "active", membershipRole: "member" });
const taskInput = { id: issueId, companyId, projectId: implicitDefault ? projectId : null, title: "Read-only isolated source", status: "todo" as const, assigneeAgentId: agentId,
projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: "git_worktree",
worktreeParentDir: path.join(root, companyId, "worktrees"), ...(provisionKind === "configured" ? { provisionCommand: `printf host-script-ran > '${marker}'` } : {}) } },
workspaceSelection: implicitDefault ? null : { version: 1 as const, source: "explicit" as const, selection: { kind: "configured_source" as const, projectWorkspaceId: sourceId, mode: "managed_isolated" as const } } };
if (newerDefault) {
if (newerDefault || placement === "local_effective_strategy") {
const { projectWorkspaceId: _source, workspaceSelection: _selection, ...createInput } = taskInput;
const created = await issueService(db).create(companyId, createInput);
expect(created.projectWorkspaceId).toBe(sourceId);
expect(created.workspaceSelection).toMatchObject({ source: "project" });
} else await db.insert(issues).values(taskInput);
if (grants !== "none") await db.insert(principalPermissionGrants).values({ companyId, principalType: "agent", principalId: agentId,
permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } });
@@ -189,6 +193,12 @@ suite("heartbeat physical workspace source trust", () => {
const actor = { type: "agent" as const, agentId, companyId, source: "agent_jwt" as const, onBehalfOfUserId: "responsible-user" };
expect(await accessService(db).decide({ actor, action: "project:read", resource: { type: "project", companyId, projectId } })).toMatchObject({ allowed: true });
expect(await accessService(db).decide({ actor, action: "project:write_workspace", resource: { type: "issue", companyId, projectId, issueId, assigneeAgentId: agentId }, scope: { projectId, assigneeAgentId: agentId } })).toMatchObject({ allowed: grants === "both" });
if (placement === "local_effective_strategy") {
const selection = { kind: "configured_source" as const, projectWorkspaceId: sourceId, mode: "managed_isolated" as const };
await expect(executionWorkspaceService(db).validateSelection({ companyId, actor, issueId, selection })).rejects.toThrow("project:write_workspace");
await expect(executionWorkspaceService(db).validateSelection({ companyId, actor, issueId,
selection: { ...selection, mode: "shared" }, preserveImplicitDefaultSettings: true })).rejects.toThrow("Task workspace default changed");
}
const environmentId = randomUUID();
await db.insert(environments).values({ id: environmentId, name: `Standard sandbox ${companyId}`, driver: "sandbox", status: "active", config: { provider: "fake", image: "fake:test" } });
await db.update(agents).set({ defaultEnvironmentId: placement === "sandbox" ? environmentId : null }).where(eq(agents.id, agentId));
@@ -205,6 +215,7 @@ suite("heartbeat physical workspace source trust", () => {
const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, bound.executionWorkspaceId!));
expect(workspace.cwd).not.toBe(cwd);
expect(await readFile(path.join(workspace.cwd!, "source.txt"), "utf8")).toBe("read-only source bytes");
expect(await readFile(path.join(cwd, "source.txt"), "utf8")).toBe("read-only source bytes");
const calls = execute.mock.calls.filter(([input]) => input.runId === run!.id);
expect(calls).toHaveLength(1);
expect(calls[0][0].context.paperclipEnvironment.driver).toBe(placement === "sandbox" ? "sandbox" : "local");
@@ -213,7 +224,6 @@ suite("heartbeat physical workspace source trust", () => {
await rm(marker, { force: true });
if (grants === "none") await rm(workspace.cwd!, { recursive: true, force: true });
await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId));
const { executionWorkspaceService } = await import("../services/execution-workspaces.js");
if (placement === "local" && grants === "both") await db.update(issues).set({ projectId, workspaceSelection: null }).where(eq(issues.id, issueId));
await executionWorkspaceService(db).update(workspace.id, { metadata: { ...workspace.metadata,
_controllerProvisionCopyOnly: false, config: { provisionCommand: `printf host-script-ran > '${marker}'` } } });
@@ -3921,6 +3921,7 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod
const cleanup = await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: "company-1",
id: "execution-workspace-1",
cwd: workspace.cwd,
providerType: "git_worktree",
@@ -3997,6 +3998,7 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod
const cleanup = await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: "company-1",
id: "execution-workspace-reused-cleanup",
cwd: reused.cwd,
providerType: "git_worktree",
@@ -4065,6 +4067,7 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod
const cleanup = await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: "company-1",
id: "execution-workspace-1",
cwd: workspace.cwd,
providerType: "git_worktree",
@@ -4129,6 +4132,7 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod
const cleanup = await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: "company-1",
id: "execution-workspace-1",
cwd: workspace.cwd,
providerType: "git_worktree",
@@ -4205,6 +4209,7 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod
await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: "company-1",
id: "execution-workspace-1",
cwd: workspace.cwd,
providerType: "git_worktree",
@@ -10011,6 +10016,7 @@ describe("realizeExecutionWorkspace with an exact existing branch", () => {
) {
return {
workspace: {
companyId: "company-1",
id: "execution-workspace-1",
cwd: workspace.cwd,
providerType: "git_worktree",
+16 -4
View File
@@ -1,4 +1,5 @@
import { assertTaskWorkspaceAccess, assertTaskWorkspaceSourceProjectAccess } from "./task-workspace-source-access.js";
import { verifiedRecordedIsolatedRemoteSource } from "./isolated-remote-workspace-source.js";
import { parseObject } from "../adapters/utils.js";
import { resolveCoreTrustPreset } from "./trust-preset-resolver.js";
import { CONTROLLER_PROVISION_COPY_ONLY, workspaceRequiresCopyOnlyProvisioning } from "./workspace-host-provision.js";
@@ -1397,7 +1398,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic
return resolveEffectiveWorkspaceStrategyType(mode, config);
}
async function validateSelection(input: { taskCreation?: TaskWorkspaceCreationTrustContext; companyId: string; actor: AuthorizationActor; selection: TaskWorkspaceSelection; issueId?: string | null; parentIssueId?: string | null; assigneeAgentId?: string | null; assigneeUserId?: string | null; assigneeAdapterOverrides?: unknown; projectId?: string | null; executionPolicy?: unknown }, reader: Db | DbTransaction = db) {
async function validateSelection(input: { taskCreation?: TaskWorkspaceCreationTrustContext; companyId: string; actor: AuthorizationActor; selection: TaskWorkspaceSelection; issueId?: string | null; parentIssueId?: string | null; assigneeAgentId?: string | null; assigneeUserId?: string | null; assigneeAdapterOverrides?: unknown; projectId?: string | null; executionPolicy?: unknown; preserveImplicitDefaultSettings?: boolean }, reader: Db | DbTransaction = db) {
const selection = taskWorkspaceSelectionSchema.parse(input.selection);
if (selection.kind === "existing") {
const [workspace] = await reader.select().from(executionWorkspaces).where(and(
@@ -1422,15 +1423,24 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic
));
if (!source) throw notFound("Workspace source is unavailable or inaccessible");
const mode = selection.mode === "shared" ? "shared_workspace" : "isolated_workspace";
let executionWorkspaceSettings: Record<string, unknown> = { mode };
if (input.preserveImplicitDefaultSettings) {
const [task] = input.issueId ? await reader.select({ settings: issues.executionWorkspaceSettings,
intent: issues.workspaceSelection }).from(issues).where(and(eq(issues.id, input.issueId),
eq(issues.companyId, input.companyId), await issueReadSqlCondition(reader, input.actor))) : [];
if (!task || !["project", "task_default"].includes(task.intent?.source ?? "")
|| !isDeepStrictEqual(task.intent?.selection, selection)) throw conflict("Task workspace default changed");
executionWorkspaceSettings = { ...parseObject(task.settings), mode };
}
const strategy = await resolveSourceWorkspaceStrategy({ ...input, typedSelection: true,
executionWorkspaceSettings: { mode }, executionWorkspacePolicy: source.executionWorkspacePolicy }, reader);
executionWorkspaceSettings, executionWorkspacePolicy: source.executionWorkspacePolicy }, reader);
await assertTaskWorkspaceSourceProjectAccess(reader, input.actor, input.companyId, source.projectId, {
write: strategy !== "git_worktree",
issueId: input.issueId, parentIssueId: input.parentIssueId,
assigneeAgentId: input.assigneeAgentId, assigneeUserId: input.assigneeUserId,
});
return { executionWorkspaceId: null, projectWorkspaceId: source.id, executionWorkspacePreference: null,
executionWorkspaceSettings: { mode: selection.mode === "shared" ? "shared_workspace" : "isolated_workspace" } };
executionWorkspaceSettings };
}
return { executionWorkspaceId: null, projectWorkspaceId: null, executionWorkspacePreference: null,
executionWorkspaceSettings: { mode: "shared_workspace" } };
@@ -3280,7 +3290,9 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic
.where(and(eq(projects.companyId, snapshot.companyId), snapshot.projectId ? eq(projects.id, snapshot.projectId) : sql`false`))
.then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy)),
]);
let resolvedBaseCwd = projectWorkspace?.cwd ?? null;
let resolvedBaseCwd: string | null;
try { resolvedBaseCwd = await verifiedRecordedIsolatedRemoteSource(snapshot) ?? projectWorkspace?.cwd ?? null; }
catch { return { ok: false, code: "rebuild_failed", message: "Failed to rebuild the execution workspace" }; }
if (isClosedExecutionWorkspaceStatus(snapshot.status) && resolvedBaseCwd == null && snapshot.strategyType === "git_worktree" && snapshot.projectId) {
try {
const managedWorkspace = await ensureManagedProjectWorkspace({
@@ -414,7 +414,8 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron
if (taskWorkspaceIntent && !boundSourceWorkspace && !nativeRecoveryExecutionWorkspaceId) {
await executionWorkspacesSvc.validateSelection({ companyId: agent.companyId,
actor: { type: "agent", agentId: agent.id, companyId: agent.companyId, runId: run.id, source: "agent_jwt", onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId },
selection: taskWorkspaceIntent.selection, issueId });
selection: taskWorkspaceIntent.selection, issueId,
preserveImplicitDefaultSettings: ["project", "task_default"].includes(taskWorkspaceIntent.source) });
}
const explicitTaskDirectory = taskWorkspaceIntent?.selection.kind === "task_directory";
const useIsolatedTaskDirectory = issueRef !== null && shouldUseIsolatedTaskDirectory({
@@ -1,5 +1,5 @@
import { execFile as execFileCallback } from "node:child_process";
import { mkdtemp, mkdir, readFile, readdir, rm, writeFile, access, rename } from "node:fs/promises";
import { mkdtemp, mkdir, readFile, readdir, rm, writeFile, access, rename, realpath } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
@@ -19,6 +19,10 @@ import { workspaceOperationService } from "../workspace-operations.js";
import { nativeWorkspaceSyncInternals, prepareNativeWorkspaceSync } from "../native-runtime/native-workspace-sync.js";
import { createHeartbeatWorkspaceResolver, prepareProjectRepositoryWorkspaces, resolveExecutionWorkspaceReuseRequestForIssue } from "./workspaces.js";
import { prepareHeartbeatWorkspace, type HeartbeatWorkspacePreparationInput } from "./workspace-preparation.js";
import * as managedRepositoryCheckout from "../managed-repository-checkout.js";
import { isolatedRemoteSourcePath } from "../isolated-remote-workspace-source.js";
import { cleanupExecutionWorkspaceArtifacts } from "../workspace-runtime.js";
import { issueService } from "../issues.js";
const execFile = promisify(execFileCallback);
const support = await getEmbeddedPostgresTestSupport();
@@ -116,6 +120,141 @@ describe.skipIf(!support.supported)("heartbeat workspace preparation boundary",
expect(input.controllerLease.assertOwned).toHaveBeenCalledTimes(2);
});
it.each(["create", "failed-save", "shared-retry-after-failed-save", "literal-branch", "legacy-explicit-source", "inherited-source", "legacy-untyped-source", "changed-source-intent", "context-source"] as const)("preserves restricted source isolation during %s", async scenario => {
const { input, source, issue: originalIssue } = await fixture(true);
await db.update(companies).set({ issueCounter: 1 }).where(eq(companies.id, originalIssue.companyId));
const issue = await issueService(db).create(originalIssue.companyId, {
title: "Prepare the selected source", projectId: originalIssue.projectId,
assigneeAgentId: input.agent.id, executionWorkspaceSettings: { mode: "isolated_workspace" },
...(scenario === "legacy-explicit-source" ? { projectWorkspaceId: originalIssue.projectWorkspaceId } : {}),
...(scenario === "inherited-source" ? { parentId: originalIssue.id } : {}),
workspaceSelectionActor: { type: "agent", source: "agent_jwt", companyId: originalIssue.companyId, agentId: input.agent.id },
});
input.task.issueRef = issue;
input.task.issueId = issue.id;
input.task.context.issueId = issue.id;
input.task.workspaceSelection = issue.workspaceSelection ?? null;
await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, input.run.id));
const [run] = await db.insert(heartbeatRuns).values({ companyId: issue.companyId, agentId: input.agent.id,
issueId: issue.id, scopeKind: "issue", status: "running", runtimeMode: "legacy" }).returning();
input.run = run;
if (scenario === "legacy-untyped-source") {
await db.update(issues).set({ workspaceSelection: null }).where(eq(issues.id, issue.id));
input.task.workspaceSelection = null;
} else if (scenario === "changed-source-intent") {
await db.update(issues).set({ workspaceSelection: { ...issue.workspaceSelection!, source: "explicit" } }).where(eq(issues.id, issue.id));
} else if (scenario === "context-source") {
await db.update(issues).set({ projectWorkspaceId: null, workspaceSelection: null }).where(eq(issues.id, issue.id));
input.task.issueRef = { ...issue, projectWorkspaceId: null };
input.task.workspaceSelection = null;
input.task.context.projectWorkspaceId = issue.projectWorkspaceId;
}
const remoteUrl = "https://example.test/public/repository.git";
await db.update(projectWorkspaces).set({ repoUrl: remoteUrl }).where(eq(projectWorkspaces.id, issue.projectWorkspaceId!));
const publishedSource = join(home, `published-${issue.id}`);
await execFile("git", ["clone", "--no-hardlinks", source, publishedSource]);
const [privateIssue] = await db.insert(issues).values({ companyId: issue.companyId, title: "Private work",
visibility: "private", projectId: issue.projectId }).returning();
const privateCwd = join(source, "private-work");
await execFile("git", ["-C", source, "worktree", "add", "-b", "restricted-task", privateCwd, "main"]);
await writeFile(join(privateCwd, "confidential.txt"), "Private source must not be copied");
await execFile("git", ["-C", privateCwd, "add", "confidential.txt"]);
await execFile("git", ["-C", privateCwd, "commit", "-m", "Restricted task work"]);
const privateCommit = (await execFile("git", ["-C", privateCwd, "rev-parse", "HEAD"])).stdout.trim();
const [privateWorkspace] = await db.insert(executionWorkspaces).values({ companyId: issue.companyId,
name: "Private workspace", projectId: issue.projectId, sourceIssueId: privateIssue.id, cwd: privateCwd, mode: "isolated_workspace",
strategyType: "git_worktree", providerType: "git_worktree", providerRef: privateCwd, branchName: "restricted-task", status: "idle" }).returning();
await db.update(issues).set({ executionWorkspaceId: privateWorkspace.id }).where(eq(issues.id, privateIssue.id));
const clone = vi.spyOn(managedRepositoryCheckout, "materializeManagedProjectWorkspace").mockImplementation(async (cwd, options) => {
expect(options.repoUrl).toBe(remoteUrl);
expect(options.localSource).toBeUndefined();
expect(options.resolveGitAuth).toBeTypeOf("function");
await execFile("git", ["clone", "--no-hardlinks", publishedSource, cwd]);
await execFile("git", ["-C", cwd, "remote", "set-url", "origin", remoteUrl]);
return { cwd, warning: null };
});
const isolatedSource = isolatedRemoteSourcePath(issue.companyId, issue.id);
if (["literal-branch", "legacy-explicit-source", "inherited-source", "legacy-untyped-source", "changed-source-intent", "context-source"].includes(scenario)) {
const selectedBranch = scenario === "literal-branch" ? "feature/literal-pin" : `test-${issue.identifier}`;
await execFile("git", ["-C", source, "checkout", "-b", selectedBranch]);
await writeFile(join(source, "selected-work.txt"), "Selected unpublished branch work\n");
await execFile("git", ["-C", source, "add", "selected-work.txt"]);
await execFile("git", ["-C", source, "commit", "-m", "Selected branch work"]);
const branchTip = (await execFile("git", ["-C", source, "rev-parse", "HEAD"])).stdout.trim();
await execFile("git", ["-C", source, "checkout", "main"]);
if (scenario === "literal-branch") input.config.mergedConfig = { workspaceStrategy: { type: "git_worktree", baseRef: "main", branchTemplate: "feature/literal-pin" } };
const acquire = vi.spyOn(input.services.envOrchestrator, "acquireForRun");
await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow("Task workspace access is no longer available");
expect(clone).not.toHaveBeenCalled();
expect(acquire).not.toHaveBeenCalled();
expect((await readIssue(issue.id)).executionWorkspaceId).toBeNull();
expect((await execFile("git", ["-C", source, "rev-parse", `refs/heads/${selectedBranch}`])).stdout.trim()).toBe(branchTip);
await expect(access(isolatedSource)).rejects.toMatchObject({ code: "ENOENT" });
return;
}
expect(issue.workspaceSelection).toMatchObject({ source: "project", selection: {
kind: "configured_source", projectWorkspaceId: issue.projectWorkspaceId, mode: "managed_isolated",
} });
if (scenario === "failed-save" || scenario === "shared-retry-after-failed-save") {
vi.spyOn(input.services.executionWorkspacesSvc, "create").mockRejectedValueOnce(new Error("Workspace persistence failed"));
await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow("Workspace persistence failed");
const worktrees = (await execFile("git", ["-C", isolatedSource, "worktree", "list", "--porcelain"])).stdout;
expect(worktrees.match(/^worktree /gm)).toHaveLength(1);
await expect(execFile("git", ["-C", isolatedSource, "rev-parse", "--verify", `refs/heads/test-${issue.identifier}`])).rejects.toBeDefined();
expect((await readIssue(issue.id)).executionWorkspaceId).toBeNull();
expect(await readFile(join(privateCwd, "confidential.txt"), "utf8")).toBe("Private source must not be copied");
if (scenario === "shared-retry-after-failed-save") {
await execFile("git", ["-C", source, "remote", "add", "origin", remoteUrl]);
await db.update(issues).set({ visibility: "open" }).where(eq(issues.id, privateIssue.id));
const saved = await prepareHeartbeatWorkspace(db, input);
const workspace = saved.persistedExecutionWorkspace!;
expect(saved.executionWorkspace.baseCwd).toBe(source);
expect(workspace.metadata?.isolatedRemoteSource).toBeUndefined();
await writeFile(join(workspace.cwd!, "unpublished-work.txt"), "Keep the shared-source task commits\n");
await execFile("git", ["-C", workspace.cwd!, "add", "unpublished-work.txt"]);
await execFile("git", ["-C", workspace.cwd!, "commit", "-m", "Unpublished task work"]);
const branchTip = (await execFile("git", ["-C", workspace.cwd!, "rev-parse", "HEAD"])).stdout.trim();
await expect(execFile("git", ["-C", isolatedSource, "cat-file", "-e", branchTip])).rejects.toBeDefined();
await execFile("git", ["-C", source, "worktree", "remove", "--force", workspace.cwd!]);
await reuse(input, workspace);
const rebuilt = await prepareHeartbeatWorkspace(db, input);
expect(rebuilt.executionWorkspace.baseCwd).toBe(await realpath(source));
expect((await execFile("git", ["-C", rebuilt.executionWorkspace.cwd, "rev-parse", "HEAD"])).stdout.trim()).toBe(branchTip);
expect(await readFile(join(rebuilt.executionWorkspace.cwd, "unpublished-work.txt"), "utf8")).toBe("Keep the shared-source task commits\n");
expect(clone).toHaveBeenCalledOnce();
}
return;
}
const result = await prepareHeartbeatWorkspace(db, input);
expect(result.executionWorkspace.baseCwd).toBe(isolatedSource);
expect(await readFile(join(result.executionWorkspace.cwd, "README.md"), "utf8")).toBe("source content\n");
await expect(access(join(result.executionWorkspace.cwd, "private-work"))).rejects.toMatchObject({ code: "ENOENT" });
expect(clone).toHaveBeenCalledOnce();
await expect(execFile("git", ["-C", isolatedSource, "cat-file", "-e", privateCommit])).rejects.toBeDefined();
await expect(execFile("git", ["-C", isolatedSource, "rev-parse", "--verify", "refs/heads/restricted-task"])).rejects.toBeDefined();
expect(await realpath(join(isolatedSource, ".git"))).not.toBe(await realpath(join(source, ".git")));
await reuse(input, result.persistedExecutionWorkspace!);
const resumed = await prepareHeartbeatWorkspace(db, input);
expect(resumed.persistedExecutionWorkspace!.id).toBe(result.persistedExecutionWorkspace!.id);
expect(resumed.executionWorkspace.baseCwd).toBe(await realpath(isolatedSource));
expect(clone).toHaveBeenCalledOnce();
expect((await readIssue(privateIssue.id)).visibility).toBe("private");
const workspace = resumed.persistedExecutionWorkspace!;
const cleanup = await cleanupExecutionWorkspaceArtifacts({ workspace,
projectWorkspace: { cwd: source, cleanupCommand: null }, runCleanupCommands: false });
expect(cleanup.warnings).toEqual([]);
await expect(access(workspace.cwd!)).rejects.toMatchObject({ code: "ENOENT" });
await db.update(executionWorkspaces).set({ status: "archived" }).where(eq(executionWorkspaces.id, workspace.id));
const reopened = await executionWorkspaceService(db).reopenClosedIsolatedExecutionWorkspaceForIssue({ workspaceId: workspace.id,
issue: { id: issue.id, companyId: issue.companyId, projectId: issue.projectId },
actor: { agentId: input.agent.id, actorType: "agent" },
authorizationActor: { type: "agent", source: "agent_jwt", agentId: input.agent.id, companyId: issue.companyId },
});
expect(reopened).toMatchObject({ ok: true });
expect(await readFile(join(workspace.cwd!, "README.md"), "utf8")).toBe("source content\n");
expect(await readFile(join(privateCwd, "confidential.txt"), "utf8")).toBe("Private source must not be copied");
});
it.each(["sandbox", "ssh", "local", null] as const)("uses the selected %s placement for controller provisioning", async driver => {
const { input, source, issue, envOrchestrator } = await fixture(true);
const marker = join(source, "host-provision-ran");
@@ -19,7 +19,7 @@ import path from "node:path";
import { CONTROLLER_PROVISION_COPY_ONLY, workspaceRequiresCopyOnlyProvisioning } from "../workspace-host-provision.js";
import { and, asc, eq } from "drizzle-orm";
import { type ExecutionWorkspace } from "@paperclipai/shared";
import { heartbeatRuns, projectWorkspaces } from "@paperclipai/db";
import { heartbeatRuns, issues, projectWorkspaces } from "@paperclipai/db";
import { getStartupTracer } from "../../instrumentation.js";
import { createHostDuplexObservabilityRecorder } from "../duplex-observability-recorder.js";
import { incrementToolRuntimeMetricCounter } from "../tool-runtime-metrics.js";
@@ -29,6 +29,7 @@ import { readNativeWorkspaceSyncReference, readNativeWorkspaceSyncLocalCwd } fro
import { parseObject } from "../../adapters/utils.js";
import { materializeNativeChatTaskRoot } from "../native-runtime/native-chat-workspace.js";
import { materializeIsolatedTaskDirectory } from "../isolated-task-directory.js";
import { canIsolateRemoteWorkspaceSource, materializeIsolatedRemoteWorkspaceSource } from "../isolated-remote-workspace-source.js";
import {
assertIsolatedWorkspaceSourceReadAccess,
retainIsolatedWorkspaceSourceAccess,
@@ -484,12 +485,38 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac
);
const isolatedWorkspaceSourceActor = { type: "agent" as const, source: "agent_jwt" as const, companyId: agent.companyId,
agentId: agent.id, runId: run.id, onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId };
const isolatedWorkspaceSourceAccess = !isDotRun && issueId && !persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId
let isolatedWorkspaceSourceAccess = null;
let isolatedRemoteSource: Record<string, unknown> | null = null;
if (!isDotRun && issueId && !persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId
&& (latestWorkspaceStrategyType === "git_worktree"
|| (workspaceReuseProvisioningPolicy.shouldRestoreExistingWorkspace && reusableExistingExecutionWorkspace?.strategyType === "git_worktree"))
? await assertIsolatedWorkspaceSourceReadAccess(db, {
|| (workspaceReuseProvisioningPolicy.shouldRestoreExistingWorkspace && reusableExistingExecutionWorkspace?.strategyType === "git_worktree"))) {
try {
isolatedWorkspaceSourceAccess = await assertIsolatedWorkspaceSourceReadAccess(db, {
companyId: agent.companyId, sourceCwd: executionWorkspaceBase.baseCwd, actor: isolatedWorkspaceSourceActor,
}) : null;
});
} catch (error) {
const [sourceChoice] = await db.select({ projectWorkspaceId: issues.projectWorkspaceId,
executionWorkspaceId: issues.executionWorkspaceId,
selection: issues.workspaceSelection }).from(issues)
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)));
if (!sourceChoice || sourceChoice.executionWorkspaceId
|| (sourceChoice.projectWorkspaceId && sourceChoice.projectWorkspaceId !== resolvedWorkspace.workspaceId)) throw error;
if (!canIsolateRemoteWorkspaceSource({ error, repoUrl: executionWorkspaceBase.repoUrl,
reuseExisting: requestedShouldReuseExisting, hasExistingWorkspace: Boolean(existingExecutionWorkspace),
selectedProjectWorkspaceId: sourceChoice.projectWorkspaceId ?? readNonEmptyString(context.projectWorkspaceId),
selection: sourceChoice.selection, strategy: parseObject(hostExecutionWorkspaceConfig.workspaceStrategy),
trustPresetKind: trustPreset.kind })) throw error;
const cwd = await materializeIsolatedRemoteWorkspaceSource({ companyId: agent.companyId,
issueId, repoUrl: executionWorkspaceBase.repoUrl!, resolveGitAuth: workspaceGitAuthProvider });
executionWorkspaceBase.baseCwd = cwd;
resolvedWorkspace.cwd = cwd;
isolatedRemoteSource = { schema: "paperclip.isolated-remote-source.v1", companyId: agent.companyId,
issueId, repoUrl: executionWorkspaceBase.repoUrl };
isolatedWorkspaceSourceAccess = await assertIsolatedWorkspaceSourceReadAccess(db, {
companyId: agent.companyId, sourceCwd: cwd, actor: isolatedWorkspaceSourceActor,
});
}
}
const beforeIsolatedSourceCopy = isolatedWorkspaceSourceAccess
? (sourceCwd: string, targetCwd: string) => retainIsolatedWorkspaceSourceAccess(db, {
companyId: agent.companyId, sourceCwd, targetCwd,
@@ -751,6 +778,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac
}
const nextExecutionWorkspaceMetadata = {
...baseExecutionWorkspaceMetadata,
...(isolatedRemoteSource ? { isolatedRemoteSource } : {}),
...(copyOnlyProvisioning ? { [CONTROLLER_PROVISION_COPY_ONLY]: true } : {}),
...(persistedWorktreeInstanceRoot
? {
@@ -832,6 +860,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac
try {
await cleanupExecutionWorkspaceArtifacts({
workspace: {
companyId: agent.companyId,
id:
reusableExistingExecutionWorkspace?.id ??
workspaceReuseRequest.requestedExecutionWorkspaceId ??
@@ -59,6 +59,7 @@ import {
type RealizedExecutionWorkspace,
} from "../workspace-runtime.js";
import { resolvePersistedGitWorkspaceSource } from "../persisted-workspace-source.js";
import { verifiedRecordedIsolatedRemoteSource } from "../isolated-remote-workspace-source.js";
import { issueService } from "../issues.js";
import { projectService } from "../projects.js";
import {
@@ -3339,6 +3340,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) {
repoName: deriveRepoNameFromRepoUrl(workspace.repoUrl),
}) : null;
const candidateBaseCwds = [
await verifiedRecordedIsolatedRemoteSource(workspace),
managedBase,
managedBase && workspace.repoUrl
? `${managedBase}-${createHash("sha256").update(workspace.repoUrl).digest("hex").slice(0, 12)}` : null,
@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { forbidden } from "../errors.js";
import { canIsolateRemoteWorkspaceSource, recordedIsolatedRemoteSource } from "./isolated-remote-workspace-source.js";
const allowed = {
error: forbidden("Task workspace access is no longer available"),
repoUrl: "https://example.test/company/repository.git",
reuseExisting: false,
hasExistingWorkspace: false,
selectedProjectWorkspaceId: null,
selection: null,
strategy: { type: "git_worktree" },
trustPresetKind: "standard",
};
describe("isolated remote source admission", () => {
it("allows a fresh default remote source after a workspace privacy denial", () => {
expect(canIsolateRemoteWorkspaceSource(allowed)).toBe(true);
});
it.each([
{ reuseExisting: true },
{ hasExistingWorkspace: true },
{ selectedProjectWorkspaceId: "chosen-source" },
{ strategy: { type: "git_worktree", existingBranch: "unpublished-work" } },
{ strategy: { type: "git_worktree", branchTemplate: "feature/literal-pin" } },
{ strategy: { type: "git_worktree", branchTemplate: "feature/{{unknown}}" } },
{ strategy: { type: "git_worktree", branchTemplate: "feature/{{agent.name}}" } },
{ error: forbidden("Project access denied") },
{ error: new Error("Task workspace access is no longer available") },
{ repoUrl: "file:///private/source" },
{ repoUrl: "/private/source" },
{ repoUrl: "https://user:password@example.test/repo" },
{ repoUrl: null },
{ trustPresetKind: "low_trust_review" },
{ trustPresetKind: "denied" },
{ trustPresetKind: "unknown" },
])("preserves the original denial for %j", patch => {
expect(canIsolateRemoteWorkspaceSource({ ...allowed, ...patch })).toBe(false);
});
it.each(["test-{{issue.identifier}}", "feature/{{issue.id}}", "feature/{{slug}}"])("allows a task-derived branch template: %s", branchTemplate => {
expect(canIsolateRemoteWorkspaceSource({ ...allowed, strategy: { type: "git_worktree", branchTemplate } })).toBe(true);
});
it.each(["explicit", "parent", "channel", "operator_cwd", "legacy"] as const)("preserves %s source intent", source => {
expect(canIsolateRemoteWorkspaceSource({ ...allowed, selection: {
version: 1, source, selection: { kind: "configured_source", projectWorkspaceId: "source", mode: "managed_isolated" },
} })).toBe(false);
});
it("requires a recorded default to match the selected source", () => {
const selection = { version: 1 as const, source: "project" as const,
selection: { kind: "configured_source" as const, projectWorkspaceId: "source", mode: "managed_isolated" as const } };
expect(canIsolateRemoteWorkspaceSource({ ...allowed, selectedProjectWorkspaceId: "source", selection })).toBe(true);
expect(canIsolateRemoteWorkspaceSource({ ...allowed, selectedProjectWorkspaceId: "other-source", selection })).toBe(false);
});
});
describe("recorded isolated source identity", () => {
const workspace = { companyId: "company", sourceIssueId: "task", repoUrl: allowed.repoUrl,
metadata: { isolatedRemoteSource: { schema: "paperclip.isolated-remote-source.v1", companyId: "company", issueId: "task", repoUrl: allowed.repoUrl } } };
it("keeps ordinary workspace sources unchanged", () => {
expect(recordedIsolatedRemoteSource({ ...workspace, metadata: null })).toBeNull();
});
it.each([{ companyId: "other-company" }, { sourceIssueId: "other-task" }, { repoUrl: "https://example.test/other" },
{ metadata: { isolatedRemoteSource: { schema: "unknown" } } },
])("rejects a changed source binding: %j", patch => {
expect(() => recordedIsolatedRemoteSource({ ...workspace, ...patch })).toThrow("source identity changed");
});
});
@@ -0,0 +1,120 @@
import path from "node:path";
import fs from "node:fs/promises";
import { execFile as execFileCallback } from "node:child_process";
import { promisify } from "node:util";
import type { TaskWorkspaceIntent } from "@paperclipai/shared";
import type { GitRemoteAuthProvider } from "./git-credentials.js";
import { resolvePaperclipInstanceRoot } from "../home-paths.js";
import { materializeManagedProjectWorkspace } from "./managed-repository-checkout.js";
const execFile = promisify(execFileCallback);
export function isolatedRemoteSourcePath(companyId: string, issueId: string): string {
if ([companyId, issueId].some(id => !/^[a-zA-Z0-9_-]+$/.test(id))) {
throw new Error("Invalid isolated repository source identity");
}
return path.join(resolvePaperclipInstanceRoot(), "isolated-repository-sources", companyId, issueId, "checkout");
}
export function recordedIsolatedRemoteSource(input: {
companyId?: string;
sourceIssueId: string | null;
repoUrl: string | null;
metadata?: Record<string, unknown> | null;
}): string | null {
const value = input.metadata?.isolatedRemoteSource;
if (value === undefined) return null;
const source = value as Record<string, unknown> | null;
if (!source || source.schema !== "paperclip.isolated-remote-source.v1"
|| !input.companyId || !input.sourceIssueId || source.companyId !== input.companyId
|| source.issueId !== input.sourceIssueId || source.repoUrl !== input.repoUrl) {
throw new Error("Recorded isolated repository source identity changed");
}
return isolatedRemoteSourcePath(input.companyId, input.sourceIssueId);
}
async function assertRemoteSourceIdentity(cwd: string, repoUrl: string) {
const root = resolvePaperclipInstanceRoot();
const canonicalCwd = await fs.realpath(cwd);
if (canonicalCwd !== path.join(await fs.realpath(root), path.relative(root, cwd))) {
throw new Error("Isolated repository source path changed");
}
const gitDir = path.join(canonicalCwd, ".git");
const entry = await fs.lstat(gitDir);
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error("Isolated repository source Git storage changed");
const options = { timeout: 10_000, maxBuffer: 16 * 1024 };
const common = await execFile("git", ["-C", canonicalCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"], options);
if (common.stdout.trim() !== gitDir) throw new Error("Isolated repository source Git storage changed");
const origin = await execFile("git", ["-C", canonicalCwd, "config", "--local", "--null", "--get-all", "remote.origin.url"], options);
if (origin.stdout !== `${repoUrl}\0`) throw new Error("Isolated repository source identity changed");
}
export async function verifiedRecordedIsolatedRemoteSource(input: Parameters<typeof recordedIsolatedRemoteSource>[0]): Promise<string | null> {
const cwd = recordedIsolatedRemoteSource(input);
if (cwd) await assertRemoteSourceIdentity(cwd, input.repoUrl!);
return cwd;
}
export function canIsolateRemoteWorkspaceSource(input: {
error: unknown;
repoUrl: string | null;
reuseExisting: boolean;
hasExistingWorkspace: boolean;
selectedProjectWorkspaceId: string | null;
selection: TaskWorkspaceIntent | null | undefined;
strategy: Record<string, unknown>;
trustPresetKind: string;
}): boolean {
const error = input.error as { status?: unknown; message?: unknown } | null;
const branchTemplate = input.strategy.branchTemplate;
const selectsFixedBranch = typeof branchTemplate === "string" && branchTemplate.length > 0
&& !/\{\{\s*(?:issue\.(?:id|identifier|title)|slug)\s*\}\}/.test(branchTemplate);
if (input.trustPresetKind !== "standard" || error?.status !== 403 || error.message !== "Task workspace access is no longer available"
|| input.reuseExisting || input.hasExistingWorkspace || input.strategy.existingBranch || selectsFixedBranch
|| (input.selection && !["project", "task_default"].includes(input.selection.source))
|| (input.selectedProjectWorkspaceId && (input.selection?.selection.kind !== "configured_source"
|| input.selection.selection.projectWorkspaceId !== input.selectedProjectWorkspaceId))) return false;
if (!input.repoUrl) return false;
try {
const url = new URL(input.repoUrl);
return ["https:", "ssh:"].includes(url.protocol) && !!url.hostname && !url.password
&& (url.protocol === "ssh:" || !url.username);
} catch { return false; }
}
export async function materializeIsolatedRemoteWorkspaceSource(input: {
companyId: string;
issueId: string;
repoUrl: string;
resolveGitAuth: GitRemoteAuthProvider;
}): Promise<string> {
const cwd = isolatedRemoteSourcePath(input.companyId, input.issueId);
const root = resolvePaperclipInstanceRoot();
await fs.mkdir(root, { recursive: true });
const canonicalRoot = await fs.realpath(root);
let parent = canonicalRoot;
for (const segment of ["isolated-repository-sources", input.companyId, input.issueId]) {
parent = path.join(parent, segment);
await fs.mkdir(parent, { mode: 0o700 }).catch((error: NodeJS.ErrnoException) => {
if (error.code !== "EEXIST") throw error;
});
const entry = await fs.lstat(parent);
if (!entry.isDirectory() || entry.isSymbolicLink() || await fs.realpath(parent) !== parent) {
throw new Error("Isolated repository source path is not a private directory");
}
}
const canonicalCwd = path.join(parent, "checkout");
const entry = await fs.lstat(canonicalCwd).catch((error: NodeJS.ErrnoException) => {
if (error.code === "ENOENT") return null;
throw error;
});
if (entry && (!entry.isDirectory() || entry.isSymbolicLink())) {
throw new Error("Isolated repository source cannot be a symbolic link");
}
const result = await materializeManagedProjectWorkspace(canonicalCwd, {
repoUrl: input.repoUrl, resolveGitAuth: input.resolveGitAuth,
});
if (result.warning) throw new Error("Isolated repository source is not a Git checkout");
await assertRemoteSourceIdentity(cwd, input.repoUrl);
return cwd;
}
+8
View File
@@ -10188,6 +10188,14 @@ export function issueService(db: Db) {
projectWorkspaceId = await resolveProjectDefaultWorkspaceId(tx, {
companyId, projectId: issueData.projectId, policy: projectPolicy,
});
if (projectWorkspaceId && isolatedWorkspacesEnabled) {
const sourceStrategy = await executionWorkspaceService(db).resolveSourceWorkspaceStrategy({ companyId,
assigneeAgentId: issueData.assigneeAgentId, assigneeAdapterOverrides: issueData.assigneeAdapterOverrides,
executionWorkspaceSettings, executionWorkspacePolicy: projectPolicy,
typedSelection: false, projectId: issueData.projectId, executionPolicy: issueData.executionPolicy }, tx);
if (sourceStrategy === "git_worktree") workspaceIntent = { version: 1, source: "project",
selection: { kind: "configured_source", projectWorkspaceId, mode: "managed_isolated" } };
}
}
if (projectWorkspaceId && workspaceSelectionActor) {
const [readableSource] = await tx.select({ id: projectWorkspaces.id, projectId: projectWorkspaces.projectId, executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projectWorkspaces)
@@ -71,7 +71,7 @@ describe("Codex selected native account verification", () => {
expect(input.environment.HOME).toBeUndefined();
expect(input.runtimeDirectory).not.toContain("host-home");
});
it.each(["same-account", "another-account", "bound-key", "invalid-handoff"] as const)("preserves the authorized unmanaged login refresh before local cleanup (%s)", async mode => {
it.each(["same-account", "another-account", "bound-key", "invalid-handoff"] as const)("preserves the server-selected login refresh before local cleanup (%s)", async mode => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-local-login-")));
const auth = (account: string, marker: string, age: number) => JSON.stringify({ tokens: { account_id: account, id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: new Date(Date.now() - age).toISOString() });
const store = join(home, "account.json");
@@ -88,7 +88,7 @@ describe("Codex selected native account verification", () => {
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: { env: { CODEX_HOME: home, ...(mode === "bound-key" ? { OPENAI_API_KEY: "selected-key" } : {}) } } }, "codex", "gpt-6.1-sol");
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", managedAiCredentialHome: home, config: { env: { CODEX_HOME: home, ...(mode === "bound-key" ? { OPENAI_API_KEY: "selected-key" } : {}) } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe(mode === "invalid-handoff" ? "fail" : "pass");
expect(JSON.parse(await readFile(store, "utf8")).tokens.access_token).toBe(mode === "same-account" ? "access-new" : "access-old");
expect((await stat(store)).mode & 0o777).toBe(0o600);
@@ -100,6 +100,66 @@ describe("Codex selected native account verification", () => {
const sshContext = { companyId: "company", adapterType: "paperclip_runner", config: {}, executionTarget: {
kind: "remote" as const, transport: "ssh" as const, remoteCwd: "/qa-workspace", spec: { host: "qa-host", username: "qa-user", port: 22, remoteCwd: "/qa-workspace", remoteWorkspacePath: "/qa-workspace", privateKey: null, knownHosts: null, strictHostKeyChecking: true },
} };
it.each(["local", "remote"].flatMap(transport => ["configured", "operator"].flatMap(source => [false, true].map(linked => ({ transport, source, linked })))))
("matches actual task auth seeding before $transport setup (source=$source, linked=$linked)", async ({ transport, source, linked }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-unbound-auth-")));
const auth = '{"tokens":{"account_id":"fixture-unbound-account","access_token":"fixture-unbound-access","refresh_token":"fixture-unbound-refresh","id_token":"fixture-unbound-id"}}';
try {
const stored = join(home, linked ? "account-store.json" : "auth.json");
await writeFile(stored, auth, { mode: 0o600 });
if (linked) await symlink(stored, join(home, "auth.json"));
const taskHome = join(home, "actual-task-home");
await prepareIsolatedCodexHome({ context: null, codexHome: taskHome, sourceCodexHome: home });
const taskAuth = await readFile(join(taskHome, "auth.json"), "utf8").catch(() => null);
expect(taskAuth).toBe(linked ? null : auth);
vi.stubEnv("CODEX_HOME", source === "operator" ? home : "");
nativeProbe.mockImplementation(async input => {
const probeHome = join(input.runtimeDirectory, "codex-home");
await prepareIsolatedCodexHome({ context: null, codexHome: probeHome, sourceCodexHome: input.transportOptions.sourceCodexHome });
expect(await readFile(join(probeHome, "auth.json"), "utf8")).toBe(auth);
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, config: {
managedAiCredentialHome: home,
env: source === "configured" ? { CODEX_HOME: home } : {},
} }, "codex", "gpt-6.1-sol");
expect(result.status).toBe(linked ? "fail" : "pass");
if (linked) {
expect(nativeProbe).not.toHaveBeenCalled();
expect(sshExecute).not.toHaveBeenCalled();
expect(result.checks[0].message).toContain(source === "configured" ? "env.CODEX_HOME" : "operator CODEX_HOME");
expect(result.checks[0].message).toContain("Legacy runner");
expect(result.checks[0].message).toContain("managed Codex Connection");
} else expect(nativeProbe).toHaveBeenCalledOnce();
expect(await readFile(stored, "utf8")).toBe(auth);
expect(JSON.stringify(result)).not.toContain("fixture-unbound-access");
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => ["OPENAI_API_KEY", "CODEX_API_KEY"].map(key => ({ transport, key }))))
("keeps the configured $key task credential authoritative over linked auth during $transport setup", async ({ transport, key }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-key-linked-auth-")));
const auth = '{"tokens":{"account_id":"unused-subscription","access_token":"unused-subscription-access"}}';
try {
await writeFile(join(home, "account-store.json"), auth, { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
nativeProbe.mockImplementation(async input => {
const probeHome = join(input.runtimeDirectory, "codex-home");
await prepareIsolatedCodexHome({ context: null, codexHome: probeHome, sourceCodexHome: input.transportOptions.sourceCodexHome,
apiKey: input.environment.CODEX_API_KEY ?? input.environment.OPENAI_API_KEY });
expect(JSON.parse(await readFile(join(probeHome, "auth.json"), "utf8"))).toEqual({ OPENAI_API_KEY: "configured-fixture-key" });
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, config: { env: { CODEX_HOME: home, [key]: "configured-fixture-key" } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("pass");
expect(nativeProbe).toHaveBeenCalledOnce();
expect(await readFile(join(home, "account-store.json"), "utf8")).toBe(auth);
expect(JSON.stringify(result)).not.toContain("configured-fixture-key");
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => [false, true].map(linked => ({ transport, linked }))))
("seeds the selected subscription before isolated $transport setup (linked=$linked)", async ({ transport, linked }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-selected-auth-")));
@@ -205,6 +205,10 @@ export async function testNativeRunnerAuthentication(context: AdapterEnvironment
try {
const authPath = join(sourceCodexHome, "auth.json");
linked = (await lstat(authPath)).isSymbolicLink();
if (linked && !context.managedAiCredentialHome?.trim() && !environment.CODEX_API_KEY?.trim()) {
const field = environment.CODEX_HOME?.trim() ? "env.CODEX_HOME" : "operator CODEX_HOME";
throw new Error(`${field} uses a linked auth.json that Paperclip Runner tasks cannot read. Choose Legacy runner in Advanced or use a managed Codex Connection.`);
}
const resolvedAuthPath = await realpath(authPath);
const source = await stat(resolvedAuthPath);
if (!source.isFile() || source.uid !== process.getuid?.()) throw new Error("Invalid Codex source credential owner.");
+5 -2
View File
@@ -1,4 +1,5 @@
import { sanitizeRuntimeServiceBaseEnv } from "./runtime-service-env.js";
import { verifiedRecordedIsolatedRemoteSource } from "./isolated-remote-workspace-source.js";
export { sanitizeRuntimeServiceBaseEnv } from "./runtime-service-env.js";
import { execFile, spawn, type ChildProcess } from "node:child_process";
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
@@ -4155,6 +4156,7 @@ async function deleteGitBranchAtVerifiedTip(input: {
export async function cleanupExecutionWorkspaceArtifacts(input: {
workspace: {
id: string;
companyId: string;
cwd: string | null;
providerType: string;
providerRef: string | null;
@@ -4181,15 +4183,16 @@ export async function cleanupExecutionWorkspaceArtifacts(input: {
}) {
const warnings: string[] = [];
const workspacePath = input.workspace.providerRef ?? input.workspace.cwd;
const isolatedSource = await verifiedRecordedIsolatedRemoteSource(input.workspace);
const repoRoot = input.workspace.providerType === "git_worktree" && workspacePath
? await resolveGitRepoRootForWorkspaceCleanup(
workspacePath,
input.projectWorkspace?.cwd ?? null,
isolatedSource ?? input.projectWorkspace?.cwd ?? null,
)
: null;
const cleanupEnv = buildExecutionWorkspaceCleanupEnv({
workspace: input.workspace,
projectWorkspaceCwd: input.projectWorkspace?.cwd ?? null,
projectWorkspaceCwd: isolatedSource ?? input.projectWorkspace?.cwd ?? null,
});
// Callers can require the workspace to match an assessed snapshot before
// cleanup begins. Destructive paths recheck immediately before removal.