binary-size-275: store SDK method values, not clients, in interfaces; add a size ceiling
The linux/arm64 binary doubled to 77.7 MB. html/template (edge and console pages) puts the linker in a mode that keeps every exported method of any concrete type stored in an interface value, so awsinv's factoryFor kept all ~2,400 EC2 client methods and the other clients' too. awsinv, alerting (SNS), host/memwatch (CloudWatch), edge/route53dns and steering now hold method values bound to the calls they make. Same SDK calls, same interfaces, tests unchanged. 77,725,858 -> 45,678,754 bytes. S3 and STS stay whole: s3.New and config.assumeWebIdentity convert their own clients to interfaces inside the SDK. Changing release.S3Store saved nothing and was reverted. 20-cross@5 shard 2 now fails a linux/arm64 build over 52 MB and says what to look for. README release notes record the shrink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 files changed,
+349
−12
.amadan/ci.d/20-cross@5+43 −0README.md+2 −0internal/alerting/alerting.go+12 −1internal/awsinv/clients.go+198 −7internal/edge/route53dns.go+29 −1internal/host/memwatch.go+11 −1internal/steering/route53.go+54 −2
CI failed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok github.com/carlosframework/platform 290.089s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 2.726s
ok github.com/carlosframework/platform/cmd/moveprobe 92.224s
ok github.com/carlosframework/platform/cmd/zddprobe 0.718s
ok github.com/carlosframework/platform/internal/activator 238.145s
ok github.com/carlosframework/platform/internal/agentsock 0.017s
ok github.com/carlosframework/platform/internal/alerting 0.004s
ok github.com/carlosframework/platform/internal/analytics 0.020s
ok github.com/carlosframework/platform/internal/appconfig 2.334s
ok github.com/carlosframework/platform/internal/apperr 0.072s
ok github.com/carlosframework/platform/internal/applog 0.150s
ok github.com/carlosframework/platform/internal/awsinv 0.004s
ok github.com/carlosframework/platform/internal/boxchoice 0.004s
ok github.com/carlosframework/platform/internal/boxid 0.649s
? github.com/carlosframework/platform/internal/boxid/boxidtest [no test files]
? github.com/carlosframework/platform/internal/boxid/internal/signtest [no test files]
ok github.com/carlosframework/platform/internal/boxvolumes 0.008s
ok github.com/carlosframework/platform/internal/caps 0.011s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.706s
ok github.com/carlosframework/platform/internal/changelog 0.310s
ok github.com/carlosframework/platform/internal/clamd 0.054s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 365.630s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 6.014s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.010s
ok github.com/carlosframework/platform/internal/dbverify 0.004s
ok github.com/carlosframework/platform/internal/dnsrec 0.003s
ok github.com/carlosframework/platform/internal/docsite 0.004s
ok github.com/carlosframework/platform/internal/domains 2.239s
ok github.com/carlosframework/platform/internal/doorkey 0.006s
ok github.com/carlosframework/platform/internal/economics 0.004s
ok github.com/carlosframework/platform/internal/edge 26.083s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.009s
ok github.com/carlosframework/platform/internal/fleetapi 0.123s
ok github.com/carlosframework/platform/internal/gate 1.664s
ok github.com/carlosframework/platform/internal/genselect 0.015s
ok github.com/carlosframework/platform/internal/geo 0.017s
ok github.com/carlosframework/platform/internal/geoip 0.030s
ok github.com/carlosframework/platform/internal/host 128.277s
ok github.com/carlosframework/platform/internal/hostactors 0.539s
ok github.com/carlosframework/platform/internal/hostbox 2.284s
ok github.com/carlosframework/platform/internal/hostclaim 0.501s
ok github.com/carlosframework/platform/internal/hostevidence 0.013s
ok github.com/carlosframework/platform/internal/hostexec 4.881s
ok github.com/carlosframework/platform/internal/hostfence 0.031s
ok github.com/carlosframework/platform/internal/hostfiles 10.527s
ok github.com/carlosframework/platform/internal/hostfloor 0.013s
ok github.com/carlosframework/platform/internal/hostguard 0.965s
ok github.com/carlosframework/platform/internal/hostops 1.427s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.013s
ok github.com/carlosframework/platform/internal/hostquarantine 0.013s
ok github.com/carlosframework/platform/internal/hostrelocate 0.441s
ok github.com/carlosframework/platform/internal/hostresolve 0.136s
ok github.com/carlosframework/platform/internal/hostwrite 0.013s
ok github.com/carlosframework/platform/internal/infraview 0.013s
ok github.com/carlosframework/platform/internal/instancectl 3.490s
ok github.com/carlosframework/platform/internal/instanceexec 2.701s
ok github.com/carlosframework/platform/internal/instancefs 0.019s
ok github.com/carlosframework/platform/internal/instancekey 0.015s
ok github.com/carlosframework/platform/internal/instanceupdates 0.021s
ok github.com/carlosframework/platform/internal/layout 1.475s
ok github.com/carlosframework/platform/internal/lease 9.806s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.005s
ok github.com/carlosframework/platform/internal/ledger 0.010s
ok github.com/carlosframework/platform/internal/mail 0.163s
ok github.com/carlosframework/platform/internal/maintenance 0.080s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.513s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.072s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 21.203s
ok github.com/carlosframework/platform/internal/moves/archive 0.758s
ok github.com/carlosframework/platform/internal/objstore 0.170s
ok github.com/carlosframework/platform/internal/ops 0.162s
ok github.com/carlosframework/platform/internal/park 0.202s
ok github.com/carlosframework/platform/internal/privatefile 0.003s
2026/10/14 22:20:58 provision: listing apps/bab/blog/instances/: list refused — its records are unseen this pass
2026/10/14 22:20:58 provision: listing apps/bab/: listdirs refused — its records are unseen this pass
2026/10/14 22:20:58 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:58 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:58 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:58 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/14 22:20:58 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/14 22:20:58 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:20:58 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/14 22:20:58 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/14 22:20:58 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/14 22:20:58 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/14 22:20:58 provision: claim install acme.go.tito.io: provision: claim admission unavailable
2026/10/14 22:20:58 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:58 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/14 22:20:58 provision: claim install bot.eleven.bac.sidecar.internal: provision: claim admission unavailable
2026/10/14 22:20:58 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/14 22:20:58 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/14 22:20:58 provision: refusing to mint route acme.go.tito.io (app "tito" acct "bac"): this box declares CARLOS_INBOUND=none (no-inbound) and cannot serve routed instance traffic
2026/10/14 22:20:59 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: no sealed config
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: REFUSING to mint sidecar bot.eleven.bac.sidecar.internal: bot.eleven.bac.sidecar.internal-sidecar.env is staged on this box — the legacy file co-spawn and a native sidecar row are two mechanisms for one host; remove the file to migrate this host onto sidecar records
2026/10/14 22:20:59 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/14 22:20:59 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/14 22:20:59 provision: refusing to mint route acme.go.tito.io (app "tito" acct "bac"): this box declares CARLOS_INBOUND=none (no-inbound) and cannot serve routed instance traffic
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: REFUSING to mint sidecar bot.eleven.bac.sidecar.internal: bot.eleven.bac.sidecar.internal-sidecar.env is staged on this box — the legacy file co-spawn and a native sidecar row are two mechanisms for one host; remove the file to migrate this host onto sidecar records
2026/10/14 22:20:59 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/14 22:20:59 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/14 22:20:59 provision: bot.eleven.bac.sidecar.internal exists but is not a sidecar row this pass owns (kind "sidecar" provisioned false app "eleven" acct "bac") — leaving it alone
2026/10/14 22:20:59 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/14 22:20:59 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/14 22:20:59 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:20:59 provision: bot.eleven.bac.sidecar.internal exists but is not a sidecar row this pass owns (kind "sidecar" provisioned false app "eleven" acct "bac") — leaving it alone
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:20:59 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/14 22:20:59 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/14 22:20:59 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rf.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rg.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rh.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route ri.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rj.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rk.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rl.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/14 22:20:59 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/14 22:20:59 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appf.example (app "appf" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appg.example (app "appg" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.apph.example (app "apph" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appi.example (app "appi" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appj.example (app "appj" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appa.example (app "appa" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appb.example (app "appb" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appe.example (app "appe" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appc.example (app "appc" acct "bac")
2026/10/14 22:20:59 provision: minted instance route x.appd.example (app "appd" acct "bac")
2026/10/14 22:20:59 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:20:59 provision: minted instance route r0034.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0048.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0066.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0085.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0125.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0002.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0027.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0090.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0113.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0022.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0025.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0001.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0021.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0028.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0080.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0012.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0023.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0047.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0099.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0024.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0096.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0063.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0005.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0124.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0004.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0018.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0046.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0055.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0091.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0095.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0100.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0115.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0029.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0129.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0082.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0084.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0086.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0114.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0037.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0102.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0111.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0112.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0120.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0006.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0032.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0035.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0076.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0087.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0088.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0098.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0122.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0010.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0015.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0061.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0019.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0041.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0042.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0054.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0065.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0070.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0101.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0058.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0069.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0097.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0106.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0017.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0030.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0075.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0007.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0016.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0043.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0045.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0036.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0104.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0121.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0126.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0050.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0000.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0040.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0059.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0081.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0094.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0105.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0119.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0053.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0044.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0074.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0026.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0062.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0078.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0108.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0127.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0003.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0056.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0057.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0068.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0109.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0067.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0093.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0049.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0064.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0014.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0020.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0071.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0103.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0107.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0051.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0092.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0117.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0031.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0038.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0079.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0118.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0009.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0011.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0013.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0039.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0060.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0116.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0008.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0033.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0052.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0089.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0110.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0123.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0128.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0072.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0073.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0077.go.tito.io (app "tito" acct "bac")
2026/10/14 22:20:59 provision: minted instance route r0083.go.tito.io (app "tito" acct "bac")
--- FAIL: TestPlan14RootReadCensus (11.05s)
plan14_root_reads_test.go:690: root read census: 5 discrepancies
changed root source envelope internal/alerting
changed root source envelope internal/awsinv
changed root source envelope internal/edge
changed root source envelope internal/host
changed root source envelope internal/steering
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: refusing to mint route acme.go.tito.io (app "tito" acct "bac"): this box declares CARLOS_INBOUND=none (no-inbound) and cannot serve routed instance traffic
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted service route svc.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route whole.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route console.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route shared.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io channel stable -> edge
2026/10/14 22:21:11 provision: converged acme.go.tito.io channel edge -> canary/rehearsal
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: empty channel
2026/10/14 22:21:11 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io environment "" -> "staging"
2026/10/14 22:21:11 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io steering "" -> "latency"
2026/10/14 22:21:11 provision: converged acme.go.tito.io steering "latency" -> ""
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io hibernate false -> true
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io hibernate true -> false (replication "daily" cleared: only a hibernating route's rung is read)
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged acme.go.tito.io status-named false -> true
2026/10/14 22:21:11 provision: converged acme.go.tito.io status-named true -> false
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged ha.go.tito.io upstreams addr "" -> "", addrs "127.0.0.1:7001,127.0.0.1:7002" -> "127.0.0.1:7001,127.0.0.1:7003" (probe "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}" -> "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}")
2026/10/14 22:21:11 provision: converged ha.go.tito.io upstreams addr "" -> "", addrs "127.0.0.1:7001,127.0.0.1:7003" -> "127.0.0.1:7003,127.0.0.1:7001" (probe "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}" -> "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}")
2026/10/14 22:21:11 provision: converged ha.go.tito.io upstreams addr "" -> "", addrs "127.0.0.1:7003,127.0.0.1:7001" -> "127.0.0.1:7003,127.0.0.1:7001" (probe "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}" -> "{\"path\":\"/healthz\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}")
2026/10/14 22:21:11 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: converged whole.go.tito.io upstreams addr "127.0.0.1:9443" -> "", addrs "" -> "127.0.0.1:9443,127.0.0.1:9444" (probe "" -> "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}")
2026/10/14 22:21:11 provision: converged whole.go.tito.io upstreams addr "" -> "", addrs "127.0.0.1:9443,127.0.0.1:9444" -> "" (probe "{\"path\":\"/\",\"status\":200,\"interval_ms\":2000,\"fail_after\":3,\"rise_after\":2}" -> "")
2026/10/14 22:21:11 provision: whole.go.tito.io: record describes a different route SHAPE than the row (kind "instance"/"service" unit ""/"" socket "/run/carlos/whole.go.tito.io.sock"/"" db "/data/whole.go.tito.io.db"/"") — not converged in place; `carlos remove whole.go.tito.io` purges this route's capability grants (registry.Delete's contract, not a bug) before the next pass re-mints it, so treat that as a last resort, not a routine fix
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping accounts/bac/account.json: malformed account manifest: invalid character 'o' in literal null (expecting 'u')
2026/10/14 22:21:11 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: manifest bac/tito: read failed: provision: manifest bac/tito: invalid character 'o' in literal null (expecting 'u')
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: disagrees with its own path (host="wrong-host.go.tito.io" account="bac" app="tito")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/acme$evil.go.tito.io.json: host "acme$evil.go.tito.io" is not a hostname
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/acme$evil.go.tito.io.json: host "acme$evil.go.tito.io" is not a hostname
2026/10/14 22:21:11 provision: not removing acme$evil.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/bad-service.go.tito.io.json: invalid shape: kind service with no addr: the platform only proxies to a service, so the record must name the upstream it proxies to
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/bad-service.go.tito.io.json: invalid shape: kind service with no addr: the platform only proxies to a service, so the record must name the upstream it proxies to
2026/10/14 22:21:11 provision: not removing bad-service.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/14 22:21:11 provision: not removing no-channel.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:21:11 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:11 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/14 22:21:11 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/14 22:21:11 provision: removed retired instance route ghost.go.tito.io (record gone)
2026/10/14 22:21:11 provision: duplicate host dup.go.tito.io: keeping apps/bac/tito/instances/dup.go.tito.io.json, skipping apps/bad/tito/instances/dup.go.tito.io.json
2026/10/14 22:21:11 provision: minted instance route dup.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:21:12 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/14 22:21:12 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:21:12 provision: removed retired static route site.go.tito.io (app in the trash)
2026/10/14 22:21:12 provision: trash marker for bac/tito unreadable: release: malformed trash marker for bac/tito: invalid character 'n' looking for beginning of object key string — not minting for this app
2026/10/14 22:21:12 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:21:12 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/14 22:21:12 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/14 22:21:12 provision: trash marker for bac/tito unreadable: release: malformed trash marker for bac/tito: invalid character 'n' looking for beginning of object key string — not minting for this app
2026/10/14 22:21:12 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: converged acme.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: converged acme.go.tito.io channel canary/rehearsal -> edge
2026/10/14 22:21:12 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (backing "unit" (box-scoped, not expressible in a record)) — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (tunnel "pi-fleet" (box-scoped, not expressible in a record)) — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (caps "port:25" (box-scoped, not expressible in a record)) — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (platform (owned by the domains reconcile pass)) — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (canary (owned by the domains reconcile pass)) — leaving it alone
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (db "/data/other.db"/"/data/acme.go.tito.io.db") — leaving it alone
2026/10/14 22:21:12 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/14 22:21:12 provision: dry-run: would mint instance route acme.go.tito.io (app "tito" acct "bac" channel "stable")
2026/10/14 22:21:12 provision: dry-run: would adopt existing route adopt.go.tito.io (app "tito" acct "bac") — this record would own its channel and environment
2026/10/14 22:21:12 provision: dry-run: would converge adopt.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: dry-run: would converge adopt.go.tito.io environment "" -> "staging"
2026/10/14 22:21:12 provision: dry-run: would converge owned.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: dry-run: would converge owned.go.tito.io environment "" -> "staging"
2026/10/14 22:21:12 provision: dry-run: would converge owned.go.tito.io hibernate false -> true
2026/10/14 22:21:12 provision: dry-run: would converge owned.go.tito.io seed
2026/10/14 22:21:12 provision: dry-run: would remove retired instance route gone.go.tito.io (record gone)
2026/10/14 22:21:12 provision: converged owned.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: converged owned.go.tito.io environment "" -> "staging"
2026/10/14 22:21:12 provision: converged owned.go.tito.io hibernate false -> true
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: adopted existing route adopt.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: converged adopt.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: converged adopt.go.tito.io environment "" -> "staging"
2026/10/14 22:21:12 provision: removed retired instance route gone.go.tito.io (record gone)
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: adopted existing route story.carlosframework.com (app "story" acct "bad") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: story.carlosframework.com exists but is not the route this record describes (kind "instance"/"static") — leaving it alone
2026/10/14 22:21:12 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted service route pub.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted service route pubs.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted service route priv.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route unit.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route exec.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted service route loop.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted service route named.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted static route pisite.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: removed retired static route site.go.tito.io (record gone)
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: converged acme.go.tito.io replicas "ie,uk" -> "uk"
2026/10/14 22:21:12 provision: converged acme.go.tito.io replicas "uk" -> ""
2026/10/14 22:21:12 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: converged acme-uk.go.tito.io replica mark true -> false
2026/10/14 22:21:12 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: acme.go.tito.io: row is on the nested layout (db "/data/instances/acme.go.tito.io/acme.go.tito.io.db" socket "/run/carlos/instances/acme.go.tito.io/app.sock"); the record's shared paths are not drift
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:12 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/14 22:21:12 provision: router.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/14 22:21:12 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/14 22:21:12 provision: converged acme.go.tito.io channel stable -> edge
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/14 22:21:12 provision: not removing acme.go.tito.io (record gone): participant of a running operation
2026/10/14 22:21:12 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/14 22:21:12 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/14 22:21:12 provision: refusing to mint held route acme.go.tito.io (app "tito" acct "bac"): this box declares CARLOS_INBOUND=none (no-inbound) and cannot serve routed instance traffic
2026/10/14 22:21:12 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/14 22:21:12 provision: acme.go.tito.io: held restoring by rb_other, not by mv_acme; leaving it
2026/10/14 22:21:12 provision: acme.go.tito.io: the row is not this pass's (app "other" acct ""); not holding it for mv_acme
2026/10/14 22:21:12 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/14 22:21:12 provision: dry-run: would mint instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac" channel "stable")
2026/10/14 22:21:12 provision: acme.go.tito.io: not held landing by mv_acme: the row's hold changed under this pass; the next pass judges it afresh
2026/10/14 22:21:12 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/14 22:21:12 provision: removed retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/14 22:21:12 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:12 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:12 provision: converged sidecar bot.eleven.bac.sidecar.internal args ["sidecar","run"] -> ["worker","run"]
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: converged sidecar bot.eleven.bac.sidecar.internal ceilings 300/3600 -> 60/120
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: converged sidecar bot.eleven.bac.sidecar.internal channel stable -> canary/eleven
2026/10/14 22:21:13 provision: converged sidecar bot.eleven.bac.sidecar.internal environment "other" -> "relay"
2026/10/14 22:21:13 provision: converged sidecar bot.eleven.bac.sidecar.internal hibernate true -> false
2026/10/14 22:21:13 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: read failed: failStore: reads disabled under apps/bac/eleven/sidecars/
2026/10/14 22:21:13 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: malformed: invalid character 'n' looking for beginning of object key string
2026/10/14 22:21:13 provision: not removing sidecar bot.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/14 22:21:13 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: empty channel
2026/10/14 22:21:13 provision: skipping sidecar record apps/bac/eleven/sidecars/held.json: empty channel
2026/10/14 22:21:13 provision: not removing sidecar held.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/14 22:21:13 provision: removed retired sidecar route old.eleven.bac.sidecar.internal (record gone)
2026/10/14 22:21:13 provision: bac/eleven has sidecar records but no sidecars manifest — not minting
2026/10/14 22:21:13 provision: bac/eleven is in the trash — not minting sidecar routes for it
2026/10/14 22:21:13 provision: sidecar bot.eleven.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: sidecar hub.relay.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: minted sidecar route hub.relay.bac.sidecar.internal (app "relay" acct "bac" args ["relay"])
2026/10/14 22:21:13 provision: sidecar bot.eleven.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: sidecar bot.eleven.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: sidecar bot.eleven.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: removed retired sidecar route bot.eleven.bac.sidecar.internal (record gone)
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: sidecar bot.eleven.bac.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/14 22:21:13 provision: skipping sidecar record apps/bac/relay/sidecars/hub.json: read failed: failStore: reads disabled under apps/bac/relay/sidecars/
2026/10/14 22:21:13 provision: REFUSING to mint sidecar bot.eleven.bac.sidecar.internal: bot.eleven.bac.sidecar.internal-sidecar.env is staged on this box — the legacy file co-spawn and a native sidecar row are two mechanisms for one host; remove the file to migrate this host onto sidecar records
2026/10/14 22:21:13 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/14 22:21:13 provision: dry-run: would mint sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" channel "stable" args ["sidecar","run"])
2026/10/14 22:21:13 provision: dry-run: would remove retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/14 22:21:13 provision: bot.eleven.bac.sidecar.internal exists but is not a sidecar row this pass owns (kind "sidecar" provisioned false app "eleven" acct "bac") — leaving it alone
2026/10/14 22:21:13 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:13 provision: converged acme.go.tito.io update settings "{\"updates\":\"manual\",\"channel\":\"stable\"}" -> ""
2026/10/14 22:21:13 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:13 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:13 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/14 22:21:13 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/14 22:21:13 provision: acme.go.tito.io: not minted this pass: the app's update policy could not be read
2026/10/14 22:21:13 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/14 22:21:13 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/14 22:21:13 provision: acme.go.tito.io: not minted this pass: the app's update policy could not be read
2026/10/14 22:21:13 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
FAIL
FAIL github.com/carlosframework/platform/internal/provision 14.474s
ok github.com/carlosframework/platform/internal/publicrelease 0.009s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 4.034s
ok github.com/carlosframework/platform/internal/relay 7.233s
ok github.com/carlosframework/platform/internal/release 19.920s
ok github.com/carlosframework/platform/internal/rollout 3.709s
ok github.com/carlosframework/platform/internal/rolloutwords 0.003s
ok github.com/carlosframework/platform/internal/rootaws 0.006s
ok github.com/carlosframework/platform/internal/rootparticipation 0.007s
ok github.com/carlosframework/platform/internal/rootstate 0.025s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.293s
ok github.com/carlosframework/platform/internal/sim/hostlife 9.602s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.440s
ok github.com/carlosframework/platform/internal/status 0.042s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.231s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.004s
ok github.com/carlosframework/platform/internal/usage 0.004s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.569s
FAIL
make: *** [Makefile:18: test] Error 1
amadan: step gate failed: exit status 2
amadan: job failed at step gate — its output is above, not at the tail of this log
diff --git a/.amadan/ci.d/20-cross@5 b/.amadan/ci.d/20-cross@5| index 9dec4b177..c6b3b40cc 100755 |
| --- a/.amadan/ci.d/20-cross@5 |
| +++ b/.amadan/ci.d/20-cross@5 |
| @@ -53,3 +53,46 @@ echo "cross-compiling $GOOS/$GOARCH" |
| # here so that adding a cgo dependency fails loudly rather than quietly |
| # changing what this covers. The tree is pure Go today. |
| GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build ./... |
| + |
| +# The size ceiling, on the one shard that builds the shipping target. |
| +# |
| +# The platform binary is what every box downloads and what the update gate |
| +# waits on, so its size is a cost to the fleet. On 2026-10-13 it doubled from |
| +# about 45 MB to 77.7 MB linux/arm64 with no one deciding that (discussion |
| +# #275). Cause: html/template, which the edge and console pages use, puts the |
| +# Go linker in a conservative mode that keeps EVERY exported method of any |
| +# concrete type stored in an interface value. internal/awsinv's factoryFor put |
| +# *ec2.Client (~2,400 methods, 14 MB) and six more SDK clients into narrow |
| +# interfaces, and the whole lot came along. The fix is to store METHOD VALUES |
| +# (a struct or func type bound to the few calls actually made) and not the |
| +# client; wrapping the client in a struct does not help. |
| +# |
| +# The ceiling is the measured size after that fix (45,678,754 bytes, 45.7 MB) |
| +# plus 6 MB of headroom for ordinary growth, rounded up to a whole MB: |
| +# 52 MB (decimal, 52000000 bytes). It is a ceiling and not an equality on |
| +# purpose. If you are here because it fired, look for an SDK client stored in |
| +# an interface, which html/template's method retention then drags in whole: |
| +# |
| +# go build -ldflags=-dumpdep -o /dev/null . 2>&1 | |
| +# grep 'go:itab.*aws-sdk-go-v2/service' |
| +# |
| +# names the conversions. Two are inherent and are not the cause: s3.New and |
| +# config.assumeWebIdentity convert their own clients inside the SDK, so S3 |
| +# and STS stay whole whatever this repo does. Raise the number only for |
| +# growth you meant, and say why in the commit. |
| +if [ "$GOOS/$GOARCH" = "linux/arm64" ]; then |
| + max_bytes=52000000 |
| + size_dir=$(mktemp -d) |
| + trap 'rm -rf "$size_dir"' EXIT |
| + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-s -w" -o "$size_dir/carlos" . |
| + size=$(wc -c <"$size_dir/carlos" | tr -d ' ') |
| + echo "linux/arm64 carlos binary: $size bytes (ceiling $max_bytes)" |
| + if [ "$size" -gt "$max_bytes" ]; then |
| + echo "the linux/arm64 binary is $size bytes, over the $max_bytes ceiling." |
| + echo "look for an SDK client stored in an interface under html/template's" |
| + echo "method retention (every exported method of the client is kept):" |
| + echo "store method values instead. See discussion #275 and the comment in" |
| + echo ".amadan/ci.d/20-cross@5." |
| + exit 1 |
| + fi |
| +fi |
diff --git a/README.md b/README.md| index 941764166..b2d8c1a51 100644 |
| --- a/README.md |
| +++ b/README.md |
| @@ -295,6 +295,8 @@ A release-specific note goes here when a release changes something an |
| operator needs to check or act on before rolling it. Read the note for |
| the release you are about to roll. |
| +**Binary size (#275).** The linux/arm64 platform binary doubled to 77.7 MB in earlier builds because SDK clients were kept whole by the linker. This release is 45.7 MB again, so boxes download about 32 MB less. CI now fails a build over 52 MB. |
| + |
| **Pointer digests (#253): writers before readers.** Once a box has verified a channel pointer that carries manifest digests, it refuses pointers for that channel without them. So digests are turned on in order. First upgrade every writer: every console, and every CLI or deploy script that promotes, rolls back or fast-tracks. Then run `carlos channels digests on`. Then watch `carlos channels digests`: a channel marked as written by an old writer means one is still in use. Rolling a writer back below this release stops it writing pointers boxes accept; `carlos channels digests off` suspends digests while it is fixed, but does not clear the boxes that have already seen them. When `carlos channels digests` lists nothing, set `CARLOS_REQUIRE_POINTER_DIGEST=on` in every box's host.env, so a new box refuses old pointers too. |
| **Instance update settings: the roll order.** Roll every box to the release that applies update settings before anyone turns on manual updates, a major hold or a pin: the console refuses those until every box reports it can protect the builds they name from pruning, and says how many boxes it is waiting on. Before rolling any box back below that release, turn retention off for the deployment (`carlos releases retention --off` for each app that has one), because an older housekeeper prunes builds a pin or a held instance still needs. After such a rollback the console shows *settings not enforced* for instances on the older boxes: they follow their channel until the box is rolled forward again. |
diff --git a/internal/alerting/alerting.go b/internal/alerting/alerting.go| index 22ceb2df0..ec088e105 100644 |
| --- a/internal/alerting/alerting.go |
| +++ b/internal/alerting/alerting.go |
| @@ -274,5 +274,16 @@ func NewSNSClient(ctx context.Context, region string) (Publisher, error) { |
| if err != nil { |
| return nil, err |
| } |
| - return sns.NewFromConfig(cfg), nil |
| + return publishFunc(sns.NewFromConfig(cfg).Publish), nil |
| +} |
| + |
| +// publishFunc is the Publisher over one bound method value. It is not |
| +// `return sns.NewFromConfig(cfg), nil` on purpose: an *sns.Client stored in |
| +// an interface makes the linker keep every exported SNS method once |
| +// html/template is linked in (discussion #275, ~0.75 MB here and ~15 MB |
| +// across the SDK clients). A method value keeps Publish alone. |
| +type publishFunc func(context.Context, *sns.PublishInput, ...func(*sns.Options)) (*sns.PublishOutput, error) |
| + |
| +func (f publishFunc) Publish(ctx context.Context, in *sns.PublishInput, opts ...func(*sns.Options)) (*sns.PublishOutput, error) { |
| + return f(ctx, in, opts...) |
| } |
diff --git a/internal/awsinv/clients.go b/internal/awsinv/clients.go| index 094e91c56..0a299b02b 100644 |
| --- a/internal/awsinv/clients.go |
| +++ b/internal/awsinv/clients.go |
| @@ -67,6 +67,189 @@ type TaggingAPI interface { |
| GetResources(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) |
| } |
| +// ec2Methods satisfies EC2API from method values of one ec2 client. |
| +type ec2Methods struct { |
| + describeInstances func(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) |
| + describeVolumes func(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) |
| + describeAddresses func(context.Context, *ec2.DescribeAddressesInput, ...func(*ec2.Options)) (*ec2.DescribeAddressesOutput, error) |
| + describeSecurityGroups func(context.Context, *ec2.DescribeSecurityGroupsInput, ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) |
| + describeVpcs func(context.Context, *ec2.DescribeVpcsInput, ...func(*ec2.Options)) (*ec2.DescribeVpcsOutput, error) |
| + describeSubnets func(context.Context, *ec2.DescribeSubnetsInput, ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) |
| + describeRegions func(context.Context, *ec2.DescribeRegionsInput, ...func(*ec2.Options)) (*ec2.DescribeRegionsOutput, error) |
| +} |
| + |
| +func (m ec2Methods) DescribeInstances(ctx context.Context, in *ec2.DescribeInstancesInput, opts ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) { |
| + return m.describeInstances(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeVolumes(ctx context.Context, in *ec2.DescribeVolumesInput, opts ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) { |
| + return m.describeVolumes(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeAddresses(ctx context.Context, in *ec2.DescribeAddressesInput, opts ...func(*ec2.Options)) (*ec2.DescribeAddressesOutput, error) { |
| + return m.describeAddresses(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeSecurityGroups(ctx context.Context, in *ec2.DescribeSecurityGroupsInput, opts ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) { |
| + return m.describeSecurityGroups(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeVpcs(ctx context.Context, in *ec2.DescribeVpcsInput, opts ...func(*ec2.Options)) (*ec2.DescribeVpcsOutput, error) { |
| + return m.describeVpcs(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeSubnets(ctx context.Context, in *ec2.DescribeSubnetsInput, opts ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) { |
| + return m.describeSubnets(ctx, in, opts...) |
| +} |
| + |
| +func (m ec2Methods) DescribeRegions(ctx context.Context, in *ec2.DescribeRegionsInput, opts ...func(*ec2.Options)) (*ec2.DescribeRegionsOutput, error) { |
| + return m.describeRegions(ctx, in, opts...) |
| +} |
| + |
| +func newEc2Methods(c *ec2.Client) ec2Methods { |
| + return ec2Methods{ |
| + describeInstances: c.DescribeInstances, |
| + describeVolumes: c.DescribeVolumes, |
| + describeAddresses: c.DescribeAddresses, |
| + describeSecurityGroups: c.DescribeSecurityGroups, |
| + describeVpcs: c.DescribeVpcs, |
| + describeSubnets: c.DescribeSubnets, |
| + describeRegions: c.DescribeRegions, |
| + } |
| +} |
| + |
| +// s3Methods satisfies S3API from method values of one s3 client. |
| +type s3Methods struct { |
| + listBuckets func(context.Context, *s3.ListBucketsInput, ...func(*s3.Options)) (*s3.ListBucketsOutput, error) |
| + getBucketLocation func(context.Context, *s3.GetBucketLocationInput, ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) |
| + getBucketVersioning func(context.Context, *s3.GetBucketVersioningInput, ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error) |
| + getBucketEncryption func(context.Context, *s3.GetBucketEncryptionInput, ...func(*s3.Options)) (*s3.GetBucketEncryptionOutput, error) |
| + getPublicAccessBlock func(context.Context, *s3.GetPublicAccessBlockInput, ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) |
| +} |
| + |
| +func (m s3Methods) ListBuckets(ctx context.Context, in *s3.ListBucketsInput, opts ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { |
| + return m.listBuckets(ctx, in, opts...) |
| +} |
| + |
| +func (m s3Methods) GetBucketLocation(ctx context.Context, in *s3.GetBucketLocationInput, opts ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) { |
| + return m.getBucketLocation(ctx, in, opts...) |
| +} |
| + |
| +func (m s3Methods) GetBucketVersioning(ctx context.Context, in *s3.GetBucketVersioningInput, opts ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error) { |
| + return m.getBucketVersioning(ctx, in, opts...) |
| +} |
| + |
| +func (m s3Methods) GetBucketEncryption(ctx context.Context, in *s3.GetBucketEncryptionInput, opts ...func(*s3.Options)) (*s3.GetBucketEncryptionOutput, error) { |
| + return m.getBucketEncryption(ctx, in, opts...) |
| +} |
| + |
| +func (m s3Methods) GetPublicAccessBlock(ctx context.Context, in *s3.GetPublicAccessBlockInput, opts ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) { |
| + return m.getPublicAccessBlock(ctx, in, opts...) |
| +} |
| + |
| +func newS3Methods(c *s3.Client) s3Methods { |
| + return s3Methods{ |
| + listBuckets: c.ListBuckets, |
| + getBucketLocation: c.GetBucketLocation, |
| + getBucketVersioning: c.GetBucketVersioning, |
| + getBucketEncryption: c.GetBucketEncryption, |
| + getPublicAccessBlock: c.GetPublicAccessBlock, |
| + } |
| +} |
| + |
| +// route53Methods satisfies Route53API from method values of one route53 client. |
| +type route53Methods struct { |
| + listHostedZones func(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) |
| + listResourceRecordSets func(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) |
| +} |
| + |
| +func (m route53Methods) ListHostedZones(ctx context.Context, in *route53.ListHostedZonesInput, opts ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) { |
| + return m.listHostedZones(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ListResourceRecordSets(ctx context.Context, in *route53.ListResourceRecordSetsInput, opts ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) { |
| + return m.listResourceRecordSets(ctx, in, opts...) |
| +} |
| + |
| +func newRoute53Methods(c *route53.Client) route53Methods { |
| + return route53Methods{ |
| + listHostedZones: c.ListHostedZones, |
| + listResourceRecordSets: c.ListResourceRecordSets, |
| + } |
| +} |
| + |
| +// iamMethods satisfies IAMAPI from method values of one iam client. |
| +type iamMethods struct { |
| + listRoles func(context.Context, *iam.ListRolesInput, ...func(*iam.Options)) (*iam.ListRolesOutput, error) |
| + listUsers func(context.Context, *iam.ListUsersInput, ...func(*iam.Options)) (*iam.ListUsersOutput, error) |
| + listAttachedRolePolicies func(context.Context, *iam.ListAttachedRolePoliciesInput, ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) |
| +} |
| + |
| +func (m iamMethods) ListRoles(ctx context.Context, in *iam.ListRolesInput, opts ...func(*iam.Options)) (*iam.ListRolesOutput, error) { |
| + return m.listRoles(ctx, in, opts...) |
| +} |
| + |
| +func (m iamMethods) ListUsers(ctx context.Context, in *iam.ListUsersInput, opts ...func(*iam.Options)) (*iam.ListUsersOutput, error) { |
| + return m.listUsers(ctx, in, opts...) |
| +} |
| + |
| +func (m iamMethods) ListAttachedRolePolicies(ctx context.Context, in *iam.ListAttachedRolePoliciesInput, opts ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) { |
| + return m.listAttachedRolePolicies(ctx, in, opts...) |
| +} |
| + |
| +func newIamMethods(c *iam.Client) iamMethods { |
| + return iamMethods{ |
| + listRoles: c.ListRoles, |
| + listUsers: c.ListUsers, |
| + listAttachedRolePolicies: c.ListAttachedRolePolicies, |
| + } |
| +} |
| + |
| +// cloudwatchMethods satisfies CloudWatchAPI from method values of one cloudwatch client. |
| +type cloudwatchMethods struct { |
| + describeAlarms func(context.Context, *cloudwatch.DescribeAlarmsInput, ...func(*cloudwatch.Options)) (*cloudwatch.DescribeAlarmsOutput, error) |
| +} |
| + |
| +func (m cloudwatchMethods) DescribeAlarms(ctx context.Context, in *cloudwatch.DescribeAlarmsInput, opts ...func(*cloudwatch.Options)) (*cloudwatch.DescribeAlarmsOutput, error) { |
| + return m.describeAlarms(ctx, in, opts...) |
| +} |
| + |
| +func newCloudwatchMethods(c *cloudwatch.Client) cloudwatchMethods { |
| + return cloudwatchMethods{ |
| + describeAlarms: c.DescribeAlarms, |
| + } |
| +} |
| + |
| +// acmMethods satisfies ACMAPI from method values of one acm client. |
| +type acmMethods struct { |
| + listCertificates func(context.Context, *acm.ListCertificatesInput, ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) |
| +} |
| + |
| +func (m acmMethods) ListCertificates(ctx context.Context, in *acm.ListCertificatesInput, opts ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) { |
| + return m.listCertificates(ctx, in, opts...) |
| +} |
| + |
| +func newAcmMethods(c *acm.Client) acmMethods { |
| + return acmMethods{ |
| + listCertificates: c.ListCertificates, |
| + } |
| +} |
| + |
| +// resourcegroupstaggingapiMethods satisfies TaggingAPI from method values of one resourcegroupstaggingapi client. |
| +type resourcegroupstaggingapiMethods struct { |
| + getResources func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) |
| +} |
| + |
| +func (m resourcegroupstaggingapiMethods) GetResources(ctx context.Context, in *resourcegroupstaggingapi.GetResourcesInput, opts ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) { |
| + return m.getResources(ctx, in, opts...) |
| +} |
| + |
| +func newResourcegroupstaggingapiMethods(c *resourcegroupstaggingapi.Client) resourcegroupstaggingapiMethods { |
| + return resourcegroupstaggingapiMethods{ |
| + getResources: c.GetResources, |
| + } |
| +} |
| + |
| // Clients is one region's worth of read-only clients. S3/Route53/IAM are |
| // global services; the assembly calls them once, from the home region's |
| // Clients, rather than once per region. |
| @@ -85,6 +268,14 @@ type Clients struct { |
| // credential chain resolves exactly once. |
| type Factory func(region string) Clients |
| +// factoryFor stores METHOD VALUES, not the SDK clients, in the interfaces. |
| +// html/template (the edge and console pages) switches the linker into a |
| +// mode that keeps every exported method of any concrete type stored in an |
| +// interface value. A *ec2.Client in an interface kept all ~2,400 EC2 |
| +// methods (14 MB), and the other clients kept theirs too: the linux/arm64 |
| +// binary doubled to 77.7 MB (discussion #275). A func value bound to one |
| +// method keeps that method and nothing else. Do not "simplify" these back |
| +// to ec2.NewFromConfig(c) in the struct; the CI size ceiling will say so. |
| func factoryFor(cfg aws.Config) Factory { |
| return func(region string) Clients { |
| c := cfg.Copy() |
| @@ -92,13 +283,13 @@ func factoryFor(cfg aws.Config) Factory { |
| c.Region = region |
| } |
| return Clients{ |
| - EC2: ec2.NewFromConfig(c), |
| - S3: s3.NewFromConfig(c), |
| - Route53: route53.NewFromConfig(c), |
| - IAM: iam.NewFromConfig(c), |
| - CloudWatch: cloudwatch.NewFromConfig(c), |
| - ACM: acm.NewFromConfig(c), |
| - Tagging: resourcegroupstaggingapi.NewFromConfig(c), |
| + EC2: newEc2Methods(ec2.NewFromConfig(c)), |
| + S3: newS3Methods(s3.NewFromConfig(c)), |
| + Route53: newRoute53Methods(route53.NewFromConfig(c)), |
| + IAM: newIamMethods(iam.NewFromConfig(c)), |
| + CloudWatch: newCloudwatchMethods(cloudwatch.NewFromConfig(c)), |
| + ACM: newAcmMethods(acm.NewFromConfig(c)), |
| + Tagging: newResourcegroupstaggingapiMethods(resourcegroupstaggingapi.NewFromConfig(c)), |
| } |
| } |
| } |
diff --git a/internal/edge/route53dns.go b/internal/edge/route53dns.go| index c7ad6fe8b..f19ff8940 100644 |
| --- a/internal/edge/route53dns.go |
| +++ b/internal/edge/route53dns.go |
| @@ -43,6 +43,34 @@ type route53API interface { |
| GetChange(ctx context.Context, in *route53.GetChangeInput, optFns ...func(*route53.Options)) (*route53.GetChangeOutput, error) |
| } |
| +// route53Methods satisfies route53API from method values of one route53 client. |
| +// Storing the *route53.Client itself in the interface would make the linker keep every exported Route53 method once html/template is linked in (discussion #275); method values keep only the calls made here. |
| +type route53Methods struct { |
| + listHostedZonesByName func(ctx context.Context, in *route53.ListHostedZonesByNameInput, optFns ...func(*route53.Options)) (*route53.ListHostedZonesByNameOutput, error) |
| + changeResourceRecordSets func(ctx context.Context, in *route53.ChangeResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ChangeResourceRecordSetsOutput, error) |
| + getChange func(ctx context.Context, in *route53.GetChangeInput, optFns ...func(*route53.Options)) (*route53.GetChangeOutput, error) |
| +} |
| + |
| +func (m route53Methods) ListHostedZonesByName(ctx context.Context, in *route53.ListHostedZonesByNameInput, opts ...func(*route53.Options)) (*route53.ListHostedZonesByNameOutput, error) { |
| + return m.listHostedZonesByName(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ChangeResourceRecordSets(ctx context.Context, in *route53.ChangeResourceRecordSetsInput, opts ...func(*route53.Options)) (*route53.ChangeResourceRecordSetsOutput, error) { |
| + return m.changeResourceRecordSets(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) GetChange(ctx context.Context, in *route53.GetChangeInput, opts ...func(*route53.Options)) (*route53.GetChangeOutput, error) { |
| + return m.getChange(ctx, in, opts...) |
| +} |
| + |
| +func newRoute53Methods(c *route53.Client) route53Methods { |
| + return route53Methods{ |
| + listHostedZonesByName: c.ListHostedZonesByName, |
| + changeResourceRecordSets: c.ChangeResourceRecordSets, |
| + getChange: c.GetChange, |
| + } |
| +} |
| + |
| type route53DNS struct { |
| client route53API |
| mu sync.Mutex |
| @@ -62,7 +90,7 @@ func newRoute53DNS(ctx context.Context) (dnsProvider, error) { |
| if err != nil { |
| return nil, fmt.Errorf("route53: load aws config: %w", err) |
| } |
| - return &route53DNS{client: route53.NewFromConfig(cfg), zoneIDs: map[string]string{}}, nil |
| + return &route53DNS{client: newRoute53Methods(route53.NewFromConfig(cfg)), zoneIDs: map[string]string{}}, nil |
| } |
| // present satisfies dnsProvider: UPSERT the challenge TXT, wait for INSYNC, |
diff --git a/internal/host/memwatch.go b/internal/host/memwatch.go| index a6eb39e3e..5172cbb45 100644 |
| --- a/internal/host/memwatch.go |
| +++ b/internal/host/memwatch.go |
| @@ -44,6 +44,16 @@ type metricPutter interface { |
| PutMetricData(context.Context, *cloudwatch.PutMetricDataInput, ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) |
| } |
| +// putMetricFunc is the metricPutter over one bound method value. Storing the |
| +// *cloudwatch.Client itself in the interface would make the linker keep every |
| +// exported CloudWatch method once html/template is linked in (discussion |
| +// #275); a method value keeps PutMetricData alone. |
| +type putMetricFunc func(context.Context, *cloudwatch.PutMetricDataInput, ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) |
| + |
| +func (f putMetricFunc) PutMetricData(ctx context.Context, in *cloudwatch.PutMetricDataInput, opts ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) { |
| + return f(ctx, in, opts...) |
| +} |
| + |
| type memwatch struct { |
| put metricPutter |
| instanceID string |
| @@ -109,7 +119,7 @@ func memwatchLoop() { |
| return |
| } |
| w := memwatch{ |
| - put: cloudwatch.NewFromConfig(cfg), |
| + put: putMetricFunc(cloudwatch.NewFromConfig(cfg).PutMetricData), |
| instanceID: id, |
| mem: readMemBytes, |
| pressure: readPressureFile, |
diff --git a/internal/steering/route53.go b/internal/steering/route53.go| index 6ec1fa7a0..653a3f25d 100644 |
| --- a/internal/steering/route53.go |
| +++ b/internal/steering/route53.go |
| @@ -88,6 +88,58 @@ type route53API interface { |
| ChangeTagsForResource(ctx context.Context, in *route53.ChangeTagsForResourceInput, optFns ...func(*route53.Options)) (*route53.ChangeTagsForResourceOutput, error) |
| } |
| +// route53Methods satisfies route53API from method values of one route53 client. |
| +// Storing the *route53.Client itself in the interface would make the linker keep every exported Route53 method once html/template is linked in (discussion #275); method values keep only the calls made here. |
| +type route53Methods struct { |
| + listResourceRecordSets func(ctx context.Context, in *route53.ListResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) |
| + changeResourceRecordSets func(ctx context.Context, in *route53.ChangeResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ChangeResourceRecordSetsOutput, error) |
| + listHealthChecks func(ctx context.Context, in *route53.ListHealthChecksInput, optFns ...func(*route53.Options)) (*route53.ListHealthChecksOutput, error) |
| + listTagsForResources func(ctx context.Context, in *route53.ListTagsForResourcesInput, optFns ...func(*route53.Options)) (*route53.ListTagsForResourcesOutput, error) |
| + createHealthCheck func(ctx context.Context, in *route53.CreateHealthCheckInput, optFns ...func(*route53.Options)) (*route53.CreateHealthCheckOutput, error) |
| + deleteHealthCheck func(ctx context.Context, in *route53.DeleteHealthCheckInput, optFns ...func(*route53.Options)) (*route53.DeleteHealthCheckOutput, error) |
| + changeTagsForResource func(ctx context.Context, in *route53.ChangeTagsForResourceInput, optFns ...func(*route53.Options)) (*route53.ChangeTagsForResourceOutput, error) |
| +} |
| + |
| +func (m route53Methods) ListResourceRecordSets(ctx context.Context, in *route53.ListResourceRecordSetsInput, opts ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) { |
| + return m.listResourceRecordSets(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ChangeResourceRecordSets(ctx context.Context, in *route53.ChangeResourceRecordSetsInput, opts ...func(*route53.Options)) (*route53.ChangeResourceRecordSetsOutput, error) { |
| + return m.changeResourceRecordSets(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ListHealthChecks(ctx context.Context, in *route53.ListHealthChecksInput, opts ...func(*route53.Options)) (*route53.ListHealthChecksOutput, error) { |
| + return m.listHealthChecks(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ListTagsForResources(ctx context.Context, in *route53.ListTagsForResourcesInput, opts ...func(*route53.Options)) (*route53.ListTagsForResourcesOutput, error) { |
| + return m.listTagsForResources(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) CreateHealthCheck(ctx context.Context, in *route53.CreateHealthCheckInput, opts ...func(*route53.Options)) (*route53.CreateHealthCheckOutput, error) { |
| + return m.createHealthCheck(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) DeleteHealthCheck(ctx context.Context, in *route53.DeleteHealthCheckInput, opts ...func(*route53.Options)) (*route53.DeleteHealthCheckOutput, error) { |
| + return m.deleteHealthCheck(ctx, in, opts...) |
| +} |
| + |
| +func (m route53Methods) ChangeTagsForResource(ctx context.Context, in *route53.ChangeTagsForResourceInput, opts ...func(*route53.Options)) (*route53.ChangeTagsForResourceOutput, error) { |
| + return m.changeTagsForResource(ctx, in, opts...) |
| +} |
| + |
| +func newRoute53Methods(c *route53.Client) route53Methods { |
| + return route53Methods{ |
| + listResourceRecordSets: c.ListResourceRecordSets, |
| + changeResourceRecordSets: c.ChangeResourceRecordSets, |
| + listHealthChecks: c.ListHealthChecks, |
| + listTagsForResources: c.ListTagsForResources, |
| + createHealthCheck: c.CreateHealthCheck, |
| + deleteHealthCheck: c.DeleteHealthCheck, |
| + changeTagsForResource: c.ChangeTagsForResource, |
| + } |
| +} |
| + |
| // SteeringZoneID returns the deployment's validated steering hosted-zone id, |
| // or "" when the deployment is dormant (env unset, blank, or malformed — |
| // malformed is logged, because a typo'd zone id is a configuration error |
| @@ -109,7 +161,7 @@ func SteeringZoneID() string { |
| // internal/edge/route53dns.go's newRoute53DNS. Route53 is a global service |
| // but the SDK still needs a signing region, so default to us-east-1 when the |
| // environment names none; an explicit AWS_REGION still wins. |
| -func NewClient(ctx context.Context) (*route53.Client, error) { |
| +func NewClient(ctx context.Context) (route53API, error) { |
| var opts []func(*awsconfig.LoadOptions) error |
| if strings.TrimSpace(os.Getenv("AWS_REGION")) == "" && strings.TrimSpace(os.Getenv("AWS_DEFAULT_REGION")) == "" { |
| opts = append(opts, awsconfig.WithRegion("us-east-1")) |
| @@ -118,7 +170,7 @@ func NewClient(ctx context.Context) (*route53.Client, error) { |
| if err != nil { |
| return nil, fmt.Errorf("steering: load aws config: %w", err) |
| } |
| - return route53.NewFromConfig(cfg), nil |
| + return newRoute53Methods(route53.NewFromConfig(cfg)), nil |
| } |
| // ownedCheck is one Route53 health check this deployment owns: tagged |