docs: approve cooperative checkpoint locks
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
## Status
|
||||
|
||||
- Status: blocked
|
||||
- Current phase: architecture escalation gate
|
||||
- Next safe action: obtain an explicit user decision because portable pathname `stat` followed by `unlink` cannot provide atomic compare-and-delete against external replacement.
|
||||
- Status: active
|
||||
- Current phase: approved cooperative-lock contract revision
|
||||
- Next safe action: document the cooperative serialization boundary, use `lstat` for best-effort replacement detection, correct the regression timing, then reverify and review.
|
||||
|
||||
## Request and scope
|
||||
|
||||
@@ -53,7 +53,7 @@ Owned paths:
|
||||
- `skills/develop/develop-workflow/scripts/checkpoint-record.mjs`
|
||||
- `skills/develop/develop-workflow/tests/checkpoint-record.test.mjs`
|
||||
|
||||
Implement a Node 22 ESM helper with an exported injectable API and a strict CLI. Validate one H1 and the ten required H2 sections used by this record. For creation, require an absent destination; for replacement, require the current SHA-256 digest. Acquire a fixed exclusive per-record lock, re-read and hash under that lock, write a unique same-directory mode-0600 temporary file, flush it, close it, atomically rename it, and flush the containing directory. Preserve unrelated stale temporary files for recovery and fail closed on a pre-existing lock. Cleanup only files and locks created by the current invocation without masking the primary failure.
|
||||
Implement a Node 22 ESM helper with an exported injectable API and a strict CLI. Validate one H1 and the ten required H2 sections used by this record. For creation, require an absent destination; for replacement, require the current SHA-256 digest. Acquire a fixed exclusive per-record lock among cooperating helper invocations, re-read and hash under that lock, write a unique same-directory mode-0600 temporary file, flush it, close it, atomically rename it, and flush the containing directory. Preserve unrelated stale temporary files for recovery and fail closed on a pre-existing lock. Cleanup invocation-created paths without masking the primary failure. External mutation of lock pathnames is outside the serialization boundary; use `lstat` as best-effort detection and preserve replacements visible before unlink, without claiming atomic defense against concurrent out-of-protocol mutation.
|
||||
|
||||
Cover valid create/replace, missing/malformed/duplicate sections, replacement without a digest, stale digest, concurrent writers, interrupted write/flush, rename failure, directory-flush failure, stale temporary artifacts, stale lock, and cleanup behavior that preserves the last valid target byte-for-byte.
|
||||
|
||||
@@ -202,6 +202,7 @@ Before integration, abandon only by preserving evidence and obtaining cleanup ap
|
||||
- Exceptional remediation standards reviewer: `develop-workflow-exception-standards` / `precedent-locator` / child `f28de7de` / session `01a06ceb-bd79-7050-86d5-693b50a01fe7`; digest confirmed; CHANGES REQUESTED.
|
||||
- Exceptional remediation specification reviewer: `develop-workflow-exception-spec` / `claim-verifier` / child `4d059ea9` / session `01a06ceb-eace-7afb-afdd-df094bb66f6a`; digest confirmed; CHANGES REQUESTED.
|
||||
- Remaining medium finding: `stat(lock)` followed by `unlink(lock)` is still a check-then-act race, and `stat` follows symlinks. An external replacement in that gap could be deleted. The regression replaced the lock after closing the original handle and did not model the exact scenario.
|
||||
- Approved architecture revision: the user selected a cooperative lock contract. Conforming helper invocations serialize through exclusive `wx` acquisition and never replace an existing lock. Concurrent out-of-protocol pathname mutation is not a supported safety boundary. `lstat` remains a best-effort guard for replacements observable before cleanup.
|
||||
- This review-brief checkpoint is necessarily a metadata-only commit after the pinned product HEAD because a record cannot contain the digest of bytes that include its own digest. Prove it touches only this record.
|
||||
|
||||
## Integration and rollback
|
||||
@@ -234,4 +235,6 @@ Rebase the feature branch onto the latest remote/local `main` when available, re
|
||||
- Remediation result: implemented the approved scope at `52576c755666f95a40907b0500f095c6d314c346`; focused tests pass 14/14, link tests pass 8/8, `bun run typecheck` exits 0, and `bun run test` exits 0.
|
||||
- Review result: both axes rejected the remediation because identity verification and pathname deletion are not atomic; reviewers remained read-only with unchanged clean state.
|
||||
- Architecture constraint: another conforming helper invocation cannot replace an existing lock because acquisition uses `open(..., "wx")`; the disputed race requires out-of-protocol filesystem mutation. Portable Node filesystem APIs provide no atomic compare-and-delete primitive for defending a path lock against that mutation.
|
||||
- Next safe action: commit this failure evidence and ask the user whether to narrow the contract to cooperative actors, authorize a platform-specific advisory-lock redesign, or stop blocked.
|
||||
- Architecture decision: the user approved narrowing the contract to cooperative helper invocations rather than broadening Slice 1–2 with a broker or platform-specific native primitive.
|
||||
- Approved follow-up: replace path `stat` with `lstat`, make the replacement test mutate the pathname while the original handle is open, synchronize documentation, reverify, and obtain fresh review.
|
||||
- Next safe action: commit this revised plan gate before dependent production mutation.
|
||||
|
||||
Reference in New Issue
Block a user