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>
4 files changed,
+13
−11
docs/superpowers/specs/2026-08-03-unit-tenant-purge-channel.md+6 −6internal/appconfig/sync.go+3 −1internal/appconfig/sync_test.go+1 −1internal/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) |
| } |