Skip to content

test: refuse SQLite connections into a checkout's data/ directory (#355) - #972

Open
ziomik wants to merge 1 commit into
bradbrok:mainfrom
ziomik:fix/355-isolate-tests-from-prod-dbs
Open

test: refuse SQLite connections into a checkout's data/ directory (#355)#972
ziomik wants to merge 1 commit into
bradbrok:mainfrom
ziomik:fix/355-isolate-tests-from-prod-dbs

Conversation

@ziomik

@ziomik ziomik commented Aug 1, 2026

Copy link
Copy Markdown

Closes #355.

⚠️ This diff is not test-only

The test guard was the goal, but writing it surfaced production code that ignored the configured data directory. Three files under src/ change behaviour as a result. Please review those hunks as production changes, not as test scaffolding.

The guard

Running pytest from a live deployment let stores fall back to their relative default db_path and open the production databases. tests/conftest.py adds a session-scoped autouse fixture that wraps sqlite3.connect and raises when the resolved path lands under <cwd>/data or <repo root>/data.

Both roots are checked because they can differ: running pytest from a live deployment against another checkout's tests resolves the stores' relative defaults against the deployment's data/. The helper handles str/bytes/PathLike, file:...?mode=ro URIs, :memory: and mode=memory.

Known limitation: the guard patches sqlite3.connect in-process, so it does not cover subprocesses.

tests/test_conftest_db_guard.py (new) covers the guard itself with 8 cases.

Production bugs it found

  • api.pyVoiceStore had db_path="data/voice_calls.db" hardcoded, and UserProfileStore was constructed with no argument at all. Both now derive their directory from the configured db_path, keeping the original basename so existing production files are not orphaned.
  • agent_registry.py (build_system_prompt) and dream_runner.py (two call sites) — same no-argument UserProfileStore().

That single omission was the cause of all 341 test_api.py failures under the guard: the profile store is read while building the system prompt and written by the dream runner, so every request path touched it. In a deployment these call sites were reading and writing whatever data/user_profiles.db was relative to the process CWD rather than the configured data directory.

A static sweep confirms no other store in src/ is instantiated without an explicit db_path. The ~23 relative defaults on the store classes themselves remain, but every caller now passes a path.

Tests

tests/test_daemon.py's two remaining cases built a bare DaemonConfig() (so working_dir="."); they now pass working_dir=tmp_path like the rest of the class.

Full suite from an isolated worktree: 4574 passed, 2 skipped, 0 failed. ruff check clean on all touched files.

Out of scope

Stores still on WAL rather than rollback journalling: pinky_identity (×2), pinky_hub, pinky_federation, pinky_memory (×2).

🤖 Opened by Engineer

Running pytest from a live deployment let stores fall back to their
relative default db_path and open the *production* databases. Add a
session-scoped autouse guard in conftest that wraps sqlite3.connect and
raises when the resolved path lands under <cwd>/data or <repo root>/data.
(Limitation: it does not cover subprocesses.)

The guard surfaced production code — not tests — ignoring the configured
data directory:

- api.py: VoiceStore had db_path="data/voice_calls.db" hardcoded and
  UserProfileStore was constructed with no argument; both now derive
  their directory from the configured db_path, keeping the basename so
  existing production files are not orphaned.
- agent_registry.build_system_prompt and dream_runner (two call sites)
  likewise passed no db_path to UserProfileStore. That single omission
  was the cause of all 341 test_api.py failures under the guard: the
  profile store was read while building the system prompt and written by
  the dream runner.

test_daemon's two remaining cases built a bare DaemonConfig(); they now
pass working_dir=tmp_path like the rest of the class.

Suite: 4574 passed, 2 skipped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant