Skip to content

feat(cleanup): reclaim disk until a free-space goal via --free-gb (ticket-093) - #121

Merged
ifuri-validator-agent[bot] merged 3 commits into
mainfrom
ticket/093-target-driven-cleanup
Oct 21, 2026
Merged

ifuri-validator-agent[bot] merged 3 commits into
mainfrom
ticket/093-target-driven-cleanup

Conversation

@tom-sapletta-com

Copy link
Copy Markdown
Contributor

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:

  1. safe service caches (existing --yes plan)
  2. stale /tmp entries (existing tmp handler, 3 days)
  3. new: stale agent release caches under ~/.local/share/*/releases|release|snapshots/* untouched 7+ days — read-only (protected) trees are reported and skipped
  4. Docker dangling images + build-cache prune — every docker call has a hard 30 s timeout, so a busy daemon skips the tier instead of hanging
  5. Docker images referenced by no container and older than --days (default 14) — used-image refs come from one cheap docker 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-gb is 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). --yes then freed 1.6 GB of a 100%-full 1.9 T disk and stopped. --threshold-gb keeps 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-gb regression guard)
  • ./project/governance-check.sh → GOV-PASS
  • live: goal-already-reached path, full dry-run ladder, real ladder at goal 15 GB (freed 0.37 GB via the new release-cache tier; docker tiers ran or skipped cleanly; privileged hints printed)

@ifuri-validator-agent ifuri-validator-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Merge will be attempted after this approval when explicitly authorized. ## Decision record (recomputable)
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"

@ifuri-validator-agent
ifuri-validator-agent Bot merged commit a32982f into main Oct 21, 2026
12 checks passed
@ifuri-validator-agent
ifuri-validator-agent Bot deleted the ticket/093-target-driven-cleanup branch October 21, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant