refactor: move host_db_path/HOST_DB_FILENAME to db_store (#352)
lint / lint (push) Successful in 1m58s
test / unit (pull_request) Successful in 57s
test / integration (pull_request) Successful in 16s
test / coverage (pull_request) Successful in 1m0s

host_db_path is shared DB infrastructure, not supervise-specific, so its
canonical home is db_store (alongside DbStore). It resolves bot_bottle_root
from supervise_types lazily inside the function — no load-time cycle, and a
monkey-patch of supervise_types.bot_bottle_root still propagates.
supervise_types re-exports both names for the historical import path
(queue_store/audit_store unchanged); the orchestrator registry now imports
from db_store. Drops the now-unused `import sys` from supervise_types.

Behavior-preserving: full unit suite unchanged (only the pre-existing
/bin/sleep sidecar-init errors remain); monkeypatch propagation verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
2026-07-13 13:36:19 -04:00
parent b174442c60
commit b4b4e08f62
3 changed files with 38 additions and 22 deletions
+25 -1
View File
@@ -11,6 +11,30 @@ except ImportError:
from migrations import TableMigrations # 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
# The single shared host state DB. All bot-bottle SQLite stores (supervise
# queue, audit, the orchestrator registry) co-tenant this one file — the
# TableMigrations schema_key namespaces each store's tables.
HOST_DB_FILENAME = "bot-bottle.db"
def host_db_path() -> Path:
"""Path to the shared host state DB, `<bot_bottle_root>/db/bot-bottle.db`.
Kept in its own `db/` subdirectory (not directly under the root) so a
backend that can only bind-mount *directories* can share this one file
with a sidecar without exposing the root's other contents (git-gate
keys, per-bottle state, ...).
Resolves `bot_bottle_root` from `supervise_types` at call time (a lazy
import — avoids a load-time cycle, and lets a monkey-patch of
`supervise_types.bot_bottle_root` propagate here)."""
try:
from . import supervise_types as _st
except ImportError: # flat imports inside the sidecar bundle
import supervise_types as _st # type: ignore[import-not-found,no-redef] # pylint: disable=import-error,no-name-in-module
return _st.bot_bottle_root() / "db" / HOST_DB_FILENAME
class DbVersionError(Exception): class DbVersionError(Exception):
"""Raised when the on-disk schema is behind the current migration list.""" """Raised when the on-disk schema is behind the current migration list."""
@@ -56,4 +80,4 @@ class DbStore:
pass pass
__all__ = ["DbStore", "DbVersionError"] __all__ = ["DbStore", "DbVersionError", "HOST_DB_FILENAME", "host_db_path"]
+1 -2
View File
@@ -35,9 +35,8 @@ import time
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from ..db_store import DbStore from ..db_store import DbStore, host_db_path
from ..migrations import TableMigrations from ..migrations import TableMigrations
from ..supervise_types import host_db_path
# 256 bits of urandom, URL-safe — unguessable per-bottle identity token. # 256 bits of urandom, URL-safe — unguessable per-bottle identity token.
IDENTITY_TOKEN_BYTES = 32 IDENTITY_TOKEN_BYTES = 32
+12 -19
View File
@@ -5,22 +5,28 @@ Proposal, Response, AuditEntry, and host_db_path without creating a
circular import (supervise imports from queue_store/audit_store and circular import (supervise imports from queue_store/audit_store and
vice-versa). vice-versa).
Patching bot_bottle_root on this module propagates through host_db_path host_db_path / HOST_DB_FILENAME now live in db_store (shared DB infra) and
because host_db_path looks up bot_bottle_root via sys.modules[__name__] are re-exported here for the historical import path. db_store.host_db_path
at call time rather than capturing it at import time. resolves bot_bottle_root from this module lazily, so patching
supervise_types.bot_bottle_root still propagates to the DB path.
""" """
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
import sys
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
# host_db_path / HOST_DB_FILENAME now live in db_store (shared DB infra);
HOST_DB_FILENAME = "bot-bottle.db" # re-exported here for the historical import path. db_store resolves
# bot_bottle_root from this module lazily, so patching
# supervise_types.bot_bottle_root still propagates.
try:
from .db_store import HOST_DB_FILENAME, host_db_path
except ImportError: # flat imports inside the sidecar bundle
from db_store import HOST_DB_FILENAME, host_db_path # type: ignore[import-not-found,no-redef] # pylint: disable=import-error,no-name-in-module
TOOL_EGRESS_BLOCK = "egress-block" TOOL_EGRESS_BLOCK = "egress-block"
TOOL_EGRESS_ALLOW = "egress-allow" TOOL_EGRESS_ALLOW = "egress-allow"
@@ -47,19 +53,6 @@ def bot_bottle_root() -> Path:
return Path.home() / ".bot-bottle" return Path.home() / ".bot-bottle"
def host_db_path() -> Path:
# Look up bot_bottle_root through this module's live namespace so that
# monkey-patches on supervise_types.bot_bottle_root take effect.
#
# Lives in its own "db" subdirectory, not directly under
# bot_bottle_root(), so backends that can only bind-mount
# directories (macos_container's `container run --mount`, which
# rejects file sources with "is not a directory") can share this
# one file with a sidecar without also exposing bot_bottle_root()'s
# other contents (git-gate keys, per-bottle state, etc.).
return sys.modules[__name__].bot_bottle_root() / "db" / HOST_DB_FILENAME
def _require_str(raw: dict[str, object], key: str) -> str: def _require_str(raw: dict[str, object], key: str) -> str:
value = raw.get(key) value = raw.get(key)
if not isinstance(value, str): if not isinstance(value, str):