chore(openspec): add host-agent-single-instance-lock change artifacts
Tests / Test passed: 759

Proposal, design, spec, and tasks for the per-installation exclusive
instance lock. 15/16 tasks complete; only manual real-environment
verification (5.4) remains, with semantics covered by unit tests in
test_app.py and test_instance_lock.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:49:45 +08:00
co-authored by Claude Opus 4.6
parent d00ada67a5
commit 82567fd248
6 changed files with 164 additions and 0 deletions
@@ -0,0 +1,37 @@
## ADDED Requirements
### Requirement: Host Agent acquires an exclusive local instance lock before any control-plane side effect
The Host Agent SHALL acquire an exclusive, non-blocking lock scoped to its configured `identity_path` state directory as the first action of application startup, before resolving host identity, enrolling any device, or contacting the control plane.
#### Scenario: Lock acquired on normal startup
- **WHEN** a Host Agent starts and no other process holds the lock for its `identity_path` state directory
- **THEN** it acquires the lock and proceeds to identity resolution, device enrollment, and the heartbeat/claim loops as before
#### Scenario: Duplicate instance is rejected before any network call
- **WHEN** a second Host Agent process starts against the same `identity_path` state directory while a first process is still running
- **THEN** the second process fails to acquire the lock and exits without resolving host identity, enrolling any device, or otherwise contacting the control plane
### Requirement: Duplicate-instance rejection is immediate and clearly identified
The Host Agent SHALL fail immediately, without waiting or retrying, when the instance lock is already held, and SHALL log an error that identifies the failure as a duplicate-instance conflict distinct from other startup failures.
#### Scenario: Operator sees an actionable error
- **WHEN** a duplicate Host Agent instance fails to acquire the lock
- **THEN** the logged error names the lock file location and identifies the cause as another instance already running, rather than a generic or unrelated startup failure
### Requirement: The instance lock releases without manual intervention after any process exit
The Host Agent SHALL release its instance lock when its process exits for any reason, including graceful shutdown, an unhandled exception, or a forced kill, without requiring any manual cleanup step before a subsequent instance can start.
#### Scenario: Lock is released after graceful shutdown
- **WHEN** a running Host Agent instance completes its graceful shutdown sequence
- **THEN** a new Host Agent instance started afterward against the same `identity_path` state directory successfully acquires the lock
#### Scenario: Lock is released after an unclean process exit
- **WHEN** a running Host Agent instance is terminated forcefully (e.g. killed) without running its shutdown sequence
- **THEN** a new Host Agent instance started afterward against the same `identity_path` state directory successfully acquires the lock without any manual lock file cleanup
### Requirement: Instance lock scope is per identity, not machine-global
The instance lock SHALL be scoped to the Host Agent's configured `identity_path` state directory, such that two Host Agent processes configured with different `identity_path` values SHALL be able to run concurrently on the same machine.
#### Scenario: Independent identities do not contend
- **WHEN** two Host Agent processes start on the same machine with different `identity_path` state directories
- **THEN** both acquire their respective locks and run concurrently without either being rejected