From 75558ba581ffac98884e46db78449c1a465efede Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:15:59 +1000 Subject: [PATCH 1/9] docs: define host-native direct coding architecture --- ...-31-remove-direct-coding-sandbox-design.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-31-remove-direct-coding-sandbox-design.md diff --git a/docs/superpowers/specs/2026-08-31-remove-direct-coding-sandbox-design.md b/docs/superpowers/specs/2026-08-31-remove-direct-coding-sandbox-design.md new file mode 100644 index 00000000..0e0809ea --- /dev/null +++ b/docs/superpowers/specs/2026-08-31-remove-direct-coding-sandbox-design.md @@ -0,0 +1,55 @@ +# Host-Native Direct Coding Design + +## Goal + +Heidi/CPTR gives ChatGPT an authenticated coding workspace on the user's local computer. Direct Coding is host-native execution, not a sandbox product. + +## Execution model + +`ChatGPT -> Heidi MCP -> CPTR owner-full -> selected CPTR workspace -> native host subprocess/PTY` + +`owner-full` remains the default control profile. Commands use the existing CPTR identity, working-directory, session, logging, cancellation, and process-group machinery directly. + +## Required removals + +Remove the entire Direct Coding sandbox abstraction, including Bubblewrap, systemd sandboxing, container/VM sandbox profiles, automatic sandbox selection, installer/package dependencies used only for sandboxing, sandbox environment variables, compatibility metadata, verification logic, tests, and documentation. + +Delete `apps/cptr/cptr/services/command_sandbox.py`. Direct Coding must not import or call it. + +Remove these configuration surfaces where they exist: + +- `CPTR_DIRECT_CODING_SANDBOX` +- `HEIDI_SANDBOX_PROFILE` +- `CPTR_DIRECT_CODING_CONTAINER_IMAGE` +- `CPTR_DIRECT_CODING_VM_RUNNER` +- Bubblewrap / `bwrap` dependency checks and install steps + +## Preserved behavior + +The change must preserve: + +- default `owner-full` deployment policy; +- selected-workspace cwd semantics; +- native host toolchain and environment access; +- PAM/non-PAM identity handling; +- PTY and non-PTY native execution; +- command sessions, streaming/logging, cancellation, and process-group isolation; +- workspace registration/path semantics; +- MCP tool inventory and the tool-only invariant (no MCP resources capability and no `ui.resourceUri`); +- FDX, Git, OAuth, deployment, and workspace lifecycle behavior unrelated to sandboxing. + +## Compatibility + +Legacy sandbox environment variables are intentionally removed rather than retained as no-op compatibility flags. Existing deployments should stop persisting them on the next managed install/update. + +`release/compatibility.json` must describe Direct Coding as host-native and must not advertise selectable sandbox profiles. + +## Acceptance criteria + +1. Repository production/runtime/config/docs contain no active Direct Coding sandbox implementation or selectable sandbox profile. +2. `command_sandbox.py` and its sandbox-specific tests are deleted. +3. Direct Coding `run_command` reaches the existing native host subprocess/PTY path directly. +4. Managed installer no longer installs Bubblewrap or writes sandbox configuration. +5. `owner-full` remains the default profile. +6. A regression test rejects reintroduction of the removed sandbox surface. +7. CPTR, installer/compatibility, MCP, and FDX CI remain green. From 2afc4d3cebcddd241db4dca0e54d1b2ffb1d89ef Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:16:22 +1000 Subject: [PATCH 2/9] docs: plan direct coding sandbox removal --- ...2026-08-31-remove-direct-coding-sandbox.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-31-remove-direct-coding-sandbox.md diff --git a/docs/superpowers/plans/2026-08-31-remove-direct-coding-sandbox.md b/docs/superpowers/plans/2026-08-31-remove-direct-coding-sandbox.md new file mode 100644 index 00000000..da6a55e9 --- /dev/null +++ b/docs/superpowers/plans/2026-08-31-remove-direct-coding-sandbox.md @@ -0,0 +1,96 @@ +# Direct Coding Sandbox Removal Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove the entire CPTR Direct Coding sandbox abstraction and make Direct Coding always use native host execution under the existing owner-full authority model. + +**Architecture:** `run_command` keeps the existing identity, cwd, PTY/subprocess, session, logging, and cancellation machinery and no longer wraps Direct Coding commands in an isolation profile. Installer, compatibility, verification, tests, and docs lose the sandbox configuration surface entirely. + +**Tech Stack:** Python/FastAPI CPTR, Bash installer/verification, JSON compatibility contract, pytest, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-08-31-remove-direct-coding-sandbox-design.md` + +## Global Constraints + +- Keep `owner-full` as the default control profile. +- Do not add MCP resources capability, `ui.resourceUri`, or any Apps UI entrypoint. +- Preserve command sessions, cwd semantics, identity handling, PTY/subprocess execution, logging, cancellation, Git/FDX/OAuth behavior. +- Remove rather than deprecate/no-op the Direct Coding sandbox configuration surface. + +--- + +### Task 1: Add removal regression contract + +**Files:** +- Create: `tests/test_direct_coding_host_contract.py` + +**Interfaces:** +- Consumes: repository source tree. +- Produces: a CI contract that fails while sandbox implementation/configuration remains. + +- [ ] Add assertions that runtime/install/compatibility sources do not contain `command_sandbox`, `CPTR_DIRECT_CODING_SANDBOX`, `HEIDI_SANDBOX_PROFILE`, `CPTR_DIRECT_CODING_CONTAINER_IMAGE`, `CPTR_DIRECT_CODING_VM_RUNNER`, or Bubblewrap/`bwrap` dependency wiring. +- [ ] Run root pytest through PR CI and verify RED against current code. +- [ ] Commit the failing contract. + +### Task 2: Switch Direct Coding to native host execution + +**Files:** +- Modify: `apps/cptr/cptr/utils/tools.py` +- Delete: `apps/cptr/cptr/services/command_sandbox.py` +- Delete: `apps/cptr/tests/test_command_sandbox.py` + +**Interfaces:** +- Consumes: existing `run_command` native PTY/subprocess path. +- Produces: Direct Coding commands that execute directly using that path. + +- [ ] Remove sandbox imports from `tools.py`. +- [ ] Remove the `direct_coding` sandbox wrapping block without changing identity/cwd/session behavior. +- [ ] Delete sandbox implementation and sandbox-specific tests. +- [ ] Verify CPTR tests in PR CI. + +### Task 3: Remove installer/config/verification sandbox surface + +**Files:** +- Modify: `scripts/install-core.sh` +- Modify: `scripts/install-lib.sh` +- Modify: `scripts/verify-stack.sh` +- Modify related installer contract tests if they encode sandbox behavior. + +**Interfaces:** +- Consumes: existing managed installation flow. +- Produces: owner-full host-native CPTR configuration with no sandbox variables/dependency installation. + +- [ ] Remove `SANDBOX_PROFILE` and all sandbox env persistence. +- [ ] Replace sandbox-specific dependency installation with only still-required security dependencies (`age`, `age-keygen`, `setcap`/`libcap2-bin`). +- [ ] Remove verification checks for sandbox profile/Bubblewrap. +- [ ] Keep owner-full default behavior unchanged. +- [ ] Verify installer/root tests and shell syntax in PR CI. + +### Task 4: Update compatibility/docs contracts + +**Files:** +- Modify: `release/compatibility.json` +- Modify docs/readmes returned by repository-wide sandbox searches. + +**Interfaces:** +- Consumes: current compatibility schema. +- Produces: host-native Direct Coding documentation with no selectable sandbox profiles. + +- [ ] Remove sandbox profile/default fields from compatibility metadata. +- [ ] Describe Direct Coding as host-native owner-authorized execution. +- [ ] Remove Bubblewrap/systemd/container/VM sandbox documentation. +- [ ] Verify `scripts/verify-compatibility.py` and root tests in PR CI. + +### Task 5: Repository-wide closure verification + +**Files:** +- Modify: `tests/test_direct_coding_host_contract.py` only if a false-positive exemption is required for historical design/plan documentation. + +**Interfaces:** +- Consumes: complete branch. +- Produces: evidence that active sandbox code/configuration is gone. + +- [ ] Search active source/config/docs for removed identifiers and confirm no applicable references remain. +- [ ] Run complete PR CI: installer, MCP, CPTR, FDX. +- [ ] Review PR diff for unrelated changes and MCP UI regressions. +- [ ] Only after green CI, mark PR ready and merge. From 5edc4cbc4cdc51666970202874b13d3162aaa30a Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:16:36 +1000 Subject: [PATCH 3/9] test: require host-native direct coding contract --- tests/test_direct_coding_host_contract.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/test_direct_coding_host_contract.py diff --git a/tests/test_direct_coding_host_contract.py b/tests/test_direct_coding_host_contract.py new file mode 100644 index 00000000..db58e88c --- /dev/null +++ b/tests/test_direct_coding_host_contract.py @@ -0,0 +1,44 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + +ACTIVE_SURFACES = ( + ROOT / "apps/cptr/cptr/utils/tools.py", + ROOT / "scripts/install-core.sh", + ROOT / "scripts/install-lib.sh", + ROOT / "scripts/verify-stack.sh", + ROOT / "release/compatibility.json", +) + +FORBIDDEN_DIRECT_CODING_SANDBOX_MARKERS = ( + "cptr.services.command_sandbox", + "CPTR_DIRECT_CODING_SANDBOX", + "HEIDI_SANDBOX_PROFILE", + "CPTR_DIRECT_CODING_CONTAINER_IMAGE", + "CPTR_DIRECT_CODING_VM_RUNNER", + '"bubblewrap"', + "bwrap", +) + + +def test_direct_coding_has_no_sandbox_implementation_or_configuration_surface() -> None: + implementation = ROOT / "apps/cptr/cptr/services/command_sandbox.py" + legacy_tests = ROOT / "apps/cptr/tests/test_command_sandbox.py" + + assert not implementation.exists(), "Direct Coding sandbox implementation must be removed" + assert not legacy_tests.exists(), "sandbox-specific CPTR tests must be removed" + + offenders: list[str] = [] + for path in ACTIVE_SURFACES: + text = path.read_text(encoding="utf-8") + for marker in FORBIDDEN_DIRECT_CODING_SANDBOX_MARKERS: + if marker in text: + offenders.append(f"{path.relative_to(ROOT)}: {marker}") + + assert not offenders, "active sandbox surface remains:\n" + "\n".join(offenders) + + +def test_owner_full_remains_the_managed_default() -> None: + installer = (ROOT / "scripts/install-core.sh").read_text(encoding="utf-8") + assert "state_default HEIDI_CONTROL_PROFILE owner-full" in installer From cb864d4270bada13065c9f2b0f274d2cbb64b724 Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:21:18 +1000 Subject: [PATCH 4/9] chore: apply host-native direct coding migration --- .../remove-direct-coding-sandbox-once.yml | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 .github/workflows/remove-direct-coding-sandbox-once.yml diff --git a/.github/workflows/remove-direct-coding-sandbox-once.yml b/.github/workflows/remove-direct-coding-sandbox-once.yml new file mode 100644 index 00000000..62f8d9ad --- /dev/null +++ b/.github/workflows/remove-direct-coding-sandbox-once.yml @@ -0,0 +1,174 @@ +name: Remove Direct Coding sandbox once + +on: + push: + branches: [remove-direct-coding-sandbox] + paths: + - .github/workflows/remove-direct-coding-sandbox-once.yml + +permissions: + contents: write + +jobs: + migrate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: remove-direct-coding-sandbox + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Remove Direct Coding sandbox implementation and configuration + shell: bash + run: | + python3 - <<'PY' + from __future__ import annotations + + import json + import re + from pathlib import Path + + root = Path.cwd() + + def replace_once(path: str, old: str, new: str) -> None: + file = root / path + text = file.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise SystemExit(f"{path}: expected exactly one replacement target, found {count}") + file.write_text(text.replace(old, new, 1), encoding="utf-8") + + tools = "apps/cptr/cptr/utils/tools.py" + replace_once( + tools, + "from cptr.services.command_sandbox import SandboxUnavailable, sandbox_command\n", + "", + ) + replace_once( + tools, + ''' if __context__.get("direct_coding"):\n try:\n sandboxed = sandbox_command(\n command=command,\n argv=__argv,\n workspace=workspace,\n work_dir=work_dir,\n allow_network=bool(__context__.get("allow_network")),\n profile=__context__.get("sandbox_profile"),\n )\n except SandboxUnavailable as e:\n return f"Error: direct coding sandbox unavailable: {e}"\n __argv = sandboxed.argv\n if sandboxed.shell_command is not None:\n command = sandboxed.shell_command\n __use_pty = False\n\n''', + "", + ) + + install_core = root / "scripts/install-core.sh" + text = install_core.read_text(encoding="utf-8") + for old in ( + 'SANDBOX_PROFILE="${HEIDI_SANDBOX_PROFILE:-bubblewrap}"\n', + ' env_line CPTR_DIRECT_CODING_SANDBOX "$SANDBOX_PROFILE"\n', + ' env_line HEIDI_SANDBOX_PROFILE "$SANDBOX_PROFILE"\n', + ): + count = text.count(old) + if count != 1: + raise SystemExit(f"scripts/install-core.sh: expected one {old.strip()!r}, found {count}") + text = text.replace(old, "", 1) + install_core.write_text(text, encoding="utf-8") + + install_lib = root / "scripts/install-lib.sh" + text = install_lib.read_text(encoding="utf-8") + pattern = re.compile( + r'''ensure_host_security_dependencies\(\) \{\n''' + r''' local packages=\(bubblewrap age\)\n''' + r''' need_cmd setcap \|\| packages\+=\(libcap2-bin\)\n''' + r''' if ! need_cmd bwrap \|\| ! need_cmd age \|\| ! need_cmd age-keygen \|\| ! need_cmd setcap; then\n''' + r''' step "Installing sandbox, encryption, and capability dependencies"\n''' + r''' apt_install "\$\{packages\[@\]\}" \|\| fail "bubblewrap, age, and libcap are required for the managed production profile"\n''' + r''' fi\n''' + r'''\}\n''' + ) + replacement = '''ensure_host_security_dependencies() {\n local packages=(age)\n need_cmd setcap || packages+=(libcap2-bin)\n if ! need_cmd age || ! need_cmd age-keygen || ! need_cmd setcap; then\n step "Installing encryption and capability dependencies"\n apt_install "${packages[@]}" || fail "age and libcap are required for the managed production profile"\n fi\n}\n''' + text, count = pattern.subn(replacement, text, count=1) + if count != 1: + raise SystemExit("scripts/install-lib.sh: host security dependency block did not match") + install_lib.write_text(text, encoding="utf-8") + + verify = root / "scripts/verify-stack.sh" + text = verify.read_text(encoding="utf-8") + text, count = re.subn( + r'''\ncheck_sandbox\(\) \{.*?\n\}\n\ncheck_tailscale\n''', + "\ncheck_tailscale\n", + text, + count=1, + flags=re.DOTALL, + ) + if count != 1: + raise SystemExit("scripts/verify-stack.sh: check_sandbox block did not match") + text = text.replace( + ' split-tailscale:backend) check_backend; check_sandbox ;;', + ' split-tailscale:backend) check_backend ;;', + ) + text = text.replace( + ' *) check_backend; check_sandbox; check_mcp ;;', + ' *) check_backend; check_mcp ;;', + ) + verify.write_text(text, encoding="utf-8") + + compatibility_path = root / "release/compatibility.json" + compatibility = json.loads(compatibility_path.read_text(encoding="utf-8")) + if "sandbox" not in compatibility: + raise SystemExit("release/compatibility.json: sandbox section already absent unexpectedly") + del compatibility["sandbox"] + migration = ( + "Direct Coding executes natively on the authorized CPTR host under the selected control profile; " + "the Bubblewrap/systemd/container/VM sandbox execution profiles and their configuration surface were removed." + ) + migrations = compatibility.setdefault("migrations", []) + if migration not in migrations: + migrations.append(migration) + compatibility_path.write_text( + json.dumps(compatibility, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + for relative in ( + "apps/cptr/cptr/services/command_sandbox.py", + "apps/cptr/tests/test_command_sandbox.py", + ): + path = root / relative + if not path.exists(): + raise SystemExit(f"expected sandbox file is missing before migration: {relative}") + path.unlink() + + active = ( + root / "apps/cptr/cptr/utils/tools.py", + root / "scripts/install-core.sh", + root / "scripts/install-lib.sh", + root / "scripts/verify-stack.sh", + root / "release/compatibility.json", + ) + forbidden = ( + "cptr.services.command_sandbox", + "CPTR_DIRECT_CODING_SANDBOX", + "HEIDI_SANDBOX_PROFILE", + "CPTR_DIRECT_CODING_CONTAINER_IMAGE", + "CPTR_DIRECT_CODING_VM_RUNNER", + '"bubblewrap"', + "bwrap", + "sandbox_profile", + ) + offenders = [] + for path in active: + body = path.read_text(encoding="utf-8") + for marker in forbidden: + if marker in body: + offenders.append(f"{path.relative_to(root)}: {marker}") + if offenders: + raise SystemExit("sandbox surface remains:\n" + "\n".join(offenders)) + + installer = (root / "scripts/install-core.sh").read_text(encoding="utf-8") + if "state_default HEIDI_CONTROL_PROFILE owner-full" not in installer: + raise SystemExit("owner-full default was accidentally changed") + PY + + python3 -m py_compile apps/cptr/cptr/utils/tools.py + python3 -m json.tool release/compatibility.json >/dev/null + bash -n scripts/install-core.sh scripts/install-lib.sh scripts/verify-stack.sh + + git rm .github/workflows/remove-direct-coding-sandbox-once.yml + git add -A + git diff --cached --check + git config user.name "heidi-maintenance-bot" + git config user.email "heidi-maintenance-bot@users.noreply.github.com" + git commit -m "refactor(cptr): remove Direct Coding sandbox layer" + git push origin HEAD:remove-direct-coding-sandbox From 15e363ec35b9fcfa0f1bff9aa3064ac1803f4d31 Mon Sep 17 00:00:00 2001 From: heidi-maintenance-bot Date: Mon, 31 Aug 2026 06:21:28 +0000 Subject: [PATCH 5/9] refactor(cptr): remove Direct Coding sandbox layer --- .../remove-direct-coding-sandbox-once.yml | 174 -------------- apps/cptr/cptr/services/command_sandbox.py | 215 ------------------ apps/cptr/cptr/utils/tools.py | 18 -- apps/cptr/tests/test_command_sandbox.py | 65 ------ release/compatibility.json | 24 +- scripts/install-core.sh | 3 - scripts/install-lib.sh | 8 +- scripts/verify-stack.sh | 19 +- 8 files changed, 21 insertions(+), 505 deletions(-) delete mode 100644 .github/workflows/remove-direct-coding-sandbox-once.yml delete mode 100644 apps/cptr/cptr/services/command_sandbox.py delete mode 100644 apps/cptr/tests/test_command_sandbox.py diff --git a/.github/workflows/remove-direct-coding-sandbox-once.yml b/.github/workflows/remove-direct-coding-sandbox-once.yml deleted file mode 100644 index 62f8d9ad..00000000 --- a/.github/workflows/remove-direct-coding-sandbox-once.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Remove Direct Coding sandbox once - -on: - push: - branches: [remove-direct-coding-sandbox] - paths: - - .github/workflows/remove-direct-coding-sandbox-once.yml - -permissions: - contents: write - -jobs: - migrate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: remove-direct-coding-sandbox - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Remove Direct Coding sandbox implementation and configuration - shell: bash - run: | - python3 - <<'PY' - from __future__ import annotations - - import json - import re - from pathlib import Path - - root = Path.cwd() - - def replace_once(path: str, old: str, new: str) -> None: - file = root / path - text = file.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise SystemExit(f"{path}: expected exactly one replacement target, found {count}") - file.write_text(text.replace(old, new, 1), encoding="utf-8") - - tools = "apps/cptr/cptr/utils/tools.py" - replace_once( - tools, - "from cptr.services.command_sandbox import SandboxUnavailable, sandbox_command\n", - "", - ) - replace_once( - tools, - ''' if __context__.get("direct_coding"):\n try:\n sandboxed = sandbox_command(\n command=command,\n argv=__argv,\n workspace=workspace,\n work_dir=work_dir,\n allow_network=bool(__context__.get("allow_network")),\n profile=__context__.get("sandbox_profile"),\n )\n except SandboxUnavailable as e:\n return f"Error: direct coding sandbox unavailable: {e}"\n __argv = sandboxed.argv\n if sandboxed.shell_command is not None:\n command = sandboxed.shell_command\n __use_pty = False\n\n''', - "", - ) - - install_core = root / "scripts/install-core.sh" - text = install_core.read_text(encoding="utf-8") - for old in ( - 'SANDBOX_PROFILE="${HEIDI_SANDBOX_PROFILE:-bubblewrap}"\n', - ' env_line CPTR_DIRECT_CODING_SANDBOX "$SANDBOX_PROFILE"\n', - ' env_line HEIDI_SANDBOX_PROFILE "$SANDBOX_PROFILE"\n', - ): - count = text.count(old) - if count != 1: - raise SystemExit(f"scripts/install-core.sh: expected one {old.strip()!r}, found {count}") - text = text.replace(old, "", 1) - install_core.write_text(text, encoding="utf-8") - - install_lib = root / "scripts/install-lib.sh" - text = install_lib.read_text(encoding="utf-8") - pattern = re.compile( - r'''ensure_host_security_dependencies\(\) \{\n''' - r''' local packages=\(bubblewrap age\)\n''' - r''' need_cmd setcap \|\| packages\+=\(libcap2-bin\)\n''' - r''' if ! need_cmd bwrap \|\| ! need_cmd age \|\| ! need_cmd age-keygen \|\| ! need_cmd setcap; then\n''' - r''' step "Installing sandbox, encryption, and capability dependencies"\n''' - r''' apt_install "\$\{packages\[@\]\}" \|\| fail "bubblewrap, age, and libcap are required for the managed production profile"\n''' - r''' fi\n''' - r'''\}\n''' - ) - replacement = '''ensure_host_security_dependencies() {\n local packages=(age)\n need_cmd setcap || packages+=(libcap2-bin)\n if ! need_cmd age || ! need_cmd age-keygen || ! need_cmd setcap; then\n step "Installing encryption and capability dependencies"\n apt_install "${packages[@]}" || fail "age and libcap are required for the managed production profile"\n fi\n}\n''' - text, count = pattern.subn(replacement, text, count=1) - if count != 1: - raise SystemExit("scripts/install-lib.sh: host security dependency block did not match") - install_lib.write_text(text, encoding="utf-8") - - verify = root / "scripts/verify-stack.sh" - text = verify.read_text(encoding="utf-8") - text, count = re.subn( - r'''\ncheck_sandbox\(\) \{.*?\n\}\n\ncheck_tailscale\n''', - "\ncheck_tailscale\n", - text, - count=1, - flags=re.DOTALL, - ) - if count != 1: - raise SystemExit("scripts/verify-stack.sh: check_sandbox block did not match") - text = text.replace( - ' split-tailscale:backend) check_backend; check_sandbox ;;', - ' split-tailscale:backend) check_backend ;;', - ) - text = text.replace( - ' *) check_backend; check_sandbox; check_mcp ;;', - ' *) check_backend; check_mcp ;;', - ) - verify.write_text(text, encoding="utf-8") - - compatibility_path = root / "release/compatibility.json" - compatibility = json.loads(compatibility_path.read_text(encoding="utf-8")) - if "sandbox" not in compatibility: - raise SystemExit("release/compatibility.json: sandbox section already absent unexpectedly") - del compatibility["sandbox"] - migration = ( - "Direct Coding executes natively on the authorized CPTR host under the selected control profile; " - "the Bubblewrap/systemd/container/VM sandbox execution profiles and their configuration surface were removed." - ) - migrations = compatibility.setdefault("migrations", []) - if migration not in migrations: - migrations.append(migration) - compatibility_path.write_text( - json.dumps(compatibility, indent=2, ensure_ascii=False) + "\n", - encoding="utf-8", - ) - - for relative in ( - "apps/cptr/cptr/services/command_sandbox.py", - "apps/cptr/tests/test_command_sandbox.py", - ): - path = root / relative - if not path.exists(): - raise SystemExit(f"expected sandbox file is missing before migration: {relative}") - path.unlink() - - active = ( - root / "apps/cptr/cptr/utils/tools.py", - root / "scripts/install-core.sh", - root / "scripts/install-lib.sh", - root / "scripts/verify-stack.sh", - root / "release/compatibility.json", - ) - forbidden = ( - "cptr.services.command_sandbox", - "CPTR_DIRECT_CODING_SANDBOX", - "HEIDI_SANDBOX_PROFILE", - "CPTR_DIRECT_CODING_CONTAINER_IMAGE", - "CPTR_DIRECT_CODING_VM_RUNNER", - '"bubblewrap"', - "bwrap", - "sandbox_profile", - ) - offenders = [] - for path in active: - body = path.read_text(encoding="utf-8") - for marker in forbidden: - if marker in body: - offenders.append(f"{path.relative_to(root)}: {marker}") - if offenders: - raise SystemExit("sandbox surface remains:\n" + "\n".join(offenders)) - - installer = (root / "scripts/install-core.sh").read_text(encoding="utf-8") - if "state_default HEIDI_CONTROL_PROFILE owner-full" not in installer: - raise SystemExit("owner-full default was accidentally changed") - PY - - python3 -m py_compile apps/cptr/cptr/utils/tools.py - python3 -m json.tool release/compatibility.json >/dev/null - bash -n scripts/install-core.sh scripts/install-lib.sh scripts/verify-stack.sh - - git rm .github/workflows/remove-direct-coding-sandbox-once.yml - git add -A - git diff --cached --check - git config user.name "heidi-maintenance-bot" - git config user.email "heidi-maintenance-bot@users.noreply.github.com" - git commit -m "refactor(cptr): remove Direct Coding sandbox layer" - git push origin HEAD:remove-direct-coding-sandbox diff --git a/apps/cptr/cptr/services/command_sandbox.py b/apps/cptr/cptr/services/command_sandbox.py deleted file mode 100644 index 4e40c950..00000000 --- a/apps/cptr/cptr/services/command_sandbox.py +++ /dev/null @@ -1,215 +0,0 @@ -"""Direct Coding process-isolation profiles. - -The sandbox is intentionally applied only to ChatGPT Direct Coding/test commands. -Interactive legacy terminals and delegated-agent execution keep their existing -runtime unless they explicitly opt into the same profile. -""" - -from __future__ import annotations - -import os -import shlex -import shutil -from dataclasses import dataclass -from pathlib import Path - - -class SandboxUnavailable(RuntimeError): - """Raised when an explicitly selected isolation profile cannot run.""" - - -@dataclass(frozen=True) -class SandboxCommand: - argv: list[str] | None - shell_command: str | None - profile: str - - -_ALLOWED = {"host", "auto", "bubblewrap", "systemd", "container", "vm"} - - -def configured_profile() -> str: - value = os.getenv("CPTR_DIRECT_CODING_SANDBOX", "bubblewrap").strip().lower() or "bubblewrap" - if value not in _ALLOWED: - raise SandboxUnavailable( - f"unsupported CPTR_DIRECT_CODING_SANDBOX={value!r}; expected one of {sorted(_ALLOWED)}" - ) - if value == "auto": - if shutil.which("bwrap"): - return "bubblewrap" - if shutil.which("systemd-run"): - return "systemd" - return "host" - return value - - -def _original_argv(command: str, argv: list[str] | None) -> list[str]: - return list(argv) if argv is not None else ["/bin/sh", "-lc", command] - - -def _bubblewrap( - *, command: str, argv: list[str] | None, workspace: Path, work_dir: Path, allow_network: bool -) -> SandboxCommand: - binary = shutil.which("bwrap") - if not binary: - raise SandboxUnavailable( - "bubblewrap sandbox requested but bwrap is not installed; install bubblewrap or choose host/systemd" - ) - workspace = workspace.resolve() - work_dir = work_dir.resolve() - if not work_dir.is_relative_to(workspace): - raise SandboxUnavailable("sandbox working directory must remain inside the workspace") - wrapped = [ - binary, - "--die-with-parent", - "--new-session", - "--unshare-pid", - "--unshare-ipc", - "--unshare-uts", - "--ro-bind", - "/", - "/", - "--dev-bind", - "/dev", - "/dev", - "--proc", - "/proc", - "--tmpfs", - "/tmp", - "--bind", - str(workspace), - str(workspace), - "--chdir", - str(work_dir), - ] - if not allow_network: - wrapped.append("--unshare-net") - wrapped.extend(["--", *_original_argv(command, argv)]) - return SandboxCommand(argv=wrapped, shell_command=None, profile="bubblewrap") - - -def _systemd( - *, command: str, argv: list[str] | None, work_dir: Path, allow_network: bool -) -> SandboxCommand: - binary = shutil.which("systemd-run") - if not binary: - raise SandboxUnavailable("systemd sandbox requested but systemd-run is not installed") - wrapped = [ - binary, - "--user", - "--scope", - "--quiet", - "--pipe", - "--wait", - "--collect", - "--property=NoNewPrivileges=yes", - "--property=PrivateTmp=yes", - "--property=TasksMax=1024", - f"--working-directory={work_dir}", - ] - if not allow_network: - # IPAddressDeny is best-effort on transient user scopes. Managed Heidi - # deployments prefer bubblewrap when hard network isolation is required. - wrapped.append("--property=IPAddressDeny=any") - wrapped.extend(["--", *_original_argv(command, argv)]) - return SandboxCommand(argv=wrapped, shell_command=None, profile="systemd") - - -def _container( - *, command: str, argv: list[str] | None, workspace: Path, work_dir: Path, allow_network: bool -) -> SandboxCommand: - podman = shutil.which("podman") - if not podman: - raise SandboxUnavailable("container sandbox requested but podman is not installed") - image = os.getenv("CPTR_DIRECT_CODING_CONTAINER_IMAGE", "").strip() - if not image: - raise SandboxUnavailable( - "container sandbox requires CPTR_DIRECT_CODING_CONTAINER_IMAGE to name a trusted development image" - ) - workspace = workspace.resolve() - relative = work_dir.resolve().relative_to(workspace) - container_cwd = Path("/workspace") / relative - wrapped = [ - podman, - "run", - "--rm", - "--read-only", - "--security-opt=no-new-privileges", - "--cap-drop=all", - "--pids-limit=1024", - "--tmpfs=/tmp:rw,nosuid,nodev,size=1g", - f"--volume={workspace}:/workspace:rw,Z", - f"--workdir={container_cwd}", - "--network=host" if allow_network else "--network=none", - image, - *_original_argv(command, argv), - ] - return SandboxCommand(argv=wrapped, shell_command=None, profile="container") - - -def _vm( - *, command: str, argv: list[str] | None, workspace: Path, work_dir: Path, allow_network: bool -) -> SandboxCommand: - runner = os.getenv("CPTR_DIRECT_CODING_VM_RUNNER", "").strip() - if not runner: - raise SandboxUnavailable( - "VM sandbox requires CPTR_DIRECT_CODING_VM_RUNNER; configure a trusted runner that accepts the documented argv" - ) - runner_argv = shlex.split(runner) - if not runner_argv or not shutil.which(runner_argv[0]): - raise SandboxUnavailable("configured CPTR_DIRECT_CODING_VM_RUNNER is not executable") - wrapped = [ - *runner_argv, - "--workspace", - str(workspace.resolve()), - "--cwd", - str(work_dir.resolve()), - "--network", - "allow" if allow_network else "deny", - "--", - *_original_argv(command, argv), - ] - return SandboxCommand(argv=wrapped, shell_command=None, profile="vm") - - -def sandbox_command( - *, - command: str, - argv: list[str] | None, - workspace: str | Path, - work_dir: str | Path, - allow_network: bool, - profile: str | None = None, -) -> SandboxCommand: - selected = (profile or configured_profile()).strip().lower() - workspace_path = Path(workspace).resolve() - work_dir_path = Path(work_dir).resolve() - if selected == "host": - return SandboxCommand(argv=argv, shell_command=None if argv is not None else command, profile="host") - if selected == "bubblewrap": - return _bubblewrap( - command=command, - argv=argv, - workspace=workspace_path, - work_dir=work_dir_path, - allow_network=allow_network, - ) - if selected == "systemd": - return _systemd(command=command, argv=argv, work_dir=work_dir_path, allow_network=allow_network) - if selected == "container": - return _container( - command=command, - argv=argv, - workspace=workspace_path, - work_dir=work_dir_path, - allow_network=allow_network, - ) - if selected == "vm": - return _vm( - command=command, - argv=argv, - workspace=workspace_path, - work_dir=work_dir_path, - allow_network=allow_network, - ) - raise SandboxUnavailable(f"unsupported sandbox profile: {selected}") diff --git a/apps/cptr/cptr/utils/tools.py b/apps/cptr/cptr/utils/tools.py index fc14d801..fd1e9e4c 100644 --- a/apps/cptr/cptr/utils/tools.py +++ b/apps/cptr/cptr/utils/tools.py @@ -52,7 +52,6 @@ preexec_for, ) from cptr.services.execution_manager import command_session_registry -from cptr.services.command_sandbox import SandboxUnavailable, sandbox_command from cptr.utils.runtime import Runtime, FileError try: @@ -1727,23 +1726,6 @@ async def run_command( env = {**os.environ, "PAGER": "cat", "GIT_PAGER": "cat"} preexec = None - if __context__.get("direct_coding"): - try: - sandboxed = sandbox_command( - command=command, - argv=__argv, - workspace=workspace, - work_dir=work_dir, - allow_network=bool(__context__.get("allow_network")), - profile=__context__.get("sandbox_profile"), - ) - except SandboxUnavailable as e: - return f"Error: direct coding sandbox unavailable: {e}" - __argv = sandboxed.argv - if sandboxed.shell_command is not None: - command = sandboxed.shell_command - __use_pty = False - master_fd = None try: diff --git a/apps/cptr/tests/test_command_sandbox.py b/apps/cptr/tests/test_command_sandbox.py deleted file mode 100644 index 911d9952..00000000 --- a/apps/cptr/tests/test_command_sandbox.py +++ /dev/null @@ -1,65 +0,0 @@ -from pathlib import Path -from types import SimpleNamespace -from unittest.mock import patch - -import pytest - -from cptr.routers.coding import _command_context -from cptr.services.command_sandbox import SandboxUnavailable, configured_profile, sandbox_command - - -def test_direct_coding_command_context_marks_sandbox_and_network_policy(): - request = SimpleNamespace() - context = _command_context( - request=request, - user_id="user_1", - workspace_id="ws_1", - workspace_path="/tmp/workspace", - worker_id="worker_1", - allow_network=True, - ) - assert context["direct_coding"] is True - assert context["allow_network"] is True - assert context["direct_worker_id"] == "worker_1" - - -def test_bubblewrap_denies_network_and_writes_only_workspace(tmp_path: Path): - workspace = tmp_path / "repo" - workspace.mkdir() - work_dir = workspace / "src" - work_dir.mkdir() - with patch("cptr.services.command_sandbox.shutil.which", return_value="/usr/bin/bwrap"): - wrapped = sandbox_command( - command="printf hello", - argv=None, - workspace=workspace, - work_dir=work_dir, - allow_network=False, - profile="bubblewrap", - ) - assert wrapped.profile == "bubblewrap" - assert wrapped.shell_command is None - assert wrapped.argv is not None - assert "--unshare-net" in wrapped.argv - assert ["--bind", str(workspace.resolve()), str(workspace.resolve())] == wrapped.argv[ - wrapped.argv.index("--bind") : wrapped.argv.index("--bind") + 3 - ] - assert wrapped.argv[-4:] == ["--", "/bin/sh", "-lc", "printf hello"] - - -def test_default_direct_coding_sandbox_is_fail_closed_bubblewrap(monkeypatch): - monkeypatch.delenv("CPTR_DIRECT_CODING_SANDBOX", raising=False) - assert configured_profile() == "bubblewrap" - - -def test_explicit_unavailable_sandbox_fails_closed(tmp_path: Path): - with patch("cptr.services.command_sandbox.shutil.which", return_value=None): - with pytest.raises(SandboxUnavailable, match="bubblewrap"): - sandbox_command( - command="true", - argv=None, - workspace=tmp_path, - work_dir=tmp_path, - allow_network=False, - profile="bubblewrap", - ) diff --git a/release/compatibility.json b/release/compatibility.json index fcbcdb64..1198efe1 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -17,15 +17,20 @@ "state_migration": "automatic-forward-compatible" }, "deployment": { - "topologies": ["all-in-one", "split-tailscale"], - "split_roles": ["backend", "mcp"], + "topologies": [ + "all-in-one", + "split-tailscale" + ], + "split_roles": [ + "backend", + "mcp" + ], "linux_systemd": true, - "supervisors": ["systemd", "foreground", "podman-quadlet"] - }, - "sandbox": { - "default_managed_profile": "bubblewrap", - "supported_profiles": ["host", "auto", "bubblewrap", "systemd", "container", "vm"], - "network_default": "deny" + "supervisors": [ + "systemd", + "foreground", + "podman-quadlet" + ] }, "migrations": [ "MCP v2.1.3 makes cptr_open_live_workbench explicitly optional so normal ChatGPT Direct Coding starts with the task-relevant tool instead of a Workbench activation prerequisite.", @@ -35,6 +40,7 @@ "The owner-full control profile is the deployment default for fresh installs and upgrades, adding command:external plus confirmed managed-workspace deletion; standard and developer remain explicit opt-down profiles, and legacy full remains an owner-full alias.", "Git workspaces warm FDX automatically after provisioning; FDX failure remains a non-fatal fallback to normal CPTR Direct Coding.", "Existing CPTR data under ~/.cptr is preserved; Heidi takes a pre-upgrade backup before activation.", - "FDX protocol 2 remains required for persistent structured resident reads." + "FDX protocol 2 remains required for persistent structured resident reads.", + "Direct Coding executes natively on the authorized CPTR host under the selected control profile; the Bubblewrap/systemd/container/VM sandbox execution profiles and their configuration surface were removed." ] } diff --git a/scripts/install-core.sh b/scripts/install-core.sh index b064c077..92814fee 100755 --- a/scripts/install-core.sh +++ b/scripts/install-core.sh @@ -265,7 +265,6 @@ if [[ "$PUBLIC_DEPLOYMENT" == 1 ]]; then fi random_mcp_token -SANDBOX_PROFILE="${HEIDI_SANDBOX_PROFILE:-bubblewrap}" step "Writing owner-only configuration" if [[ "$INCLUDES_BACKEND" == 1 ]]; then @@ -276,7 +275,6 @@ if [[ "$INCLUDES_BACKEND" == 1 ]]; then env_line CPTR_FDX_REQUEST_TIMEOUT_SECONDS 20 env_line CPTR_FDX_DAEMON_IDLE_TTL_SECONDS 600 env_line CPTR_FDX_MAX_DAEMONS 8 - env_line CPTR_DIRECT_CODING_SANDBOX "$SANDBOX_PROFILE" env_line PATH "$HEIDI_HOME/current/venv/bin:$HEIDI_HOME/current/runtime/node/bin:$HEIDI_HOME/current/bin:$HOME/.local/bin:$HOME/.cargo/bin:/usr/local/bin:/usr/bin:/bin" env_line PYTHONUNBUFFERED 1 } >"$CPTR_ENV_FILE"; chmod 600 "$CPTR_ENV_FILE" @@ -454,7 +452,6 @@ HEIDI_SERVICE_UNITS="${HEIDI_SERVICE_UNITS% }" env_line HEIDI_PUBLIC_ORIGIN "$PUBLIC_ORIGIN" env_line HEIDI_MCP_URL "$MCP_URL" env_line HEIDI_CONTROL_PROFILE "$CONTROL_PROFILE" - env_line HEIDI_SANDBOX_PROFILE "$SANDBOX_PROFILE" env_line HEIDI_PUBLIC_TRANSPORT "$PUBLIC_TRANSPORT" env_line HEIDI_MCP_DOMAIN "$MCP_DOMAIN" env_line HEIDI_MCP_ALLOWED_EMAIL "$MCP_ALLOWED_EMAIL" diff --git a/scripts/install-lib.sh b/scripts/install-lib.sh index 0f36d327..fe1b0bce 100755 --- a/scripts/install-lib.sh +++ b/scripts/install-lib.sh @@ -67,11 +67,11 @@ PY } ensure_host_security_dependencies() { - local packages=(bubblewrap age) + local packages=(age) need_cmd setcap || packages+=(libcap2-bin) - if ! need_cmd bwrap || ! need_cmd age || ! need_cmd age-keygen || ! need_cmd setcap; then - step "Installing sandbox, encryption, and capability dependencies" - apt_install "${packages[@]}" || fail "bubblewrap, age, and libcap are required for the managed production profile" + if ! need_cmd age || ! need_cmd age-keygen || ! need_cmd setcap; then + step "Installing encryption and capability dependencies" + apt_install "${packages[@]}" || fail "age and libcap are required for the managed production profile" fi } diff --git a/scripts/verify-stack.sh b/scripts/verify-stack.sh index 597b580d..e56f02fc 100755 --- a/scripts/verify-stack.sh +++ b/scripts/verify-stack.sh @@ -188,27 +188,12 @@ check_mcp() { fi } -check_sandbox() { - [[ "$ROLE" == mcp ]] && return 0 - local profile="${HEIDI_SANDBOX_PROFILE:-bubblewrap}" - case "$profile" in - bubblewrap) - if command -v bwrap >/dev/null 2>&1 && bwrap --version >/dev/null 2>&1; then pass "Direct Coding bubblewrap sandbox available"; else fail_check dependency "Direct Coding sandbox" "bubblewrap is selected but bwrap is unavailable"; fi - ;; - host) pass "Direct Coding sandbox profile: host (explicit reduced isolation)" ;; - systemd) command -v systemd-run >/dev/null 2>&1 && pass "Direct Coding systemd-run sandbox available" || fail_check dependency "Direct Coding sandbox" "systemd-run unavailable" ;; - container) command -v podman >/dev/null 2>&1 && pass "Direct Coding Podman sandbox available" || fail_check dependency "Direct Coding sandbox" "podman unavailable" ;; - vm) [[ -n "${CPTR_DIRECT_CODING_VM_RUNNER:-}" ]] && pass "Direct Coding VM runner configured" || fail_check dependency "Direct Coding sandbox" "VM runner is not configured" ;; - *) fail_check compatibility "Direct Coding sandbox" "unsupported profile $profile" ;; - esac -} - check_tailscale check_compatibility case "$TOPOLOGY:$ROLE" in - split-tailscale:backend) check_backend; check_sandbox ;; + split-tailscale:backend) check_backend ;; split-tailscale:mcp) check_mcp ;; - *) check_backend; check_sandbox; check_mcp ;; + *) check_backend; check_mcp ;; esac if ((${#FAIL_LABELS[@]})); then From 654f10746edca1c732e7b8154106fc4f55ff355c Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:23:49 +1000 Subject: [PATCH 6/9] test: harden host-native direct coding contract --- tests/test_direct_coding_host_contract.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_direct_coding_host_contract.py b/tests/test_direct_coding_host_contract.py index db58e88c..8ff9cb03 100644 --- a/tests/test_direct_coding_host_contract.py +++ b/tests/test_direct_coding_host_contract.py @@ -1,3 +1,4 @@ +import json from pathlib import Path @@ -17,6 +18,7 @@ "HEIDI_SANDBOX_PROFILE", "CPTR_DIRECT_CODING_CONTAINER_IMAGE", "CPTR_DIRECT_CODING_VM_RUNNER", + "sandbox_profile", '"bubblewrap"', "bwrap", ) @@ -39,6 +41,11 @@ def test_direct_coding_has_no_sandbox_implementation_or_configuration_surface() assert not offenders, "active sandbox surface remains:\n" + "\n".join(offenders) +def test_compatibility_contract_has_no_direct_coding_sandbox_profile() -> None: + compatibility = json.loads((ROOT / "release/compatibility.json").read_text(encoding="utf-8")) + assert "sandbox" not in compatibility + + def test_owner_full_remains_the_managed_default() -> None: installer = (ROOT / "scripts/install-core.sh").read_text(encoding="utf-8") assert "state_default HEIDI_CONTROL_PROFILE owner-full" in installer From 8eb76fcf25927a85305d92d7bed3f6340d51f52c Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:25:02 +1000 Subject: [PATCH 7/9] chore: update host-native installer contract --- .../update-host-native-contract-once.yml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/update-host-native-contract-once.yml diff --git a/.github/workflows/update-host-native-contract-once.yml b/.github/workflows/update-host-native-contract-once.yml new file mode 100644 index 00000000..f4c2d8a3 --- /dev/null +++ b/.github/workflows/update-host-native-contract-once.yml @@ -0,0 +1,39 @@ +name: Update host-native installer contract once + +on: + push: + branches: [remove-direct-coding-sandbox] + paths: + - .github/workflows/update-host-native-contract-once.yml + +permissions: + contents: write + +jobs: + update-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: remove-direct-coding-sandbox + fetch-depth: 0 + - name: Replace stale sandbox compatibility assertion + run: | + python3 - <<'PY' + from pathlib import Path + + path = Path("tests/test_installer_contract.py") + text = path.read_text(encoding="utf-8") + old = '''def test_compatibility_manifest_matches_canonical_runtime_inventory_and_sandbox():\n compatibility = json.loads(read("release/compatibility.json"))\n verifier = load_compatibility_verifier()\n result = verifier.verify(ROOT, compatibility["heidi_version"])\n assert result["mcp_tool_count"] == compatibility["mcp"]["registered_action_count"]\n assert "cptr_workspace_lifecycle" in verifier.compact_tool_names(ROOT)\n assert compatibility["deployment"]["topologies"] == ["all-in-one", "split-tailscale"]\n assert "sandbox" in compatibility\n''' + new = '''def test_compatibility_manifest_matches_canonical_runtime_inventory_and_host_native_direct_coding():\n compatibility = json.loads(read("release/compatibility.json"))\n verifier = load_compatibility_verifier()\n result = verifier.verify(ROOT, compatibility["heidi_version"])\n assert result["mcp_tool_count"] == compatibility["mcp"]["registered_action_count"]\n assert "cptr_workspace_lifecycle" in verifier.compact_tool_names(ROOT)\n assert compatibility["deployment"]["topologies"] == ["all-in-one", "split-tailscale"]\n assert "sandbox" not in compatibility\n assert any("executes natively on the authorized CPTR host" in item for item in compatibility["migrations"])\n''' + if text.count(old) != 1: + raise SystemExit("stale installer compatibility contract did not match exactly") + path.write_text(text.replace(old, new, 1), encoding="utf-8") + PY + git rm .github/workflows/update-host-native-contract-once.yml + git add tests/test_installer_contract.py + git diff --cached --check + git config user.name "heidi-maintenance-bot" + git config user.email "heidi-maintenance-bot@users.noreply.github.com" + git commit -m "test: expect host-native direct coding compatibility" + git push origin HEAD:remove-direct-coding-sandbox From 253c9f3fdba63534387de11733bc0dd0f2bc9a06 Mon Sep 17 00:00:00 2001 From: heidi-maintenance-bot Date: Mon, 31 Aug 2026 06:25:12 +0000 Subject: [PATCH 8/9] test: expect host-native direct coding compatibility --- .../update-host-native-contract-once.yml | 39 ------------------- tests/test_installer_contract.py | 5 ++- 2 files changed, 3 insertions(+), 41 deletions(-) delete mode 100644 .github/workflows/update-host-native-contract-once.yml diff --git a/.github/workflows/update-host-native-contract-once.yml b/.github/workflows/update-host-native-contract-once.yml deleted file mode 100644 index f4c2d8a3..00000000 --- a/.github/workflows/update-host-native-contract-once.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Update host-native installer contract once - -on: - push: - branches: [remove-direct-coding-sandbox] - paths: - - .github/workflows/update-host-native-contract-once.yml - -permissions: - contents: write - -jobs: - update-contract: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: remove-direct-coding-sandbox - fetch-depth: 0 - - name: Replace stale sandbox compatibility assertion - run: | - python3 - <<'PY' - from pathlib import Path - - path = Path("tests/test_installer_contract.py") - text = path.read_text(encoding="utf-8") - old = '''def test_compatibility_manifest_matches_canonical_runtime_inventory_and_sandbox():\n compatibility = json.loads(read("release/compatibility.json"))\n verifier = load_compatibility_verifier()\n result = verifier.verify(ROOT, compatibility["heidi_version"])\n assert result["mcp_tool_count"] == compatibility["mcp"]["registered_action_count"]\n assert "cptr_workspace_lifecycle" in verifier.compact_tool_names(ROOT)\n assert compatibility["deployment"]["topologies"] == ["all-in-one", "split-tailscale"]\n assert "sandbox" in compatibility\n''' - new = '''def test_compatibility_manifest_matches_canonical_runtime_inventory_and_host_native_direct_coding():\n compatibility = json.loads(read("release/compatibility.json"))\n verifier = load_compatibility_verifier()\n result = verifier.verify(ROOT, compatibility["heidi_version"])\n assert result["mcp_tool_count"] == compatibility["mcp"]["registered_action_count"]\n assert "cptr_workspace_lifecycle" in verifier.compact_tool_names(ROOT)\n assert compatibility["deployment"]["topologies"] == ["all-in-one", "split-tailscale"]\n assert "sandbox" not in compatibility\n assert any("executes natively on the authorized CPTR host" in item for item in compatibility["migrations"])\n''' - if text.count(old) != 1: - raise SystemExit("stale installer compatibility contract did not match exactly") - path.write_text(text.replace(old, new, 1), encoding="utf-8") - PY - git rm .github/workflows/update-host-native-contract-once.yml - git add tests/test_installer_contract.py - git diff --cached --check - git config user.name "heidi-maintenance-bot" - git config user.email "heidi-maintenance-bot@users.noreply.github.com" - git commit -m "test: expect host-native direct coding compatibility" - git push origin HEAD:remove-direct-coding-sandbox diff --git a/tests/test_installer_contract.py b/tests/test_installer_contract.py index b548c876..ca3bb29b 100644 --- a/tests/test_installer_contract.py +++ b/tests/test_installer_contract.py @@ -181,14 +181,15 @@ def test_bootstrap_revalidates_all_signed_source_files_before_reusing_release(): assert "target.is_symlink() or not target.is_dir()" in source -def test_compatibility_manifest_matches_canonical_runtime_inventory_and_sandbox(): +def test_compatibility_manifest_matches_canonical_runtime_inventory_and_host_native_direct_coding(): compatibility = json.loads(read("release/compatibility.json")) verifier = load_compatibility_verifier() result = verifier.verify(ROOT, compatibility["heidi_version"]) assert result["mcp_tool_count"] == compatibility["mcp"]["registered_action_count"] assert "cptr_workspace_lifecycle" in verifier.compact_tool_names(ROOT) assert compatibility["deployment"]["topologies"] == ["all-in-one", "split-tailscale"] - assert "sandbox" in compatibility + assert "sandbox" not in compatibility + assert any("executes natively on the authorized CPTR host" in item for item in compatibility["migrations"]) def test_installer_defaults_to_owner_full_profile(): From e2f4e0ab861e8dcd2bf290f90eb6337e656947e6 Mon Sep 17 00:00:00 2001 From: Heidi Dang <35790+heidi-dang@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:25:42 +1000 Subject: [PATCH 9/9] test: guard removed sandbox selectors and verifier --- tests/test_direct_coding_host_contract.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_direct_coding_host_contract.py b/tests/test_direct_coding_host_contract.py index 8ff9cb03..089f514d 100644 --- a/tests/test_direct_coding_host_contract.py +++ b/tests/test_direct_coding_host_contract.py @@ -19,6 +19,8 @@ "CPTR_DIRECT_CODING_CONTAINER_IMAGE", "CPTR_DIRECT_CODING_VM_RUNNER", "sandbox_profile", + "check_sandbox()", + "default_managed_profile", '"bubblewrap"', "bwrap", )