Files
alkstore/tasks/suite-lock-rows.md
glm-5.3-flash 98de4a49cd Contract-suite lock rows (task suite-lock-rows): two version-stamped rows discharging the lock backlog rows — lock_ttl_expiry_and_reacquisition (the ADR-008 §7 guarantee row: a held lock excludes a second acquirer (contender None); after a 1 s TTL the exclusion lapses silently — no revocation event, no error — and a second owner acquires; the original holder's post-expiry renew is refused (false, the lost-it arm); the second owner's release frees the name for a third acquirer, which consumes cleanly; tolerance-sleep posture, state outcomes only; ADR-008 §7 + ADR-019 §1, duration-guard legs cross-referenced to duration_refusal_on_non_positive_ttl) and concurrent_try_lock_loser_is_a_value (the contention posture: four sequential contenders — two owners, repeated — against a held lock all land the clean None value, never Database, never a busy-throw; the backlog row's SQLite busy-path open question answered: no divergence from the pg reference; release-then-contend cycle proves the loser path leaves no state blocking a later acquire, granted to a former loser and consumed cleanly; ADR-008 §5 + §7 + ADR-019 §1) — wired into both engines' suite targets (SQLite tokio tests, pg harness_row!s), 23 rows per column up from 21. Dispositions in Notes: no divergence found (no fix/ADR call needed), ADR-023 §2 excluded from stamps (its guard property is the duration-refusal row's, cross-referenced), the backlog parenthetical re-acquire-does-not-refresh-TTL stays engine-pinned, renew-refusal asserted after the second owner's re-acquisition (strongest form), contenders distinct-owner only (same-owner re-acquire grants per the inherited substrate shape), and one unreproducible first-cold-run pg failure recorded (message lost to truncation; 13 subsequent clean runs incl. concurrent SQLite+pg — no timing hazard identified, the lapse assertions are post-sleep state outcomes). Drive-by: alkstore-contract-suite's tokio dep gained the macros feature — a pre-existing compile break in the crate's own tests/suite_harness.rs (since 7f749ac) failed the harness target and the workspace test gate on HEAD; test-side non-event (ADR-017 §2 class 4). Verified: cargo test -p alkstore-sqlite -p alkstore-postgres green (SQLite 121+23+9, pg 189+23 vs harness server on :15432, server-less pg skips clean), workspace cargo test green (12 binaries), clippy -D warnings, fmt clean
2026-10-10 06:58:54 +00:00

8.1 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
suite-lock-rows Contract-suite rows — lock TTL/expiry re-acquisition + concurrent try_lock loser completed
narrow medium phase implementation
wave-5
contract-suite
locks

Description

Add the named-locks contract rows to alkstore-contract-suite/src/properties.rs and wire them into both engines' suite targets, discharging two backlog rows (core-contract.md §Verification backlog): "Named-lock TTL/expiry re-acquisition on SQLite" (pinned on pg by the POC's lock probe; on SQLite it rests on the forked substrate's lock machinery — the suite pins both engines to identical behavior) and "Concurrent try_lock loser/error behavior on SQLite" (the substrate's busy-path under lock contention is the thing to pin — the pg side returns cleanly).

Rows to add:

  • lock_ttl_expiry_and_reacquisition — the ADR-008 §7 guarantee row: mutual exclusion bounded by TTL + renewal. Pin: a held lock excludes a second acquirer (None); after TTL expiry exclusion lapses silently (no revocation event, no error — a second owner acquires); the original holder's post-expiry renew is refused (false — the deadline-lapse refusal, ADR-010 §2's uniform predicate shape on the lock handle); release on the second owner frees the name for a third acquirer. Short TTL (1 s) + tolerance sleeps — sequential single-store, fits the determinism posture.
  • concurrent_try_lock_loser_is_a_value — contention posture: with a lock held, contender try_lock calls return the clean no-work value (None) on both engines — never a Database error, never a busy-throw surfacing (the SQLite substrate's busy-path is the risk site; the pg side is the reference behavior). Drive several contenders sequentially against one held lock, and a release-then-contend cycle proving the loser path leaves no state that blocks a later acquire.

Risk note: what the SQLite busy-path actually returns under contention is the open question this row exists to answer — if it surfaces Database where pg returns None, that is a real conformance finding: stop, record it in Notes, and raise it (an engine fix or an ADR-level asymmetry call) rather than pinning the divergence silently.

Acceptance Criteria

  • Two rows exist, version-stamped (ADR-008 §7, ADR-019 §1, ADR-023 §2 as applicable)
  • Rows wired into both engines' contract_suite.rs targets
  • SQLite column green server-less; pg column green against the harness server
  • Any engine divergence found is recorded in Notes with its disposition (fix or documented asymmetry) — not pinned silently
  • cargo test -p alkstore-sqlite -p alkstore-postgres green (pg rows skip cleanly server-less); clippy -D warnings; fmt clean

References

  • docs/architecture/core-contract.md §Verification backlog (lock TTL/expiry re-acquisition; concurrent try_lock loser)
  • docs/architecture/decisions/008-contract-v1-pinning.md §7
  • docs/architecture/decisions/019-mechanism-handle-surfaces.md §1
  • docs/architecture/engine-sqlite.md (the substrate's lock machinery)

Notes

Decisions of record the description didn't pin:

  • The open question is answered: no divergence — the SQLite substrate's busy-path under lock contention never surfaces Database: contended try_lock returns the same clean Ok(None) value as pg (the engines route lock ops through the writer-slot-serialized surface / pool acquire respectively, and both acquire ops are opportunistic-expiry-delete + insert-or-ignore + read-back, so a held row reads as foreign — the loser value — on both). No engine fix and no ADR-level asymmetry call was needed; the row pins the convergence rather than recording one.
  • Stamp scope — ADR-023 §2 is not on either row's stamp list: the duration-guard behavior it pins (ttl <= 0 rejecting on try_lock/renew) is duration_refusal_on_non_positive_ttl's property (cross-referenced in the TTL row's doc text, as are that row's legs from this one). The rows carry stamps for the behavior they test: ADR-008 §7 (the guarantee row / contention composition), ADR-008 §5 (the loser-value-not-error rule the contention row pins), ADR-019 §1 (the Lock handle surface).
  • Backlog-row parenthetical stays engine-side — the backlog row's "re-acquire-does-not-refresh-TTL behavior is upstream's, inherited deliberately" note remains pinned by the SQLite engine tests (store/lock_tests.rs's mapping-row pin); the suite row pins the task description's five lifecycle legs only and does not re-pin the same-owner-re-acquire-returns-Some / TTL-not-refreshed shape (deliberate scope: that shape is the acquisition semantics' non-lapse twin, both engine tests pin it, and neither task text asked the suite to own it).
  • renew-refusal ordering — the original holder's renew is asserted after the second owner's re-acquisition (the strongest form: the row the refuser targets belongs to another owner), rather than immediately post-lapse while the stale row survives; both forms land false on both engines, so only the strongest is pinned.
  • Contenders are distinct-owner only — the contention row never drives a same-owner try_lock against a held lock: the inherited substrate shape means a same-owner re-acquire grants (Some, original row kept) rather than losing, so including it in a "loser is a value" row would misdescribe the property.
  • One unreproducible pg failure, first cold run — the very first pg full-suite run failed row_lock_ttl_expiry_and_ reacquisition; the failure message was lost to output truncation and the row passed on every subsequent run (5 isolated full-suite runs, 3 concurrent SQLite+pg full-suite runs, 5 focused --test-threads=16 runs — 13 clean total). No divergence observed, no repro found; recorded per the stream- rows task's precedent. The TTL legs' assertions are post-sleep state outcomes (a slower clock can only delay the lapse, never un-lapse it), so no timing hazard is identified.

Summary

What landed, verified how:

Two rows added to alkstore-contract-suite/src/properties.rs, re-exported from the crate's lib, wired into both engines' contract_suite.rs targets (SQLite: direct test fns; pg: the harness_row! macro with unique factory tags) — 23 rows in each engine's column, up from 21:

  • lock_ttl_expiry_and_reacquisition — the ADR-008 §7 guarantee row: held → contender None; post-1 s-TTL silent lapse (second owner acquires, no error, no revocation event); original holder's post-expiry renew refused (false); second owner's release frees the name for a third acquirer. Stamped ADR-008 §7 + ADR-019 §1; duration-guard legs cross-referenced to duration_refusal_on_non_positive_ttl (ADR-023 §2).
  • concurrent_try_lock_loser_is_a_value — the contention posture: four sequential contenders (two distinct owners, repeated) against a held lock all get the clean None value — never Database, never a busy-throw (the backlog row's SQLite busy-path open question, answered: no divergence from the pg reference) — plus a release-then-contend cycle granting a former loser (no blocking state left) and consuming cleanly. Stamped ADR-008 §5 + §7 + ADR-019 §1.

Drive-by fix: alkstore-contract-suite/Cargo.toml's tokio dep gained the macros feature — a pre-existing break (since 7f749ac added the dep without it) left the crate's own tests/suite_harness.rs target failing to compile, which failed cargo test -p alkstore-contract-suite and the workspace gate on HEAD before this task. Test-side non-event (ADR-017 §2 class 4); harness target green (3 tests).

Verification: cargo test -p alkstore-sqlite -p alkstore-postgres green (SQLite lib 121 + suite 23/23 + harness-shape; pg lib 189 + suite 23/23 against the harness server on :15432), server-less pg rows skip cleanly, full workspace cargo test green (12 binaries), cargo clippy --workspace --all-targets -- -D warnings clean, cargo fmt --check clean. Stability: 13 clean runs of the pg suite's lock rows/full suite as recorded in Notes.