diff --git a/bot_bottle/db_store.py b/bot_bottle/db_store.py index 5d4bf37..8950401 100644 --- a/bot_bottle/db_store.py +++ b/bot_bottle/db_store.py @@ -11,6 +11,30 @@ except ImportError: 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, `/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): """Raised when the on-disk schema is behind the current migration list.""" @@ -56,4 +80,4 @@ class DbStore: pass -__all__ = ["DbStore", "DbVersionError"] +__all__ = ["DbStore", "DbVersionError", "HOST_DB_FILENAME", "host_db_path"] diff --git a/bot_bottle/orchestrator/registry.py b/bot_bottle/orchestrator/registry.py index b8655ae..be835c0 100644 --- a/bot_bottle/orchestrator/registry.py +++ b/bot_bottle/orchestrator/registry.py @@ -35,9 +35,8 @@ import time from dataclasses import dataclass from pathlib import Path -from ..db_store import DbStore +from ..db_store import DbStore, host_db_path from ..migrations import TableMigrations -from ..supervise_types import host_db_path # 256 bits of urandom, URL-safe — unguessable per-bottle identity token. IDENTITY_TOKEN_BYTES = 32 diff --git a/bot_bottle/supervise_types.py b/bot_bottle/supervise_types.py index 80af0ae..1e579f0 100644 --- a/bot_bottle/supervise_types.py +++ b/bot_bottle/supervise_types.py @@ -5,22 +5,28 @@ Proposal, Response, AuditEntry, and host_db_path without creating a circular import (supervise imports from queue_store/audit_store and vice-versa). -Patching bot_bottle_root on this module propagates through host_db_path -because host_db_path looks up bot_bottle_root via sys.modules[__name__] -at call time rather than capturing it at import time. +host_db_path / HOST_DB_FILENAME now live in db_store (shared DB infra) and +are re-exported here for the historical import path. db_store.host_db_path +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 import dataclasses -import sys import uuid from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path - -HOST_DB_FILENAME = "bot-bottle.db" +# host_db_path / HOST_DB_FILENAME now live in db_store (shared DB infra); +# 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_ALLOW = "egress-allow" @@ -47,19 +53,6 @@ def bot_bottle_root() -> Path: 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: value = raw.get(key) if not isinstance(value, str):