refactor(supervise): fold Supervise into the facade as Supervisor; drop dead flat-fallbacks
tracker-policy-pr / check-pr (pull_request) Successful in 8s
test / integration-docker (pull_request) Successful in 12s
test / unit (pull_request) Successful in 43s
lint / lint (push) Failing after 59s
test / integration-firecracker (pull_request) Successful in 3m16s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped

Move the `Supervise` lifecycle out of its own `orchestrator/supervisor/
supervise.py` and into the package `__init__`, renaming the class to
`Supervisor`. Callers now import it from `bot_bottle.orchestrator.supervisor`
alongside the queue surface it belongs with.

Remove the dead `try/except ImportError` flat-import fallbacks from the
package-only store modules (db_store, audit_store, config_store, queue_store)
and image_cache. Those fallbacks existed for when the store files were
flat-copied into the gateway; post-PRD-0070 the data plane never opens the DB,
so these modules are only ever imported as part of the package. The two gateway
data-plane files that may still be loaded flat (egress_addon_core,
git_gate_render) keep their fallbacks.

Full unit suite green (2251).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 14:24:06 -04:00
parent 44e2b5a897
commit 3e2cbcab88
11 changed files with 47 additions and 84 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ from ..git_gate import GitGate, GitGatePlan
from ..log import die from ..log import die
from ..manifest import Manifest, ManifestBottle from ..manifest import Manifest, ManifestBottle
from ..supervisor.plan import SupervisePlan from ..supervisor.plan import SupervisePlan
from ..orchestrator.supervisor.supervise import Supervise from ..orchestrator.supervisor import Supervisor
from . import BottleSpec from . import BottleSpec
@@ -102,7 +102,7 @@ def prepare_supervise(bottle: ManifestBottle, slug: str) -> SupervisePlan | None
return None return None
supervise_dir = supervise_state_dir(slug) supervise_dir = supervise_state_dir(slug)
supervise_dir.mkdir(parents=True, exist_ok=True) supervise_dir.mkdir(parents=True, exist_ok=True)
return Supervise().prepare(slug, supervise_dir) return Supervisor().prepare(slug, supervise_dir)
def merge_provision_env_vars(provision: AgentProvisionPlan) -> AgentProvisionPlan: def merge_provision_env_vars(provision: AgentProvisionPlan) -> AgentProvisionPlan:
-3
View File
@@ -5,10 +5,7 @@ from __future__ import annotations
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
try:
from .store.config_store import ConfigStore from .store.config_store import ConfigStore
except ImportError:
from config_store import ConfigStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
class StaleImageError(Exception): class StaleImageError(Exception):
@@ -6,16 +6,10 @@ import os
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
try:
from ...supervisor.types import Proposal, Response from ...supervisor.types import Proposal, Response
from ...paths import host_db_path from ...paths import host_db_path
from ...store.db_store import DbStore from ...store.db_store import DbStore
from ...store.migrations import TableMigrations from ...store.migrations import TableMigrations
except ImportError:
from supervisor.types import Proposal, Response # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from paths import host_db_path # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from db_store import DbStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from migrations import TableMigrations # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
class QueueStore(DbStore): class QueueStore(DbStore):
+27 -5
View File
@@ -1,10 +1,10 @@
"""The orchestrator's view of the supervise plane. """The orchestrator's view of the supervise plane.
Bundles the orchestrator-owned supervise surface: the queue/audit I/O and diff Bundles the orchestrator-owned supervise surface: the queue/audit I/O and diff
rendering (`queue`) and the `Supervise` lifecycle (`supervise`). For rendering (`queue`), and the `Supervisor` lifecycle (host-side database
convenience it re-exports the neutral vocabulary (`bot_bottle.supervisor`'s staging). For convenience it re-exports the neutral vocabulary
types + `SupervisePlan`) so orchestrator-side callers — service, CLI, tests — (`bot_bottle.supervisor`'s types + `SupervisePlan`) so orchestrator-side
import from one place. callers — service, CLI, tests — import from one place.
The data plane must NOT import this package; it imports `bot_bottle.supervisor` The data plane must NOT import this package; it imports `bot_bottle.supervisor`
(neutral) directly and reaches the queue over the control-plane RPC. (neutral) directly and reaches the queue over the control-plane RPC.
@@ -12,8 +12,12 @@ The data plane must NOT import this package; it imports `bot_bottle.supervisor`
from __future__ import annotations from __future__ import annotations
from abc import ABC
from pathlib import Path
from ...supervisor.types import * # noqa: F401,F403 — re-export the neutral vocabulary from ...supervisor.types import * # noqa: F401,F403 — re-export the neutral vocabulary
from ...supervisor.plan import SupervisePlan from ...supervisor.plan import SupervisePlan
from ..store.store_manager import StoreManager
from .queue import ( from .queue import (
archive_all_proposals, archive_all_proposals,
archive_proposal, archive_proposal,
@@ -31,4 +35,22 @@ from .queue import (
write_proposal, write_proposal,
write_response, write_response,
) )
from .supervise import Supervise
class Supervisor(ABC):
"""Per-bottle supervise lifecycle. Encapsulates host-side database staging;
the gateway's start/stop lifecycle is backend-specific.
`prepare` migrates the orchestrator's `bot-bottle.db` and returns the
neutral `SupervisePlan`. It touches the orchestrator store manager, so it
lives here rather than in the neutral `bot_bottle.supervisor` package; the
backend (which drives launch) may import it — backend → orchestrator is an
allowed direction, unlike gateway → orchestrator."""
def prepare(self, slug: str, stage_dir: Path) -> SupervisePlan:
"""Stage the host database. Returns the plan; `internal_network` must be
set by the launch step before .start runs."""
del stage_dir
mgr = StoreManager.instance()
mgr.migrate()
return SupervisePlan(slug=slug, db_path=mgr.db_path)
@@ -1,36 +0,0 @@
"""The `Supervise` lifecycle — host-side database staging (PRD 0013 / 0070).
`Supervise.prepare` migrates the orchestrator's `bot-bottle.db` and returns the
neutral `SupervisePlan`. It touches the orchestrator store manager, so it lives
here rather than in the neutral `bot_bottle.supervisor` package; the backend
(which drives launch) may import it — backend → orchestrator is an allowed
direction, unlike gateway → orchestrator.
"""
from __future__ import annotations
from abc import ABC
from pathlib import Path
from ..store.store_manager import StoreManager
from ...supervisor.plan import SupervisePlan
class Supervise(ABC):
"""Per-bottle supervise daemon. Encapsulates host-side database staging;
the gateway's start/stop lifecycle is backend-specific."""
def prepare(
self,
slug: str,
stage_dir: Path,
) -> SupervisePlan:
"""Stage the host database. Returns the plan; `internal_network` must be
set by the launch step before .start runs."""
del stage_dir
mgr = StoreManager.instance()
mgr.migrate()
return SupervisePlan(
slug=slug,
db_path=mgr.db_path,
)
-6
View File
@@ -5,16 +5,10 @@ from __future__ import annotations
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
try:
from ..supervisor.types import AuditEntry from ..supervisor.types import AuditEntry
from ..paths import host_db_path from ..paths import host_db_path
from .db_store import DbStore from .db_store import DbStore
from .migrations import TableMigrations from .migrations import TableMigrations
except ImportError:
from supervisor.types import AuditEntry # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from paths import host_db_path # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from db_store import DbStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from migrations import TableMigrations # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
class AuditStore(DbStore): class AuditStore(DbStore):
-5
View File
@@ -4,14 +4,9 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
try:
from .db_store import DbStore from .db_store import DbStore
from .migrations import TableMigrations from .migrations import TableMigrations
from ..paths import host_db_path from ..paths import host_db_path
except ImportError:
from db_store import DbStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from migrations import TableMigrations # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
from paths import host_db_path # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
DEFAULT_CACHED_IMAGE_STALE_WARNING_DAYS = 1 DEFAULT_CACHED_IMAGE_STALE_WARNING_DAYS = 1
-3
View File
@@ -6,10 +6,7 @@ import sqlite3
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
try:
from .migrations import TableMigrations from .migrations import TableMigrations
except ImportError:
from migrations import TableMigrations # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
class DbVersionError(Exception): class DbVersionError(Exception):
+1 -1
View File
@@ -11,6 +11,6 @@ tier's private code, importable by `bot_bottle.gateway` **and**
status / poll-status constants, and the shared daemon constants. status / poll-status constants, and the shared daemon constants.
* `plan` — `SupervisePlan`, the launch-time staging DTO. * `plan` — `SupervisePlan`, the launch-time staging DTO.
The orchestrator-only half (queue I/O, diff rendering, the `Supervise` The orchestrator-only half (queue I/O, diff rendering, the `Supervisor`
lifecycle) lives under `bot_bottle.orchestrator.supervisor`. lifecycle) lives under `bot_bottle.orchestrator.supervisor`.
""" """
+3 -3
View File
@@ -1,9 +1,9 @@
"""`SupervisePlan` — the launch-time supervise staging DTO. """`SupervisePlan` — the launch-time supervise staging DTO.
A pure value type (no store access), so it lives in the neutral package: the A pure value type (no store access), so it lives in the neutral package: the
backend builds it at launch and the orchestrator's `Supervise.prepare` returns backend builds it at launch and the orchestrator's `Supervisor.prepare` returns
it. The behaviour that fills it in — staging the host database — is the it. The behaviour that fills it in — staging the host database — is the
orchestrator's, in `bot_bottle.orchestrator.supervisor.supervise`. orchestrator's, in `bot_bottle.orchestrator.supervisor.Supervisor`.
""" """
from __future__ import annotations from __future__ import annotations
@@ -14,7 +14,7 @@ from pathlib import Path
@dataclass(frozen=True) @dataclass(frozen=True)
class SupervisePlan: class SupervisePlan:
"""Output of `Supervise.prepare`; consumed by `.start`. """Output of `Supervisor.prepare`; consumed by `.start`.
`db_path` is the host database bind-mounted into the gateway at `db_path` is the host database bind-mounted into the gateway at
/run/supervise/bot-bottle.db. `internal_network` is empty at prepare time; /run/supervise/bot-bottle.db. `internal_network` is empty at prepare time;
+1 -1
View File
@@ -352,7 +352,7 @@ class TestToolConstants(unittest.TestCase):
) )
class _StubSupervise(supervise.Supervise): class _StubSupervise(supervise.Supervisor):
"""Concrete Supervise subclass for testing the prepare template.""" """Concrete Supervise subclass for testing the prepare template."""
def start(self, plan): # type: ignore def start(self, plan): # type: ignore