fix(oci): daily pull limit counts logical pulls, not every manifest request (BUNYIP-43) #55

Merged
nrupard merged 2 commits from fix/bunyip-43-oci-pull-count into main 2026-06-03 19:34:29 +02:00
Owner

Closes BUNYIP-43. OCI_PULLS_PER_USER_PER_DAY metered every manifest request, so one docker pull of a multi-arch image burned 3+ of the allowance (tag HEAD + tag GET + digest GET). With the default 50 a member got only ~16 real pulls; verified in BUNYIP-35 (limit=3, first pull consumed the whole day's allowance).

Fix (issue option 1)

In get_manifest, meter (daily cap + concurrency) only TAG-addressed manifest requests; digest-addressed requests (the multi-arch platform-manifest follow-ups within the same pull) are served but not metered. A docker pull resolves the tag and then fetches the platform manifest by digest, so a pull now meters at most its tag requests and the digest follow-ups are free. Documented the semantics on OciConfig::pulls_per_user_per_day.

First cut metered only GET-by-tag, but live testing showed the docker client resolves via HEAD-by-tag then GETs by digest, so that variant counted zero. Metering any tag-addressed request (!is_digest) is the correct rule and matches the issue.

Verification

clippy --workspace --all-targets -D warnings clean, fmt clean, 209 lib tests pass.

Live: with oci_pull_daily_counts truncated, just verify-oci (two docker pulls) records 2 (one per logical pull) instead of 6 (3 per pull pre-fix).

🤖 Generated with Claude Code

Closes BUNYIP-43. `OCI_PULLS_PER_USER_PER_DAY` metered every manifest request, so one `docker pull` of a multi-arch image burned 3+ of the allowance (tag HEAD + tag GET + digest GET). With the default 50 a member got only ~16 real pulls; verified in BUNYIP-35 (limit=3, first pull consumed the whole day's allowance). ## Fix (issue option 1) In `get_manifest`, meter (daily cap + concurrency) only TAG-addressed manifest requests; digest-addressed requests (the multi-arch platform-manifest follow-ups within the same pull) are served but not metered. A `docker pull` resolves the tag and then fetches the platform manifest by digest, so a pull now meters at most its tag requests and the digest follow-ups are free. Documented the semantics on `OciConfig::pulls_per_user_per_day`. First cut metered only GET-by-tag, but live testing showed the docker client resolves via HEAD-by-tag then GETs by digest, so that variant counted zero. Metering any tag-addressed request (`!is_digest`) is the correct rule and matches the issue. ## Verification clippy `--workspace --all-targets -D warnings` clean, fmt clean, 209 lib tests pass. Live: with `oci_pull_daily_counts` truncated, `just verify-oci` (two docker pulls) records **2** (one per logical pull) instead of **6** (3 per pull pre-fix). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(oci): daily pull limit counts logical pulls, not every manifest request (BUNYIP-43)
All checks were successful
Check / fmt / clippy / build / test (pull_request) Successful in 1m29s
53a7dfd9c8
OCI_PULLS_PER_USER_PER_DAY metered every manifest request, so one `docker pull` of a multi-arch image burned 3+ of the allowance (tag HEAD + tag GET + digest GET), making the default of 50 yield only ~16 real pulls.

Meter (daily cap + concurrency) only TAG-addressed manifest requests in get_manifest; digest-addressed requests (the multi-arch platform-manifest follow-ups within the same pull) are served but not metered. A docker pull resolves the tag and then fetches the platform manifest by digest, so a pull now meters at most its tag requests and the digest follow-ups are free. Documented the semantics on OciConfig::pulls_per_user_per_day.

(First cut metered only GET-by-tag, but live testing showed the docker client resolves via HEAD-by-tag then GETs by digest, so that variant counted zero; metering any tag-addressed request is the correct rule and matches the issue's option 1.)

Verified live: with the counter truncated, `just verify-oci` (two docker pulls) now records 2 in oci_pull_daily_counts (one per logical pull) instead of 6. clippy/fmt clean, 209 lib tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: nrupard <natrsmith11@gmail.com>
fix(oci): address PR #55 review - extract should_meter + test, fix doc, note tradeoffs
All checks were successful
Check / fmt / clippy / build / test (pull_request) Successful in 1m7s
Create release / Create release from merged PR (pull_request) Has been skipped
63e1eb7c8e
- Extract the tag-vs-digest metering decision into a pure should_meter(reference) fn with a unit test (should_meter_tag_but_not_digest), so the BUNYIP-43 rule has automated coverage and a future refactor cannot silently re-meter digest follow-ups (the handler is otherwise hard to unit-test).
- Fix the OciConfig::pulls_per_user_per_day doc: it claimed "one docker pull counts once: only a GET by tag is metered, not the HEAD", but the code meters any tag-addressed request (HEAD or GET), so a client doing both meters twice. Reworded to match, and noted a by-digest pull is unmetered.
- Comment the early drop(_guard) (release the concurrency slot before cloning response bytes).
- Document the concurrency side effect: gating the whole limiter.acquire means digest requests lose concurrency bounding; filed PSA-42 (dunite UsageLimiter concurrency-only acquire) and referenced it.

Known, accepted tradeoffs (fair-use cap on authenticated + entitled members, not a security boundary): a direct by-digest pull is unmetered, and digest manifest fetches are not concurrency-bounded until PSA-42 lands.

clippy/fmt clean, 210 lib tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: nrupard <natrsmith11@gmail.com>
nrupard deleted branch fix/bunyip-43-oci-pull-count 2026-06-03 19:34:29 +02:00
Sign in to join this conversation.
No reviewers
No labels
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
psa-systems/bunyip!55
No description provided.