Files
alkstore/tasks/pg-engine-locks.md
T

93 lines
4.3 KiB
Markdown

---
id: pg-engine-locks
name: Postgres engine — named locks (`try_lock`, `Lock` handle, duration guards)
status: pending
depends_on: [pg-engine-seam-tx]
scope: narrow
risk: low
impact: component
level: implementation
tags: [wave-4, postgres-engine]
---
## Description
Implement the named-lock mechanism's pg arm — the smallest mechanism,
and the one with the strongest POC ground (the pg POC's lock probe
pinned acquire/release/renew with TTL and expiry re-acquisition
directly). Over the schema task's locks table:
- **`try_lock(name, owner, ttl)`** — shared-namespace + non-empty
owner validation (`InvalidName`); the **duration guard** (`ttl <=
0` → opaque `Err(Database)`, detail in the source chain — ADR-023
§2's duration kind; an exclusion mechanism must not silently hand
back a lease the caller believes it holds); the closed-store check;
then the acquire op on a pool connection: opportunistic-expiry-
delete + insert-or-reacquire + read-back (the substrate's
`lock_acquire` semantics, re-derived — the POC's probe shape).
`Ok(None)` = held elsewhere (no-work is a value); `Ok(Some(boxed
Lock))` on grant.
- **`Lock` handle** (`renew`, consuming `release`):
- `renew(ttl)` — the same duration guard; the new **full TTL
window from now** (not additive); `false` = lost it (expired and
re-acquired by another owner — the owner-scoped update's 0-rows).
- `release(self: Box<Self>)` — consuming; the owner-scoped delete;
`true` = was held (row gone by this owner's hand), `false` =
row-less or foreign (the substrate's row-scoped truth-teller
posture — no handle-local "held" flag caching).
- **Guarantee row posture** (ADR-008 §7): mutual exclusion bounded by
TTL + renewal; expiry lapses *silently* (no revocation event —
expired rows are deleted opportunistically by the next acquire's
expiry-delete, no general expiry sweeper — the inherited posture,
documented); re-acquirable after expiry (the POC-pinned property —
pin it engine-side here; the contract-suite row remains for the
suite itself).
- **Same-owner re-acquire**: pin the inherited disposition (the
substrate's shape: the same owner's re-acquire keeps the original
row, TTL *not* refreshed, reports granted — the SQLite task's
pinned posture; engine-sqlite.md's mapping row calls it inherited
deliberately, and the pg arm should match it for cross-engine
equivalence — flag in Notes if the pg realization differs).
- **Post-lapse release arms**: pin both (the stale holder's release
deletes its own stale row → `true`; after a foreign re-acquire →
`false`) — the SQLite task's pinned shape, matched here.
- **Closed-store posture**: `try_lock`/`renew`/`release` fail closed
with opaque `Database` (the engine-wide shape).
- **No `lock_tx`** — deliberate absence (core-contract.md's named-
locks section); nothing to build, just don't invent one.
## Acceptance Criteria
- [ ] `try_lock`: validation (name + owner), duration guard
(`ttl <= 0` → opaque `Err`, detail in the source chain),
closed-store check, then acquire — `Ok(None)` on held-elsewhere
- [ ] Exclusion + loser shape pinned (two owners, one wins); expiry
re-acquisition pinned (the POC's property, engine-side)
- [ ] `renew`: duration guard; full-window reset; `false` on
lost/expired-and-reacquired
- [ ] `release`: consuming, owner-scoped, both boolean arms pinned
(stale-row delete = `true`; foreign/row-less = `false`)
- [ ] Same-owner re-acquire disposition pinned (and flagged if it
differs from the SQLite arm's)
- [ ] Silent lapse posture documented (no revocation event; no
general expiry sweeper)
- [ ] `cargo test -p alkstore-postgres` (harness server), clippy
`-D warnings`, fmt clean; gates green server-less
## References
- docs/architecture/core-contract.md (named locks section)
- docs/architecture/decisions/008-contract-v1-pinning.md §7 (guarantee row)
- docs/architecture/decisions/023-fourth-review-round.md §2 (duration kind)
- docs/architecture/engine-postgres.md (Mapping the contract: named locks)
- docs/research/poc-pg-posture-findings.md (the lock probe row)
- alkstore-sqlite/src/lock.rs (the structural twin — match its pinned dispositions)
- alkstore/src/lock.rs (the trait)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion