# Dependency Lock Boundary 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:** Stop enforcing `deps.lock` for system/bootstrap tools such as `git`, `make`, and `tmux`, while keeping lock enforcement for downloaded tools.

**Architecture:** `deps.toml` stays the source of truth for strategy. `scripts/update-lock.py` will only write lock entries for lock-managed installers, and `scripts/install-deps` will treat system installers as presence-only. The tests should cover both halves of the boundary: system tools can pass without lock entries, and downloaded tools still fail when the lock is missing or mismatched.

**Tech Stack:** Bash, Python 3, TOML, `unittest`, GitHub Actions.

---

### Task 1: Filter system tools out of lock generation

**Files:**
- Modify: `scripts/update-lock.py`
- Create: `tests/test_update_lock_boundary.py`

- [ ] **Step 1: Write the failing test**

Add a boundary test that creates a manifest with one system tool and one lock-managed tool, then verifies the generated lock only contains the lock-managed tool.

```python
import importlib.util
import os
import pathlib
import tempfile
import unittest
from unittest import mock


class UpdateLockBoundaryTest(unittest.TestCase):
    @staticmethod
    def load_update_lock_module():
        repo = pathlib.Path(__file__).resolve().parents[1]
        update_lock = repo / "scripts" / "update-lock.py"
        spec = importlib.util.spec_from_file_location("update_lock", update_lock)
        assert spec and spec.loader
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        return module

    def test_update_lock_skips_system_tools(self):
        update_lock = self.load_update_lock_module()

        with tempfile.TemporaryDirectory() as tmp:
            tmp_path = pathlib.Path(tmp)
            bin_dir = tmp_path / "bin"
            bin_dir.mkdir()

            for name, version in {"git": "git version 2.50.1", "nvim": "NVIM v0.12.2"}.items():
                cmd = bin_dir / name
                cmd.write_text(f"#!/usr/bin/env bash\necho '{version}'\n", encoding="utf-8")
                cmd.chmod(0o755)

            manifest = tmp_path / "deps.toml"
            manifest.write_text(
                """
[deps.git]
command = "git"
target = "latest"

[deps.git.mac]
installer = "system"
source = "https://git-scm.com/downloads"

[deps.nvim]
command = "nvim"
target = "v0.12.2"

[deps.nvim.linux]
installer = "official_tarball"
source = "https://neovim.io/doc/install/"
""".strip(),
                encoding="utf-8",
            )

            lock = tmp_path / "deps.lock"

            env = os.environ.copy()
            env["PATH"] = f"{bin_dir}:{env['PATH']}"

            with mock.patch.dict(os.environ, env, clear=False):
                with mock.patch.object(update_lock, "current_arch", return_value="arm64"):
                    with mock.patch.object(update_lock, "checksum_for_source", return_value="deadbeef"):
                        with mock.patch("sys.argv", [
                            "update-lock.py",
                            str(manifest),
                            str(lock),
                            "--platform",
                            "linux",
                            "--manifest-commit",
                            "test",
                        ]):
                            update_lock.main()

            text = lock.read_text(encoding="utf-8")
            self.assertIn("[deps.nvim.linux.arm64]", text)
            self.assertNotIn("[deps.git", text)
```

- [ ] **Step 2: Run the test and confirm it fails**

Run:

```bash
python3 -m unittest tests.test_update_lock_boundary.UpdateLockBoundaryTest.test_update_lock_skips_system_tools -v
```

Expected: fail because `scripts/update-lock.py` still writes lock entries for system tools.

- [ ] **Step 3: Implement the boundary filter**

Update `scripts/update-lock.py` so only lock-managed installers are written to `deps.lock`.

```python
LOCK_MANAGED_INSTALLERS = {
    "official_tarball",
    "official_release",
    "official_script",
    "official_cask",
    "community_ubuntu",
}


def should_lock(installer: str) -> bool:
    return installer in LOCK_MANAGED_INSTALLERS
```

Use that gate when iterating manifest entries. If an entry is `installer = "system"`, skip it entirely when writing the lock.

- [ ] **Step 4: Run the test and confirm it passes**

Run:

```bash
python3 -m unittest tests.test_update_lock_boundary.UpdateLockBoundaryTest.test_update_lock_skips_system_tools -v
```

Expected: PASS, and the generated lock contains only the lock-managed tool.

- [ ] **Step 5: Commit**

```bash
git add scripts/update-lock.py tests/test_update_lock_boundary.py
git commit -m "feat: skip system tools in deps lock generation"
```

### Task 2: Make install/check treat system tools as presence-only

**Files:**
- Modify: `scripts/install-deps`
- Create: `tests/test_install_deps_lock_boundary.py`

- [ ] **Step 1: Write the failing test**

Add two subprocess-based tests:

1. system tools pass without lock entries
2. downloaded tools still fail without lock entries

