Print parity across backends #96

Closed
opened 2026-05-28 21:17:52 -04:00 by didericis · 3 comments
Owner

Problem

The y/N preflight print() method is declared @abstractmethod on BottlePlan but implemented independently on each backend. The two implementations produce different output for equivalent configurations.

After PRD 0038 (smolmachines env contract), the env_names divergence is resolved — resolved.forwarded is now merged into agent_provision.guest_env at prepare time on both backends, so the displayed env names are equivalent.

The remaining gaps are two rendering differences, both using the same underlying plan fields:

Field Docker smolmachines
git gate lines upstream_host:upstream_port from resolved git_gate_plan.upstreams Name → Upstream from manifest bottle.git
egress lines host [auth:scheme] host only (auth dropped)

The smolmachines docstring says "same shape as the Docker backend's" — that intent is real but not enforced by anything structural.

Proposed data-layer solution

Both backends already carry git_gate_plan: GitGatePlan and egress_plan: EgressPlan as fields. The fix is to hoist these (along with agent_provision and supervise_plan) to BottlePlan and implement print concretely there, eliminating the per-backend implementations.

  1. Hoist shared fields — move git_gate_plan, egress_plan, agent_provision, and supervise_plan from the concrete subclasses up to BottlePlan. Both backends already populate them at prepare time with the same types.

  2. Unify git display — use git_gate_plan.upstreams (the resolved plan) everywhere, dropping smolmachines's use of manifest-level bottle.git. Format: upstream_host:upstream_port, consistent with Docker today.

  3. Unify egress display — use the full [auth:scheme] annotation in both backends, since egress_plan.routes carries auth_scheme on both.

  4. Make print concrete — once the above is in place, remove @abstractmethod from BottlePlan.print and implement it there. The backends drop their own print methods entirely.

## Problem The y/N preflight `print()` method is declared `@abstractmethod` on `BottlePlan` but implemented independently on each backend. The two implementations produce different output for equivalent configurations. After PRD 0038 (smolmachines env contract), the env_names divergence is resolved — `resolved.forwarded` is now merged into `agent_provision.guest_env` at prepare time on both backends, so the displayed env names are equivalent. The remaining gaps are two rendering differences, both using the same underlying plan fields: | Field | Docker | smolmachines | |---|---|---| | git gate lines | `upstream_host:upstream_port` from resolved `git_gate_plan.upstreams` | `Name → Upstream` from manifest `bottle.git` | | egress lines | `host [auth:scheme]` | `host` only (auth dropped) | The smolmachines docstring says "same shape as the Docker backend's" — that intent is real but not enforced by anything structural. ## Proposed data-layer solution Both backends already carry `git_gate_plan: GitGatePlan` and `egress_plan: EgressPlan` as fields. The fix is to hoist these (along with `agent_provision` and `supervise_plan`) to `BottlePlan` and implement `print` concretely there, eliminating the per-backend implementations. 1. **Hoist shared fields** — move `git_gate_plan`, `egress_plan`, `agent_provision`, and `supervise_plan` from the concrete subclasses up to `BottlePlan`. Both backends already populate them at prepare time with the same types. 2. **Unify git display** — use `git_gate_plan.upstreams` (the resolved plan) everywhere, dropping smolmachines's use of manifest-level `bottle.git`. Format: `upstream_host:upstream_port`, consistent with Docker today. 3. **Unify egress display** — use the full `[auth:scheme]` annotation in both backends, since `egress_plan.routes` carries `auth_scheme` on both. 4. **Make `print` concrete** — once the above is in place, remove `@abstractmethod` from `BottlePlan.print` and implement it there. The backends drop their own `print` methods entirely.
didericis-claude was assigned by didericis 2026-05-28 21:18:12 -04:00
didericis added the Kind/Enhancement label 2026-05-28 21:18:19 -04:00
didericis-claude changed title from Consolidate print command for plans to Print parity across backends 2026-06-02 11:34:35 -04:00
Author
Owner

Is there a reason for the forwarded_env divergence? Gut tells me we should just be adding forwarded_env for smolmachines as well, but I don't remember the exact purpose of it.

