A SQLite WAL checkpoint can return BUSY after moving some frames, return success while leaving a reader dependent on the WAL, or complete all frame transfer without making the WAL immediately safe to reset. Those are different states, and treating the checkpoint call’s return status as the entire verdict is an avoidable diagnostic error.
SQLite’s contract is explicit. A WAL reader fixes an end mark for the duration of its read transaction. A checkpoint may copy frames only as far as active readers permit. PASSIVE does not wait for readers or writers. FULL, RESTART, and TRUNCATE are progressively stronger operations, with RESTART additionally waiting until readers no longer need the WAL and TRUNCATE reducing the WAL to zero bytes only after successful restart semantics. The checkpoint APIs separately report the WAL frame count and the total number of frames checkpointed.
The operational question is therefore not merely “did PRAGMA wal_checkpoint say busy?” It is: what checkpoint state was reached, which evidence shows that active readers still constrain progress or reset, and is the WAL still required for correct live reads?
This playbook defines that answer with a disposable SQLite 3.54.1 laboratory, synthetic rows, an independently authored expected-case manifest, negative controls, and a per-case evidence ledger. Nothing below claims an execution occurred here. Every fixture outcome is EXPECTED / NOT EXECUTED until the supplied commands are run and reconciled. The release verdict is one of ACCEPT, REPAIR, RERUN, or HOLD.
Define the admission and release contract
The admission contract is narrower than “SQLite is in WAL mode” and narrower than “the database is healthy.” The candidate evidence set must show the exact SQLite runtime, the checkpoint mode requested, whether a reader transaction was active, the checkpoint return code or PRAGMA busy indicator, log, checkpointed, WAL file state, and the independently checked logical rows.
SQLite defines the two checkpoint counters precisely. In sqlite3_wal_checkpoint_v2(), pnLog is the total number of frames in the WAL and pnCkpt is the total number of frames checkpointed, including frames checkpointed before the current call. For PRAGMA wal_checkpoint, the second and third columns expose corresponding WAL and checkpoint progress; the first column maps the relevant SQLITE_BUSY outcome to 1.
That makes log - checkpointed useful, but only within its documented scope. A positive difference means some WAL frames had not been transferred into the database file at the conclusion of the checkpoint. It does not, by itself, identify an application transaction, a specific reader, or the precise page set that one reader will later access. Reader end marks and page lookups are internal concurrency state; the checkpoint counters are aggregate progress indicators. SQLite documents that each reader keeps an end mark and that a checkpoint stops before crossing an end mark that would make the database file inconsistent with that reader’s snapshot.
The release contract therefore requires reconciliation, not interpretation by return code alone:
Predicate | Required evidence | Reject when |
Runtime identity | SQLite version and source ID | Runtime differs from pinned baseline |
WAL state | journal_mode=wal | Mode is not WAL |
Case identity | Manifest case ID exactly once | Missing or duplicate case |
Checkpoint progress | busy/rc, log, checkpointed | Counters absent or impossible |
Reader state | Controlled transaction schedule | Reader ownership is unknown |
Live logical state | Independent row assertions | Committed state differs |
Reset/truncate safety | Successful RESTART/TRUNCATE evidence | WAL reset assumed from FULL alone |
This is deliberately distinct from Refonte Learning’s SQLite WAL backup committed rows workflow. That article asks whether a recovery artifact contains required committed state, including the case where committed pages remain WAL-resident. The present playbook asks whether a live checkpoint progressed far enough, whether readers still constrain the WAL, and whether reset or truncation is currently safe.
Pin the baseline and source versions
Use SQLite 3.54.1, released October 16, 2026, as this playbook’s fixed laboratory baseline. SQLite’s official release history gives source ID 2026-10-16 14:45:20 555e07ef33f69a0957d24d8e555b702464a131d6f1607f4c3c1e4551e8e6ca76 and SHA3-256 e2b869d3003b33c942b7dd7063604fbc53553e1b66609570df39b9b7b558ee81 for sqlite3.c. The official download page identifies sqlite-amalgamation-3540100.zip as SQLite 3.54.1 and gives the archive SHA3-256 as 92281c86e2957f9b9305890f9ac04204caa7b677cc868dc326d942689df28e30.
Pinning matters here for more than cosmetic reproducibility. SQLite’s WAL implementation has changed over its lifetime, and the current WAL documentation also contains a dedicated WAL-reset bug section. The release history records fixes to a WAL-reset corruption bug in 2026 releases before this baseline. Testing a distribution-provided SQLite of unknown provenance and calling the result “SQLite behavior” would therefore weaken the evidence unnecessarily.
Use the canonical amalgamation rather than inferring the engine version from a language runtime. The compiler is part of provenance and must be captured, but the contract under test is the pinned SQLite source. Do not silently replace 3.54.1 with whatever libsqlite3.so happens to be installed on the host.
A minimal acquisition sequence is:
set -euo pipefail
mkdir -p sqlite-checkpoint-lab/{src,build,evidence,cases}
cd sqlite-checkpoint-lab
curl -fL \
https://www.sqlite.org/2026/sqlite-amalgamation-3540100.zip \
-o src/sqlite-amalgamation-3540100.zip
sha3sum -a 256 src/sqlite-amalgamation-3540100.zip \
| tee evidence/sqlite-archive.sha3
cc --version | head -n1 | tee evidence/compiler.txt
uname -a | tee evidence/platform.txtThe archive digest must equal the official value above before extraction. If the host lacks sha3sum, use an independently verified SHA3-256 implementation and record that tool’s identity rather than substituting SHA-256 and labeling it SHA3. The commands are NOT EXECUTED in this article.
Pin the runtime and source hashes
Assertion: the compiled fixture must report sqlite3_libversion() as 3.54.1, and sqlite3_sourceid() must contain the documented 3.54.1 source ID. The downloaded archive must match the official SHA3-256.
Expected outcome (EXPECTED / NOT EXECUTED): archive digest matches 92281c…df28e30; runtime reports 3.54.1; source ID begins with 2026-10-16 14:45:20 and contains the documented source hash.
Rejection condition: any version, source-ID, or archive-hash mismatch is HOLD. Do not reinterpret case differences as checkpoint behavior until provenance is reconciled.
After extracting the amalgamation, capture your fixture bytes independently:
unzip -q src/sqlite-amalgamation-3540100.zip -d src/
sha3sum -a 256 \
src/sqlite-amalgamation-3540100/sqlite3.c \
| tee evidence/sqlite3-c.sha3
sha256sum fixture.c expected-cases.json \
| tee evidence/fixture-source.sha256The fixture hash is intentionally captured at execution time rather than invented in this article. A copied or edited fixture is a different test input and must have a different recorded identity.
Build an isolated fixture with synthetic inputs
The laboratory should have no production path, no copied production database, no application credentials, and no network filesystem. SQLite’s WAL documentation requires participating WAL processes to be on the same host because the wal-index uses shared memory; this fixture therefore uses local disposable files only.
The minimum schedule needs two independent SQLite connections. The writer creates deterministic synthetic rows; the reader holds a read transaction open; the checkpoint connection invokes each checkpoint mode. Automatic checkpointing is disabled with PRAGMA wal_autocheckpoint=0 so the test, rather than an automatic threshold, controls checkpoint timing. SQLite documents the default auto-checkpoint threshold as 1000 pages and documents that automatic checkpoints are PASSIVE.
The fixture should create this logical state:
PRAGMA journal_mode=WAL;
PRAGMA wal_autocheckpoint=0;
PRAGMA busy_timeout=0;
CREATE TABLE event(
id INTEGER PRIMARY KEY,
phase TEXT NOT NULL,
payload TEXT NOT NULL
);
INSERT INTO event VALUES
(1, 'baseline', 'alpha'),
(2, 'baseline', 'beta');Then establish a clean baseline with PRAGMA wal_checkpoint(TRUNCATE). After that, commit a first synthetic writer batch, open a second connection, start BEGIN, and issue a SELECT so that a WAL snapshot is actually established. SQLite documents that WAL readers preserve their snapshot for the transaction’s duration; later writer commits remain invisible to that reader until it ends the transaction.
A second writer batch is then committed. That schedule creates the useful boundary: the reader’s snapshot ends before the newest WAL frames.
This differs from the SQLite BUSY_SNAPSHOT transaction retry article. That direct comparison concerns promoting an obsolete read transaction into a writer and correctly restarting the whole application transaction after SQLITE_BUSY_SNAPSHOT. Here the reader remains a reader; the failure under study is checkpoint progress or WAL reset being constrained by that reader.
For the strongest reproduction, use the C API against the pinned amalgamation. A small harness should open three handles with sqlite3_open_v2(), execute the schedule above, call sqlite3_wal_checkpoint_v2() with a named database of "main", and emit tab-separated records containing case_id, requested mode, rc, log, checkpointed, reader phase, WAL size, and logical-row checks. Naming "main" matters because SQLite says pnLog and pnCkpt are undefined when the checkpoint request spans all attached WAL databases through a null or empty database name.
Compile it against the pinned source:
cc -O2 -std=c17 -pthread \
-Isrc/sqlite-amalgamation-3540100 \
fixture.c \
src/sqlite-amalgamation-3540100/sqlite3.c \
-ldl -o build/checkpoint-fixture
build/checkpoint-fixture --version \
| tee evidence/runtime.txt
build/checkpoint-fixture \
--root "$PWD/cases" \
--ledger "$PWD/evidence/observed.tsv"Those commands are runnable design, not execution evidence: EXPECTED / NOT EXECUTED.
Author the independent expected-case manifest
The oracle must exist before the first checkpoint case runs. Do not generate expected values from observed.tsv; doing so would make the fixture validate itself.
Hard-coding exact frame counts is usually the wrong oracle for this exercise. A transaction may write several database pages, the same page may appear in multiple WAL frames, and schema/page layout details can affect the absolute frame count. SQLite’s API contract gives us stronger, version-relevant invariants: whether all frames were checkpointed, whether a suffix remains, whether a mode returned BUSY, and whether successful TRUNCATE leaves both counters zero.
Create expected-cases.json before execution:
{
"contract": "sqlite-checkpoint-lab-v1",
"sqlite": "3.54.1",
"status": "EXPECTED / NOT EXECUTED",
"cases": [
{
"id": "baseline-no-reader",
"expect": "busy=0 AND log=checkpointed"
},
{
"id": "reader-pins-snapshot",
"expect": "reader_old_view=true AND writer_new_view=true"
},
{
"id": "passive-checkpoint",
"expect": "rc=SQLITE_OK AND checkpointed<log"
},
{
"id": "full-checkpoint-busy",
"expect": "rc=SQLITE_BUSY AND checkpointed<log"
},
{
"id": "restart-checkpoint",
"expect": "latest_reader_active=true AND rc=SQLITE_BUSY AND log=checkpointed"
},
{
"id": "truncate-checkpoint",
"expect": "latest_reader_active=true AND rc=SQLITE_BUSY AND wal_not_truncated=true"
},
{
"id": "reader-closes-then-retry",
"expect": "rc=SQLITE_OK AND log=0 AND checkpointed=0"
},
{
"id": "committed-row-integrity",
"expect": "fresh_reader_matches_manifest=true"
}
]
}The RESTART and TRUNCATE cases use a fresh scenario in which the active reader starts after the latest commit. That distinction is deliberate. It lets FULL first checkpoint all frames while a current reader remains active, and then asks whether WAL reset can proceed. SQLite documents exactly this difference: FULL waits until readers use the most recent snapshot and checkpoints all frames; RESTART additionally waits until readers are finished with the WAL; TRUNCATE adds zero-length truncation after successful restart semantics.
Freeze the oracle before executing
Assertion: expected-cases.json must be created and hashed before observed.tsv exists.
Expected outcome (EXPECTED / NOT EXECUTED): eight unique case IDs exist, each has one documented predicate, and status remains EXPECTED / NOT EXECUTED until a real run produces evidence.
Rejection condition: if the expected manifest is regenerated from observed counters, edited after seeing failures without a documented contract change, or lacks a pre-run hash, classify the attempt as RERUN, not ACCEPT.
The manifest deliberately predicts relations such as checkpointed < log, not arbitrary counts such as “seven frames remain.” Exact frame counts belong in the observed ledger, where they can be reconciled without pretending the article knew them in advance.
Reproduce the misleading success condition
The easiest false positive is a PASSIVE checkpoint that returns normal success while failing to checkpoint every frame.
SQLite’s SQLITE_CHECKPOINT_PASSIVE mode checkpoints as many frames as possible without waiting for readers or writers, and its busy handler is never invoked. The documentation explicitly says PASSIVE may leave the checkpoint unfinished. Therefore SQLITE_OK from a PASSIVE call is not evidence that log == checkpointed.
Construct the stale-reader schedule this way. First, write batch A. Then reader R executes BEGIN and reads COUNT(*), fixing its end mark. Writer W commits batch B afterward. R still sees the pre-B snapshot, while a fresh connection sees batch B because WAL mode provides snapshot isolation between those transactions.
Now call:
PRAGMA main.wal_checkpoint(PASSIVE);The expected evidence is:
case=passive-checkpoint
status=EXPECTED / NOT EXECUTED
busy=0
assertion=checkpointed < logFor SQL users, remember that the first PRAGMA column is not “checkpoint complete.” SQLite says it is normally zero and becomes one when a FULL, RESTART, or TRUNCATE operation is blocked in the way corresponding to SQLITE_BUSY. PASSIVE therefore requires inspection of the progress columns, not merely its first column.
For example, if wal_checkpoint returns 0 | 11 | 6, the documented conclusion is that the WAL had eleven frames and six were checkpointed by the conclusion of the call. Five frames remain uncheckpointed. Do not upgrade that statement to “reader R needs exactly frames 7–11.” The aggregate checkpoint counters do not expose a per-reader dependency map. SQLite’s reader algorithm may retrieve the latest copy of a required page before that reader’s end mark, and the wal-index mediates those lookups.
The safe operational statement is narrower: frames beyond the checkpointable boundary cannot yet be applied to the database file without violating an active snapshot, so the WAL must remain available under SQLite’s normal WAL-management rules.
Run the controlled positive comparison
A useful checkpoint experiment needs a case in which the same kind of write workload has no reader holding an incompatible snapshot.
Create a fresh disposable database, disable auto-checkpointing, commit the synthetic rows, and issue FULL without any reader transaction:
PRAGMA main.wal_checkpoint(FULL);
The expected result is EXPECTED / NOT EXECUTED: first column 0, log == checkpointed, and both counts greater than zero if the case produced WAL frames. SQLite documents that FULL waits for the writer lock and for readers to be on the most recent snapshot, then checkpoints all frames and syncs the database file.
That positive case makes the stale-reader negative case interpretable. Without it, a partial checkpoint could be blamed on an incorrectly built SQLite, wrong journal mode, a second checkpointer, or a fixture that never generated the expected WAL.
This validation pattern is methodologically similar to Refonte Learning’s GitHub Actions matrix coverage playbook: that article rejects a green aggregate job as proof that every intended matrix identity existed and ran, and instead reconciles an independently declared denominator against recorded jobs. It is not a SQLite article and does not establish checkpoint semantics, but its evidence principle is applicable: aggregate success is weaker than per-case reconciliation.
For checkpoint work, the corresponding denominator is the case manifest plus documented invariants. An SQLITE_OK result is evidence about that API call. It is not permission to infer reader absence, WAL reset, backup completeness, or recovery correctness unless the specific checkpoint mode and counters support those additional conclusions.
Retain the negative-control evidence
Assertion: the evidence bundle must preserve both the no-reader complete-checkpoint case and the reader-pinned partial-checkpoint case.
Expected outcome (EXPECTED / NOT EXECUTED): the positive case satisfies log == checkpointed; the stale-reader PASSIVE case satisfies checkpointed < log while PASSIVE itself still returns normally.
Rejection condition: if both cases collapse into the same observed state, do not rationalize the fixture. RERUN after checking WAL mode, transaction establishment, auto-checkpoint settings, and whether the reader actually executed a read before the second writer commit.
The negative control is especially important because closing the reader accidentally can “fix” the test. Reader lifetime is therefore evidence, not fixture plumbing.
Probe failures and boundary conditions
FULL, RESTART, and TRUNCATE answer different operational questions.
In a stale-reader case with busy_timeout=0, FULL cannot wait indefinitely for that reader to advance. SQLite says that when the busy handler gives up while FULL, RESTART, or TRUNCATE is waiting for the writer lock or readers, the operation proceeds from that point like PASSIVE, checkpoints whatever it can without further blocking, and returns SQLITE_BUSY.
That yields the expected FULL failure record:
case=full-checkpoint-busy
status=EXPECTED / NOT EXECUTED
rc=SQLITE_BUSY
assertion=checkpointed < log
reader_state=older snapshot still openNow construct a different boundary. Commit all writes first. Start a reader afterward, so it is reading the most recent WAL snapshot. FULL is allowed to checkpoint all frames even though that reader remains open. The expected intermediate state is SQLITE_OK with log == checkpointed. This proves checkpoint transfer completeness.
Do not stop there.
With that same reader still active, call RESTART. SQLite says RESTART performs FULL semantics and then waits until all readers are finished with the WAL so the next writer can restart the log from the beginning. With a zero wait budget, the expected result is SQLITE_BUSY, even though the frame counters can show log == checkpointed.
That case is the cleanest answer to “which WAL frames are still needed?” Sometimes no additional frames remain to be checkpointed, yet the WAL still cannot safely be reset because an existing reader retains WAL-related snapshot state. Checkpoint completeness and WAL reset safety are separate predicates.
TRUNCATE adds one more condition: only on successful completion does SQLite truncate the log to zero bytes, and on successful TRUNCATE both output counters are zero.
A failed TRUNCATE must therefore not be converted into a filesystem command such as rm database-wal. The WAL and shared-memory files are part of SQLite’s live coordination state; manual deletion would bypass the locking and reader protections the checkpoint modes are designed to preserve. SQLite itself documents WAL as a persistent part of database state while the database is in use.
Capture a per-case evidence ledger
Each execution should emit one record per expected case. A release gate should never derive case identity from row order.
Use a ledger schema such as:
case_id
sqlite_version
sqlite_source_id
fixture_sha256
mode
rc
busy_column
log_frames
checkpointed_frames
wal_bytes_before
wal_bytes_after
reader_snapshot_class
reader_open
fresh_row_count
fresh_row_digest
started_utc
finished_utc
statusThe expected manifest and observed ledger serve different purposes. The manifest defines what must happen before execution. The ledger records what did happen. Keeping them separate prevents a false positive in which a new or missing case silently changes the denominator.
Frame counts need context. pnCkpt is total checkpointed frames in the current WAL, including frames that were already checkpointed before the call, not “frames copied by this invocation.” Therefore a ledger should retain the pre-call and post-call checkpoint observations if the engineering question requires incremental progress.
For example, this is acceptable as a format, but its values must come from a real run:
passive-checkpoint EXPECTED / NOT EXECUTED
full-checkpoint-busy EXPECTED / NOT EXECUTED
restart-checkpoint EXPECTED / NOT EXECUTED
truncate-checkpoint EXPECTED / NOT EXECUTEDDo not publish sample numbers in the observed columns unless they were actually produced by the pinned fixture. A fabricated log=12, checkpointed=7 would be worse evidence than leaving the values explicitly unexecuted.
WAL file size is supplemental evidence rather than a substitute for frame counters. SQLite normally reuses a WAL instead of truncating it on ordinary checkpoints; the WAL documentation explains that recycling is generally preferred and that final cleanup commonly occurs when the last connection closes. Thus a nonzero -wal byte size after a complete FULL checkpoint is not, by itself, a failure.
Reject missing and duplicate records
Assertion: observed case IDs must equal the expected manifest’s case IDs exactly, with cardinality one for every ID.
Expected outcome (EXPECTED / NOT EXECUTED): eight expected identities, eight observed identities, zero missing cases, zero unexpected cases, zero duplicates.
Rejection condition: missing, duplicate, or unexpected ledger entries are RERUN unless their origin is unexplained, in which case use HOLD until evidence ownership is clear.
A compact reconciler can compare sets first, then evaluate each case’s predicate. That order matters because a suite containing seven passing cases is not evidence for an eight-case contract.
Reconcile identities and final state
The release decision has two layers: checkpoint-state reconciliation and logical-state reconciliation.
Checkpoint reconciliation evaluates the requested mode against rc, log, and checkpointed. Logical reconciliation asks whether fresh readers still observe every committed synthetic row that the manifest requires. SQLite’s isolation contract says separate connections normally see only committed transactions; a fresh read transaction after the writer commit should therefore observe the current committed state.
Use a stable logical checksum rather than “query returned rows.” For example:
SELECT
count(*) AS n,
sum(id) AS id_sum,
group_concat(id || ':' || phase || ':' || payload, '|')
FROM (
SELECT id, phase, payload
FROM event
ORDER BY id
);The expected row identities belong in the independent manifest. The candidate database must be compared against them; the candidate must not author them.
This is the same evidence discipline expressed in Refonte Learning’s Probability calibrator release validation: the article binds a release artifact to independently defined identities and evaluation assumptions rather than accepting an attractive aggregate metric without proving the candidate is the intended one. The technology and tests are unrelated, so no calibration procedure is reused here; only the identity-reconciliation principle overlaps.
For WAL diagnostics, reconciliation prevents two common category errors. First, checkpointed == log proves frame-transfer completeness for that WAL observation, but not necessarily RESTART completion. Second, correct committed rows in a live database do not prove that a copied backup artifact contains them. The Refonte WAL-backup article directly demonstrates why: committed data may still reside in WAL when a main-database-only copy is taken.
This playbook therefore never uses “fresh reader sees committed rows” as evidence that a backup is correct. Backup correctness requires qualification of the backup or restore artifact under its own recovery contract.
Separate documentation from observation
Every release record should label evidence as one of four things: documented contract, fixture hypothesis, expected result, or observed result.
Documented contract: SQLite states that a read transaction has a stable WAL snapshot; a checkpoint must stop before applying changes that would invalidate an active reader; PASSIVE may finish partially; FULL can wait for current-snapshot readers and checkpoint all frames; RESTART additionally waits for readers to stop using the WAL; TRUNCATE additionally shrinks the WAL to zero on success.
Fixture hypothesis: a deliberately ordered reader and writer schedule will place the reader behind a later commit, so FULL with zero wait budget should encounter a reader boundary.
Expected result: the manifest predicts SQLITE_BUSY plus checkpointed < log for that case. It remains EXPECTED / NOT EXECUTED until execution.
Observed result: only the generated ledger, tied to runtime and source hashes, may claim actual values.
This separation matters because the closest existing Refonte SQLite articles address adjacent but different failures. The WAL-backup article tests recovery-artifact completeness. The BUSY_SNAPSHOT article tests stale-reader promotion to a writer and whole-transaction retry. Direct reading of both shows neither substitutes for checkpoint progress and RESTART/TRUNCATE acceptance.
The GitHub Actions article is closer in evidence architecture than technical subject: it predeclares case identities and rejects aggregate success when required cases are absent.
Explain the repair boundary
Assertion: repair begins at the smallest owned boundary that changes the violated predicate without bypassing SQLite’s reader protections.
Expected outcome (EXPECTED / NOT EXECUTED): stale readers are ended by their owning application; checkpoint policy is retried through SQLite; no -wal or -shm file is manually removed.
Rejection condition: any proposed repair that deletes or truncates WAL storage behind active SQLite connections is HOLD.
The distinction is straightforward: if the problem is a legitimate long-lived reader, repair its transaction lifetime or checkpoint scheduling. Do not “repair” a correct SQLITE_BUSY response by defeating the lock protocol that produced it.
Implement the smallest safe repair
A checkpoint busy return is often evidence that SQLite is protecting a valid concurrent state, not evidence of database corruption.
For a stale reader, the smallest safe repair is usually to let the reader owner finish its transaction at a legitimate application boundary, then retry the required checkpoint mode. SQLite documents that a WAL reader keeps the same snapshot until the transaction ends.
If PASSIVE is operationally sufficient, no repair may be required at all. PASSIVE is explicitly designed to make nonblocking progress and may leave frames for a later checkpoint. A checkpointed < log record can therefore be ACCEPT for a “make opportunistic progress” policy while being REPAIR or HOLD for a “WAL must be restartable now” policy.
If the contract requires a reusable WAL start point, retry RESTART after known readers exit. If it requires a zero-length WAL, retry TRUNCATE and require its successful semantics. SQLite says a successful TRUNCATE sets both pnLog and pnCkpt to zero.
The repair must not confuse reader lifetime with database durability. A reader that ends allows later checkpoint work to proceed, but it does not retroactively create a backup, validate an external copy, or reconcile non-SQLite side effects.
The fixture’s retry sequence should therefore be:
observe BUSY
preserve ledger
identify controlled reader case
end reader transaction
close or release reader connection
retry required checkpoint mode
capture new rc/log/checkpointed
re-run fresh logical-state assertion
reconcile against manifestThat sequence is procedural guidance, not an assertion that every production reader may safely be terminated. Ownership matters. If the diagnostic process does not know whether a reader belongs to an active request, export, migration, analytics job, or another process, the correct decision is HOLD until the owner can define the transaction boundary.
Test rollback, recovery and rerun safety
The laboratory must prove that failure handling does not mutate the intended committed state.
Checkpointing transfers committed WAL content into the database file; it is not an application transaction rollback mechanism. SQLite defines checkpoint as movement of WAL transactions back into the original database. Ending a read transaction changes that reader’s snapshot lifetime but does not undo committed writer transactions.
After each failure-oriented checkpoint case, open a fresh connection and verify the synthetic committed-state manifest. If the fresh read differs, the fixture has crossed from a checkpoint-control experiment into a data-integrity incident and must be held.
Reruns should start from a new disposable directory rather than recycling a failed database. This keeps residual -wal and -shm state from becoming an implicit input to a supposedly fresh case. SQLite normally manages those files as part of WAL operation and performs final WAL cleanup when the last connection closes under normal circumstances.
The release-gate principle parallels Refonte Learning’s Pytest strict XPASS release gate: a process-level success token is not necessarily the policy decision, and unexpected evidence requires classification rather than automatic approval. That article’s pytest semantics are not reused here; the relevant overlap is explicit evidence ownership and release disposition.
Recovery evidence should include the last accepted manifest hash, fixture hash, runtime identity, exact case schedule, final logical digest, and checkpoint state. If a retry uses different source bytes or a different SQLite engine, it is a new attempt, not a continuation of the old evidence set.
Assign recovery ownership
Assertion: every action that ends a reader, retries a checkpoint, or disposes of a fixture has a named operational owner in the execution record.
Expected outcome (EXPECTED / NOT EXECUTED): reader owner controls reader completion; database operator owns checkpoint retry; release reviewer owns reconciliation.
Rejection condition: if the active reader cannot be attributed, if external side effects are uncertain, or if the WAL was altered outside SQLite, classify the state as HOLD.
Ownership is part of rollback safety because a technically possible operation can still be an invalid recovery action when it crosses an application boundary the operator does not own.
Apply the ACCEPT / REPAIR / RERUN / HOLD matrix
The final gate should be mechanical enough that two reviewers examining the same ledger arrive at the same classification.
Evidence state | Decision | Required action |
PASSIVE returns normally; partial progress is allowed by contract; logical state reconciles | ACCEPT | Preserve evidence; permit later checkpoint |
FULL succeeds with log == checkpointed; only frame-transfer completeness was required | ACCEPT | Do not infer WAL reset |
FULL is BUSY because a controlled stale reader is open | REPAIR | End reader at owned boundary, retry |
RESTART is BUSY while log == checkpointed and reader remains active | REPAIR | Resolve reader lifetime; WAL reset is not yet accepted |
TRUNCATE is BUSY or WAL remains required | REPAIR | Do not delete WAL; retry through SQLite after readers exit |
Reader closes; TRUNCATE succeeds with 0/0; logical state matches | ACCEPT | Record successful reset/truncate evidence |
Runtime/hash differs from pinned baseline | RERUN | Rebuild from verified 3.54.1 inputs |
Expected case missing or duplicate | RERUN | Correct harness/evidence collection |
Reader schedule was not actually established | RERUN | Fix deterministic transaction ordering |
WAL or SHM was manually altered during live use | HOLD | Stop; investigate database state |
Reader ownership is unknown | HOLD | Reconcile owner and transaction purpose |
Fresh committed-row check disagrees with manifest | HOLD | Treat as data-integrity discrepancy |
Backup artifact is claimed correct only because checkpoint completed | HOLD | Validate the backup separately |
The crucial distinction is the middle one. log == checkpointed answers whether all current WAL frames have been copied into the database file. RESTART adds another requirement: readers must no longer depend on the WAL so the log can restart. TRUNCATE adds zero-length truncation after successful restart conditions. SQLite documents those as separate mode semantics.
Accordingly, never compute the release verdict from log - checkpointed alone. A positive value tells you frames remain uncheckpointed. Zero tells you frame transfer is complete. Neither value alone tells you whether an existing reader still prevents WAL restart.
Likewise, a BUSY return is not automatically REPAIR. If the reader identity is unowned, the source build changed, evidence is missing, or logical state conflicts with the manifest, continuing automatically would turn diagnosis into speculation. Use HOLD.
For the independent manifest supplied above, every case remains EXPECTED / NOT EXECUTED in this article. No ACCEPT decision is claimed for the fixture itself. ACCEPT becomes available only after a real observed ledger matches the pinned runtime, case identities, checkpoint invariants, reader schedule, and committed-row oracle.
Develop related skills with Refonte Learning
The engineering technique behind this playbook is broader than one SQLite PRAGMA: bind behavior to a versioned runtime, declare expected cases independently, reproduce both positive and negative controls, preserve failure evidence, distinguish API success from release acceptance, and repair only at a boundary the operator owns.
Refonte Learning’s Backend Development program page describes a three-month program with a stated dedication of 10–12 hours per week. Its published competencies include Node.js and Express, MongoDB and SQL database management, RESTful APIs and microservices, authentication and authorization, testing and debugging, Docker/cloud deployment, and a capstone project. The page’s “Program Specifics” section lists a basic understanding of programming concepts as a requirement, while a later “Admission Prerequisites” section separately says applicants must be working toward a bachelor’s or higher-level degree; readers should treat both published statements as part of the current page rather than collapsing them into a single inferred prerequisite.
For engineers who want structured practice around those broader backend topics, the Refonte Learning Backend Development program is a relevant option to review. The page also states that successful completion can include training and internship certificates and describes potential internship exposure, but it does not establish SQLite WAL checkpoint specialization; no such claim is made here.
For this checkpoint playbook, the final acceptance boundary remains intentionally narrower: verify the exact SQLite runtime, execute the disposable cases, reconcile every expected identity, distinguish checkpoint progress from WAL reset safety, verify committed live state independently, and keep backup correctness as a separate recovery contract. SQLite’s own WAL, checkpoint-PRAGMA, C checkpoint API, and isolation documentation provide the behavioral contract; the fixture supplies the evidence needed to decide ACCEPT, REPAIR, RERUN, or HOLD for one controlled execution.
