40a7ac80d3
Write apply-progress.md, tick all 35 tasks, add verify-report.md (28/28 SS-101..128 PASS). Gates green: 322 pytest, ruff clean, FE build+lint 0 errors, PrometheusChartWidget extraction 4/4 non-regressive. No blocking code findings. Process note: slice 2 was +644 lines over 400-line budget (additive, no scope creep; retrospectively extract+widgets could split).
4.8 KiB
4.8 KiB
Apply Progress: Service Storage Harness
Change: service-storage-harness
Phase: apply-progress
Date: 2026-07-09
Status: complete — all 35 tasks done, all gates green, verified (see verify-report.md)
Slices delivered
Four slices, each its own commit, each leaving pytest / npm run build / npm run lint / ruff green.
Slice 1 — Harness + qBit store + client + integration (commit e7bd0af, amended)
services/service_data.py—ServiceDataHarness: lifecycle-only (concern registration via dataclass, idempotentrun_migrationscatching "duplicate column name" per-statement,cascade_delete(service_id)iterating owned tables). No generic data ops (SS-101..104).services/qbittorrent_store.py—QbittorrentSampleStore:qbittorrent_speed_samples(service_id, ts, dl_speed, up_speed)+ index in dedicatedqbittorrent.db;append/window/prune, MAX_SAMPLES=120; registered as a harness concern (SS-105..107).clients/qbittorrent.py—QbittorrentClient: cookie login via/api/v2/auth/login, 403 re-login+retry,maindata()via/api/v2/sync/maindata(SS-108..110).integrations/qbittorrent.py+ registry entry — config (base_url, timeout_seconds) + secret (username, password) schema; 3 widget kinds declared.- Initialized in
main.pylifespan. - Tests:
test_service_data.py(harness lifecycle),test_qbittorrent_store.py,test_qbittorrent_client.py.
Slice 2 — qBit widgets + LineSeriesChart extract (commit 8b0e7ea, amended)
QbittorrentWidgetSourceinwidgets/sources.py— 3 branches:totals(item count from maindata),active(filter state ∈ {downloading, uploading}),speed(append sample + return{series}from.window(), ts×1000 for JS ms). Errors →{error}. Registered inSERVICE_ADAPTERS(SS-111..115).frontend/src/components/LineSeriesChart.tsx— shared recharts renderer extracted fromPrometheusChartWidget(~40 lines, props{series, height?});PrometheusChartWidgetbecomes a thin wrapper. Extraction non-regressive: 4 PrometheusChartWidget tests stay green (SS-118).QbittorrentTotalsWidget(count),QbittorrentActiveTorrentsWidget(list),QbittorrentSpeedWidget(uses LineSeriesChart). Registry binding + barrel + tests (SS-116..117).
Slice 3 — MediaIndex migration (commit c87f398) — LOAD-BEARING
- Idempotent harness migration:
ALTER TABLE media_items ADD COLUMN service_id TEXT NOT NULL DEFAULT ''.media_index.dbfile location UNCHANGED. Existing rows backfill toservice_id=''via DEFAULT (SS-119, SS-120, SS-124). - Scoped
replace_items—DELETE FROM media_items WHERE service_id = ?replaces the prior globalDELETE FROM media_items. FIXES the latent global-clear bug where rebuilding for one Jellyfin wiped another's rows.service_idstamped into inserted rows. Regression testtest_replace_scoped_by_service_id_preserves_other_servicesproves svc-A survives svc-B's rebuild (SS-121). query(service_id="")shows all rows (backward-compat);query(service_id="X")scopes. Existing MediaIndex + API tests pass unchanged (62 passed) (SS-122).- Worker threads the real
service_id(already plumbed via--service-id) intoreplace_itemsso new rows are stamped (SS-123).
Slice 4 — Cascade-delete wiring (commit 75c949a)
settings_store.delete_servicecallsServiceDataHarness.cascade_delete(service_id)after existing cleanup, best-effort try/except (failure logs, doesn't crash the delete) (SS-125).- Integration test proves end-to-end cascade across BOTH concerns (qBit samples + media items) with multi-instance preservation (SS-126).
Deviations from tasks.md
- Slice 2 over the 400-line review budget (verify-report flagged +644 source lines). The slice is additive (3 new widgets + extraction + tests), no scope creep, but the per-slice budget from
openspec/config.yamlwas exceeded. Retrospectively this could have been split (extraction in one slice, qBit widgets in another). No code defect; recorded here as a process note for future slicing. The verify agent flagged it WARNING, not blocking.
Final gate results
| Gate | Result |
|---|---|
backend && PYTHONPATH=src python3 -m pytest -q |
322 passed, 2 warnings (pre-existing pythonjsonlogger DeprecationWarning) |
backend && PYTHONPATH=src python3 -m ruff check src tests |
All checks passed |
frontend && npm run build |
exit 0 (pre-existing chunk-size warning) |
frontend && npm run lint |
0 errors, 1 pre-existing warning (WidgetConfigDialog.tsx, untouched) |
frontend && npx vitest run PrometheusChartWidget.test.tsx |
4 passed (extraction non-regression confirmed) |
Verification
See verify-report.md — adversarial fresh-context review: 28/28 PASS. No blocking code findings. Archive blocker is doc-only (this file + the ticked tasks.md clear it).