17. Remove just promote; the sandbox keeps one auditable home#

Date: 2026-07-24

Status#

Accepted

Supersedes 10. just promote copies by value and never edits devcontainer.json (ADR 10).

Context#

just promote (10. just promote copies by value and never edits devcontainer.json, ADR 10) copied the security-critical machinery — install.sh, claude-shadow, the guard scripts, the verify battery — by value into target workspaces, so a teammate cloning the target needed nothing but the devcontainer. ADR 10 valued two properties: self-sufficiency and a frozen audit surface (“what ran is what’s at this SHA”).

Living with it inverted that judgment:

  • Frozen copies are frozen vulnerabilities. A security fix landed in this repo never reaches a promoted target unless someone remembers to re-promote, and nothing signals staleness — unlike the container launcher’s notify-only version check. Drift is invisible exactly where it matters most.

  • It recreates the copy-per-project shape 3. Live in a standalone repo, extracted from python-copier-template (ADR 3) walked away from. The repo exists because a security tool needs one canonical, audit-friendly home; promote re-proliferated it, push-driven instead of template-driven.

  • It widens the attack surface 16. The integrity guard’s re-stamp trust anchor must live outside the workspace (ADR 16) identified. Promote shipped the trust-anchor guard scripts into every promoted target’s writable workspace, multiplying the places where planted tamper could wait for a rebuild to bless it.

  • The other consumers cover the real use cases. A project devcontainer gets the same result from one postCreate line that clones this repo at a pinned tag and runs ./install — the target carries a reference, not ~1,500 lines of machinery. Everyone else has the published container image.

Decision#

Remove promote entirely: promote.sh, tests/promote.sh, the CI step, the justfile recipe, and the how-to. Projects that want the sandbox reference this repo instead of embedding it — a postCreate clone at a pinned tag (Sandbox a team devcontainer), or the container image. Pinning the tag keeps ADR 10’s “what ran is what’s at this SHA” property; upgrading becomes a deliberate, reviewable pin bump instead of a forgotten re-promote.

Consequences#

  • justfile recipes are no longer constrained to be promote-target-safe; the file is dev-tooling for this repo only.

  • install.sh keeps its source guard ([ "${BASH_SOURCE[0]}" = "$0" ] && main "$@") — the container image build sources it through the same seam.

  • ADR 10’s companion principle — keep dogfood ≈ guest, prefer install.sh over devcontainer wiring so a clone+install inside any devcontainer gets fixes for free — survives unchanged; it is about the installer, not promote, and is now load-bearing for the pinned-tag path too.

  • ADR 10’s other discipline — never auto-edit devcontainer.json (JSONC in the wild; print a snippet, trust the user) — also outlives the mechanism and applies to the pinned-tag how-to.

  • The one property lost is offline self-sufficiency: a promoted target worked from git clone <target> alone. The pinned-tag path needs GitHub reachable at container-create time, which devcontainer creation effectively requires anyway. An air-gapped consumer would have to vendor the repo deliberately — accepted; no such consumer exists.