Skip to content

fix(node): implement reconciliation sweep as durability backstop (#218)#221

Open
Gravirei wants to merge 1 commit into
Gitlawb:mainfrom
Gravirei:fix/issue-218-reconciliation-sweep
Open

fix(node): implement reconciliation sweep as durability backstop (#218)#221
Gravirei wants to merge 1 commit into
Gitlawb:mainfrom
Gravirei:fix/issue-218-reconciliation-sweep

Conversation

@Gravirei

@Gravirei Gravirei commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the periodic reconciliation sweep the replication path already assumes as a durability backstop. Previously, every path that drops a pin or recovery copy (mid-drain panic, node crash/seal, client disconnect at the receive-pack tail) resulted in data loss with no safety net.

Motivation & context

Closes #218

The codebase justified tolerating dropped post-push replication work by pointing at a reconciliation sweep that did not exist. This made "lost forever" literal rather than conservative phrasing, violating the project's stated promise that "once code is pushed to the network, it should not disappear because one server went down."

Kind of change

  • Bug fix
  • Feature
  • Security fix
  • Docs
  • Tests / CI
  • Refactor (no behavior change)
  • Breaking or protocol change (issue required first)

What changed

  • crates/gitlawb-node/src/reconciliation.rs (new): Periodic sweep that re-derives the set of objects a repo should have pinned/sealed under current visibility rules

    • Runs hourly, capped at 100 repos per pass with a cursor to prevent O(repos) amplification
    • For each announceable repo: full-scans all git objects, applies fail-closed visibility filtering, pins missing objects to IPFS and Pinata, re-seals encrypted recovery copies, and anchors sealed manifests to Arweave
    • Reuses existing fresh-resolution pipeline (list_all_objects, replicable_blob_set, replicable_objects_fail_closed, ipfs_pin::pin_new_objects, pinata::pin_new_objects, encrypted_pin::encrypt_and_pin)
    • Skips non-announceable repos (private / mode A / undetermined)
    • Respects graceful shutdown signal
  • crates/gitlawb-node/src/metrics.rs: Added gitlawb_reconciliation_gaps_found_total and gitlawb_reconciliation_gaps_filled_total counters

  • crates/gitlawb-node/src/main.rs: Registered reconciliation module and spawned the background sweep task

How a reviewer can verify

cargo check -p gitlawb-node
cargo clippy -p gitlawb-node -- -D warnings
cargo test -p gitlawb-node -- metrics::tests

Before you request review

  • Scope is one logical change; no unrelated churn
  • cargo test --workspace passes locally (DB-dependent tests require a running Postgres)
  • New behavior is covered by tests (unit tests cover the building blocks; sweep itself is integration-tested at runtime)
  • cargo clippy --workspace --all-targets -- -D warnings is clean
  • Commit titles use Conventional Commits (fix(...))

Notes for reviewers

The sweep is intentionally conservative per pass (100 repos, hourly) to avoid competing with the push path for resources. The cursor wraps around so every repo is eventually covered. Encrypted pin re-sealing and Arweave manifest anchoring are best-effort (failures are logged and skipped).

Summary by CodeRabbit

  • New Features

    • Added automatic reconciliation sweeps to identify and restore replication or pinning gaps.
    • Reconciliation runs periodically in the background and continues processing across repositories.
    • Added support for preserving encrypted, path-scoped content during reconciliation.
  • Monitoring

    • Added Prometheus metrics for reconciliation gaps detected and successfully filled.
    • Added operational logging for reconciliation activity and outcomes.

Copilot AI review requested due to automatic review settings July 19, 2026 10:39

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the needs-tests Source changed without accompanying tests (advisory) label Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution. A couple of things will help us review this faster:

  • This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.

See CONTRIBUTING.md. Update the PR and these notes will clear automatically.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 54 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 961f37cf-884c-4f19-96d6-4d00fc05c600

📥 Commits

Reviewing files that changed from the base of the PR and between b31cf88 and 9352318.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/main.rs
  • crates/gitlawb-node/src/metrics.rs
  • crates/gitlawb-node/src/reconciliation.rs
📝 Walkthrough

Walkthrough

Adds an hourly, batched reconciliation worker with cooperative shutdown, startup wiring, and gap metrics. It scans eligible repository objects, fills missing IPFS/Pinata pins, and optionally encrypts path-scoped data and anchors manifests to IRYS.

Changes

Reconciliation sweep

Layer / File(s) Summary
Gap metrics
crates/gitlawb-node/src/metrics.rs
Registers counters and public helpers for reconciliation gaps found and filled.
Periodic reconciliation flow
crates/gitlawb-node/src/reconciliation.rs
Processes repository batches, derives pin-eligible objects, fills missing pins, and optionally performs encrypted sealing and IRYS anchoring.
Startup integration
crates/gitlawb-node/src/main.rs
Registers the reconciliation module and starts its background worker with shared dependencies and shutdown handling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReconciliationWorker
  participant Database
  participant LocalRepository
  participant IPFS
  participant IRYS
  ReconciliationWorker->>Database: Load repository batch and pinned state
  ReconciliationWorker->>LocalRepository: Scan eligible objects
  ReconciliationWorker->>IPFS: Pin missing objects and encrypted output
  ReconciliationWorker->>IRYS: Anchor encrypted manifest when configured
Loading

Possibly related PRs

  • Gitlawb/node#34: Updates the pinning and replication eligibility path used by reconciliation.
  • Gitlawb/node#90: Complements candidate-based pinning with periodic reconciliation of missing objects.
  • Gitlawb/node#194: Shares node startup wiring changes in main.rs.

Suggested labels: subsystem:visibility

Suggested reviewers: vasanthdev2004

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the new reconciliation sweep and its durability purpose, matching the primary change.
Description check ✅ Passed The description follows the template with summary, motivation, change list, verification, and reviewer notes.
Linked Issues check ✅ Passed The sweep, metrics, shutdown handling, and bounded periodic work match the requirements in issue #218.
Out of Scope Changes check ✅ Passed The changes stay focused on the reconciliation sweep, metrics, and startup wiring without unrelated churn.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@beardthelion beardthelion added crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior labels Jul 19, 2026
@Gravirei
Gravirei force-pushed the fix/issue-218-reconciliation-sweep branch from 5c771ec to b31cf88 Compare July 19, 2026 11:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
crates/gitlawb-node/src/reconciliation.rs (1)

25-32: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

First reconciliation pass waits a full hour after startup.

The sweep exists as a crash/restart backstop, but the loop always sleeps SWEEP_INTERVAL_SECS (1h) before the first pass. A crash/restart is precisely when unfilled gaps are most likely, so the backstop is least effective right when it's needed most.

♻️ Run one pass immediately, then fall into the interval
     tokio::spawn(async move {
         let node_seed = *node_keypair.to_seed();
         let mut cursor = 0usize;
+
+        // Run one pass shortly after startup so a crash/restart doesn't
+        // wait a full interval before the backstop kicks in.
+        run_startup_pass(&db, &config, &http_client, &node_seed, &node_did, &mut cursor).await;
 
         loop {
🤖 Prompt for 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.

In `@crates/gitlawb-node/src/reconciliation.rs` around lines 25 - 32, Update the
reconciliation loop inside the tokio::spawn block so one sweep executes
immediately on startup before waiting for SWEEP_INTERVAL_SECS. Preserve the
existing periodic interval behavior for all subsequent passes, including the
current cursor and node_seed handling.
🤖 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 `@crates/gitlawb-node/src/reconciliation.rs`:
- Around line 151-157: Replace the per-object awaited db.is_pinned calls in the
repo_gaps loop with one batched query for all object_list SHAs, using an array
membership check against pinned_cids, then compute repo_gaps from the returned
pinned set while preserving the existing treatment of lookup errors as gaps.
- Around line 147-259: Decouple the encrypted reseal and Arweave-anchor flow
from the public-object gap checks in the reconciliation loop. Update the early
exits around repo_gaps so zero public gaps skips only public pinning, metrics,
and related reporting, while the has_path_scoped block still runs to repair
withheld copies and anchor manifests. Preserve the existing behavior for
repositories with public gaps and avoid invoking encrypted resealing when its
existing path/configuration guards are not satisfied.
- Around line 68-77: Update the reconciliation sweep around list_all_repos so
quarantined repositories are excluded before batching and cursor advancement.
Reuse the repository’s established quarantined filtering behavior or predicate,
ensuring batch, pagination, and empty-result handling operate only on eligible
repositories.

---

Nitpick comments:
In `@crates/gitlawb-node/src/reconciliation.rs`:
- Around line 25-32: Update the reconciliation loop inside the tokio::spawn
block so one sweep executes immediately on startup before waiting for
SWEEP_INTERVAL_SECS. Preserve the existing periodic interval behavior for all
subsequent passes, including the current cursor and node_seed handling.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53b027bc-082b-4c03-98fb-79aedf72c8d0

📥 Commits

Reviewing files that changed from the base of the PR and between ad7c2b2 and b31cf88.

📒 Files selected for processing (3)
  • crates/gitlawb-node/src/main.rs
  • crates/gitlawb-node/src/metrics.rs
  • crates/gitlawb-node/src/reconciliation.rs

Comment thread crates/gitlawb-node/src/reconciliation.rs Outdated
Comment thread crates/gitlawb-node/src/reconciliation.rs Outdated
Comment thread crates/gitlawb-node/src/reconciliation.rs Outdated
@Gravirei
Gravirei force-pushed the fix/issue-218-reconciliation-sweep branch from b31cf88 to 9352318 Compare July 20, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior needs-tests Source changed without accompanying tests (advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement the reconciliation sweep the replication path already assumes as a durability backstop

3 participants