Skip to content

feat(cli): report quota usage deltas around permanent purge batches - #36

Merged
FeathBow merged 1 commit into
mainfrom
feat/quota-observed-delta
Aug 9, 2026
Merged

feat(cli): report quota usage deltas around permanent purge batches#36
FeathBow merged 1 commit into
mainfrom
feat/quota-observed-delta

Conversation

@FeathBow

@FeathBow FeathBow commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

Wires the action/batch result contract and the reusable quota probe into the three permanent-cleanup batches — direct clean --purge, clean expiry purge, and explicit trash purge — so each one reports a read-only quota usage delta measured around the mutation. It takes a pre snapshot, runs the mutation, takes a post snapshot, and reports a signed per-scope delta. The observation report is additive: default JSON/human output and exit codes are otherwise unchanged.

Guarantees

  • Never blocks or alters the mutation. A failed pre canonicalization, a provider error, or a non-absolute request is captured and reported as unavailable; the mutation still runs and its result and exit code are preserved. Observation failures cannot change what is deleted or the process exit status.
  • Only comparable snapshots yield a delta. Provider, data source, filesystem, mount identity (device major/minor and source), subject, and the observation anchor must all match between the before and after probes, so a mount replaced between them is reported as an explicit incomparable dimension rather than a fabricated delta. Anchors that resolve to the same scope fold to a single probe.
  • Honest numbers. Signed deltas use i128 and are never clamped or sign-flipped; JSON carries the full signed values (via arbitrary_precision), and human output states plainly that the observed change is not attributed to degu.
  • Housekeeping is observed too. An entry-empty plan that still has aged claim markers now runs and observes that housekeeping on the human path as well, instead of the previous early Trash is empty. return.

Residual boundary

A subject with privileged mount-namespace control can still stage an ABA mount swap within a single probe window to influence a reporting-only delta. This grants no mutation authority, never changes deletion selection or exit code, and is the same hostile-root boundary degu already documents; closing it would require Linux 5.14+ quotactl_fd or an inherited-FD execution model and is out of scope here.

Validation

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings on the host and for x86_64-unknown-linux-musl: clean.
  • cargo test --workspace on the host and on a non-root Linux host under umask 002: green. Unit tests pin the signed-delta boundaries and JSON round-trip, the pre→execute→post ordering, non-blocking behavior on canonicalization/provider/relative-request failure, per-identity folding (including refusal to fold across filesystems or a replaced mount), a partially-failed batch still being observed, and non-causal, escape-safe human copy; integration tests cover direct/expiry/trash purge end-to-end and the empty-plan housekeeping observation.

Wraps the three permanent-cleanup batches — direct `clean --purge`, clean
expiry purge, and explicit `trash purge` — with a read-only quota
observation: a pre snapshot, the mutation, a post snapshot, and a signed
per-scope usage delta. Observation is strictly best-effort and never
blocks or alters the mutation: a failed pre canonicalization, provider
error, or non-absolute request is reported as unavailable, and the
mutation result and exit code are preserved regardless.

A delta is reported only when the before/after snapshots are comparable —
provider, data source, filesystem, mount identity (device major/minor and
source), subject, and observation anchor must all match — so a mount
replaced between the two probes yields an explicit incomparable result
rather than a fabricated delta. Anchors resolving to the same scope fold
to one probe. Signed deltas use i128 and are never clamped; JSON carries
the full signed values and human output states the observed change is not
attributed to degu.

Empty entry plans still run, and observe, the aged-claim-marker
housekeeping the human path previously skipped. The observation report is
additive: default JSON/human output and exit codes are otherwise
unchanged.

A subject with privileged mount-namespace control can still stage an ABA
mount swap within a single probe to influence a reporting-only delta; that
grants no mutation authority and never changes deletion selection or exit
code, and remains the existing hostile-root boundary.
@FeathBow
FeathBow force-pushed the feat/quota-observed-delta branch from 14716ca to 5874c5d Compare August 9, 2026 16:44
@FeathBow
FeathBow requested a balanced review from Copilot August 9, 2026 16:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (2)

crates/degu/src/commands/trash/purge.rs:29

  • plan.has_housekeeping_scope() is effectively always true for plan_purge_all(): trash_roots() always yields at least the state root (lifecycle/storage.rs:173-186), and plan_all_trash() always pushes one batch per root (lifecycle/purge.rs:103-125). That makes the TRASH_IS_EMPTY early-return and the not_attempted_action(..., NotStartedReason::Empty) branch below effectively unreachable, but they still add branching complexity to the command.

Consider simplifying by removing the has_housekeeping_scope() checks here and always going through the planned+coordinate observation path (or otherwise keying the “empty” fast-path off a condition that can actually occur).

        if !plan.has_housekeeping_scope() {
            return stdoutln!("{}", super::output::TRASH_IS_EMPTY);
        }

crates/degu/src/lifecycle/purge/plan.rs:70

  • has_housekeeping_scope() currently returns !self.batches.is_empty(), but batches are always populated because trash_roots() always returns at least one root (lifecycle/storage.rs:173-186) and the purge planners always push a batch per root (lifecycle/purge.rs:64-85 and 103-125). As a result, this helper does not actually distinguish “nothing to do” cases and can mislead callers into thinking the value is meaningful.

Either remove this helper (and inline the invariant where needed) or redefine it to reflect the condition you actually care about (e.g., presence of any purge entries or housekeeping work).

    /// Even an entry-empty batch may purge aged numeric claim markers.
    pub(crate) fn has_housekeeping_scope(&self) -> bool {
        !self.batches.is_empty()
    }

@FeathBow
FeathBow merged commit 18f992c into main Aug 9, 2026
11 checks passed
@FeathBow
FeathBow deleted the feat/quota-observed-delta branch August 9, 2026 17:04
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.

2 participants