You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CAS (metadata_type = content_addressed MergeTree backend) — consolidated audit tracking
This is a tracking issue for a static-analysis audit of the Content-Addressed Storage (CAS) MergeTree
disk backend. It now consolidates 135 distinct findings from a fresh re-run in which all 39 audits were
redone from scratch against the current PR, each with a unique CAS-### id.
The CAS-### ids are renumbered for this round and do NOT correspond to the previous numbering.
Nothing was carried forward; numeric coincidences with the old catalogue are meaningless. Where an item is
carried over from the previous triage, the old id is referenced inline as prev CAS-###.
Important
This is a static/logical review. Many items are expected to be by-design, not-a-bug, latent, or
already-handled. The checklist is meant to be triaged item-by-item — please dismiss or resolve freely.
This round was audited against cas-code-only-strip at base commit 842f2b37b8f: the same code as PR [experimental] CAS (Content-addressed storage) over shared object storage for antalya-26.6 #2159, with docs, comments and tests removed so that only the core code remains. Prior rounds showed comments and docs drifting out of step with behaviour, so stripping them forces every finding to rest on the code alone.
Finding the full detail for a CAS-###
The checklist line is a one-line summary. For the complete write-up, open NEW-FINDINGS.md in the
2026-08-12 gist and search for the id: each entry lists the class, every code anchor, the impact and
the exact trigger, plus a Reported by: line naming the source audit findings (e.g. CAS-012 → ad6-6, ad6-10). Those ids are prefixed with their report name, so ad6-* lives in ad6-s3-lifecycle-cross-region.md in the same gist — search that file for the id to get the full
reasoning, the consequence chain and the code walk-through. NEW-FINDINGS.md also ends with a table
mapping each of the 39 reports to the ids it produced, for the reverse direction.
How to triage
For each item, when reviewed:
Check the box once it is triaged (resolved, dismissed, or filed as its own issue).
Add reasoning as a comment referencing the CAS-### id.
Verdicts are pre-filled in this round: items inherited from the previous triage carry that verdict, with the old id and quoted reasoning, and are pre-checked; items previously marked ✅ fixed but found again are flagged ⚠️ was-fixed / still-present and are the priority.
Severity is the highest assigned by any source audit. Class tags: DATA-LOSS · LEAK · LIVENESS · CONCURRENCY · INTEGRITY · SECURITY · DECODE/DoS · COMPAT · FEATURE-GAP · PERF/SCALE · OBSERV/DAY2 · COMPLIANCE · CONFIG · TEST-GAP · CORRECTNESS.
Grading rule used this round: a fail-closed loud failure grades below silent corruption, and operability, cost
and scale gaps are not High on their own. Resulting counts: 24 graded High, of which 3 are already-accepted
design positions (pre-checked), leaving 21 open High.
🔴 High
CAS-001 Shadow/FREEZE and backup namespaces are pool-global while exclusion primitives are per-server-root, so two servers share one unfenced shadow ref table and UNFREEZE on either deletes the other's frozen parts · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-070) · evidence strengthened — CA/ContentAddressedMetadataStorage.cpp:897-900
CAS-002 Manifest-trust adoptEvidence bypasses the durable condemn marker and EDGE-BEFORE-OBSERVE, so a committed manifest can name a blob GC has already deleted, with no source to re-upload from · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-031) · evidence strengthened · anchor relocated — CA/Pool/CasPartWriteTxn.cpp:478-486
CAS-003 The GC lease has no TTL, is stealable on differential observation, and the destructive phases are never revalidated, so two GC actors can run destructive phases concurrently on one pool · CONCURRENCY — resolution: 🔴 still-present (carried from prev CAS-032) · evidence strengthened — CA/Gc/CasGc.cpp:3155-3186
CAS-004GC REBUILD has no writer/mount interlock and "read-only" does not gate writes, so a rebuild is accepted on a live writable disk with inserts in flight and the mount census result is discarded · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-015, which was 🟡 partial) · evidence strengthened — contradicts the partial's premise that "mount-lease should take correctly now" — CA/Gc/CasGc.cpp:2725
CAS-005 A repointed committed ref is unrevertible and durable CAS mutations happen before commit() with a silent best-effort rollback, so readers can observe aborted and intermediate states · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-021 + prev CAS-097) · evidence strengthened — CA/ContentAddressedTransaction.cpp:280-289
CAS-006 Cross-namespace RENAME/moveDirectory is a per-ref non-atomic migration ending in an unconditional source drop with no reconciler, so a crash mid-rename splits the refs and drops any added during the walk · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-022 + prev CAS-044) · evidence strengthened — CA/ContentAddressedTransaction.cpp:846-874
CAS-007 Nested server_root_id is accepted and victim selection is prefix-based, so SYSTEM CAS DROP POOL MEMBER on srid=a erases the namespaces and control objects of a live member srid=a/b · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-064) · evidence strengthened — escalated from collision to nesting, a new destructive consequence — CA/Pool/CasServerRoot.h:104-134
CAS-008 Content addressing defaults to a non-cryptographic 128-bit hash and reads never re-verify, so a chosen collision silently substitutes data for every future reader of that content · SECURITY — resolution: 📐 by-design / YAGNI (prev CAS-003 + prev CAS-005 — Filimonov: "selectable hash landed — closes weak-collision concern; will not re-verify hash on read — incompatible with "CH does not slow down""; "S3 has many durability nines and hashes objects itself; CAS will not re-hash on read") — CA/ContentAddressedSettings.cpp:33
CAS-009 An occupied content address is admitted on existence alone, and no re-upload, resurrect or staged body is ever re-hashed — every admission is size-checked, never digest-checked · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-038) · evidence strengthened — partially inherited: the "do not re-verify an already-stored object" half is closed by prev CAS-005 (📐 — Filimonov: "S3 has many durability nines and hashes objects itself; CAS will not re-hash on read"); the scratch/staged-body half remains open — CA/Pool/CasPartWriteTxn.cpp:250-305
CAS-010 An empty conditional token turns a fenced write into an unconditional clobber — putOverwrite validates only the token type, so an absent ETag plus an absent-HEAD sends no precondition at all · INTEGRITY — resolution: 🆕 new this round — CA/Backend/CasObjectStorageBackend.cpp:165-173
CAS-011 Plain-object writes bypass the request controller and the margin-checked fence and never resolve their indeterminate outcomes, retrying one key up to 100 times with zero backoff · INTEGRITY — resolution: 🆕 new this round — CA/Pool/CasPlainObjects.cpp:21-41
CAS-012 Lifecycle rules, Object Lock and storage-class transitions are undetected and fail open, so an expiration rule silently deletes live blobs and Glacier reads have no restore-and-retry path · DATA-LOSS — resolution: 📐 by-design / 🛠 docs (prev CAS-016 + prev CAS-017 — Filimonov: "lifecycle expiration must be off like versioning; add explicit user-facing bucket requirements; hard to detect without admin access"; "do not enable Object Lock/WORM/retention/lifecycle/versioning on the bucket — plain bucket only") · the Glacier restore-and-retry half stays open under prev CAS-052 — CA/Backend/CasObjectStorageBackend.cpp:272-282
CAS-013 One node admitting a hash algorithm rewrites the pool-wide reader floor to its own build number — mounting one disk with blob_hash_allow_new=1 locks every older build out of the entire pool · COMPAT — resolution: ↗ out-of-scope / deferred (prev CAS-009 — Filimonov: "needs attention later; not a blocker; model may be wrong") — CA/Pool/CasPoolMeta.cpp:72
CAS-014 The file-placement classifier is a closed suffix allowlist that misses shipped MergeTree file names (primary.cidx, .mrk4, secondary-index data files), sending them down the fully-in-memory inline path · CORRECTNESS — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:65-73
CAS-015 Waits on CAS single-flight, leader and recovery paths have no deadline and no cancellation, so a stalled store hangs concurrent INSERTs, readers of a recovering namespace and DROP TABLE · LIVENESS — resolution: 🔴 still-present (carried from prev CAS-034 + prev CAS-083) · evidence strengthened — CA/Pool/CasRefLedger.cpp:1457-1492
CAS-016attempt_timeout_ms never reaches the wire and the blob payload read bypasses the CAS backend entirely, so reads run on the default 500-retry profile with no deadline and no classification · LIVENESS — resolution: 🆕 new this round — CA/Backend/CasRequestControl.h:84
CAS-017 Namespace removal latches read/write admission closed before anything is durable, and the ref lane has terminal states with no exit, so a transient backend error leaves the table permanently unusable · LIVENESS — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:3451-3458
CAS-018 Latches and leadership are set or released outside RAII and noexcept/destructor paths allocate, so a throw under a memory limit deadlocks the namespace forever or terminates the process · LIVENESS — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:1519-1541
CAS-019 Part-folder single flight is keyed by ref only, collapsing different manifest ids onto one key, so a follower straddling a repoint receives the leader's view for a different manifest than it resolved · CORRECTNESS — resolution: 🆕 new this round — CA/Parts/PartFolderAccess.cpp:231-269
CAS-020getStorageObjects returns objects that are not the file's bytes because the envelope offset is dropped, so MOVE PART/PARTITION TO DISK and TTL moves produce a corrupt destination part with no error · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-047) · evidence strengthened — now with a concrete corrupting consumer — CA/ContentAddressedMetadataStorage.cpp:1336-1340
CAS-021 Ambiguous conditional-write outcomes are reported as definite ones: content equality is treated as proof of our own authorship, and a landed-then-timed-out write is reported as another writer's object · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-035) · evidence strengthened — now bidirectional — CA/Backend/CasRequestControl.cpp:427-435CAS-021: Ambiguous conditional-write outcomes are reported as definite ownership #2207
CAS-022 The orphan-manifest sweep applies no protection at all to a manifest whose namespace has no catalog row — it deletes the body and emits BlobSourceRetirement for every entry during a namespace's first write · DATA-LOSS — resolution: 🆕 new this round — CA/Gc/CasOrphanManifestSweep.cpp:546
CAS-023 Deletes are accepted and silently do nothing when GC is disabled or the pool has settled as vanished — with gc_enabled=false every manual reclamation verb is refused with BAD_ARGUMENTS · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-043) · evidence strengthened — partially inherited: the GC-deferred-reclaim-latency half is closed by prev CAS-018 (📐/❌ — Filimonov: "erase SLA is not part of the disk contract; operator can GC RUN anytime"); the gc_enabled=false accept-and-do-nothing and vanished-pool silent-success halves are open — CA/ContentAddressedMetadataStorage.cpp:611
CAS-024 Two CAS disks sharing a pool and a server_root_id resolve to one (namespace, ref), so a MOVE PARTITION TO DISK between them publishes then drops the same ref and the part is gone, silently · DATA-LOSS — resolution: 🔴 still-present (carried from prev CAS-064) · evidence strengthened — escalated to silent data loss — CA/ContentAddressedMetadataStorage.cpp:886-889
🟠 Medium
CAS-025GC REBUILD discards the condemn universe and permanently orphans already-unreferenced blobs — prior_runs starts empty, the fold takes only +1 deltas, and the graduation guard is vacuous · LEAK — resolution: 🔴 still-present (carried from prev CAS-015) · anchor relocated — CA/Gc/CasGc.cpp:2809-2824
CAS-026 Relink treats pool_uuid equality as proof of "same bucket" and publishes the adopted part unverified — no presence check on any blob dependency and check_consistency=false on publish · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-031) · evidence strengthened — src/Storages/MergeTree/DataPartsExchange.cpp:313-330
CAS-027 Any bucket-credential peer can permanently disable, fence or misdirect another member; there is no intra-pool authentication — one PUT to a peer's owner/mount retires it or steals its slot · SECURITY — resolution: 📐 by-design (prev CAS-004 + prev CAS-211 — Filimonov: "bucket credential = whole trust boundary; all pool users same trust"; "bad actor with pool access is already doomed; provenance self-asserted / content-equality observable") — CA/Pool/CasServerRoot.cpp:68-79
CAS-028 Blob keys are unsalted pool-global content hashes: no per-subject shred, guessable residue, and a dedup confirmation oracle observable from system.cas_log · SECURITY — resolution: 📐 by-design / ❌ wontfix (prev CAS-019 + prev CAS-211 — Filimonov: "this is the essence of CAS dedup — will not "fix"") · the crypto-shred half stays open under prev CAS-071 — CA/Pool/CasPartWriteTxn.cpp:145
CAS-029 The provider dialect is declared by configuration and never detected, so the one bucket-versioning precondition runs only for GCS clients and fails open when it does run · DATA-LOSS — resolution: ⚠️ was-fixed / still-present (prev CAS-011 was ✅ "CAS checks at startup that versioning is off; versioned buckets unsupported") — the check is skipped entirely on AWS S3 and every S3-compatible store, and downgraded to a warning on GCS, so the residue is detection rather than support — CA/Backend/CasObjectStorageBackend.cpp:53-67
CAS-030skip_access_check removes every bucket-configuration defense, is not recorded anywhere in the pool, and the decommission remount hard-codes it · CONFIG — resolution: 🆕 new this round — CA/Pool/CasPool.cpp:339-347
CAS-031 The conditional-write contract is validated only for single-PUT and then assumed for multipart, so on a store that ignores If-None-Match on CompleteMultipartUpload every large blob loses exclusivity · INTEGRITY — resolution: 🆕 new this round — CA/Backend/CasProbe.cpp:42
CAS-032 Zero cross-region / replicated-bucket awareness — nothing binds a pool identity to an endpoint or region, so failover onto a CRR destination is indistinguishable from the primary · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-051) · anchor relocated — CA/Pool/CasPoolMeta.cpp:100-104
CAS-033 All reclamation is gated on a whole-pool "clean pass" predicate with no bound and no retention signal — one anomaly anywhere suppresses every destructive action pool-wide indefinitely · LEAK — resolution: 📐 by-design (prev CAS-033 — Filimonov: "prefer fail-closed safety under GC uncertainty; reclaim may stall") — CA/Gc/CasGc.cpp:2063-2064
CAS-034 Per-round reclamation budgets sit below the steady-state creation rate — 5,000 ref objects per 60 s round and one 1,000-key janitor page, while every part commit creates two ref objects · LEAK — resolution: 📐 by-design / ❌ wontfix (prev CAS-018 + prev CAS-106 — Filimonov: "erase SLA is not part of the disk contract; operator can GC RUN anytime; GC cadence ~5–10 min; GDPR faster-than-that unlikely") · the budget-versus-rate arithmetic is not disposed of by that verdict and should be carried as an open question — CA/ContentAddressedSettings.cpp:46
CAS-035 The GC fold and its enumerations are O(total pool) every round, unbudgeted, with unbounded peak memory — the full ref LIST is retained with no cursor and is called even on deferred rounds · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-057 + prev CAS-050) · evidence strengthened — contradicts prev CAS-057's 🟡 "single LIST, no fan-out" mitigation — CA/Gc/CasBlobInDegree.cpp:484-555
CAS-036 Any bucket-sourced control object is materialized in memory unbounded and one planted object costs quadratic CPU — a multi-gigabyte _pool_meta or ref-log body OOMs the victim before any format check · DECODE/DoS — resolution: ⚠️ was-fixed / still-present (prev CAS-026 was ✅ "protobuf/binary formats removed → self-describing text") — removing protobuf removed the ParseFromArray specifics, but bucket-sourced bytes are still sized from attacker-declared values with no cap, at one of the two lines the fix cited — CA/Backend/CasObjectStorageBackend.cpp:284-293
CAS-037 Every CAS numeric field silently wraps mod 2^64 and offset + length overflows on the read path, so a planted manifest sz produces a wrapped read window that defeats every decoder range gate · DECODE/DoS — resolution: ⚠️ was-fixed / still-present (prev CAS-039 was ✅ "logical_size removed from envelope") — deleting the field removed the one documented exploit; the arithmetic itself was never hardened and the same wrap is now reachable on the read path — CA/Formats/CasTextFormat.cpp:193-223
CAS-038 Decoders make liveness- and safety-critical fields optional and default them to the least-safe value, so a truncated mount lease decodes as "expired, unfenced" and junk fold seals decode unvalidated · DECODE/DoS — resolution: 🔴 still-present (carried from prev CAS-077) · evidence strengthened — now systemic across four decoders — CA/Formats/CasServerRootFormats.cpp:147-169
CAS-039gc_shards is adopted from bucket bytes with no upper bound and silently overrides the node's configured value while sizing vectors and loop bounds, on only a >= 1 check · DECODE/DoS — resolution: 🔴 still-present (carried from prev CAS-066) · evidence strengthened · anchor relocated — CA/Formats/CasPoolMetaFormat.cpp:116
CAS-040 The part-manifest payload-zone banner is written raw and validated only on decode, so an entry path containing LF encodes successfully and produces a committed part that can never be decoded again · INTEGRITY — resolution: 🆕 new this round — CA/Formats/CasPartManifestFormat.cpp:64-67
CAS-041 The manifest payload digest is recomputed by canonical re-encode of the decoded model, so any tolerated or foreign field reads as CORRUPTED_DATA — and the same implementation costs 2x work per decode · COMPAT — resolution: 🔴 still-present (carried from prev CAS-027) · evidence strengthened — same root cause, harder consequence: silent additive-field loss became a hard fail — CA/Formats/CasPartManifestFormat.cpp:263-267
CAS-042 One global build number is stamped as every object's minimum reader and the per-format change-point registry is populated but never consulted, so one generation bump invalidates every format · COMPAT — resolution: ↗ out-of-scope / deferred (prev CAS-009 — Filimonov: "needs attention later; not a blocker; model may be wrong") · the Roster throw stays open under prev CAS-076 — CA/Formats/CasFormat.cpp:82-93
CAS-043 The relink handshake negotiates a replication protocol number that says nothing about CAS generation, and the generation mismatch escapes the byte-fetch fallback, so a skewed fetch fails outright · COMPAT — resolution: ⚠️ was-fixed / still-present (prev CAS-209 was an adjudicated verified-safe 📐 "fail-closed publish-nothing → byte-fetch fallback; format bumps caught by the manifest's own compatibility check") — the fallback's catch filters CORRUPTED_DATA only while the generation check throws UNKNOWN_FORMAT_VERSION, so a skewed fetch fails outright instead of degrading — CA/ContentAddressedMetadataStorage.cpp:1610-1619
CAS-044 The 16 MiB per-part inline budget is enforced only at commit with no fallback, so a legitimately wide part fails its INSERT permanently and reproducibly with no re-classification to blob placement · FEATURE-GAP — resolution: 🔴 still-present (carried from prev CAS-100) · anchor relocated — CA/Pool/CasPartWriteTxn.cpp:54
CAS-045 The part-folder view cache accounts every retained manifest as 256 bytes because both producers hardwire .manifest_size = 0, so part_folder_cache_bytes is inoperative and nothing is excluded as oversized · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-049) · evidence strengthened — contradicts the 🟡 "bytes+count LRU" mitigation — CA/Parts/PartFolderAccess.cpp:128-131
CAS-046 Local scratch staging is unreserved, unaccounted, uncapped, held for the whole transaction and never swept at startup, while the CAS disk reports no free space at all · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-096) · evidence strengthened — CA/ContentAddressedTransaction.cpp:1223-1235
CAS-047 The blob upload pool is process-global, 16 threads with a 16-slot queue, and enqueue blocks — one wide part exceeds the queue by itself and all CAS uploads for every disk serialize behind it · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasBlobUploadPool.cpp:45-49
CAS-048 A CAS part publish runs object-store I/O while DataPartsLock is held, so a throttling or 5xx bucket stalls the table's parts lock and blocks every SELECT, merge scheduling and part-set mutation · LIVENESS — resolution: ⚠️ was-fixed / still-present (prev CAS-006 was ✅ "durable publish no longer runs under DataPartsLock") — the off-lock publish exists on the replicated renameParts() path but was not applied to the covering-part path used by DROP/REPLACE PARTITION; the prior round said the same in the untriaged NEW-BC7-1 — src/Storages/MergeTree/MergeTreeData.cpp:5918-5922
CAS-049GC STOP, shutdown and FSCK serialize behind whole in-flight unbounded scans, so an operator cannot stop GC, forget a disk or shut down while a round or scan is in flight against a slow bucket · LIVENESS — resolution: 🆕 new this round — CA/Gc/CasGcScheduler.cpp:213
CAS-050 The GC scheduler joins its thread objects outside the mutex that guards them and its threads self-exit independently, leaving a joinable-but-dead scheduler that reports itself as running · CONCURRENCY — resolution: 🆕 new this round — CA/Gc/CasGcScheduler.cpp:67-79
CAS-051 Snapshot-publish dispatch can leak its pending count, hanging quiesceRefTablesForRemount and dropNamespaceImpl forever, and its fan-out is unbounded pool-wide · LIVENESS — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:2754-2783
CAS-052 Anomaly reporting calls shared_from_this() on a possibly expiring pool, throwing bad_weak_ptr out of an unguarded region or handing a detached thread a pool being destroyed · CONCURRENCY — resolution: 🆕 new this round — CA/Pool/CasPool.cpp:972-1029
CAS-053 The ref-table runtime cache budget is enforced only at recovery, cannot evict a table being written, and its arithmetic can underflow and evict every evictable table at once · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:1149-1210
CAS-054 Ref publication re-encodes the whole namespace every 256 transactions and obtains row byte counts by re-serializing rows; debug and sanitizer builds turn every txn apply into a further O(R) re-encode · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:2741-2745
CAS-055 Every hardlinked file re-reads the source part manifest from the object store because part_folder_validate ships as always, costing hundreds to thousands of round trips per mutation · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-086) · evidence strengthened · anchor relocated — CA/ContentAddressedTransaction.cpp:816
CAS-056 A single-file write or unlink on a committed part republishes the whole manifest twice and emits one adopt event per blob entry inside the ref-log CAS lambda · PERF/SCALE — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:256-290
CAS-057moveFile/replaceFile on a committed part file always throws LOGICAL_ERROR, so the standard "write .tmp then replaceFile" pattern is unusable and unique-key delete bitmaps fail on CAS · FEATURE-GAP — resolution: 🚫 not-a-bug / 🟡 soft (prev CAS-007 — Filimonov: "should be fine — tests catch nothing; glance someday — not a blocker") · the dismissal was explicitly soft and the fresh evidence names a real caller, so this should be re-opened — CA/ContentAddressedTransaction.cpp:1030-1055
CAS-058 Cross-disk ATTACH/REPLACE PARTITION FROM into a CAS disk is unimplemented and fails part-way through the part, leaving partial state rather than being rejected up front · FEATURE-GAP — resolution: 🆕 new this round — src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:593-621
CAS-059 An encrypted disk can be layered over CAS with no capability check and reports "not content-addressed" while still routing writes into CAS, disabling every CAS-aware MergeTree hook · CONFIG — resolution: ↗ out-of-scope (prev CAS-046 — Filimonov: "CAS+encryption needs design/dev/testing; should be workable later — not now") · the missing gate itself stays open under prev CAS-113 — src/Disks/DiskEncrypted.cpp:190-208
CAS-060 Per-file random IV makes every file a unique blob, silently destroying dedup — two replicas merging identical rows produce entirely distinct blobs and nothing detects it · CORRECTNESS — resolution: ↗ out-of-scope (prev CAS-046 — Filimonov: "CAS+encryption needs design/dev/testing; should be workable later — not now") — src/Disks/DiskEncryptedTransaction.cpp:105-112
CAS-061 Only gc/state has a rebuild path; every other control object has no DR path, and because the tools open the pool through _pool_meta first, damage to that one object disables the DR tools themselves · OBSERV/DAY2 — resolution: 🔴 still-present (carried from prev CAS-063) · evidence strengthened — CA/Pool/CasPool.cpp:293-368
CAS-062SYSTEM CAS FSCK is counts-only and no repair path exists anywhere — the SQL path can say the pool is corrupt but never which keys, with no timeout and no scoping · OBSERV/DAY2 — resolution: ⚠️ was-fixed / still-present (prev CAS-013 was ✅ "SQL fsck landed; still slow vs GC — backlog to speed up") — making the diagnostic callable without making it informative does not deliver prev CAS-013's premise; the repair half remains separately open as prev CAS-093 — src/Interpreters/InterpreterSystemQuery.cpp:2534
CAS-063 The only way to clear a dead member's mount slot is a verb that first erases that member's data, and a half-decommissioned member is invisible to cas_mounts and not repairable by a re-run · OBSERV/DAY2 — resolution: 🔴 still-present (carried from prev CAS-062, which was 🛠 will-fix) · anchor relocated — CA/Tools/CasDecommission.cpp:137-183
CAS-064 No CAS decoder is fuzzed, no property-based tests exist, and three live format classes skip the shared failure-mode battery, although every decoder consumes bucket-sourced input · TEST-GAP — resolution: 📐 by-design / YAGNI (prev CAS-010 — Filimonov: "same trust model as CAS-005 — trust S3; less trust ⇒ more perf loss; no decoder fuzz mandate as gate") — src/Disks/tests/cas_format_test_battery.h
CAS-065 No CI lane exercises a native or GCS conditional-write dialect — the exclusivity guarantee CAS is built on is only ever tested against the emulated in-process backend · TEST-GAP — resolution: ⚠️ was-fixed / still-present (prev CAS-012 was ✅ "e2e tested on real S3 and GCS; Azure still not") — a static audit cannot see a manual run, but what stands is that the most safety-critical path has no repeatable regression-protecting lane — src/Disks/tests/gtest_cas_backend_contract.cpp:250-258
CAS-066 Emulated single-process mode is chosen by storage type alone with no override and only an INFO-level warning, so two servers over one shared local path both get in-process-only exclusivity · CONFIG — resolution: 🔴 still-present (carried from prev CAS-065) · evidence strengthened — the read-only-mount gap and the emulated-mode auto-selection are new sub-findings — CA/ContentAddressedMetadataStorage.cpp:509-520
CAS-067 The emulated conditional-write token is a filesystem mtime, so coarse mtime granularity validates a stale token, and its state pruning stalls permanently on clock skew · INTEGRITY — resolution: 🆕 new this round — CA/Backend/CasObjectStorageBackend.cpp:347-362
CAS-068putIfAbsentControlled swallows deterministic local failures and reports them as ambiguity, so a LOGICAL_ERROR or NOT_IMPLEMENTED that provably never landed wedges the ref lane into recovery · INTEGRITY — resolution: 🆕 new this round — CA/Backend/CasRequestControl.cpp:271-281
CAS-069 Empty catches reclassify transient read failures as corruption — a MEMORY_LIMIT_EXCEEDED on a large gc/state is indistinguishable from genuine corruption and drives a full baseline rebuild · INTEGRITY — resolution: 🆕 new this round — CA/Gc/CasGc.cpp:2633-2648
CAS-070 Remount self-healing is permanently disabled by a lost wakeup, a latched flag, or one unhandled throw — after a fence-out the mount stays fenced closed until process restart · LIVENESS — resolution: 🆕 new this round — CA/Pool/CasMountRuntime.cpp:341-369
CAS-071 Mount and pool state is read and written outside the mutex that guards it — keeper replacement races readers, fence/deadline state is torn, and pool identity is published under a TSA suppression · CONCURRENCY — resolution: 📐 by-design (prev CAS-090, whose verdict is recorded with anchors: Pool/CasMountRuntime.h:400; renewWatermarkOnce unlocked at :156-163; reassign under Pool::remount_mutex only) — CA/Pool/CasMountRuntime.h:164
CAS-072 Staged-manifest debris cleanup tracks only one precommit binding and can delete a body a live precommit still owns, leaving a durable reference to a missing object · DATA-LOSS — resolution: 🆕 new this round — CA/Pool/CasPartWriteTxn.cpp:866-884
CAS-073 The condemn marker is not incarnation-scoped, is accepted as its own proof, and is never cleared, so a marker written for a previous incarnation licenses deleting the new one · INTEGRITY — resolution: 🆕 new this round — CA/Formats/CasBlobMetaFormat.h:14-22
CAS-074 Generation prune advances a monotone cursor past still-referenced generations and the compensating hand-off is one-shot, so nothing ever revisits snap_pruned_through and those objects leak · LEAK — resolution: 🔴 still-present (carried from prev CAS-072) · evidence strengthened — CA/Gc/CasGc.cpp:2456-2500
CAS-075 The blob body is durable before its meta marker and no GC phase enumerates bodies, so a crash in the window leaves a body reclaimed by no sweep at all and excluded from the clean() verdict · LEAK — resolution: 🔴 still-present (carried from prev CAS-060) · evidence strengthened — CA/Pool/CasPartWriteTxn.cpp:423-429
CAS-076 GC seals a generation before committing gc/state, so repeated crashes accumulate complete-looking seals for generations no state references and nothing prunes them · LEAK — resolution: 🔴 still-present (carried from prev CAS-072 + prev CAS-108) · anchor relocated — CA/Gc/CasGc.cpp:2254
CAS-077 A permanently lost node pins its own manifest debris as unreclaimable — the sweep needs the owning mount lease for a watermark floor, and the only verb that removes the lease also erases its data · LEAK — resolution: 🆕 new this round — CA/Gc/CasOrphanManifestSweep.cpp:373-387
CAS-078 The namespace janitor rewinds its durable cursor to the beginning on a transient LIST failure, so one 5xx discards all prior progress on a janitor already limited to one page per round · LEAK — resolution: 🆕 new this round — CA/Gc/CasNamespaceJanitor.cpp:22-31
CAS-079 Ref-object trimming is starved by any concurrent catalog mutation anywhere in the pool — one shared catalog object means any CREATE/DROP aborts all ref-log and snapshot trimming for the round · LEAK — resolution: 🆕 new this round — CA/Gc/CasGc.cpp:2320-2336
CAS-080 A snapshot published without a checkpoint advance is not re-driven on a quiescent namespace, so the namespace pays full log replay on every recovery until the table is written again · CORRECTNESS — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:2937-3035
CAS-081 S3 staging residue is retained on abort and swept only for one's own server_root_id, so whole part-file plaintext persists after any killed INSERT and no shipped tool ever lists it · LEAK — resolution: 🔴 still-present (carried from prev CAS-060) · evidence strengthened — CA/ContentAddressedTransaction.cpp:148-172
CAS-082 There is no multipart-upload hygiene anywhere in CAS and capability-probe debris is explicitly excluded from the residual scan — incomplete uploads are billed, invisible to fsck and never aborted · LEAK — resolution: 🔴 still-present (carried from prev CAS-084) · evidence strengthened — contradicts the dismissal's premise ("MPU leftovers typical for S3, GC cleans eventually"): nothing in CAS ever cleans them — CA/Backend/CasProbe.cpp:20-32
CAS-083 Lightweight DELETE and mutations free the deleted rows' bytes only for rewritten files — surviving entries are republished against the same blobs, so deleted row bytes stay in the pool · LEAK — resolution: 📐 by-design / ❌ wontfix (prev CAS-019 + prev CAS-018 — Filimonov: "this is the essence of CAS dedup — will not "fix"") — CA/ContentAddressedTransaction.cpp:782-829
CAS-084 Reclaimed blobs are never evicted from the node-local filesystem cache, so after GC deletes a blob its bytes stay on local disk: capacity is held and deleted content is still readable · LEAK — resolution: 🆕 new this round — src/Disks/DiskObjectStorage/DiskObjectStorageCache.cpp:21-23
CAS-085always_use_copy_instead_of_hardlinks=1 makes every CAS clone and mutation throw NOT_IMPLEMENTED and nothing rejects the setting, breaking mutations, FREEZE and ATTACH/REPLACE PARTITION · FEATURE-GAP — resolution: 🔴 still-present (carried from prev CAS-112) · evidence strengthened — no longer latent: real MergeTree callers reach it — CA/ContentAddressedTransaction.cpp:363-366
CAS-086 IDisk directory and metadata queries deviate from the contract their generic callers rely on, so removeDirectory's non-empty guard never fires and a populated part directory is dropped without error · CORRECTNESS — resolution: 🔴 still-present (carried from prev CAS-048 + prev CAS-099) · evidence strengthened — now a contract-level cluster — CA/ContentAddressedMetadataStorage.cpp:1293-1305
CAS-087 The part-path parser lets a component named detached/moving outrank part-dir detection, and silently reinterprets unclassified part dirs as table-level files, so misclassification surfaces only on read · CORRECTNESS — resolution: 🔴 still-present (carried from prev CAS-073) · evidence strengthened — same file, now both directions — CA/Parts/PartPathParser.cpp:140-162
CAS-088resurrect is an unconditional, budget-free, fence-unchecked overwrite that returns a token it did not write, so two writers on one condemned blob can each believe they own the object · INTEGRITY — resolution: 🆕 new this round — CA/Backend/CasObjectStorageBackend.cpp:814-856
CAS-089 The blob envelope is written but never read back: the offset comes from pool meta and its identity field is silently truncatable, so any divergence in envelope length goes undetected · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-024) · evidence strengthened — CA/Pool/CasManifestReader.cpp:137-144
CAS-090 Encryption interactions: SSE-C breaks staging promotion, CAS manifest metadata stays plaintext, AES-CTR carries no MAC, and immutable shared blobs make re-keying a pool impossible · SECURITY — resolution: 🔴 still-present (carried from prev CAS-113) · evidence strengthened — prev CAS-204's 📐 (SSE-S3/KMS transparent) is unaffected; SSE-C is the case that breaks — src/IO/S3/Client.cpp:1273-1287
CAS-091Layout::checkNamespace accepts . and .. segments unlike every other CAS path validator, so under the auto-selected emulated mode over local object storage the traversal is real on the filesystem · SECURITY — resolution: 🔴 still-present (carried from prev CAS-074) · evidence strengthened — traversal is now real rather than theoretical — CA/Formats/CasLayout.cpp:295-319
CAS-092 The write fence and the request it admits are on different clocks, and one cross-node wall-clock liveness gate survives on the decommission epoch mint; both CLOCK_BOOTTIME reads are Linux-only · CORRECTNESS — resolution: ⚠️ was-fixed / still-present (prev CAS-030 was ✅ "lease liveness via token stability on observer clock, no cross-node wall-clock trust") — the fix removed cross-node wall-clock trust from lease renewal; one such decision remains on the destructive SYSTEM CAS DROP MEMBER path — CA/Pool/CasServerRoot.cpp:196-209
CAS-093 The temp text-index directory lives inside the part, publishes a ref before the part exists, and its removeRecursive cleanup is one of the silent no-ops, so the temp ref and its blobs survive · CORRECTNESS — resolution: 🔴 still-present (carried from prev CAS-061) · evidence strengthened — promoted from test-gap to defect — src/Storages/MergeTree/TextIndexUtils.cpp:601-609
CAS-094GC REBUILD refusals and failures are not side-effect free — a refused rebuild reports performed=0 while leaving complete-looking run objects and a fold seal a later round can adopt or reject · INTEGRITY — resolution: 🔴 still-present (carried from prev CAS-108) · anchor relocated — CA/Gc/CasGc.cpp:2811-2824
CAS-095cas-gc-dryrun is not a preview of the next round and is silently empty in exactly the disaster state it exists for — a damaged gc/state yields preview_deletes=0, as if nothing needed deleting · OBSERV/DAY2 — resolution: 🆕 new this round — CA/Gc/CasGc.cpp:3017-3080
CAS-096 The rebuild reports almost nothing about the quality of the baseline it blesses — a rebuild that silently drops a parent row and its hold reports performed=1 and nothing else · OBSERV/DAY2 — resolution: 🆕 new this round — CA/Gc/CasGc.h:48-63
CAS-097 No surface names namespaces or raw keys, and cas-inspect cannot decode 8 of the 18 CAS formats, mis-decodes _files/ names and requires a raw key no shipped command can enumerate · OBSERV/DAY2 — resolution: 🔴 still-present (carried from prev CAS-062, which was 🛠 will-fix) · anchor relocated — CA/Tools/CasInspect.cpp:517-576
CAS-098 GC health is process-local and ephemeral, GC STOP is node-local and unobservable, and last_success_age_seconds=0 means both "never led" and "succeeded just now" · OBSERV/DAY2 — resolution: ⚠️ was-fixed / still-present (prev CAS-014 was ✅/🛠 "should be OK now; worth re-verify — not a blocker") — the metric surface landed but the specific silent failure it was added for is still invisible; this is that re-verify and it fails — CA/Gc/CasGcScheduler.cpp:312-327
CAS-099 Rolling restart and planned node removal have no quiesce, drain or leadership-handoff verb — the only shutdown is "stop and join", so a rolling restart drops GC leadership abruptly · OBSERV/DAY2 — resolution: 🆕 new this round — CA/Gc/CasGcScheduler.cpp:67-79
CAS-100 fsck skips whole check families yet still reports a clean, non-partial result — source-edge checksums are never verified on a healthy pool and the stale-edge finding is unreachable from SQL · OBSERV/DAY2 — resolution: 🔴 still-present (carried from prev CAS-093) · evidence strengthened — the verdict is unsound, not merely unrepaired — CA/Tools/CasFsck.cpp:654
CAS-101 GC round counters are derived from budget-truncated logs and phase observability reports constants and the wrong round, so after a large DROP the round undercounts real deletes · OBSERV/DAY2 — resolution: 🆕 new this round — CA/Gc/CasGc.cpp:652-690
CAS-102 11 of the 156 CAS ProfileEvents can never fire and server-root I/O is counted as GC I/O, so lease and mount request volume cannot be measured or alerted on · OBSERV/DAY2 — resolution: ⚪ info (prev CAS-214 — recorded as "classifyCasNs uses unanchored substring match (metric misattribution only, no correctness impact)") — CA/Backend/CasInstrumentedBackend.cpp:109-122
CAS-103 Savings and outcome counters are incremented before the outcome they claim is decided, so dedup-savings metrics count bytes that were in fact uploaded, over-reporting the headline value of the feature · OBSERV/DAY2 — resolution: 🆕 new this round — CA/Pool/CasPartWriteTxn.cpp:155-175
CAS-104 Audit-event dispatch funnels read and write hot paths through one mutex and the shipped config enables the sink by default, so every ref resolve and manifest read serializes on that mutex · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasEventDispatcher.cpp:17-44
CAS-105 The whole mount/lease/request budget and seven pool-level caps are unreachable from configuration, so none of the scaling problems in this report can be tuned and the validator that exists is dead code · CONFIG — resolution: 🆕 new this round — CA/ContentAddressedSettings.cpp:29-58
CAS-106 The non-CAS config key allowlist is a fixed 18-entry set, so ordinary object-storage keys (connect_timeout_ms, max_connections, SSE keys) abort disk registration rather than being passed through · CONFIG — resolution: 🆕 new this round — CA/ContentAddressedSettings.cpp:23-27
CAS-107 No CAS setting can be changed by config reload, the ignore is silent, and a removed CAS disk keeps its mount lease held so the slot cannot be reclaimed by anyone else · CONFIG — resolution: 🆕 new this round — src/Disks/DiskObjectStorage/MetadataStorages/IMetadataStorage.h:340-343
CAS-108 Dead code and test-only seams are compiled into the production binary — fault-injection surfaces link into clickhouse-server and a process-global unsynchronized test hook is read racily on every part commit · TEST-GAP — resolution: 🆕 new this round — CA/Backend/CasInMemoryBackend.{h,cpp}
CAS-109 There is no deterministic crash-at-step-N harness and settings validation has one fail-closed test for the whole surface, so every crash-consistency window in this round is uncovered · TEST-GAP — resolution: 🆕 new this round — src/Disks/tests/gtest_cas_settings.cpp
CAS-110resolveRef's allow_stale is plumbed through two layers and silently discarded, so callers that explicitly ask for a stale-tolerant resolve get the strict path and can block on recovery or throw · CORRECTNESS — resolution: ⚠️ was-fixed / still-present (prev CAS-085 was ✅ "allow_stale retired in code") — "retired" was implemented as "ignored" rather than "removed", so the callers that relied on the semantics lost them with no compile error — CA/Pool/CasRefLedger.cpp:214-215
CAS-111 The per-namespace 64 MiB ref-table admission cap fails writes permanently and non-retryably at roughly 610k refs, reached by ordinary part accumulation on a wide-partitioned table · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-100) · evidence strengthened — quantified; also qualifies prev CAS-008's ✅, since write availability is still coupled to a 64 MiB encoded-object ceiling on live state where trimming cannot help — CA/Pool/CasRefLedger.cpp:2161-2169
CAS-112 Every ref append re-reads and linearly rescans the pool-global ref catalog — at least two full catalog GETs plus linear scans per part commit, scaling in the number of namespaces in the pool · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasRefCatalog.cpp:109
CAS-113 Encoded-size caps are validated only after the oversized buffer has been built, so hitting a 256 MiB object cap requires first materializing 256 MiB — a memory spike rather than an early rejection · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasPartWriteTxn.cpp:541-544
CAS-114 Recovery must seal every skipped writer epoch one at a time, so first touch of an idle table costs O(mount generations) sequential durable write pairs with no cap · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasRefLedger.cpp:740-824
CAS-115 Cache weight functions under-account their entries — PartManifestWeight misses ~2x of the per-entry footprint and DedupWeight returns a constant 64 (~3.1x under), so both byte budgets over-admit · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-049) · evidence strengthened — contradicts the 🟡 mitigation quantitatively — CA/Pool/CasManifestReader.h:49-58
CAS-116 Staging is quadratic in the number of files in a part — every staging mutation rescans all prior staged entries and moveDirectory re-scans the destination set once per source entry · PERF/SCALE — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:510
CAS-117 CAS stores one object plus one meta object plus a 256-byte envelope per part file, so a wide part of small files is dominated by per-object overhead in request count and stored bytes · PERF/SCALE — resolution: 🆕 new this round — CA/Pool/CasManifestReader.cpp:133-144
CAS-118 No cache can serve a read without a network round trip and one logical read resolves the ref several times — head() runs before the cache probe and resolve() before the view-cache probe · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-086 + prev CAS-067) · evidence strengthened — CA/Pool/CasManifestReader.cpp:56-78
CAS-119 Throttling amplification: conditional writes use a single-attempt client, so retry moves from the jittered SDK to CAS's un-jittered fixed backoff up to 16 times, each adding a resolution GET · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-053) · evidence strengthened — CA/Backend/CasObjectStorageBackend.cpp:628-639
CAS-120 There is no relink fast path for local moves inside one CAS pool and the CAS copy is serial — every CAS destination goes through a plain recursive readFile/writeFile loop, even CAS-to-CAS on one pool · PERF/SCALE — resolution: 🔴 still-present (carried from prev CAS-041) · evidence strengthened — independently confirmed twice — src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:702-718
CAS-121 A CAS table outside an Atomic database cannot be backed up at all, and every CAS backup re-reads every byte because areBlobPathsRandom() returns false and forces checksums FromReading · FEATURE-GAP — resolution: 🔴 still-present (carried from prev CAS-042) · evidence strengthened — src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:417-422
CAS-122 The !-prefixed critical-key escape hatch has no producer and strict formats report an additive field as data corruption, routing an operator to fsck/rebuild instead of a rollback · COMPAT — resolution: ⚠️ was-fixed / still-present (prev CAS-075 was ✅ "header_hash removed; !-key gate CasTextFormat.cpp:249–251") — the removal half landed, but the enforcement half it was traded against is dead code, so "critical extension" enforcement still relies on writer honesty — CA/Formats/CasTextFormat.cpp:240-242
🟡 Low / hardening
CAS-123 There is no byte accounting outside blobs/ and no reclaim-forecast surface, so a bucket-versus-table gap cannot be attributed to manifests, ref logs, snapshots, staging or generations · OBSERV/DAY2 — resolution: 🔴 still-present (carried from prev CAS-040) · anchor relocated — CA/Tools/CasFsck.cpp:578-596
CAS-124 Empty content hashes to the all-zero digest, which is also the sentinel fsck substitutes for an unparsable key, so a legitimate zero-length blob and a parse failure are indistinguishable · INTEGRITY — resolution: 🆕 new this round — CA/Primitives/CasBlobDigest.h:41
CAS-125Xxh3Streamer dereferences a null state in its constructor, so the allocation-failure guard is dead and blob_hash=xxh3-128 under memory pressure faults instead of raising CANNOT_ALLOCATE_MEMORY · CORRECTNESS — resolution: 🆕 new this round — CA/Primitives/CasXxh3Streamer.h:17
CAS-126 The write-fence pre-check exists only on the S3 staging path, so on the default local backend a writer whose fence was lost mid-write streams the whole body and discovers the loss only at commit · INTEGRITY — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:607-622
CAS-127 Avoidable per-byte and per-line copying and allocation on hot paths — three full copies per byte on blob writes, byte-at-a-time readLine with no reserve, and repeated substring searches per request · PERF/SCALE — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:1220-1235
CAS-128 Inline entries staged into a destination part that never gets a build fail the whole commit closed — writing a small metadata file into part A then moving it into an unwritten part aborts the transaction · CORRECTNESS — resolution: 🆕 new this round — CA/ContentAddressedTransaction.cpp:800-812
CAS-129 The writer-epoch fence on a build is checked only at entry to promote, not at the durable append, so a build admitted under epoch E1 can append on a fresh runtime after a self-remount to E2 · CONCURRENCY — resolution: 🔴 still-present (carried from prev CAS-002) · evidence strengthened — this is the recheck Filimonov asked for ("looks overstated / "высосана из пальца"; maybe recheck carefully — not a blocker"); the window is bounded by operation_deadline_ms (90 s) and graded Low, consistent with "not a blocker" — CA/Pool/CasPartWriteTxn.cpp:125-128
CAS-130 Mount-lease and epoch identity fields are written but not enforced — doStart writes a literal seq = 1 over the slot claimMount just bumped, and allocateWriterEpoch can return 0 · CORRECTNESS — resolution: 🔴 still-present (carried from prev CAS-080 + prev CAS-029) · evidence strengthened — CA/Pool/CasServerRoot.cpp:1021
CAS-131 Audit-event and cache-counter attribution defects: timestamps and thread_id/query_id are taken on the draining thread, and cache-hit resolves are missing from the log · OBSERV/DAY2 — resolution: 🆕 new this round — CA/ContentAddressedMetadataStorage.cpp:431-456
CAS-132 Bucket layout, hostnames, PIDs and server UUIDs are disclosed in errors reachable by unprivileged SQL users and via system.cas_mounts, which is readable with SELECT on system · SECURITY — resolution: 🆕 new this round — CA/Pool/CasServerRoot.cpp:368-386
CAS-133cas_mounts renders a transient LIST failure identically to a non-existent pool or a not-yet-started disk, so one throttled LIST makes a healthy pool look absent · OBSERV/DAY2 — resolution: 🆕 new this round — src/Storages/System/StorageSystemContentAddressedMounts.cpp:146-156
CAS-134 A receiver with two CAS pools in one policy advertises only the first, so fetches into the second pool silently transfer every byte instead of relinking, with no diagnostic · FEATURE-GAP — resolution: 🆕 new this round — src/Storages/MergeTree/DataPartsExchange.cpp:586-604
CAS-135 Emulated mode holds one mutex across multiple round trips per operation and a process-wide mutex across a whole blob body, serializing all CAS-over-local reads, writes and resurrects · PERF/SCALE — resolution: 🆕 new this round — CA/Backend/CasObjectStorageBackend.cpp:491-507
Genuine data-loss / correctness paths (the short list to look at first)
CAS-001 (pool-global shadow namespaces), CAS-002 (adopt bypasses the condemn marker), CAS-005
(unrevertible repoint), CAS-006 (rename ends in an unconditional source drop), CAS-022 (orphan sweep
with no catalog row), CAS-023 and CAS-024 (deletes that silently do nothing; a MOVE that erases the
part), plus the fencing pair CAS-003/CAS-010. Everything else biases to a reclaimable leak, a liveness/operability cliff, or an unverified edge.
Separately, the highest-information group is the ⚠️ was-fixed / still-present set — CAS-029, CAS-036, CAS-037, CAS-043, CAS-048, CAS-062, CAS-065, CAS-092, CAS-098, CAS-110, CAS-122 — because
each contradicts a verdict already recorded as fixed, so the current mental model is wrong there. CAS-048, CAS-092, CAS-110, CAS-036, CAS-122, CAS-029 and CAS-098 are the safety- or
availability-relevant ones and should be read first.
CAS (
metadata_type = content_addressedMergeTree backend) — consolidated audit trackingThis is a tracking issue for a static-analysis audit of the Content-Addressed Storage (CAS) MergeTree
disk backend. It now consolidates 135 distinct findings from a fresh re-run in which all 39 audits were
redone from scratch against the current PR, each with a unique
CAS-###id.The
CAS-###ids are renumbered for this round and do NOT correspond to the previous numbering.Nothing was carried forward; numeric coincidences with the old catalogue are meaningless. Where an item is
carried over from the previous triage, the old id is referenced inline as
prev CAS-###.Important
This is a static/logical review. Many items are expected to be by-design, not-a-bug, latent, or
already-handled. The checklist is meant to be triaged item-by-item — please dismiss or resolve freely.
📎 Audit reports
RECONCILIATION.md+ New Findings.NEW-FINDINGS.md+RECONCILIATION-2031.md.antalya-26.6, headfeature/antalya-26.6/CAS.cas-code-only-stripat base commit842f2b37b8f: the same code as PR [experimental] CAS (Content-addressed storage) over shared object storage for antalya-26.6 #2159, with docs, comments and tests removed so that only the core code remains. Prior rounds showed comments and docs drifting out of step with behaviour, so stripping them forces every finding to rest on the code alone.Finding the full detail for a
CAS-###The checklist line is a one-line summary. For the complete write-up, open
NEW-FINDINGS.mdin the2026-08-12 gist and search for the id: each entry lists the class, every code anchor, the impact and
the exact trigger, plus a
Reported by:line naming the source audit findings (e.g. CAS-012 →ad6-6,ad6-10). Those ids are prefixed with their report name, soad6-*lives inad6-s3-lifecycle-cross-region.mdin the same gist — search that file for the id to get the fullreasoning, the consequence chain and the code walk-through.
NEW-FINDINGS.mdalso ends with a tablemapping each of the 39 reports to the ids it produced, for the reverse direction.
How to triage
For each item, when reviewed:
resolution:inline with a verdict, e.g.✅ fixed (#PR)·🛠 will-fix·❌ wontfix·🚫 not-a-bug·📐 by-design·🟡 needs-repro·↗ split-out (#NNN)·🔴 still-present.CAS-###id.⚠️ was-fixed / still-presentand are the priority.Severity is the highest assigned by any source audit. Class tags:
DATA-LOSS · LEAK · LIVENESS · CONCURRENCY · INTEGRITY · SECURITY · DECODE/DoS · COMPAT · FEATURE-GAP · PERF/SCALE · OBSERV/DAY2 · COMPLIANCE · CONFIG · TEST-GAP · CORRECTNESS.Grading rule used this round: a fail-closed loud failure grades below silent corruption, and operability, cost
and scale gaps are not High on their own. Resulting counts: 24 graded High, of which 3 are already-accepted
design positions (pre-checked), leaving 21 open High.
🔴 High
DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-070) · evidence strengthened —CA/ContentAddressedMetadataStorage.cpp:897-900adoptEvidencebypasses the durable condemn marker and EDGE-BEFORE-OBSERVE, so a committed manifest can name a blob GC has already deleted, with no source to re-upload from ·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-031) · evidence strengthened · anchor relocated —CA/Pool/CasPartWriteTxn.cpp:478-486CONCURRENCY— resolution: 🔴 still-present (carried from prev CAS-032) · evidence strengthened —CA/Gc/CasGc.cpp:3155-3186GC REBUILDhas no writer/mount interlock and "read-only" does not gate writes, so a rebuild is accepted on a live writable disk with inserts in flight and the mount census result is discarded ·INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-015, which was 🟡 partial) · evidence strengthened — contradicts the partial's premise that "mount-lease should take correctly now" —CA/Gc/CasGc.cpp:2725commit()with a silent best-effort rollback, so readers can observe aborted and intermediate states ·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-021 + prev CAS-097) · evidence strengthened —CA/ContentAddressedTransaction.cpp:280-289RENAME/moveDirectoryis a per-ref non-atomic migration ending in an unconditional source drop with no reconciler, so a crash mid-rename splits the refs and drops any added during the walk ·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-022 + prev CAS-044) · evidence strengthened —CA/ContentAddressedTransaction.cpp:846-874server_root_idis accepted and victim selection is prefix-based, soSYSTEM CAS DROP POOL MEMBERonsrid=aerases the namespaces and control objects of a live membersrid=a/b·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-064) · evidence strengthened — escalated from collision to nesting, a new destructive consequence —CA/Pool/CasServerRoot.h:104-134SECURITY— resolution: 📐 by-design / YAGNI (prev CAS-003 + prev CAS-005 — Filimonov: "selectable hash landed — closes weak-collision concern; will not re-verify hash on read — incompatible with "CH does not slow down""; "S3 has many durability nines and hashes objects itself; CAS will not re-hash on read") —CA/ContentAddressedSettings.cpp:33INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-038) · evidence strengthened — partially inherited: the "do not re-verify an already-stored object" half is closed by prev CAS-005 (📐 — Filimonov: "S3 has many durability nines and hashes objects itself; CAS will not re-hash on read"); the scratch/staged-body half remains open —CA/Pool/CasPartWriteTxn.cpp:250-305putOverwritevalidates only the token type, so an absent ETag plus an absent-HEAD sends no precondition at all ·INTEGRITY— resolution: 🆕 new this round —CA/Backend/CasObjectStorageBackend.cpp:165-173INTEGRITY— resolution: 🆕 new this round —CA/Pool/CasPlainObjects.cpp:21-41DATA-LOSS— resolution: 📐 by-design / 🛠 docs (prev CAS-016 + prev CAS-017 — Filimonov: "lifecycle expiration must be off like versioning; add explicit user-facing bucket requirements; hard to detect without admin access"; "do not enable Object Lock/WORM/retention/lifecycle/versioning on the bucket — plain bucket only") · the Glacier restore-and-retry half stays open under prev CAS-052 —CA/Backend/CasObjectStorageBackend.cpp:272-282blob_hash_allow_new=1locks every older build out of the entire pool ·COMPAT— resolution: ↗ out-of-scope / deferred (prev CAS-009 — Filimonov: "needs attention later; not a blocker; model may be wrong") —CA/Pool/CasPoolMeta.cpp:72primary.cidx,.mrk4, secondary-index data files), sending them down the fully-in-memory inline path ·CORRECTNESS— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:65-73DROP TABLE·LIVENESS— resolution: 🔴 still-present (carried from prev CAS-034 + prev CAS-083) · evidence strengthened —CA/Pool/CasRefLedger.cpp:1457-1492attempt_timeout_msnever reaches the wire and the blob payload read bypasses the CAS backend entirely, so reads run on the default 500-retry profile with no deadline and no classification ·LIVENESS— resolution: 🆕 new this round —CA/Backend/CasRequestControl.h:84LIVENESS— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:3451-3458noexcept/destructor paths allocate, so a throw under a memory limit deadlocks the namespace forever or terminates the process ·LIVENESS— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:1519-1541CORRECTNESS— resolution: 🆕 new this round —CA/Parts/PartFolderAccess.cpp:231-269getStorageObjectsreturns objects that are not the file's bytes because the envelope offset is dropped, soMOVE PART/PARTITION TO DISKand TTL moves produce a corrupt destination part with no error ·INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-047) · evidence strengthened — now with a concrete corrupting consumer —CA/ContentAddressedMetadataStorage.cpp:1336-1340INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-035) · evidence strengthened — now bidirectional —CA/Backend/CasRequestControl.cpp:427-435CAS-021: Ambiguous conditional-write outcomes are reported as definite ownership #2207BlobSourceRetirementfor every entry during a namespace's first write ·DATA-LOSS— resolution: 🆕 new this round —CA/Gc/CasOrphanManifestSweep.cpp:546gc_enabled=falseevery manual reclamation verb is refused withBAD_ARGUMENTS·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-043) · evidence strengthened — partially inherited: the GC-deferred-reclaim-latency half is closed by prev CAS-018 (📐/❌ — Filimonov: "erase SLA is not part of the disk contract; operator canGC RUNanytime"); thegc_enabled=falseaccept-and-do-nothing and vanished-pool silent-success halves are open —CA/ContentAddressedMetadataStorage.cpp:611server_root_idresolve to one(namespace, ref), so aMOVE PARTITION TO DISKbetween them publishes then drops the same ref and the part is gone, silently ·DATA-LOSS— resolution: 🔴 still-present (carried from prev CAS-064) · evidence strengthened — escalated to silent data loss —CA/ContentAddressedMetadataStorage.cpp:886-889🟠 Medium
GC REBUILDdiscards the condemn universe and permanently orphans already-unreferenced blobs —prior_runsstarts empty, the fold takes only+1deltas, and the graduation guard is vacuous ·LEAK— resolution: 🔴 still-present (carried from prev CAS-015) · anchor relocated —CA/Gc/CasGc.cpp:2809-2824pool_uuidequality as proof of "same bucket" and publishes the adopted part unverified — no presence check on any blob dependency andcheck_consistency=falseon publish ·INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-031) · evidence strengthened —src/Storages/MergeTree/DataPartsExchange.cpp:313-330owner/mountretires it or steals its slot ·SECURITY— resolution: 📐 by-design (prev CAS-004 + prev CAS-211 — Filimonov: "bucket credential = whole trust boundary; all pool users same trust"; "bad actor with pool access is already doomed; provenance self-asserted / content-equality observable") —CA/Pool/CasServerRoot.cpp:68-79system.cas_log·SECURITY— resolution: 📐 by-design / ❌ wontfix (prev CAS-019 + prev CAS-211 — Filimonov: "this is the essence of CAS dedup — will not "fix"") · the crypto-shred half stays open under prev CAS-071 —CA/Pool/CasPartWriteTxn.cpp:145DATA-LOSS— resolution:CA/Backend/CasObjectStorageBackend.cpp:53-67skip_access_checkremoves every bucket-configuration defense, is not recorded anywhere in the pool, and the decommission remount hard-codes it ·CONFIG— resolution: 🆕 new this round —CA/Pool/CasPool.cpp:339-347If-None-MatchonCompleteMultipartUploadevery large blob loses exclusivity ·INTEGRITY— resolution: 🆕 new this round —CA/Backend/CasProbe.cpp:42INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-051) · anchor relocated —CA/Pool/CasPoolMeta.cpp:100-104LEAK— resolution: 📐 by-design (prev CAS-033 — Filimonov: "prefer fail-closed safety under GC uncertainty; reclaim may stall") —CA/Gc/CasGc.cpp:2063-2064LEAK— resolution: 📐 by-design / ❌ wontfix (prev CAS-018 + prev CAS-106 — Filimonov: "erase SLA is not part of the disk contract; operator canGC RUNanytime; GC cadence ~5–10 min; GDPR faster-than-that unlikely") · the budget-versus-rate arithmetic is not disposed of by that verdict and should be carried as an open question —CA/ContentAddressedSettings.cpp:46PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-057 + prev CAS-050) · evidence strengthened — contradicts prev CAS-057's 🟡 "single LIST, no fan-out" mitigation —CA/Gc/CasBlobInDegree.cpp:484-555_pool_metaor ref-log body OOMs the victim before any format check ·DECODE/DoS— resolution:ParseFromArrayspecifics, but bucket-sourced bytes are still sized from attacker-declared values with no cap, at one of the two lines the fix cited —CA/Backend/CasObjectStorageBackend.cpp:284-293offset + lengthoverflows on the read path, so a planted manifestszproduces a wrapped read window that defeats every decoder range gate ·DECODE/DoS— resolution:logical_sizeremoved from envelope") — deleting the field removed the one documented exploit; the arithmetic itself was never hardened and the same wrap is now reachable on the read path —CA/Formats/CasTextFormat.cpp:193-223DECODE/DoS— resolution: 🔴 still-present (carried from prev CAS-077) · evidence strengthened — now systemic across four decoders —CA/Formats/CasServerRootFormats.cpp:147-169gc_shardsis adopted from bucket bytes with no upper bound and silently overrides the node's configured value while sizing vectors and loop bounds, on only a>= 1check ·DECODE/DoS— resolution: 🔴 still-present (carried from prev CAS-066) · evidence strengthened · anchor relocated —CA/Formats/CasPoolMetaFormat.cpp:116INTEGRITY— resolution: 🆕 new this round —CA/Formats/CasPartManifestFormat.cpp:64-67CORRUPTED_DATA— and the same implementation costs 2x work per decode ·COMPAT— resolution: 🔴 still-present (carried from prev CAS-027) · evidence strengthened — same root cause, harder consequence: silent additive-field loss became a hard fail —CA/Formats/CasPartManifestFormat.cpp:263-267COMPAT— resolution: ↗ out-of-scope / deferred (prev CAS-009 — Filimonov: "needs attention later; not a blocker; model may be wrong") · theRosterthrow stays open under prev CAS-076 —CA/Formats/CasFormat.cpp:82-93COMPAT— resolution:catchfiltersCORRUPTED_DATAonly while the generation check throwsUNKNOWN_FORMAT_VERSION, so a skewed fetch fails outright instead of degrading —CA/ContentAddressedMetadataStorage.cpp:1610-1619FEATURE-GAP— resolution: 🔴 still-present (carried from prev CAS-100) · anchor relocated —CA/Pool/CasPartWriteTxn.cpp:54.manifest_size = 0, sopart_folder_cache_bytesis inoperative and nothing is excluded as oversized ·PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-049) · evidence strengthened — contradicts the 🟡 "bytes+count LRU" mitigation —CA/Parts/PartFolderAccess.cpp:128-131PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-096) · evidence strengthened —CA/ContentAddressedTransaction.cpp:1223-1235PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasBlobUploadPool.cpp:45-49DataPartsLockis held, so a throttling or 5xx bucket stalls the table's parts lock and blocks every SELECT, merge scheduling and part-set mutation ·LIVENESS— resolution:DataPartsLock") — the off-lock publish exists on the replicatedrenameParts()path but was not applied to the covering-part path used byDROP/REPLACE PARTITION; the prior round said the same in the untriagedNEW-BC7-1—src/Storages/MergeTree/MergeTreeData.cpp:5918-5922GC STOP, shutdown andFSCKserialize behind whole in-flight unbounded scans, so an operator cannot stop GC, forget a disk or shut down while a round or scan is in flight against a slow bucket ·LIVENESS— resolution: 🆕 new this round —CA/Gc/CasGcScheduler.cpp:213CONCURRENCY— resolution: 🆕 new this round —CA/Gc/CasGcScheduler.cpp:67-79quiesceRefTablesForRemountanddropNamespaceImplforever, and its fan-out is unbounded pool-wide ·LIVENESS— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:2754-2783shared_from_this()on a possibly expiring pool, throwingbad_weak_ptrout of an unguarded region or handing a detached thread a pool being destroyed ·CONCURRENCY— resolution: 🆕 new this round —CA/Pool/CasPool.cpp:972-1029PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:1149-1210PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:2741-2745part_folder_validateships asalways, costing hundreds to thousands of round trips per mutation ·PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-086) · evidence strengthened · anchor relocated —CA/ContentAddressedTransaction.cpp:816PERF/SCALE— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:256-290moveFile/replaceFileon a committed part file always throwsLOGICAL_ERROR, so the standard "write.tmpthenreplaceFile" pattern is unusable and unique-key delete bitmaps fail on CAS ·FEATURE-GAP— resolution: 🚫 not-a-bug / 🟡 soft (prev CAS-007 — Filimonov: "should be fine — tests catch nothing; glance someday — not a blocker") · the dismissal was explicitly soft and the fresh evidence names a real caller, so this should be re-opened —CA/ContentAddressedTransaction.cpp:1030-1055ATTACH/REPLACE PARTITION FROMinto a CAS disk is unimplemented and fails part-way through the part, leaving partial state rather than being rejected up front ·FEATURE-GAP— resolution: 🆕 new this round —src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:593-621CONFIG— resolution: ↗ out-of-scope (prev CAS-046 — Filimonov: "CAS+encryption needs design/dev/testing; should be workable later — not now") · the missing gate itself stays open under prev CAS-113 —src/Disks/DiskEncrypted.cpp:190-208CORRECTNESS— resolution: ↗ out-of-scope (prev CAS-046 — Filimonov: "CAS+encryption needs design/dev/testing; should be workable later — not now") —src/Disks/DiskEncryptedTransaction.cpp:105-112gc/statehas a rebuild path; every other control object has no DR path, and because the tools open the pool through_pool_metafirst, damage to that one object disables the DR tools themselves ·OBSERV/DAY2— resolution: 🔴 still-present (carried from prev CAS-063) · evidence strengthened —CA/Pool/CasPool.cpp:293-368SYSTEM CAS FSCKis counts-only and no repair path exists anywhere — the SQL path can say the pool is corrupt but never which keys, with no timeout and no scoping ·OBSERV/DAY2— resolution:src/Interpreters/InterpreterSystemQuery.cpp:2534cas_mountsand not repairable by a re-run ·OBSERV/DAY2— resolution: 🔴 still-present (carried from prev CAS-062, which was 🛠 will-fix) · anchor relocated —CA/Tools/CasDecommission.cpp:137-183TEST-GAP— resolution: 📐 by-design / YAGNI (prev CAS-010 — Filimonov: "same trust model as CAS-005 — trust S3; less trust ⇒ more perf loss; no decoder fuzz mandate as gate") —src/Disks/tests/cas_format_test_battery.hTEST-GAP— resolution:src/Disks/tests/gtest_cas_backend_contract.cpp:250-258localpath both get in-process-only exclusivity ·CONFIG— resolution: 🔴 still-present (carried from prev CAS-065) · evidence strengthened — the read-only-mount gap and the emulated-mode auto-selection are new sub-findings —CA/ContentAddressedMetadataStorage.cpp:509-520INTEGRITY— resolution: 🆕 new this round —CA/Backend/CasObjectStorageBackend.cpp:347-362putIfAbsentControlledswallows deterministic local failures and reports them as ambiguity, so aLOGICAL_ERRORorNOT_IMPLEMENTEDthat provably never landed wedges the ref lane into recovery ·INTEGRITY— resolution: 🆕 new this round —CA/Backend/CasRequestControl.cpp:271-281MEMORY_LIMIT_EXCEEDEDon a largegc/stateis indistinguishable from genuine corruption and drives a full baseline rebuild ·INTEGRITY— resolution: 🆕 new this round —CA/Gc/CasGc.cpp:2633-2648LIVENESS— resolution: 🆕 new this round —CA/Pool/CasMountRuntime.cpp:341-369CONCURRENCY— resolution: 📐 by-design (prev CAS-090, whose verdict is recorded with anchors:Pool/CasMountRuntime.h:400;renewWatermarkOnceunlocked at:156-163; reassign underPool::remount_mutexonly) —CA/Pool/CasMountRuntime.h:164DATA-LOSS— resolution: 🆕 new this round —CA/Pool/CasPartWriteTxn.cpp:866-884INTEGRITY— resolution: 🆕 new this round —CA/Formats/CasBlobMetaFormat.h:14-22snap_pruned_throughand those objects leak ·LEAK— resolution: 🔴 still-present (carried from prev CAS-072) · evidence strengthened —CA/Gc/CasGc.cpp:2456-2500clean()verdict ·LEAK— resolution: 🔴 still-present (carried from prev CAS-060) · evidence strengthened —CA/Pool/CasPartWriteTxn.cpp:423-429gc/state, so repeated crashes accumulate complete-looking seals for generations no state references and nothing prunes them ·LEAK— resolution: 🔴 still-present (carried from prev CAS-072 + prev CAS-108) · anchor relocated —CA/Gc/CasGc.cpp:2254LEAK— resolution: 🆕 new this round —CA/Gc/CasOrphanManifestSweep.cpp:373-387LEAK— resolution: 🆕 new this round —CA/Gc/CasNamespaceJanitor.cpp:22-31LEAK— resolution: 🆕 new this round —CA/Gc/CasGc.cpp:2320-2336CORRECTNESS— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:2937-3035server_root_id, so whole part-file plaintext persists after any killed INSERT and no shipped tool ever lists it ·LEAK— resolution: 🔴 still-present (carried from prev CAS-060) · evidence strengthened —CA/ContentAddressedTransaction.cpp:148-172LEAK— resolution: 🔴 still-present (carried from prev CAS-084) · evidence strengthened — contradicts the dismissal's premise ("MPU leftovers typical for S3, GC cleans eventually"): nothing in CAS ever cleans them —CA/Backend/CasProbe.cpp:20-32DELETEand mutations free the deleted rows' bytes only for rewritten files — surviving entries are republished against the same blobs, so deleted row bytes stay in the pool ·LEAK— resolution: 📐 by-design / ❌ wontfix (prev CAS-019 + prev CAS-018 — Filimonov: "this is the essence of CAS dedup — will not "fix"") —CA/ContentAddressedTransaction.cpp:782-829LEAK— resolution: 🆕 new this round —src/Disks/DiskObjectStorage/DiskObjectStorageCache.cpp:21-23always_use_copy_instead_of_hardlinks=1makes every CAS clone and mutation throwNOT_IMPLEMENTEDand nothing rejects the setting, breaking mutations, FREEZE and ATTACH/REPLACE PARTITION ·FEATURE-GAP— resolution: 🔴 still-present (carried from prev CAS-112) · evidence strengthened — no longer latent: real MergeTree callers reach it —CA/ContentAddressedTransaction.cpp:363-366removeDirectory's non-empty guard never fires and a populated part directory is dropped without error ·CORRECTNESS— resolution: 🔴 still-present (carried from prev CAS-048 + prev CAS-099) · evidence strengthened — now a contract-level cluster —CA/ContentAddressedMetadataStorage.cpp:1293-1305detached/movingoutrank part-dir detection, and silently reinterprets unclassified part dirs as table-level files, so misclassification surfaces only on read ·CORRECTNESS— resolution: 🔴 still-present (carried from prev CAS-073) · evidence strengthened — same file, now both directions —CA/Parts/PartPathParser.cpp:140-162resurrectis an unconditional, budget-free, fence-unchecked overwrite that returns a token it did not write, so two writers on one condemned blob can each believe they own the object ·INTEGRITY— resolution: 🆕 new this round —CA/Backend/CasObjectStorageBackend.cpp:814-856INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-024) · evidence strengthened —CA/Pool/CasManifestReader.cpp:137-144SECURITY— resolution: 🔴 still-present (carried from prev CAS-113) · evidence strengthened — prev CAS-204's 📐 (SSE-S3/KMS transparent) is unaffected; SSE-C is the case that breaks —src/IO/S3/Client.cpp:1273-1287Layout::checkNamespaceaccepts.and..segments unlike every other CAS path validator, so under the auto-selected emulated mode over local object storage the traversal is real on the filesystem ·SECURITY— resolution: 🔴 still-present (carried from prev CAS-074) · evidence strengthened — traversal is now real rather than theoretical —CA/Formats/CasLayout.cpp:295-319CLOCK_BOOTTIMEreads are Linux-only ·CORRECTNESS— resolution:SYSTEM CAS DROP MEMBERpath —CA/Pool/CasServerRoot.cpp:196-209removeRecursivecleanup is one of the silent no-ops, so the temp ref and its blobs survive ·CORRECTNESS— resolution: 🔴 still-present (carried from prev CAS-061) · evidence strengthened — promoted from test-gap to defect —src/Storages/MergeTree/TextIndexUtils.cpp:601-609GC REBUILDrefusals and failures are not side-effect free — a refused rebuild reportsperformed=0while leaving complete-looking run objects and a fold seal a later round can adopt or reject ·INTEGRITY— resolution: 🔴 still-present (carried from prev CAS-108) · anchor relocated —CA/Gc/CasGc.cpp:2811-2824cas-gc-dryrunis not a preview of the next round and is silently empty in exactly the disaster state it exists for — a damagedgc/stateyieldspreview_deletes=0, as if nothing needed deleting ·OBSERV/DAY2— resolution: 🆕 new this round —CA/Gc/CasGc.cpp:3017-3080performed=1and nothing else ·OBSERV/DAY2— resolution: 🆕 new this round —CA/Gc/CasGc.h:48-63cas-inspectcannot decode 8 of the 18 CAS formats, mis-decodes_files/names and requires a raw key no shipped command can enumerate ·OBSERV/DAY2— resolution: 🔴 still-present (carried from prev CAS-062, which was 🛠 will-fix) · anchor relocated —CA/Tools/CasInspect.cpp:517-576GC STOPis node-local and unobservable, andlast_success_age_seconds=0means both "never led" and "succeeded just now" ·OBSERV/DAY2— resolution:CA/Gc/CasGcScheduler.cpp:312-327OBSERV/DAY2— resolution: 🆕 new this round —CA/Gc/CasGcScheduler.cpp:67-79OBSERV/DAY2— resolution: 🔴 still-present (carried from prev CAS-093) · evidence strengthened — the verdict is unsound, not merely unrepaired —CA/Tools/CasFsck.cpp:654OBSERV/DAY2— resolution: 🆕 new this round —CA/Gc/CasGc.cpp:652-690OBSERV/DAY2— resolution: ⚪ info (prev CAS-214 — recorded as "classifyCasNsuses unanchored substring match (metric misattribution only, no correctness impact)") —CA/Backend/CasInstrumentedBackend.cpp:109-122OBSERV/DAY2— resolution: 🆕 new this round —CA/Pool/CasPartWriteTxn.cpp:155-175PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasEventDispatcher.cpp:17-44CONFIG— resolution: 🆕 new this round —CA/ContentAddressedSettings.cpp:29-58connect_timeout_ms,max_connections, SSE keys) abort disk registration rather than being passed through ·CONFIG— resolution: 🆕 new this round —CA/ContentAddressedSettings.cpp:23-27CONFIG— resolution: 🆕 new this round —src/Disks/DiskObjectStorage/MetadataStorages/IMetadataStorage.h:340-343clickhouse-serverand a process-global unsynchronized test hook is read racily on every part commit ·TEST-GAP— resolution: 🆕 new this round —CA/Backend/CasInMemoryBackend.{h,cpp}TEST-GAP— resolution: 🆕 new this round —src/Disks/tests/gtest_cas_settings.cppresolveRef'sallow_staleis plumbed through two layers and silently discarded, so callers that explicitly ask for a stale-tolerant resolve get the strict path and can block on recovery or throw ·CORRECTNESS— resolution:allow_staleretired in code") — "retired" was implemented as "ignored" rather than "removed", so the callers that relied on the semantics lost them with no compile error —CA/Pool/CasRefLedger.cpp:214-215PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-100) · evidence strengthened — quantified; also qualifies prev CAS-008's ✅, since write availability is still coupled to a 64 MiB encoded-object ceiling on live state where trimming cannot help —CA/Pool/CasRefLedger.cpp:2161-2169PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasRefCatalog.cpp:109PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasPartWriteTxn.cpp:541-544PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasRefLedger.cpp:740-824PartManifestWeightmisses ~2x of the per-entry footprint andDedupWeightreturns a constant 64 (~3.1x under), so both byte budgets over-admit ·PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-049) · evidence strengthened — contradicts the 🟡 mitigation quantitatively —CA/Pool/CasManifestReader.h:49-58moveDirectoryre-scans the destination set once per source entry ·PERF/SCALE— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:510PERF/SCALE— resolution: 🆕 new this round —CA/Pool/CasManifestReader.cpp:133-144head()runs before the cache probe andresolve()before the view-cache probe ·PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-086 + prev CAS-067) · evidence strengthened —CA/Pool/CasManifestReader.cpp:56-78PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-053) · evidence strengthened —CA/Backend/CasObjectStorageBackend.cpp:628-639readFile/writeFileloop, even CAS-to-CAS on one pool ·PERF/SCALE— resolution: 🔴 still-present (carried from prev CAS-041) · evidence strengthened — independently confirmed twice —src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:702-718areBlobPathsRandom()returnsfalseand forces checksumsFromReading·FEATURE-GAP— resolution: 🔴 still-present (carried from prev CAS-042) · evidence strengthened —src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp:417-422!-prefixed critical-key escape hatch has no producer and strict formats report an additive field as data corruption, routing an operator to fsck/rebuild instead of a rollback ·COMPAT— resolution:header_hashremoved;!-key gateCasTextFormat.cpp:249–251") — the removal half landed, but the enforcement half it was traded against is dead code, so "critical extension" enforcement still relies on writer honesty —CA/Formats/CasTextFormat.cpp:240-242🟡 Low / hardening
blobs/and no reclaim-forecast surface, so a bucket-versus-table gap cannot be attributed to manifests, ref logs, snapshots, staging or generations ·OBSERV/DAY2— resolution: 🔴 still-present (carried from prev CAS-040) · anchor relocated —CA/Tools/CasFsck.cpp:578-596INTEGRITY— resolution: 🆕 new this round —CA/Primitives/CasBlobDigest.h:41Xxh3Streamerdereferences a null state in its constructor, so the allocation-failure guard is dead andblob_hash=xxh3-128under memory pressure faults instead of raisingCANNOT_ALLOCATE_MEMORY·CORRECTNESS— resolution: 🆕 new this round —CA/Primitives/CasXxh3Streamer.h:17INTEGRITY— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:607-622readLinewith no reserve, and repeated substring searches per request ·PERF/SCALE— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:1220-1235CORRECTNESS— resolution: 🆕 new this round —CA/ContentAddressedTransaction.cpp:800-812promote, not at the durable append, so a build admitted under epoch E1 can append on a fresh runtime after a self-remount to E2 ·CONCURRENCY— resolution: 🔴 still-present (carried from prev CAS-002) · evidence strengthened — this is the recheck Filimonov asked for ("looks overstated / "высосана из пальца"; maybe recheck carefully — not a blocker"); the window is bounded byoperation_deadline_ms(90 s) and graded Low, consistent with "not a blocker" —CA/Pool/CasPartWriteTxn.cpp:125-128doStartwrites a literalseq = 1over the slotclaimMountjust bumped, andallocateWriterEpochcan return 0 ·CORRECTNESS— resolution: 🔴 still-present (carried from prev CAS-080 + prev CAS-029) · evidence strengthened —CA/Pool/CasServerRoot.cpp:1021thread_id/query_idare taken on the draining thread, and cache-hit resolves are missing from the log ·OBSERV/DAY2— resolution: 🆕 new this round —CA/ContentAddressedMetadataStorage.cpp:431-456system.cas_mounts, which is readable with SELECT onsystem·SECURITY— resolution: 🆕 new this round —CA/Pool/CasServerRoot.cpp:368-386cas_mountsrenders a transient LIST failure identically to a non-existent pool or a not-yet-started disk, so one throttled LIST makes a healthy pool look absent ·OBSERV/DAY2— resolution: 🆕 new this round —src/Storages/System/StorageSystemContentAddressedMounts.cpp:146-156FEATURE-GAP— resolution: 🆕 new this round —src/Storages/MergeTree/DataPartsExchange.cpp:586-604PERF/SCALE— resolution: 🆕 new this round —CA/Backend/CasObjectStorageBackend.cpp:491-507Genuine data-loss / correctness paths (the short list to look at first)
CAS-001(pool-global shadow namespaces),CAS-002(adopt bypasses the condemn marker),CAS-005(unrevertible repoint),
CAS-006(rename ends in an unconditional source drop),CAS-022(orphan sweepwith no catalog row),
CAS-023andCAS-024(deletes that silently do nothing; a MOVE that erases thepart), plus the fencing pair
CAS-003/CAS-010. Everything else biases to a reclaimable leak, aliveness/operability cliff, or an unverified edge.
Separately, the highest-information group is the
⚠️ was-fixed / still-presentset —CAS-029,CAS-036,CAS-037,CAS-043,CAS-048,CAS-062,CAS-065,CAS-092,CAS-098,CAS-110,CAS-122— becauseeach contradicts a verdict already recorded as fixed, so the current mental model is wrong there.
CAS-048,CAS-092,CAS-110,CAS-036,CAS-122,CAS-029andCAS-098are the safety- oravailability-relevant ones and should be read first.