[WIP] CAS draft (adopting to CI/CD, not for review / merge)#2073
Draft
filimonov wants to merge 2603 commits into
Draft
[WIP] CAS draft (adopting to CI/CD, not for review / merge)#2073filimonov wants to merge 2603 commits into
filimonov wants to merge 2603 commits into
Conversation
filimonov
added a commit
that referenced
this pull request
Jul 17, 2026
…ent_addressed stateless lanes) Config Workflow check failed with 'Workflows are outdated' for master.yml, pull_request.yml, pull_request_community.yml, release_builds.yml. Regenerated via 'python3 -m praktika yaml'. The regeneration adds the two CAS stateless jobs to the generated workflows: 'Stateless tests (arm_binary, content_addressed storage, parallel)' and 'Stateless tests (arm_binary, content_addressed s3 storage, parallel)' (the rustfs-backed lane). CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=927ea142c9cb14759623861eb004261d0b4b1c8f&name_0=PR&name_1=Config+Workflow PR: #2073 Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 17, 2026
…teless lane The lane's start_rustfs expected a pre-extracted binary at ci/tmp/rustfs and failed on CI runners where nothing provisions it (the workflow wipes ci/tmp on every run). Download the static musl build for the runner architecture from the RustFS GitHub release (1.0.0-beta.9) when the binary is absent, mirroring how setup_minio.sh downloads minio/mc. Validated locally: the beta.9 binary passes the conditional-operation semantics the CA pool requires (second 'If-None-Match: *' PUT -> 412, wrong-etag conditional DELETE -> 412, right-etag DELETE succeeds), and download_rustfs provisions an executable binary end-to-end. PR: #2073 Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 17, 2026
Fast test fails at cmake generation: 'Target "dbms" links to ch_contrib::crc32c but the target was not found' — the fast-test job initializes a limited submodule list that does not include contrib/crc32c, so the unconditional add_contrib is skipped while the dbms link line still references the target. The dependency is dead: it was wired in for per-block CRC32C in the early CAS run-file format (5f1272c), which was later replaced by the text record-stream codecs; no source file includes the library today. Restore the pre-CAS state: crc32c is built only for google-cloud-cpp, and dbms does not link it. CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=835251f81cb5af73ad9eaa3a835f50f0c8b678db&name_0=PR&name_1=Fast+test PR: #2073 Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 17, 2026
Fast test builds without SSL and failed on the unconditional 'openssl/evp.h' include in CasBlobHashingWriteBuffer.cpp. Wrap the OpenSSL-backed Sha256 hashing write buffer and the one-shot digest in '#if USE_SSL'; on non-SSL builds selecting blob_hash = 'sha256' now fails closed with SUPPORT_IS_DISABLED. CityHash128 and XXH3-128 blob hashes are unaffected. PR: #2073 Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 18, 2026
… test regression) CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Fast+test PR: #2073 A CAS parser commit grouped `RELOAD_DICTIONARY`/`RELOAD_MODEL`/ `RELOAD_FUNCTION` with `CONTENT_ADDRESSED_GARBAGE_COLLECTION` into a format case that prints only the optional disk, dropping the reload targets: `SYSTEM RELOAD MODEL my_model` formatted as `SYSTEM RELOAD MODEL` (failed 04117_parser_system_query_variants and 04124_parser_system_query_extra in Fast test). Fold all four types back into the generic target-printing case (table / target_model / target_function / disk else-if chain) — for the CA GC command the disk branch produces the identical output. Both stateless tests verified locally via clickhouse-local against their references. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 18, 2026
…als (arm_tidy, T13 batch 1) CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Build+(arm_tidy) PR: #2073 Removes default arguments from all virtual/override methods flagged by `google-default-arguments` (147 sites: `CasBackend.h` interface, `IObjectStorage.h`/`S3ObjectStorage.h`, all backend implementers, test helpers/fixtures) and adds non-virtual convenience overloads on the base classes that forward the previous default values. Derived classes gain `using` declarations to unhide the base overloads. Qualified parent-implementation calls in test fault backends switched to the explicit 3-arg form — the 2-arg form would now route through the base forwarder and re-enter the derived override virtually (double fault injection; caught by the battery). Bulk edits produced by codex (gpt-5.6-luna) per the T13 brief; overload visibility and qualified-call fixes plus verification by Claude. Battery 919/919 green. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov
added a commit
that referenced
this pull request
Jul 18, 2026
CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Build+(arm_tidy) PR: #2073 Semantics-preserving conformance for the remaining flagged classes: readability-container-contains, readability-isolate-declaration, google-runtime-int (AWS SDK retry-API overrides keep `long` with targeted NOLINT — the override contract owns the type), readability-duplicate-include, cppcoreguidelines-init-variables, cert-msc, modernize-raw-string-literal, modernize-use-starts-ends-with, bugprone-empty-catch (comments only — no new behavior), googletest naming, bugprone-argument-comment, bugprone-optional-value-conversion, bugprone-misplaced-widening-cast (CasTypes.h site audited: not a real precision bug — the value is range-validated to 0-5; cast made explicit without value change). CasRefCowMap's own `contains` keeps its `find` with NOLINT (self-recursion). Bulk edits by codex (gpt-5.6-luna) per the T13 brief (.superpowers/sdd/task-13-batch2-report.md); one over-removed include (PartFolderAccess.h) restored and verification by Claude. Battery 919/919. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
srid+async-metrics (`3b2c9abb822`), verb result sets (`cb111510c1a`), citation sweep (`11bf17fbf66`), CasLayout out-of-line (`4fafb4edb28`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…L MEMBER Shared checkNotReadOnly() gate now fronts every mutating CAS entry point (createTransaction, GC round, GC rebuild, and the pool-member decommission verb) so an observe-only <readonly> disk can no longer drive a destructive decommission of a peer. Review finding 3(a). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd_watermark) openForDecommission now force-sets background_watermark=true alongside read_only/skip_access_check, so a decommission driven through an observe-only host disk still renews its ~30s lease and a long drain no longer aborts midway. Review finding 3(b). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… disks An explicit use_fake_transaction=true on a metadata type that requires deferred transactions would silently break the atomic manifest/ref publish (per-file autocommit, no commit point). Fail closed at disk registration with BAD_ARGUMENTS instead of honoring it. Review finding 4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…keys find() anchored base_it with base->find(key), which for an overlay-only key returned base->end() and collapsed the next ++ to end(), silently dropping later base entries. Use lower_bound (identical when the key is shadowed). Review finding 10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dth mismatch Validate the digest hex width before fromHex so malformed persisted outcome logs surface as CORRUPTED_DATA (the decoder's fail-close contract) rather than fromHex's BAD_ARGUMENTS, matching CasPartManifestFormat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… is CORRUPTED_DATA)
Step 10 re-runs DROP POOL MEMBER on an already-decommissioned slot and asserted
BAD_ARGUMENTS ('unknown member'). Since decommission now TOMBSTONES the owner
anchor in place instead of deleting it, the re-run finds the tombstone and is
refused with CORRUPTED_DATA + an 'explicitly decommissioned' message. Update the
assertion to match the intended behavior.
Caused by: ClickHouse@9707a61
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…review finding 2) The three CasEvent emits in PartWriteTxn::abandon() are now wrapped in try/catch(tryLogCurrentException), mirroring promote(): a throwing event sink (bad_alloc growing the log queue, Context/log-shutdown edge) can no longer turn an already-durable abandon into a reported failure. alive=false now flips only AFTER the correctness-bearing appendRefOps precommit removal, so a caller that catches an append failure can retry abandon() on the same object instead of hitting 'has been abandoned'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…poch precommit) A promote failure inside publishEntries previously unwound the build via its destructor, which only retires the build seq -- never removing the durable live-epoch precommit binding (GC and the prior-epoch-scoped stale sweep both leave it alone until remount). Wrap the body so any exception calls build->abandon() (which removes the binding) before rethrowing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wallowing A thrown abandon during transaction destruction can leave a live-epoch precommit binding that persists until remount; log it via tryLogCurrentException so it is diagnosable rather than silently discarded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…unt row The 4 GC-health columns (is_leader, pending_reclaim, last_success_age_seconds, wedged_namespace_count) are process-local facts about THIS server's scheduler; stamping them onto every peer's mount row misreads as 'peer B is GC leader' during incidents. Make them Nullable and populate only the row whose srid is this server's own server_root_id; peer rows read NULL. Review finding 5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CasGc.cpp's ~12 log sites used a fixed getLogger("CasGc"), indistinguishable
on a node running GC on more than one CA disk. Gc now takes a LoggerPtr (member
renamed to 'logger' to avoid the pervasive outcome-log binding), threaded from
CasGcScheduler's disk-scoped logger and from a CasGc(<srid>)-scoped logger for
the rebuild path; CasBlobInDegree's fold logs interpolate the pool prefix.
Review finding (docs/operability).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n the wire boundary) Manifest bytes arrive over the interserver relink channel; enforce the same path hygiene as CasLayout::checkNamespace at decode (reject empty/leading-slash/ '.'/'..' segments) so no future consumer inherits a traversal. Syntactic only -- legal projection subdirs pass. Review finding (security defense-in-depth). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w finding 12) findTableUuidComponent accepted any 3-char component followed by a same-prefix component, so a legacy Ordinary layout like data/abc/abcxyz/... false-anchored as an Atomic <uuid[:3]>/<uuid> pair, producing a wrong table_uuid/part/file split and misrouted CAS keys. Require the 3-char dir to be lowercase-hex and the next component to be a well-formed 36-char dashed UUID. The test suites used placeholder 'uui/uuid-N' path fixtures that are not hex/UUID-shaped and stopped anchoring under the tightened check. Rewrote them to real deterministic Atomic-UUID pairs (with the corresponding table_uuid / namespace assertions) across gtest_ca_wiring, gtest_ca_transaction, gtest_cas_promote_republish, gtest_cas_s3_staging; two new CaPartPathParser anchor tests pin the fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the 4 copy-pasted getMetadataStorage/isContentAddressed/dynamic_cast detection lambdas (InterpreterSystemQuery x3, StorageSystemContentAddressedMounts x1) with the existing ContentAddressedMetadataStorage::tryFromDisk helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-floor doc Three unrelated small cleanups: drop 2 unused CAS includes from the generic DiskObjectStorageTransaction.cpp; document on state_mutex above SingleWriterSlot::renewOnce why holding it across the heartbeat PUT is safe (a warning for anyone adding a new locked accessor); and document the GC pre-CAS prune floor's one-generation slack as an intentional, data-loss-free forensics-window nuance (keeping gc_snap_generations_to_keep config-precise, rather than changing retention behavior). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…not the concrete class DiskObjectStorage::prepareRead downcast to the concrete ContentAddressedMetadataStorage to reach prepareInManifestRead/getBlobViewPlan, coupling the generic read path to one derived class and blocking generic IMetadataStorage decorators. Expose the two hooks on the purpose-built IContentAddressedExchange interface (BlobViewPlan relocated there so override return types match) and cast to that instead. Review finding 7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g listRefs existsDirectory's emptiness checks called listRefs(ns) (builds a full std::map<String,Resolved> under the ref-table lock) just to test emptiness. Add CasRefLedger::hasAnyRefWithPrefix (same recovery preamble, early-exit scan, no map materialization) + a Pool forwarder, and swap the 5 existsDirectory probe sites to it. Review finding (performance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…them The suites CaWiring* (13), CaTransaction* (6), CaPartPathParser, CaDedupCache, CaInlinePlacement started with 'Ca' (lowercase a) and were silently skipped by the Cas*:CA*:Ref* gate filter -- ~91 tests never ran in the gate. Rename them all to the dominant Cas* convention (no TEST_F fixtures involved; one production comment citation in PartPathParser.cpp updated). The gate filter Cas*:Ref* now covers them (1039 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
05011 gains a zero-grant ACCESS_DENIED assertion on plain SYSTEM CONTENT ADDRESSED GARBAGE COLLECTION (denial fires before UNKNOWN_DISK). New 05016 proves a zero-grant user is denied DROP POOL MEMBER, and that granting only SYSTEM CONTENT ADDRESSED DROP POOL MEMBER unlocks it (then fails on UNKNOWN_DISK). Review finding 3(c) / tests gap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llback-disable Extend MockS3::Client with CopyObject/DeleteObject/GetBucketVersioning handlers and error injection; cover S3ObjectStorage::removeObjectIfTokenMatches (412->TokenMismatch, 404->NotFound, success) and copyObjectConditional (412->not-created), and drive copyS3File with if_none_match to prove an AccessDenied on the conditional single-copy propagates instead of silently falling back to an unconditional copy, and a losing 412 surfaces isPreconditionFailed(). (The multipart UploadPartCopy fallback site remains uncovered -- would need an UploadPartCopy mock handler; tracked as a gap.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t gets its own message Review follow-up (Task 8, approved): 1. [doc] Correct the `CasGcScheduler::isQuiescent` rationale. GC `gc/state`/heartbeat writes are NOT fence-generation/`mayMutate`-checked (they bypass `CasPlainObjects`; Task 4 excluded them), so the old "rounds fail-closed on the lost write fence" claim was wrong. Name the three real backstops a live scheduler is safe against the proof by: (a) `gcStateKey`/`gcHbKey` under `poolPrefix` are covered by the proof per-tick emptiness LIST, so any landed write RESETS the empty-sample streak; (b) `round_in_flight` is held for the whole round body (`SCOPE_EXIT`), closing the landed-just-after-LIST window; (c) the `has_observation` guard throws `CORRUPTED_DATA` rather than recreate an absent `gc/state`. A future refactor must not weaken the LIST-reset coupling believing a fence protects it. 2. Route the `IdentityLost` case in `checkOpAdmitted` to `Pool::throwIfLifecycleTerminal` (its own richer [D5] message) instead of the transient "mount lease not held" string — `IdentityLost` does NOT auto-recover, so the honest "recover by restart or FORGET; a matching-sentinel restore does not auto-revive" diagnosis is correct. Only `TransientNotLive` keeps the transient message; the truth-absent short-circuit is gated on the settled `Vanished*` states. Test (d) now asserts the distinct message per sub-state. Full CA gate 1184/1184 green; CasOperationGate 9/9. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…ve pool-meta positive The last silent-empty-load killer (rev.7 spec §1 "empty-proof rule" [B3]). On a pre-terminal (Live) or read-only pool, an enumeration about to answer EMPTY at a `TableDir`/`DetachedContainer` root now first CONFIRMS the pool identity object (`_pool_meta`) with an AUTHORITATIVE, UNCACHED probe — because "empty" at a table root is exactly what a silently-erased backing looks like, and a read-only pool has no keeper/lease/erasure-observer to catch that erasure any other way (MergeTree skips both directory-create and the `format_version.txt` write on a read-only disk, so enumeration is its ONLY line of defense against ATTACHing an empty table over an erased pool). The rule is a separate Probe-path hook, NOT part of `checkOpAdmitted`: it lives in `listDirectory`'s `TableDir`/`DetachedContainer` empty answers (which `iterateDirectory` and `isDirectoryEmpty` both funnel through), and fires only after the gate admitted a Probe and only when the computed listing is empty — `Present` authorizes the empty answer, `KeyAbsent`/`ContainerAbsent` throw the typed 668 "backing may be erased", `AccessDenied`/`Indeterminate` throw the typed transient 668 (fail-closed, retryable). A `Vanished` pool never reaches it: the gate short-circuits `Probe` to `TruthAbsent` (truth-empty) before any classification. Non-empty answers pass untouched — zero cost on the hot path; the only added I/O is one backend HEAD per EMPTY table-root enumeration (attach/load). Tests (new `gtest_cas_empty_proof.cpp`, suite `CasEmptyProof`): (a) a read-only mount over an erased backing throws typed 668 on a table-root enumeration instead of answering empty — the RO-ATTACH silent-empty killer (RED without the hook); (b) a Live genuinely-empty table dir answers empty with EXACTLY one uncached probe; (c) a non-empty table dir issues no probe; (d) a Vanished pool answers truth-empty with no probe; (e) a deeper part-dir empty answer is NOT gated; (f) an Indeterminate probe throws the transient 668, never empty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…e-first protocol Adds SYSTEM CONTENT ADDRESSED FORGET '<disk>' (rev.7 spec §5): the operator force-Vanish that decommissions a content-addressed disk node-locally, driving its pool to Vanished(forgotten) with the fence-first protocol. It works on a NOT-live disk (its whole purpose) and is the tests' future teardown handle. Protocol (Pool::forgetDisk): publish terminal-intent first (bounds the joins), trip the local fence, stop+join the GC scheduler (injected — owned above the Pool), stop+join the self-remount thread, drain the ref lanes and retire the keeper WITHOUT an unearned clean farewell (clean release only when drained), then publish Vanished(forgotten) with the [D5] message carrying the operator's decommission timestamp. - CasMountRuntime: split the terminal-intent latch from the state-transition idempotency guard (new publishVanishedIntent + terminal_state_published) so FORGET can publish intent early and settle the state late; store the reason for the [D5] message. - Re-latch the fence after joining the remount thread: a remount already in flight when intent is published can complete a reclaim during the join and re-arm the fence, so the terminal mayMutate()==false must be re-asserted. - keeperReset after finishTeardown: the pool outlives FORGET, so ~Pool re-runs teardown; MountLeaseKeeper::stop is single-shot (ASan-aborts on a double terminate), so drop the keeper here. - Verb wiring copied from the FSCK family (AccessType, AST/parser, interpreter, 01271 grants). ContentAddressedMetadataStorage::forgetDisk is the entry — a lifecycle verb like the Factory class, it bypasses the store()-class gate. gtest_cas_forget.cpp: fence+state+[D5] timestamp, real GC-scheduler join, idempotent double FORGET, IdentityLost escape hatch, clean-farewell drained gating, FORGET racing an active remount (bounded, no deadlock), and the end-to-end verb through the six-class gate. CasForget* 7/7; full CA gate Cas*:CA* 1115/1115. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…ed; intent comments corrected Review follow-up for SYSTEM CONTENT ADDRESSED FORGET. - Regression guard for the fence re-latch (trip#2). The prior racing test kept every self-remount at StayTransient, so it never reached armMountFence and would stay GREEN if trip#2 were deleted. The new ForgetReLatchesFenceAfterAReclaimReachesArmMountFence drives a REAL tryRemountOnce from FORGET's GC-stop step (fenced-out mount → the reclaim reaches armMountFence and re-arms the fence AFTER trip#1; tryRemountOnce's step-0 gate checks isVanished(), still false in the window, so it does not bail), and asserts mayMutate()==false. Verified RED with trip#2 removed (mayMutate() Actual=true) and GREEN with it. The reclaim success is asserted (ASSERT_TRUE(reclaimed)) so the guard can never pass vacuously. - ForgetStopsAndJoinsRealGcScheduler: the is_leader==false assertion was vacuous (the 3600s scheduler never led) and could not be made both real and correct, because the test's GC-stop callback is just sched.stop(), which does NOT clear the in-process i_am_leader hint — only the metadata-storage handler clears leadership by destroying the scheduler. Replaced with the isQuiescent() join check the test actually proves, plus a comment. - Corrected two stale comments in CasMountRuntime.cpp (scheduleRemount arm guard and the remount loop): since the latch/state split, the terminal-intent latch is published by publishVanishedIntent (FORGET step 1, or enterVanished's first step for a natural transition), not "FIRST by enterVanished". CasForget* 8/8; definitive gate Cas*:CaLifecycle*:CaWiring*:ContentAddressed*:CountingBackendShape*:RefSnapshotCodec*:RefTableCacheEviction*:RefWriter* 1198/1198. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…n stop Adds SYSTEM CONTENT ADDRESSED GC STOP '<disk>' / GC START '<disk>' (rev.7 spec §6): granular operator control of ONLY the background GC scheduler. The disk stays fully usable meanwhile — reads and writes are unaffected; this is not a lifecycle transition. STOP is stop-in-place: it joins the scheduler's worker + heartbeat threads and clears the in-process i_am_leader hint, but RETAINS the scheduler object so a later START restarts the SAME instance (its gc_id and persistent lease observer preserved). START goes through the uniform GC gate (checkOpAdmitted(Admin)): it refuses on a transient / IdentityLost / Vanished pool (typed 668 / [D5]) and on a not-mounted disk — restarting GC there would only spin failing rounds. Leadership is not auto-restored; it re-enters via the next round's normal gc/state acquisition. - CasGcScheduler::stop now clears i_am_leader AFTER both joins (final: no round can re-set it). start() was already re-entrant post-join (ThreadFromGlobalPool operator= is safe once join() reset the state), so it is unchanged. The durable gc/state lease is untouched. - ContentAddressedMetadataStorage::gcStop snapshots the scheduler by COPY (never std::move) and leaves it in the member, so gcHealth / gcQuiescentForErasureProof keep reading its real (stopped => quiescent) state — the T6 erasure-proof coupling — and gcStart re-enters it. Locking is the FORGET shape (lifecycle -> gc_scheduler -> pointer; blocking stop/start outside the locks). - GC RUN on a stopped scheduler runs a manual round anyway (the Gc engine is not stopped, only its pacer; explicit operator intent must not be swallowed) — it falls out of stop-in-place with no extra code. - Verb wiring copied from the FORGET family (AccessType, AST/parser, interpreter, 01271 grants). gcStop is ungated (stopping GC on a sick disk is legitimate); gcStart is gated. Both idempotent. gtest_cas_gc_stop_start.cpp: stop joins + clears leadership (leadership acquired via a real manual round FIRST — RED-verified without the clear), start-after-stop resumes background rounds with the SAME gc_id and re-enters leadership via the normal acquisition (condition-variable wait, no sleeps), stop/start idempotent, START refuses on Vanished (668) while STOP on Vanished with a live scheduler succeeds, and disk reads/writes are unaffected while GC is stopped. CasGcStopStart 5/5; definitive CA gate 1203/1203. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Two review minors on the Task 11 GC STOP/START verbs: - [M1] The transient (TransientNotLive) 668 message in checkOpAdmitted — the one SYSTEM CONTENT ADDRESSED GC START hits when the disk is still recovering — now appends "; retry once the disk recovers to Live". Actionable for every caller, and specifically for an operator who STOPped GC pre-maintenance and runs START mid-recovery: this is a wait, not a dead end. The IdentityLost message is left as-is (it already gives restart/FORGET, and it never auto-recovers to Live). The operation-gate test asserts the transient message by substring, so it stays green. - [M2] An operator-facing comment at the GC RUN dispatch site: a manual SYSTEM CONTENT ADDRESSED GC RUN executes REGARDLESS of GC STOP (the pacer is stopped, the engine is not) — explicit intent wins — and can transiently surface is_leader=true in introspection until a peer steals the lease or GC START resumes pacing. CasGcStopStart 5/5; definitive CA gate 1203/1203. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
… backlog entry (I3) From the whole-increment T1-11 review: the spec still demanded 'GC thread fully exited' before the proof (the rev-t8-adjudicated as-built form is no-round-in-flight + three backstops); the backlog entry instructed the exact predicate that adjudication ruled wrong and claimed unwired what T8 already wired. Capability wiring vs ship-dormant = pending controller/user decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…non-gated lifecycle snapshot Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…D5] text to lifecycle_detail
Task 14's fail-closed teardown checks lifecycle || '(' || lifecycle_reason || ')'
== 'vanished(forgotten)', so lifecycle_reason must be the bare sub-state word
(erased/replaced/forgotten), not the free-text [D5] reason. Split the full [D5]
text into a new lifecycle_detail column so spec §1's requirement that the reason
strings appear in the snapshot still holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…t-column schema) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…dering rev-t12 minors on the lifecycle snapshot (Spec approved): - system table: correct the synthesized-row comment (store()->poolAccess() throwIfLifecycleTerminal DOES refuse a terminal pool, so a Vanished/IdentityLost disk reaches the snapshot row via the catch, not via an empty listMounts). - system table: narrow the store() catch to INVALID_STATE only, re-throwing any other exception so a genuine fault propagates instead of folding into a row. - CasMountRuntime::enterIdentityLost: stamp lifecycleSinceWallS BEFORE the pool_lifecycle CAS (the before-publish handoff enterVanished uses; safe because the edge only fires from TransientNotLive under remount_mutex). Header comment on lifecycleSinceWallS rewritten to describe both edge classes honestly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…ecision) Excise the natural Vanished(erased) proof stack from the design: the probe taxonomy stays (serves the identity gate, empty-proof rule, bootstrap), the fence-generation checks stay ([C2] late-PUT hole), but the emptiness proof, outstanding-durable counter, capability, grace arithmetic and gc_quiescent_fn go; IdentityLost becomes a fail-loud terminal (threads exit; cure = FORGET or restart); GC self-exit widens to IdentityLost too (closes the last G2-zombie case). Section 9 gains the 10-item excision list for the dedicated task. Companion C2 disposition note + BACKLOG entry marked OBSOLETE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…tocol Two TLC-checked gates for the rev.7 disk-lifecycle redesign (Tasks 1-11), full verdict in .superpowers/sdd/tla-rev7-report.md: CaDiskLifecycle (FORGET + lifecycle): ALL INVARIANTS + liveness HOLD — fence latched and fully-terminal state at FORGET completion under every interleaving (trip#2 proven necessary+sufficient against the join-window reclaim: _sab_notrip2 reproduces the Task-10 race RED), earned farewell only, one-way-ness, terminal-implies-latched-fence, GC destroyed and unrestartable after FORGET, and FORGET always completes even when a racing natural promotion wins enterVanished (first-terminal-wins). CaErasureProof (the Vanished(erased) crown property): writer paths HOLD (op-gate + DurableRequestGuard counter + LIST-reset discipline); the [D1] grace is PROVEN load-bearing (_sab_nograce: a zombie request — guard released on timeout, request still in flight — lands after the second sample). TWO REAL GC-side windows found: a fresh (never-observed) scheduler's round CREATES gc/state between the observer's final LIST and its round_in_flight read and completes before it (the Task-8 prose containment does not close the completed-round window), and the out-of-round heartbeatLoop pulses + missing scheduler lifecycle-exit recreate control keys in the qual window and after promotion. Both are pre-activation findings (natural erasure is dormant until the strong-LIST capability is wired); the fix — Live-gate scheduled rounds AND hb pulses, mirroring checkOpAdmitted(Admin) — is mechanically validated GREEN (_fix_gclivegate). Task 15 gate: OPEN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
… M1 intent-bail
Retask (user decision): the natural Vanished(erased) proof stack is being
excised from the code, so CaDiskLifecycle becomes the sole Task-15 gate,
simplified to the v1 state machine: no VanishedErased, IdentityLost on
authoritative sentinel absence alone, Vanished = {Replaced (identity
gate), Forgotten (FORGET)}. Two queued hardenings are modeled as landed:
[C1] GC scheduler loop/heartbeat self-exit on isVanished() at each wake
(new liveness GcExitsAfterVanished; _sab_nogcselfexit reproduces the
pre-fix bug as a stuttering lasso — VanishedReplaced with the scheduler
running forever), and [M1] the remount attempt's step-0 intent-bail (no
new attempt once vanished_intent is published; an attempt already past
its bail still races honestly — trip#2 + first-terminal-wins close it).
Re-run verdict: ALL invariants (I1 trip#2 sufficiency, I1b, I2 earned
farewell, I3 one-way, I4a/I4b terminal-implies-latched-fence, I6 GC
destroyed after FORGET) + both liveness properties HOLD (268 distinct
states); 3 sabotages RED, 3 witnesses reachable. Task-15 gate stays
OPEN, conditional on C1/M1 landing as queued.
CaErasureProof.tla is retained untouched as HISTORICAL (its verdict —
writer machinery sound, grace load-bearing, two real GC-side windows —
is part of the excision evidence and the v2 door); README statuses and
the report at .superpowers/sdd/tla-rev7-report.md updated accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…1); fence checks on condemned displacement (I2)
Three fixes from the whole-increment (T1-11) review (rev-increment verdict, ledger
".superpowers/sdd/progress.md"): 1 Critical + M1 hardening + I2 spec-truth restoration.
[C1 — CRITICAL] The GC scheduler never exited on NATURAL terminal transitions. Neither
`CasGcScheduler::loop` nor `heartbeatLoop` consulted lifecycle, so after a natural
`VanishedErased` every tick `acquireOrRenewLease` threw `CORRUPTED_DATA` against the
vanished `gc/state` forever (the G2 zombie: error-log + Failed round row every interval),
and after `VanishedReplaced` the `allow_steal=true` rounds could steal a FOREIGN pool's
`gc/state` lease and fold/condemn/delete its objects. Both loops now check
`store->isVanished() || store->vanishedIntentPublished()` at each wake and self-exit their
own loop (no join from the callback context — C6-safe; `stop()`/`~CasGcScheduler` still join
the finished thread cleanly). Exiting on the published FORGET intent too is earliest-signal
discipline, matching the remount loop's dual check (`d00057186ab`). `IdentityLost` publishes
no intent and deliberately keeps ticking (rev-t8-adjudicated: its writes are contained).
[M1] The FORGET-blind-window race. `forgetDisk` publishes the terminal-intent latch at step 1
(`publishVanishedIntent`), then joins the remount thread; an in-flight `tryRemountOnce` that
passed step 0 BEFORE the intent could reach `evaluateErasureProofEmptySample` (which has no
intent check) and conclude `VanishedErased` mid-FORGET — stranding FORGET's own
`enterVanished(VanishedForgotten)` (first terminal transition wins) and mislabeling the
operator-visible reason.
PLACEMENT NOTE (deviation from the literal "step-0 bail" in the review): the check is added at
the PROMOTION point (the `SentinelsGoneEmptyPrefix` case, guarding
`evaluateErasureProofEmptySample` on `vanishedIntentPublished()`), not at step 0. A step-0 gate
only catches a reclaim that STARTS after the intent — it cannot catch the in-flight reclaim that
is the actual race. Placing it at step 0 also breaks landed T10 test
`CasForget.ForgetReLatchesFenceAfterAReclaimReachesArmMountFence`, which drives a reclaim to
`armMountFence` during FORGET on purpose: that Recover/re-arm path is a separate hazard guarded
by `forgetDisk`'s post-join re-trip (trip#2). The promotion-point check closes the real erasure
race while leaving Recover/`armMountFence` (and trip#2's coverage) untouched. Scoped to the
`VanishedErased` erasure proof per the review; the `VanishedReplaced` verdict is not guarded here
(same class, out-of-contract origin) — flagged as a related follow-up.
[I2] Spec-truth restoration for [C2] (backlog {#c2-resurrect-putoverwrite-fence-check}).
`PartWriteTxn::uploadFromSource`'s condemned-displacement branches (`resurrectStaged`,
`putOverwrite`) were counted by a `DurableRequestGuard` but had NO fence-generation check — the
only durable writes left outside Task 4's gate. Both now capture the fence generation at the
displacement decision and `checkFenceOrThrow` immediately before the raw backend write, aborting
with the typed transient error (`INVALID_STATE`) instead of letting a stale-incarnation write
land. Scope held exactly to these two calls; the debris deletes
(`cleanupStagedManifestDebrisBestEffort`, `cleanupPendingTempFiles`) are structurally
incarnation-safe and untouched.
New accessor `vanishedIntentPublished()` on `CasMountRuntime`/`Pool` (the CasMountRuntime half
already landed via 0fdaef2 during a concurrent session) exposes the terminal-intent latch to
the scheduler and the M1 promotion guard.
Tests (all with RED-demo verification — each new test observed failing without its fix):
- C1: CasGcStopStart.{SchedulerSelfExitsOnNaturalVanished, SchedulerKeepsTickingOnIdentityLost
(negative control), StopAndDestroyCleanAfterSelfExit}. RED: waitForTerminalSelfExit times out
(loops zombie). Bounded condition-variable waits, no sleeps.
- M1: CasErasureProof.ForgetIntentBlocksNaturalErasedPromotion. RED: promotes to VanishedErased
mid-FORGET (isVanished() == true).
- I2: CasFenceGeneration.{CondemnedPutOverwriteAborts, CondemnedResurrectStagedAborts}...
WhenFenceTripsBeforeDurableCall. RED: displacement write lands (throws NETWORK_ERROR, not 668).
build_asan unit_tests_dbms: NINJA_EXIT=0. Definitive CA gate
(Cas*:CaLifecycle*:CaWiring*:ContentAddressed*:CountingBackendShape*:RefSnapshotCodec*:RefTableCacheEviction*:RefWriter*)
= 1215/1215 PASSED. Backlog {#c2-resurrect-putoverwrite-fence-check} marked DONE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…ec rev.8)
Owner decision (2026-07-23): remove the natural Vanished(erased) proof
entirely rather than ship it dormant ("чем меньше кода, тем лучше").
Erasure is never proven by the system, only asserted by the operator via
SYSTEM CONTENT ADDRESSED FORGET. The reviewed+tested implementation stays
in git history as the v2 door.
Excised (spec rev.8 §9 items 1-10):
- CasPool: evaluateErasureProofEmptySample, the streak/sample state, the
grace arithmetic, the SentinelsGoneEmptyPrefix verdict + its M1 promotion
guard. Step 0's KeyAbsent-of-both-sentinels now goes straight to
IdentityLost (no prefix-emptiness leg).
- CasSentinelProbe: probePrefixEmptiness + the probePrefixEmptinessRaw
Backend virtual / InstrumentedBackend forwarding. probeSentinel stays.
- The DurableRequestGuard / outstandingDurableRequests / beginDurableRequest
counter machinery (CasMountRuntime, CasPlainObjects, CasPartWriteTxn,
ContentAddressedTransaction, CasPool). Every fence-generation capture and
checkFenceOrThrow (incl. the staging finalize token and the I2
resurrect/putOverwrite displacement checks) is KEPT.
- Backend::supportsErasureProof / ObjectStorageBackend::
setStrongPrefixListCapable + strong_prefix_list_capable + the test-only
capability backend.
- PoolConfig::gc_quiescent_fn + ContentAddressedMetadataStorage::
gcQuiescentForErasureProof + the T8 wiring. CasGcScheduler::isQuiescent /
round_in_flight stay (the FORGET / GC-STOP join-completion signal).
- PoolLifecycle::VanishedErased (a -Wswitch sweep across
casLifecycleToString, casLifecycleReasonWord, lifecycleReasonDetail, the
gate, the snapshot, and the tests). The [D5] erased message goes with it.
rev.8 terminal semantics + adjudicated additions:
- IdentityLost is now a fail-loud TERMINAL state: the remount observer
thread self-exits (new CasMountRuntime::remountTerminal() folds
IdentityLost into the vanished_intent boundary) and the GC scheduler
self-exit widens to isVanished || vanishedIntent || IdentityLost (§9 item
8), clearing the in-process leader hint on the self-exit path (item 9).
- The surviving Replaced verdict gains the one-line mid-FORGET intent guard
(item 7): if vanishedIntentPublished() bail before enterVanished(Replaced),
so FORGET's Vanished(forgotten) wins — the only remaining mid-FORGET
natural-terminal race post-excision.
Tests: gtest_cas_erasure_proof.cpp deleted; the lifecycle/gate/forget/
gc-stop-start/sentinel-probe/fence-generation/snapshot/empty-proof suites
adjusted (IdentityLost terminal-exit assertions replace the demoted-observer
ones; the counter and prefix-emptiness tests removed; the fence-generation
tests incl. the I2 pair kept; a new ForgetIntentBlocksNaturalReplacedPromotion
and RemountThreadSelfExitsOnceIdentityLost added).
Net -1095 lines. Build clean; definitive CAS gtest gate 1200/1200 PASSED.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…t_body advisory FSCK now runs on a RUNNING (Mounted) content-addressed disk via the Admin-class gate (checkOpAdmitted, consistent with SYSTEM CONTENT ADDRESSED GC RUN): the "run SYSTEM CONTENT ADDRESSED UNMOUNT first" refusal is removed. The dormant observe-only path is kept transitionally until Task 15. The read-only scan tolerates concurrent writers because its ref-walk findings are revalidated against a fresh authoritative read before being reported. A committed ref's missing manifest body stays a HARD dangle, but only after revalidation: HEAD the exact object AND re-resolve the exact ref freshly (the manifest sibling of the existing blob-Dangling recheck), so a concurrent republish/drop + legitimate GC delete of the superseded manifest no longer surfaces as a phantom. meta_without_body becomes ADVISORY: GC's body-then-meta delete ordering runs on a bounded, error-suppressed advisory pool that may drop the op, so no finite grace makes a persistent body-less .meta hard evidence. It is still counted and reported; it is excluded from FsckReport::clean(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…n a mounted disk) FSCK no longer refuses on a Running disk (T13 removed the "run SYSTEM CONTENT ADDRESSED UNMOUNT first" refusal), so the fsck_refused_while_mounted probe now prints a clean summary instead of the refusal — its grep -c returns 0, making the reference's expected 1 deterministically RED. Remove only that block (the echo + grep from the script and the matching row from the .reference). The rest of the flow (GC RUN / DROP / UNMOUNT / dormant FSCK / non-CA reject / requires- disk) is untouched — the dormant-to-running switch of that flow is Task 14. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Switch the three CA stateless tests to the rev.8 FORGET-based teardown. After
DROP TABLE ... SYNC, SYSTEM CONTENT ADDRESSED FORGET force-Vanishes the disk
(node-local), then the test verifies via system.content_addressed_mounts that
`lifecycle || '(' || lifecycle_reason || ')'` reads exactly `vanished(forgotten)`
before rm -rf; a failed FORGET or an unexpected lifecycle aborts with the pool
dir left in place (fail-closed, the scripts have no set -e).
FSCK now runs on the RUNNING disk (T13) instead of after UNMOUNT; every
UNMOUNT/dormant reference is removed from the flows and comments. 05020 also
smoke-tests FORGET idempotency (a second FORGET stays vanished(forgotten)), and
the equality SELECT doubles as the SQL-level FORGET + T12 lifecycle-column smoke.
FORGET logs an operator WARNING that the harness (--send_logs_level=warning)
would flag as stderr, so the FORGET calls run at --send_logs_level=fatal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Remove the 2026-07-21 Task 4-8 Dormant/UNMOUNT reuse lifecycle now that the rev.7/8 lifecycle gate stands alone (TLA Model 2 green): - MountState enum + every branch; unmountSynchronously/mountExplicitly + the drain loop; the SYSTEM CONTENT ADDRESSED UNMOUNT/MOUNT verbs end to end (two AccessTypes, AST enum/format, parser, interpreter dispatch + handlers). - The 9 [Task 15] transitional branches: the 7 probe-method isMounted() guards, the tryGetInManifestBytes guard, and checkOpAdmitted's not-Mounted branch. - The FSCK dormant observe-only leg (FSCK is running-only, Admin-gated) and openPoolView's now-dead observe_only parameter. - The 4 old lifecycle gtests + the gate's transitional test. throwNotMounted(MountState) becomes throwStorageNotStarted(): the null-pool answer (before startup / after shutdown -- the storage-level Constructing/ ShutDown lifecycle) is now FAIL-LOUD for every op class, Probe included, per spec sec.1. This flips the shutdown-window probe answer from benign-absent to throw; two new gtests (ShutdownDiskProbesFailLoud, NullPoolFailsLoudForEveryClass) pin it. Safe: post-rollback a null pool is reachable only pre-startup (not yet exposed) or post-shutdown (server teardown) -- never during normal running-server operation, so the T8a DROP-TABLE-wedge scenario (UNMOUNT on a live server) is structurally gone. KEEP: poolAccess null-pool + throwIfLifecycleTerminal, atomic startup(), the gated GC RUN entry points, FORGET/GC STOP/START, empty-proof, fence-generation, Part 1 abort-hardening. Gate 1200/1200 (0 FAILED); stateless 05020/04290/04295 3/3 OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
05018_content_addressed_mount_unmount.{sh,reference}: DELETED — a pure e2e
of the removed Dormant/UNMOUNT machinery (the SQL analog of the deleted
lifecycle gtests); its mounted-GC-RUN coverage lives in 05020.
05019: TRIMMED to FSCK-only access control and renamed to
05019_content_addressed_fsck_access.{sh,reference} — a test name must not
advertise verbs that no longer exist. FORGET / GC STOP / GC START access
coverage is tracked for the acceptance task (T16).
Verified: praktika run [ OK ] 0.84s, Failed: 0 (test_task15b_05019.log).
Committed by the controller: three disposition messages raced the
implementer's sends; the files are test-only and the disposition was
already adjudicated in the ledger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
… ledger Closes the accumulated adjudicated test-debt from eleven per-task reviews as one testing task (spec rev.8 §9). Adds four gtests and one stateless test; folds the rev-t15 cosmetic terminology nits. - CasGcStopStart.ConcurrentStopStartFromTwoThreadsStaysConsistent [T11 M3]: two-thread storage-layer GC STOP/START barrage — serialized, last-call-wins, no torn state, futures-bounded no-deadlock, data plane intact. - CasGcStopStart.OperatorStopPersistsAcrossTransientRecovery [T11 cannot-verify]: operator STOP persists across a real transient→Live self-remount; only an explicit START resumes rounds (same gc_id). - CasEmptyProof.ReadOnlyWithOnlyPoolMetaDeletedThrowsErasedFlavoredOnKeyAbsent [T9 minor]: delete ONLY _pool_meta on the real Local backend → KeyAbsent → the erased-flavored 668, distinct from the whole-root ContainerAbsent case. - CasOperationGate.RemoveThrowsDuringTransientAndDrainsAfterRecovery: the §4 transient auto-recovery / DROP-drain round-trip on one storage — Remove throws 668 in the gap, drains after recovery. - 05022_content_addressed_verb_access.sh: access control for FORGET / GC STOP / GC START (denied before disk resolution; granted → BAD_ARGUMENTS on default; no-disk → SYNTAX_ERROR), mirroring 05019 (kept FSCK-named). - Cosmetic: rename CaWiring.OperationsRefuseWhenNotMounted → OperationsRefuseWithoutPublishedPool; retire obsolete "Mounted"/"not mounted" terminology in the empty-proof comment and gcStop's no-scheduler log. no-silent-empty and FORGET-on-live are covered by existing suites (cited in the report); no duplicates added. Gate 1204/1204 (was 1200, +4); 05022 praktika 2×OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…log round-closure
Closing sweep for the rev.8 disk-lifecycle round (spec rev.8 §9).
Comment relics (T17 notes item 2):
- `ContentAddressedMetadataStorage::stagingKeyPrefix` — `store()` "throws
INVALID_STATE when not mounted" -> "when no pool is published
(pre-`startup`/post-`shutdown`)".
- `gcStop` declaration comment — "not mounted" -> "not started".
Stale rev.7 erased-proof wording in user-facing surfaces (whole-increment review
M8, routed to T17). rev.8 excised the natural erased proof: `IdentityLost` is a
fail-loud TERMINAL state, the remount + GC threads self-exit (no observer), and
the only `Vanished` sub-states are `replaced`/`forgotten`. Fixed the surfaces
that still described the excised behavior:
- `CasIdentityLost` / `CasDataRootVanished` ProfileEvents descriptions.
- The `IdentityLost` transition WARN in `CasMountRuntime::enterIdentityLost`.
- The `IdentityLost` [D5] detail in `Pool::lifecycleReasonDetail` and the
reason-word / detail comment in the snapshot doc-block (dropped the dead
`erased` word and the "a live erase in progress" over-specification).
No test observes these strings (verified by grep). Re-gated on the rebuilt
binaries: CA gtest gate 1204/1204 (build_asan/test_task17_gate.log); stateless
CA family 6/6 x2 (build_asan/test_task17_stateless_{a,b}.log).
BACKLOG:
- New round-closure section {#disk-lifecycle-rev8-closure}: what the round
resolved (05020 test isolation; G1 abort / G2 zombie-spam terminal case; the
throw-when-uncertain gate; FSCK-on-running) vs what stays deliberately
deferred (the disk-lifecycle-leak proper — no eject on DROP; reuse machinery
rolled back in T15), plus the four accepted residuals (a-d).
- Confirmed the two round-marked entries; retired the stale "DECISION PENDING"
paragraph on the now-OBSOLETE erased-capability entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Last spec-vs-code pass for the disk-lifecycle round. - §3 "thread exits": the GC scheduler self-exit condition as built is `isVanished() || vanishedIntentPublished() || lifecycle()==IdentityLost` (in both `loop` and `heartbeatLoop`). The spec listed only `Vanished` AND `IdentityLost`, omitting the pre-terminal FORGET-intent leg (earliest-signal discipline). Added it, cited the as-built expression, and listed the GC scheduler wake among the terminal-intent-latch consumers. - §5-7: `FSCK`-on-running is landed (T13), not planned — dropped the stale "planned T13". §9 excision list re-verified against the tree (all 11 excised symbols absent, `gtest_cas_erasure_proof.cpp` removed, every KEPT symbol present); no §9 edit needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…tityLost enum doc A reviewer found two stale rev.7 strings that commit 87aeefa's sweep missed. rev.8 is FORGET-only: the system never proves erasure, and a pool can only be VanishedReplaced (foreign pool_id proven) or VanishedForgotten (operator-asserted via `SYSTEM CONTENT ADDRESSED FORGET`). - `StorageSystemContentAddressedMounts.cpp`: the SQL-exposed `lifecycle_reason` / `lifecycle_detail` column comments for `system.content_addressed_mounts` still said "erased, replaced or forgotten" / "data root erased / replaced / decommissioned...". Dropped the dead `erased` word to match `casLifecycleReasonWord` (which returns only "replaced" or "forgotten") and the sibling doc-comment in `ContentAddressedMetadataStorage.h` that 87aeefa already fixed. - `CasMountRuntime.h`: the `IdentityLost` enum doc-comment still described "(a live erase in flight)". Identity loss means the pool's sentinels were observed absent (`KeyAbsent` on both) — the cause is unknown to the system. Replaced the parenthetical with "(both KeyAbsent)", matching the wording 87aeefa already established for the sibling ProfileEvents descriptions, the `IdentityLost` WARN log, and `lifecycleReasonDetail`. Verified no test (src/, tests/queries/, tests/integration/) observes either the old or new strings — the three stateless tests that read `lifecycle_reason` (05020_content_addressed_fsck.sh, 04290/04295_content_addressed_*_no_leftovers.sh) only `SELECT` the runtime column value, never assert on the comment text. Re-gated on rebuilt build_asan/build binaries: CA gtest gate 1204/1204 (build_asan/test_t17fix_gate.log). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
The manual GC verbs checked the admission gate BEFORE acquiring `gc_scheduler_mutex`, so a call admitted while `Live` could block behind a concurrent `SYSTEM CONTENT ADDRESSED FORGET` and then resume on the now-Vanished pool: - I-1: `runGarbageCollectionRoundNow` / `runOneGcRoundForTest` would create a fresh `CasGcScheduler` on the decommissioned pool and run a full round. - I-2: `runGcRebuildNow` took no `gc_scheduler_mutex` at all, so an in-flight baseline rebuild was invisible to FORGET and kept issuing durable `gc/`-plane writes after FORGET reported every CAS thread stopped. Both now follow `gcStart`'s lock-then-gate order: acquire `gc_scheduler_mutex` first (the rebuild holds it for its whole duration, so FORGET serializes behind it), then re-run `checkOpAdmitted(Admin)` under the lock. `Gc::rebuildBaseline` holds only the Pool/backend, so it never re-takes the mutex — no deadlock. Adds two deterministic regression tests (bounded cv waits, no sleeps) via a test-only seam: `GcRunAdmittedWhileLiveRefusesAfterConcurrentForget` (a resumed RUN refuses and does not resurrect the scheduler) and `GcRebuildInFlightSerializesForget` (FORGET waits out an in-flight rebuild). Both verified RED against the pre-fix ordering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…(minors)
Post-`VanishedErased` excision hygiene (rev.8 has only `VanishedReplaced` /
`VanishedForgotten`):
- M-1: five comments said "the three `Vanished*` values"; there are two. Fixed
in `CasMountRuntime.{h,cpp}`, `CasPool.h`, and `casLifecycleToString`.
- M-2: comments still described an "erased" state/conclusion the system never
reaches. Reworded to the rev.8 truth (replaced/forgotten; the [D5] messages
renounce the erasure certainty) in `StorageSystemContentAddressedMounts`,
`CasPool::forgetDisk`, `ContentAddressedTransaction`, and the op-class table.
- M-3: `checkFenceOrThrow` claimed "backing may be temporarily unreachable" on
every fence trip, but the fence is also tripped by FORGET (deliberate
decommission) and stays tripped in every terminal state. Reworded to name both
possibilities (transient OR terminal) without promising recovery, and to
identify the pool. No test asserts the old text (the gate's transient message,
which legitimately keeps "temporarily unreachable", is unchanged).
Comments/message only — no behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
content addressable storage - draft PR
Documentation entry for user-facing changes
TBD.
Exclude tests:
Regression jobs to run: