carlos / platform Public

release, appconfig, docs: correct three comments the review flagged

- agent.go: adoptLoop/canaryAdoptLoop no longer call Pass/PassFilter
  "with no options at all" — both now pass BakeDir. Reworded to say
  they call it without the nag option specifically.
- sync.go: lostKeys' doc claimed the rendered and on-disk key sets are
  "the same by construction" unqualified; qualified with the
  agent-owned names applyAgentVars adds and removes, which is why
  they're skipped in the loop below.
- spec doc §7.3: the rollout note described the first-sync-pass
  fleet-wide restart in past tense, but the branch is unmerged and no
  such pass has run. Rewritten in future/conditional tense to match
  sync.go's own comment, keeping the "weighed and accepted
  deliberately" framing.
- sync_test.go: renamed TestSyncConvergesToEmptyAfterEveryVarIsUnset
  to TestSyncConvergesToAgentOwnedOnlyAfterEveryVarIsUnset — the file
  no longer converges to empty, it converges to CARLOS_PURGE=header
  alone, which is what the test body already documented.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 584c6a30a55ba9d857f1085d6d5c36167e16df3f parent df644dc
4 files changed, +13 −11
  • docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md +6 −6
  • internal/appconfig/sync.go +3 −1
  • internal/appconfig/sync_test.go +1 −1
  • internal/host/agent.go +3 −3
diff --git a/docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md b/docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md
index 5c4e5134f..fe27c7868 100644
--- a/docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md
+++ b/docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md
@@ -701,15 +701,15 @@ also forwarded; the fix in both cases is upgrading the edge, not the app.
Landing it is not free of side effects, though: because every existing
`/etc/carlos/apps/<host>.env` on the fleet gains this line, the *first*
- sync pass after this shipped changed the byte content of every active
- unit tenant's file — `syncHost`'s `bytes.Equal` convergence check saw a
- diff for each one and restarted it once (hibernated routes were
+ sync pass after this ships will change the byte content of every active
+ unit tenant's file — `syncHost`'s `bytes.Equal` convergence check will see
+ a diff for each one and will restart each once (hibernated routes are
unaffected; `try-restart` is a no-op on an inactive unit). That one-time
- fleet-wide restart was weighed and accepted deliberately, not an
+ fleet-wide restart is weighed and accepted deliberately, not an
overlooked side effect: the var cannot take effect without a restart.
Contrast `internal/appconfig/syncexec.go`'s `execAddrConfigHeader`,
- which declined this exact trade for the socket-exec shape — a bare
- comment header wasn't worth bouncing every instance on the fleet. Here
+ which declines this exact trade for the socket-exec shape — a bare
+ comment header isn't worth bouncing every instance on the fleet. Here
the restart is not a cost of the payload, it *is* the payload landing.
Where the var *is* set, it is **agent-owned in both directions**: set
diff --git a/internal/appconfig/sync.go b/internal/appconfig/sync.go
index b1f8b4c5a..8a471e7e0 100644
--- a/internal/appconfig/sync.go
+++ b/internal/appconfig/sync.go
@@ -240,7 +240,9 @@ func reap(routes []registry.Route, envDir string) {
// render is what the caller is deciding whether to WRITE, and this decision
// has to be made before rendering (a refused route must never reach the
// render-diff-write-restart path at all). RenderEnv emits exactly one
-// KEY=value line per var, so the two key sets are the same by construction.
+// KEY=value line per var, so the two key sets are the same by construction —
+// except for the agent-owned names applyAgentVars adds to and removes from
+// the rendered file afterward, which is why they are skipped below.
//
// Blank lines and #-comments are ignored, matching how systemd's
// EnvironmentFile reads these files: they carry no configuration, so their
diff --git a/internal/appconfig/sync_test.go b/internal/appconfig/sync_test.go
index 45a205673..c331df0b5 100644
--- a/internal/appconfig/sync_test.go
+++ b/internal/appconfig/sync_test.go
@@ -161,7 +161,7 @@ func TestSyncLeavesHandStagedFileAloneWhenNoManifestExists(t *testing.T) {
}
}
-func TestSyncConvergesToEmptyAfterEveryVarIsUnset(t *testing.T) {
+func TestSyncConvergesToAgentOwnedOnlyAfterEveryVarIsUnset(t *testing.T) {
// The mirror of the case above: the manifest object DOES exist, it just
// has no vars left. That is a real, operator-intended state and must
// converge — every tenant var is dropped and the unit restarted. The
diff --git a/internal/host/agent.go b/internal/host/agent.go
index 12914a8ad..5de8f098a 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -775,9 +775,9 @@ func RunAdopt(args []string) {
// canary-TTL nag (spec §5) belongs to the hourly root pass
// (carlos-adopt.service, this verb's ExecStart), the same split PR #9's
// config sync above uses. adoptLoop and canaryAdoptLoop in this same
- // package call release.Pass/PassFilter with no options at all, so an
- // expired canary nags once an hour from here, never every 2-3 seconds
- // from the in-process fast loop.
+ // package call release.Pass/PassFilter without the nag option (they pass
+ // BakeDir only), so an expired canary nags once an hour from here, never
+ // every 2-3 seconds from the in-process fast loop.
if err := release.Pass(ctx, reg, store, pub, release.OptDir(), restartRoute, release.NagExpiredCanaries(os.Stdout), release.BakeDir(dataDir())); err != nil {
log.Fatalf("adopt: %v", err)
}