Is there a reason for the `forwarded_env` divergence? Gut tells me we should just be adding `forwarded_env` for smolmachines as well, but I don't remember the exact purpose of it.
Collaborator

forwarded_env exists specifically to keep secret values off argv, off the env-file, and out of the compose YAML. In compose.py the service gets bare names only (env.append(name) with no =value), so Docker inherits the value from the compose-up process env — which holds forwarded_env via **plan.forwarded_env in compose_env. The OAuth token and any host-env-interpolated manifest values flow through that path.

smolmachines has no equivalent today: smolvm machine create -e K=V always puts the value on argv. That's the rank-1 gap from audit #134, filed as #135 and tracked as PRD 0038 (branch exists, not merged yet). So the asymmetry in print isn't a display bug we accidentally introduced — it reflects a real structural difference.

For the print fix, the _extra_env_keys() hook sidesteps the question cleanly: Docker overrides it to return forwarded_env.keys(), and smolmachines can override it once PRD 0038 lands and gives it an equivalent field (whatever that ends up being named). The base print stays ignorant of the mechanism either way. That said, it's worth confirming what 0038 actually settles on before wiring the hook — if smolmachines ends up with a forwarded_env field of its own, hoisting that to BottlePlan directly would be simpler than the hook.

`forwarded_env` exists specifically to keep secret values off argv, off the env-file, and out of the compose YAML. In `compose.py` the service gets bare names only (`env.append(name)` with no `=value`), so Docker inherits the value from the compose-up process env — which holds `forwarded_env` via `**plan.forwarded_env` in `compose_env`. The OAuth token and any host-env-interpolated manifest values flow through that path. smolmachines has no equivalent today: `smolvm machine create -e K=V` always puts the value on argv. That's the rank-1 gap from audit #134, filed as #135 and tracked as PRD 0038 (branch exists, not merged yet). So the asymmetry in `print` isn't a display bug we accidentally introduced — it reflects a real structural difference. For the print fix, the `_extra_env_keys()` hook sidesteps the question cleanly: Docker overrides it to return `forwarded_env.keys()`, and smolmachines can override it once PRD 0038 lands and gives it an equivalent field (whatever that ends up being named). The base `print` stays ignorant of the mechanism either way. That said, it's worth confirming what 0038 actually settles on before wiring the hook — if smolmachines ends up with a `forwarded_env` field of its own, hoisting that to `BottlePlan` directly would be simpler than the hook.
Collaborator

Correction to my previous comment: PRD 0038 was already merged via #141 (and is Active) — I was looking at a stale local clone. Apologies for the noise.

After that change, smolmachines prepare.py merges resolved.forwarded into guest_env before building agent_provision, so agent_provision.guest_env now includes the forwarded keys. The env_names divergence I flagged is largely already resolved by that work — the _extra_env_keys() hook I proposed is unnecessary.

The real remaining gaps are just the two formatting differences: git gate lines (Docker renders the resolved upstream_host:upstream_port from git_gate_plan.upstreams; smolmachines renders the manifest-level Name → Upstream from bottle.git) and egress lines (Docker includes [auth:scheme]; smolmachines drops it). Both use the same underlying plan fields (git_gate_plan and egress_plan), so unifying the rendering is straightforward once the fields are hoisted to BottlePlan.

Correction to my previous comment: PRD 0038 was already merged via #141 (and is Active) — I was looking at a stale local clone. Apologies for the noise. After that change, smolmachines `prepare.py` merges `resolved.forwarded` into `guest_env` before building `agent_provision`, so `agent_provision.guest_env` now includes the forwarded keys. The env_names divergence I flagged is largely already resolved by that work — the `_extra_env_keys()` hook I proposed is unnecessary. The real remaining gaps are just the two formatting differences: git gate lines (Docker renders the resolved `upstream_host:upstream_port` from `git_gate_plan.upstreams`; smolmachines renders the manifest-level `Name → Upstream` from `bottle.git`) and egress lines (Docker includes `[auth:scheme]`; smolmachines drops it). Both use the same underlying plan fields (`git_gate_plan` and `egress_plan`), so unifying the rendering is straightforward once the fields are hoisted to `BottlePlan`.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#96