Data Model — Cross-OS Primitive Unification (Phase 1)

This mission has no persistent datastore; the "entities" are the canonical primitives and their invariants.

E-01 — Canonical lock (kernel/locks.py)

Represents: mutual exclusion over a resource, cross-process, on Windows (mandatory msvcrt) and POSIX (fcntl) alike.

Structure:

kernel.clock), host, version. Written to the sidecar, never the payload.

unlock): an async context manager and a sync context manager.

non-blocking-retry-until-deadline (current) and blocking-with-timeout (filelock/bootstrap).

  • LockRecord (holder metadata): schema, pid, started_at (via
  • Two facades over one _LockCore (open / os-lock / write-record / truncate /
  • Parameters: blocking: bool, timeout_s: float | None — cover both

Invariants: 1. Sidecar derivation is owned by the primitive. Caller passes the resource path; the primitive derives resource.parent / f".{resource.name}.lock". A caller cannot point the lock at the payload. (FR-007) 2. No payload handle is ever yielded. Both context managers yield a LockRecord, never a file handle to the protected resource → a held lock can never be read as a payload (the #4703 footgun structurally impossible). (FR-007) 3. Release truncates, never unlinks (preserves inode identity so a contender's O_CREAT cannot mint a rival inode). (NFR-005) 4. Re-entrancy preserved where callers relied on it (status/locking.py thread-local counts). (NFR-005) 5. Sole sanctioned holder of raw msvcrt/fcntl (FR-010 gate).

E-02 — Canonical safe-delete (specify_cli/core/safe_delete.py)

Represents: removal of managed, possibly-read-only assets on both OS.

Operations: force_writable(path), safe_unlink(path), safe_rmdir(path), safe_rmtree(path) (+ an rmtree onerror shim).

Invariants: 1. No-follow (lstat) + masked (S_IMODE): clears the write bit on the entry itself, never a symlink target outside the managed tree. (FR-002, SC-006) 2. Windows read-only refusal handled: clear the write bit, then unlink/rmdir. (FR-001) 3. One implementation; the 3 live copies removed, the frozen migration copy untouched. (FR-003, C-002)

E-03 — OS-detection seam (kernel/paths.py::is_windows)

Represents: the single "is this Windows?" answer.

Invariants: 1. Patchable via module attribute — overridable in tests without faking os.name (which flips pathlib→WindowsPath and crashes pytest). (C-003) 2. One definition; the 3 _is_windows defs + the inline file_lock.py check route through it; only C-module import guards stay raw. (FR-005, FR-012)

E-04 — DIRECTIVE_043 gates (tests/architectural/)

Represents: by-construction bans that keep E-01/E-03 canonical.

Invariants (non-vacuity, NFR-003): 1. Concrete floor: fails if the sanctioned module no longer contains the primitive (deleting the door must not trivially pass). 2. Self-mutation: a synthetic injected violation must be flagged. 3. Shrink-only allowlist: seeded fail-closed with all current sites; fails on growth; terminal = empty (lock) / import-guards-only (OS). Excludes upgrade/migrations/ (C-002). 4. Predicate honours the holder (C-004): permits the canonical module's own raw calls + sidecar-record read.

State transitions (lock lifecycle)

idle → acquire(blocking?, timeout?) → held(yields LockRecord)
held → release → idle           (truncate, not unlink)
held → force_release → idle     (stale-holder recovery; truncate)
acquire (contended, non-blocking) → NotAcquired
acquire (contended, blocking, deadline) → TimeoutError

No transition exposes the protected payload's bytes to the holder while held.