Skip to content

fix(daemon): close rebuild-index safety gaps from CodeRabbit review - #3318

Merged
Sinity merged 1 commit into
masterfrom
fix/coderabbit-rebuild-index-findings
Jul 27, 2026
Merged

fix(daemon): close rebuild-index safety gaps from CodeRabbit review#3318
Sinity merged 1 commit into
masterfrom
fix/coderabbit-rebuild-index-findings

Conversation

@Sinity

@Sinity Sinity commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses the 8 CodeRabbit findings on PR #3076 (daemon-coordinated online
index rebuilds) that were left unaddressed at merge time, tracked as
polylogue-ogn1. Each finding was independently re-verified against current
source before any fix was applied — several turned out to already be
resolved by the time this bead was filed.

Problem

polylogue-ogn1 notes: "CodeRabbit's review on the PR (commit 667ad8e,
before the final rebase) flagged 8 actionable findings that were not
addressed before merge because the merging task was scoped specifically to
a conflict-free rebase onto origin/master, not a full code-review pass."

Disposition matrix (finding -> status, evidence)

# Finding Disposition Evidence
1 HTTP handler must reject max_blob_mb without raw_ids/only_missing before promoting Already resolved validate_rebuild_index_request already enforced this in the originally merged commit 1c2a07f9b (git show 1c2a07f9b:polylogue/maintenance/rebuild_index.py), called first thing inside rebuild_index_from_source and again explicitly in the HTTP handler before dispatch. No change needed.
2/5 Bridge's fixed ~30s run_sync timeout vs. the 600s CLI/HTTP rebuild contract Fixed Added DaemonWriteThreadBridge.run_sync_with_timeout (per-call override); run_sync now delegates to it with the bridge's own default so every other caller is unaffected. The rebuild-index HTTP route calls it with _REBUILD_INDEX_WRITE_TIMEOUT_S = 600.0, matching the CLI's own urlopen(..., timeout=600).
3 CLI --daemon-url default read POLYLOGUE_DAEMON_URL directly, bypassing the 5-layer config chain Fixed _rebuild_index.py now has its own _default_daemon_url() resolving via load_polylogue_config().daemon_url, matching status.py's established pattern.
4 HTTP handler fell back to running the rebuild directly when write_bridge was missing, instead of failing closed Fixed A real DaemonAPIHTTPServer always installs write_bridge in __init__ (confirmed by reading the constructor), so this branch was unreachable in production — but a route that can execute an authority-promoting write outside the sole-writer coordinator is a bypass of the daemon's single-writer invariant, not a safe fallback. Now returns 503 write_coordinator_unavailable.
6 missing_index_raw_ids returned [] whenever index.db didn't exist yet Fixed Now falls back to the full source set (all_index_rebuild_raw_ids) when the index tier is absent — a fresh/lost index has nothing indexed, so every source row is missing by definition. Previously --only-missing silently rebuilt nothing on a fresh archive or right after ops reset --index.
7 Partial-selection-cannot-promote validation only enforced by the CLI, not the shared service Already resolved Same evidence as #1validate_rebuild_index_request is called as the first statement inside the shared rebuild_index_from_source, so every caller (CLI, HTTP) goes through it.
8 rebuild_index.py (substrate) imported the CLI's private _archive_readiness_status, inverting the repo's layering rule Fixed Extracted the readiness-surface computation into polylogue/storage/archive_readiness.py (archive_readiness_status + its _archive_readiness_counts/_action_readiness_counts/_archive_status_surfaces helpers); status.py now delegates to the same shared implementation instead of owning the only copy.
9 Daemon-driven execution could promote from an empty source snapshot (raw_count == 0), unlike the CLI's own early exit Already resolved Same evidence as #1/#7 — the raw_count == 0 short-circuit lives inside rebuild_index_from_source itself, before any replay, executed by every caller.
10 test_rebuild_index_route_uses_the_bridge_run_sync_writer_path replaced _handle_rebuild_index wholesale, proving only its own stand-in Fixed Rewritten (renamed to test_rebuild_index_route_uses_the_bridge_run_sync_with_timeout_writer_path) to drive the real _do_post_impl dispatch and the real _handle_rebuild_index implementation end to end, with only the typed rebuild service itself stubbed.

