mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-18 06:06:24 +00:00
GREEN for the invariant test added in the prior commit. Resolves the dead-on- arrival blocker introduced by PR #738: the server opens SQLite mode=ro after #1283/#1289, so the `confirm=true` path of `/api/admin/prune-geo-filter` would fail in production with "attempt to write a readonly database". The author's tests only passed because setupTestDB uses `:memory:` without mode=ro. ## Architecture: file-marker queue (chosen over IPC/HTTP proxy) Server → marker file → ingestor → result file → server. New package `internal/prunequeue` defines the on-disk protocol: <dir(dbPath)>/prune-requests/request-<id>.json (written by server) <dir(dbPath)>/prune-requests/result-<id>.json (written by ingestor) Atomic via os.Rename. The ingestor removes the request file as part of writing the result, so the directory naturally drains. Why file markers over an internal localhost HTTP socket: * No new listener / port / auth surface to manage. * Inherits the same backup/permissions story as the SQLite file — they share a directory and a deployment. * Survives ingestor restarts: pending requests are processed on next boot. * Trivial to inspect / clear with `ls` / `trash` during ops. * Matches the existing maintenance-ticker pattern in cmd/ingestor/main.go. ## HTTP surface (unchanged for clients in spirit) POST /api/admin/prune-geo-filter * dry-run (default): same response shape as before — preview list. * confirm=true: now returns 202 Accepted + {requestId, statusUrl, count, nodes}. Body must still carry the pubkeys snapshot from the preview (TOCTOU guard preserved — the ingestor honors the list verbatim, does NOT re-evaluate geo_filter membership). GET /api/admin/prune-geo-filter/status?id=<id> (NEW, requires API key) * pending: 200 {requestId, status:"pending"} * done: 200 {requestId, status:"done", deleted, requestedAt, completedAt} * error: 200 {requestId, status:"error", error, ...} * 404 when neither marker nor result exist. * 400 for invalid ids (path-traversal guard: hex/[0-9a-f] only, ≤64 chars). ## TOCTOU + cascade-cleanup from prior CR * TOCTOU: server snapshots the preview list at confirm time and writes the PRUNED list (intersection of preview + still-outside) into the marker. The ingestor does NOT re-check geo_filter — it executes exactly what the operator confirmed. * Cascade cleanup: ingestor's DeleteNodesByPubkeys chunks under SQLite's variable limit (500 per stmt). It deliberately limits scope to the `nodes` table; the schema has no FKs, so observation/transmission rows are left for the regular packet-retention ticker. Documented in-line. ## Read-only invariant test Extends cmd/server/readonly_invariant_test.go (TestServerDBHasNoWriteMethods) to forbid `DeleteNodesByPubkeys` on the server `*DB`. With this commit the method is gone, so the test passes: $ go test -run TestServerDBHasNoWriteMethods ./... ok github.com/corescope/server 0.190s ## Tests * cmd/server/routes_test.go: rewrote the confirm-test to assert 202 + marker file existence; added pending→done status-roundtrip test; added 404 + path-traversal-id rejection tests. Old TestDeleteNodesByPubkeys removed (the method now lives in cmd/ingestor). * cmd/ingestor/prune_geofilter_test.go: end-to-end queue test (request file on disk → RunPendingPruneRequests → result file + DB rows actually deleted), plus empty-queue no-op. * internal/prunequeue/prunequeue_test.go: round-trip, path-traversal rejection, missing-result returns (nil, nil). ## Files * internal/prunequeue/{prunequeue.go,prunequeue_test.go,go.mod} — new package * cmd/ingestor/prune_geofilter.go — Store.DeleteNodesByPubkeys + RunPendingPruneRequests * cmd/ingestor/prune_geofilter_test.go — queue end-to-end test * cmd/ingestor/main.go — startup pass + 15s ticker for the queue, Stop on shutdown * cmd/server/db.go — DeleteNodesByPubkeys removed; only GetNodesForGeoPrune (read) remains * cmd/server/routes.go — handlePruneGeoFilter rewritten to enqueue; handlePruneGeoFilterStatus added; route registered * cmd/server/routes_test.go — new tests; old direct-DELETE coverage removed