feat(lanes): per-lane database, Redis, and .env isolation (A1+A2)

Gives each lane its own slot-derived runtime (ports, detached process
lifecycle, profile-driven hooks) and its own database/Redis logical
index/.env file, so two lanes running the same repo's stack at once no
longer share state. Machine-level DB/Redis credentials live at
~/.ccam/secrets.env (mode 0600, never returned by any route); a hook's
output is redacted of that password (raw and URL-encoded forms) before
it reaches a log file or the lane_hook_output websocket broadcast.
Wired into provision/up/reset/remove; reset accepts --keep-db to skip
the drop/recreate/migrate/reseed block entirely.
This commit is contained in:
2026-08-04 10:03:40 +07:00
parent d71086f677
commit 9d145865dd
19 changed files with 3265 additions and 12 deletions
+26
View File
@@ -493,6 +493,32 @@ try {
db.prepare("ALTER TABLE lanes ADD COLUMN detected_at TEXT").run();
}
// Migrate: per-lane runtime allocation. `slot` is the small integer every runtime
// fact derives from (ports now; database name and Redis index later) — the
// numbering Shipyard gets for free from its fixed lane1..lane9 directories and
// CCAM, keyed by cwd, has to allocate. `ports` is a JSON map name -> port,
// recording the number a lane ACTUALLY got: the preferred `base + slot` may be
// taken by something outside CCAM, so the allocator steps aside and the real
// number has to survive a restart or the next boot would move a live lane.
//
// Allocation is lazy — a lane that is only ever watched never takes a slot — so
// the column is nullable and the uniqueness constraint is partial. That index is
// the backstop under the lock in lane-slots.js, not a substitute for it.
// One probe per column, same reasoning as the detection columns above.
try {
db.prepare("SELECT slot FROM lanes LIMIT 1").get();
} catch {
db.prepare("ALTER TABLE lanes ADD COLUMN slot INTEGER").run();
}
try {
db.prepare("SELECT ports FROM lanes LIMIT 1").get();
} catch {
db.prepare("ALTER TABLE lanes ADD COLUMN ports TEXT NOT NULL DEFAULT '{}'").run();
}
db.prepare(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_lanes_slot ON lanes(slot) WHERE slot IS NOT NULL"
).run();
// Migrate: link agent rows to a workflow run. Workflow inner-agents are already
// ingested as subagents (same subagents/ dir); these columns add the grouping +
// phase that the run journal provides. Additive, safe on existing DBs.