Solution

  • polylogue/daemon/write_coordinator.py — new run_sync_with_timeout.
  • polylogue/daemon/http.py — fail-closed on missing write bridge; 600s
    timeout constant for the rebuild-index route.
  • polylogue/cli/commands/maintenance/_rebuild_index.py — config-resolved
    daemon URL default.
  • polylogue/maintenance/rebuild_index.pymissing_index_raw_ids fresh-index
    fix; import switched to the extracted shared readiness module.
  • polylogue/storage/archive_readiness.py — new archive_readiness_status
    (+ helpers), the layering fix for refactor: rebrand as polylogue and move config/state to XDG #8.
  • polylogue/cli/commands/status.py — delegates to the shared module instead
    of owning the readiness computation.
  • Tests: tests/unit/daemon/test_maintenance_endpoints.py (fail-closed proof),
    tests/unit/daemon/test_write_coordinator.py (real timeout-override proof),
    tests/unit/maintenance/test_rebuild_index_selection.py (new, fresh-index
    selection + shared-service validation), tests/unit/daemon/test_http_write_coordination.py
    (real-dispatch rewrite), tests/unit/cli/commands/test_status.py (import
    fixup for the moved functions).

Verification

  • mypy polylogue (strict): Success: no issues found in 1082 source files
  • ruff check . / ruff format --check .: all checks passed / all formatted
  • devtools verify --quick: exit_code: 0 (includes degrade-loudly, now
    allowlisted with rationale for the two extracted typed-signal except-handlers
    in docs/plans/degrade-loudly-allowlist.yaml)
  • devtools render all --check: no out of sync lines, exit 0
  • devtools test across the affected area (status/rebuild_index/daemon http/
    write_coordinator/maintenance CLI, 8 files): 264 passed, 1 pre-existing
    unrelated failure (test_archive_facade_route_catalog_covers_public_async_facade,
    confirmed failing identically on origin/master via git stash before
    this change — a facade/hook-event route catalog completeness check
    unrelated to rebuild-index)

Ref polylogue-ogn1

Co-Authored-By: Claude noreply@anthropic.com

Problem: PR #3076 (daemon-coordinated online index rebuilds) shipped with 8
CodeRabbit findings never addressed, tracked as debt in polylogue-ogn1
because the merge task was scoped to a conflict-free rebase, not review.

What changed, verified against current source (not blindly reapplied):

1. HTTP handler must reject max_blob_mb without raw_ids/only_missing before
   promoting -- ALREADY FIXED prior to merge (validate_rebuild_index_request
   already enforced this in the merged commit 1c2a07f; confirmed via
   `git show 1c2a07f:polylogue/maintenance/rebuild_index.py`). No change.

2/5. write_coordinator.py's bridge held a fixed ~30s run_sync timeout while
   the CLI/HTTP rebuild contract allows up to 600s. Added
   DaemonWriteThreadBridge.run_sync_with_timeout (a per-call override,
   scoped to this need rather than a general timeout-override framework);
   run_sync now delegates to it with the bridge's own default. The
   rebuild-index HTTP route calls it with a 600s budget matching the CLI's
   own urlopen(..., timeout=600).

3. CLI --daemon-url default read POLYLOGUE_DAEMON_URL directly via
   os.environ.get, bypassing the 5-layer config precedence chain every
   other daemon-URL consumer goes through. Now resolves via
   load_polylogue_config().daemon_url, matching status.py's own
   _default_daemon_url pattern.

4. HTTP handler fell back to executing the rebuild directly when
   server.write_bridge was missing, instead of failing closed. A real
   DaemonAPIHTTPServer always installs write_bridge in __init__, so this
   was unreachable in production, but a route that CAN execute an
   authority-promoting archive write outside the sole-writer coordinator is
   a bypass of the daemon's single-writer invariant, not a safe fallback.
   Now returns 503 write_coordinator_unavailable instead.

