6.5 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| pg-fix-scheduler-error-classification | Classify run_schedules' renew-failure arms on pg (the sqlite twin, review 004 item 9) | completed | narrow | low | component | implementation |
|
Description
The pg twin of sqlite-fix-scheduler-error-classification (completed),
recorded in that task's Notes as an unresolved follow-up:
alkstore-postgres/src/scheduler.rs:682 carries the identical
.unwrap_or(false) flattening the sqlite task fixed — a mid-run
lease.renew(LEADER_TTL_S) failure (including the closed-store
Database error) is mapped to false and misreported as
Error::LeadershipLost, misdirecting the documented respawn recipe at
a dead store.
The classification decision of record is already landed and engine-neutral — the sqlite task's dated (2026-10-11) ADR-009 §1 / core-contract.md §Errors annotation pins the arm set for the family — so this task implements pg to match it, not re-decides:
renew→Ok(false)— the genuine loss arm (expired and re-acquired elsewhere; exclusion lapses silently at TTL expiry, ADR-008 §7) — returnsErr(LeadershipLost), unchanged, return-before-tick. The respawn recipe matches on this arm only.renew→Err(e)— the machinery never reached the lock semantics; not a loss, neverLeadershipLost. TheErrpropagates unchanged: the closed-store arm arrives pre-classified (pg's post-closerenewfails closed withDatabase, pinned by pg lock_tests), and any other storage failure carries the driver's source chain.
No new variant minted — ADR-008 §5's act-differently rule holds
(Closed stays the wake-receiver variant; closed store is Database
engine-wide, the pg posture already).
Tests need the harness server (the reason the sqlite task recorded
rather than landed this): model the two pins on the sqlite task's —
a mid-run close exits with the closed Database classification, never
LeadershipLost; a mid-run lease loss still returns LeadershipLost
(the respawn arm unchanged).
Acceptance Criteria
- The pg renew arm implements the landed arm set:
Ok(false)→LeadershipLostunchanged;Err(e)propagates unchanged (no flattening) — no re-deciding; any discrepancy with the sqlite task's decisions of record recorded, not deviated around - Both arms pinned by tests against the harness server (the closed-store arm and the genuine-loss arm, modeled on the sqlite task's two pins)
- The ADR-009 §1 / core-contract annotations verified to read engine-neutral (they were landed as family-wide; cite them — only pg-specific doc text needs touching, e.g. engine-postgres.md's scheduler row if it describes the arm)
cargo test -p alkstore-postgres(harness server), clippy-D warnings, fmt clean; workspace gates green server-less
References
- tasks/sqlite-fix-scheduler-error-classification.md (the twin — the arm set's decisions of record, the two pin shapes, the recorded follow-up this task discharges)
- docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 9 (the original finding — recorded the sqlite site only; the pg twin surfaced at implementation)
- docs/architecture/decisions/009-*.md §1 (the dated classification annotation of record), core-contract.md §Errors
- alkstore-postgres/src/scheduler.rs:675-690 (the flattening site)
Notes
- No discrepancy with the decisions of record: the sqlite task's
arm set implemented verbatim —
Ok(false)keepsErr(LeadershipLost)return-before-tick (the respawn arm),Err(e)propagates unchanged (no new variant, no re-deciding; theClosed-stays-wake-receiver posture untouched). - Harness server (was the recorded blocker): the
pglo-poccontainer was up on :15432; the standard env (ALKSTORE_PG_HOST/PORT/USER/PASSWORD/DB=…/15432/postgres/poc/blobs) ran the whole pg suite live — nothing was held back to the server-less skip posture. - Engine note (how the closed arm arrives): pg's closed-store
classification is proactive —
PgLockHandle::renewchecks the store's sharedclosedflag before any SQL (locked.rs closed-sign first, the sqlite twin's shape; pinned by pg lock_tests) — so the test drives it through the fixture store's ownEngineCtx(run_schedulesruns onfx.store.engine_ctx(), sharing the closed flag and pool;close()then flips the runner's flag), not through a second store instance the way the sqlite pin's same-file DB works. The genuine-loss arm drives through the runner-store pattern (secondopen_storeon the same dsn/schema) with an admin-clientDELETEof the lock row — the twin's rogue deletion shape. - Doc alignment: the pg scheduler's module doc got the arm-set
sentence (the pg-specific text this criterion calls out) — the
SQLite twin's wording (
Ok(false)= the loss /Err(e)= opaque-propagate, review 004 item 9). ADR-009 §1 and core-contract.md §Errors verified engine-neutral as landed (2026-10-11, dated annotation; no engine names) — cited, not touched; engine-postgres.md's scheduler row doesn't describe the renew arm, so it needs nothing.
Summary
The pg leader loop's top-of-iteration renew no longer flattens a
failed renewal into LeadershipLost (scheduler.rs — the
unwrap_or(false) the sqlite task recorded): Ok(false) keeps the
Err(LeadershipLost) respawn arm unchanged, and a failed renewal
propagates its opaque Database classification unchanged (the
closed-store arm arrives pre-classified — the engine-wide "the store
is closed" source, the proactive closed-sign posture) — respawning a
leader against a dead store is no longer the pg outcome either; the
sqlite/pg twins now classify identically. Two acceptance tests pin
the arms against the harness server, modeled on the sqlite pair:
closed_store_run_reports_the_closed_classification_not_leadership_ lost (the runner runs on the fixture's own EngineCtx and the store
closes mid-run — the exit is the closed Database classification,
never LeadershipLost) and mid_run_lease_loss_still_returns_ leadership_lost (a deleted lease row mid-run still returns the
loss, the respawn arm unchanged). Verified: full
cargo test -p alkstore-postgres green on the live harness server
(133 lib + 25 contract suite + 9 schema tests), workspace gates green
server-less, clippy -D warnings clean, cargo fmt --check clean,
doc gate clean, fuzz corpus replay (6/6) green.