```python
import os
import pathlib
import subprocess
import tempfile
import unittest


class InstallDepsLockBoundaryTest(unittest.TestCase):
    def test_system_tool_check_does_not_require_lock_entry(self):
        repo = pathlib.Path(__file__).resolve().parents[1]
        script = repo / "scripts" / "install-deps"

        with tempfile.TemporaryDirectory() as tmp:
            tmp_path = pathlib.Path(tmp)
            bin_dir = tmp_path / "bin"
            bin_dir.mkdir()

            git_cmd = bin_dir / "git"
            git_cmd.write_text("#!/usr/bin/env bash\necho 'git version 2.50.1'\n", encoding="utf-8")
            git_cmd.chmod(0o755)

            manifest = tmp_path / "deps.toml"
            manifest.write_text(
                """
[deps.git]
command = "git"
target = "latest"

[deps.git.mac]
installer = "system"
source = "https://git-scm.com/downloads"
""".strip(),
                encoding="utf-8",
            )

            lock = tmp_path / "deps.lock"
            lock.write_text("[meta]\nmanifest_commit = \"test\"\n", encoding="utf-8")

            env = os.environ.copy()
            env["PATH"] = f"{bin_dir}:{env['PATH']}"
            env["DEPS_FILE"] = str(manifest)
            env["LOCK_FILE"] = str(lock)

            result = subprocess.run([str(script), "check", "--only", "git"], env=env, capture_output=True, text=True)

            self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
            self.assertIn("git target=latest installed=git version 2.50.1", result.stdout)

    def test_downloaded_tool_check_still_requires_lock_entry(self):
        repo = pathlib.Path(__file__).resolve().parents[1]
        script = repo / "scripts" / "install-deps"

        with tempfile.TemporaryDirectory() as tmp:
            tmp_path = pathlib.Path(tmp)
            bin_dir = tmp_path / "bin"
            bin_dir.mkdir()

            nvim_cmd = bin_dir / "nvim"
            nvim_cmd.write_text("#!/usr/bin/env bash\necho 'NVIM v0.12.2'\n", encoding="utf-8")
            nvim_cmd.chmod(0o755)

            manifest = tmp_path / "deps.toml"
            manifest.write_text(
                """
[deps.nvim]
command = "nvim"
target = "v0.12.2"

[deps.nvim.mac]
installer = "official_cask"
source = "https://neovim.io/doc/install/"
""".strip(),
                encoding="utf-8",
            )

            lock = tmp_path / "deps.lock"
            lock.write_text("[meta]\nmanifest_commit = \"test\"\n", encoding="utf-8")

            env = os.environ.copy()
            env["PATH"] = f"{bin_dir}:{env['PATH']}"
            env["DEPS_FILE"] = str(manifest)
            env["LOCK_FILE"] = str(lock)

            result = subprocess.run([str(script), "check", "--only", "nvim"], env=env, capture_output=True, text=True)

            self.assertNotEqual(result.returncode, 0)
            self.assertIn("lock entry missing", result.stdout + result.stderr)
```

- [ ] **Step 2: Run the tests and confirm they fail**

Run:

```bash
python3 -m unittest tests.test_install_deps_lock_boundary.InstallDepsLockBoundaryTest -v
```

Expected: failure, because `scripts/install-deps` still treats system tools as lock-managed and still requires lock entries for them.

- [ ] **Step 3: Implement the boundary in `scripts/install-deps`**

Update the lock helper functions so they return empty values for `installer = "system"`:

```bash
lock_version_required() {
  local name=$1 installer=$2

  case "$installer" in
    system)
      printf ""
      return 0
      ;;
  esac
  # existing strict lock lookup stays here
}
```

Apply the same rule to `lock_checksum_required()` and `lock_source_required()`.

Then update `install_one()` and `check_one()` so system tools only require the command to exist:

```bash
if [ "$installer" = "system" ]; then
  [ -n "$current" ] || die "$name is required on $(platform) but is not installed"
  info "$name target=$target installed=$(normalize_text "$current")"
  return 0
fi
```

That means:

- no lock lookup for system tools
- no checksum or source lookup for system tools
- no version equality check for system tools

- [ ] **Step 4: Run the tests and confirm they pass**

Run:

```bash
python3 -m unittest tests.test_install_deps_lock_boundary.InstallDepsLockBoundaryTest -v
```

Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add scripts/install-deps tests/test_install_deps_lock_boundary.py
git commit -m "feat: make system tools lock-exempt"
```

### Task 3: Update docs and verify the new boundary

**Files:**
- Modify: `README.md`
- Modify: `docs/roadmap.md`
- Modify: `docs/superpowers/specs/2026-05-10-deps-lock-boundary-design.md` if any wording needs tightening

- [ ] **Step 1: Write the failing docs check**

Add a short assertion to the implementation checklist in your head: the docs must say `git`, `make`, and `tmux` are presence-only checks and are not locked.

No code snippet is needed here; the change is textual and should be visible in the README and roadmap.

- [ ] **Step 2: Update the docs**

Make the README and roadmap explicit about the new split:

```markdown
System/bootstrap tools such as `git`, `make`, and `tmux` are verified for
availability only. They are not pinned in `deps.lock`.
```

Keep the existing downloaded-tool lock explanation intact so the boundary is clear.

- [ ] **Step 3: Run the full local verification**

Run:

```bash
make check
make deps-check
make doctor
python3 -m unittest discover -s tests -p 'test_*.py'
```

Expected:

- `make check` passes
- `make deps-check` passes
- `make doctor` passes
- the unittest suite passes

- [ ] **Step 4: Commit**

```bash
git add README.md docs/roadmap.md docs/superpowers/specs/2026-05-10-deps-lock-boundary-design.md
git commit -m "docs: clarify lock boundary for system tools"
```
