Legacy NULL-scope dedupe only compared Gitea targets, so duplicate rows with the same provider key and issue but different fingerprint/status/ event_count/etc. collapsed to the lowest link_id and silently dropped observation data. Migration now compares all meaningful observation fields and refuses to discard conflicts (#619 RC3 / #613).
2.8 KiB
2.8 KiB
Control-plane DB substrate (#613)
Status: Implemented (SQLite single-writer MVP)
ADR: mcp-allocator-control-plane-observability-adr.md
Module: control_plane_db.py
Architecture statement
DB coordinates, Gitea records, Sentry/GlitchTip observe; the bridge is the only path that turns observations into Gitea work.
What this ships
| Capability | Notes |
|---|---|
| Schema | sessions, work_items, leases, assignments, terminal_locks, events, incident_links |
| Atomic assign+lease | ControlPlaneDB.assign_and_lease — one BEGIN IMMEDIATE transaction |
| Mutation gate | require_valid_assignment — live lease + allowed action + non-terminal work + non-stale head |
| Heartbeat / release / expire | Lease lifecycle helpers |
| Terminal-lock index | Routing signal for #600 (terminal path first) |
incident_links |
Provider-neutral link model for #612 — not assignable work; scope keys NULL-safe |
Hard rules (enforced in code)
- Assignable
work_items.kind∈ {issue,pr} only — never raw Sentry/GlitchTip incidents. - Two concurrent sessions cannot both receive an active assignment on the same open work item (second gets
wait). - Merged/closed work items return
no_safe_workat assign time, andrequire_valid_assignmentfails closed if the work item becomes terminal later. - Assignments pin
expected_head_sha; mutations fail closed if the work item head drifts. - SQLite path is the single-writer MVP (
GITEA_CONTROL_PLANE_DB, default under~/.cache/gitea-tools/control-plane/). Multi-session multi-host production requires Postgres or a single allocator daemon (ADR §6). incident_linksoptional scope fields are stored as empty strings (never NULL) so UNIQUE is canonical across minimal upserts.- Legacy
incident_linksmigration collapses NULL-scope duplicates only when Gitea targets and all meaningful observation metadata agree (fingerprint, status, event_count, permalink, timestamps, linked PRs, etc.). Conflicting metadata fails closed — no silent discard.
Dependency chain
#613 control-plane DB (this) → #600 allocator API → #612 incident bridge
- #600 must call this substrate (not file locks / comment-only leases alone) for completion.
- #612 must write
incident_linkshere and create Gitea issues; the allocator assigns those issues, not raw incidents.
Non-goals (intentionally deferred)
- Full
gitea_allocate_next_workrouting policy (REQUEST_CHANGES → author, etc.) — #600 - Sentry/GlitchTip provider adapters and auto-watchdog — #612
- Gitea comment/label mirror writers (may be added by allocator tooling later)
Tests
python3 -m pytest tests/test_control_plane_db.py -q