Repository navigation
feat(cleanup): reclaim disk until a free-space goal via --free-gb (ticket-093) - #121
Conversation
There was a problem hiding this comment.
Validator approval after policy checks for exact head 1dce59765cac6a716211af95945ffbe9f395d35e.
Ticket: ticket-093
Correlation ID: local-semcod-fixos-pr-121-ticket-093
Model: gemini/gemini-3.8-flash
Reviewed diff chunks: 4
Advisory LLM verdict: APPROVE
Advisory summary: Reviewed all 4 diff chunk(s). Diff chunk 1 introduces fixos/cli/_cleanup_free_target.py containing helper functions and the ladder execution logic for target-driven cleanup (free_bytes, find_stale_release_caches, remove_release_caches, and safe batched Docker cleanup with timeouts). Implementation uses safe subprocess invocations without shell expansion and defensive path write-permission checks. | Diff chunk 2 implements _run_free_target_mode and the CLI option --free-gb / --target-free-gb in cleanup_cmd.py. It establishes a multi-tier ladder for space reclamation, ensures mutually exclusive option handling, and outputs helpful privileged hints when the goal is not reached. | Diff chunk 3 adds metadata in intent.json and comprehensive unit tests in tests/unit/test_cleanup_free_target.py covering free bytes measurement, ladder tier progression and goal halting, stale release cache discovery and permission protection (including root execution checks), and Docker invocation error handling. | This chunk adds unit tests verifying CLI parameter wiring for --free-gb, mutual exclusion with conflicting cleanup action flags, and preservation of --threshold-gb scan threshold semantics.
Advisory findings: none
The LLM output above is advisory and was not used as the approval trust root.
Semantic review prerequisite: not_required; policy 676cb4516bbfed2a000e40b9b1b6e4a430ecc761ec546aeb53d721a1905cfdd7.
Actual PR impact radar
Exact range: e13966afc627ea3aa71197d72fb82d94d39864d0...1dce59765cac6a716211af95945ffbe9f395d35e
Change digest: 926ffe837b1c7a3cd338e92edbfe4daa42e5ea1f8a5f8913a3681a627f6e10e6
Score: 72/100 (L), estimated 99 min, split recommended: true
Affected services/components: repository-wide/unclassified
Machine-readable radar JSONL and SVG
{"actual_change":{"additions":995,"base_sha":"e13966afc627ea3aa71197d72fb82d94d39864d0","binary_files":0,"categories":{"code":2,"configuration":1,"docs":1,"tests":1},"change_digest":"926ffe837b1c7a3cd338e92edbfe4daa42e5ea1f8a5f8913a3681a627f6e10e6","comparison":"e13966afc627ea3aa71197d72fb82d94d39864d0...1dce59765cac6a716211af95945ffbe9f395d35e","deletions":1,"file_count":5,"files":["fixos/cli/_cleanup_free_target.py","fixos/cli/cleanup_cmd.py","project/ticket-093/README.md","project/ticket-093/intent.json","tests/unit/test_cleanup_free_target.py"],"head_sha":"1dce59765cac6a716211af95945ffbe9f395d35e","service_count":0,"services":[]},"assessment_mode":"observed-pr","axes":{"coupling":5,"delivery":3,"scope":5,"uncertainty":3,"validation":2},"complexity":"L","confidence":0.9,"diagnostics":["RADAR-ACCEPTANCE-MISSING","RADAR-BUDGET-EXCEEDED"],"estimate":{"budget_minutes":30,"minutes":99,"within_budget":false},"impact":{"components":["fixos","local","name","project","tests","timeout"],"files":["fixos/cli/_cleanup_free_target.py","fixos/cli/cleanup_cmd.py","local/share","name/digest","project/governance-check.sh","project/ticket-093/README.md","project/ticket-093/intent.json","tests/unit","tests/unit/test_cleanup_free_target.py","timeout/missing-binary"],"public_interfaces":[],"runtime_dependencies":0},"schema":"subactor.ticket-radar/v1","score":72,"split":{"parts":[{"estimated_minutes":15,"name":"Implement fixos","scope":["fixos"]},{"estimated_minutes":15,"name":"Implement local","scope":["local"]},{"estimated_minutes":15,"name":"Implement name","scope":["name"]},{"estimated_minutes":15,"name":"Implement project","scope":["project"]},{"estimated_minutes":15,"name":"Implement tests","scope":["tests"]},{"estimated_minutes":15,"name":"Validate and project to trackers","scope":["tests","planfile","github/gitlab/jira projections"]}],"reason":"estimated_minutes_exceed_budget","recommended":true},"standards":[{"id":"wellmanifest/dsl","revision":"6c60fc4e0dd1f1bb74f46a7745e28019908d1203","version":"0.1.0-dev"},{"id":"wellmanifest/ticket-lifecycle","revision":"5bf581907a87b46a13a73e6c033d3abe4d9a306f","version":"0.1.0-dev"},{"id":"wellmanifest/git-lifecycle","revision":"7d77d4b7af57e69bc75c3a0290b3a4805c5c4438","version":"0.2.0-dev"},{"id":"wellmanifest/logs","revision":"48c284ef7a069055c0bcb6b900147ce5e65f8b43","version":"0.3.0"}],"ticket_ref":"ticket-093"}<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img"><title>ticket-093: feat(cleanup): reclaim disk until a free-space goal via --free-gb (ticket-093)</title><rect width="128" height="128" rx="12" fill="#f8fafc"/><g stroke-width="1"><polygon points="64,55 72,61 69,71 59,71 56,61" fill="none" stroke="#d7dde5"/><polygon points="64,47 80,59 74,78 54,78 48,59" fill="none" stroke="#d7dde5"/><polygon points="64,38 89,56 79,85 49,85 39,56" fill="none" stroke="#d7dde5"/><polygon points="64,30 97,53 84,92 44,92 31,53" fill="none" stroke="#d7dde5"/><polygon points="64,21 105,51 89,99 39,99 23,51" fill="none" stroke="#d7dde5"/><line x1="64" y1="64" x2="64" y2="21" stroke="#aab4c0"/><line x1="64" y1="64" x2="105" y2="51" stroke="#aab4c0"/><line x1="64" y1="64" x2="89" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="39" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="23" y2="51" stroke="#aab4c0"/></g><polygon points="64,21 105,51 79,85 54,78 39,56" fill="#fb923c" fill-opacity="0.45" stroke="#c2410c" stroke-width="2"/><circle cx="64" cy="64" r="3" fill="#c2410c"/><g font-family="sans-serif" font-size="7" fill="#334155"><text x="64" y="11" text-anchor="middle">SCO</text><text x="114" y="48" text-anchor="middle">COU</text><text x="95" y="107" text-anchor="middle">UNC</text><text x="33" y="107" text-anchor="middle">VAL</text><text x="14" y="48" text-anchor="middle">DEL</text></g><text x="64" y="124" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#0f172a">L · 99m</text></svg>DECISION D-093-0354
TICKET ticket-093
HEAD_SHA 1dce59765cac6a716211af95945ffbe9f395d35e
CORRELATION_ID local-semcod-fixos-pr-121-ticket-093
ACTOR agent:ifuri-validator-agent[bot]
APPLIED_RULE P-CORE-015
INPUT author_login = "tom-sapletta-com"
INPUT observed_checks = ["test-summary=PASS","test (3.12)=PASS","test (3.11)=PASS","test (3.10)=PASS","test-alpine=PASS","test-arch=PASS","test-ubuntu=PASS","test-debian=PASS","governance / enforce=PASS","test-fedora=PASS","governance / remote lifecycle=PASS","onedev/local-verify=PASS"]
INPUT required_checks = ["onedev/local-verify","test (3.10)","test (3.11)","test (3.12)","test-summary"]
INPUT required_checks_source = "protected registry (env/request)"
INPUT reviewer_login = "ifuri-validator-agent[bot]"
INPUT semantic_review_assessment = {"schema":"subactor.validator/semantic-review-assessment/v1","subject":{"repository":"semcod/fixos","pull_request":121,"head_sha":"1dce59765cac6a716211af95945ffbe9f395d35e","base_sha":"e13966afc627ea3aa71197d72fb82d94d39864d0","diff_sha256":"331214d1cdbb9970835f1225a4401802aae718c809500782385e8ac919674912"},"policy":{"policy_schema":"subactor.validator/semantic-review-policy/v1","policy_version":1,"policy_sha256":"676cb4516bbfed2a000e40b9b1b6e4a430ecc761ec546aeb53d721a1905cfdd7","required":false,"critical_paths":[],"observed_paths":["fixos/cli/_cleanup_free_target.py","fixos/cli/cleanup_cmd.py","project/ticket-093/README.md","project/ticket-093/intent.json","tests/unit/test_cleanup_free_target.py"]},"grounding":"full-diff-not-per-finding-proof","execution_authority":false,"status":"not_required","reason":null,"review_sha256":null,"unresolved":[]}
INPUT superseded_checks = []
INPUT ticket_radar_receipt = {"schema":"subactor.ticket-radar/v1","base_sha":"e13966afc627ea3aa71197d72fb82d94d39864d0","head_sha":"1dce59765cac6a716211af95945ffbe9f395d35e","change_digest":"926ffe837b1c7a3cd338e92edbfe4daa42e5ea1f8a5f8913a3681a627f6e10e6","score":72,"complexity":"L","estimated_minutes":99,"split_recommended":true,"services":[],"authority":"ADVISORY","promotion":"FORBIDDEN"}
VERDICT APPROVE AUTHORITY DETERMINISTIC
REJECTED REQUEST_CHANGES BECAUSE NO_UNSAFE_CHANGE_REASON_FOUND
ADVISORY llm_verdict = "APPROVE" MODEL "gemini/gemini-3.8-flash"
ASSERT VERDICT_AUTHORITY != "ADVISORY"
What
fixos cleanup --free-gb N(alias--target-free-gb) walks a bounded ladder of no-privilege tiers and stops as soon as the filesystem has N GB free:--yesplan)~/.local/share/*/releases|release|snapshots/*untouched 7+ days — read-only (protected) trees are reported and skipped--days(default 14) — used-image refs come from one cheapdocker ps -a --format {{.Image}}call (container-inspect chunking raced with the daemon)When the goal is still unmet, the command prints the exact privileged next steps (old snap revisions, journal vacuum, rotated syslog) instead of failing silently.
Why
The NoLimits LIM-STORAGE-001 runbook prescribes
fixos cleanup --threshold-gb 10, but--threshold-gbis a per-service scan threshold: with it, only services larger than 10 GB are reported — on the incident host exactly the protected ones (Ollama, snap).--yesthen freed 1.6 GB of a 100%-full 1.9 T disk and stopped.--threshold-gbkeeps its meaning; its help now says it is not a free-space goal.Validation
python -m pytest tests/unit -q→ 752 passed (20 new tests: ladder stop-at-goal, protected-path skipping, docker timeout/missing-binary, name/digest ref parsing, CLI wiring,--threshold-gbregression guard)./project/governance-check.sh→ GOV-PASS