carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

Tenant Email, Plan 4 of 5: The per-instance config layer and the delivery ack

> For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Give an app a third config bundle, apps/<acct>/<app>/instances-config/<host>.json, resolved after the named environment, so one instance can be handed a value none of its siblings ever see, and make the agent say out loud which version of that layer it rendered: ConfigAck gains Schema and InstanceConfigETag, and the agent rewrites the ack on every pass rather than only when a file changes, so a console can tell "this box has not got the layer yet" apart from "this box has nothing to render". Nothing in this plan writes an overlay for anyone: there is no CLI verb, no console door, and no email change. Step 5 is the layer's first caller.

Architecture: internal/appconfig gains instances.go: the key, the host rule, the CAS read/write/delete, LoadMergedHost, and the two functions the sweeps reach the same answer through (instanceOverlays, one prefix listing per app, and applyInstanceOverlay, the pure merge). Sync and SyncExec keep their group caches exactly as they are — the group is still (Account, App, Environment) and the default bundle is still read once per (account, app) — and each route's own merge happens in the materialization loop, so a pass costs one List per app plus one Get per route that actually has an overlay. Four guards written against a two-layer merge are revisited one per task, each with the case that would have been wrong: lostKeys/defaultMissing (sync.go:260, :388), the mass-removal guard (syncexec.go:341-380), AwaitingRestart (syncexec.go:771) and allShadowed (syncexec.go:872). ConfigAck grows two fields, Schema and InstanceConfigETag, and keeps its single change-only writer. An earlier draft had the agent rewrite the ack on every pass so that absence became decidable; the spec now rules that out (§6.5), because it is a Get and a Put per converged route per pass on every box, and on a fleet-door box control/config-ack/ is outside both the read and the write scope (internal/edge/storedoor.go:389, :489), so every one of those passes would fail, log, and still leave the console with nothing. Delivery stays decidable where it matters: writing an instance's credential always changes that instance's file, so it always produces an ack.

Tech Stack: Go 1.26, internal/appconfig (bundles, merge, the two sync sweeps, the ack), internal/host (the agent's two config loops and configAckFunc), internal/release (Store, NewDirStore, AppPrefix, ErrCASConflict), internal/registry (route rows), internal/console (reads acks; unchanged by this plan).

Spec: docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md — §11 step 4 is the whole mandate: §6.2 (the per-instance config layer) and §6.5 (confirming delivery). Everything else in §6 is step 5's: no instance senders, no PerInstance transition, no instance email doors, no svcrole.Mail, no uid check, no claimDomain change. §5 and §7 landed in plans 2 and 3 and are not touched.

ExecDelivery is not read from this layer, and that is the existing ruling, not a new one. appconfig.go:44-71 records why the flag is bundle-level and why per-route was refused: "a per-route flag would need a registry column and — since a Provisioned route's Channel and Environment belong to its instance record — a record field and reconcile convergence too: three doors onto one bit", and "the config-that-lies hazard the original ruling named is real at that granularity, where two routes resolving through the SAME bundle could disagree about whether it is delivered" (:66-71). A per-HOST bundle is exactly that granularity. So resolveExecDelivery (syncexec.go:688) is never handed the instance layer, checkOptedIn never reads it, and an exec_delivery field that somehow appears in one of these objects is inert — pinned by a test in task 3. The layer carries vars and sealed secrets, nothing else.

The sweeps do not literally call LoadMergedHost, and that is deliberate. LoadMergedHost(ctx, s, acct, app, env, host) is the single-shot API — the one function anything outside this package asks "what does this host actually get" — and it fetches all three layers itself. Calling it per route inside Sync/SyncExec would re-fetch config.json and the named bundle once per route and throw away the group caches those loops exist for: a box with forty routes of one app would go from two bucket reads to eighty. The loops instead reach the same answer through the same two pieces LoadMergedHost is built from — instanceOverlays for the per-app listing and applyInstanceOverlay for the merge — and task 3 pins the two paths against each other with a test that resolves one host both ways and compares.

Global Constraints

  • Run gofmt -w on every file you touched before running the gate. gofmt -l . only NAMES an unformatted file; it fixes nothing. Several blocks below are pasted with aligned trailing comments (task 1's key constants, task 9's ConfigAck fields) that gofmt will realign, so a plan followed literally goes red on the gate's last step unless gofmt -w ran first.
  • The gate before every commit: go build ./... && go vet ./... && go test ./... -count=1 all clean, and gofmt -l . empty over the whole tree. Env: GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off — Go may need GOCACHE pointed somewhere writable, and "read-only file system" from the toolchain is that tell. Socket-binding tests need TMPDIR=/tmp/g.
  • This repo lands through amadan branch merge, never a GitHub PR. Branch worktree-tenant-email-senders. CI is .amadan/ci.d/, triggered by a push to the amadan remote; a branch pushed only to origin has been gated by nothing.
  • No new capability for a member, and no email change. This plan ships alone. After every task an app that gets config today gets the same bytes, in the same file, with the same restart behaviour, because nothing writes an overlay: the tests are the only writers in the tree when this plan ends.
  • Wire-shape changes are additive. Live ConfigAck objects exist in production buckets, written by agents that have never heard of Schema, and must still parse. Schema == 0 therefore means "an agent older than this release" and is a first-class state, never an error.
  • Absence is never a removal. Every branch this plan touches in the two sweeps has the same rule the existing code has: a fetch failure leaves the file exactly as it is, and only a determination can remove one. The overlay adds a second way a route can be configured, so it also adds a second way an "absent" verdict can be wrong — tasks 5 and 7 are that, and nothing else.
  • Copy: no em dashes. Full stop and a new sentence, lower case. Comments are full sentences saying why, never process history; log lines use -> between values.
  • No member-facing strings are added by this plan, so no copy-review gate is due. If a task grows one, run it.
  • Commit after every task with the trailer Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>.

---

File Structure

| File | Responsibility | |---|---| | internal/appconfig/instances.go (create) | InstancesConfigPrefix, InstanceConfigKey, ValidInstanceHost, LoadInstanceConfig, ApplyInstance, DeleteInstanceConfig, LoadMergedHost, applyInstanceOverlay, instanceOverlays | | internal/appconfig/instances_test.go (create) | the key and host rules, the CAS write, the generation bump, three-layer precedence, the sweeps-vs-LoadMergedHost equivalence | | internal/appconfig/store.go (modify) | getIfExistsAtETag (the etag-returning read getIfExistsAt delegates to); applyAt returns the new etag | | internal/appconfig/sync.go (modify) | the per-app overlay index, the per-route merge, defaultMissing extended to the overlay-alone case | | internal/appconfig/syncexec.go (modify) | the same index and merge; absent base with an overlay is not a removal; the mass-removal guard; AwaitingRestart and allShadowed | | internal/appconfig/configack.go (modify) | Schema, InstanceConfigETag, ConfigAckSchema, ConfigAckPrefix, LatestConfigAck, InstanceConfigDelivery | | internal/appconfig/routefingerprint.go (modify) | doc only: the layer is bucket-side, so the generation bump is its signal | | internal/host/agent.go (modify) | configAckFunc stamps ConfigAckSchema on every ack it writes |

---

Task 1: The layer's key, its host rule and its reader

Files:

  • Create: internal/appconfig/instances.go, internal/appconfig/instances_test.go
  • Modify: internal/appconfig/store.go (getIfExistsAt at :54)

Interfaces:

  • Consumes: release.AppPrefix (internal/release/release.go:212), release.ValidAccount (internal/release/qualified.go:53), release.ValidApp (internal/release/release.go:240), Manifest (appconfig.go:21), getIfExistsAt (store.go:54).
  • Produces: ``go func InstancesConfigPrefix(acct, app string) string // apps/<acct>/<app>/instances-config/ func InstanceConfigKey(acct, app, host string) string // .../instances-config/<host>.json func ValidInstanceHost(host string) bool func LoadInstanceConfig(ctx context.Context, s release.Store, acct, app, host string) (*Manifest, string, bool, error) // manifest, etag, found func getIfExistsAtETag(ctx context.Context, s release.Store, key string) (*Manifest, string, bool, error) ``
  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (create):

package appconfig

import (
	"context"
	"encoding/json"
	"strings"
	"testing"

	"github.com/carlosframework/platform/internal/release"
)

// putInstanceRaw writes an instance-layer object without going through the
// package's own writer, which does not exist until task 2. Tests in this
// file that only READ the layer use it, so task 1 can be verified on its
// own.
func putInstanceRaw(t *testing.T, ctx context.Context, s release.Store, acct, app, host string, m *Manifest) string {
	t.Helper()
	body, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		t.Fatalf("marshal instance manifest: %v", err)
	}
	etag, err := s.Put(ctx, InstanceConfigKey(acct, app, host), body, "")
	if err != nil {
		t.Fatalf("put instance manifest: %v", err)
	}
	return etag
}

// The layer sits under the app's own prefix, beside config.json and
// environments/, so release.PurgeApp's prefix sweep reaches it and a purged
// app leaves no instance overlay behind holding sealed secrets.
func TestInstanceConfigKeysSitUnderTheAppsOwnPrefix(t *testing.T) {
	if got, want := InstancesConfigPrefix("bab", "titogo"), "apps/bab/titogo/instances-config/"; got != want {
		t.Fatalf("InstancesConfigPrefix = %q, want %q", got, want)
	}
	if got, want := InstanceConfigKey("bab", "titogo", "book.customer.example.com"),
		"apps/bab/titogo/instances-config/book.customer.example.com.json"; got != want {
		t.Fatalf("InstanceConfigKey = %q, want %q", got, want)
	}
}

// A host becomes a bucket key segment here, so the one gate every caller
// passes through is where a separator, a traversal or an empty name is
// refused. The shape is a bare lowercase hostname, the same thing a route
// row's Host column holds and the same thing sync.go's own path guard
// (rt.Host != filepath.Base(rt.Host)) already insists on before it derives
// a file name from one.
func TestValidInstanceHostRefusesAnythingThatIsNotABareHost(t *testing.T) {
	for _, bad := range []string{
		"", ".", "..", "a/b", "../etc", "Book.example.com", "book..example.com",
		"-lead.example.com", "trail-.example.com", "book.example.com/",
		strings.Repeat("a", 254),
	} {
		if ValidInstanceHost(bad) {
			t.Errorf("ValidInstanceHost(%q) = true", bad)
		}
	}
	for _, good := range []string{"book.customer.example.com", "inst1", "a-b.c-d.example.com", "127-0-0-1.example.com"} {
		if !ValidInstanceHost(good) {
			t.Errorf("ValidInstanceHost(%q) = false", good)
		}
	}
}

// The reader answers three ways, and the caller needs all three apart: a
// manifest and the etag it was read at, "no object" without an error (an
// instance with no overlay is the normal case, not a fault), and an error
// for anything else. The etag is what the delivery ack carries (design
// §6.5), so a read that dropped it would leave the agent unable to say
// which version of the layer it rendered.
func TestLoadInstanceConfigReadsTheLayerAndItsETag(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())

	m, etag, found, err := LoadInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com")
	if err != nil || found || m != nil || etag != "" {
		t.Fatalf("absent overlay = %+v %q %v %v, want (nil, \"\", false, nil)", m, etag, found, err)
	}

	want := putInstanceRaw(t, ctx, s, "bab", "titogo", "book.customer.example.com",
		&Manifest{Schema: 1, App: "titogo", Vars: map[string]Var{"TENANT_SMTP_USER": {Kind: "env", Value: "book"}}})
	m, etag, found, err = LoadInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com")
	if err != nil || !found || m == nil {
		t.Fatalf("present overlay = %+v %v %v", m, found, err)
	}
	if etag == "" || etag != want {
		t.Fatalf("etag = %q, want the object's own %q", etag, want)
	}
	if m.Vars["TENANT_SMTP_USER"].Value != "book" {
		t.Fatalf("overlay lost its var: %+v", m.Vars)
	}

	// The same newer-schema refusal every other bundle read makes: acting on
	// a manifest whose unknown fields json.Unmarshal has already dropped
	// would silently discard whatever they meant.
	putInstanceRaw(t, ctx, s, "bab", "titogo", "newer.example.com", &Manifest{Schema: 2, App: "titogo"})
	if _, _, _, err := LoadInstanceConfig(ctx, s, "bab", "titogo", "newer.example.com"); err == nil {
		t.Fatal("a schema-2 overlay was accepted, want a refusal naming the upgrade")
	}

	// A host that is not a bare hostname never reaches the bucket at all.
	if _, _, _, err := LoadInstanceConfig(ctx, s, "bab", "titogo", "../config"); err == nil {
		t.Fatal("LoadInstanceConfig accepted a traversal host")
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestInstanceConfigKeys|TestValidInstanceHost|TestLoadInstanceConfig' ./internal/appconfig/

Expected: build failure, undefined: InstancesConfigPrefix.

  • [ ] Step 3: Write the code

internal/appconfig/instances.go (create):

// The instance config layer (tenant-email senders design §6.2): a third
// bundle, one object per route host, resolved after the named environment.
// Same Manifest shape, same sealed envelopes, same name and value rules as
// the other two — a per-instance bundle is not a new format, just a third
// bucket key the same shape is written to.
//
// Bucket layout addition:
//
//	apps/<acct>/<app>/instances-config/<host>.json   CAS-guarded, schema-1 manifest
//
// It exists so a credential can be scoped to ONE instance. #148's shape is
// one instance per customer under a single app, and everything the platform
// delivers today lands in a bundle every instance of the app renders, so a
// per-customer secret delivered through the default bundle reaches every
// other customer's process.
//
// Manifest.ExecDelivery is NOT read from this layer. That is the ruling at
// appconfig.go:66-71, unchanged: a per-route delivery flag would need a
// registry column, a record field and reconcile convergence, and two routes
// resolving through the same bundle could disagree about whether they are
// delivered. A per-host bundle is exactly the granularity that ruling
// refused, so resolveExecDelivery is never handed one of these manifests
// and a field that somehow appears in one is inert.
package appconfig

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/carlosframework/platform/internal/release"
)

// InstancesConfigPrefix is the list prefix behind "which instances of this
// app have an overlay" — every instances-config/<host>.json key shares it.
// Inside apps/<acct>/<app>/ on purpose: release.PurgeApp's prefix sweep
// reaches it, so a purged app cannot leave an object full of sealed
// secrets behind.
func InstancesConfigPrefix(acct, app string) string {
	return release.AppPrefix(acct, app) + "instances-config/"
}

