Skip to content

fix(daemon): reconcile blob publications periodically - #3886

Merged
Sinity merged 5 commits into
masterfrom
feature/fix/periodic-blob-publication-reconcile-final
Aug 8, 2026
Merged

fix(daemon): reconcile blob publications periodically#3886
Sinity merged 5 commits into
masterfrom
feature/fix/periodic-blob-publication-reconcile-final

Conversation

@Sinity

@Sinity Sinity commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Reconcile safely terminal blob-publication reservations every 900 seconds during daemon uptime. The periodic path uses the daemon write coordinator, resolves the active index generation, advances through bounded 200-row pages, and preserves unresolved reservations that still have bytes but no reference.

Problem

Blob-publication reconciliation ran only during daemon startup. A long-lived daemon could retain reservations that later became safely clearable until the next restart, while the two measured unresolved reservations correctly remained outside automatic cleanup.

Solution

Add a periodic maintenance loop beside blob GC, register it in daemon service lifecycle and schema blocking, and return a typed reconciliation outcome from the shared operation. A stable publication-ID cursor prevents retained rows from starving later cleanup. Real source and active-generation fixtures prove that missing-blob and referenced reservations are cleared while present, unreferenced reservations survive repeated ticks.

Verification

devtools test tests/unit/daemon/test_blob_gc_periodic.py tests/unit/daemon/test_daemon_cli.py tests/unit/pipeline/test_blob_publication_crash_matrix.py tests/unit/pipeline/test_acquisition_blob_gc_age_gate.py -k 'publication or reconcile_blob_publications'

Result: 11 passed, 128 deselected.

The same four files without the selector produced 134 passes and five failures. All five node IDs are present in the exact-master baseline ledger.

devtools verify --quick

Result: all 24 steps passed in 20260808T124246Z-quick-1875233-196795f4 at 625a0851ca6110f41428baa632a17eed172aaba2.

devtools verify refused before test selection because current master has no valid testmon seed. The exact-master full baseline 20260808T062115Z-full-1249172-f00c535f has 414 failures and 16 errors. That verification-stack residual remains under polylogue-93xe.

Bead disposition matrix

Assigned Bead Whole-Bead disposition Evidence refs Named successor for residual work
polylogue-7rds partial focused real-route test, final quick gate, exact-master baseline receipt polylogue-93xe

Risks and Follow-ups

No production mutation was performed in this lane. The periodic operation deliberately does not clear present, unreferenced reservations; explicit abandonment retains its existing confirmation and receipt protocol.

Sinity and others added 3 commits August 8, 2026 14:14
Problem: Startup reconciliation clears terminal publication reservations, but reservations can become safely classifiable during a long-lived daemon run. Unresolved reservations must remain protected for explicit abandonment policy.

What changed: Add an independently scheduled, coordinator-owned reconciliation loop beside blob GC. Reuse the exclusion-backed classifier with a maintenance actor, register the loop in daemon startup and schema-blocked inventories, and cover repeated safe cleanup plus lifecycle cancellation through real archive fixtures.

Compatibility: No abandoned-reservation policy or live cleanup behavior changes. Unresolved reservations remain durable.

Verification: direnv exec . devtools test tests/unit/daemon/test_blob_gc_periodic.py tests/unit/daemon/test_daemon_cli.py::test_run_daemon_services_waits_for_fts_startup_before_watcher tests/unit/daemon/test_daemon_cli.py::test_run_daemon_services_drains_servers_when_main_task_is_cancelled tests/unit/daemon/test_catch_up_gate_timeout.py::test_run_daemon_services_schema_block_logs_parked_loops_and_emits_event; direnv exec . devtools verify --quick

Ref polylogue-7rds

Co-Authored-By: Claude <noreply@anthropic.com>
Preserve the concrete reconciliation result type across the shared startup and periodic daemon route instead of widening the new return value to Any.
Record the real-route verification for polylogue-7rds and close the implementation scope. Link the unavailable default testmon gate to polylogue-93xe, which owns the exact-master verification baseline.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 42 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: 791c1b54-516f-4cf8-848a-7fef981580f6

📥 Commits

Reviewing files that changed from the base of the PR and between d90ed88 and 625a085.

📒 Files selected for processing (5)
  • .beads/issues.jsonl
  • polylogue/daemon/blob_gc_periodic.py
  • polylogue/daemon/cli.py
  • polylogue/storage/blob_publication.py
  • tests/unit/daemon/test_blob_gc_periodic.py
📝 Walkthrough

Walkthrough

