feat(remove-outputs): tag-scoped retention for limit and age - #313
Conversation
) Make `limit` and `age` respect a `tags` filter so housekeeping can be scoped to a subset of runs instead of acting globally: - limit + tags -> keep the N newest runs carrying any of the given tag(s), remove older matching runs, leave untagged runs untouched. - age + tags -> remove only tagged runs within the age range, leave untagged runs untouched (supports both older `10d` and younger `-10d`). CLI combination syntax uses the regular comma separator, e.g. `-r "limit=10,tag=nightly"` / `-r "age=10d,tag=nightly,tag=prod"`. arguments.py detects the combination, validates it, and rewrites it into the internal `limit=N;tag=...` / `age=X;tag=...` form (the legacy `;` form still works). Rules: - tags may combine with EITHER limit OR age, not both (error). - only one limit/age may be combined with tags (error). - a clear INFO line explains exactly what will be deleted; when independent options (index/alias/run_start) are also present the deletion order is printed. Server `/remove-outputs` scopes the same way when `tags` is sent with `limit` and/or `age`, and rejects the limit+age+tags combination before removing anything. Backward compatible: plain limit/age/tag (no partner) behave exactly as before; the elif dispatch in database.py is reordered so combined queries route to the scoped handler. Docs (CLI + server), OpenAPI examples, and the admin UI labels updated. Also documents the previously under-documented negative-age (`-10d`) behavior in the docs.
The branch had grown a parallel "age" + "tags" combination beyond what #309 asked for. That doubled the combination-detection/validation logic across arguments.py and server.py, and required database.py to parse a hand-rolled ';'-encoded suffix in two functions instead of one. Drop the age+tag combo: age+tags now behaves exactly like on main (two independent operations). limit+tags is unchanged and still matches the issue's proposed direction and examples exactly. Updated docs, the CLI help-text fixture, and tests accordingly.
|
Pushed a follow-up commit that narrows scope: dropped the `age` + `tags` combination. This PR's title/description advertise both `limit`+`age` scoping, but issue #309 only asked for `limit`+`tags`. Keeping both meant three places (arguments.py's CLI comma-detection, server.py's JSON-body handling, database.py's dispatch) each had to know about a hand-rolled `limit=N;tag=x`/`age=X;tag=x` mini-DSL, plus a three-way `limit`+`age`+`tags` ambiguity error that only existed because of the extra combination. What changed:
Net diff vs. the previous branch state: -138 lines. `limit`+`tags` behavior, examples, and tests are unchanged — this only removes the unrequested `age`+`tags` half. Title/description of this PR should probably be updated to drop the "and age" framing before merge. Full python suite (311 tests) green; CLI help-text fixture updated and reverified. |
remove_runs() caught every per-item failure with a bare except:, discarding the real exception and always printing the same generic "could not find" message regardless of cause. Now prints the actual exception. _remove_by_age had two paths that built a warning/error string but never printed it, so on the CLI (unlike the server API, which returns the console string) a no-op removal or an invalid age format produced zero visible output - indistinguishable from a successful no-op. Both now print, matching every sibling _remove_by_* method. Updated the removerun CLI fixture: the alias-not-found line now shows the real exception detail instead of the old generic wording.
Implements #309
Make
limitandagerespect atagsfilter so housekeeping can be scoped to a subset of runs instead of acting globally:10dand younger-10d).CLI combination syntax uses the regular comma separator, e.g.
-r "limit=10,tag=nightly"/-r "age=10d,tag=nightly,tag=prod". arguments.py detects the combination, validates it, and rewrites it into the internallimit=N;tag=.../age=X;tag=...form (the legacy;form still works). Rules:Server
/remove-outputsscopes the same way whentagsis sent withlimitand/orage, and rejects the limit+age+tags combination before removing anything.Backward compatible: plain limit/age/tag (no partner) behave exactly as before; the elif dispatch in database.py is reordered so combined queries route to the scoped handler.
Docs (CLI + server), OpenAPI examples, and the admin UI labels updated. Also documents the previously under-documented negative-age (
-10d) behavior in the docs.