Add per-attempt workspace isolation (Epic 3) - #14
Merged
Conversation
Make execution attempts first-class and server-owned. Each
POST /runs/{id}/chain/execute now allocates an attempt with a
server-minted attempt_id, captures the base_commit (git rev-parse HEAD)
and workspace_id it ran against, scopes that attempt's evidence to
artifacts/{run_id}/{attempt_id}/, and persists the attempt into the
durable run_attempts table. A retry mints the next attempt.
Adds a production_mode runtime setting: when on, a caller-supplied
execution_path is refused 422 and the server allocates the workspace
from its own workspace_root; off (default) keeps dev/test behavior,
accepting execution_path only after WorkspacePolicy validation.
Authority is unchanged: allocation is audit metadata only — it decides
no PASS/FAIL/BLOCKED and relaxes no gate. No merge/deploy/bypass surface
is added; GET /runs/{id}/attempts is a read-only audit view.
- RunAttempt embedded in RunRecord.attempts (round-trips via snapshot;
no new RunRepository port method)
- WorkspaceAllocator reuses WorkspacePolicy + GitRunner.head_commit
- ArtifactStore gains optional attempt_id scoping (backward compatible)
- Postgres _project upserts run_attempts (FK never nulled)
- Docs: docs/workspace_isolation.md; README + persistence.md updated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQW4FXw46KBvMCVc2EduXT
RunAttempt.workspace_id was set to str(resolved) — the absolute host
filesystem path — and that same value was reused as the execution path.
Since GET /runs/{id}/attempts returns asdict(attempt) and Postgres
persists workspace_id, the audit surface exposed server filesystem layout.
Separate the opaque public workspace identity from the internal execution
path. WorkspaceAllocator.allocate now returns a WorkspaceAllocation
carrying the attempt (with an opaque workspace_id of the form
workspace-{run_id}-a{n}) and the validated execution_path. The endpoint
runs the chain against allocation.execution_path; only the opaque id is
appended to the record, persisted, and returned by the API.
Storage, serialization, and the Postgres projection are unchanged — they
faithfully carry whatever workspace_id holds, so the snapshot, the
/attempts response, and run_attempts.workspace_id all become opaque.
Tests: workspace_id is not an absolute path; GET /attempts response does
not contain workspace_root; execution still uses the validated resolved
path; Postgres-gated assertion that the stored workspace_id is opaque.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015R7wjqoCZDF1pqNp8AiUJ6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Make execution attempts first-class and server-owned. Each
POST /runs/{id}/chain/execute now allocates an attempt with a
server-minted attempt_id, captures the base_commit (git rev-parse HEAD)
and workspace_id it ran against, scopes that attempt's evidence to
artifacts/{run_id}/{attempt_id}/, and persists the attempt into the
durable run_attempts table. A retry mints the next attempt.
Adds a production_mode runtime setting: when on, a caller-supplied
execution_path is refused 422 and the server allocates the workspace
from its own workspace_root; off (default) keeps dev/test behavior,
accepting execution_path only after WorkspacePolicy validation.
Authority is unchanged: allocation is audit metadata only — it decides
no PASS/FAIL/BLOCKED and relaxes no gate. No merge/deploy/bypass surface
is added; GET /runs/{id}/attempts is a read-only audit view.
no new RunRepository port method)
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01JQW4FXw46KBvMCVc2EduXT