feat(storage): report nested depth so Forgejo's split needs no kubectl exec #28

Merged
coilysiren merged 1 commit from aos/claude/uq98-usage-depth into main 2026-08-17 20:16:23 +00:00
Owner

Follow-up to #27, which is merged. Refs #26.

#27 fixed the OOM and added the pod-ephemeral domain, which took four of the incident's eight manual commands unattended. It did not touch the Forgejo measurement, and the review of that was fair: adding SQL to forgejo-storage-measure.sh improved what a human sees once they are already at the keyboard and did nothing about the fact that a human has to run it.

The barrier was granularity, not access

Forgejo's data sits on a local-path PVC on the host, in the tree this server already walks and already totals at 123 GB. scan_usage_profile reported immediate children with recursive totals and nothing below, so the data / git / packages split, and data/attachments inside it, were only reachable by a du per level. _scan_pressure_children has the same one-level shape. No permission was missing.

Change

max_depth on UsageProfile, with children nested accordingly. k3s-storage and pod-ephemeral ship at depth 3: claim -> data -> attachments, and pod -> volumes -> volume type. The ceiling is fixed at 5 in code because each level multiplies the response, and limit now clips reported detail at every depth rather than only the top.

Depth changes reporting, never the walk. _scan_node reuses the same flat _scan_tree for everything below the reported levels and absorbs its totals, so the numbers cannot drift between views:

depth=1 total=757760 entries=9    depth=3 total=757760 entries=9
  pvc-forgejo = 757760              pvc-forgejo = 757760
                                      pvc-forgejo/data = 503808
                                        pvc-forgejo/data/attachments = 401408
                                        pvc-forgejo/data/lfs = 102400
                                      pvc-forgejo/packages = 200704

A regression test scans one tree at depth 1 and depth 3 and asserts identical bytes, apparent bytes, and entry counts. A depth that moved a byte would make both views untrustworthy, so that invariant is the thing worth pinning. A second assertion holds that a parent is never a bare sum of the children shown beneath it, since it still owns its own inode and its loose files.

Where this leaves the eight manual commands

Five fully unattended, one halved, two operator-only by design. The remaining half is attachment and package ownership - which repository, which release or issue, which age bucket - which lives only in PostgreSQL and still needs an attended kubectl exec. Forgejo's API exposes per-release and per-issue assets, so an aosguard verb could reconstruct it without exec; that is agentic-os work and a separate issue.

Verification

ward exec lint, ward exec test (47 passed, 1 new), and ward exec precommit are clean on this branch rebased onto current main, after the docs reorganisation landed there.

🤖 Generated with Claude Code

Follow-up to #27, which is merged. Refs #26. #27 fixed the OOM and added the `pod-ephemeral` domain, which took four of the incident's eight manual commands unattended. It did not touch the Forgejo measurement, and the review of that was fair: adding SQL to `forgejo-storage-measure.sh` improved what a human sees once they are already at the keyboard and did nothing about the fact that a human has to run it. ## The barrier was granularity, not access Forgejo's data sits on a **local-path PVC on the host**, in the tree this server already walks and already totals at 123 GB. `scan_usage_profile` reported immediate children with recursive totals and nothing below, so the `data` / `git` / `packages` split, and `data/attachments` inside it, were only reachable by a `du` per level. `_scan_pressure_children` has the same one-level shape. No permission was missing. ## Change `max_depth` on `UsageProfile`, with children nested accordingly. `k3s-storage` and `pod-ephemeral` ship at depth 3: claim -> `data` -> `attachments`, and pod -> `volumes` -> volume type. The ceiling is fixed at 5 in code because each level multiplies the response, and `limit` now clips reported detail at every depth rather than only the top. **Depth changes reporting, never the walk.** `_scan_node` reuses the same flat `_scan_tree` for everything below the reported levels and absorbs its totals, so the numbers cannot drift between views: ``` depth=1 total=757760 entries=9 depth=3 total=757760 entries=9 pvc-forgejo = 757760 pvc-forgejo = 757760 pvc-forgejo/data = 503808 pvc-forgejo/data/attachments = 401408 pvc-forgejo/data/lfs = 102400 pvc-forgejo/packages = 200704 ``` A regression test scans one tree at depth 1 and depth 3 and asserts identical bytes, apparent bytes, and entry counts. A depth that moved a byte would make both views untrustworthy, so that invariant is the thing worth pinning. A second assertion holds that a parent is never a bare sum of the children shown beneath it, since it still owns its own inode and its loose files. ## Where this leaves the eight manual commands Five fully unattended, one halved, two operator-only by design. The remaining half is attachment and package **ownership** - which repository, which release or issue, which age bucket - which lives only in PostgreSQL and still needs an attended `kubectl exec`. Forgejo's API exposes per-release and per-issue assets, so an aosguard verb could reconstruct it without `exec`; that is agentic-os work and a separate issue. ## Verification `ward exec lint`, `ward exec test` (47 passed, 1 new), and `ward exec precommit` are clean on this branch rebased onto current `main`, after the docs reorganisation landed there. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A snapshot reported only immediate children with recursive totals, so the
`data` vs `git` vs `packages` split inside a claim, and `data/attachments`
inside that, were unreachable from the MCP. Both live on the host filesystem
this server already walks, so the only thing keeping them attended was
reporting granularity.

Add `max_depth` to UsageProfile and nest children accordingly. Depth changes
reporting, never the walk: `_scan_node` reuses the same flat `_scan_tree` for
everything below the reported levels and absorbs its totals, so bytes and entry
counts are identical at every depth. A regression test scans one tree at depth
1 and depth 3 and asserts the totals match, because a depth that moved a byte
would make both views untrustworthy.

Ship `k3s-storage` and `pod-ephemeral` at depth 3: claim -> data -> attachments,
and pod -> volumes -> volume type. Ceiling is fixed at 5 in code because each
level multiplies the response.

Refs coilyco-flight-deck/node-stats-mcp#26

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/node-stats-mcp!28
No description provided.