OfflineKit import-CAS capacity guard procedure

This procedure is the operator and developer response for OfflineKit’s owned volume state volume:offlinekit-import-cas. It covers the doctor measurement and the P7 pre-write pause introduced by SPRINT_20260722_025 OK-CAP, the carrier row for SPRINT_20260722_028 NPS-3.

Contract

The budget is a positive byte count. An absent budget leaves the import write path enabled unchanged and makes the doctor check report the measured size as unbudgeted, at info severity and visible. An invalid declared budget, an unmeasurable CAS, an unmeasurable carrier, arithmetic overflow, or a projected size above the budget pauses the promotion before the verified carrier is moved out of quarantine.

Do not spell the logical key directly as Doctor__State__volume__offlinekit-import-cas__BudgetBytes in Compose. Although that is the usual .NET environment-key transformation, the embedded hyphen makes it an invalid POSIX shell identifier and the image’s sh -c entrypoint drops it before dotnet starts. The shell-safe alias above is the deployment contract, and offlinekit-web maps it onto the logical key before anything reads it.

What is measured, and what is not

Only cas/ — the promoted, verified carriers this service is the custody authority for. quarantine/ and multipart-buffer/ share the same StorageRoot and are deliberately excluded: the import path deletes both on success and on failure, so folding them in would make the number jump with in-flight uploads and make a budget impossible to set. cas/ is the part that only ever grows.

Symbolic links are not followed. A CAS tree reached through a link could be counted twice or could leave the volume entirely, and either makes the number describe something other than what was provisioned.

Why the guard is on the promote path

OfflineKitImportStorage.PromoteAsync is the single write that grows the CAS: it moves a verified carrier from quarantine into cas/sha256/{xx}/{digest}. The guard runs there, and its position within that method is load-bearing:

The verified carrier is left in quarantine when a promotion is paused. A paused write that also destroyed the operator’s upload would turn a capacity problem into a data-loss one.

The measurement and the pause share one authority

Both go through OfflineKitPartsOwnedStateMeasurer.MeasureCasBytes over the same root under the same state id. This is deliberate: two implementations would let an operator read a healthy capacity check while writes were being refused, which is the confusion a capacity guard exists to remove.

Measured zero versus unmeasurable

A CAS directory that exists and is empty measures 0 bytes and reports healthy. That is a true statement.

A CAS directory that is missing throws rather than reporting 0. On a host that started this cannot be a misconfiguration — OfflineKitImportOptions.BindAndValidate creates all four storage roots at composition, so an absent cas/ means the tree went away under a running process: the volume was unmounted, remounted elsewhere, or removed. The doctor check reports that as an unhealthy Warning naming the exception type, instead of reporting 0 bytes and “healthy” for a store that is not there.

A subtree that cannot be enumerated also throws. A partial total reported as a complete one would silently under-report a store that may be over budget.

Operator response

The check reports above 80% or over budget

  1. Read the current size from the check evidence (state.size.bytes) and the declared budget (state.size.budget-bytes).
  2. Reclaim by removing CAS objects for carriers no longer needed. The CAS is content-addressed by carrier digest under cas/sha256/{xx}/{digest}; the custody ledger offlinekit.bundle_imports names which digests are still referenced. Do not remove an object a ledger row still points at — the import record would then name content the estate cannot produce.
  3. Or raise OFFLINEKIT_IMPORT_CAS_BUDGET_BYTES if the volume genuinely has room and the budget was set too low. Raising the budget without checking the volume converts a capacity warning into a disk-full outage.

An import returns HTTP 507 CAS_WRITE_PAUSED

The verified carrier is still in quarantine and was not promoted. The response message carries the projected size, the budget, the current size and the carrier size. Reclaim as above, then re-submit the same carrier — the import is idempotent on the carrier digest.

The check reports offlinekit-import-cas-measurement-unavailable

The measured tree is gone or unreadable from the running process. Check that the volume carrying OfflineKit:Import:StorageRoot is still mounted in the container and that the process can read it. Until it is measurable the P7 guard pauses every promotion, which is the intended fail-closed behaviour: an estate that cannot see its store must not keep writing to it.