[ENV-A5] Host-side exact-SHA source snapshot and private-workspace seed staging (backend-neutral; Docker mount enforcement in #271)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 45/100
Research direction
Start with src/autoforge/materialize.py and its materialize() implementation and proof, then read src/autoforge/safefs.py and the existing sandbox tests. Trace how the exact base SHA is staged and how #269’s minimal Git input seeds a private workspace; the fake staging backend tests should show the interface and failure cases. Done means staging is re-proved at hand-off, paths and snapshot contents are validated, reuse and cleanup are run-scoped, and the documented invariants are covered.
Written by the indexing model from the issue text.
Description
Artifact: implementation-ready child of EPIC #266. Wave A (workspace isolation and staging). Size: M (reduced from L on 2026-10-23; see the scope note). Target is REMOTE CLI agents: Claude Code, OpenCode 2.x, Pi; Codex and SDK rollout are not part of this gate.
Scope after the 2026-10-23 planning reconciliation (review: #266 comment). The architecture below is still the Docker MVP's target. This issue now owns only its host-side, backend-neutral part: building the exact-SHA source snapshot by reusing
materialize(), keeping the proven tree staged for the launch, validating paths, and preparing the private-workspace seed. It is proven with fakes. Enforcing the mounts in Docker, launching anything, and every real-Docker proof moved to #271, #272 and #274 (see "Acceptance criteria moved" below). This removes the cycle in which #292's acceptance needed #271's Docker backend while #271 was blocked by #292. Size reduced from L to M.
Objective
Implement the host source → container-private writable workspace boundary so the sandbox can read a host-owned, exact-SHA code snapshot via read-only bind mount but all AI-agent code changes, dependency installation, compilation, validation and local Git commits happen inside a private sandbox workspace. The operator's checkout, HOME, controller state, Git common-dir and GitHub credentials are never writable/exposed inside the agent container. The container is disposable; an exact verified Git candidate returns through #269, not through a write to the host checkout.
Required architecture
- Controller (host): choose expected repository + issue/run + base commit SHA, create an immutable per-run source snapshot from that exact tree, check staging integrity, manage source/root identities and bind rules; the read-only source may be a host-owned staging directory under AutoForge runtime (prefer this to the user's dirty main checkout). A user-provided checkout path is not automatically trusted: never bind arbitrary host paths based on repository YAML.
- Source bind: only approved source snapshot, mapped read-only to
/source. No bind of host$HOME,.gitfile, linked-worktree admin, common-dir, Docker socket, gh/SSH/keychain sockets, agent authentication store or Controller config. No unrelated untracked/ignored/secret files; snapshot content must match the expected Git tree (allow explicit audited non-Git inputs in a future separate design, not MVP). - Workspace: per issue/run writable Docker volume or other sandbox-private storage at
/workspace, with a private self-contained Git repository initialized/seeded using #269's minimal object transport; not a writable bind of the host checkout nor the controller-owned linked worktree. Make snapshot-to-workspace copy/materialization reproducible, ownership-safe and idempotent after lifecycle interruption; preserve tracked executable bits/symlinks, submodules and LFS with explicit supported/unsupported diagnostics. - Runtime: launch all existing supported CLI providers and commands from
/workspace;HOMEis a fresh restricted container home distinct from/sourceand/workspace. Build tools/compilers/test runners execute only inside the container, using private temp/cache/build artifact paths. Image must carry the required toolchains; do not run setup scripts on the host as a fallback. - Output: export only a bounded, complete, verifiable candidate commit/pack+manifest via #269. The Host Controller strictly imports and validates before any host
git push,ghoperation or candidate publication. Build output/logs have explicit artifact budget and confidentiality rules. - Permissions: no Docker socket, privileged mode, arbitrary mount flags, host PID/namespace,
--network hostor writable repository/Controller paths. Use #270's security/capability policy and #290's separate model credential projection; no LLM credential is bundled in source snapshot. - Compatibility: preserve existing bwrap and LOCAL behavior. Any different optional writable bind of a dedicated issue staging directory must be explicitly designed and independently security-reviewed later; not the Docker MVP default.
Hard dependencies
- #267 — accepted backend/workspace ADR (host/container ownership)
- #269 — minimal Git source/candidate artifact protocol
- #270 — container mount policy and enforceable capability model
- Consumed by: #271 Docker backend and #274 Docker E2E; #273 config/doctor and #272 recovery must reflect workspace semantics.
Acceptance criteria
Rewritten 2026-10-23 (planning reconciliation). The original criteria are quoted verbatim, with their new owners, under "Acceptance criteria moved (ownership transfer)" below. None of them was dropped.
- The source snapshot is produced by reusing
materialize()(src/autoforge/materialize.py:690) and its proof (ADR 0006 D6.3) for the exact base SHA, with no second tree-export path. The candidate-validation path (ADR 0006 D6.4) is unchanged. -
materialize()removes its temporary tree on exit, so staging either keeps the proven tree under a controller-owned, run-scoped staging directory for the launch's lifetime or holds the context open across it. Either way the tree is re-proved against the expected tree immediately before it is handed to a backend, the trust boundary. A tree changed between proof and hand-off is refused. A read-only bind alone is not treated as protection against host-side mutation. - Every staging and seed path is validated with the existing
safefsprimitives before use: no symlink or path traversal, no mount nesting, no source-path substitution, and no path outside the controller-owned staging root. The snapshot contains no host.gitfile or admin dir, no common dir, noHOMEcontent, and no untracked or ignored file. - Two stagings from the same base SHA give byte-identical tracked trees (modes and symlinks included), and the operator's checkout is not changed by staging.
- The private-workspace seed is prepared from #269's minimal Git input for the exact base SHA. It has no alternates to the host store, no host refs, no remote that carries a credential, and no enabled hooks. Its history depth follows #267's decision 11. Submodules, LFS, Git filters and sparse or partial clones each get an explicit supported or unsupported diagnostic, and no repository hook or filter runs on the host.
- The file modes and the UID/GID ownership the container needs are recorded as data and handed to #271. This issue does not change ownership for any backend.
- A backend-neutral staging and volume interface has a fake implementation. Fake-backend tests prove that
/sourceis presented read-only and/workspacewritable at the interface level. Real Docker volumes and mounts are #271's. - Cleanup is idempotent and scoped by run identity. A crash before or after snapshot staging or seed preparation leaves at most a run-scoped directory, which a later step re-proves before reuse or removes. Staging never mixes issues or runs. Container, volume and secret-projection cleanup belongs to #272.
- Docs explain source snapshot vs live host bind: host checkout edits do not automatically become container input unless the controller explicitly snapshots an approved commit; no direct main-checkout writable mount in MVP. (Original criterion 10, kept here.)
- Existing tests pass, and each invariant of ADRs 0002/0004/0005/0006/0008 is preserved or amended per #267's invariant map. (Original criterion 11, revised: the Docker backend amends some ADR 0005 decisions explicitly, see #267.)
Acceptance criteria moved (ownership transfer, 2026-10-23)
These are the original criteria, quoted verbatim, with where each one now lives. Their substance is unchanged. Those that need a real Docker backend or a launch moved, because this issue no longer has one. Each destination issue lists the criteria it received.
-
/sourceis mounted read-only from the controller-prepared host snapshot;/workspaceis private and writable; a deliberate write to/sourcefails and does not mutate host bytes.→ #271 (mount enforcement), proven on real Docker in #274
-
Preparing two independent sandboxes from the same base SHA yields byte-identical tracked file trees, independent writable workspaces, and no host checkout drift.
→ first half (byte-identical tracked trees, no host checkout drift) stays here; second half (independent writable workspaces) → #271, proven in #274
-
Agent changes,
git add/commit, compiler writes and test-generated artifacts are confined to private/workspace, temp or explicitly approved private volumes. No build command from a repository can execute on host as a fallback.→ #274 (proof); the no-host-fallback rule for repository commands is enforced by #271's
EXPORT_COMMANDownership -
Snapshot contains no host
.gitadmin/common-dir, HOME or nontracked/ignored credential files; reject symlink/path traversal, mount nesting and source path substitution attacks before launch.→ stays here (rewritten above as the snapshot-content and
safefscriteria) -
Rootless/userns and non-root launch permissions checked; no raw user-provided host mount paths or repo-controlled Docker run flags accepted. Unsupported Docker features fail closed in
sandbox.mode: required.→ #271 (launch permissions, no raw mounts or flags) and #273 (fail-closed selection and diagnostics)
-
Git candidate produced from private workspace preserves exact base/candidate SHA relationship, imports only through #269's controller-side verified path, and never changes host shared Git refs without controller authorization.
→ split: the exact-base seed stays here; candidate import semantics → #269; real-workspace proof → #274
-
Host crash before/after source staging, workspace seeding, snapshot copy, export and container cleanup reconciles safely without mixing issues; abandoned volumes/snapshots are scoped by run identity.
→ split: snapshot staging and seed crash recovery stay here; export and container cleanup → #272
-
Two concurrent worker containers cannot access each other's code, volumes, build caches, secret leases or artifacts; tests cover host HOME/GitHub token absolute paths and Docker socket denial.
→ #274
-
Fake and real-Docker tests verify read-only source and writable container workspace; record skips as untested, never as success.
→ split: fake tests stay here; real-Docker tests → #271 (backend tests) and #274 (E2E)
-
Docs explain source snapshot vs live host bind: host checkout edits do not automatically become container input unless the controller explicitly snapshots an approved commit; no direct main-checkout writable mount in MVP.
→ stays here, unchanged
-
Existing tests pass and ADRs 0002/0004/0005/0006/0008 remain intact.
→ stays here, revised as above
Implementation surface
src/autoforge/agent_worktree.py, git_transport.py, materialize.py, sandbox_policy.py, sandbox.py, safefs.py, sandbox tests with a fake staging backend (no Docker module: that is #271's), docs/configuration.md and autoforge.example.yaml.
Out of scope
Do not create GitHub PRs, run gh, grant GitHub credentials to the agent, ship user-selectable unrestricted bind mounts, implement model auth (owned by #290), rewrite controller Git transport, or gate Public Alpha #255.
Source delivery per runtime (owner decision, 2026-10-23)
#267 decision 12 makes the choice between a /source bind and copy-in per runtime, decided by proven capability.
- A runtime proven to share the controller's filesystem (local rootless Docker or Podman): the staging keeps the re-proved snapshot for a read-only bind, as above.
- Any other runtime (a remote sandbox, a VM-hosted daemon, a
DOCKER_HOSTordocker contextthat points elsewhere): only the workspace seed is prepared, and nothing is kept for/source, because the seed pack already carries the exact tree.
The backend-neutral staging interface and its fake cover both cases.
- The staging interface supports both delivery modes; fake tests show that a copy-in runtime gets the seed only and no retained snapshot, and that a bind runtime gets the re-proved snapshot
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 5h 57m
- Merged PRs (30d)
- 71
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from iceboundrock/AutoForge
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
iceboundrock/AutoForge#304 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
iceboundrock/AutoForge#333 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
iceboundrock/AutoForge#332 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 1/100
iceboundrock/AutoForge#331 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 45/100
iceboundrock/AutoForge#329 · 1 comment ·
Maintainers usually reply within 1 day
All issues in iceboundrock/AutoForge
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
nansencenter/geodataset#39 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
awslabs/visual-asset-management-system#804 ·
Maintainers usually reply within 1 day
-
follow-up
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
odoo/odoo#297927 · 1 comment ·
Maintainers usually reply within 1 day
-
link-rot
Difficulty 1/5 Under an hour Newbie friendliness 67/100