Commit Graph

1504 Commits

Author SHA1 Message Date
Kpa-clawbot c53af5cf66 ci: update frontend-coverage.json [skip ci] 2026-05-01 15:17:30 +00:00
Kpa-clawbot 9f606600e2 ci: update e2e-tests.json [skip ci] 2026-05-01 15:17:28 +00:00
Kpa-clawbot 053aef1994 fix(spa): decouple navigate() from theme fetch + add data-loaded sync attributes (#955) (#958)
## Summary

Fixes the chained async init race identified in RCA #3 of #955.

`navigate()` (which dispatches page handlers and fetches data) was gated
behind `/api/config/theme` resolving via `.finally()`. Tests use
`waitUntil: 'domcontentloaded'` which returns BEFORE theme fetch
resolves, creating a race condition where 3+ serial network requests
must complete before any DOM rows appear.

## Changes

### Decouple navigate() from theme fetch (public/app.js)
- Move `navigate()` call out of the theme fetch `.finally()` block
- Call it immediately on DOMContentLoaded — theme is purely cosmetic and
applies in parallel

### Add data-loaded sync attributes (public/nodes.js, map.js,
packets.js)
- Set `data-loaded="true"` on the container element after each page's
data fetch resolves and DOM renders
- Nodes: set on `#nodesLeft` after `loadNodes()` renders rows
- Map: set on `#leaflet-map` after `renderMarkers()` completes
- Packets: set on `#pktLeft` after `loadPackets()` renders rows

### Update E2E tests (test-e2e-playwright.js)
- Add `await page.waitForSelector('[data-loaded="true"]', { timeout:
15000 })` before row/marker assertions
- Increase map marker timeout from 3s to 8s as additional safety margin
- Tests now synchronize on data readiness rather than racing DOM
appearance

## Verification

- Spun up local server on port 13586 with e2e-fixture.db
- Confirmed navigate() is called immediately (not gated on theme)
- Confirmed data-loaded attributes are present in served JS
- API returns data correctly (2 nodes from fixture)

Closes #955 (RCA #3)

