Scanner CAS capacity guard procedure
This procedure is the operator and developer response for Scanner’s owned object-store state bucket:scanner-artifacts. It covers the doctor measurement and the P7 pre-write pause introduced by SPRINT_20260722_017 SCN-CAS.
Contract
- Canonical bucket:
scanner-artifacts - Owned-state id:
bucket:scanner-artifacts - Logical budget key:
Doctor:State:bucket:scanner-artifacts:BudgetBytes - Shell-safe deployment variable:
SCANNER_CAS_BUDGET_BYTES - Doctor check id:
doctor.standard.state.size-budget.bucket-scanner-artifacts - Stable write failure:
scanner-cas-write-paused - Stable measurement failure:
scanner-cas-measurement-unavailable
The budget is a positive byte count. An absent budget leaves the existing write path enabled and makes the doctor check report the measured size as unbudgeted. An invalid declared budget, an unmeasurable current size, an unmeasurable payload length, arithmetic overflow, or a projected size above the budget pauses the primary-bucket write before the backing driver’s PutAsync is called.
Scanner-web registers the owned-state doctor check. Both scanner-web and scanner-worker receive the P7 decorator through AddScannerStorage, and both hosts map SCANNER_CAS_BUDGET_BYTES to the canonical logical key before storage is registered.
Do not spell the logical key directly as Doctor__State__bucket__scanner-artifacts__BudgetBytes in Compose. Although that is the usual .NET environment-key transformation, the embedded hyphen makes it an invalid POSIX shell identifier; Scanner’s sh -c image entrypoint drops it before dotnet starts. The shell-safe alias above is the deployment contract.
Driver capability
The shipped object store is SeaweedFS. Scanner connects to SeaweedFS’s S3-compatible endpoint through the S3 driver and measures the whole bucket or a prefix with paginated ListObjectsV2. Here, S3 names the protocol/client used to enumerate object sizes; it is not a second product store. Authentication uses the existing SigV4 access-key/secret-key contract (STELLAOPS_S3_ACCESS_KEY_ID and STELLAOPS_S3_SECRET_ACCESS_KEY), not a bearer token. Missing object sizes, a truncated response without a token, token repetition, and byte-count overflow fail measurement; none becomes zero.
The legacy compatibility-only native RustFS client has an evidenced contract only for PUT|GET|DELETE /api/v1/buckets/{bucket}/objects/{key}. It has no evidenced list/size route. Its measurement therefore fails immediately with scanner-cas-measurement-unavailable and performs no speculative HTTP request. Do not add a guessed listing route and do not replace the failure with 0.
For a legacy RustFS-native estate, choose one of these reviewed remediations before declaring the capacity budget:
- move Scanner to the supported S3-compatible driver and grant it bucket-list permission as well as its existing object permissions; or
- add an owner-approved, authenticated server-side list/size contract to RustFS, then implement and test that exact contract in the Scanner driver.
Until one is complete, setting a budget on the native RustFS path deliberately pauses primary CAS writes because the precondition cannot be proven.
Triage a failure
- Capture the stable failure code, service role, configured object-store driver, configured bucket, budget value, and correlation/scan id. Do not remove the guard merely to make a scan proceed.
- Confirm the configured bucket is exactly
scanner-artifacts. A different primary bucket is a different owned state and is rejected by this guard. - For SeaweedFS over S3, verify the Scanner SigV4 credential can list the canonical bucket and that every truncated page returns a new continuation token. A missing or repeated token is a backing endpoint failure, not an empty bucket.
- Compare the doctor evidence fields
state.size.bytesandstate.size.budget-bytes. If the state is legitimately full, apply the approved retention/capacity action; do not delete immutable CAS evidence outside its retention authority. - Restore a positive, reviewed budget and rerun the check. Preserve the before and after doctor results plus the write-pause log in the window evidence.
Red/green acceptance in an approved window
- Record the current driver, bucket inventory/size, budget, and service image digest.
- On a SeaweedFS-over-S3 non-production or owner-approved disposable path, set
SCANNER_CAS_BUDGET_BYTESbelow the measured current size on both scanner-web and scanner-worker, then restart/reload both roles through the normal configuration procedure. - Verify the doctor check is Critical/unhealthy and reports the real non-zero size.
- Attempt one owner-approved disposable Scanner CAS write. Verify
scanner-cas-write-pausedand prove by before/after object inventory that no object was written. - Restore the exact prior budget, verify the doctor result returns to its expected state, and repeat one authorized write-path smoke test.
The scanner-web path completed this proof in the approved 2026-08-23 window. Image sha256:00a9a0f97b7102e920f429470935786cf4843038de18da70babce04acc03e6a4 measured 142,432,843 bytes with no budget, reported Critical/unhealthy with SCANNER_CAS_BUDGET_BYTES=1, and refused a tenant-valid SBOM upload with scanner-cas-write-paused; the before/after measurement remained exactly 142,432,843 bytes. Removing the override restored Info/healthy with the alias absent. Scanner-worker was not restarted in that window, and the post-restore green write in step 5 was omitted because it would create durable state; shared composition and focused tests cover both roles and the normal write path.
Important limit: this is not a hard global quota
The guard performs measure -> compare -> write. Concurrent scanner-web or scanner-worker replicas can measure the same starting size and both proceed. The check is therefore a fail-closed pre-write safety gate and an observable P7 forcing function, not an atomic cross-replica quota reservation. It can also conservatively count an overwrite as additional bytes.
A hard global invariant requires an owner-approved atomic reservation/commit protocol or an enforceable backend quota with equivalent failure semantics. Until that exists and is tested under concurrent writers, no sprint or runbook may claim hard estate-wide quota enforcement.
