Files
kandev/docs/decisions/2026-08-09-exclusive-runtime-state-ownership.md
T
2026-08-09 21:44:52 +01:00

69 lines
3.0 KiB
Markdown

# ADR-2026-08-09-exclusive-runtime-state-ownership: Lock Runtime State Before Backend Startup
**Status:** accepted
**Date:** 2026-08-09
**Area:** backend, infra
## Context
Backend startup changes shared state before it binds the HTTP port. It opens logs, creates database
backups, applies migrations, and reconciles active sessions. Launcher port checks do not protect
direct backend commands or a bind race.
A development command started a second backend against the live Kandev home. The process changed
session state and then failed to bind the occupied HTTP port. SQLite serialized individual writes,
but it did not protect the ownership of the runtime state.
## Decision
Each backend process must acquire non-blocking operating-system advisory locks before shared-state
initialization. The process locks the canonical Kandev home. It also locks a custom SQLite database
when that database is outside the home.
The backend acquires all required locks after it loads configuration and before it initializes the
backend logger. It holds the locks until backend cleanup is complete. Lock acquisition errors stop
startup before logs, backups, migrations, session recovery, agentctl, or HTTP services start.
The implementation uses file-handle locks on Unix and Windows. A crash releases these locks. The
implementation keeps lock files after release because removing a locked file can create two lock
identities.
The lock scope is independent of ports. Intentional local instances use separate Kandev homes,
separate SQLite databases, and separate ports. The launcher supervisor stops the old backend before
it starts the replacement, so a planned restart preserves this ownership rule.
## Consequences
A stray direct backend command cannot change a live Kandev database or session state. The failure
appears before the process creates its normal backend log, so the launcher or terminal must show a
clear stderr message.
The lock adds platform-specific code and native Windows coverage. Custom SQLite paths outside the
home need a second lock target. Postgres backends still lock their local home, but this decision does
not define active-active Postgres operation.
An operator can find an old lock file after a crash. The file is harmless because the operating
system lock, not the file, proves ownership.
## Alternatives Considered
### Bind the HTTP port first
Rejected. A different port can still open the same home or SQLite database. Port ownership does not
protect logs, secrets, backups, worktrees, or migrations.
### Keep ownership checks in launchers
Rejected. Direct `__backend`, `make dev-backend`, service managers, and bind races can bypass a
launcher check. The backend owns persistent initialization and must enforce the boundary.
### Use a PID file
Rejected. A crash leaves stale PID data, and operating systems can reuse a PID. A PID file does not
provide atomic ownership.
### Depend on SQLite write locking
Rejected. SQLite protects transactions, not the semantic ownership of startup recovery. It also
does not protect other files in the Kandev home.