Co-authored-by: you <you@example.com>
2026-05-01 08:07:08 -07:00
Kpa-clawbot 7aef3c355c fix(ci): freshen fixture timestamps before E2E to avoid time-based filter exclusion (#955) (#957)
## Problem

The E2E fixture DB (`test-fixtures/e2e-fixture.db`) has static
timestamps from March 29, 2026. The map page applies a default
`lastHeard=30d` filter, so once the fixture ages past 30 days all nodes
are excluded from `/api/nodes?lastHeard=30d` — causing the "Map page
loads with markers" test to fail deterministically.

This started blocking all CI on ~April 28, 2026 (30 days after March
29).

Closes #955 (RCA #1: time-based fixture rot)

## Fix

Added `tools/freshen-fixture.sh` — a small script that shifts all
`last_seen`/`first_seen` timestamps forward so the newest is near
`now()`, preserving relative ordering between nodes. Runs in CI before
the Go server starts. Does **not** modify the checked-in fixture (no
binary blob churn).

## Verification

```
$ cp test-fixtures/e2e-fixture.db /tmp/fix4.db
$ bash tools/freshen-fixture.sh /tmp/fix4.db
Fixture timestamps freshened in /tmp/fix4.db
nodes: min=2026-05-01T07:10:00Z max=2026-05-01T14:51:33Z

$ ./corescope-server -port 13585 -db /tmp/fix4.db -public public &
$ curl -s "http://localhost:13585/api/nodes?limit=200&lastHeard=30d" | jq '{total, count: (.nodes | length)}'
{
  "total": 200,
  "count": 200
}
```

All 200 nodes returned with the 30-day filter after freshening (vs 0
without the fix).

Co-authored-by: you <you@example.com>
2026-05-01 08:06:19 -07:00
Kpa-clawbot 9ac484607f ci: update go-server-coverage.json [skip ci] 2026-05-01 15:05:20 +00:00
Kpa-clawbot b562de32ff ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 15:05:19 +00:00
Kpa-clawbot 6f0c58c94a ci: update frontend-tests.json [skip ci] 2026-05-01 15:05:18 +00:00
Kpa-clawbot 7d1c679f4f ci: update frontend-coverage.json [skip ci] 2026-05-01 15:05:17 +00:00
Kpa-clawbot ead08c721d ci: update e2e-tests.json [skip ci] 2026-05-01 15:05:16 +00:00
Kpa-clawbot 57e272494d feat(server): /api/healthz readiness endpoint gated on store load (#955) (#956)
## Summary

Fixes RCA #2 from #955: the HTTP listener and `/api/stats` go live
before background goroutines (pickBestObservation, neighbor graph build)
finish, causing CI readiness checks to pass prematurely.

## Changes

1. **`cmd/server/healthz.go`** — New `GET /api/healthz` endpoint:
- Returns `503 {"ready":false,"reason":"loading"}` while background init
is running
   - Returns `200 {"ready":true,"loadedTx":N,"loadedObs":N}` once ready

2. **`cmd/server/main.go`** — Added `sync.WaitGroup` tracking
pickBestObservation and neighbor graph build goroutines. A coordinator
goroutine sets `readiness.Store(1)` when all complete.
`backfillResolvedPathsAsync` is NOT gated (async by design, can take 20+
min).

3. **`cmd/server/routes.go`** — Wired `/api/healthz` before system
endpoints.

4. **`.github/workflows/deploy.yml`** — CI wait-for-ready loop now polls
`/api/healthz` instead of `/api/stats`.

5. **`cmd/server/healthz_test.go`** — Tests for 503-before-ready,
200-after-ready, JSON shape, and anti-tautology gate.

## Rule 18 Verification

Built and ran against `test-fixtures/e2e-fixture.db` (499 tx):
- With the small fixture DB, init completes in <300ms so both immediate
and delayed curls return 200
- Unit tests confirm 503 behavior when `readiness=0` (simulating slow
init)
- On production DBs with 100K+ txs, the 503 window would be 5-15s
(pickBestObservation processes in 5000-tx chunks with 10ms yields)

## Test Results

```
=== RUN   TestHealthzNotReady    --- PASS
=== RUN   TestHealthzReady       --- PASS  
=== RUN   TestHealthzAntiTautology --- PASS
ok  github.com/corescope/server  19.662s (full suite)
```

Co-authored-by: you <you@example.com>
2026-05-01 07:55:57 -07:00
Kpa-clawbot d870a693d0 ci: update go-server-coverage.json [skip ci] 2026-05-01 09:36:22 +00:00
Kpa-clawbot d9904cc138 ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 09:36:21 +00:00
Kpa-clawbot 7aa59eabde ci: update frontend-tests.json [skip ci] 2026-05-01 09:36:20 +00:00
Kpa-clawbot ac7d2b64f7 ci: update frontend-coverage.json [skip ci] 2026-05-01 09:36:19 +00:00
Kpa-clawbot fd3bf1a892 ci: update e2e-tests.json [skip ci] 2026-05-01 09:36:18 +00:00
Kpa-clawbot f16afe7fdf ci: update go-server-coverage.json [skip ci] 2026-05-01 09:34:29 +00:00
Kpa-clawbot ed66e54e57 ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 09:34:28 +00:00
Kpa-clawbot 22079a1fc4 ci: update frontend-tests.json [skip ci] 2026-05-01 09:34:27 +00:00
Kpa-clawbot 232882d308 ci: update frontend-coverage.json [skip ci] 2026-05-01 09:34:25 +00:00
Kpa-clawbot fb640bcfc3 ci: update e2e-tests.json [skip ci] 2026-05-01 09:34:25 +00:00
Kpa-clawbot 096887228f release: v3.6.0 'Forensics' notes v3.6.0 2026-05-01 09:24:42 +00:00
Kpa-clawbot 4c39f041ba ci: update go-server-coverage.json [skip ci] 2026-05-01 09:10:25 +00:00
Kpa-clawbot 1c755ed525 ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 09:10:24 +00:00
Kpa-clawbot c78606a416 ci: update frontend-tests.json [skip ci] 2026-05-01 09:10:24 +00:00
Kpa-clawbot 718d2e201a ci: update frontend-coverage.json [skip ci] 2026-05-01 09:10:23 +00:00
Kpa-clawbot d3d41f3bf2 ci: update e2e-tests.json [skip ci] 2026-05-01 09:10:22 +00:00
Kpa-clawbot 7bb5ff9a7f fix(e2e): tag flying-packet polyline so test selector doesn't pick up geofilter polygons (#953)
## Bug

Master CI failing on `Map trace polyline uses hash-derived color when
toggle ON`. The test selector `path.leaflet-interactive` was too broad —
it matched **geofilter region polygons** (`L.polygon` calls in
`live.js:1052`/`map.js:327`), which are styled with theme variables, not
`hsl()`. None of those polygons have an `hsl(` stroke, so the assertion
failed even though the actual flying-packet polylines DO use hash colors
correctly.

## Fix

1. Tag flying-packet polylines with a dedicated class
`live-packet-trace` (`public/live.js:2728`).
2. Update the test selector to target that class specifically.
3. Treat "no flying-packet polylines drawn in the test window" as SKIP
(not fail) — animation may not trigger in 3s.

## Verification (rule 18)

- Read implementation at `live.js:2724-2729`: polyline color IS set from
`hashFill` when toggle is ON. The implementation is correct.
- Read polygon callers at `live.js:1052` (geofilter regions) — confirmed
they share the same `path.leaflet-interactive` class.
- The test was selecting wrong DOM nodes; fix narrows to dedicated
class.

No code logic changed — only DOM tagging + test selector.

Co-authored-by: Kpa-clawbot <bot@example.invalid>
2026-05-01 09:00:49 +00:00
Kpa-clawbot b9758111b0 feat(hash-color): bright vivid fill + dark outline + live feed/polyline surfaces (#951)
## Hash-Color: Bright Vivid Fill + Dark Outline + Extended Surfaces

Follow-up to #948 (merged). Revises the hash-color algorithm for better
perceptual discrimination and extends hash coloring to additional Live
page surfaces.

### Algorithm Changes (`public/hash-color.js`)
- **Hue**: bytes 0-1 (16-bit → 0-360°) — unchanged
- **Saturation**: byte 2 (55-95%) — NEW, was fixed 70%
- **Lightness**: byte 3 (light 50-65%, dark 55-72%) — NEW, was fixed
L=30/38/65
- **Outline** (`hashToOutline`): same-hue dark color (L=25% light, L=15%
dark) — NEW
- Sentinel threshold raised to 8 hex chars (need 4 bytes of entropy)
- Drops WCAG fill-darkening approach — outline carries contrast instead

### Live Page Updates (`public/live.js`)
- **Dot marker**: uses `hashToOutline()` for stroke (was TYPE_COLOR)
- **Polyline trace**: uses hash fill color (unified dot + trace by hash)
- **Feed items**: 4px `border-left` stripe matching packets table

### Test Updates
- `test-hash-color.js`: 32 tests (S variability, L variability, outline
< fill, same hue, pairwise distance)
- `test-e2e-playwright.js`: 2 new assertions (feed stripe, polyline hsl
stroke)

### Verification
- 20 real advert hashes from fixture DB: all produce unique hues (20/20)
- Pairwise HSL distance: avg=0.51, min=0.04
- Go server built and run against fixture DB — HTML serves updated
module
- VM sandbox render-check confirms distinct vivid fills with darker
outlines

Closes #946 §2.10/§2.11 scope extension.

---------

Co-authored-by: you <you@example.com>
Co-authored-by: Kpa-clawbot <bot@example.invalid>
2026-05-01 08:53:04 +00:00
Kpa-clawbot 3bd354338e ci: update go-server-coverage.json [skip ci] 2026-05-01 08:20:25 +00:00
Kpa-clawbot 81ae2689f0 ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 08:20:25 +00:00
Kpa-clawbot f428064efe ci: update frontend-tests.json [skip ci] 2026-05-01 08:20:24 +00:00
Kpa-clawbot c024a55328 ci: update frontend-coverage.json [skip ci] 2026-05-01 08:20:23 +00:00
Kpa-clawbot 7034fe74b5 ci: update e2e-tests.json [skip ci] 2026-05-01 08:20:22 +00:00
Kpa-clawbot 0a9a4c4223 feat(live + packets): color packet markers by hash (#946) (#948)
## Summary

Implements #946 — deterministic HSL coloring of packet markers by hash
for visual propagation tracing.

### What's new

1. **`public/hash-color.js`** — Pure IIFE
(`window.HashColor.hashToHsl(hashHex, theme)`) deriving hue from first 2
bytes of packet hash. Theme-aware lightness with WCAG ≥3.0 contrast
against `--content-bg` (`#f4f5f7` light / `#0f0f23` dark,
`style.css:32,55`). Green/yellow zone (hue 45°-195°) uses L=30% in light
theme to maintain contrast.

2. **Live page dots + contrails** — `drawAnimatedLine` fills the flying
dot and tints the contrail polyline with the hash-derived HSL when
toggle is ON. Ghost-hop dots remain grey (`#94a3b8`). Matrix mode path
(`drawMatrixLine`) is untouched.

3. **Packets table stripe** — `border-left: 4px solid <hsl>` on `<tr>`
in both `buildGroupRowHtml` (group + child rows) and `buildFlatRowHtml`.
Absent when toggle OFF.

4. **Toggle UI** — "Color by hash" checkbox in `#liveControls` between
Realistic and Favorites. Default ON. Persisted to
`localStorage('meshcore-color-packets-by-hash')`. Dispatches `storage`
event for cross-tab sync. Packets page listens and re-renders.

### Performance

- `hashToHsl` is O(1) — two `parseInt` calls + arithmetic. No allocation
beyond the result string.
- Called once per `drawAnimatedLine` invocation (not per animation
frame).
- Packets table: called once per visible row during render (existing
virtualization applies).

### Tests

- `test-hash-color.js`: 16 unit tests — purity, theme split, yellow-zone
clamp, sentinel, variability (anti-tautology gate), WCAG sweep (step 15°
both themes).
- `test-packets.js`: 82 tests still passing (no regression).
- `test-e2e-playwright.js`: 4 new E2E tests — toggle presence/default,
persistence across reload, table stripe present when ON, absent when
OFF.

### Acceptance criteria addressed

All items from spec §6 implemented. TYPE_COLORS retained on
borders/lines. Ghost hops stay grey. Matrix mode suppressed. Cross-tab
storage event dispatched.

Closes #946

---------

Co-authored-by: you <you@example.com>
Co-authored-by: Kpa-clawbot <bot@example.invalid>
2026-05-01 01:10:11 -07:00
Kpa-clawbot 994544604f fix(path-inspector): Show on Map missed origin and stripped first hop (#950)
## Bug

Path Inspector "Show on Map" only rendered the first node of a candidate
path. Multi-hop candidates appeared as a single dot instead of a
polyline.

## Root cause

`public/path-inspector.js:186-188` and `public/map.js:574` (cross-page
handler) called:

```js
drawPacketRoute(candidate.path.slice(1), candidate.path[0]);
```

But `drawPacketRoute(hopKeys, origin)` (`public/map.js:390`) expects
`origin` to be an **object** with `pubkey`/`lat`/`lon`/`name` properties
— not a bare string. The code at lines 451-460 does `origin.lat` /
`origin.pubkey` lookups; with a string, both branches fail, `originPos`
stays null, and the originating node never gets prepended to
`positions`.

Combined with `slice(1)` stripping the head, the resulting polyline was
missing the first hop AND the origin marker — and short paths could
collapse to a single resolved node.

## Fix

Pass the full path as `hopKeys` and `null` as origin. `drawPacketRoute`
already iterates `hopKeys`, resolves each against `nodes[]`, and draws a
marker for every resolved hop. The "origin" arg was meant for cases
where the originator is a separate object (e.g., from packet detail with
sender metadata), not for paths where the origin IS the first hop.

```js
drawPacketRoute(candidate.path, null);
```

Two call sites fixed: in-page direct call (`path-inspector.js:188`) and
cross-page handler (`map.js:574`).

## Verification

**Code reading only.** I did NOT manually load the page or visually
verify the polyline renders. Reviewer should:
1. Open Path Inspector, query a multi-prefix path with ≥3 known hops
2. Click "Show on Map"
3. Confirm polyline draws through every resolved node, not just the
first

`drawPacketRoute` is hard to unit-test without a real Leaflet map, so no
automated test added.

---------

Co-authored-by: Kpa-clawbot <bot@example.invalid>
Co-authored-by: you <you@example.com>
2026-05-01 08:01:37 +00:00
Kpa-clawbot 405094f7eb ci: update go-server-coverage.json [skip ci] 2026-05-01 07:24:51 +00:00
Kpa-clawbot 89b63dc38a ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 07:24:50 +00:00
Kpa-clawbot 8194801b94 ci: update frontend-tests.json [skip ci] 2026-05-01 07:24:49 +00:00
Kpa-clawbot 4427c92c32 ci: update frontend-coverage.json [skip ci] 2026-05-01 07:24:48 +00:00
Kpa-clawbot c5799f868e ci: update e2e-tests.json [skip ci] 2026-05-01 07:24:47 +00:00
Kpa-clawbot 6345c6fb05 fix(ingestor): observability + bounded backoff for MQTT reconnect (#947) (#949)
## Summary

Fixes #947 — MQTT ingestor silently stalls after `pingresp not received`
disconnect due to paho's default 10-minute reconnect backoff and zero
observability of reconnect attempts.

## Changes

### `cmd/ingestor/main.go`
- **Extract `buildMQTTOpts()`** — encapsulates MQTT client option
construction for testability
- **`SetMaxReconnectInterval(30s)`** — bounds paho's default 10-minute
exponential backoff (source: `options.go:137` in
`paho.mqtt.golang@v1.5.0`)
- **`SetConnectTimeout(10s)`** — prevents stuck connect attempts from
blocking reconnect cycle
- **`SetWriteTimeout(10s)`** — prevents stuck publish writes
- **`SetReconnectingHandler`** — logs `MQTT [<tag>] reconnecting to
<broker>` on every reconnect attempt, giving operators visibility into
retry behavior
- **Enhanced `SetConnectionLostHandler`** — now includes broker address
in log line for multi-source disambiguation

### `cmd/ingestor/mqtt_opts_test.go` (new)
- Tests verify `MaxReconnectInterval`, `ConnectTimeout`, `WriteTimeout`
are set correctly
- Tests verify credential and TLS configuration
- Anti-tautology: tests fail if timing settings are removed from
`buildMQTTOpts()`

## Operator impact

After this change, a pingresp disconnect produces:
```
MQTT [staging] disconnected from tcp://broker:1883: pingresp not received, disconnecting
MQTT [staging] reconnecting to tcp://broker:1883
MQTT [staging] reconnecting to tcp://broker:1883
MQTT [staging] connected to tcp://broker:1883
MQTT [staging] subscribed to meshcore/#
```

Max gap between disconnect and first reconnect attempt: ~30s (was up to
10 minutes).

---------

Co-authored-by: you <you@example.com>
2026-05-01 00:01:07 -07:00
efiten f99c9c21d9 feat(live): node filter — show only traffic through a specific node (closes #771 M3) (#924)
Adds a node filter input to the live controls bar. When active, only
packets whose hop chain passes through the selected node(s) are
animated. A counter shows "Showing X of Y" so operators know traffic is
filtered, not absent.

## Changes
- `packetInvolvesFilterNode(pkt, filterKeys)`: pure filter function
using same prefix-matching logic as the favorites filter
- `setNodeFilter(keys)`: sets filter state, resets counters, persists to
localStorage
- `updateNodeFilterUI()`: updates counter + clear button visibility +
datalist autocomplete
- Filter input in controls bar (after Favorites toggle): text input +
datalist autocomplete + × clear button + counter div
- Filter wired into `renderPacketTree`: increments total/shown counters,
returns early when packet doesn't match
- URL hash sync: `?node=ABCD1234` — read on init, written on filter
change

## Tests
8 new unit tests covering filter logic, localStorage persistence, and
edge cases; 78 pass, 0 regressions.

Closes #771 (M3 of 3)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 23:53:08 -07:00
efiten 69080a852f feat(geofilter-docs): app-served docs page (#820) (#900)
## Summary

- Adds `public/geofilter-docs.html` — a self-contained, app-served
documentation page for the geofilter feature, matching the builder's
dark theme
- Updates the GeoFilter Builder's help-bar "Documentation" link from
GitHub markdown URL to the local `/geofilter-docs.html`

## Docs coverage

Polygon syntax, coordinate ordering (`[lat, lon]` — not GeoJSON `[lon,
lat]`), multi-polygon clarification (single polygon only), examples
(Belgium rectangle + irregular shape), legacy bounding box format, prune
script usage.

## Test plan

- [x] Open `/geofilter-docs.html` — dark theme renders, all sections
visible
- [x] Open `/geofilter-builder.html` → click "Documentation" → navigates
to `/geofilter-docs.html` in same tab
- [x] Click "← GeoFilter Builder" on docs page → navigates back to
`/geofilter-builder.html`

Closes #820

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 23:50:54 -07:00
efiten e460932668 fix(store): apply retentionHours cutoff in Load() to prevent OOM on cold start (#917)
## Problem

`Load()` loaded all transmissions from the DB regardless of
`retentionHours`, so `buildSubpathIndex()` processed the full DB history
on every startup. On a DB with ~280K paths this produces ~13.5M subpath
index entries, OOM-killing the process before it ever starts listening —
causing a supervisord crash loop with no useful error message.

## Fix

Apply the same `retentionHours` cutoff to `Load()`'s SQL that
`EvictStale()` already uses at runtime. Both conditions
(`retentionHours` window and `maxPackets` cap) are combined with AND so
neither safety limit is bypassed.

Startup now builds indexes only over the retention window, making
startup time and memory proportional to recent activity rather than
total DB history.

## Docs

- `config.example.json`: adds `retentionHours` to the `packetStore`
block with recommended value `168` (7 days) and a warning about `0` on
large DBs
- `docs/user-guide/configuration.md`: documents the field and adds an
explicit OOM warning

## Test plan

- [x] `cd cmd/server && go test ./... -run TestRetentionLoad` — covers
the retention-filtered load: verifies packets outside the window are
excluded, and that `retentionHours: 0` still loads everything
- [x] Deploy on an instance with a large DB (>100K paths) and
`retentionHours: 168` — server reaches "listening" in seconds instead of
OOM-crashing
- [x] Verify `config.example.json` has `retentionHours: 168` in the
`packetStore` block
- [x] Verify `docs/user-guide/configuration.md` documents the field and
warning

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Kpa-clawbot <kpaclawbot@outlook.com>
2026-05-01 06:47:55 +00:00
Kpa-clawbot aeae7813bc fix: enable SQLite incremental auto-vacuum so DB shrinks after retention (#919) (#920)
Closes #919

## Summary

Enables SQLite incremental auto-vacuum so the database file actually
shrinks after retention reaper deletes old data. Previously, `DELETE`
operations freed pages internally but never returned disk space to the
OS.

## Changes

### 1. Auto-vacuum on new databases
- `PRAGMA auto_vacuum = INCREMENTAL` set via DSN pragma before
`journal_mode(WAL)` in the ingestor's `OpenStoreWithInterval`
- Must be set before any tables are created; DSN ordering ensures this

### 2. Post-reaper incremental vacuum
- `PRAGMA incremental_vacuum(N)` runs after every retention reaper cycle
(packets, metrics, observers, neighbor edges)
- N defaults to 1024 pages, configurable via `db.incrementalVacuumPages`
- Noop on `auto_vacuum=NONE` databases (safe before migration)
- Added to both server and ingestor

### 3. Opt-in full VACUUM for existing databases
- Startup check logs a clear warning if `auto_vacuum != INCREMENTAL`
- `db.vacuumOnStartup: true` config triggers one-time `PRAGMA
auto_vacuum = INCREMENTAL; VACUUM`
- Logs start/end time for operator visibility

### 4. Documentation
- `docs/user-guide/configuration.md`: retention section notes that
lowering retention doesn't immediately shrink the DB
- `docs/user-guide/database.md`: new guide covering WAL, auto-vacuum,
migration, manual VACUUM

### 5. Tests
- `TestNewDBHasIncrementalAutoVacuum` — fresh DB gets `auto_vacuum=2`
- `TestExistingDBHasAutoVacuumNone` — old DB stays at `auto_vacuum=0`
- `TestVacuumOnStartupMigratesDB` — full VACUUM sets `auto_vacuum=2`
- `TestIncrementalVacuumReducesFreelist` — DELETE + vacuum shrinks
freelist
- `TestCheckAutoVacuumLogs` — handles both modes without panic
- `TestConfigIncrementalVacuumPages` — config defaults and overrides

## Migration path for existing databases

1. On startup, CoreScope logs: `[db] auto_vacuum=NONE — DB needs
one-time VACUUM...`
2. Set `db.vacuumOnStartup: true` in config.json
3. Restart — VACUUM runs (blocks startup, minutes on large DBs)
4. Remove `vacuumOnStartup` after migration

## Test results

```
ok  github.com/corescope/server    19.448s
ok  github.com/corescope/ingestor  30.682s
```

---------

Co-authored-by: you <you@example.com>
2026-04-30 23:45:00 -07:00
Kpa-clawbot 43f17ed770 ci: update go-server-coverage.json [skip ci] 2026-05-01 06:40:20 +00:00
Kpa-clawbot 9ada3d7e93 ci: update go-ingestor-coverage.json [skip ci] 2026-05-01 06:40:19 +00:00
Kpa-clawbot 7a04462dde ci: update frontend-tests.json [skip ci] 2026-05-01 06:40:18 +00:00
Kpa-clawbot c0f39e298a ci: update frontend-coverage.json [skip ci] 2026-05-01 06:40:17 +00:00
Kpa-clawbot cc2b731c77 ci: update e2e-tests.json [skip ci] 2026-05-01 06:40:16 +00:00