From 244ad6a91428a8b2d863b99633159ddf226befd2 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 1 Jul 2026 21:45:08 +0000 Subject: [PATCH] refactor: extract QueueStore and AuditStore to their own modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves _QueueStore → bot_bottle/queue_store.py (public QueueStore) and _AuditStore → bot_bottle/audit_store.py (public AuditStore). Removes the public queue_db_path() function; QueueStore resolves the DB path via host_db_path() on the host, or via the SUPERVISE_DB_PATH env var in the sidecar container (internal mechanism, not public API). Adds queue_store.py and audit_store.py to Dockerfile.sidecars so the sidecar bundle picks them up. Updates __all__ in supervise.py. Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile.sidecars | 2 + bot_bottle/audit_store.py | 120 +++++++++++ bot_bottle/queue_store.py | 248 ++++++++++++++++++++++ bot_bottle/supervise.py | 330 +++--------------------------- tests/unit/test_supervise.py | 3 +- tests/unit/test_supervise_edge.py | 3 - 6 files changed, 394 insertions(+), 312 deletions(-) create mode 100644 bot_bottle/audit_store.py create mode 100644 bot_bottle/queue_store.py diff --git a/Dockerfile.sidecars b/Dockerfile.sidecars index ba13551..2b43ee1 100644 --- a/Dockerfile.sidecars +++ b/Dockerfile.sidecars @@ -66,6 +66,8 @@ COPY bot_bottle/egress_dlp_config.py /app/egress_dlp_config.py COPY bot_bottle/egress_addon.py /app/egress_addon.py COPY bot_bottle/dlp_detectors.py /app/dlp_detectors.py COPY bot_bottle/yaml_subset.py /app/yaml_subset.py +COPY bot_bottle/queue_store.py /app/queue_store.py +COPY bot_bottle/audit_store.py /app/audit_store.py COPY bot_bottle/supervise.py /app/supervise.py COPY bot_bottle/supervise_server.py /app/supervise_server.py COPY bot_bottle/sidecar_init.py /app/sidecar_init.py diff --git a/bot_bottle/audit_store.py b/bot_bottle/audit_store.py new file mode 100644 index 0000000..2682217 --- /dev/null +++ b/bot_bottle/audit_store.py @@ -0,0 +1,120 @@ +"""SQLite-backed audit store for supervise (PRD 0013).""" + +from __future__ import annotations + +import sqlite3 +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .supervise import AuditEntry + + +def _sv() -> object: + """Lazy import of supervise to avoid a circular-import at module init time. + Mirrors our own module identity so patches on supervise.bot_bottle_root + propagate correctly in both flat (sidecar / sys.path-injection tests) and + package contexts.""" + import sys + sv_name = "supervise" if __name__ == "audit_store" else "bot_bottle.supervise" + if sv_name in sys.modules: + return sys.modules[sv_name] + try: + import bot_bottle.supervise as _m + except ImportError: + import supervise as _m # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module + return _m + + +def _audit_entry_from_row(row: sqlite3.Row) -> AuditEntry: + m = _sv() + return m.AuditEntry( # type: ignore[attr-defined] + timestamp=row["timestamp"], + bottle_slug=row["bottle_slug"], + component=row["component"], + operator_action=row["operator_action"], + operator_notes=row["operator_notes"], + justification=row["justification"], + diff=row["diff"], + ) + + +def _host_db_path() -> Path: + return _sv().host_db_path() # type: ignore[attr-defined,no-any-return] + + +class AuditStore: + """SQLite-backed persistent store for supervise audit entries.""" + + def __init__(self, db_path: Path | None = None) -> None: + self.db_path = db_path or _host_db_path() + self.db_path.parent.mkdir(parents=True, exist_ok=True) + self._init() + + def write_audit_entry(self, entry: AuditEntry) -> Path: + with self._connect() as conn: + conn.execute( + """ + INSERT INTO supervise_audit_entries ( + timestamp, bottle_slug, component, operator_action, + operator_notes, justification, diff + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + entry.timestamp, + entry.bottle_slug, + entry.component, + entry.operator_action, + entry.operator_notes, + entry.justification, + entry.diff, + ), + ) + self._chmod() + return self.db_path + + def read_audit_entries(self, component: str, slug: str) -> list[AuditEntry]: + if not self.db_path.is_file(): + return [] + with self._connect() as conn: + rows = conn.execute( + """ + SELECT * FROM supervise_audit_entries + WHERE component = ? AND bottle_slug = ? + ORDER BY id + """, + (component, slug), + ).fetchall() + return [_audit_entry_from_row(row) for row in rows] + + def _connect(self) -> sqlite3.Connection: + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + return conn + + def _init(self) -> None: + with self._connect() as conn: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS supervise_audit_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL, + bottle_slug TEXT NOT NULL, + component TEXT NOT NULL, + operator_action TEXT NOT NULL, + operator_notes TEXT NOT NULL, + justification TEXT NOT NULL, + diff TEXT NOT NULL + ) + """ + ) + self._chmod() + + def _chmod(self) -> None: + try: + self.db_path.chmod(0o600) + except OSError: + pass + + +__all__ = ["AuditStore"] diff --git a/bot_bottle/queue_store.py b/bot_bottle/queue_store.py new file mode 100644 index 0000000..1ede262 --- /dev/null +++ b/bot_bottle/queue_store.py @@ -0,0 +1,248 @@ +"""SQLite-backed queue store for supervise proposals and responses (PRD 0013).""" + +from __future__ import annotations + +import os +import sqlite3 +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .supervise import Proposal, Response + + +def _sv() -> object: + """Lazy import of supervise to avoid a circular-import at module init time. + By the time any QueueStore method is called, both modules are fully loaded. + + Mirrors our own module identity: when we are 'queue_store' (sidecar flat + context or tests that inject bot_bottle/ into sys.path) we use the flat + 'supervise' module so that patches on supervise.bot_bottle_root propagate + correctly. When we are 'bot_bottle.queue_store' we use 'bot_bottle.supervise'.""" + import sys + sv_name = "supervise" if __name__ == "queue_store" else "bot_bottle.supervise" + if sv_name in sys.modules: + return sys.modules[sv_name] + try: + import bot_bottle.supervise as _m + except ImportError: + import supervise as _m # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module + return _m + + +def _proposal_from_row(row: sqlite3.Row) -> Proposal: + m = _sv() + return m.Proposal( # type: ignore[attr-defined] + id=row["id"], + bottle_slug=row["bottle_slug"], + tool=row["tool"], + proposed_file=row["proposed_file"], + justification=row["justification"], + arrival_timestamp=row["arrival_timestamp"], + current_file_hash=row["current_file_hash"], + ) + + +def _response_from_row(row: sqlite3.Row) -> Response: + m = _sv() + return m.Response( # type: ignore[attr-defined] + proposal_id=row["proposal_id"], + status=row["status"], + notes=row["notes"], + final_file=row["final_file"], + ) + + +def _host_db_path() -> Path: + return _sv().host_db_path() # type: ignore[attr-defined,no-any-return] + + +class QueueStore: + """SQLite-backed persistent store for supervise proposals and responses.""" + + def __init__(self, queue_key: str, db_path: Path | None = None) -> None: + self.queue_key = queue_key + if db_path is not None: + self.db_path = db_path + else: + # In the sidecar container SUPERVISE_DB_PATH points at the + # bind-mounted host DB. On the host this env var is never set, + # so we always fall through to host_db_path(). + env_path = os.environ.get("SUPERVISE_DB_PATH", "").strip() + self.db_path = Path(env_path) if env_path else _host_db_path() + self.db_path.parent.mkdir(parents=True, exist_ok=True) + self._init() + + def write_proposal(self, proposal: Proposal) -> Path: + with self._connect() as conn: + conn.execute( + """ + INSERT OR REPLACE INTO supervise_proposals ( + queue_key, id, bottle_slug, tool, proposed_file, justification, + arrival_timestamp, current_file_hash, archived + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0) + """, + ( + self.queue_key, + proposal.id, + proposal.bottle_slug, + proposal.tool, + proposal.proposed_file, + proposal.justification, + proposal.arrival_timestamp, + proposal.current_file_hash, + ), + ) + self._chmod() + return self.db_path + + def read_proposal(self, proposal_id: str) -> Proposal: + with self._connect() as conn: + row = conn.execute( + """ + SELECT * FROM supervise_proposals + WHERE queue_key = ? AND id = ? AND archived = 0 + """, + (self.queue_key, proposal_id), + ).fetchone() + if row is None: + raise FileNotFoundError(proposal_id) + return _proposal_from_row(row) + + def list_pending_proposals(self) -> list[Proposal]: + if not self.db_path.is_file(): + return [] + with self._connect() as conn: + rows = conn.execute( + """ + SELECT p.* FROM supervise_proposals p + WHERE p.archived = 0 + AND p.queue_key = ? + AND NOT EXISTS ( + SELECT 1 FROM supervise_responses r + WHERE r.queue_key = p.queue_key + AND r.proposal_id = p.id + AND r.archived = 0 + ) + ORDER BY p.arrival_timestamp, p.id + """, + (self.queue_key,), + ).fetchall() + return [_proposal_from_row(row) for row in rows] + + def list_all_pending_proposals(self) -> list[Proposal]: + if not self.db_path.is_file(): + return [] + with self._connect() as conn: + rows = conn.execute( + """ + SELECT p.* FROM supervise_proposals p + WHERE p.archived = 0 + AND NOT EXISTS ( + SELECT 1 FROM supervise_responses r + WHERE r.queue_key = p.queue_key + AND r.proposal_id = p.id + AND r.archived = 0 + ) + ORDER BY p.arrival_timestamp, p.id + """ + ).fetchall() + return [_proposal_from_row(row) for row in rows] + + def write_response(self, response: Response) -> Path: + with self._connect() as conn: + conn.execute( + """ + INSERT OR REPLACE INTO supervise_responses ( + queue_key, proposal_id, status, notes, final_file, archived + ) VALUES (?, ?, ?, ?, ?, 0) + """, + ( + self.queue_key, + response.proposal_id, + response.status, + response.notes, + response.final_file, + ), + ) + self._chmod() + return self.db_path + + def read_response(self, proposal_id: str) -> Response: + with self._connect() as conn: + row = conn.execute( + """ + SELECT * FROM supervise_responses + WHERE queue_key = ? AND proposal_id = ? AND archived = 0 + """, + (self.queue_key, proposal_id), + ).fetchone() + if row is None: + raise FileNotFoundError(proposal_id) + return _response_from_row(row) + + def archive_proposal(self, proposal_id: str) -> None: + if not self.db_path.is_file(): + return + with self._connect() as conn: + conn.execute( + """ + UPDATE supervise_proposals SET archived = 1 + WHERE queue_key = ? AND id = ? + """, + (self.queue_key, proposal_id), + ) + conn.execute( + """ + UPDATE supervise_responses SET archived = 1 + WHERE queue_key = ? AND proposal_id = ? + """, + (self.queue_key, proposal_id), + ) + + def _connect(self) -> sqlite3.Connection: + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + return conn + + def _init(self) -> None: + with self._connect() as conn: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS supervise_proposals ( + queue_key TEXT NOT NULL, + id TEXT NOT NULL, + bottle_slug TEXT NOT NULL, + tool TEXT NOT NULL, + proposed_file TEXT NOT NULL, + justification TEXT NOT NULL, + arrival_timestamp TEXT NOT NULL, + current_file_hash TEXT NOT NULL, + archived INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (queue_key, id) + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS supervise_responses ( + queue_key TEXT NOT NULL, + proposal_id TEXT NOT NULL, + status TEXT NOT NULL, + notes TEXT NOT NULL, + final_file TEXT, + archived INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (queue_key, proposal_id) + ) + """ + ) + self._chmod() + + def _chmod(self) -> None: + try: + self.db_path.chmod(0o600) + except OSError: + pass + + +__all__ = ["QueueStore"] diff --git a/bot_bottle/supervise.py b/bot_bottle/supervise.py index 6605bb3..ba00b50 100644 --- a/bot_bottle/supervise.py +++ b/bot_bottle/supervise.py @@ -33,8 +33,6 @@ from __future__ import annotations import dataclasses import difflib import hashlib -import os -import sqlite3 import time import uuid from abc import ABC @@ -109,11 +107,6 @@ def host_db_path() -> Path: return bot_bottle_root() / HOST_DB_FILENAME -def queue_db_path() -> Path: - env_path = os.environ.get("SUPERVISE_DB_PATH", "").strip() - return Path(env_path) if env_path else host_db_path() - - # --- Dataclasses ----------------------------------------------------------- @@ -226,37 +219,46 @@ class AuditEntry: return dataclasses.asdict(self) +try: + from .queue_store import QueueStore + from .audit_store import AuditStore +except ImportError: + # Sidecar bundle: files are flat-copied under /app, not a package. + from queue_store import QueueStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module + from audit_store import AuditStore # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module + + # --- Queue I/O ------------------------------------------------------------- def write_proposal(proposal: Proposal) -> Path: """Persist `proposal` in the queue database, mode 0o600. Directory is created if missing.""" - return _QueueStore(proposal.bottle_slug).write_proposal(proposal) + return QueueStore(proposal.bottle_slug).write_proposal(proposal) def read_proposal(bottle_slug: str, proposal_id: str) -> Proposal: - return _QueueStore(bottle_slug).read_proposal(proposal_id) + return QueueStore(bottle_slug).read_proposal(proposal_id) def list_pending_proposals(bottle_slug: str) -> list[Proposal]: """All proposals for `bottle_slug` that do not yet have a matching response. Sorted by `arrival_timestamp` so the operator sees the queue FIFO.""" - return _QueueStore(bottle_slug).list_pending_proposals() + return QueueStore(bottle_slug).list_pending_proposals() def list_all_pending_proposals() -> list[Proposal]: """All pending proposals across bottles, sorted FIFO.""" - return _QueueStore("").list_all_pending_proposals() + return QueueStore("").list_all_pending_proposals() def write_response(bottle_slug: str, response: Response) -> Path: - return _QueueStore(bottle_slug).write_response(response) + return QueueStore(bottle_slug).write_response(response) def read_response(bottle_slug: str, proposal_id: str) -> Response: - return _QueueStore(bottle_slug).read_response(proposal_id) + return QueueStore(bottle_slug).read_response(proposal_id) def wait_for_response( @@ -272,7 +274,7 @@ def wait_for_response( natural shape, since the operator's response time is unbounded. Polls SQLite so the implementation stays portable and stdlib-only.""" - store = _QueueStore(bottle_slug) + store = QueueStore(bottle_slug) while True: try: return store.read_response(proposal_id) @@ -286,7 +288,7 @@ def wait_for_response( def archive_proposal(bottle_slug: str, proposal_id: str) -> None: """Mark both proposal and response rows processed. Idempotent — missing rows are silently skipped.""" - _QueueStore(bottle_slug).archive_proposal(proposal_id) + QueueStore(bottle_slug).archive_proposal(proposal_id) # --- Audit log ------------------------------------------------------------- @@ -294,12 +296,12 @@ def archive_proposal(bottle_slug: str, proposal_id: str) -> None: def write_audit_entry(entry: AuditEntry) -> Path: """Append `entry` to the host supervise audit table.""" - return _AuditStore().write_audit_entry(entry) + return AuditStore().write_audit_entry(entry) def read_audit_entries(component: str, slug: str) -> list[AuditEntry]: """Load all audit entries for the given component+slug.""" - return _AuditStore().read_audit_entries(component, slug) + return AuditStore().read_audit_entries(component, slug) # --- Diff rendering -------------------------------------------------------- @@ -325,293 +327,6 @@ def sha256_hex(content: str) -> str: return hashlib.sha256(content.encode("utf-8")).hexdigest() -# --- SQLite storage -------------------------------------------------------- - - -class _QueueStore: - def __init__(self, queue_key: str) -> None: - self.queue_key = queue_key - self.db_path = queue_db_path() - self.db_path.parent.mkdir(parents=True, exist_ok=True) - self._init() - - def write_proposal(self, proposal: Proposal) -> Path: - with self._connect() as conn: - conn.execute( - """ - INSERT OR REPLACE INTO supervise_proposals ( - queue_key, id, bottle_slug, tool, proposed_file, justification, - arrival_timestamp, current_file_hash, archived - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0) - """, - ( - self.queue_key, - proposal.id, - proposal.bottle_slug, - proposal.tool, - proposal.proposed_file, - proposal.justification, - proposal.arrival_timestamp, - proposal.current_file_hash, - ), - ) - self._chmod() - return self.db_path - - def read_proposal(self, proposal_id: str) -> Proposal: - with self._connect() as conn: - row = conn.execute( - """ - SELECT * FROM supervise_proposals - WHERE queue_key = ? AND id = ? AND archived = 0 - """, - (self.queue_key, proposal_id), - ).fetchone() - if row is None: - raise FileNotFoundError(proposal_id) - return _proposal_from_row(row) - - def list_pending_proposals(self) -> list[Proposal]: - if not self.db_path.is_file(): - return [] - with self._connect() as conn: - rows = conn.execute( - """ - SELECT p.* FROM supervise_proposals p - WHERE p.archived = 0 - AND p.queue_key = ? - AND NOT EXISTS ( - SELECT 1 FROM supervise_responses r - WHERE r.queue_key = p.queue_key - AND r.proposal_id = p.id - AND r.archived = 0 - ) - ORDER BY p.arrival_timestamp, p.id - """, - (self.queue_key,), - ).fetchall() - return [_proposal_from_row(row) for row in rows] - - def list_all_pending_proposals(self) -> list[Proposal]: - if not self.db_path.is_file(): - return [] - with self._connect() as conn: - rows = conn.execute( - """ - SELECT p.* FROM supervise_proposals p - WHERE p.archived = 0 - AND NOT EXISTS ( - SELECT 1 FROM supervise_responses r - WHERE r.queue_key = p.queue_key - AND r.proposal_id = p.id - AND r.archived = 0 - ) - ORDER BY p.arrival_timestamp, p.id - """ - ).fetchall() - return [_proposal_from_row(row) for row in rows] - - def write_response(self, response: Response) -> Path: - with self._connect() as conn: - conn.execute( - """ - INSERT OR REPLACE INTO supervise_responses ( - queue_key, proposal_id, status, notes, final_file, archived - ) VALUES (?, ?, ?, ?, ?, 0) - """, - ( - self.queue_key, - response.proposal_id, - response.status, - response.notes, - response.final_file, - ), - ) - self._chmod() - return self.db_path - - def read_response(self, proposal_id: str) -> Response: - with self._connect() as conn: - row = conn.execute( - """ - SELECT * FROM supervise_responses - WHERE queue_key = ? AND proposal_id = ? AND archived = 0 - """, - (self.queue_key, proposal_id), - ).fetchone() - if row is None: - raise FileNotFoundError(proposal_id) - return _response_from_row(row) - - def archive_proposal(self, proposal_id: str) -> None: - if not self.db_path.is_file(): - return - with self._connect() as conn: - conn.execute( - """ - UPDATE supervise_proposals SET archived = 1 - WHERE queue_key = ? AND id = ? - """, - (self.queue_key, proposal_id), - ) - conn.execute( - """ - UPDATE supervise_responses SET archived = 1 - WHERE queue_key = ? AND proposal_id = ? - """, - (self.queue_key, proposal_id), - ) - - def _connect(self) -> sqlite3.Connection: - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row - return conn - - def _init(self) -> None: - with self._connect() as conn: - conn.execute( - """ - CREATE TABLE IF NOT EXISTS supervise_proposals ( - queue_key TEXT NOT NULL, - id TEXT NOT NULL, - bottle_slug TEXT NOT NULL, - tool TEXT NOT NULL, - proposed_file TEXT NOT NULL, - justification TEXT NOT NULL, - arrival_timestamp TEXT NOT NULL, - current_file_hash TEXT NOT NULL, - archived INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (queue_key, id) - ) - """ - ) - conn.execute( - """ - CREATE TABLE IF NOT EXISTS supervise_responses ( - queue_key TEXT NOT NULL, - proposal_id TEXT NOT NULL, - status TEXT NOT NULL, - notes TEXT NOT NULL, - final_file TEXT, - archived INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (queue_key, proposal_id) - ) - """ - ) - self._chmod() - - def _chmod(self) -> None: - try: - self.db_path.chmod(0o600) - except OSError: - pass - - -class _AuditStore: - def __init__(self, db_path: Path | None = None) -> None: - self.db_path = db_path or host_db_path() - self.db_path.parent.mkdir(parents=True, exist_ok=True) - self._init() - - def write_audit_entry(self, entry: AuditEntry) -> Path: - with self._connect() as conn: - conn.execute( - """ - INSERT INTO supervise_audit_entries ( - timestamp, bottle_slug, component, operator_action, - operator_notes, justification, diff - ) VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - entry.timestamp, - entry.bottle_slug, - entry.component, - entry.operator_action, - entry.operator_notes, - entry.justification, - entry.diff, - ), - ) - self._chmod() - return self.db_path - - def read_audit_entries(self, component: str, slug: str) -> list[AuditEntry]: - if not self.db_path.is_file(): - return [] - with self._connect() as conn: - rows = conn.execute( - """ - SELECT * FROM supervise_audit_entries - WHERE component = ? AND bottle_slug = ? - ORDER BY id - """, - (component, slug), - ).fetchall() - return [_audit_entry_from_row(row) for row in rows] - - def _connect(self) -> sqlite3.Connection: - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row - return conn - - def _init(self) -> None: - with self._connect() as conn: - conn.execute( - """ - CREATE TABLE IF NOT EXISTS supervise_audit_entries ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL, - bottle_slug TEXT NOT NULL, - component TEXT NOT NULL, - operator_action TEXT NOT NULL, - operator_notes TEXT NOT NULL, - justification TEXT NOT NULL, - diff TEXT NOT NULL - ) - """ - ) - self._chmod() - - def _chmod(self) -> None: - try: - self.db_path.chmod(0o600) - except OSError: - pass - - -def _proposal_from_row(row: sqlite3.Row) -> Proposal: - return Proposal( - id=row["id"], - bottle_slug=row["bottle_slug"], - tool=row["tool"], - proposed_file=row["proposed_file"], - justification=row["justification"], - arrival_timestamp=row["arrival_timestamp"], - current_file_hash=row["current_file_hash"], - ) - - -def _response_from_row(row: sqlite3.Row) -> Response: - return Response( - proposal_id=row["proposal_id"], - status=row["status"], - notes=row["notes"], - final_file=row["final_file"], - ) - - -def _audit_entry_from_row(row: sqlite3.Row) -> AuditEntry: - return AuditEntry( - timestamp=row["timestamp"], - bottle_slug=row["bottle_slug"], - component=row["component"], - operator_action=row["operator_action"], - operator_notes=row["operator_notes"], - justification=row["justification"], - diff=row["diff"], - ) - - # --- Sidecar plan + abstract lifecycle ------------------------------------- @@ -642,8 +357,8 @@ class Supervise(ABC): must be set by the launch step before .start runs.""" del stage_dir db_path = host_db_path() - _QueueStore(slug) - _AuditStore(db_path) + QueueStore(slug) + AuditStore(db_path) return SupervisePlan( slug=slug, db_path=db_path, @@ -662,10 +377,12 @@ def _require_str(raw: dict[str, object], key: str) -> str: __all__ = [ "ACTION_OPERATOR_EDIT", "AuditEntry", + "AuditStore", "COMPONENT_FOR_TOOL", "DEFAULT_POLL_INTERVAL_SEC", "DB_PATH_IN_CONTAINER", "Proposal", + "QueueStore", "Response", "STATUSES", "STATUS_APPROVED", @@ -690,7 +407,6 @@ __all__ = [ "host_db_path", "list_pending_proposals", "list_all_pending_proposals", - "queue_db_path", "read_audit_entries", "read_proposal", "read_response", diff --git a/tests/unit/test_supervise.py b/tests/unit/test_supervise.py index 88a594d..f41ceba 100644 --- a/tests/unit/test_supervise.py +++ b/tests/unit/test_supervise.py @@ -20,7 +20,6 @@ from bot_bottle.supervise import ( archive_proposal, host_db_path, list_pending_proposals, - queue_db_path, read_audit_entries, read_proposal, read_response, @@ -132,7 +131,7 @@ class TestQueueIO(unittest.TestCase): p = _proposal() path = write_proposal(p) self.assertTrue(path.exists()) - self.assertEqual(queue_db_path(), path) + self.assertEqual(host_db_path(), path) self.assertEqual(0o600, path.stat().st_mode & 0o777) loaded = read_proposal(self.slug, p.id) self.assertEqual(p, loaded) diff --git a/tests/unit/test_supervise_edge.py b/tests/unit/test_supervise_edge.py index 87fb289..421c5ba 100644 --- a/tests/unit/test_supervise_edge.py +++ b/tests/unit/test_supervise_edge.py @@ -38,9 +38,6 @@ class TestPathHelpers(unittest.TestCase): def test_bot_bottle_root(self) -> None: self.assertTrue(str(supervise.bot_bottle_root()).endswith(".bot-bottle")) - def test_queue_db_path_is_host_db_path(self) -> None: - self.assertEqual(supervise.host_db_path(), supervise.queue_db_path()) - class TestReadMalformed(unittest.TestCase): def test_read_proposal_missing_row(self) -> None: