feat(smolmachines): rewrite Smolfile to smolvm 0.8.0 schema + drop gvproxy (PRD 0023 chunk 2a) #64

Merged
didericis merged 1 commits from prd-0023-chunk-2a-smolfile-rewrite into main 2026-05-27 04:08:33 -04:00
Owner

Summary

First sub-PR of chunk 2: rewrite the Smolfile renderer chunk 1 shipped (it emitted the gvproxy-era shape that smolvm doesn't accept) and drop the dead gvproxy code. Net: −410 lines.

What changed

Smolfile renderer now emits smolvm 0.8.0's actual schema:

env = ["HTTPS_PROXY=http://192.168.50.2:8888", ...]

[network]
allow_cidrs = ["192.168.50.2/32"]

That's it. image / entrypoint / cmd come from the .smolmachine artifact built in chunk 2b; cpus / memory stay at smolvm defaults until the manifest grows knobs for them.

Tests round-trip the output through stdlib tomllib and explicitly assert no --outbound-localhost-only leaks (the TSI flag the PRD's design pivot specifically rejected) and no leftover unixgram / gvproxy keys from the abandoned design.

util.py:

  • smolmachines_gvproxy_subnetsmolmachines_bundle_subnet, returning (subnet, gateway, bundle_ip). Bundle always lands at .2; gateway at .1. Third octet from sha256(slug) % 254, skipping 17 to avoid the docker-default bridge collision.
  • allocate_loopback_port: deleted. The bundle gets a pinned docker IP now; no per-bottle loopback ports.
  • smolmachines_preflight: only checks smolvm on PATH (no more gvproxy).

prepare.py:

  • No more gvproxy YAML or socket. Just derive subnet → bundle_ip and write the Smolfile.
  • Agent env uses IP literals (http://<bundle-ip>:8888) since the guest will have no DNS resolver inside TSI's allowlist.

bottle_plan.py: swap the gvproxy fields for bundle_subnet / bundle_gateway / bundle_ip / smolfile_path. The .smolmachine artifact path lands in chunk 2b.

Deleted:

  • claude_bottle/backend/smolmachines/gvproxy_config.py (101 lines).
  • tests/unit/test_smolmachines_gvproxy_config.py (117 lines).

Test status

516 unit tests passing (was 532; deleted 17 gvproxy-config + old smolfile cases, added 13 new for the rewritten renderer + util).

What's left in chunk 2

  • 2b: smolvm.py subprocess wrapper (machine create / start / exec / cp / stop / delete + pack create).
  • 2c: sidecar_bundle.py (per-bottle docker bridge with pinned IP, bundle bringup + teardown).
  • 2d: launch.py end-to-end wiring (bundle → VM via --from --smolfile, SmolmachinesBottle.exec via smolvm machine exec) + integration smoke + localhost-reach probe + egress-port-bypass probe.
## Summary First sub-PR of chunk 2: rewrite the Smolfile renderer chunk 1 shipped (it emitted the gvproxy-era shape that smolvm doesn't accept) and drop the dead gvproxy code. Net: **−410 lines**. ## What changed **Smolfile renderer** now emits smolvm 0.8.0's actual schema: ``` env = ["HTTPS_PROXY=http://192.168.50.2:8888", ...] [network] allow_cidrs = ["192.168.50.2/32"] ``` That's it. `image` / `entrypoint` / `cmd` come from the `.smolmachine` artifact built in chunk 2b; `cpus` / `memory` stay at smolvm defaults until the manifest grows knobs for them. Tests round-trip the output through stdlib `tomllib` and explicitly assert no `--outbound-localhost-only` leaks (the TSI flag the PRD's design pivot specifically rejected) and no leftover `unixgram` / `gvproxy` keys from the abandoned design. **util.py:** - `smolmachines_gvproxy_subnet` → `smolmachines_bundle_subnet`, returning `(subnet, gateway, bundle_ip)`. Bundle always lands at `.2`; gateway at `.1`. Third octet from `sha256(slug) % 254`, skipping `17` to avoid the docker-default bridge collision. - `allocate_loopback_port`: deleted. The bundle gets a pinned docker IP now; no per-bottle loopback ports. - `smolmachines_preflight`: only checks `smolvm` on PATH (no more `gvproxy`). **prepare.py:** - No more gvproxy YAML or socket. Just derive subnet → bundle_ip and write the Smolfile. - Agent env uses IP literals (`http://<bundle-ip>:8888`) since the guest will have no DNS resolver inside TSI's allowlist. **bottle_plan.py:** swap the gvproxy fields for `bundle_subnet` / `bundle_gateway` / `bundle_ip` / `smolfile_path`. The `.smolmachine` artifact path lands in chunk 2b. **Deleted:** - `claude_bottle/backend/smolmachines/gvproxy_config.py` (101 lines). - `tests/unit/test_smolmachines_gvproxy_config.py` (117 lines). ## Test status 516 unit tests passing (was 532; deleted 17 gvproxy-config + old smolfile cases, added 13 new for the rewritten renderer + util). ## What's left in chunk 2 - **2b:** `smolvm.py` subprocess wrapper (`machine create / start / exec / cp / stop / delete` + `pack create`). - **2c:** `sidecar_bundle.py` (per-bottle docker bridge with pinned IP, bundle bringup + teardown). - **2d:** `launch.py` end-to-end wiring (bundle → VM via `--from --smolfile`, `SmolmachinesBottle.exec` via `smolvm machine exec`) + integration smoke + localhost-reach probe + egress-port-bypass probe.
didericis added 1 commit 2026-05-27 04:01:29 -04:00
feat(smolmachines): rewrite Smolfile to smolvm 0.8.0 schema + drop gvproxy (PRD 0023 chunk 2a)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 39s
c73d717f71
First sub-PR of chunk 2: rewrite the renderer chunk 1 shipped to
match smolvm 0.8.0's actual Smolfile shape, delete the dead
gvproxy renderer + its tests, simplify the prepare flow now that
there's no gvproxy socket + no loopback-port allocation.

Smolfile renderer:
- Old shape (under the abandoned gvproxy design): name = ...,
  command = [...], [[net]] attachment = "unixgram",
  socket = "...".
- New shape (smolvm 0.8.0): env = [...] (sorted K=V pairs),
  [network] allow_cidrs = ["<bundle-ip>/32"]. Nothing else.
  image / entrypoint / cmd come from the .smolmachine artifact
  built in chunk 2b; cpus / memory left at smolvm defaults.
- Tests assert no leakage of TSI's --outbound-localhost-only or
  the old gvproxy/unixgram keys.

util.py:
- smolmachines_gvproxy_subnet → smolmachines_bundle_subnet,
  returning (subnet, gateway, bundle_ip). bundle_ip is always
  at .2 (gateway .1); subnet is /24, third octet derived from
  the slug hash, skipping the docker-default 17 to avoid the
  common 192.168.17.x collision.
- allocate_loopback_port: deleted. The bundle gets a pinned
  docker IP now; the agent dials that IP directly through TSI.
- smolmachines_preflight: dropped the gvproxy check; only
  smolvm is required.

prepare.py:
- Drops the gvproxy.yaml render + the loopback port allocation
  + the gvproxy_socket field on the plan.
- Derives subnet / gateway / bundle_ip from the slug and
  populates the new SmolmachinesBottlePlan fields.
- Agent env now uses IP-literal URLs (http://<bundle-ip>:8888
  etc) since the guest will have no DNS resolver inside TSI's
  allowlist.

bottle_plan.py:
- Old fields: gvproxy_config_path, gvproxy_socket,
  gvproxy_subnet, gvproxy_gateway, host_port_map.
- New fields: bundle_subnet, bundle_gateway, bundle_ip,
  smolfile_path. (smolmachine artifact path lands in chunk 2b.)

Net: -410 lines. Full unit suite: 516 passing.

The VM lifecycle + bundle bringup + launch wiring + smoke tests
land in chunk 2b.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis merged commit bd4b9de9e6 into main 2026-05-27 04:08:33 -04:00
didericis deleted branch prd-0023-chunk-2a-smolfile-rewrite 2026-05-27 04:08:40 -04:00
Sign in to join this conversation.