4.7 KiB
PRD 0044: Print Parity Across Backends
- Status: Draft
- Author: didericis-claude
- Created: 2026-06-02
- Issue: #96
Summary
Hoist git_gate_plan, egress_plan, agent_provision, and supervise_plan
from the concrete BottlePlan subclasses up to BottlePlan, and implement
print concretely there. This eliminates the two per-backend output divergences
and ensures any future backend gets correct preflight rendering for free.
Problem
BottlePlan.print is @abstractmethod, so each backend provides its own
implementation. The two current implementations have drifted:
| 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 so operators see one format across backends" — that intent is real but nothing enforces it.
The env_names divergence previously noted in this issue was resolved by PRD 0038
(smolmachines env contract): resolved.forwarded is now merged into
agent_provision.guest_env at prepare time on both backends, so displayed env
names are equivalent.
Goals / Success Criteria
BottlePlancarriesgit_gate_plan,egress_plan,agent_provision, andsupervise_planas concrete fields; subclasses no longer declare them independently.BottlePlan.printis a concrete method; subclasses have noprintimplementation of their own.- Both backends render git gate lines as
name → upstream_host:upstream_port(usinggit_gate_plan.upstreams), not the manifest-level URL. - Both backends render egress lines as
host [auth:scheme](dropping the annotation only whenauth_schemeis empty). - Unit tests assert the unified output for both backends from a single shared test helper.
Non-goals
- No changes to the Docker or smolmachines launch, prepare, or cleanup paths.
- No changes to how env values are resolved or injected (that is PRD 0038).
- No changes to the manifest schema or
GitEntry. - No new CLI flags or dashboard changes.
Scope
In scope:
bot_bottle/backend/__init__.py— addgit_gate_plan,egress_plan,agent_provision, andsupervise_planfields toBottlePlan; replace@abstractmethod printwith a concrete implementation.bot_bottle/backend/docker/bottle_plan.py— remove the four hoisted fields and theprintmethod.bot_bottle/backend/smolmachines/bottle_plan.py— remove the four hoisted fields and theprintmethod.tests/unit/— add or update tests asserting unified preflight output; a shared helper can build a minimal plan fixture for each backend and assert the same lines appear.
Out of scope:
- Changes to
bot_bottle/backend/print_util.pybeyond what the newprintimplementation requires. - Changes to
BottleCleanupPlan.printor any other print method. - Integration tests that launch a real bottle.
Design
Move the four fields that both concrete subclasses already declare —
git_gate_plan: GitGatePlan, egress_plan: EgressPlan,
agent_provision: AgentProvisionPlan, supervise_plan: SupervisePlan | None
— up to BottlePlan. Both backends' prepare paths already produce these with
the same types, so no prepare-time changes are needed.
Replace the @abstractmethod print with a concrete implementation on
BottlePlan that:
- Builds
env_namesfrombottle.env.keys() | agent_provision.guest_env.keys()filtered throughagent_provision.hidden_env_names. - Builds git gate lines from
git_gate_plan.upstreamsasf"{u.name} → {u.upstream_host}:{u.upstream_port}". - Builds egress lines from
egress_plan.routesasf"{r.host} [auth:{r.auth_scheme}]"whenr.auth_schemeis non-empty, elser.host. - Renders the standard two-column preflight block (leading blank line, agent, provider, env, skills, bottle, git identity, git gate, egress, trailing blank line).
Docker's forwarded_env keys are already merged into agent_provision.guest_env
via the agent_provision_plan builder, so no special handling is needed for
env_names.
Testing Strategy
- Add a shared fixture builder (e.g.
make_plan(backend)) in a new or existing unit test module that constructs a minimalDockerBottlePlanandSmolmachinesBottlePlanfrom the same spec and plan fields. - Assert that
plan.print(remote_control=False)produces identical git gate and egress lines for both backends given the samegit_gate_planandegress_plan. - Test the
auth_schemeannotation: present when non-empty, absent otherwise. - Test git gate rendering:
name → host:portformat.
Run:
python3 -m unittest discover -s tests/unit
Open Questions
None.