8.0 KiB
spec, created, status
| spec | created | status |
|---|---|---|
| docs/specs/executors/requirements/port-collision-safety.md | 2026-08-09 | completed |
Implementation Plan: Backend Runtime-State Ownership
Overview
This repair prevents a second backend from changing the runtime state of a live Kandev instance. It also makes startup session recovery publish authoritative state before backend readiness.
The confirmed reproduction used this command in a task worktree while another backend was active:
make -C apps/backend dev
The second process opened the shared database and reconciled sessions. It then failed to bind the
occupied HTTP port. The first backend later saw an already-settled session, so it did not clear the
stored generating task summary.
This plan implements ADR-2026-08-09-exclusive-runtime-state-ownership. It also updates the status-delivery and interrupted-task contracts.
Backend
Runtime-state ownership locks
Add apps/backend/internal/backendapp/ownershiplock/ with a small cross-platform lock API. The
package owns target selection, canonical paths, lock acquisition, and lock release.
lock.godefines the owner type, conflict error, target metadata, and all-or-nothing acquisition.lock_unix.gouses a non-blocking exclusive file-handle lock.lock_windows.gouses the equivalent non-blocking Windows file lock throughgolang.org/x/sys/windows, which is already a backend dependency.targets.goselects the Kandev-home lock. It adds a database lock when SQLite uses a path outside the canonical home.- Lock files remain after release. The open file handle owns the lock.
Update apps/backend/internal/backendapp/main.go. Acquire ownership after config.Load() and flag
overrides, but before logger.NewBackendLogger(). Hold ownership until Run completes all service
cleanup. On any acquisition error, write one clear stderr error and return exit code 1.
The rejected process must not create backend logs, open a database, take a backup, apply a migration, launch agentctl, reconcile a session, or start an HTTP listener.
Add the native lock package to apps/backend/Makefile and
.github/workflows/backend-tests.yml Windows-sensitive package lists.
Startup state publication
Update apps/backend/internal/orchestrator/service.go. Route a successful startup transition from
STARTING or RUNNING to WAITING_FOR_INPUT through the existing semantic session-state
publication path.
The transition must keep these existing startup behaviors:
- Abandon an orphan turn.
- Move an eligible task from
IN_PROGRESStoREVIEW. - Add the interrupted-task marker.
- Preserve the executor row and resume data.
- Repair confirmed-dead local process data.
The semantic state event must contain the authoritative primary-session state and an explicit empty
foreground-activity value. The existing task activity publisher remains the settle fallback for a
RUNNING session. A session that is already WAITING_FOR_INPUT remains a no-op.
No status-summary schema or frontend change is necessary. The current projector consumes semantic
session events and explicit task activity values. The current sidebar already shows the interrupted
icon when generating is absent.
Public Documentation
Update these public pages:
docs/public/backend-development.mddocs/public/contributing.mddocs/public/configuration.md
State that one backend owns a Kandev home at a time. Explain that raw backend commands use the
normal home unless KANDEV_HOME_DIR is set. Keep the existing isolated command example.
Do not document lock-file removal as a recovery step. A stale file does not hold an operating-system lock.
Tests
Runtime-state ownership
-
What: A second process cannot acquire the same Kandev-home lock.
-
File:
apps/backend/internal/backendapp/ownershiplock/lock_test.go -
How: Use a subprocess helper and temporary directories. Assert conflict, release after normal exit, release after forced exit, and independent-home success.
-
What: Separate homes cannot open the same external SQLite database.
-
File:
apps/backend/internal/backendapp/ownershiplock/targets_test.go -
How: Build target sets for default and custom database paths. Assert canonical deduplication and external-database ownership.
-
What: Backend startup stops before shared-state initialization when ownership is unavailable.
-
File:
apps/backend/internal/backendapp/main_test.go -
How: Hold a temporary-home lock, invoke the backend entry seam, and assert exit code 1. Assert that no database, backup, backend log, agentctl child, or listener appears.
Startup state publication
-
What: Startup recovery publishes
RUNNING -> WAITING_FOR_INPUTthrough the semantic session event path. -
File:
apps/backend/internal/orchestrator/reconcile_restart_test.go -
How: Seed a running session, task, orphan turn, executor row, and recording event bus. This regression must fail before the production change because the state event is absent.
-
What: A stale
RUNNINGandgeneratingsummary converges after startup recovery. -
File:
apps/backend/internal/task/statussummary/projector_test.go -
How: Seed a stored summary, send the authoritative recovery event, and assert a newer revision with
WAITING_FOR_INPUT, empty foreground activity, and zero active subagents. -
What: Existing task review, interruption, resume, and executor-row behavior does not change.
-
File:
apps/backend/internal/orchestrator/reconcile_restart_test.goandapps/backend/internal/orchestrator/turn_lifecycle_test.go -
How: Extend the current startup-reconciliation cases. Assert the existing database outcomes with the new event assertions.
Public docs
- What: Published docs describe one-home ownership and isolated direct backend commands.
- Files:
docs/public/backend-development.md,docs/public/contributing.md, anddocs/public/configuration.md - How: Run the public-doc tests and validator.
Verification Results
- Focused ownership tests:
go test -tags fts5 ./internal/backendapp/ownershiplock ./internal/backendapp— 304 tests passed. - Startup recovery, task service, and projector tests:
go test -tags fts5 ./internal/orchestrator ./internal/task/service ./internal/task/statussummary— 2,658 tests passed. - Public documentation tests:
node --test scripts/validate-public-docs.test.mjs— 58 tests passed;node scripts/validate-public-docs.mjs— 41 pages validated.
Implementation Waves And Parallel Candidates
Wave 1 contains two parallel candidates. User authorization is required before delegation.
- Task 01: Enforce runtime-state ownership —
parallel-safe - Task 02: Publish startup recovery state —
parallel-safe
The tasks use disjoint source packages. Neither task changes a schema, generated contract, dependency file, or shared package configuration.
Wave 2:
- Task 03: Document runtime ownership — depends on Task 01
Implementation remains sequential in the primary conversation unless the user explicitly authorizes subagents.
Risks
- File-lock behavior differs between Unix and Windows. Native Windows coverage is required.
- A process test can leak a child and retain a lock after an early test error. Each test must register cleanup before its first assertion.
- A launcher restart must wait for the old backend to release ownership. Existing supervisors already stop and join the old process before replacement startup. Regression coverage must keep this behavior.
- Network filesystems can have different advisory-lock guarantees. This repair targets supported local Kandev homes and local SQLite files.
Out of Scope
- Automatic
KANDEV_HOME_DIRselection for raw backend commands. - Active-active backend support for Postgres or NATS.
- A sidebar or debug-panel redesign.
- A database migration or status-summary schema change.
Open Questions
None.