fix(code-index): stamp continuations before the seat looks idle - #1836
Conversation
A seat waiter that sees reconcile_in_progress at zero treats the owner as finished. Noting BusyFollowUp after that drop sampled an empty slot and then raced the failure ceiling. The stamp now holds the pass guard for the note only. A store directory that does not exist yet is an unpublished plan, not Storage(NotFound). Planning against latest_generation_id before cold open created the scoped store was that error. The retention journey now re-reads the store on the serving-seat signal. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
|
|
Pairs with #1826 (admission retake / BusyFollowUp stamp). Merge either order; avoid double-editing the same pass-tail paths without rebase. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…-failure-ceiling-3ef6 # Conflicts: # crates/tracedecay/src/daemon/production_harness/generation_retention_test.rs
A missing scope root is an unpublished plan (#1836), not GenerationStoreBusy (690e843): the only production caller re-plans on its interval either way, and an absent root has no publisher to wait for, so Busy would log degraded and retry fast forever for a never-indexed project. The enumerate-then-open deferrals stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Takes batch D's resolution of the missing-scope-root contract: the root-case test is deleted there (735bfbd) and #1836's own test already pins the unpublished plan, so the rewrite from db2a28a is dropped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> # Conflicts: # crates/tracedecay-code-index-retention/src/code_index_generations/tests.rs
Two landed changes disagreed about a store root that does not exist yet. ScriptedAlchemy#1811 deferred with `GenerationStoreBusy`, ScriptedAlchemy#1836 returned the unpublished plan. Both tests survived the batch-D merge, so `preparation_defers_when_the_scope_root_does_not_exist_yet` failed deterministically against ScriptedAlchemy#1836's early return. Keep the unpublished plan. The only production caller is `tracedecay-maintenance::store_maintenance::run_code_generation_retention`. It routes `GenerationStoreBusy` through `defer_generation_store_busy`, which logs `retention_degraded failure=generation_store_busy` and returns `Failed`. `generation.rs` turns `Failed` into `MaintenanceTickOutcome` `Retry`, which `tick.rs` schedules on the short `retry_delay` and counts in `daemon.maintenance.generation.retry_total`. That deferral is a counted, logged failure, not a quiet wait. An empty plan returns `Complete`, which `tick.rs` schedules on the ordinary `interval`. Neither answer is terminal, so no caller plans once and stops. The maintenance loop re-plans every tick, and the mounted retention journey loops on `Ok(_)` and `GenerationStoreBusy` alike. The difference is duration. An absent scope root has no publisher to wait for, because cold open creates it inside the worker, so a mounted project that is never indexed keeps an absent root for the life of the daemon and the deferral would report degraded on every tick. The unpublished plan still converges the moment the store appears. The failure ceiling ScriptedAlchemy#1836 names is `SERVING_SEAT_FAILURE_CEILING`, a test-only wall bound on a positive serving-seat wait in `code_index_scheduler/tests/mod.rs`. It consumes no retention error, so it constrains neither contract. ScriptedAlchemy#1811's deeper `NotFound` mapping stays. `deferred_if_absent` still covers the two enumerate-then-open sites, where `read_dir` already proved the name existed and a peer can unlink it before the open. That race is unreachable from the root check, and d6d8665 already reverted the two durable-state sites on the same reasoning. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
A serving-seat waiter that sees
reconcile_in_progressat zero treats the owner as idle.BusyFollowUpwas stamped after that drop, so the waiter sampled an empty slot and then raced the failure ceiling.Storage(NotFound).latest_generation_idcan move before cold open creates the scoped store.This does not retake the background admission permit around text projection. That ordering is #1826.
ci.ymlis unchanged. No merge.Verification
cargo test -p tracedecay-code-index-retention missing_store_is_an_unpublished_plan_not_a_storage_failure— 1 passedcargo check -p tracedecay-code-index-runtime --tests— finishedcargo check -p tracedecay --tests --lib— finished