PRD 0009: Remove ssh-gate and bottle.ssh #13

Merged
didericis merged 5 commits from deprecate-ssh-gate into main 2026-05-13 00:01:01 -04:00
Owner

Summary

Implements the PRD that lands in this same branch. ssh-gate was introduced in PRD 0007 as an L4 forwarder for non-git SSH; in practice every upstream declared in any bottle has been a git remote, and PRD 0008's git-gate now handles those with credential isolation, gitleaks scanning, and insteadOf URL rewrites that capture push / fetch / clone / pull / ls-remote. ssh-gate was left doing transport-only forwarding with no gating value over the git-gate path; this PR removes it.

The change is mostly deletion across four commits, one per layer:

  1. Manifest. SshEntry, Bottle.ssh, the shadow-route validator between bottle.ssh and bottle.git, and the SSH-only port helper are gone. A legacy ssh key on any bottle now parse-fails with a one-line hint pointing at bottle.git (PRD 0008) so the migration is visible and one-shot rather than a silent ignore.
  2. Sidecar + backend wiring. claude_bottle/ssh_gate.py, the Docker sidecar (backend/docker/ssh_gate.py), and the SSH provisioner (backend/docker/provision/ssh.py) are deleted. The abstract BottleBackend loses provision_ssh and _validate_ssh_entries; the docker backend drops the DockerSSHGate instance, the gate kwarg threaded through prepare / launch, the gate_plan field on DockerBottlePlan, the y/N preflight's ssh-hosts block, and the ssh_hosts / ssh_gate keys in the dry-run JSON (the latter is a breaking change for any consumer of start --dry-run --format=json). cli info switches to printing the bottle's declared git remotes. Pipelock's docstring picks up the git-gate framing now that there's no PRD-0007 boundary to call out.
  3. Tests. tests/unit/test_ssh_gate.py and the fixture_with_ssh fixtures are deleted; the pipelock-allowlist test rewrites to exercise an egress duplicate (the property the ssh-leak guard was hitching onto); the manifest's shadow-route assertion becomes a legacy-ssh-dies-with-hint assertion; the orphan-cleanup integration drops the SSHGate.stop idempotency check (pipelock equivalent stays); the dry-run-plan integration drops assertions on the removed keys.
  4. Docs. README diagram drops the socat box and the agent's ~/.ssh/config; the manifest example shows git: instead of ssh:. PRD 0007 keeps the file but carries a Status: Superseded by PRD 0009 header with a one-paragraph block explaining why — the prior design's rationale stays in-tree as audit history.

52 unit tests pass. If non-git SSH ever returns, the git-gate pattern (gate holds credentials, agent gets a rewritten URL, gate makes the upstream connection) is the template for a fresh sidecar; ssh-gate's L4-only design doesn't come back.

## Summary Implements the PRD that lands in this same branch. ssh-gate was introduced in PRD 0007 as an L4 forwarder for non-git SSH; in practice every upstream declared in any bottle has been a git remote, and PRD 0008's git-gate now handles those with credential isolation, gitleaks scanning, and `insteadOf` URL rewrites that capture push / fetch / clone / pull / ls-remote. ssh-gate was left doing transport-only forwarding with no gating value over the git-gate path; this PR removes it. The change is mostly deletion across four commits, one per layer: 1. **Manifest.** `SshEntry`, `Bottle.ssh`, the shadow-route validator between bottle.ssh and bottle.git, and the SSH-only port helper are gone. A legacy `ssh` key on any bottle now parse-fails with a one-line hint pointing at `bottle.git` (PRD 0008) so the migration is visible and one-shot rather than a silent ignore. 2. **Sidecar + backend wiring.** `claude_bottle/ssh_gate.py`, the Docker sidecar (`backend/docker/ssh_gate.py`), and the SSH provisioner (`backend/docker/provision/ssh.py`) are deleted. The abstract `BottleBackend` loses `provision_ssh` and `_validate_ssh_entries`; the docker backend drops the `DockerSSHGate` instance, the `gate` kwarg threaded through prepare / launch, the `gate_plan` field on `DockerBottlePlan`, the y/N preflight's ssh-hosts block, and the `ssh_hosts` / `ssh_gate` keys in the dry-run JSON (the latter is a breaking change for any consumer of `start --dry-run --format=json`). `cli info` switches to printing the bottle's declared git remotes. Pipelock's docstring picks up the git-gate framing now that there's no PRD-0007 boundary to call out. 3. **Tests.** `tests/unit/test_ssh_gate.py` and the `fixture_with_ssh` fixtures are deleted; the pipelock-allowlist test rewrites to exercise an egress duplicate (the property the ssh-leak guard was hitching onto); the manifest's shadow-route assertion becomes a legacy-ssh-dies-with-hint assertion; the orphan-cleanup integration drops the SSHGate.stop idempotency check (pipelock equivalent stays); the dry-run-plan integration drops assertions on the removed keys. 4. **Docs.** README diagram drops the socat box and the agent's `~/.ssh/config`; the manifest example shows `git:` instead of `ssh:`. PRD 0007 keeps the file but carries a `Status: Superseded by PRD 0009` header with a one-paragraph block explaining why — the prior design's rationale stays in-tree as audit history. 52 unit tests pass. If non-git SSH ever returns, the git-gate pattern (gate holds credentials, agent gets a rewritten URL, gate makes the upstream connection) is the template for a fresh sidecar; ssh-gate's L4-only design doesn't come back.
didericis added 1 commit 2026-05-12 23:34:39 -04:00
docs(prds): add PRD 0009 to remove ssh-gate and bottle.ssh
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 34s
efcafae810
ssh-gate was built for non-git SSH (PRD 0007), but every
upstream currently declared in any bottle is a git remote, and
those now flow through git-gate (PRD 0008) with credential
isolation, gitleaks scanning, and `insteadOf` URL rewrites.
ssh-gate is left doing L4 forwarding with no gating value over
git-gate's path; carrying it means a redundant sidecar lifecycle,
a shadow-route validator between bottle.ssh and bottle.git, and
a third place to keep an SSH identity in sync.

Goal is straightforward deletion: bottle.ssh becomes a parse
error pointing at bottle.git, the SshEntry / SSHGate / socat
provisioner / pipelock allowlist branch all go away, and PRD
0007 carries a "Superseded by PRD 0009" header so the rationale
of the prior design stays in the tree.
didericis added 4 commits 2026-05-12 23:58:01 -04:00
Drop the SshEntry dataclass, the Bottle.ssh field, the shadow-
route validator, and the SSH-only _opt_port helper. A legacy
bottle.ssh key now parse-fails with a one-line hint pointing at
bottle.git (PRD 0008), which is the replacement.

BREAKING: manifests carrying bottle.ssh will not load. Migration
is per-entry: drop the ssh entry, add a git entry with a Name +
full Upstream URL + IdentityFile.
Delete claude_bottle/ssh_gate.py, the DockerSSHGate sidecar,
and the provision_ssh provisioner (~/.ssh/config + ssh-agent
wiring). Unwire the gate from the abstract BottleBackend
(provision orchestration drops the ssh step,
_validate_ssh_entries goes away) and from the Docker backend
(prepare/launch lose the `gate` kwarg, bottle_plan drops the
gate_plan field, dry-run JSON drops the ssh_hosts / ssh_gate
keys, y/N preflight drops the ssh-hosts block). cli/info now
prints declared git remotes instead of ssh hosts. pipelock's
docstring picks up the git-gate framing now that there's no
PRD-0007 boundary to call out.

BREAKING (dry-run JSON): the `ssh_hosts` and `ssh_gate` keys
are gone from `start --dry-run --format=json`. Consumers should
read `git_remotes` / `git_gate` instead.
- Delete tests/unit/test_ssh_gate.py and the fixture_with_ssh helpers.
- test_pipelock_yaml: drop the ssh-leak guard (structurally
  impossible now); the remaining tests switch to fixture_minimal.
- test_pipelock_allowlist: rewrite the union/dedup test to
  exercise an egress.allowlist that duplicates a baked default
  (the property the ssh-leak assertion was hitching onto).
- test_manifest_git: shadow-route assertion becomes a legacy-ssh-
  dies-with-hint assertion, since bottle.ssh is now parse-fail.
- test_orphan_cleanup: drop the SSHGate.stop idempotency check;
  pipelock equivalent stays.
- test_dry_run_plan: drop assertions on the removed ssh_hosts /
  ssh_gate keys.

52 unit tests pass.
docs: drop ssh from README/example, supersede PRD 0007 (PRD 0009)
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 21s
30d92bef48
- README architecture diagram drops the socat/ssh image box and
  the agent's ~/.ssh/config; the prose-bullets section drops the
  ssh image; the manifest example swaps `ssh:` for `git:` so
  someone copy-pasting it picks up the new shape.
- claude-bottle.example.json: `default` bottle's `"ssh": []` is
  gone (now just an empty bottle); the gitea-dev example already
  uses `git:` since the ExtraHosts work.
- PRD 0007 carries a "Superseded by PRD 0009" header at the top
  with a one-paragraph block explaining why; the file stays so
  the rationale of the prior design is still in-tree.
- git_gate.py: drop the now-stale shadow-route mention from a
  docstring (the validator went away in the manifest layer).
didericis merged commit 3d9103d5b5 into main 2026-05-13 00:01:01 -04:00
Sign in to join this conversation.