Skip to content

[WIP] CAS draft (adopting to CI/CD, not for review / merge)#2073

Draft
filimonov wants to merge 2603 commits into
antalya-26.6from
cas-gc-rebuild
Draft

[WIP] CAS draft (adopting to CI/CD, not for review / merge)#2073
filimonov wants to merge 2603 commits into
antalya-26.6from
cas-gc-rebuild

Conversation

@filimonov

@filimonov filimonov commented Jul 17, 2026

Copy link
Copy Markdown
Member

Changelog category (leave one):

  • Not for changelog (changelog entry is not required)

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:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Workflow [PR], commit [0ff1cbf]

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>
filimonov and others added 21 commits July 20, 2026 10:45
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>
filimonov and others added 30 commits July 23, 2026 04:48
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants