Luci · gitignore audit: `*.db` + `mission-control/` (whole d
Luci
gitignore audit: `*.db` + `mission-control/` (whole dir, separate repo) both exclude mc.db. Already covered. But mc.db lives in `mission-control/` which is its own repo. Check that repo's gitignore + where DB init happens.
Luci
Key findings:
- mc.db gitignored in mission-control repo (lines 11-13). ✓ Already covered.
- `email.db` **tracked** despite being in gitignore (line 9) — pre-existing leak. Note for later.
- mc.db is 402MB in `mission-control/` — a **separate repo**, not part of this workspace worktree.
Now find DB init/startup path + any existing schema versioning.
Luci
Big finding: schema versioning **already exists** — `models.verify_schema_version()`, `models.EXPECTED_SCHEMA_VERSION`, PRAGMA user_version with a migration ladder (currently v16), extensive regression tests. The ticket's premise ("schema version untracked, silent drift") appears already solved. Also `email.db` is an **empty** 0-byte placeholder (blob e69de29) — no data leak.
Let me confirm models.py: the guard, version, and whether it runs on startup.
Luci
EXPECTED_SCHEMA_VERSION = 20. Full guard exists. Let me read the actual implementation to understand what it does and find where it's invoked at startup.
Luci
The entire ticket appears already implemented. The one real risk: `verify_schema_version()` defined but maybe **not wired at startup** (my grep for non-test callers returned empty). That would be the actual gap. Verify the call site.
Luci
Confirmed gap: `verify_schema_version()` is **defined and tested but never called in production code** — only test files and comments reference it. The guard exists but isn't wired at startup. Let me see how app.py and migrate.py boot to find the right wiring point.
Luci
migrate.py also only references it in comments. The guard is dead code. Let me read app.py's startup block and migrate.py's main to wire it correctly.
Luci
Need to know how MC actually starts (direct `python app.py` vs gunicorn import) to wire the guard at the right place.
Luci
Critical: production runs via **gunicorn `app:app`** — the `__main__` block never executes. The guard must run at **module import time** so gunicorn import fails on mismatch. But 10+ tests `import app`. Need to check how tests set up the DB path to avoid breaking them.
Luci
Everything lines up:
- Live mc.db = v20 = EXPECTED. Guard wiring safe for prod now.
- conftest redirects `MC_DB_PATH` to a fresh safe DB before importing app → guard sees no tickets table → returns True. Tests won't break.
- `email.db` = empty 0-byte tracked file despite `*.db`/`email.db` ignore → untrack it (the gitignore-audit deliverable).
Read app.py top to place the import-time guard.
Luci
Place: module-level call after the import block. Design is sound — idempotent, honors `MC_SKIP_SCHEMA_CHECK`, prod gunicorn import enforces it. Let me read conftest to mirror the test DB pattern for my regression test.
Luci
conftest confirms safety. Now implement. Three changes in the mission-control repo: (1) wire the guard at app.py import, (2) regression test, (3) untrack empty `email.db`.
Luci
Now the regression test:
Luci
Now run the regression tests + untrack email.db:
Luci
"No tests collected" — likely a collection/import error masked by rtk. Run raw.
Luci
5 behaviour tests pass. Static test regex needs MULTILINE — `^` matched whole-string start. Fix:
Luci
All green. app imports cleanly (guard passes on live v20). Run a wider slice of app-importing tests to confirm the import-time guard broke nothing.
Luci
6 new tests pass, 53 app-importing tests pass, email.db untracked, live import clean. Now Tier 2 council review — dispatch one Claude subagent on the diff (per dev-loop 2.6; full 5-model council is overkill for ~15-line wiring of an existing, tested guard).
Luci
Reviewer: ship after fixing Major #1 — a root-level test file that imports app outside conftest. Let me verify the actual risk (pytest.ini testpaths + that file's import order).
Luci
`pytest.ini testpaths = tests` → the root-level `test_mc3803` file is **excluded from the normal suite**. And its `import app` is **deferred inside setUp (line 73), after** `models.DB_PATH = self.db_path` (line 71) — so the guard already fires against the test DB, correct ordering. Reviewer assumed a module-top import; it's actually deferred. Let me confirm no module-top `import app` in that file.
Luci
Confirmed: no module-top `import app` — only the deferred one at line 73, after DB_PATH is set. Reviewer's Major #1 doesn't apply (assumed module-top import). The file is also excluded by `testpaths=tests`. Verify empirically that it still passes standalone with the guard live:
Luci
Major #1 empirically refuted (passes standalone). Major #2 is a deploy-runbook note. Let me confirm how `user_version` gets bumped on deploy (migrations) and whether a schema doc exists to add the escape-hatch note.
Luci
Both Majors resolved:
- **#1** empirically refuted (passes standalone; import is deferred post-DB_PATH-set; excluded by testpaths).
- **#2** already handled — `migrations/020` bumps `user_version=20` via `migrate.py`, and the guard's own RuntimeError message already tells the operator to run `migrate.py` or set `MC_SKIP_SCHEMA_CHECK=1`. Deploy order (migrate → restart) is the existing path; the guard only fires when that order is skipped, which is the intended catch.
Verdict: CLEAN to ship. Commit + push in the mission-control repo.
Luci
`git rm --cached email.db` already staged the deletion (`D email.db`). app.py + test staged. The other modified files (models.py, ticket_runtime.py, runtime_health_probe.py, mc4458 plan) belong to **other work** — leave them unstaged, commit only my three. Index currently holds exactly: `M app.py`, `D email.db`, `A test`.
Luci
Committed + pushed to mission-control master. Verify the commit contains only my three files:
Luci
Clean commit — exactly my 3 files, no bleed from other in-flight work. Pushed.
The running gunicorn was started before this change, so the guard isn't live yet. Code is edited in-place at the path gunicorn serves; a restart applies it. Safe to self-approve (controller safety rule): live mc.db is v20 = EXPECTED and `import app` already verified clean, so boot will succeed. Restart + confirm health.
Luci
MC restarted — `active`, HTTP 200, no schema errors. Guard now live in production, passing against v20 mc.db. Clean boot.
Report DONE via mc-coord.
Luci
DONE: MC-4456 complete.
**Audit result — premise mostly already solved, one real gap closed:**
1. **`.gitignore` audit** ✓ — `mc.db` already excluded twice: `mission-control/.gitignore` lines 11-13 (`mc.db`, `mc.db-*`, `mc.db.*`) + workspace `*.db`. Found one leak: `email.db` (empty 0-byte file) tracked despite being ignored → `git rm --cached` (file kept on disk).
2. **schema_version table** ✓ — already existed via canonical SQLite `PRAGMA user_version` (`EXPECTED_SCHEMA_VERSION=20`, migration ladder, `verify_schema_version()`). No new table needed.
3. **Startup migration guard** — **the actual bug**: `verify_schema_version()` was defined + unit-tested but **never called in production** (dead code). MC runs as gunicorn `app:app`, so the `__main__` block never executes. Wired the guard at app.py **module-import scope** — mismatch raises `RuntimeError`, gunicorn import fails, MC refuses to serve a drifted DB. Legacy v0 auto-stamps forward; `MC_SKIP_SCHEMA_CHECK=1` bypasses.
**Evidence:** `tests/test_mc4456_schema_guard.py` (6/6 pass) pins the wiring (static check it's at module scope, not in the skipped `__main__`) + guard behaviour. 53 app-importing tests unaffected. Council review (CLEAN — both raised Majors refuted/pre-handled). Restarted `luci-dashboard.service` → active, HTTP 200, guard live against v20. Commit `f94989e` pushed to mission-control master.
{"mc_signal":"DONE","ticket":"MC-4456","nonce":"MC_TICKET_TURN:4460:1780141953862:e1f59bec","payload":"Wired mc.db schema-version guard at gunicorn import (was dead code), untracked empty email.db, gitignore already excluded mc.db; 6 regression tests pass, MC restarted clean, committed+pushed f94989e"}