Online integrity checking
Documentation/Run your server
Online integrity checking4 min read

Online integrity checking

On this page

The serving process gradually verifies the bytes of every indexed object against its full content identity. It uses the same streaming verifier as doctor, including envelope checks, decompression, payload length, and digest validation. Packed and loose objects are covered. Verification never repairs, deletes, quarantines, or accepts data.

This checks indexed object bytes. It does not replace the offline doctor: metadata consistency, missing references absent from the locator, unindexed pack entries, and whole-pack inventory still require that broader audit. A clean pass describes the bytes observed during the pass, not an atomic snapshot or a promise that the storage remains healthy afterward.

Configuration

The checker is enabled by default in serve, including with an existing configuration that omits this section:

toml
[integrity]
enabled = true
mib_per_second = 1
idle_secs = 1
interval_secs = 86400
  • mib_per_second: maximum scheduled decoded payload throughput, 1–1024 MiB/s. Compressed input, object headers, index lookups, and progress-file I/O are additional. One 64 KiB read can start immediately; unused budget does not accumulate into a later burst.
  • idle_secs: quiet time after foreground work, 0–3600 seconds. Queued and executing TCP requests pause verification; idle connections do not. Online checkpoint work also pauses it. The checker checks admission between reads, never holds the publication gate, and consumes no foreground worker permit.
  • interval_secs: wait after a repository pass completes before starting its next pass, 60–31536000 seconds. The default is one day.
  • enabled = false: the checker does not start on the next launch. Saved reports remain available.

There is one checker thread for the whole server. It holds one repository's progress and one streaming object reader at a time, with a 64 KiB read buffer and the existing bounded decompressor. It rotates repositories after at most 16 objects, caps object starts at 20 per second, and polls shutdown while waiting. A large object finishes before that rotation, but it can pause between reads. An OS read already in progress cannot be preempted. This is cooperative load control, not a guarantee of zero disk contention. Continuously busy servers can defer verification indefinitely; inspect coverage age rather than assuming a daily pass completes.

Coverage and reports

sh
scribe-server integrity-status --runtime-dir /srv/scribe/run
scribe-server --json integrity-status --runtime-dir /srv/scribe/run --repo REPOSITORY_ID

The command reads saved progress without opening or locking the data root. It works while the server runs and after it stops. Reports include the current pass's visited-object count, successfully verified payload bytes, failures, bounded per-object findings, the last completed pass, and the last clean pass. They are saved reports, not a live health or process-liveness probe. Repository selection takes an ID, not a name.

Exit codes:

  • 0: saved completed passes have no findings, and no current findings/error are recorded.
  • 1: current or last-completed coverage contains failures, or a scan error was saved.
  • 2: no matching report, no completed pass yet, or reports cannot be read.

Progress is saved after each batch, pass completion, or cooperative shutdown. Up to 32 detailed findings are retained per pass; the failure count continues beyond that. Every detected object failure emits integrity.object_failed with check = objects.content, repository ID, object ID, and error. The offline doctor emits that same event. integrity.pass_completed records pass totals; integrity.scan_failed reports a checker that could not advance. Diagnostic logs retain their normal bounded/lossy behavior.

A pass records an initial upper object-key boundary and seeks forward through the locator with short read transactions. It does not retain a database snapshot for hours. New uploads behind the cursor or beyond that boundary are covered by the next pass; concurrent uploads never become accepted history because the checker sees them. Indexed objects belonging to unfinished uploads are checked too.

Restart and recovery

Checksummed progress lives in <runtime-dir>/integrity/<repository-id>.json. It is disposable operational state, excluded from canonical history, object identity, and the wire protocol. No repository migration is required. Keep the runtime directory between launches if you want to resume coverage.

Only completed object checks advance the cursor. Shutdown interrupts waiting or throttled verification; after restart, an unfinished object is read from the beginning. Progress replacement uses a temporary file and rename without durability flushes: losing recent progress repeats work. It never permits a partial check to be reported as verified. Crash leftovers with the matching .writing name are replaced on the next progress save.

Root and repository incarnation changes discard coverage from the old generation with an explicit event. Malformed, oversized, or checksum-damaged state is reported and refused, rather than silently read as a valid cursor. To reset a damaged report, stop the server, retain the report for diagnosis, and remove only that repository's .json and .writing progress files. Restart begins a new pass. Do not remove repository files to reset verification.

For object failures, preserve the evidence and use the existing diagnosis and explicit recovery tools. With a known-good copy, verified object repair can replace damaged bytes without rolling back newer accepted history. Online verification is not a substitute for independent backups and restore drills.

Source docs/online-integrity.mdSnapshot 2b8dcccb