Task — engineering-spec@1

Install concurrency lock

doneTASK-IMP-103
module improvement · class product · priority p2 · created 2026-07-17 · shipped 2026-07-17
depends on none · blocks TASK-IMP-104, TASK-IMP-106

TASK-IMP-103: Install concurrency lock

Summary

install.sh removes the vendored machine and re-copies it with no lock, so a second install - or any reader touching .cyberos/ during the window - can observe a half-vendored machine. Guard the vendor step with a mkdir lock carrying the owning pid and a start timestamp, refuse a concurrent install with both pids named, break a demonstrably stale lock with a warning, and release on every exit path.

Problem

tools/install/install.sh:57 runs rm -rf "$CY/cuo" "$CY/plugin" "$CY/mcp" and :58 re-copies. Between those lines the machine does not exist. grep -c "install.lock\|flock" install.sh returns 0 - there is no lock of any kind. Observed twice on a slow mount during the 2026-07-16 sachviet run as transient missing-file probes; reported as IMP-10 in IMPROVEMENT_HANDOFF.md and then never triaged across five batches, which makes it the only unexecuted finding from the original audit.

The failure is transient and needs concurrency to bite, which is exactly why it has survived: nothing forces it to happen, and nothing stops it either. Every consumer that adds CI-driven installs, or runs an agent that reads .cyberos/ while a human re-installs, reaches it.

Proposed Solution

Acquire .cyberos/.install.lock by mkdir (atomic on POSIX) before the vendor step, writing pid and started_at into it. On collision, read the lock: if the owning pid is alive on this host, refuse with both pids and the lock's age. If the lock is older than a threshold AND its pid is dead, break it with a warning naming what was broken. Release via trap on EXIT, INT, and TERM so a killed install does not wedge the next one. Same-host liveness only - the pid in a lock written by another machine on a shared mount means nothing, and the code must say so rather than guess.

Alternatives Considered

Success Metrics

Scope

In scope: install.sh lock acquire/refuse/break/release, the uninstall-side removal of a lock it owns, suite arms.

Out of scope / Non-Goals

Dependencies

None upstream. Blocks TASK-IMP-104 (its version check must precede this lock in install.sh) and TASK-IMP-106 (its summary reports this task's lock-removal branch). Ship 103 first; the three are parent-serialised per §11a and MUST NOT run as concurrent swarm members.

AI Authorship Disclosure

1. Description (normative)

2. Acceptance criteria

3. Edge cases

Audit

§1 - Verdict summary

Spec is 88 lines, 6 §1 clauses, 5 ACs, 6 edge cases, 5 test arms. The window it closes is verified on main (install.sh:57-58 rm -rf then cp -R; zero lock references). Scope bounded to the machine's own directory. Passes after 6 findings.

§2 - Findings (all resolved)

ISS-001 - Lock refusal conflated with lock-creation failure

A mkdir failure on a read-only .cyberos/ is not contention, but the refusal message could not tell the operator which had happened - sending them to hunt a process that does not exist. Resolved: §3 edge case makes the distinction normative; AC 1 asserts the contention wording.

ISS-002 - Stale-break threshold hard-coded

A fixed 900 s cannot suit both a laptop and a slow CI mount. Resolved: §1 #1.3 names CYBEROS_LOCK_STALE_SECS with a 900 s default - an operator dial, not a constant.

ISS-003 - Dead pid + fresh lock would break a just-started install

Breaking on a dead pid alone races a lock written microseconds ago. Resolved: §1 #1.4 requires BOTH age threshold and pid death; AC 3 asserts the fresh-dead-pid case refuses.

ISS-004 - Cross-host pid liveness is undecidable on a shared mount

kill -0 against another machine's pid is meaningless, and reading it as dead breaks a live install. Resolved: §3 edge case adopts TASK-IMP-093's lease convention - foreign/unreadable is alive until the threshold.

ISS-005 - Refusal path could release a lock it does not own

A naive trap releases on every exit including refusal, deleting the holder's lock. Resolved: §1 #1.5 forbids it; AC 4 covers the signal path.

ISS-006 - Uninstall could delete a foreign lock

Symmetric to the batch-5 .cyberos-owned finding on shared skills. Resolved: §1 #1.6 scopes removal to a lock uninstall owns; AC 5 asserts both arms.

§3 - Resolution

All 6 concerns addressed. The machine floor (task-lint) ran FIRST and was clean before any judgment family was applied, per TASK-IMP-084. Score = 10/10.


End of TASK-IMP-103 audit.