The daemon now runs blob-publication reconciliation every blob-GC interval after catch-up. It uses the write coordinator, retries transient SQLite locks, preserves unresolved reservations, and includes startup and cancellation lifecycle tests.

Changes

Blob-publication reconciliation

Layer / File(s) Summary
Reconciliation path
polylogue/daemon/blob_gc_periodic.py, polylogue/daemon/cli.py, tests/unit/daemon/test_blob_gc_periodic.py
The daemon adds periodic reconciliation through the write coordinator. Terminal missing-publication rows are cleared, unresolved rows remain, and transient SQLite locks retry on the next interval.
Daemon maintenance wiring
polylogue/daemon/cli.py, tests/unit/daemon/test_daemon_cli.py
Daemon startup schedules the reconciliation loop after watcher catch-up. Lifecycle tests verify startup scheduling and cancellation cleanup.
Implementation record
.beads/issues.jsonl
The issue record marks the implementation closed and records verification results and the related follow-up issue.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Daemon
  participant ReconciliationLoop
  participant WriteCoordinator
  Daemon->>ReconciliationLoop: start after watcher catch-up
  ReconciliationLoop->>WriteCoordinator: reconcile publication reservations
  WriteCoordinator-->>ReconciliationLoop: return reconciliation result
  ReconciliationLoop-->>Daemon: emit maintenance event
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: periodic blob-publication reconciliation in the daemon.
Description check ✅ Passed The description includes all required sections, verification results, scope evidence, bead disposition, and follow-up risks.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fix/periodic-blob-publication-reconcile-final

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.beads/issues.jsonl:
- Line 912: Update the issue record for polylogue-7rds so the relates-to
dependency on polylogue-93xe is created before the closed_at timestamp and
closure metadata, preserving consistent dependency ordering and counts. Keep the
existing residual-transfer relationship and closure disposition intact.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8a4153ee-5faa-4799-b978-93590bc47492

📥 Commits

Reviewing files that changed from the base of the PR and between e6228af and d90ed88.

📒 Files selected for processing (5)
  • .beads/issues.jsonl
  • polylogue/daemon/blob_gc_periodic.py
  • polylogue/daemon/cli.py
  • tests/unit/daemon/test_blob_gc_periodic.py
  • tests/unit/daemon/test_daemon_cli.py

Comment thread .beads/issues.jsonl Outdated
Correct the successor-edge timestamp to UTC so the durable residual transfer precedes the polylogue-7rds closure record.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d90ed88438

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread polylogue/daemon/blob_gc_periodic.py Outdated
while True:
await asyncio.sleep(BLOB_PUBLICATION_RECONCILIATION_INTERVAL_SECONDS)
try:
await _reconcile_blob_publications(actor="maintenance.blob_publication_reconciliation")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the active index before periodic reconciliation

When .index-active-pointer selects a generation different from archive_root/index.db—the supported interrupted-promotion state—the new periodic loop delegates to _reconcile_blob_publications, which still passes the conventional root / "index.db" path. A crash-left receipt referenced only by an attachment in the active generation is therefore classified as unresolved and retained on every tick instead of being cleared; resolve the active index with resolve_active_index_path(root) before invoking the reconciler.

Useful? React with 👍 / 👎.

Comment thread polylogue/daemon/blob_gc_periodic.py Outdated
while True:
await asyncio.sleep(BLOB_PUBLICATION_RECONCILIATION_INTERVAL_SECONDS)
try:
await _reconcile_blob_publications(actor="maintenance.blob_publication_reconciliation")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bound each periodic reconciliation pass

When a failed bulk publication leaves a large reservation backlog, this scheduled call holds the daemon write coordinator while inspect_blob_publication_receipts loads every row, performs per-row filesystem/reference checks, and then deletes the entire clearable set in one transaction. Unlike the adjacent blob-GC loop's 200-row cap, there is no batch limit here, so a periodic tick can monopolize Polylogue's sole writer and stall live ingestion and API writes for an unbounded interval; paginate a bounded number of reservations per pass and leave the remainder for later ticks.

AGENTS.md reference: AGENTS.md:L181-L183

Useful? React with 👍 / 👎.

Resolve attachment references against the active index generation. Page periodic reservation scans in 200-row windows with a stable cursor so unresolved rows do not starve later terminal cleanup or monopolize the daemon writer.
@Sinity
Sinity merged commit 1cb33c3 into master Aug 8, 2026
3 checks passed
@Sinity
Sinity deleted the feature/fix/periodic-blob-publication-reconcile-final branch August 8, 2026 12:51
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