carlos / platform Public

Sidecar isolation Task 6 (1/6): CARLOS_SIDECAR_DB tells every worker where its database is (D4)

workerEnv gains the row's DBPath as CARLOS_SIDECAR_DB, agent-owned and
applied over the tenant's env layers like the token, for flat and nested
workers alike. Both composers (the wake and the crash restart) pass it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f1533ce5dffa6cfcafbbdac11515157bcc4eb81a parent 9e5e786
2 files changed, +48 −9
  • internal/activator/activator.go +16 −9
  • internal/activator/worker_nested_test.go +32 −0
diff --git a/internal/activator/activator.go b/internal/activator/activator.go
index 63f39416b..9bbc96be4 100644
--- a/internal/activator/activator.go
+++ b/internal/activator/activator.go
@@ -2466,7 +2466,7 @@ func (a *Service) doWake(ctx context.Context, e *actEntry) (*wokenInstance, erro
ident release.StagedIdentity
pend pendingGeneration
)
- env := workerEnv(a.agentSocketFor(rt), host, token)
+ env := workerEnv(a.agentSocketFor(rt), host, token, rt.DBPath)
if plan.planned() {
// A planned worker (zero-downtime spec §6.5): the named build,
// behind planGate at each exec, with startWorker's publication
@@ -3330,19 +3330,26 @@ func (a *Service) agentSocketFor(rt registry.Route) string {
}
// workerEnv is what a spawn hands the child: where to call back, who it is,
-// and this incarnation's credential. The backend applies it OVER the tenant's
-// own env file so a tenant cannot forge its own token (StartWorker's
-// contract). A free function of plain strings, not a method on the entry, so
-// the two callers — the wake (off-lock, with a token still in a local) and the
-// crash restart (under a.mu) — both compose it without touching entry state
-// they don't own.
-func workerEnv(socket, host, token string) map[string]string {
+// this incarnation's credential, and where its database is. The backend
+// applies it OVER the tenant's own env file so a tenant cannot forge its own
+// token (StartWorker's contract). A free function of plain strings, not a
+// method on the entry, so the two callers — the wake (off-lock, with a token
+// still in a local) and the crash restart (under a.mu) — both compose it
+// without touching entry state they don't own.
+//
+// db is the row's DBPath, handed over as CARLOS_SIDECAR_DB (sidecar
+// isolation D4) for flat and nested workers alike: a worker opens its
+// database there and nowhere else. One that computes /data/<host>.db itself
+// stops working the moment its row is nested, where /data is a read-only
+// tmpfs in the namespace.
+func workerEnv(socket, host, token, db string) map[string]string {
name, _, _ := strings.Cut(host, ".") // the sidecar's declared name
return map[string]string{
"CARLOS_AGENT_SOCKET": socket,
"CARLOS_SIDECAR_TOKEN": token,
"CARLOS_SIDECAR_HOST": host,
"CARLOS_SIDECAR_NAME": name,
+ "CARLOS_SIDECAR_DB": db,
}
}
@@ -3638,7 +3645,7 @@ func (a *Service) respawnWorkerLocked(e *actEntry, stopOld Proc, why string, ont
if refused == nil {
// No registry write (spec §7.9): see doWake.
if tok, err = newWorkerToken(); err == nil {
- proc, ident, pend, err = a.startWorker(ctx, rt, workerEnv(a.agentSocketFor(rt), host, tok), onto, gate)
+ proc, ident, pend, err = a.startWorker(ctx, rt, workerEnv(a.agentSocketFor(rt), host, tok, rt.DBPath), onto, gate)
}
}
cancel()
diff --git a/internal/activator/worker_nested_test.go b/internal/activator/worker_nested_test.go
new file mode 100644
index 000000000..7e58d7c71
--- /dev/null
+++ b/internal/activator/worker_nested_test.go
@@ -0,0 +1,32 @@
+//go:build linux
+
+package activator
+
+import (
+ "testing"
+ "time"
+)
+
+// Sidecar isolation D4: every worker, flat or nested, is told where its
+// database is. CARLOS_SIDECAR_DB is agent-owned, applied over the tenant's
+// env layers like the token, and names the row's DBPath verbatim.
+func TestFlatWorkerGetsSidecarDB(t *testing.T) {
+ t.Parallel()
+ a, b, _, _ := testActivator(t, time.Hour)
+ rt := testWorkerRoute("bot.x.internal")
+ if err := a.WakeWorker(rt); err != nil {
+ t.Fatalf("WakeWorker: %v", err)
+ }
+ env := b.lastWorkerEnv()
+ if env["CARLOS_SIDECAR_DB"] != rt.DBPath {
+ t.Fatalf("CARLOS_SIDECAR_DB = %q, want the row's DBPath %q (D4: a worker opens its database there and nowhere else)", env["CARLOS_SIDECAR_DB"], rt.DBPath)
+ }
+ // A crash restart composes the env again, under a.mu, and must carry
+ // the same path.
+ b.lastWorker().crash()
+ a.sweepOnce()
+ waitFor(t, "the crash restart", func() bool { return b.workerStarts() == 2 })
+ if env := b.lastWorkerEnv(); env["CARLOS_SIDECAR_DB"] != rt.DBPath {
+ t.Fatalf("restart env CARLOS_SIDECAR_DB = %q, want %q", env["CARLOS_SIDECAR_DB"], rt.DBPath)
+ }
+}