feat(office): bound run history growth with a scheduled retention sweep (#3566)
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
created: 2026-09-09
|
||||
status: complete
|
||||
requirements:
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-001
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-002
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-003
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-004
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-005
|
||||
system_design:
|
||||
- ../../specs/office/system-design/run-history-retention.md
|
||||
- ../../specs/office/system-design/run-history-retention-operations.md
|
||||
legacy_specs: []
|
||||
---
|
||||
|
||||
# Implementation Plan: Office Run History Retention
|
||||
|
||||
## Overview
|
||||
|
||||
Office writes `office_routine_runs` and `run_events` rows on every routine firing and
|
||||
run lifecycle transition, and nothing ever deletes them on a schedule. A `*/5 * * * *`
|
||||
routine produces roughly 105,000 `office_routine_runs` rows a year; one reference
|
||||
install had already accumulated 323 consecutive `coalesced` rows from a routine that
|
||||
was doing nothing useful. This plan adds one scheduled sweep, on its own interval
|
||||
(never the 5s Office tick), that bounds `office_routine_runs`, `runs`, and their
|
||||
satellites (`run_events`, `office_run_route_attempts`, `office_run_skills`) by age,
|
||||
with a per-owner floor, identical behavior on SQLite and PostgreSQL, and an operator
|
||||
surface (Settings > System > Data & Logs) that reports policy, counts, previews, and
|
||||
warnings before and while rows are deleted.
|
||||
|
||||
The two halves of the contract are split the same way the specs are split: the sweep
|
||||
itself, its eligibility rules, and engine parity are
|
||||
[run history retention](../../specs/office/system-design/run-history-retention.md)
|
||||
(REQ-001, REQ-002, REQ-005); the settings record, preview marker, health warnings, and
|
||||
System page surface are
|
||||
[run history retention operations](../../specs/office/system-design/run-history-retention-operations.md)
|
||||
(REQ-003, REQ-004).
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- A `internal/office/retention` package: settings store, eligibility/count/delete
|
||||
queries shared by preview and delete, a session-scoped PostgreSQL advisory lock with
|
||||
a SQLite single-process equivalent, a scheduler goroutine on its own interval, and an
|
||||
HTTP handler for `GET`/`PUT /api/v1/system/retention`.
|
||||
- Status-only classification of history vs. live state for both `office_routine_runs`
|
||||
and `runs`, a per-owner floor, oldest-first chunked batch deletion, and satellite rows
|
||||
deleted in the same transaction as their parent run.
|
||||
- A per-table preview (report, delete nothing) on each table's first evaluation, and
|
||||
`health.Issue` warnings before the cap and on sweep/count failure.
|
||||
- A `RetentionSettingsCard` on Settings > System > Data & Logs showing policy, retained
|
||||
counts, preview state, last sweep, and backlog/error warnings.
|
||||
- Expression indexes serving the sweep's filter/order on both engines.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Filesystem/container cleanup (owned by storage maintenance).
|
||||
- Routine or workspace deletion (already deletes runs structurally; unaffected).
|
||||
- Any change to run lifecycle, routine dispatch, or task/session/checkout data.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] [Task 01: Bound run history with a scheduled retention sweep](task-01-bound-run-history-with-retention-sweep.md)
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
id: "01-bound-run-history-with-retention-sweep"
|
||||
title: "Bound Office run history with a scheduled retention sweep"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-001
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-002
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-003
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-004
|
||||
- REQ-OFFICE-RUN-HISTORY-RETENTION-005
|
||||
acceptance_criteria:
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.1
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.2
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.3
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.4
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.5
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.6
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.7
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.8
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.9
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-001.10
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.1
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.2
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.3
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.4
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.5
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.6
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.7
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.8
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.9
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.10
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.11
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.12
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-002.13
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.1
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.2
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.3
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.4
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.5
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.6
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.7
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.8
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.9
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.10
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-003.11
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.1
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.2
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.3
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.4
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.5
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.6
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.7
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.8
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-004.9
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-005.1
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-005.2
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-005.3
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-005.4
|
||||
- AC-OFFICE-RUN-HISTORY-RETENTION-005.5
|
||||
system_design:
|
||||
- ../../specs/office/system-design/run-history-retention.md
|
||||
- ../../specs/office/system-design/run-history-retention-operations.md
|
||||
---
|
||||
|
||||
# Task 01: Bound Office Run History with a Scheduled Retention Sweep
|
||||
|
||||
## Summary
|
||||
|
||||
Add `internal/office/retention`: a settings-backed sweep that ages out
|
||||
`office_routine_runs` and `runs` history (plus their satellites) on its own
|
||||
interval, a per-owner floor, status-only live/history classification, a preview
|
||||
pass before first deletion, engine-identical behavior on SQLite and PostgreSQL,
|
||||
and a `GET`/`PUT /api/v1/system/retention` operator surface with a matching
|
||||
Settings > System > Data & Logs card.
|
||||
|
||||
## In scope
|
||||
|
||||
- Eligibility, count, and delete queries shared by preview and the real sweep,
|
||||
keyed on `COALESCE(completed_at, created_at)` / `COALESCE(finished_at,
|
||||
requested_at)`, oldest-first, chunked across statements.
|
||||
- A session-scoped PostgreSQL advisory lock (with a SQLite single-process
|
||||
equivalent) so only one backend sweeps at a time.
|
||||
- A per-owner floor (newest N rows retained regardless of age) applied to
|
||||
count, preview, and delete identically, re-asserted in the DELETE statement.
|
||||
- Satellite deletion (`run_events`, `office_run_route_attempts`,
|
||||
`office_run_skills`) in the same transaction as their parent run.
|
||||
- `health.Issue` warnings for approaching the backlog cap and for count/sweep
|
||||
failure; a one-time preview per table before any row is deleted.
|
||||
- `RetentionSettingsCard` on Settings > System > Data & Logs: policy, retained
|
||||
counts, preview/backlog state, last sweep, errors.
|
||||
- Expression indexes serving the sweep's filter/order on both engines.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Filesystem/container artifact cleanup (storage maintenance owns that).
|
||||
- Routine/workspace deletion cascades (already delete runs structurally).
|
||||
- Any run lifecycle, routing, or task/session/checkout behavior change.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- History rows (terminal `office_routine_runs`/`runs` statuses) older than the
|
||||
configured window are deleted in batches; live-state rows (`received`,
|
||||
`task_created`, `queued`, `claimed`) are never age-pruned; an unrecognized
|
||||
status fails safe as live state and warns.
|
||||
- The newest `floor` rows per owner survive regardless of age.
|
||||
- Each table's first sweep is a preview: it reports would-delete counts and
|
||||
deletes nothing; a later sweep performs real deletion.
|
||||
- Behavior, including the advisory lock and batch chunking, is identical on
|
||||
SQLite and PostgreSQL.
|
||||
- `GET`/`PUT /api/v1/system/retention` read/write policy and report current
|
||||
counts, preview state, last sweep outcome, and backlog/error warnings.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/backend && go test -race -count=1 ./internal/office/retention/... ./internal/office/repository/sqlite/...
|
||||
cd apps/backend && KANDEV_TEST_POSTGRES_DSN=<dsn> go test -race -count=1 -v ./internal/office/retention/...
|
||||
cd apps/backend && golangci-lint run ./internal/office/retention/...
|
||||
cd apps/web && pnpm run typecheck
|
||||
cd apps && pnpm --filter @kandev/web test -- --run retention-settings-card system-route-copy
|
||||
cd apps/web && pnpm e2e:run -- --project chromium --grep "System retention settings"
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/office/retention/*.go`
|
||||
- `apps/backend/internal/office/repository/sqlite/base_migrations.go`,
|
||||
`retention_indexes_test.go`, `retention_indexes_postgres_test.go`
|
||||
- `apps/backend/internal/backendapp/*` (scheduler/handler wiring)
|
||||
- `apps/web/src/**/retention-settings-card.tsx` and its tests
|
||||
- `docs/specs/office/requirements/run-history-retention*.md`,
|
||||
`docs/specs/office/system-design/run-history-retention*.md`
|
||||
- `docs/public/operations.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Risks
|
||||
|
||||
- A dialect-sensitive query bug that only reproduces on PostgreSQL (mitigated
|
||||
by an environment-gated Postgres suite covering the advisory lock, the
|
||||
EvalPlanQual TOCTOU window, and batch chunking).
|
||||
- A too-aggressive window or floor deleting rows an operator still needed
|
||||
(mitigated by the default 30-day window, the per-owner floor, and the
|
||||
preview-before-delete behavior).
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- `REQ-OFFICE-RUN-HISTORY-RETENTION-001` through `-005`.
|
||||
- [run history retention](../../specs/office/system-design/run-history-retention.md)
|
||||
and
|
||||
[run history retention operations](../../specs/office/system-design/run-history-retention-operations.md).
|
||||
- The reference install's 323 consecutive `coalesced` routine-run rows (28
|
||||
days, one bricked routine) cited in the requirements' Overview.
|
||||
|
||||
## Results
|
||||
|
||||
- Implemented `internal/office/retention` (settings store, `Sweeper`,
|
||||
`Scheduler`, `CensusTracker`, PostgreSQL advisory lock with SQLite
|
||||
equivalent, `Handler` for `GET`/`PUT /api/v1/system/retention`) plus the
|
||||
`RetentionSettingsCard` on Settings > System > Data & Logs.
|
||||
- Full backend gauntlet green: `go build ./...`, `go vet`, `gofmt -l`,
|
||||
`go test -race ./internal/office/retention/...` (SQLite), the
|
||||
PostgreSQL-gated suite against a real scratch instance (125 tests, 0
|
||||
skipped, all PASS, covering the advisory lock, the EvalPlanQual
|
||||
concurrent-resurrection TOCTOU regression, and chunked batch deletes),
|
||||
`golangci-lint run ./...` (0 issues).
|
||||
- Frontend: `pnpm run typecheck`, the retention card and System route-copy
|
||||
Vitest suites, and a scoped Playwright run (`--grep "System retention
|
||||
settings"`, 2 passed).
|
||||
- Delivered as PR [#3566](https://github.com/kdlbs/kandev/pull/3566)
|
||||
("feat(office): bound run history growth with a scheduled retention
|
||||
sweep"), through four Build rounds and four Review rounds; remaining
|
||||
non-blocking test-rigor gaps and CI-wiring follow-ups are tracked on the
|
||||
linked follow-up card rather than blocking this PR.
|
||||
Reference in New Issue
Block a user