|
| str | _default_clock () |
| "tuple[str, Optional[str]]" | _probe_posix (int pid) |
| Optional[str] | _proc_start_time_posix (int pid) |
| "tuple[str, Optional[str]]" | _probe_windows (int pid) |
| "tuple[str, Optional[str]]" | _default_probe (int pid) |
| Optional[_LockRecord] | _read_record (Path lock_path) |
| None | _atomic_create (Path lock_path, int pid, str start_time, str acquired_at) |
| LockReclaimEvent | _make_event (Path lock_path, Optional[_LockRecord] record, str reason, *, bool force, Clock clock) |
| LockReclaimEvent | _decide_reclaim (Path lock_path, Optional[_LockRecord] record, bool force, Probe probe, Clock clock) |
| LockHandle | acquire (Path lock_path, *, bool force=False, Optional[Clock] clock=None, Optional[Probe] probe=None) |
| None | release (LockHandle handle) |
The single-worktree ``regenerate`` file lock ([OP-7], §5.3, Plan 142i R5).
Purpose
-------
Serialize concurrent ``regenerate`` runs in one worktree with an atomic
(``O_CREAT|O_EXCL``) lock file carrying the holder's **PID + process-start-time +
acquire-timestamp**. A second live holder fails closed with ``REGEN_LOCK_HELD``.
A lock is reclaimed **only on provable holder death** — a **dead PID** or a
**process-start-time mismatch** (PID reuse) — never on elapsed time ([OP-7]: a
renewal-less file lock cannot tell a slow-but-alive holder from a dead one, so a
staleness auto-reclaim would let a second run steal a live lock and corrupt the
swap). A **live-or-inconclusive** holder is never auto-reclaimed; operator
``--force`` is the sole override and every reclamation is surfaced as a typed
``LockReclaimEvent`` (never an unobserved log line).
Responsibilities
----------------
- ``acquire(lock_path, *, force, clock, probe)`` — atomically take the lock or
fail closed / reclaim-on-provable-death; return a ``LockHandle``.
- ``release(handle)`` — drop our own lock.
- ``LockReclaimEvent`` — the typed, assertable reclamation record.
Testability
-----------
``acquire`` takes injectable **clock** + **process-probe** seams so the tests
cover live-PID / dead-PID / start-time-mismatch / live-but-slow / malformed-lock
/ ``--force`` paths without real sleeps, real PID reuse, or live-PID races.
Diagnostics
-----------
Domain: OC_CHAT.MAIN
Levels:
L2 - acquire/release lifecycle
L3 - reclaim classification
L4 - liveness-probe details
Contracts
---------
- Atomic create — ``O_CREAT|O_EXCL`` so the lock file has no create race.
- Reclaim ⇔ provable death (dead PID or start-time mismatch) OR ``force``.
- ``force`` reclaims but NEVER skips validation/canonicalisation/derive-verify
(that is the pipeline's job); it only reclaims the lock and logs the event.
- Every probe FAILURE ⇒ inconclusive ⇒ fail-closed (never mapped to "dead").
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.registry.regenerate.envelope (REGEN_LOCK_HELD / RegenerateError)
- oc_chat.registry.clock (RealClock wall time for the default acquire timestamp)