3c0d2fb66f
Addresses review on #519 (@didericis 6143 + the fail-hard direction over the codex skip). Base owns the flow (6143 / ADR 0006). `attach_bottled_agents_to_gateway()` is now a concrete method on `BottleBackend` that delegates to `gateway_attach.reconcile_running_bottles`; backends override only three primitives — `_gateway_attach_resources()`, `_running_bottles()`, `_attach_bottle_to_gateway()`. The shared control flow + error policy live in one place so a backend can't drift onto a bespoke loop or a silent skip. Keeps the reconcile flow + `GatewayAttachResources` out of `backend/base.py` (the size guardrail) in a dedicated `backend/gateway_attach.py`. New ADR 0006 records the "shared behaviour in the base backend, subclasses override primitives" theme. Fail hard, never skip (the maintainer's direction over the codex review's skip). Any attach failure now aborts bring-up instead of being logged and skipped: a bottle that silently can't reach the fresh gateway (its egress just starts failing TLS) is worse than a loud failure. Every bottle is attempted and the failures are raised together (aggregate `InfraLaunchError`) so one bring-up surfaces the full blast radius. Resource gathering (the CA fetch) also fails hard. PRD 0081 goal + design updated to match (reversed from the earlier "tolerate per-bottle failures" draft). Bumps the base.py guardrail cap 580->600 for the new contract surface (the delegator + 3 abstract primitives); the flow itself lives in gateway_attach.py. Refs #516, #519. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.9 KiB
Markdown
66 lines
2.9 KiB
Markdown
# ADR 0006: Shared behaviour lives in the base backend
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-07-27
|
|
- **Deciders:** didericis
|
|
|
|
## Context
|
|
|
|
`BottleBackend` has three concrete implementations (Firecracker, docker,
|
|
macOS-container). Cross-backend operations can be expressed two ways:
|
|
|
|
1. **Abstract method per backend** — the base declares `@abstractmethod foo()`
|
|
and each backend writes its own `foo()` end to end.
|
|
2. **Template method** — the base owns the *control flow* of `foo()` as a
|
|
concrete method and each backend overrides small **primitives** it calls.
|
|
|
|
Form 1 gives each backend total freedom, which is exactly the problem: the three
|
|
implementations drift. The bring-up reconcile (PRD 0081) first shipped as form 1
|
|
and immediately diverged — each backend re-implemented "gather CA → list running
|
|
bottles → push to each", and one backend's copy silently skipped failures while
|
|
another's aborted, an inconsistency caught only in review (#519). The behaviour
|
|
that must be identical (the loop, the error policy, the ordering) was duplicated
|
|
in the place least likely to stay in sync.
|
|
|
|
## Decision
|
|
|
|
Prefer the template-method form for cross-backend behaviour: **encode shared
|
|
control flow and policy in the base backend; backends override primitives, not
|
|
control flow.** Lean toward *less* freedom in the concrete backends and *more*
|
|
consistency in the base.
|
|
|
|
Concretely, a cross-backend operation on `BottleBackend` should be a concrete
|
|
method that:
|
|
|
|
- owns the sequencing, iteration, and error policy (e.g. fail-hard vs.
|
|
best-effort, fail-fast vs. attempt-all-then-aggregate); and
|
|
- delegates only the irreducibly backend-specific steps to `@abstractmethod`
|
|
primitives with narrow, well-typed signatures.
|
|
|
|
The first application is `attach_bottled_agents_to_gateway()` (PRD 0081): the
|
|
base gathers resources once, attempts every running bottle, and raises an
|
|
aggregate `InfraLaunchError` if any failed; backends supply only
|
|
`_gateway_attach_resources()`, `_running_bottles()`, and
|
|
`_attach_bottle_to_gateway()`.
|
|
|
|
A backend-specific `@abstractmethod` with no shared control flow (e.g.
|
|
`_launch_impl`, `enumerate_active`) stays as form 1 — this ADR is about
|
|
operations whose *shape* should be uniform, not about banning abstract methods.
|
|
|
|
## Consequences
|
|
|
|
- The error policy, ordering, and loop for a cross-backend operation have one
|
|
source of truth; a new backend cannot forget them or diverge.
|
|
- New backends implement smaller, more focused primitives instead of re-deriving
|
|
a whole flow.
|
|
- The base carries more logic and needs generic-enough primitive signatures
|
|
(e.g. a backend-specific attach-target type parameter on `BottleBackend`).
|
|
- Genuinely backend-specific one-offs still use a plain abstract method; the
|
|
guidance is a default, not an absolute.
|
|
|
|
## Links
|
|
|
|
- PRD 0081 (`docs/prds/0081-reprovision-gateway-state-on-bringup.md`).
|
|
- Review that prompted this: PR #519.
|
|
- `bot_bottle/backend/base.py` (`BottleBackend.attach_bottled_agents_to_gateway`).
|