6. missing_index_raw_ids returned [] whenever index.db did not exist yet,
   making --only-missing rebuild nothing on a fresh archive or right after
   `ops reset --index` -- exactly the case it exists to handle. Now falls
   back to the full source set (every row is missing by definition).

7/9. Partial-selection-cannot-promote and empty-source-count-0 guards --
   ALREADY enforced inside the shared rebuild_index_from_source service
   itself (validate_rebuild_index_request called first thing; raw_count==0
   short-circuit before any replay), not merely the CLI. Confirmed present
   in the original merged commit. No change.

8. rebuild_index.py (substrate) imported the CLI's private
   _archive_readiness_status, inverting this repo's documented
   surfaces-may-not-import-substrate rule. Extracted the readiness-surface
   computation (archive_readiness_status + its _archive_readiness_counts/
   _action_readiness_counts/_archive_status_surfaces helpers) into
   polylogue/storage/archive_readiness.py; status.py now delegates to the
   same shared implementation instead of owning the only copy.

10. test_rebuild_index_route_uses_the_bridge_run_sync_writer_path replaced
   _handle_rebuild_index wholesale with its own body calling bridge.run_sync
   -- it only proved the test's own stand-in worked, never the real
   production handler. Rewritten to drive the real _do_post_impl dispatch
   and the real _handle_rebuild_index implementation end to end.

Solution: polylogue/daemon/write_coordinator.py (run_sync_with_timeout),
polylogue/daemon/http.py (fail-closed + 600s timeout on rebuild-index),
polylogue/cli/commands/maintenance/_rebuild_index.py (config-resolved
daemon URL), polylogue/maintenance/rebuild_index.py (missing_index_raw_ids
fix), polylogue/storage/archive_readiness.py (readiness extraction),
polylogue/cli/commands/status.py (delegates instead of owning). New/updated
tests prove each fix: a rejected unsafe fallback (test_maintenance_endpoints
.py), a real per-call timeout override (test_write_coordinator.py), a
fresh-index --only-missing selection (test_rebuild_index_selection.py), and
a real-dispatch rebuild-index route proof (test_http_write_coordination.py).

Verification:
- mypy polylogue (strict): Success: no issues found in 1082 source files
- ruff check . / ruff format --check .: All checks passed / all formatted
- devtools verify --quick: exit_code 0 (includes degrade-loudly, now
  allowlisted for the two extracted typed-signal except-handlers)
- devtools render all --check: no "out of sync" lines, exit 0
- devtools test across the affected area (status/rebuild_index/daemon http/
  write_coordinator/maintenance CLI): 264 passed, 1 pre-existing unrelated
  failure (test_archive_facade_route_catalog_covers_public_async_facade,
  confirmed failing identically on origin/master before this change via
  `git stash`)

Ref polylogue-ogn1

Co-Authored-By: Claude <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a76927c-56a4-4208-8310-78b3f479cbf1

📥 Commits

Reviewing files that changed from the base of the PR and between 56ba960 and b655942.

📒 Files selected for processing (12)
  • docs/plans/degrade-loudly-allowlist.yaml
  • polylogue/cli/commands/maintenance/_rebuild_index.py
  • polylogue/cli/commands/status.py
  • polylogue/daemon/http.py
  • polylogue/daemon/write_coordinator.py
  • polylogue/maintenance/rebuild_index.py
  • polylogue/storage/archive_readiness.py
  • tests/unit/cli/commands/test_status.py
  • tests/unit/daemon/test_http_write_coordination.py
  • tests/unit/daemon/test_maintenance_endpoints.py
  • tests/unit/daemon/test_write_coordinator.py
  • tests/unit/maintenance/test_rebuild_index_selection.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/coderabbit-rebuild-index-findings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity merged commit bb36a1c into master Jul 27, 2026
3 checks passed
@Sinity
Sinity deleted the fix/coderabbit-rebuild-index-findings branch July 27, 2026 11:21
Sinity added a commit that referenced this pull request Jul 27, 2026
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.

1 participant