// InstanceConfigKey is the bucket key of one instance's overlay. host is a
// route's Host column verbatim, which is what makes the key derivable from
// a registry row with nothing to look up.
func InstanceConfigKey(acct, app, host string) string {
	return InstancesConfigPrefix(acct, app) + host + ".json"
}

// instanceHostRe is a bare lowercase hostname: labels of letters, digits
// and hyphens, joined by dots, never starting or ending with a hyphen or a
// dot. Case-restricted like ValidEnvironmentName and for the same reason,
// with one more: this string is also the file name sync derives on the box
// (sync.go's filepath.Base guard), so one convention keeps a bucket key and
// a file name from disagreeing about which host they belong to.
var instanceHostRe = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$`)

// ValidInstanceHost reports whether host is usable as an instance overlay
// key. A separator or a traversal is refused at the one gate every caller
// passes through, because this string is concatenated into a bucket key: a
// host of "../config" would otherwise name the app's default bundle.
func ValidInstanceHost(host string) bool {
	if host == "" || len(host) > 253 || strings.Contains(host, "..") {
		return false
	}
	// Per-label bound as well as the whole-name one, matching DNS and
	// launchctx/controlname's label rule. Without it a single 200-character
	// label passes here and is refused everywhere else that handles the same
	// host, which is a disagreement between two gates rather than one rule.
	for _, label := range strings.Split(host, ".") {
		if len(label) > 63 {
			return false
		}
	}
	return instanceHostRe.MatchString(host)
}

// LoadInstanceConfig reads one instance's overlay, returning the manifest,
// the etag it was read at, and whether the object exists at all.
//
// The etag is the point of this signature. The delivery ack (design §6.5)
// carries the etag the agent actually rendered, and the console compares it
// with the one its own Put returned: that comparison is decidable, where a
// map of delivered key names cannot tell an old agent apart from a new one
// rendering a route with no overlay vars.
//
// Not-found is (nil, "", false, nil): an instance with no overlay is the
// normal case and the state every instance is in until something writes one.
func LoadInstanceConfig(ctx context.Context, s release.Store, acct, app, host string) (*Manifest, string, bool, error) {
	if !release.ValidAccount(acct) {
		return nil, "", false, fmt.Errorf("config: invalid account %q", acct)
	}
	if !release.ValidApp(app) {
		return nil, "", false, fmt.Errorf("config: invalid app %q", app)
	}
	if !ValidInstanceHost(host) {
		return nil, "", false, fmt.Errorf("config: invalid instance host %q", host)
	}
	return getIfExistsAtETag(ctx, s, InstanceConfigKey(acct, app, host))
}

internal/appconfig/store.go, replacing getIfExistsAt (:49-73) with an etag-returning read and a wrapper that keeps every existing caller's signature:

// getIfExistsAt is getIfExists generalized to any manifest key — Key(acct,
// app) for the default bundle, EnvironmentKey(acct, app, name) for a named
// one (see environments.go), InstanceConfigKey(acct, app, host) for an
// instance overlay (instances.go). Same shape either way: all three are the
// identical manifest format at sibling bucket paths, so one read path
// serves them.
func getIfExistsAt(ctx context.Context, s release.Store, key string) (*Manifest, bool, error) {
	m, _, found, err := getIfExistsAtETag(ctx, s, key)
	return m, found, err
}

// getIfExistsAtETag is getIfExistsAt plus the object's etag, which the
// instance layer's reader needs and the other two bundles' readers do not:
// the delivery ack names the etag of the overlay the agent rendered
// (senders design §6.5), and an etag dropped here could not be recovered
// later without a second read racing the first.
func getIfExistsAtETag(ctx context.Context, s release.Store, key string) (*Manifest, string, bool, error) {
	raw, etag, err := s.Get(ctx, key)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, "", false, nil
		}
		return nil, "", false, err
	}
	var m Manifest
	if err := json.Unmarshal(raw, &m); err != nil {
		return nil, "", false, fmt.Errorf("config %s: %w", key, err)
	}
	// json.Unmarshal has already dropped any fields this binary doesn't
	// know about, so acting on a newer-schema manifest would silently
	// discard whatever those fields meant. Refuse instead.
	if m.Schema > 1 {
		return nil, "", false, fmt.Errorf("config %s: schema %d is newer than this binary understands — upgrade carlos", key, m.Schema)
	}
	return &m, etag, true, nil
}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS, including every existing bundle test unchanged.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/instances.go internal/appconfig/instances_test.go internal/appconfig/store.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the instance config layer's key, host rule and etag-returning reader (plan 4, task 1)"

---

Task 2: Writing and deleting the layer, and the generation bump

Files:

  • Modify: internal/appconfig/instances.go, internal/appconfig/instances_test.go, internal/appconfig/store.go (applyAt at :146, apply at :115), internal/appconfig/environments.go (applyEnvironment at :108)

Interfaces:

  • Consumes: applyAt (store.go:146), touchLogged (generation.go:107), GenKey (generation.go:51).
  • Produces: ``go func ApplyInstance(ctx context.Context, s release.Store, acct, app, host string, sets map[string]Var, unsets []string) (removed int, etag string, err error) func DeleteInstanceConfig(ctx context.Context, s release.Store, acct, app, host string) error func applyAt(ctx context.Context, s release.Store, key, app, bundle string, sets map[string]Var, unsets []string) (int, string, error) // now returns the new etag ``
  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// The write door. It returns the etag it wrote at, which is the whole
// mechanism behind the delivery check (design §6.5): the caller keeps that
// string and compares it with what an agent's ack says it rendered.
func TestApplyInstanceWritesTheLayerAndReturnsItsETag(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())

	removed, etag, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com",
		map[string]Var{"TENANT_SMTP_USER": {Kind: "env", Value: "book"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	if removed != 0 {
		t.Fatalf("removed = %d on a create, want 0", removed)
	}
	if etag == "" {
		t.Fatal("ApplyInstance returned an empty etag, so nothing can check delivery against it")
	}
	m, readETag, found, err := LoadInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com")
	if err != nil || !found {
		t.Fatalf("LoadInstanceConfig after the write = %+v %v %v", m, found, err)
	}
	if readETag != etag {
		t.Fatalf("the reader saw etag %q, the writer returned %q — the delivery check compares these two", readETag, etag)
	}
	if m.Schema != 1 || m.App != "titogo" || m.Vars["TENANT_SMTP_USER"].Value != "book" {
		t.Fatalf("the written overlay is not a schema-1 manifest of this app: %+v", m)
	}

	// A second write moves the etag, which is what makes a stale ack
	// detectable at all.
	_, etag2, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com",
		map[string]Var{"TENANT_SMTP_USER": {Kind: "env", Value: "book2"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance (second): %v", err)
	}
	if etag2 == etag {
		t.Fatalf("etag did not move on a changed write: %q", etag2)
	}

	// Unset reports how many of the keys were actually there, the same
	// contract every other bundle's unset has.
	removed, _, err = ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com", nil,
		[]string{"TENANT_SMTP_USER", "NEVER_SET"})
	if err != nil || removed != 1 {
		t.Fatalf("unset = %d %v, want 1 and no error", removed, err)
	}

	// The var-name and var-value rules are the bundle's, not this layer's:
	// applyAt refuses before anything reaches the bucket.
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com",
		map[string]Var{"not a name": {Kind: "env", Value: "x"}}, nil); err == nil {
		t.Fatal("an invalid var name was accepted into the instance layer")
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "../config", nil, nil); err == nil {
		t.Fatal("ApplyInstance accepted a traversal host")
	}
}

// An unset against an overlay that does not exist is refused rather than
// minting an empty one, the same refusal applyAt already makes for the
// other two bundles and for the same reason: an empty manifest that EXISTS
// means "configured, then fully unset" by this package's contract, which is
// a state the sweeps converge a file to.
func TestApplyInstanceRefusesUnsetOnlyAgainstAMissingOverlay(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	_, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com", nil, []string{"K"})
	if err == nil {
		t.Fatal("unsetting from a nonexistent overlay was turned into a create")
	}
	if !strings.Contains(err.Error(), "book.customer.example.com") {
		t.Fatalf("the refusal does not name the instance: %v", err)
	}
}

// Writing the layer bumps the app's generation object, the way
// SetExecDeliveryAt does. It has to: ConfigRouteFingerprints digests
// registry columns only (routefingerprint.go:58) and cannot see a
// bucket-side change, so without the bump an overlay would wait out the
// agent's hourly floor with nothing to signal it.
func TestWritingTheInstanceLayerBumpsTheGeneration(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"LOG_LEVEL": "debug"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	genETag := func() string {
		t.Helper()
		_, etag, err := s.Get(ctx, GenKey("bab", "titogo"))
		if err != nil {
			t.Fatalf("read generation: %v", err)
		}
		return etag
	}
	before := genETag()
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com",
		map[string]Var{"TENANT_SMTP_USER": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	afterWrite := genETag()
	if afterWrite == before {
		t.Fatal("writing the instance layer did not bump the generation, so the fast loop would never see it")
	}
	if err := DeleteInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com"); err != nil {
		t.Fatalf("DeleteInstanceConfig: %v", err)
	}
	if genETag() == afterWrite {
		t.Fatal("removing the instance layer did not bump the generation, so the route would keep its last merge")
	}
	if _, _, found, err := LoadInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com"); err != nil || found {
		t.Fatalf("the overlay survived its delete: found=%v err=%v", found, err)
	}
	// Deleting again is a no-op success, so a retried teardown converges.
	if err := DeleteInstanceConfig(ctx, s, "bab", "titogo", "book.customer.example.com"); err != nil {
		t.Fatalf("second DeleteInstanceConfig: %v", err)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestApplyInstance|TestWritingTheInstanceLayer' ./internal/appconfig/

Expected: build failure, undefined: ApplyInstance.

  • [ ] Step 3: Write the code

internal/appconfig/store.go, applyAt (:146) returns the new etag. The body is unchanged except the three early returns and the final Put:

func applyAt(ctx context.Context, s release.Store, key, app, bundle string, sets map[string]Var, unsets []string) (int, string, error) {
	for k, v := range sets {
		if !ValidVarName(k) {
			return 0, "", fmt.Errorf("config: invalid var name %q", k)
		}
		if v.Kind == "env" {
			if err := ValidVarValue(v.Value); err != nil {
				return 0, "", fmt.Errorf("config: var %s: %w", k, err)
			}
		}
	}
	m, etag, found, err := getForWriteAt(ctx, s, key, app)
	if err != nil {
		return 0, "", err
	}
	if !found && len(sets) == 0 {
		return 0, "", fmt.Errorf("config: %s has no %s bundle — nothing to unset", app, bundle)
	}
	if m.Vars == nil {
		m.Vars = map[string]Var{}
	}
	removed := 0
	for _, k := range unsets {
		if _, ok := m.Vars[k]; ok {
			removed++
		}
		delete(m.Vars, k)
	}
	for k, v := range sets {
		m.Vars[k] = v
	}
	m.Schema, m.App = 1, app
	raw, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return 0, "", err
	}
	// The new etag is returned because the instance layer's writer (design
	// §6.5) has to hand it to whoever will later compare it with an agent's
	// delivery ack. The other two bundles' callers ignore it.
	newETag, err := s.Put(ctx, key, raw, etag)
	if err != nil {
		return 0, "", err
	}
	return removed, newETag, nil
}

Its two existing callers drop the new value: apply (store.go:122) becomes

	removed, _, err := applyAt(ctx, s, Key(acct, app), app, bundle, sets, unsets)

and applyEnvironment (environments.go:118) becomes

	removed, _, err := applyAt(ctx, s, EnvironmentKey(acct, app, envName), app, envName, sets, unsets)

internal/appconfig/instances.go (append):

// ApplyInstance writes sets and removes unsets from ONE instance's overlay
// in one CAS write, returning how many of unsets were actually present and
// THE ETAG IT WROTE AT.
//
// That etag is the contract with the box. The console keeps it, the agent
// reports the etag it rendered in its config ack, and the two are compared
// (design §6.5, InstanceConfigDelivery). A map of delivered key names would
// not do: it cannot tell an agent too old to render this layer apart from a
// new agent rendering a route whose overlay sets nothing it recognises.
//
// There is no CLI verb and no console door onto this function, by design
// (§6.2, "only the platform writes this layer"). It exists so a credential
// can be scoped to one instance, and the only caller that scopes one is the
// email walk in step 5.
//
// Unset-only against an overlay that does not exist is refused, not turned
// into a create, for the reason applyAt's own doc comment gives: an empty
// manifest that EXISTS is a state the sweeps converge a file to, and
// minting one to hold nothing would deliver an empty file to a live
// instance.
func ApplyInstance(ctx context.Context, s release.Store, acct, app, host string, sets map[string]Var, unsets []string) (int, string, error) {
	if !release.ValidAccount(acct) {
		return 0, "", fmt.Errorf("config: invalid account %q", acct)
	}
	if !release.ValidApp(app) {
		return 0, "", fmt.Errorf("config: invalid app %q", app)
	}
	if !ValidInstanceHost(host) {
		return 0, "", fmt.Errorf("config: invalid instance host %q", host)
	}
	removed, etag, err := applyAt(ctx, s, InstanceConfigKey(acct, app, host), app, "instance "+host, sets, unsets)
	if err != nil {
		return 0, "", err
	}
	// The generation object is the ONLY signal this layer has.
	// ConfigRouteFingerprints (routefingerprint.go) digests registry columns
	// and cannot see a bucket-side change, so an overlay written without
	// this touch would wait out configSyncLoop's hourly floor. Same
	// best-effort demotion every other write here makes: the manifest has
	// landed, so a failed touch is logged, never returned.
	touchLogged(ctx, s, acct, app)
	return removed, etag, nil
}

// DeleteInstanceConfig removes one instance's overlay outright, which
// converges that route back to its group's merge on the next pass. Absent
// is success: a retried teardown must converge rather than fail, and
// release.Store.Delete is already idempotent.
//
// The generation is touched even when nothing was removed. Branching on it
// would mean a Get before every delete to find out, and one redundant touch
// costs a tiny object write where a missed one costs an hour of a live
// instance holding config nothing meant it to have.
func DeleteInstanceConfig(ctx context.Context, s release.Store, acct, app, host string) error {
	if !release.ValidAccount(acct) {
		return fmt.Errorf("config: invalid account %q", acct)
	}
	if !release.ValidApp(app) {
		return fmt.Errorf("config: invalid app %q", app)
	}
	if !ValidInstanceHost(host) {
		return fmt.Errorf("config: invalid instance host %q", host)
	}
	if err := s.Delete(ctx, InstanceConfigKey(acct, app, host)); err != nil {
		return err
	}
	touchLogged(ctx, s, acct, app)
	return nil
}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/instances.go internal/appconfig/instances_test.go internal/appconfig/store.go internal/appconfig/environments.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: write and delete an instance overlay, bumping the app's generation (plan 4, task 2)"

---

Task 3: LoadMergedHost, the three-layer resolve

Files:

  • Modify: internal/appconfig/instances.go, internal/appconfig/instances_test.go

Interfaces:

  • Consumes: LoadMerged (environments.go:169), unchanged in signature.
  • Produces: ``go func LoadMergedHost(ctx context.Context, s release.Store, acct, app, envName, host string) (*Manifest, string, error) // merged, instance overlay etag func applyInstanceOverlay(app string, base, inst *Manifest) *Manifest func instanceOverlays(ctx context.Context, s release.Store, acct, app string) (map[string]bool, error) ``
  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// The precedence the design states: default < environment < instance
// (§6.2). Three layers, each winning per key over the one below it, and the
// instance layer's etag handed back so the caller can later check what was
// delivered.
func TestLoadMergedHostResolvesDefaultThenEnvironmentThenInstance(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{
		"FROM_DEFAULT": "default", "FROM_ENV": "default", "FROM_INSTANCE": "default"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if _, err := ApplyEnvironment(ctx, s, "bab", "titogo", "prod", map[string]Var{
		"FROM_ENV":      {Kind: "env", Value: "environment"},
		"FROM_INSTANCE": {Kind: "env", Value: "environment"}}, nil); err != nil {
		t.Fatalf("ApplyEnvironment: %v", err)
	}
	_, want, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com", map[string]Var{
		"FROM_INSTANCE": {Kind: "env", Value: "instance"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}

	m, etag, err := LoadMergedHost(ctx, s, "bab", "titogo", "prod", "book.customer.example.com")
	if err != nil {
		t.Fatalf("LoadMergedHost: %v", err)
	}
	if etag != want {
		t.Fatalf("etag = %q, want the overlay's own %q", etag, want)
	}
	for k, wantVal := range map[string]string{
		"FROM_DEFAULT": "default", "FROM_ENV": "environment", "FROM_INSTANCE": "instance"} {
		if got := m.Vars[k].Value; got != wantVal {
			t.Errorf("%s = %q, want %q", k, got, wantVal)
		}
	}

	// A sibling with no overlay of its own gets the two-layer merge and an
	// empty etag, which is what says "this host has no overlay" rather than
	// "this agent did not render one".
	m2, etag2, err := LoadMergedHost(ctx, s, "bab", "titogo", "prod", "other.customer.example.com")
	if err != nil {
		t.Fatalf("LoadMergedHost (sibling): %v", err)
	}
	if etag2 != "" {
		t.Fatalf("a host with no overlay reported etag %q", etag2)
	}
	if m2.Vars["FROM_INSTANCE"].Value != "environment" {
		t.Fatalf("the sibling saw another instance's value: %+v", m2.Vars)
	}
}

// With no overlay, LoadMergedHost is LoadMerged, to the field. That matters
// for the unbound case in particular: LoadMerged returns the default bundle
// OBJECT there, exec_delivery and all, and a merge that quietly rebuilt it
// would strip a flag other code reads off the same manifest.
func TestLoadMergedHostWithNoOverlayIsExactlyLoadMerged(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"K": "v"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if err := SetExecDelivery(ctx, s, "bab", "titogo", ExecDeliveryFile); err != nil {
		t.Fatalf("SetExecDelivery: %v", err)
	}
	want, err := LoadMerged(ctx, s, "bab", "titogo", "")
	if err != nil {
		t.Fatalf("LoadMerged: %v", err)
	}
	got, etag, err := LoadMergedHost(ctx, s, "bab", "titogo", "", "book.customer.example.com")
	if err != nil || etag != "" {
		t.Fatalf("LoadMergedHost = %q %v", etag, err)
	}
	if got.ExecDelivery != want.ExecDelivery || got.Vars["K"] != want.Vars["K"] || len(got.Vars) != len(want.Vars) {
		t.Fatalf("LoadMergedHost diverged from LoadMerged with no overlay: %+v vs %+v", got, want)
	}
}

// exec_delivery is never read from this layer (the ruling at
// appconfig.go:66-71). An overlay that carries the field does not put it in
// the merged view, so nothing downstream can act on it.
func TestInstanceLayerNeverCarriesExecDelivery(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"K": "v"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	putInstanceRaw(t, ctx, s, "bab", "titogo", "book.customer.example.com", &Manifest{
		Schema: 1, App: "titogo", Vars: map[string]Var{"OWN": {Kind: "env", Value: "1"}},
		ExecDelivery: ExecDeliveryFile})
	m, _, err := LoadMergedHost(ctx, s, "bab", "titogo", "", "book.customer.example.com")
	if err != nil {
		t.Fatalf("LoadMergedHost: %v", err)
	}
	if m.ExecDelivery != "" {
		t.Fatalf("exec_delivery = %q: the instance layer must never decide delivery", m.ExecDelivery)
	}
	if m.Vars["OWN"].Value != "1" {
		t.Fatalf("the overlay's vars were dropped along with its flag: %+v", m.Vars)
	}
}

// A route bound to a bundle that does not exist is still an ERROR, overlay
// or no overlay. The binding asserted the bundle exists; an overlay does not
// make that assertion true, and answering with a partial merge would hand a
// live route a file built from a binding nobody honoured.
func TestLoadMergedHostStillRefusesAMissingBoundBundle(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"K": "v"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.customer.example.com",
		map[string]Var{"OWN": {Kind: "env", Value: "1"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	if _, _, err := LoadMergedHost(ctx, s, "bab", "titogo", "gone", "book.customer.example.com"); err == nil {
		t.Fatal("a missing bound bundle resolved anyway because the host had an overlay")
	}
}

// The index the sweeps use: one listing per app answers "which of this
// app's instances have an overlay at all", so a pass costs a Get only for
// the instances that do.
func TestInstanceOverlaysListsOnlyThisAppsHosts(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	have, err := instanceOverlays(ctx, s, "bab", "titogo")
	if err != nil || len(have) != 0 {
		t.Fatalf("empty app = %v %v, want an empty set and no error", have, err)
	}
	for _, host := range []string{"book.customer.example.com", "cart.customer.example.com"} {
		if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", host,
			map[string]Var{"OWN": {Kind: "env", Value: host}}, nil); err != nil {
			t.Fatalf("ApplyInstance %s: %v", host, err)
		}
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "eleven", "other.example.com",
		map[string]Var{"OWN": {Kind: "env", Value: "x"}}, nil); err != nil {
		t.Fatalf("ApplyInstance (other app): %v", err)
	}
	have, err = instanceOverlays(ctx, s, "bab", "titogo")
	if err != nil {
		t.Fatalf("instanceOverlays: %v", err)
	}
	if len(have) != 2 || !have["book.customer.example.com"] || !have["cart.customer.example.com"] {
		t.Fatalf("instanceOverlays = %v, want just this app's two hosts", have)
	}
	if have["other.example.com"] {
		t.Fatal("another app's overlay leaked into this app's index")
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestLoadMergedHost|TestInstanceLayerNever|TestInstanceOverlays' ./internal/appconfig/

Expected: build failure, undefined: LoadMergedHost.

  • [ ] Step 3: Write the code

internal/appconfig/instances.go (append; add sort to the imports only if a later task needs it — it does not here):

// LoadMergedHost is LoadMerged plus the instance layer: default <
// environment < instance, the instance winning per key, and the overlay's
// etag returned alongside so a caller can check delivery against it later
// (design §6.2, §6.5). An empty etag means this host has no overlay, which
// is a different statement from an agent that never rendered one.
//
// LoadMerged keeps its signature and its meaning. Everything that asks
// "what does this APP's environment resolve to" still gets two layers, and
// only a caller that names a host gets three.
//
// The two sync sweeps do NOT call this function, deliberately: they resolve
// one group base for every route that shares it and would re-fetch
// config.json per route if they came through here. They reach the same
// answer through instanceOverlays and applyInstanceOverlay instead, and the
// equivalence is pinned by a test rather than by hope.
func LoadMergedHost(ctx context.Context, s release.Store, acct, app, envName, host string) (*Manifest, string, error) {
	if !ValidInstanceHost(host) {
		return nil, "", fmt.Errorf("config: invalid instance host %q", host)
	}
	base, err := LoadMerged(ctx, s, acct, app, envName)
	if err != nil {
		return nil, "", err
	}
	inst, etag, found, err := LoadInstanceConfig(ctx, s, acct, app, host)
	if err != nil {
		return nil, "", err
	}
	if !found {
		return base, "", nil
	}
	return applyInstanceOverlay(app, base, inst), etag, nil
}

// applyInstanceOverlay lays inst over base, per key. base may be nil (the
// app has no default bundle and the route is unbound), in which case the
// merged view is the overlay ALONE — which the callers treat as the
// wholesale replacement it is, see sync.go's defaultMissing guard.
//
// A nil overlay returns base untouched, pointer and all: that is the path
// every instance without an overlay takes, and rebuilding the manifest
// there would strip exec_delivery off the default bundle object LoadMerged
// hands back for an unbound route.
//
// The merged view never carries exec_delivery FROM the overlay. See this
// file's own doc comment: a per-host delivery flag is the granularity the
// #195 ruling refused, and the merged manifest is what gets rendered into a
// file, where a delivery flag has no business appearing anyway.
func applyInstanceOverlay(app string, base, inst *Manifest) *Manifest {
	if inst == nil {
		return base
	}
	merged := &Manifest{Schema: 1, App: app, Vars: map[string]Var{}}
	if base != nil {
		for k, v := range base.Vars {
			merged.Vars[k] = v
		}
	}
	for k, v := range inst.Vars {
		merged.Vars[k] = v
	}
	return merged
}

// instanceOverlays lists which of this app's instances have an overlay at
// all: one listing per (account, app), cached by the caller for a whole
// pass, so a sweep pays one List per app and then one Get per instance that
// actually has one. Reading every route's key blind would cost a bucket
// round trip per instance on a box where nothing has an overlay, which is
// every box in the fleet the day this ships.
//
// A key that does not end in .json, or whose host does not survive
// ValidInstanceHost, is skipped rather than reported: nothing can have
// written it through this package, so it is not this app's overlay
// whatever it is.
func instanceOverlays(ctx context.Context, s release.Store, acct, app string) (map[string]bool, error) {
	prefix := InstancesConfigPrefix(acct, app)
	keys, err := s.List(ctx, prefix)
	if err != nil {
		return nil, err
	}
	out := make(map[string]bool, len(keys))
	for _, k := range keys {
		host := strings.TrimSuffix(strings.TrimPrefix(k, prefix), ".json")
		if host == strings.TrimPrefix(k, prefix) || !ValidInstanceHost(host) {
			continue
		}
		out[host] = true
	}
	return out, nil
}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/instances.go internal/appconfig/instances_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: LoadMergedHost resolves default < environment < instance (plan 4, task 3)"

---

Task 4: Sync applies the overlay per route, at one listing per app

Files:

  • Modify: internal/appconfig/instances.go, internal/appconfig/sync.go (the materialization loop at :143-176), internal/appconfig/instances_test.go

Interfaces:

  • Consumes: instanceOverlays, applyInstanceOverlay (task 3), Sync's existing group caches (sync.go:72-105).
  • Produces: ``go func instanceOverlayLoader(ctx context.Context, s release.Store) func(acct, app, host string) (*Manifest, string, error) ` One closure per pass, shared by both sweeps: one List per (account, app), then a Get` only for a host the listing named.
  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append; add bytes, crypto/ecdh, os, path/filepath, sync and github.com/carlosframework/platform/internal/registry to the file's imports):

// countingStore counts the reads and writes a pass makes, per key, so a
// test can pin the COST of a feature and not just its output. The instance
// layer's promise is one extra listing per app and one extra Get per
// instance that actually has an overlay, which is a claim only a counter
// can keep honest.
type countingStore struct {
	release.Store
	mu    sync.Mutex
	gets  map[string]int
	lists map[string]int
	puts  map[string]int
}

func newCountingStore(s release.Store) *countingStore {
	return &countingStore{Store: s, gets: map[string]int{}, lists: map[string]int{}, puts: map[string]int{}}
}

func (c *countingStore) Get(ctx context.Context, key string) ([]byte, string, error) {
	c.mu.Lock()
	c.gets[key]++
	c.mu.Unlock()
	return c.Store.Get(ctx, key)
}

func (c *countingStore) List(ctx context.Context, prefix string) ([]string, error) {
	c.mu.Lock()
	c.lists[prefix]++
	c.mu.Unlock()
	return c.Store.List(ctx, prefix)
}

func (c *countingStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
	c.mu.Lock()
	c.puts[key]++
	c.mu.Unlock()
	return c.Store.Put(ctx, key, body, ifMatch)
}

func (c *countingStore) n(m map[string]int, k string) int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return m[k]
}

// syncInstanceKit is the unit-path fixture these tests share: a store, a
// registry, an env dir and a parsed key pair.
func syncInstanceKit(t *testing.T) (context.Context, release.Store, *registry.Registry, string, *ecdh.PrivateKey) {
	t.Helper()
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	reg := testRegistry(t)
	priv, _, err := GenerateKey()
	if err != nil {
		t.Fatalf("GenerateKey: %v", err)
	}
	privKey, err := ParsePrivateKey(priv, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	return ctx, s, reg, t.TempDir(), privKey
}

func unitRoute(t *testing.T, reg *registry.Registry, host, app, acct, env string) {
	t.Helper()
	if err := reg.Upsert(registry.Route{
		Host: host, Kind: "instance", App: app, Account: acct, Status: "ready",
		Environment: env, Backing: registry.BackingUnit,
	}); err != nil {
		t.Fatalf("Upsert %s: %v", host, err)
	}
}

// The delivery this layer exists for: one instance of an app gets a value
// its siblings never see. The sibling's file is the app merge, untouched.
func TestSyncRendersTheInstanceOverlayForItsOwnRouteOnly(t *testing.T) {
	ctx, s, reg, envDir, priv := syncInstanceKit(t)
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes", "TENANT": "none"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	unitRoute(t, reg, "cart.example.com", "titogo", "bab", "")

	if err := Sync(ctx, reg, s, priv, envDir, func(string) error { return nil }, nil); err != nil {
		t.Fatalf("Sync: %v", err)
	}
	book, err := os.ReadFile(filepath.Join(envDir, "book.example.com.env"))
	if err != nil {
		t.Fatalf("read book env: %v", err)
	}
	if !bytes.Contains(book, []byte("TENANT=book\n")) || !bytes.Contains(book, []byte("APP_WIDE=yes\n")) {
		t.Fatalf("the overlaid route's file is wrong:\n%s", book)
	}
	cart, err := os.ReadFile(filepath.Join(envDir, "cart.example.com.env"))
	if err != nil {
		t.Fatalf("read cart env: %v", err)
	}
	if bytes.Contains(cart, []byte("book")) || !bytes.Contains(cart, []byte("TENANT=none\n")) {
		t.Fatalf("a sibling saw another instance's config:\n%s", cart)
	}

	// The sweep and the single-shot API must agree about what a host gets.
	// The sweeps resolve through the group cache and never call
	// LoadMergedHost, so this is the only thing holding the two together.
	want, _, err := LoadMergedHost(ctx, s, "bab", "titogo", "", "book.example.com")
	if err != nil {
		t.Fatalf("LoadMergedHost: %v", err)
	}
	rendered, err := RenderEnv(want, priv)
	if err != nil {
		t.Fatalf("RenderEnv: %v", err)
	}
	if !bytes.Contains(book, rendered) {
		t.Fatalf("the pass and LoadMergedHost disagree:\npass:\n%s\nLoadMergedHost:\n%s", book, rendered)
	}
}

// The cost. One listing per app, a Get for the instance that has an
// overlay, and NOTHING for the instance that does not.
func TestSyncCostsOneListingPerAppAndOneGetPerOverlay(t *testing.T) {
	ctx, base, reg, envDir, priv := syncInstanceKit(t)
	if err := SetEnv(ctx, base, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, base, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	unitRoute(t, reg, "cart.example.com", "titogo", "bab", "")
	unitRoute(t, reg, "desk.example.com", "titogo", "bab", "")

	s := newCountingStore(base)
	if err := Sync(ctx, reg, s, priv, envDir, func(string) error { return nil }, nil); err != nil {
		t.Fatalf("Sync: %v", err)
	}
	if got := s.n(s.lists, InstancesConfigPrefix("bab", "titogo")); got != 1 {
		t.Fatalf("instance overlay listings = %d, want exactly 1 for three routes of one app", got)
	}
	if got := s.n(s.gets, InstanceConfigKey("bab", "titogo", "book.example.com")); got != 1 {
		t.Fatalf("overlay Gets for book = %d, want 1", got)
	}
	for _, host := range []string{"cart.example.com", "desk.example.com"} {
		if got := s.n(s.gets, InstanceConfigKey("bab", "titogo", host)); got != 0 {
			t.Fatalf("overlay Gets for %s = %d, want none: the listing already said it has no overlay", host, got)
		}
	}
}

// A listing that fails is not a determination. The route keeps its file and
// the pass says so, the same contract a bundle fetch failure already has.
func TestSyncLeavesTheFileAloneWhenTheOverlayListingFails(t *testing.T) {
	ctx, base, reg, envDir, priv := syncInstanceKit(t)
	if err := SetEnv(ctx, base, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	path := filepath.Join(envDir, "book.example.com.env")
	if err := os.WriteFile(path, []byte("APP_WIDE=stale\n"), 0o600); err != nil {
		t.Fatalf("stage file: %v", err)
	}
	failing := &failingListStore{Store: base, failPrefix: InstancesConfigPrefix("bab", "titogo"),
		err: errors.New("s3: transient 503 SlowDown")}
	out := captureStdout(t, func() {
		if err := Sync(ctx, reg, failing, priv, envDir, func(string) error { return nil }, nil); err != nil {
			t.Fatalf("Sync: %v", err)
		}
	})
	cur, err := os.ReadFile(path)
	if err != nil || string(cur) != "APP_WIDE=stale\n" {
		t.Fatalf("the file moved on a listing failure: %q %v", cur, err)
	}
	if !strings.Contains(out, "book.example.com") || !strings.Contains(out, "left as-is") {
		t.Fatalf("the pass was silent about an unresolved instance layer:\n%s", out)
	}
}

// failingListStore makes List fail for exactly one prefix, the listing
// shape of failingKeyStore (syncexec_test.go).
type failingListStore struct {
	release.Store
	failPrefix string
	err        error
}

func (f *failingListStore) List(ctx context.Context, prefix string) ([]string, error) {
	if prefix == f.failPrefix {
		return nil, f.err
	}
	return f.Store.List(ctx, prefix)
}

Add "errors" and "strings" to instances_test.go's imports.

  • [ ] Step 2: Run it to verify it fails
go test -run 'TestSyncRendersTheInstanceOverlay|TestSyncCostsOneListing|TestSyncLeavesTheFileAlone' ./internal/appconfig/

Expected: TestSyncRendersTheInstanceOverlayForItsOwnRouteOnly fails on TENANT=none in the overlaid route's file (the overlay is not applied yet), after a build failure for failingListStore's first use is resolved by the block above.

  • [ ] Step 3: Write the code

internal/appconfig/instances.go (append):

// instanceOverlayLoader is one pass's instance-layer reader, shared by both
// sweeps so neither can drift from the other about what a host gets or what
// it costs. One List per (account, app), cached for the pass including its
// failure, then a Get only for a host that listing named.
//
// A listing FAILURE is returned to the caller, never folded into "no
// overlay". The two sweeps treat it the way they treat a bundle fetch
// failure: leave the file exactly as it is, say so per route, retry next
// pass. Folding it away would silently deliver the app-wide merge to an
// instance that is meant to have its own credential, which is the one
// outcome this layer exists to prevent.
//
// A host the listing named but whose object is gone by the time of the Get
// is simply "no overlay": a delete racing the pass converges the route back
// to its group's merge, which is what the delete asked for.
func instanceOverlayLoader(ctx context.Context, s release.Store) func(acct, app, host string) (*Manifest, string, error) {
	type appKey struct{ acct, app string }
	type indexRes struct {
		hosts map[string]bool
		err   error
	}
	index := map[appKey]indexRes{}
	return func(acct, app, host string) (*Manifest, string, error) {
		ak := appKey{acct, app}
		idx, done := index[ak]
		if !done {
			hosts, err := instanceOverlays(ctx, s, acct, app)
			if err != nil {
				err = fmt.Errorf("%s/%s: instance config listing failed: %w", acct, app, err)
			}
			idx = indexRes{hosts: hosts, err: err}
			index[ak] = idx
		}
		if idx.err != nil {
			return nil, "", idx.err
		}
		if !idx.hosts[host] {
			return nil, "", nil
		}
		m, etag, found, err := LoadInstanceConfig(ctx, s, acct, app, host)
		if err != nil {
			return nil, "", fmt.Errorf("%s/%s: instance config for %s: %w", acct, app, host, err)
		}
		if !found {
			return nil, "", nil
		}
		return m, etag, nil
	}
}

internal/appconfig/sync.go, in Sync: build the loader once, just before the materialization loop (after the resolution loop ends at :142):

	// The instance layer (senders design §6.2), resolved per route AFTER the
	// group base above. The group is still (Account, App, Environment) and
	// the default bundle is still read once per app: only the third layer is
	// per host, and it costs one listing per app plus a Get for each
	// instance that actually has an overlay.
	overlayFor := instanceOverlayLoader(ctx, s)
	// One line per app when its listing fails, not one per route: every
	// route of that app fails identically and for the same reason.
	said := map[groupKey]bool{}

Then in the materialization loop (:143-176), name the group key once and apply the overlay between the m == nil skip and the backing check:

	for _, rt := range routes {
		if !syncable(rt) {
			continue
		}
		k := groupKey{release.RouteAccount(rt), rt.App, rt.Environment}
		r := resolved[k]
		if r.err != nil {
			env := rt.Environment
			if env == "" {
				env = "default"
			}
			fmt.Printf("  %s: config unresolved (environment %q), env file left as-is: %v\n", rt.Host, env, r.err)
			continue
		}
		m := r.m
		if m == nil {
			continue
		}
		// The instance overlay wins per key over the group's merge. An
		// unresolved overlay is NOT an absent one: this route keeps its file
		// and the pass says which route and why, exactly as it does for a
		// group whose bundle could not be read.
		inst, _, err := overlayFor(k.acct, k.app, rt.Host)
		if err != nil {
			// Said ONCE per (account, app), not once per route. The listing
			// this failed on is per app, so every route of that app fails
			// identically and for the same reason; a line each would bury the
			// cause under its own repetitions on a box with many routes. The
			// loader already holds the error, so said is only about output.
			if !said[k] {
				said[k] = true
				fmt.Printf("  %s/%s: instance config unresolved, env files left as-is: %v\n", k.acct, k.app, err)
			}
			continue
		}
		m = applyInstanceOverlay(k.app, m, inst)
		if rt.Backing != registry.BackingUnit {
			fmt.Printf("  %s: exec-backed — config delivered by the agent's in-process loop to /data/<host>.config.env, not this pass\n", rt.Host)
			continue
		}
		syncHost(rt, m, r.defaultMissing, priv, envDir, restart, ack)
	}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/instances.go internal/appconfig/instances_test.go internal/appconfig/sync.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the unit sync pass applies each route's instance overlay (plan 4, task 4)"

---

Task 5: The lostKeys / defaultMissing guard, revisited for an overlay alone

Files:

  • Modify: internal/appconfig/sync.go (lostKeys at :260, syncHost's guard at :388-407, the materialization loop from task 4), internal/appconfig/instances_test.go

Interfaces:

  • Consumes: lostKeys (sync.go:260), agentOwned (agentvars.go).
  • Produces: no new exported surface. syncHost's defaultMissing parameter keeps its name and gains a second way to be true.

Why this is a guard change and not a merge change. sync.go:388 refuses to overwrite a hand-staged /etc/carlos/apps/<host>.env when the merged view is an overlay ALONE, because the app has no config.json and the render is a wholesale replacement rather than an overlay (2026-08-02 review, CRITICAL 1). Before this plan there was exactly one way into that state: a route bound to an environment. The instance layer adds a second, and it does not go through a binding at all — an UNBOUND route of an app with no default bundle used to resolve to "never configured" and have its file left alone (sync.go:120-123), and with an overlay it now resolves to something. Without this task, that route's hand-staged file would be replaced by the overlay's vars alone, as root, and the unit restarted.

  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// The CRITICAL 1 guard's second door. An app with no config.json, a route
// bound to nothing, and an overlay: the merged view is that overlay ALONE,
// which is a wholesale replacement of whatever a hand-staged file holds.
// Refuse it, loudly, every pass, and name the way out.
func TestSyncRefusesToDropHandStagedKeysForAnOverlayAlone(t *testing.T) {
	ctx, s, reg, envDir, priv := syncInstanceKit(t)
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	path := filepath.Join(envDir, "book.example.com.env")
	staged := "HAND_STAGED=1\nTENANT=old\n"
	if err := os.WriteFile(path, []byte(staged), 0o600); err != nil {
		t.Fatalf("stage file: %v", err)
	}

	var restarted []string
	out := captureStdout(t, func() {
		if err := Sync(ctx, reg, s, priv, envDir, func(h string) error {
			restarted = append(restarted, h)
			return nil
		}, nil); err != nil {
			t.Fatalf("Sync: %v", err)
		}
	})
	cur, err := os.ReadFile(path)
	if err != nil || string(cur) != staged {
		t.Fatalf("a hand-staged file was replaced by an overlay-alone render: %q %v", cur, err)
	}
	if len(restarted) != 0 {
		t.Fatalf("restarted %v for a route this pass refused", restarted)
	}
	if !strings.Contains(out, "HAND_STAGED") || !strings.Contains(out, "book.example.com") {
		t.Fatalf("the refusal does not name the route and the key it would have dropped:\n%s", out)
	}
}

// The same shape, self-healing: nothing is lost, so the route converges.
// An app with no default bundle and an instance overlay is a legitimate
// state (step 5 creates exactly that for a fresh instance), and this guard
// must not freeze it forever.
func TestSyncConvergesAnOverlayAloneWhenNothingIsLost(t *testing.T) {
	ctx, s, reg, envDir, priv := syncInstanceKit(t)
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	if err := Sync(ctx, reg, s, priv, envDir, func(string) error { return nil }, nil); err != nil {
		t.Fatalf("Sync: %v", err)
	}
	cur, err := os.ReadFile(filepath.Join(envDir, "book.example.com.env"))
	if err != nil {
		t.Fatalf("read env file: %v", err)
	}
	if !bytes.Contains(cur, []byte("TENANT=book\n")) {
		t.Fatalf("the overlay-alone render did not land:\n%s", cur)
	}
}

// And the case that must NOT change: no default bundle, no overlay, no
// binding is still "never configured at all", and a never-configured app's
// hand-staged file is not this pass's to touch.
func TestSyncStillLeavesUnconfiguredAppsAlone(t *testing.T) {
	ctx, s, reg, envDir, priv := syncInstanceKit(t)
	unitRoute(t, reg, "book.example.com", "titogo", "bab", "")
	path := filepath.Join(envDir, "book.example.com.env")
	staged := "HAND_STAGED=1\n"
	if err := os.WriteFile(path, []byte(staged), 0o600); err != nil {
		t.Fatalf("stage file: %v", err)
	}
	if err := Sync(ctx, reg, s, priv, envDir, func(string) error { return nil }, nil); err != nil {
		t.Fatalf("Sync: %v", err)
	}
	cur, err := os.ReadFile(path)
	if err != nil || string(cur) != staged {
		t.Fatalf("an unconfigured app's file moved: %q %v", cur, err)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestSyncRefusesToDropHandStaged|TestSyncConvergesAnOverlayAlone|TestSyncStillLeavesUnconfigured' ./internal/appconfig/

Expected: TestSyncRefusesToDropHandStagedKeysForAnOverlayAlone fails — the file has been replaced by TENANT=book — and TestSyncConvergesAnOverlayAloneWhenNothingIsLost fails because the route resolves as never configured today.

  • [ ] Step 3: Write the code

internal/appconfig/sync.go, the materialization loop from task 4: fetch the overlay BEFORE the m == nil skip, and let an overlay stand alone.

		// The instance overlay wins per key over the group's merge. An
		// unresolved overlay is NOT an absent one: this route keeps its file
		// and the pass says which route and why, exactly as it does for a
		// group whose bundle could not be read.
		inst, _, err := overlayFor(k.acct, k.app, rt.Host)
		if err != nil {
			if !said[k] {
				said[k] = true
				fmt.Printf("  %s/%s: instance config unresolved, env files left as-is: %v\n", k.acct, k.app, err)
			}
			continue
		}
		m := r.m
		if m == nil && inst == nil {
			continue // never configured at all: this file is not ours to touch
		}
		// An overlay with no group base behind it is a wholesale
		// replacement, not an overlay — the same state a bound route with no
		// default bundle is in, reached without a binding. It gets the same
		// guard: see syncHost.
		defaultMissing := r.defaultMissing
		if m == nil {
			defaultMissing = true
		}
		m = applyInstanceOverlay(k.app, m, inst)

The syncHost call further down the same loop takes the local rather than the resolution's field, or this compiles to "declared and not used" and the guard never sees the new case:

		syncHost(rt, m, defaultMissing, priv, envDir, restart, instETag, ack)

internal/appconfig/sync.go, syncHost's guard (:388-407): the condition is unchanged, the message names which overlay it is.

	if defaultMissing && existed && len(cur) > 0 {
		lost, parsed := lostKeys(cur, m)
		// Which overlay is standing alone. A bound route names its
		// environment; an unbound one is here because this instance has its
		// own config layer (senders design §6.2) and its app has no default
		// bundle, and naming an empty environment would be a riddle.
		alone := "this instance's own config layer"
		if rt.Environment != "" {
			alone = fmt.Sprintf("environment %q", rt.Environment)
		}
		switch {
		case !parsed:
			// Unparseable content has unknown keys, so nothing here can prove
			// the render drops none of them. Assume the worst — this guard's
			// whole job is to be wrong in the safe direction.
			fmt.Printf("  %s: %s has no default config bundle, so the merged view is %s ALONE — "+
				"refusing to replace %s, which this pass cannot parse as KEY=value and so cannot prove is safe to overwrite; "+
				"import its config (`carlos env set --app %s KEY=value ...`) or remove the file\n",
				rt.Host, rt.App, alone, path, rt.App)
			return
		case len(lost) > 0:
			fmt.Printf("  %s: %s has no default config bundle, so the merged view is %s ALONE — "+
				"refusing to drop %d var(s) already in %s (%s); import them (`carlos env set --app %s KEY=value ...`) "+
				"and this route converges on the next pass, or remove the file if it is dead\n",
				rt.Host, rt.App, alone, len(lost), path, strings.Join(lost, ", "), rt.App)
			return
		}
	}

internal/appconfig/sync.go, lostKeys's doc comment (:241-259) gains the third layer, and resolution.defaultMissing's (:66-70) gains the second door:

		// defaultMissing marks a bound route's merged view as the named
		// bundle ALONE, because the app has no config.json at all. Such a
		// render is not an overlay, it is a wholesale replacement, so
		// syncHost refuses to let it drop keys the file on disk already holds
		// (see its doc comment; 2026-08-02 review, CRITICAL 1 and round 2).
		// The materialization loop sets the same flag for an UNBOUND route
		// whose only config is its instance overlay (senders design §6.2),
		// which is the same replacement reached by a different door.
		defaultMissing bool

and, on lostKeys:

// It compares against m.Vars rather than the rendered bytes on purpose: the
// 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). m is the route's FULL merge, all
// three layers of it (default, environment, instance), because the question
// is what the file would lose, not which bundle a key came from.
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/sync.go internal/appconfig/instances_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the key-loss guard covers an instance overlay standing alone (plan 4, task 5)"

---

Task 6: SyncExec applies the overlay per route

Files:

  • Modify: internal/appconfig/syncexec.go (the materialization loop at :382-470), internal/appconfig/instances_test.go

Interfaces:

  • Consumes: instanceOverlayLoader, applyInstanceOverlay (tasks 3 and 4), SyncExec's existing group and opt-in caches (syncexec.go:121-268).
  • Produces: no new exported surface.
  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// The exec half of the same delivery: one activator-spawned instance gets
// its own value, its sibling does not, and the listing is paid for once.
func TestSyncExecRendersTheInstanceOverlayForItsOwnRouteOnly(t *testing.T) {
	ctx, base, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if err := SetEnv(ctx, base, "bab", "titogo", map[string]string{"APP_WIDE": "yes", "TENANT": "none"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, base, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	for _, host := range []string{"book.example.com", "cart.example.com"} {
		if err := reg.Upsert(registry.Route{
			Host: host, Kind: "instance", App: "titogo", Account: "bab", Status: "ready",
			Socket: "/run/carlos/" + host + ".sock",
		}); err != nil {
			t.Fatalf("Upsert %s: %v", host, err)
		}
	}

	s := newCountingStore(base)
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", nil); err != nil {
		t.Fatalf("SyncExec: %v", err)
	}
	book, err := os.ReadFile(filepath.Join(dd, "book.example.com.config.env"))
	if err != nil {
		t.Fatalf("read book config: %v", err)
	}
	if !bytes.Contains(book, []byte("TENANT=book\n")) || !bytes.Contains(book, []byte("APP_WIDE=yes\n")) {
		t.Fatalf("the overlaid route's config file is wrong:\n%s", book)
	}
	cart, err := os.ReadFile(filepath.Join(dd, "cart.example.com.config.env"))
	if err != nil {
		t.Fatalf("read cart config: %v", err)
	}
	if bytes.Contains(cart, []byte("book")) || !bytes.Contains(cart, []byte("TENANT=none\n")) {
		t.Fatalf("a sibling saw another instance's config:\n%s", cart)
	}
	if got := s.n(s.lists, InstancesConfigPrefix("bab", "titogo")); got != 1 {
		t.Fatalf("instance overlay listings = %d, want exactly 1 for two routes of one app", got)
	}
	if got := s.n(s.gets, InstanceConfigKey("bab", "titogo", "cart.example.com")); got != 0 {
		t.Fatalf("read an overlay for a host the listing did not name (%d times)", got)
	}
}

// exec_delivery is a BUNDLE-level flag and the instance layer is not one of
// the bundles it is read from (the ruling at appconfig.go:66-71). A TCP-addr
// route whose app has never opted in gets no file, whatever its overlay
// says — a per-host delivery flag is exactly the granularity that ruling
// refused, and honouring one here would be the config-that-lies hazard it
// names.
func TestSyncExecNeverReadsExecDeliveryFromTheInstanceLayer(t *testing.T) {
	ctx, s, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	putInstanceRaw(t, ctx, s, "bab", "titogo", "router.example.com", &Manifest{
		Schema: 1, App: "titogo", Vars: map[string]Var{"TENANT": {Kind: "env", Value: "book"}},
		ExecDelivery: ExecDeliveryFile})
	if err := reg.Upsert(registry.Route{
		Host: "router.example.com", Kind: "instance", App: "titogo", Account: "bab",
		Status: "ready", Addr: "127.0.0.1:8080",
	}); err != nil {
		t.Fatalf("Upsert: %v", err)
	}
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", nil); err != nil {
		t.Fatalf("SyncExec: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dd, "router.example.com.config.env")); !os.IsNotExist(err) {
		t.Fatalf("an instance overlay opted a route into file delivery: %v", err)
	}

	// Opt the APP in, and the same route gets the same overlay merged in the
	// ordinary way — the flag's door is the bundle, the overlay's payload is
	// vars.
	if err := SetExecDelivery(ctx, s, "bab", "titogo", ExecDeliveryFile); err != nil {
		t.Fatalf("SetExecDelivery: %v", err)
	}
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", nil); err != nil {
		t.Fatalf("SyncExec (opted in): %v", err)
	}
	cur, err := os.ReadFile(filepath.Join(dd, "router.example.com.config.env"))
	if err != nil {
		t.Fatalf("read router config: %v", err)
	}
	if !bytes.Contains(cur, []byte("TENANT=book\n")) {
		t.Fatalf("the overlay's vars did not land once the app opted in:\n%s", cur)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestSyncExecRendersTheInstanceOverlay|TestSyncExecNeverReadsExecDelivery' ./internal/appconfig/

Expected: TestSyncExecRendersTheInstanceOverlayForItsOwnRouteOnly fails with TENANT=none in the overlaid route's file.

  • [ ] Step 3: Write the code

internal/appconfig/syncexec.go: build the loader once, just after the mass-removal guard's skipRemovals block (:380) and before the materialization loop:

	// The instance layer (senders design §6.2), resolved per route after the
	// group base. Same loader, same cost, same failure contract as the unit
	// pass's: one listing per app, a Get only for a host it named, and an
	// unresolved overlay leaves the file exactly as it is.
	overlayFor := instanceOverlayLoader(ctx, s)
	// One line per app when its listing fails, not one per route: every
	// route of that app fails identically and for the same reason.
	said := map[groupKey]bool{}

Then, inside the materialization loop, fetch it immediately after the r.err branch (:397-407) — BEFORE any branch that can remove a file — and apply it at the call:

		// Fetched here, used at the bottom, because a failure must be able to
		// stop a REMOVAL too: deciding a file is orphaned while unable to see
		// whether this instance has an overlay would delete a live tenant's
		// delivered secrets on a listing blip.
		inst, _, instErr := overlayFor(k.acct, k.app, rt.Host)
		if instErr != nil {
			if !said[k] {
				said[k] = true
				fmt.Printf("  %s/%s: instance config unresolved, config files left as-is: %v\n", k.acct, k.app, instErr)
			}
			continue
		}
		if r.m == nil && skipRemovals {
			continue
		}

and, at the call site (:469):

		// The group's merge, overlaid by this instance's own layer. A nil
		// base stays nil: an absent manifest is a REMOVAL on this path, and
		// task 7 is the one case where an overlay changes that.
		m := r.m
		if m != nil {
			m = applyInstanceOverlay(k.app, m, inst)
		}
		syncExecHost(rt, m, Key(k.acct, k.app), priv, dataDir, hostBounce, header, ack)
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/syncexec.go internal/appconfig/instances_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the exec sync pass applies each route's instance overlay (plan 4, task 6)"

---

Task 7: An overlay is not an absence, and the mass-removal guard says so

Files:

  • Modify: internal/appconfig/syncexec.go (the guard at :341-380, the removal branches at :408-424, the call site), internal/appconfig/instances_test.go

Interfaces:

  • Consumes: skipRemovals (syncexec.go:375), release.HostAccountLabel (internal/release/qualified.go:188).
  • Produces: no new exported surface.

What this task decides, and why the guard's counting does not move. SyncExec owns the *.config.env namespace, so an ABSENT manifest removes the file (syncexec.go:954-963). "Absent" was a group fact — no config.json, and no bound bundle — and with a third layer it is not: an instance with an overlay and an app with no default bundle is a configured route, and removing its file would strip the very credential this layer exists to deliver. So the per-route removal branches consult the overlay, while the mass-removal guard's COUNT stays exactly where it is, over group bases. That is deliberate: the guard exists for a registry-wide account mangle, where every key of every app 404s at once — the overlay listing included, since it hangs off the same mangled prefix — so counting overlays would not make it more sensitive, and a route with an overlay never reaches a removal branch anyway.

  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// An app with no default bundle at all, and one instance with an overlay.
// The overlay is the whole configuration of that route, so its file is
// written, not removed — while its sibling, which has nothing anywhere, is
// reaped exactly as it is today.
func TestSyncExecConvergesAnOverlayAloneInsteadOfRemoving(t *testing.T) {
	ctx, s, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	for _, host := range []string{"book.example.com", "cart.example.com"} {
		if err := reg.Upsert(registry.Route{
			Host: host, Kind: "instance", App: "titogo", Account: "bab", Status: "ready",
			Socket: "/run/carlos/" + host + ".sock",
		}); err != nil {
			t.Fatalf("Upsert %s: %v", host, err)
		}
		if err := os.WriteFile(filepath.Join(dd, host+".config.env"), []byte("OLD=1\n"), 0o600); err != nil {
			t.Fatalf("stage %s: %v", host, err)
		}
	}
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", nil); err != nil {
		t.Fatalf("SyncExec: %v", err)
	}
	book, err := os.ReadFile(filepath.Join(dd, "book.example.com.config.env"))
	if err != nil {
		t.Fatalf("the overlaid route's file was removed: %v", err)
	}
	if !bytes.Contains(book, []byte("TENANT=book\n")) || bytes.Contains(book, []byte("OLD=1")) {
		t.Fatalf("the overlay-alone render did not converge:\n%s", book)
	}
	if _, err := os.Stat(filepath.Join(dd, "cart.example.com.config.env")); !os.IsNotExist(err) {
		t.Fatalf("a route with no config anywhere kept its file: %v", err)
	}
}

// The mass-removal guard, with an overlay in the middle of it. Two apps
// whose manifests all read absent still freeze every removal on the box,
// and the one route that has an overlay still converges: writing a file a
// bundle asked for is not a removal, and the guard exists to stop
// deletions, not delivery.
func TestSyncExecMassRemovalGuardHoldsRemovalsButNotOverlays(t *testing.T) {
	ctx, s, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	routes := map[string]string{
		"book.example.com": "titogo",
		"cart.example.com": "titogo",
		"desk.example.com": "eleven",
	}
	for host, app := range routes {
		if err := reg.Upsert(registry.Route{
			Host: host, Kind: "instance", App: app, Account: "bab", Status: "ready",
			Socket: "/run/carlos/" + host + ".sock",
		}); err != nil {
			t.Fatalf("Upsert %s: %v", host, err)
		}
		if err := os.WriteFile(filepath.Join(dd, host+".config.env"), []byte("OLD=1\n"), 0o600); err != nil {
			t.Fatalf("stage %s: %v", host, err)
		}
	}
	out := captureStdout(t, func() {
		if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", nil); err != nil {
			t.Fatalf("SyncExec: %v", err)
		}
	})
	if !strings.Contains(out, "refusing to remove any config files this pass") {
		t.Fatalf("the mass-removal guard did not fire:\n%s", out)
	}
	for _, host := range []string{"cart.example.com", "desk.example.com"} {
		cur, err := os.ReadFile(filepath.Join(dd, host+".config.env"))
		if err != nil || string(cur) != "OLD=1\n" {
			t.Fatalf("%s lost its file while the guard was up: %q %v", host, cur, err)
		}
	}
	book, err := os.ReadFile(filepath.Join(dd, "book.example.com.config.env"))
	if err != nil {
		t.Fatalf("the overlaid route was frozen by a guard about removals: %v", err)
	}
	if !bytes.Contains(book, []byte("TENANT=book\n")) {
		t.Fatalf("the overlay did not converge under the guard:\n%s", book)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestSyncExecConvergesAnOverlayAlone|TestSyncExecMassRemovalGuard' ./internal/appconfig/

Expected: both fail — book.example.com.config.env is removed in the first and left at OLD=1 in the second, because an absent base still decides the route on its own.

  • [ ] Step 3: Write the code

internal/appconfig/syncexec.go, the two removal branches (:408-424) consult the overlay:

		// An overlay is configuration, so a route that has one is never
		// "absent" however its group base resolved. Both branches below decide
		// REMOVAL, and neither may fire for a route whose own layer is
		// standing right there.
		if r.m == nil && inst == nil && skipRemovals {
			continue
		}
		// The #344 guard: an absent manifest removes the config file ONLY
		// when the account it was looked up under agrees with the account
		// the host itself names. A platform host's <sqid> label is not a
		// moving part; the row's account column is (the domains reconcile
		// stamps it from bucket listings, and a mis-stamp deleted a live
		// tenant's delivered secrets on 2026-08-28). Held, loudly, like an
		// unresolved group — the fix is the row's account, not this file.
		if r.m == nil && inst == nil {
			if sq := release.HostAccountLabel(rt.Host, appsDomain); sq != "" && sq != k.acct {
				fmt.Printf("  %s: no config manifest under account %q, but the host names account %q — config file left as-is; fix the route row's account column (#344)\n",
					rt.Host, k.acct, sq)
				continue
			}
		}

and the call site takes the overlay even with no base:

		// The group's merge, overlaid by this instance's own layer. A nil
		// base with an overlay is the overlay ALONE, which is a real
		// configuration and not the absence that drives a removal; nil and
		// nil together stay nil, which still removes the file.
		m := r.m
		if m != nil || inst != nil {
			m = applyInstanceOverlay(k.app, m, inst)
		}
		syncExecHost(rt, m, Key(k.acct, k.app), priv, dataDir, hostBounce, header, ack)

internal/appconfig/syncexec.go, the mass-removal guard's comment (:341-364) gains the paragraph that says why its arithmetic did not move:

	// The instance layer does not enter this count, and does not need to.
	// The fault this guard exists for is registry-wide — a mangled account
	// column makes EVERY key of every app 404 at once, the overlay listing
	// included, since it hangs off the same mangled prefix — so an overlay
	// could not survive the fault it is counting. A route that does have an
	// overlay never reaches a removal branch at all (see below), so it is
	// neither protected nor endangered by this number.
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/syncexec.go internal/appconfig/instances_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: an instance overlay is configuration, so it is never a removal (plan 4, task 7)"

---

Task 8: AwaitingRestart and allShadowed, revisited for a third layer

Files:

  • Modify: internal/appconfig/syncexec.go (AwaitingRestart at :771, allShadowed at :872), internal/appconfig/awaitingrestart_test.go

Interfaces:

  • Consumes: instanceOverlayLoader (task 4), resolveExecDelivery (syncexec.go:688), execAddrCandidate (syncexec.go:574).
  • Produces: ``go func allShadowed(keys []string, above ...*Manifest) bool // every key stated by at least one layer above the written one ` AwaitingRestart keeps its signature exactly: (ctx, s, routes, acct, app, envName, keys) RestartPending`.

On exported surface that arrives before its caller. This plan lands three readers whose only callers are step 5's: LoadMergedHost (task 3), and LatestConfigAck with InstanceConfigDelivery (task 10). That is deliberate — they are the decidable half of a question step 5 has to answer at a door, and proving an etag comparison means what it says belongs with the layer, not with the door. The ruling below is narrower than it reads: it is about inventing a NEW question nothing has asked yet, not about landing the answer to one step 5 is already known to ask.

Reporting an INSTANCE-layer write is step 5's, and that is a ruling. AwaitingRestart answers "a write to app's envName bundle landed a file nothing will read": its whole vocabulary is (app, environment), and the CLI and console callers (appconfig_cli.go:83, internal/console/apiconfig.go:544) pass exactly what a member just wrote. Nothing in this plan writes an instance overlay, so there is no call site that would need the instance-layer question answered, and inventing one now would mean an exported function with no caller and no test but its own. What this task owes is the other direction, which is live the moment task 6 shipped: a write to the default or the named bundle can now be SHADOWED by an instance overlay, and naming such a route would send someone to restart a process for a value it was never going to see. Step 5 adds the instance-write reporting alongside the door that does the writing.

  • [ ] Step 1: Write the failing test

internal/appconfig/awaitingrestart_test.go (append; add context, os, strings and github.com/carlosframework/platform/internal/release to the imports as needed):

// The third layer shadows a default-bundle write the same way a named
// bundle does, and for the same reason: the merged manifest does not move,
// so syncExecHost's bytes.Equal writes nothing and there is no file for
// anyone to restart over.
func TestAwaitingRestartInstanceOverlayShadowsADefaultWrite(t *testing.T) {
	ctx, s, _, _, _, _ := execKit(t)
	if err := SetExecDelivery(ctx, s, "bab", "relay", ExecDeliveryFile); err != nil {
		t.Fatalf("SetExecDelivery: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "relay", "connect.example.com",
		map[string]Var{"OWN_KEY": {Kind: "env", Value: "instance"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	routes := []registry.Route{addrRoute("connect.example.com", "relay", "bab", "")}

	if got := AwaitingRestart(ctx, s, routes, "bab", "relay", "", []string{"OWN_KEY"}); !got.Empty() {
		t.Fatalf("got %+v, want nothing: OWN_KEY is shadowed by this instance's own layer", got)
	}
	got := AwaitingRestart(ctx, s, routes, "bab", "relay", "", []string{"OTHER_KEY"})
	if len(got.Hosts) != 1 || got.Hosts[0].Host != "connect.example.com" {
		t.Fatalf("Hosts = %+v, want the route for a key its overlay does not state", got.Hosts)
	}
	got = AwaitingRestart(ctx, s, routes, "bab", "relay", "", []string{"OWN_KEY", "OTHER_KEY"})
	if len(got.Hosts) != 1 {
		t.Fatalf("Hosts = %+v, want the route when any key is unshadowed", got.Hosts)
	}
}

// The case that did not exist before the third layer: a write to the
// route's OWN bound bundle used to be unshadowable, because nothing sat
// above it. Something does now.
func TestAwaitingRestartInstanceOverlayShadowsANamedBundleWrite(t *testing.T) {
	ctx, s, _, _, _, _ := execKit(t)
	if err := SetExecDelivery(ctx, s, "bab", "relay", ExecDeliveryFile); err != nil {
		t.Fatalf("SetExecDelivery: %v", err)
	}
	if _, err := ApplyEnvironment(ctx, s, "bab", "relay", "prod",
		map[string]Var{"K": {Kind: "env", Value: "environment"}}, nil); err != nil {
		t.Fatalf("ApplyEnvironment: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "relay", "connect.example.com",
		map[string]Var{"K": {Kind: "env", Value: "instance"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	routes := []registry.Route{addrRoute("connect.example.com", "relay", "bab", "prod")}

	if got := AwaitingRestart(ctx, s, routes, "bab", "relay", "prod", []string{"K"}); !got.Empty() {
		t.Fatalf("got %+v, want nothing: K is shadowed by this instance's own layer", got)
	}
	if got := AwaitingRestart(ctx, s, routes, "bab", "relay", "prod", []string{"OTHER"}); len(got.Hosts) != 1 {
		t.Fatalf("Hosts = %+v, want the route for an unshadowed key", got.Hosts)
	}
}

// A read this function could not make is not a determination, on this layer
// as on the other two: the route is named as unresolved rather than
// reported either way.
func TestAwaitingRestartUnresolvedWhenTheOverlayCannotBeRead(t *testing.T) {
	ctx, s, _, _, _, _ := execKit(t)
	if err := SetExecDelivery(ctx, s, "bab", "relay", ExecDeliveryFile); err != nil {
		t.Fatalf("SetExecDelivery: %v", err)
	}
	if _, _, err := ApplyInstance(ctx, s, "bab", "relay", "connect.example.com",
		map[string]Var{"OWN_KEY": {Kind: "env", Value: "instance"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	blip := &failingKeyStore{Store: s, failKey: InstanceConfigKey("bab", "relay", "connect.example.com"),
		err: errors.New("s3: transient 503 SlowDown")}
	routes := []registry.Route{addrRoute("connect.example.com", "relay", "bab", "")}
	got := AwaitingRestart(ctx, blip, routes, "bab", "relay", "", []string{"OWN_KEY"})
	if len(got.Hosts) != 0 || len(got.Unresolved) != 1 || got.Unresolved[0] != "connect.example.com" {
		t.Fatalf("got %+v, want the host as unresolved and nothing claimed either way", got)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestAwaitingRestartInstanceOverlay|TestAwaitingRestartUnresolvedWhenTheOverlay' ./internal/appconfig/

Expected: all three fail — the route is named in every case, because nothing consults the third layer.

  • [ ] Step 3: Write the code

internal/appconfig/syncexec.go, AwaitingRestart (:771): one more loader beside the two caches, and the shadow test takes every layer above the bundle that was written.

	// The instance layer, through the same loader the sweeps use, so this
	// notice and the pass that writes the files agree about what sits above
	// a bundle. One listing for the app, then a Get per route that has an
	// overlay; both live for one call, like the caches above.
	overlayFor := instanceOverlayLoader(ctx, s)
	// One line per app when its listing fails, not one per route: every
	// route of that app fails identically and for the same reason.
	said := map[groupKey]bool{}

Inside the route loop, after the opt-in verdict's if !v.On { continue } (:841-843):

		// Which layers sit ABOVE the bundle that was written, for this
		// route. A write cannot change what a route resolves to for a key
		// some higher layer states, and the third layer sits above both of
		// the other two.
		inst, _, err := overlayFor(acct, app, rt.Host)
		if err != nil {
			out.Unresolved = append(out.Unresolved, rt.Host)
			continue
		}
		var above []*Manifest
		if envName == "" && rt.Environment != "" {
			above = append(above, eb.m)
		}
		above = append(above, inst)
		// The shadowing test, and the reason keys is a parameter: where every
		// touched key is shadowed, the merged manifest is byte-identical,
		// syncExecHost's bytes.Equal writes nothing, and naming the route
		// would send someone to restart a process for a value it was never
		// going to see.
		if len(keys) > 0 && allShadowed(keys, above...) {
			continue
		}
		out.Hosts = append(out.Hosts, RestartHost{Host: rt.Host, Unit: rt.Unit})

The old shadow test (:844-857) is replaced by the block above, including its comment: the "only for a default-bundle write reaching a BOUND route" carve-out is gone, because a named write can now be shadowed too.

internal/appconfig/syncexec.go, allShadowed (:868-882):

// allShadowed reports whether every one of keys is STATED by at least one
// of the layers above the bundle a write touched — the test that decides
// whether that write can have changed what a route actually resolves to. An
// unset key counts the same as a set one: removing a key a higher layer
// shadows changes the merge by nothing.
//
// above is variadic because how many layers sit over a write depends on
// which bundle was written: a default-bundle write to a bound route is
// under both the named bundle and the instance overlay, a named-bundle
// write is under the overlay alone, and a write to the overlay itself has
// nothing over it. A nil layer (no bundle of that kind for this route) is
// skipped rather than treated as stating nothing, so an empty list answers
// false and the route is named.
func allShadowed(keys []string, above ...*Manifest) bool {
	for _, k := range keys {
		stated := false
		for _, m := range above {
			if m == nil {
				continue
			}
			if _, ok := m.Vars[k]; ok {
				stated = true
				break
			}
		}
		if !stated {
			return false
		}
	}
	return true
}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1 && go test ./internal/console/ ./ -count=1

Expected: PASS, including TestAwaitingRestartDefaultWriteReachesBoundRoute and TestAPIConfigPutNamesRoutesAwaitingRestart unchanged.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/syncexec.go internal/appconfig/awaitingrestart_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the restart notice knows an instance overlay shadows a bundle write (plan 4, task 8)"

---

Task 9: ConfigAck says which agent wrote it and which overlay it rendered

Files:

  • Modify: internal/appconfig/configack.go (ConfigAck at :36-64), internal/appconfig/sync.go (syncHost at :343, its ack at :451), internal/appconfig/syncexec.go (syncExecHost at :948, its ack at :1046), internal/appconfig/configack_test.go
  • Modify, because they call the two functions this task re-signs and the package will not compile without them: internal/appconfig/sync_test.go (syncHost at :403, :1057, :1075) and internal/appconfig/syncexec_test.go (syncExecHost at :776). Each gets "" for the new instETag, in the position shown in the Produces block below: after restart for syncHost, after header for syncExecHost. These are four one-word edits, and skipping them makes Step 4's "Expected: PASS" impossible to reach.

Interfaces:

  • Produces: ``go const ConfigAckSchema = 1 type ConfigAck struct { ...; Schema int json:"schema"; InstanceConfigETag string json:"instanceconfigetag,omitempty" } func syncHost(rt registry.Route, m *Manifest, defaultMissing bool, priv *ecdh.PrivateKey, envDir string, restart func(host string) error, instETag string, ack func(ConfigAck)) func syncExecHost(rt registry.Route, m *Manifest, key string, priv *ecdh.PrivateKey, dataDir string, bounce func(host string) error, header, instETag string, ack func(ConfigAck)) ``
  • [ ] Step 1: Write the failing test

internal/appconfig/configack_test.go (append):

// The ack says which version of the instance layer the agent rendered
// (design §6.5). The console holds the etag its own write returned and
// compares the two, which is decidable in a way a map of delivered key
// names is not: an old agent and a new agent rendering an overlay that sets
// nothing look identical by key, and not at all by etag.
func TestConfigAckNamesTheInstanceOverlayItRendered(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	reg := testRegistry(t)
	envDir := t.TempDir()
	priv, _ := syncRelayTestKeys(t)

	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	_, want, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	for _, host := range []string{"book.example.com", "cart.example.com"} {
		if err := reg.Upsert(registry.Route{
			Host: host, Kind: "instance", App: "titogo", Account: "bab", Status: "ready",
			Backing: registry.BackingUnit,
		}); err != nil {
			t.Fatalf("Upsert %s: %v", host, err)
		}
	}
	acks := map[string]ConfigAck{}
	if err := Sync(ctx, reg, s, priv, envDir, func(string) error { return nil },
		func(a ConfigAck) { acks[a.Host] = a }); err != nil {
		t.Fatalf("Sync: %v", err)
	}
	book, ok := acks["book.example.com"]
	if !ok {
		t.Fatal("no ack for the overlaid route")
	}
	if book.Schema != ConfigAckSchema {
		t.Fatalf("Schema = %d, want %d — absence is how an older agent is recognised", book.Schema, ConfigAckSchema)
	}
	if book.InstanceConfigETag != want {
		t.Fatalf("InstanceConfigETag = %q, want the etag the write returned %q", book.InstanceConfigETag, want)
	}
	cart, ok := acks["cart.example.com"]
	if !ok {
		t.Fatal("no ack for the sibling")
	}
	if cart.Schema != ConfigAckSchema || cart.InstanceConfigETag != "" {
		t.Fatalf("sibling ack = %+v, want the schema and an empty etag: it has no overlay to render", cart)
	}
}

// The same statement from the exec half, which is the half an instance
// sender's box actually runs (design §6.4's delivery check).
func TestSyncExecConfigAckNamesTheInstanceOverlayItRendered(t *testing.T) {
	ctx, s, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	_, want, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}
	if err := reg.Upsert(registry.Route{
		Host: "book.example.com", Kind: "instance", App: "titogo", Account: "bab",
		Status: "ready", Socket: "/run/carlos/book.sock",
	}); err != nil {
		t.Fatalf("Upsert: %v", err)
	}
	var got []ConfigAck
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "",
		func(a ConfigAck) { got = append(got, a) }); err != nil {
		t.Fatalf("SyncExec: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("acks = %+v, want exactly one", got)
	}
	if got[0].Schema != ConfigAckSchema || got[0].InstanceConfigETag != want {
		t.Fatalf("ack = %+v, want schema %d and etag %q", got[0], ConfigAckSchema, want)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'ConfigAckNamesTheInstanceOverlay' ./internal/appconfig/

Expected: build failure, undefined: ConfigAckSchema.

  • [ ] Step 3: Write the code

internal/appconfig/configack.go, on ConfigAck (:36):

// ConfigAckSchema is the ack schema THIS binary writes. It exists so a
// reader can tell an agent that renders the instance config layer (senders
// design §6.2) from one that does not, and the only honest way to do that
// is a positive statement: an ack written by an older agent decodes with
// Schema 0, because the field was not there to write.
//
// It is not the manifest schema and is not tied to it. Bump it when an
// agent starts stating something a reader may need to depend on.
const ConfigAckSchema = 1

and, inside the struct:

	// Schema is ConfigAckSchema as written by the agent that produced this
	// ack, and 0 for every ack written before this field existed. A reader
	// asking "does this box render the instance config layer" reads this and
	// nothing else: absence of an ack, or an ack at schema 0, both mean the
	// question cannot be answered from the box's own statements yet.
	Schema int `json:"schema"`
	// InstanceConfigETag is the etag of the instance overlay
	// (apps/<acct>/<app>/instances-config/<host>.json) this pass rendered
	// into the file, empty when the route has no overlay at all. The writer
	// keeps the etag its own Put returned and compares the two
	// (InstanceConfigDelivery): equal is delivered, different is stale. A
	// map of delivered var names could not make that distinction — an agent
	// too old to read the layer and a new agent rendering an overlay whose
	// vars the reader does not recognise produce the same map.
	InstanceConfigETag string `json:"instance_config_etag,omitempty"`

internal/appconfig/sync.go: syncHost takes instETag string before ack, the materialization loop keeps the etag it already fetched (inst, instETag, err := overlayFor(...)) and passes it, and the ack at :451 becomes

	a := ConfigAck{Schema: ConfigAckSchema, Host: rt.Host, Shape: shapeOf(rt), At: time.Now(), InstanceConfigETag: instETag}

internal/appconfig/syncexec.go: syncExecHost takes instETag string after header, the materialization loop passes the etag from its own overlayFor call, and the ack at :1046 becomes

		a := ConfigAck{Schema: ConfigAckSchema, Host: rt.Host, Shape: shapeOf(rt), At: time.Now(), InstanceConfigETag: instETag}
  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ ./internal/console/ -count=1

Expected: PASS. The console's relayConfigAcks decodes the two new fields without being touched, since they are additive.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/configack.go internal/appconfig/sync.go internal/appconfig/syncexec.go internal/appconfig/configack_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: the config ack carries its schema and the instance overlay it rendered (plan 4, task 9)"

---

Task 10: Reading the ack: delivered, stale, or not yet delivered

Files:

  • Modify: internal/appconfig/configack.go (ConfigAckKey at :69), internal/appconfig/configack_test.go

Interfaces:

  • Produces: ``go const ConfigAckPrefix = "control/config-ack/" const ( InstanceConfigDelivered = "delivered" InstanceConfigStale = "stale" InstanceConfigUnknownAgent = "unknown-agent" InstanceConfigNotYetDelivered = "not-yet-delivered" ) func LatestConfigAck(ctx context.Context, s release.Store, host string) (*ConfigAck, error) func InstanceConfigDelivery(a *ConfigAck, want string) string ``

Who calls this, and when. Nothing in this plan. Step 5's instance enable refuses a box that cannot render the layer, and carlos email status --instance <host> prints delivered, stale or not yet delivered (design §6.4, §6.5); both are that step's doors. What lands here is the decidable part, with the four states named and tested, so step 5 spends its judgement on the refusal's wording rather than on re-deriving what an etag comparison means. The console's own configAckPrefix (internal/console/relayops.go:73) is the same string and is left alone: relayConfigAcks batches a whole app's routes through one listing and has no reason to change for this.

  • [ ] Step 1: Write the failing test

internal/appconfig/configack_test.go (append):

// The four answers, and they are four rather than two on purpose. "This
// agent is too old to render the layer" and "this agent rendered an older
// version of it" call for different sentences from whoever asks: one is a
// box to roll, the other is a pass to wait for.
func TestInstanceConfigDeliveryVerdicts(t *testing.T) {
	cases := []struct {
		name string
		ack  *ConfigAck
		want string
		got  string
	}{
		{"not yet acked", nil, "etag-1", InstanceConfigNotYetDelivered},
		{"agent older than the layer", &ConfigAck{Schema: 0, InstanceConfigETag: ""}, "etag-1", InstanceConfigUnknownAgent},
		{"rendered this overlay", &ConfigAck{Schema: ConfigAckSchema, InstanceConfigETag: "etag-1"}, "etag-1", InstanceConfigDelivered},
		{"rendered an older one", &ConfigAck{Schema: ConfigAckSchema, InstanceConfigETag: "etag-0"}, "etag-1", InstanceConfigStale},
		{"no overlay, and none expected", &ConfigAck{Schema: ConfigAckSchema}, "", InstanceConfigDelivered},
		{"no overlay, but one was written", &ConfigAck{Schema: ConfigAckSchema}, "etag-1", InstanceConfigStale},
	}
	for _, c := range cases {
		if got := InstanceConfigDelivery(c.ack, c.want); got != c.got {
			t.Errorf("%s: InstanceConfigDelivery = %q, want %q", c.name, got, c.got)
		}
	}
}

// One route can have acks from more than one box across a migration, so the
// reader takes the freshest and ignores every other host's.
func TestLatestConfigAckPicksTheFreshestForTheHost(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	older := ConfigAck{Schema: ConfigAckSchema, Box: "box-1", Host: "book.example.com", Shape: "unit",
		At: time.Date(2026, 9, 15, 10, 0, 0, 0, time.UTC), InstanceConfigETag: "etag-0"}
	newer := ConfigAck{Schema: ConfigAckSchema, Box: "box-2", Host: "book.example.com", Shape: "unit",
		At: time.Date(2026, 9, 15, 12, 0, 0, 0, time.UTC), InstanceConfigETag: "etag-1"}
	other := ConfigAck{Schema: ConfigAckSchema, Box: "box-2", Host: "cart.example.com", Shape: "unit",
		At: time.Date(2026, 9, 15, 13, 0, 0, 0, time.UTC), InstanceConfigETag: "etag-9"}
	for _, a := range []ConfigAck{older, newer, other} {
		if err := WriteConfigAck(ctx, s, a); err != nil {
			t.Fatalf("WriteConfigAck %s/%s: %v", a.Box, a.Host, err)
		}
	}
	got, err := LatestConfigAck(ctx, s, "book.example.com")
	if err != nil || got == nil {
		t.Fatalf("LatestConfigAck = %+v %v", got, err)
	}
	if got.Box != "box-2" || got.InstanceConfigETag != "etag-1" {
		t.Fatalf("LatestConfigAck = %+v, want box-2's etag-1", got)
	}
	if v := InstanceConfigDelivery(got, "etag-1"); v != InstanceConfigDelivered {
		t.Fatalf("delivery = %q, want %q", v, InstanceConfigDelivered)
	}

	none, err := LatestConfigAck(ctx, s, "desk.example.com")
	if err != nil || none != nil {
		t.Fatalf("LatestConfigAck for a host with no ack = %+v %v, want (nil, nil)", none, err)
	}
	if v := InstanceConfigDelivery(none, "etag-1"); v != InstanceConfigNotYetDelivered {
		t.Fatalf("delivery = %q, want %q", v, InstanceConfigNotYetDelivered)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestInstanceConfigDeliveryVerdicts|TestLatestConfigAck' ./internal/appconfig/

Expected: build failure, undefined: InstanceConfigDelivery.

  • [ ] Step 3: Write the code

internal/appconfig/configack.go, ConfigAckKey (:69) gains a named prefix, and the reader follows it:

// ConfigAckPrefix is where every route's config-adoption ack lives, one
// object per (box, route host).
const ConfigAckPrefix = "control/config-ack/"

// ConfigAckKey is where a route's config-adoption ack lives: additive, one
// object per (box, route host), rewritten (never appended to) on every sync
// pass, by WriteConfigAck, when what it delivers changes
// it did not.
func ConfigAckKey(box, host string) string {
	return ConfigAckPrefix + box + "/" + host + ".json"
}

// The four answers to "is this instance running the overlay I wrote", named
// because three of them are not "no" and a caller that flattened them would
// tell an operator to roll a box that is merely one pass behind.
const (
	// InstanceConfigDelivered: the agent rendered exactly this version of
	// the overlay. A route with no overlay, whose caller expects none, is
	// also delivered: there is nothing outstanding.
	InstanceConfigDelivered = "delivered"
	// InstanceConfigStale: the agent rendered a different version. One more
	// pass, or a generation that never got read, is the usual cause.
	InstanceConfigStale = "stale"
	// InstanceConfigUnknownAgent: this box's agent predates the instance
	// config layer, so its ack says nothing about it. Not "no" — the answer
	// cannot be had until the box is rolled.
	InstanceConfigUnknownAgent = "unknown-agent"
	// InstanceConfigNotYetDelivered: no ack for this host at all. Acks are
	// change-only, so this is the honest answer for a route whose file has
	// not moved since its agent rolled. It never means "old agent": that
	// question is the box record's agent version, which step 5 reads.
	InstanceConfigNotYetDelivered = "not-yet-delivered"
)

// InstanceConfigDelivery compares an ack with the etag a write returned and
// says which of the four states the route is in. want is the etag of the
// overlay the caller wrote, or "" when the caller wrote none.
//
// The comparison is on etags rather than on delivered var names because
// only etags are decidable: an agent too old to read the layer and a new
// agent rendering an overlay whose vars the reader does not recognise
// deliver the same set of names (design §6.5).
func InstanceConfigDelivery(a *ConfigAck, want string) string {
	switch {
	case a == nil:
		return InstanceConfigNotYetDelivered
	case a.Schema < ConfigAckSchema:
		return InstanceConfigUnknownAgent
	case a.InstanceConfigETag == want:
		return InstanceConfigDelivered
	default:
		return InstanceConfigStale
	}
}

// LatestConfigAck reads the freshest ack naming host, across every box that
// has written one, and nil (with no error) when nothing has. A route can
// legitimately have acks from two boxes at once across a migration, and the
// freshest is the one describing what is running now.
//
// It lists the whole ack prefix, which is the same limit relayConfigAcks
// accepts (internal/console/relayops.go): there is no per-host box list to
// narrow a listing by. The Gets are narrowed, though — only keys whose
// basename is this host are read.
func LatestConfigAck(ctx context.Context, s release.Store, host string) (*ConfigAck, error) {
	keys, err := s.List(ctx, ConfigAckPrefix)
	if err != nil {
		return nil, err
	}
	var best *ConfigAck
	for _, key := range keys {
		if !strings.HasSuffix(key, "/"+host+".json") {
			continue
		}
		body, _, err := s.Get(ctx, key)
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				continue
			}
			return nil, err
		}
		var a ConfigAck
		if err := json.Unmarshal(body, &a); err != nil {
			// One unreadable object must not hide the box that answered
			// properly, and there is nothing in it to report either way.
			log.Printf("config: config ack %s: %v", key, err)
			continue
		}
		if best == nil || a.At.After(best.At) {
			latest := a
			best = &latest
		}
	}
	return best, nil
}

Add "log" and "strings" to configack.go's imports.

  • [ ] Step 4: Run it to verify it passes
go test ./internal/appconfig/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/configack.go internal/appconfig/configack_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: read a route's ack and say delivered, stale or not yet delivered (plan 4, task 10)"

---

Task 11: The signal, and one pass end to end

Files:

  • Modify: internal/appconfig/appconfig.go (the package's bucket layout at :6-11), internal/appconfig/routefingerprint.go (ConfigRouteFingerprints's doc at :34-57), internal/appconfig/instances_test.go

Interfaces: none new. This task pins the two facts the whole layer rests on and walks one pass over a box that has every shape of it.

Why the fingerprint does not move, and must not. ConfigRouteFingerprints digests registry columns — host, account, app, environment (routefingerprint.go:67) — and the instance layer is a bucket object. It cannot see one being written and should not try: reading the layer per route per tick is exactly the cost the fast loop exists to avoid, the same reasoning the doc already gives for leaving exec_delivery out (:47-52). The generation object is the signal, which is why task 2 bumps it on every write and delete.

  • [ ] Step 1: Write the failing test

internal/appconfig/instances_test.go (append):

// The layer's only convergence signal is the generation object. The route
// fingerprint cannot see a bucket write and must not start paying to try:
// this pins both halves of that at once.
func TestWritingAnOverlayMovesTheGenerationAndNotTheFingerprint(t *testing.T) {
	ctx := context.Background()
	s := release.NewDirStore(t.TempDir())
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	routes := []registry.Route{{
		Host: "book.example.com", Kind: "instance", App: "titogo", Account: "bab",
		Status: "ready", Socket: "/run/carlos/book.sock",
	}}
	execBefore, unitBefore := ConfigRouteFingerprints(routes)
	_, genBefore, err := s.Get(ctx, GenKey("bab", "titogo"))
	if err != nil {
		t.Fatalf("read generation: %v", err)
	}

	if _, _, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil); err != nil {
		t.Fatalf("ApplyInstance: %v", err)
	}

	execAfter, unitAfter := ConfigRouteFingerprints(routes)
	if execAfter != execBefore || unitAfter != unitBefore {
		t.Fatal("the route fingerprint moved for a bucket-side write, which would cost a read per route per tick")
	}
	_, genAfter, err := s.Get(ctx, GenKey("bab", "titogo"))
	if err != nil {
		t.Fatalf("read generation: %v", err)
	}
	if genAfter == genBefore {
		t.Fatal("the generation did not move, so the fast loop has nothing to notice and delivery waits out the hourly floor")
	}
}

// One pass over a box holding every shape this layer comes in: an instance
// with an overlay over its app's bundle, a sibling with none, and an
// instance whose app has no bundle at all so its overlay stands alone.
// Then the overlay is removed and the route converges back, with the ack
// saying so.
func TestOnePassOverEveryInstanceLayerShape(t *testing.T) {
	ctx, s, reg, dd, privB64, _ := execKit(t)
	priv, err := ParsePrivateKey(privB64, "test")
	if err != nil {
		t.Fatalf("ParsePrivateKey: %v", err)
	}
	if err := SetEnv(ctx, s, "bab", "titogo", map[string]string{"APP_WIDE": "yes", "TENANT": "none"}); err != nil {
		t.Fatalf("SetEnv: %v", err)
	}
	_, bookETag, err := ApplyInstance(ctx, s, "bab", "titogo", "book.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "book"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance (book): %v", err)
	}
	_, deskETag, err := ApplyInstance(ctx, s, "bab", "solo", "desk.example.com",
		map[string]Var{"TENANT": {Kind: "env", Value: "desk"}}, nil)
	if err != nil {
		t.Fatalf("ApplyInstance (desk): %v", err)
	}
	for host, app := range map[string]string{
		"book.example.com": "titogo", "cart.example.com": "titogo", "desk.example.com": "solo"} {
		if err := reg.Upsert(registry.Route{
			Host: host, Kind: "instance", App: app, Account: "bab", Status: "ready",
			Socket: "/run/carlos/" + host + ".sock",
		}); err != nil {
			t.Fatalf("Upsert %s: %v", host, err)
		}
	}

	acks := map[string]ConfigAck{}
	collect := func(a ConfigAck) { acks[a.Host] = a }
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", collect); err != nil {
		t.Fatalf("SyncExec: %v", err)
	}
	read := func(host string) string {
		t.Helper()
		body, err := os.ReadFile(filepath.Join(dd, host+".config.env"))
		if err != nil {
			t.Fatalf("read %s: %v", host, err)
		}
		return string(body)
	}
	if got := read("book.example.com"); !strings.Contains(got, "TENANT=book\n") || !strings.Contains(got, "APP_WIDE=yes\n") {
		t.Fatalf("book: %q", got)
	}
	if got := read("cart.example.com"); !strings.Contains(got, "TENANT=none\n") {
		t.Fatalf("cart saw another instance's config: %q", got)
	}
	if got := read("desk.example.com"); got != "TENANT=desk\n" {
		t.Fatalf("desk, whose app has no bundle, should render its overlay alone: %q", got)
	}
	for host, want := range map[string]string{
		"book.example.com": bookETag, "cart.example.com": "", "desk.example.com": deskETag} {
		a, ok := acks[host]
		if !ok {
			t.Fatalf("no ack for %s", host)
		}
		if a.Schema != ConfigAckSchema || a.InstanceConfigETag != want {
			t.Fatalf("%s ack = %+v, want schema %d and etag %q", host, a, ConfigAckSchema, want)
		}
		if v := InstanceConfigDelivery(&a, want); v != InstanceConfigDelivered {
			t.Fatalf("%s delivery = %q, want %q", host, v, InstanceConfigDelivered)
		}
	}

	// Remove one overlay: that route converges back to its app's merge, and
	// its ack stops naming an overlay, which is what tells a reader the
	// removal actually reached the box.
	if err := DeleteInstanceConfig(ctx, s, "bab", "titogo", "book.example.com"); err != nil {
		t.Fatalf("DeleteInstanceConfig: %v", err)
	}
	acks = map[string]ConfigAck{}
	if err := SyncExec(ctx, reg, s, priv, dd, func(string) error { return nil }, "", collect); err != nil {
		t.Fatalf("SyncExec (after delete): %v", err)
	}
	if got := read("book.example.com"); !strings.Contains(got, "TENANT=none\n") {
		t.Fatalf("book did not converge back to its app's merge: %q", got)
	}
	if a := acks["book.example.com"]; a.InstanceConfigETag != "" || a.Schema != ConfigAckSchema {
		t.Fatalf("book ack after the delete = %+v, want the schema and no overlay", a)
	}
	if got := read("desk.example.com"); got != "TENANT=desk\n" {
		t.Fatalf("desk was disturbed by a sibling app's delete: %q", got)
	}
}
  • [ ] Step 2: Run it to verify it fails
go test -run 'TestWritingAnOverlayMovesTheGeneration|TestOnePassOverEveryInstanceLayerShape' ./internal/appconfig/

Expected: PASS, both of them, on the first run. Tasks 1 to 11 built everything these assert; this task exists to pin the composition and the two facts nothing else states, and a test that fails here means an earlier task's behaviour is not what that task claimed. If either fails, fix the earlier task rather than this test.

  • [ ] Step 3: Write the code

Documentation only. internal/appconfig/appconfig.go, the package doc's bucket layout (:6-11):

// Bucket layout (deployment bucket, account-scoped like every other app key
// internal/release writes):
//
//	apps/<acct>/<app>/config.json                     CAS-guarded config manifest
//	apps/<acct>/<app>/environments/<name>.json        named environment overlay
//	apps/<acct>/<app>/instances-config/<host>.json    one instance's overlay
//
// The three resolve default < environment < instance, per key, and only a
// caller that names a host gets the third (LoadMergedHost, instances.go).

internal/appconfig/routefingerprint.go, ConfigRouteFingerprints's doc (after the exec_delivery paragraph at :47-52):

// The INSTANCE CONFIG LAYER is deliberately not here either, and for the
// same reason with a sharper edge: apps/<acct>/<app>/instances-config/
// <host>.json is a bucket object, so seeing a change to one would cost a
// listing per app per tick at best and a read per route at worst. It does
// not need to be seen here — writing or deleting an overlay bumps the app's
// generation object (ApplyInstance, DeleteInstanceConfig), which this loop
// already watches, and that is the layer's whole signal.
  • [ ] Step 4: Run the whole package
go test ./internal/appconfig/ ./internal/host/ ./internal/console/ -count=1

Expected: PASS.

  • [ ] Step 5: Gate and commit
gofmt -w internal/appconfig/appconfig.go internal/appconfig/routefingerprint.go internal/appconfig/instances_test.go
go build ./... && go vet ./... && go test ./... -count=1 && gofmt -l .
git add internal/appconfig
git commit -m "appconfig: pin the instance layer's signal and one pass over every shape of it (plan 4, task 11)"

---

Self-review

Spec coverage. §6.2's third bundle at apps/<acct>/<app>/instances-config/<host>.json, same manifest shape, same sealed envelopes, same name and value rules: tasks 1 and 2. §6.2's "LoadMerged keeps its signature; LoadMergedHost adds the third overlay": task 3. §6.2's "Sync/SyncExec call it per route after resolving the group base, so a pass costs one extra bucket read per instance that has an overlay and none for instances that do not": tasks 4 and 6, with the cost pinned by a counting store and the one deviation (the sweeps use the two functions LoadMergedHost is built from, not LoadMergedHost itself) stated in the header and pinned by an equivalence test. §6.2's "writing the layer bumps the app's generation object, as SetExecDeliveryAt does": task 2, with task 12 pinning that the route fingerprint does not move. §6.2's four guards: lostKeys/defaultMissing in task 5, the mass-removal guard in task 7, AwaitingRestart and allShadowed in task 8. §6.2's "ExecDelivery is not read from this layer": tasks 3 and 6. §6.2's "only the platform writes this layer: no CLI verb, no console door": nothing in this plan adds either, and the only callers of ApplyInstance in the tree when this plan ends are tests. §6.5's Schema and InstanceConfigETag: task 9. §6.5's "acks stay change-only, and the capability question belongs to the box record's agent version": nothing here writes an extra ack, and the agent-version refusal is step 5's. §6.5's "the console compares the etag with the one its own Put returned": task 10 lands the comparison and the states; the door that calls it is step 5's.

Out of scope and deliberately absent. No instance senders, no PerInstance flag, no per-instance mode transition, no instance email doors, no svcrole.Mail, no uid or backing check at enable, no claimDomain change, no CLI verb, no console door, no mail package change of any kind. Nothing in this plan writes an overlay outside a test, so nothing in the fleet behaves differently after it rolls except that every converged route now leaves an ack behind on every pass.

Four rulings this plan takes. (1) The sweeps do not call LoadMergedHost; they compose instanceOverlays and applyInstanceOverlay, because calling it per route would refetch the group bundles per route and discard the caches the group loop exists for. The two paths are pinned against each other in task 4. (2) AwaitingRestart keeps its (app, environment) vocabulary and gains only the shadowing half; reporting an instance-layer write arrives with step 5's door, because an exported reporting function with no writer and no caller is a guess about what step 5 needs. (3) An every-pass ack carries the previous ack's delivery facts forward rather than restating this pass's, or the hourly pass would walk carlos relay status back from "adopted, restarted" to "awaiting delivery" for every route on the fleet. (4) The mass-removal guard's count stays over group bases: the fault it guards against is registry-wide and takes the overlay listing down with everything else, and a route with an overlay never reaches a removal branch anyway.

The orphan question, and why it is step 5's. Nothing deletes instances-config/<host>.json when an instance is retired. release.PurgeApp's prefix sweep covers a deleted APP, since the key sits under apps/<acct>/<app>/, but a deleted INSTANCE leaves its overlay behind, holding sealed secrets, ready to be re-applied to whatever takes its host name next. That is a real hazard and it is not reachable from this plan: nothing here writes an overlay, so nothing here can orphan one. The sweep belongs beside the writer — step 5's instance teardown calls DeleteInstanceConfig, and the reconcile pass that retires a record is where a belt-and-braces sweep goes. This paragraph is the note that says so, and step 5's plan owes the task.

Cost, stated plainly. Per sync pass, per (account, app) with routes on the box: one List of instances-config/. Per route with an overlay: one Get. Per converged route whose delivered file CHANGED: one Put of its ack, exactly as today. A pass that changes nothing writes nothing and reads nothing extra beyond the one List per app, so a box with two hundred settled routes pays one listing per app per pass and no writes at all.

Placeholder scan. None. Every task carries its test code and its implementation code in full, including the three fixtures the tests share (putInstanceRaw, countingStore, syncInstanceKit), each defined in the task that first uses it. execKit (internal/appconfig/syncexec_test.go:24), testRegistry (sync_test.go:13), captureStdout (syncexec_test.go:46), failingKeyStore (syncexec_test.go:71), addrRoute (awaitingrestart_test.go:14) and syncRelayTestKeys (configack_test.go:17) already exist and are used as they are.

Type consistency. InstancesConfigPrefix, InstanceConfigKey, ValidInstanceHost, LoadInstanceConfig, ApplyInstance, DeleteInstanceConfig, LoadMergedHost, applyInstanceOverlay, instanceOverlays, instanceOverlayLoader, getIfExistsAtETag, allShadowed, ConfigAckSchema, ConfigAckPrefix, LatestConfigAck, InstanceConfigDelivery, InstanceConfigDelivered, InstanceConfigStale and InstanceConfigNotYetDelivered are spelled identically in every task that produces or consumes them.

Two things worth saying plainly, because a reader will wonder. The etag this plan puts in the ack is an S3 etag over sealed JSON, so it names a version without revealing anything about the values inside it; it is safe in a reply a member can read. And a flat exec route still shares the agent's uid, so every sibling on that box can read its <host>.config.env whatever this layer does — per-instance delivery is a scoping mechanism, not an isolation one, and the isolation is step 5's uid refusal plus #126's per-instance uids.

The roll note this plan owes. Four lines.

  1. Every box's store policy needs s3:ListBucket on the deployment bucket before this rolls. A pass now lists instances-config/ once per app, and a listing that fails leaves every route of that app with the file it already has — correct, but it is a freeze, and it would hit routes that have no overlay at all. In-repo policies grant it unconditionally (infra/modules/region-host/main.tf:174) and boxes already list apps/ during adoption (internal/provision/reconcile.go:651), so platform boxes are safe; a hand-edited or self-hosted policy might not be. Check before rolling, not after.
  2. A fleet-door box cannot write config acks at all. control/config-ack/ is outside the door's read and write scopes (internal/edge/storedoor.go:389, :489), so delivery to an instance on such a box reads as unconfirmed however well it actually went. Granting the door control/config-ack/<box>/ is its own piece of work; until then, instance senders should not be placed there.
  3. An ack written by a box still on an older agent decodes with schema: 0. Step 5's enable refuses on the box record's agent version rather than on that, but the field is what makes an old ack legible rather than confusing.
  4. Acks stay change-only, so this plan adds no new write pressure: a settled route writes nothing, exactly as today.