host: the relocation build checks its authority before it takes the replica lease, ReadPin refuses a files digest without its retained copy, and tests pin every install, build and verify check that survived deletion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 files changed,
+313
−17
internal/host/claimassembly.go+3 −0internal/host/relocationbuild.go+21 −7internal/host/relocationbuild_test.go+249 −0internal/hostrelocate/pin.go+14 −4internal/hostrelocate/pin_envkey_test.go+17 −2internal/hostrelocate/pin_test.go+9 −4
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 307.628s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.055s
ok github.com/carlosframework/platform/internal/activator 134.879s
ok github.com/carlosframework/platform/internal/agentsock 0.018s
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.195s
ok github.com/carlosframework/platform/internal/apperr 0.076s
ok github.com/carlosframework/platform/internal/applog 0.173s
ok github.com/carlosframework/platform/internal/awsinv 0.005s
ok github.com/carlosframework/platform/internal/boxchoice 0.006s
ok github.com/carlosframework/platform/internal/boxid 0.720s
? 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.005s
ok github.com/carlosframework/platform/internal/cgroup 0.189s
ok github.com/carlosframework/platform/internal/changelog 0.288s
ok github.com/carlosframework/platform/internal/clamd 0.058s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 317.072s
? 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 5.784s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.011s
ok github.com/carlosframework/platform/internal/dbverify 0.008s
ok github.com/carlosframework/platform/internal/dnsrec 0.005s
ok github.com/carlosframework/platform/internal/docsite 0.010s
ok github.com/carlosframework/platform/internal/domains 1.963s
ok github.com/carlosframework/platform/internal/doorkey 0.008s
ok github.com/carlosframework/platform/internal/economics 0.005s
ok github.com/carlosframework/platform/internal/edge 25.711s
? 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.133s
ok github.com/carlosframework/platform/internal/gate 1.655s
ok github.com/carlosframework/platform/internal/genselect 0.020s
ok github.com/carlosframework/platform/internal/geo 0.013s
ok github.com/carlosframework/platform/internal/geoip 0.049s
ok github.com/carlosframework/platform/internal/host 44.974s
ok github.com/carlosframework/platform/internal/hostactors 0.329s
ok github.com/carlosframework/platform/internal/hostbox 1.347s
ok github.com/carlosframework/platform/internal/hostclaim 0.403s
ok github.com/carlosframework/platform/internal/hostevidence 0.007s
ok github.com/carlosframework/platform/internal/hostexec 0.303s
ok github.com/carlosframework/platform/internal/hostfence 0.042s
ok github.com/carlosframework/platform/internal/hostfiles 4.060s
ok github.com/carlosframework/platform/internal/hostfloor 0.010s
ok github.com/carlosframework/platform/internal/hostguard 0.139s
ok github.com/carlosframework/platform/internal/hostops 1.259s
ok github.com/carlosframework/platform/internal/hostquarantine 0.002s
ok github.com/carlosframework/platform/internal/hostrelocate 0.389s
ok github.com/carlosframework/platform/internal/hostresolve 0.150s
ok github.com/carlosframework/platform/internal/hostwrite 0.002s
ok github.com/carlosframework/platform/internal/infraview 0.003s
ok github.com/carlosframework/platform/internal/instancectl 1.699s
ok github.com/carlosframework/platform/internal/instanceexec 2.687s
ok github.com/carlosframework/platform/internal/instancefs 0.004s
ok github.com/carlosframework/platform/internal/instancekey 0.002s
ok github.com/carlosframework/platform/internal/layout 1.329s
ok github.com/carlosframework/platform/internal/lease 9.432s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.010s
ok github.com/carlosframework/platform/internal/ledger 0.012s
ok github.com/carlosframework/platform/internal/mail 0.164s
ok github.com/carlosframework/platform/internal/maintenance 0.076s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.505s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.075s
ok github.com/carlosframework/platform/internal/metrics 0.011s
ok github.com/carlosframework/platform/internal/moves 21.091s
ok github.com/carlosframework/platform/internal/moves/archive 0.742s
ok github.com/carlosframework/platform/internal/objstore 0.176s
ok github.com/carlosframework/platform/internal/ops 0.180s
ok github.com/carlosframework/platform/internal/park 0.184s
ok github.com/carlosframework/platform/internal/privatefile 0.004s
2026/10/11 21:41:16 provision: listing apps/bab/blog/instances/: list refused — its records are unseen this pass
2026/10/11 21:41:16 provision: listing apps/bab/: listdirs refused — its records are unseen this pass
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/11 21:41:16 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/11 21:41:16 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:16 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/11 21:41:16 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/11 21:41:16 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/11 21:41:16 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/11 21:41:16 provision: claim install acme.go.tito.io: provision: claim admission unavailable
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/11 21:41:16 provision: claim install bot.eleven.bac.sidecar.internal: provision: claim admission unavailable
2026/10/11 21:41:16 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/11 21:41:16 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/11 21:41:16 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/11 21:41:16 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: no sealed config
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:16 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/11 21:41:17 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/11 21:41:17 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/11 21:41:17 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/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 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/11 21:41:17 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/11 21:41:17 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/11 21:41:17 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/11 21:41:17 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/11 21:41:17 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/11 21:41:17 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:17 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/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:17 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/11 21:41:17 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/11 21:41:17 provision: minted instance route rh.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route ri.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rk.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rl.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rj.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rf.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rg.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/11 21:41:17 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/11 21:41:17 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appg.example (app "appg" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.apph.example (app "apph" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appi.example (app "appi" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appj.example (app "appj" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appa.example (app "appa" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appb.example (app "appb" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appe.example (app "appe" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appc.example (app "appc" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appd.example (app "appd" acct "bac")
2026/10/11 21:41:17 provision: minted instance route x.appf.example (app "appf" acct "bac")
2026/10/11 21:41:17 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:17 provision: minted instance route r0095.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0043.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0056.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0060.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0039.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0061.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0075.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0090.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0107.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0004.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0100.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0014.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0042.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0051.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0106.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0041.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0064.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0066.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0072.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0094.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0115.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0013.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0020.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0021.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0088.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0069.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0079.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0099.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0103.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0011.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0019.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0049.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0089.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0005.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0009.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0048.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0073.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0078.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0113.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0070.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0080.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0096.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0098.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0054.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0037.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0047.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0087.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0109.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0117.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0052.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0055.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0000.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0015.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0017.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0040.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0077.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0124.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0003.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0058.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0116.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0118.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0126.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0050.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0059.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0104.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0108.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0128.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0026.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0062.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0071.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0082.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0085.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0120.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0057.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0065.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0001.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0002.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0018.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0063.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0101.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0129.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0007.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0031.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0046.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0105.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0008.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0044.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0111.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0121.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0122.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0016.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0022.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0032.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0010.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0029.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0074.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0097.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0023.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0045.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0083.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0086.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0012.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0093.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0112.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0123.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0006.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0028.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0033.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0035.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0038.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0053.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0110.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0125.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0030.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0034.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0068.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0084.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0092.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0119.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0127.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0081.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0091.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0102.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0114.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0025.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0027.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0036.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0076.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0024.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:17 provision: minted instance route r0067.go.tito.io (app "tito" acct "bac")
--- FAIL: TestPlan9B8RecordWriteCensus (0.42s)
plan9_b8_census_test.go:371: record-write census rejected 3 site(s); update the explicit inventory only after reviewing the source:
changed site internal/hostops/operations.go:Controller.Attempt: source differs from the reviewed digest
unlisted site internal/host/relocationbuild.go:authorityStore.CopyVersion [def:CopyVersion,sel:CopyVersion]: review it, classify it, and update its explicit inventory entry
unlisted site internal/host/relocationbuild.go:authorityStore.PutIfAbsent [def:PutIfAbsent,sel:PutIfAbsent]: review it, classify it, and update its explicit inventory entry
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 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/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route svc.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route whole.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route console.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route shared.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: converged acme.go.tito.io channel edge -> canary/rehearsal
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: empty channel
2026/10/11 21:41:18 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io environment "" -> "staging"
2026/10/11 21:41:18 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io steering "" -> "latency"
2026/10/11 21:41:18 provision: converged acme.go.tito.io steering "latency" -> ""
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io hibernate false -> true
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io hibernate true -> false (replication "daily" cleared: only a hibernating route's rung is read)
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: converged acme.go.tito.io status-named false -> true
2026/10/11 21:41:18 provision: converged acme.go.tito.io status-named true -> false
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: skipping accounts/bac/account.json: malformed account manifest: invalid character 'o' in literal null (expecting 'u')
2026/10/11 21:41:18 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: manifest bac/tito: read failed: provision: manifest bac/tito: invalid character 'o' in literal null (expecting 'u')
2026/10/11 21:41:18 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/11 21:41:18 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/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 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/11 21:41:18 provision: not removing acme$evil.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:18 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/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 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/11 21:41:18 provision: not removing bad-service.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:18 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/11 21:41:18 provision: not removing no-channel.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/11 21:41:18 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/11 21:41:18 provision: removed retired instance route ghost.go.tito.io (record gone)
2026/10/11 21:41:18 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/11 21:41:18 provision: minted instance route dup.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:18 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/11 21:41:18 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:18 provision: removed retired static route site.go.tito.io (app in the trash)
2026/10/11 21:41:18 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/11 21:41:18 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:18 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/11 21:41:18 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/11 21:41:18 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/11 21:41:18 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:18 provision: converged acme.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:18 provision: converged acme.go.tito.io channel canary/rehearsal -> edge
2026/10/11 21:41:18 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/11 21:41:18 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 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/11 21:41:18 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:18 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/11 21:41:18 provision: dry-run: would mint instance route acme.go.tito.io (app "tito" acct "bac" channel "stable")
2026/10/11 21:41:18 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/11 21:41:18 provision: dry-run: would converge adopt.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: dry-run: would converge adopt.go.tito.io environment "" -> "staging"
2026/10/11 21:41:18 provision: dry-run: would converge owned.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: dry-run: would converge owned.go.tito.io environment "" -> "staging"
2026/10/11 21:41:18 provision: dry-run: would converge owned.go.tito.io hibernate false -> true
2026/10/11 21:41:18 provision: dry-run: would converge owned.go.tito.io seed
2026/10/11 21:41:18 provision: dry-run: would remove retired instance route gone.go.tito.io (record gone)
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: adopted existing route adopt.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:18 provision: converged adopt.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: converged adopt.go.tito.io environment "" -> "staging"
2026/10/11 21:41:18 provision: converged owned.go.tito.io channel stable -> edge
2026/10/11 21:41:18 provision: converged owned.go.tito.io environment "" -> "staging"
2026/10/11 21:41:18 provision: converged owned.go.tito.io hibernate false -> true
2026/10/11 21:41:18 provision: removed retired instance route gone.go.tito.io (record gone)
2026/10/11 21:41:18 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: adopted existing route story.carlosframework.com (app "story" acct "bad") — this record now owns its channel and environment
2026/10/11 21:41:18 provision: story.carlosframework.com exists but is not the route this record describes (kind "instance"/"static") — leaving it alone
2026/10/11 21:41:18 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route pubs.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route pub.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route exec.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route loop.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route named.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted service route priv.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted instance route unit.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:18 provision: minted static route pisite.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: removed retired static route site.go.tito.io (record gone)
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: converged acme.go.tito.io replicas "ie,uk" -> "uk"
2026/10/11 21:41:19 provision: converged acme.go.tito.io replicas "uk" -> ""
2026/10/11 21:41:19 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: converged acme-uk.go.tito.io replica mark true -> false
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 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/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/11 21:41:19 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/11 21:41:19 provision: router.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/11 21:41:19 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/11 21:41:19 provision: converged acme.go.tito.io channel stable -> edge
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/11 21:41:19 provision: not removing acme.go.tito.io (record gone): participant of a running operation
2026/10/11 21:41:19 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/11 21:41:19 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/11 21:41:19 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/11 21:41:19 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/11 21:41:19 provision: acme.go.tito.io: held restoring by rb_other, not by mv_acme; leaving it
2026/10/11 21:41:19 provision: acme.go.tito.io: the row is not this pass's (app "other" acct ""); not holding it for mv_acme
2026/10/11 21:41:19 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/11 21:41:19 provision: dry-run: would mint instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac" channel "stable")
2026/10/11 21:41:19 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/11 21:41:19 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/11 21:41:19 provision: removed retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: converged sidecar bot.eleven.bac.sidecar.internal args ["sidecar","run"] -> ["worker","run"]
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: converged sidecar bot.eleven.bac.sidecar.internal ceilings 300/3600 -> 60/120
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: converged sidecar bot.eleven.bac.sidecar.internal channel stable -> canary/eleven
2026/10/11 21:41:19 provision: converged sidecar bot.eleven.bac.sidecar.internal environment "other" -> "relay"
2026/10/11 21:41:19 provision: converged sidecar bot.eleven.bac.sidecar.internal hibernate true -> false
2026/10/11 21:41:19 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: read failed: failStore: reads disabled under apps/bac/eleven/sidecars/
2026/10/11 21:41:19 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: malformed: invalid character 'n' looking for beginning of object key string
2026/10/11 21:41:19 provision: not removing sidecar bot.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/11 21:41:19 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: empty channel
2026/10/11 21:41:19 provision: skipping sidecar record apps/bac/eleven/sidecars/held.json: empty channel
2026/10/11 21:41:19 provision: not removing sidecar held.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/11 21:41:19 provision: removed retired sidecar route old.eleven.bac.sidecar.internal (record gone)
2026/10/11 21:41:19 provision: bac/eleven has sidecar records but no sidecars manifest — not minting
2026/10/11 21:41:19 provision: bac/eleven is in the trash — not minting sidecar routes for it
2026/10/11 21:41:19 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/11 21:41:19 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/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: minted sidecar route hub.relay.bac.sidecar.internal (app "relay" acct "bac" args ["relay"])
2026/10/11 21:41:19 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/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 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/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 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/11 21:41:19 provision: removed retired sidecar route bot.eleven.bac.sidecar.internal (record gone)
2026/10/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 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/11 21:41:19 provision: skipping sidecar record apps/bac/relay/sidecars/hub.json: read failed: failStore: reads disabled under apps/bac/relay/sidecars/
2026/10/11 21:41:19 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/11 21:41:19 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/11 21:41:19 provision: dry-run: would mint sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" channel "stable" args ["sidecar","run"])
2026/10/11 21:41:19 provision: dry-run: would remove retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/11 21:41:19 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
FAIL
FAIL github.com/carlosframework/platform/internal/provision 3.001s
ok github.com/carlosframework/platform/internal/publicrelease 0.006s
ok github.com/carlosframework/platform/internal/registry 3.511s
ok github.com/carlosframework/platform/internal/relay 7.226s
ok github.com/carlosframework/platform/internal/release 17.702s
ok github.com/carlosframework/platform/internal/rollout 1.474s
ok github.com/carlosframework/platform/internal/rootaws 0.007s
ok github.com/carlosframework/platform/internal/s3compat 0.004s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.183s
ok github.com/carlosframework/platform/internal/sim/hostlife 2.341s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.781s
ok github.com/carlosframework/platform/internal/status 0.042s
ok github.com/carlosframework/platform/internal/steering 0.005s
ok github.com/carlosframework/platform/internal/stopevidence 0.100s
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.002s
ok github.com/carlosframework/platform/internal/usage 0.005s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.543s
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/internal/host/claimassembly.go b/internal/host/claimassembly.go| index e07a49179..179c87e9a 100644 |
| --- a/internal/host/claimassembly.go |
| +++ b/internal/host/claimassembly.go |
| @@ -125,6 +125,9 @@ type ClaimAssembly struct { |
| // the verified identity and the instance-control socket. |
| stopLocal func(context.Context) (claimStopLocal, error) |
| collectStop func(context.Context, StopRequest, StopDrainer) (StopResult, error) |
| + // acquireReplicaLease is the relocation build's replica lease, a test |
| + // seam for its loss. Nil uses lease.Acquire. |
| + acquireReplicaLease func(ctx context.Context, bucket, prefix, region string) (replicaLease, error) |
| } |
| func (a ClaimAssembly) Build(ctx context.Context) (ClaimProviders, error) { |
diff --git a/internal/host/relocationbuild.go b/internal/host/relocationbuild.go| index 7f4afdf6e..ec7dd5db0 100644 |
| --- a/internal/host/relocationbuild.go |
| +++ b/internal/host/relocationbuild.go |
| @@ -69,7 +69,18 @@ func (a ClaimAssembly) relocationBuildP(cfg RelocationAssemblyConfig) func(conte |
| if err != nil || p == "" { |
| return errors.Join(fmt.Errorf("%w: no prepared generation to build", hostrelocate.ErrRefused), err) |
| } |
| - held, err := lease.Acquire(ctx, cfg.Bucket, c.Holder.Location, cfg.LeaseRegion) |
| + // The authority first: a stale build never writes the lease object |
| + // on holder.location, where it would refuse the source's own wake. |
| + if err := auth(ctx); err != nil { |
| + return err |
| + } |
| + acquire := a.acquireReplicaLease |
| + if acquire == nil { |
| + acquire = func(ctx context.Context, bucket, prefix, region string) (replicaLease, error) { |
| + return lease.Acquire(ctx, bucket, prefix, region) |
| + } |
| + } |
| + held, err := acquire(ctx, cfg.Bucket, c.Holder.Location, cfg.LeaseRegion) |
| if err != nil { |
| return err |
| } |
| @@ -114,6 +125,13 @@ func (a ClaimAssembly) relocationBuildP(cfg RelocationAssemblyConfig) func(conte |
| } |
| } |
| +// replicaLease is the part of *lease.Held the build uses: its loss and |
| +// its release. |
| +type replicaLease interface { |
| + Lost() <-chan struct{} |
| + Release(context.Context) error |
| +} |
| + |
| // authorityStore is the versioned store the builder writes through: every |
| // write re-checks the build's authority first, so a delayed build whose |
| // phase, executor, stamp, execution lease or replica lease moved writes |
| @@ -218,12 +236,6 @@ func (v PreparedFromInstallManifest) Verify(ctx context.Context, o hostclaim.Obs |
| if err != nil { |
| return hostclaim.PreparedAuthority{}, false, err |
| } |
| - // A files digest without its retained copy, or a copy without its |
| - // digest, is never read as a host that published no files (absence is |
| - // never evidence); CopyPinnedManifest refuses the same pin. |
| - if (pin.Files.SHA256 == "") != (pin.Files.Retained == "") { |
| - return hostclaim.PreparedAuthority{}, false, fmt.Errorf("%w: the pinned files manifest names a digest or a retained copy without the other", hostrelocate.ErrRefused) |
| - } |
| p := c.Op.Journal["prepared"] |
| raw, _, err := v.Files.Get(ctx, hostfiles.ManifestKey(c.Holder.Location+"/files/"+p+"/")) |
| if errors.Is(err, os.ErrNotExist) { |
| @@ -236,6 +248,8 @@ func (v PreparedFromInstallManifest) Verify(ctx context.Context, o hostclaim.Obs |
| if err := json.Unmarshal(raw, &m); err != nil || m.V != hostfiles.ManifestVersion || m.Seq != 0 { |
| return hostclaim.PreparedAuthority{}, false, fmt.Errorf("%w: P's files manifest is not a prepared manifest", hostrelocate.ErrRefused) |
| } |
| + // ReadPin refuses a files digest without its retained copy, so no |
| + // retained copy here is a host that published no files. |
| pinned := hostfiles.Manifest{Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}} |
| if pin.Files.Retained != "" { |
| // The pinner's retained copy of the pinned bytes, never the source |
diff --git a/internal/host/relocationbuild_test.go b/internal/host/relocationbuild_test.go| index efb0cd2cf..d6c6d9f36 100644 |
| --- a/internal/host/relocationbuild_test.go |
| +++ b/internal/host/relocationbuild_test.go |
| @@ -628,3 +628,252 @@ func TestPreparedRefusesAFilesDigestWithoutItsRetainedCopy(t *testing.T) { |
| t.Fatalf("done=%v err=%v", done, err) |
| } |
| } |
| + |
| +// staleObs is the build's observation with change applied to a copy of its |
| +// claim, as a worker holding an older read sees it; the stored claim is |
| +// unchanged. |
| +func (f *buildFixture) staleObs(change func(*hostclaim.Claim)) hostclaim.Observed { |
| + c := *f.obs.Claim |
| + h := *c.Holder |
| + c.Holder = &h |
| + c.Op.Journal = map[string]string{} |
| + for k, v := range f.obs.Claim.Op.Journal { |
| + c.Op.Journal[k] = v |
| + } |
| + change(&c) |
| + o := f.obs |
| + o.Claim = &c |
| + return o |
| +} |
| + |
| +// Rulings 43 and 52: the guard is activated, and P built, only for the |
| +// installation the installing CAS issued. A stored claim whose holder stamp |
| +// is not the install stamp, that lost this box, that is stuck or whose P |
| +// was opened writes no guard record and nothing in the bucket. An |
| +// observation older than the claim, at another install stamp, of another |
| +// incarnation, or naming another P or pin, writes nothing in the bucket. |
| +func TestRelocationBuildRefusesAnInstallationTheClaimDidNotIssue(t *testing.T) { |
| + for _, tc := range []struct { |
| + name string |
| + stored func(*hostclaim.Claim) // a change to the stored claim |
| + stale func(*buildFixture, *hostclaim.Claim) // a change to the observation only |
| + want error |
| + }{ |
| + {name: "a holder stamp that is not the install stamp", stored: func(c *hostclaim.Claim) { c.Holder.Stamp++ }, want: hostrelocate.ErrRefused}, |
| + {name: "this box declared lost", stored: func(c *hostclaim.Claim) { c.Lost = append(c.Lost, "dst") }, want: hostrelocate.ErrRefused}, |
| + {name: "the op stuck", stored: func(c *hostclaim.Claim) { c.Op.Journal["stuck"] = "an operator's" }, want: hostrelocate.ErrRefused}, |
| + {name: "P opened", stored: func(c *hostclaim.Claim) { c.Op.Journal["prepared_opened"] = c.Op.Journal["prepared"] }, want: hostrelocate.ErrRefused}, |
| + {name: "an observation at another install stamp", stale: func(_ *buildFixture, c *hostclaim.Claim) { |
| + c.Op.Journal["install_stamp"] = strconv.FormatUint(c.Holder.Stamp+1, 10) |
| + }, want: hostguard.ErrStampMismatch}, |
| + {name: "an observation of another incarnation", stale: func(_ *buildFixture, c *hostclaim.Claim) { c.Holder.Incarnation = "another" }, want: hostguard.ErrStampMismatch}, |
| + {name: "an observation naming another P", stale: func(_ *buildFixture, c *hostclaim.Claim) { c.Op.Journal["prepared"] = "p2" }, want: hostrelocate.ErrRefused}, |
| + {name: "an observation naming another pin", stale: func(f *buildFixture, c *hostclaim.Claim) { |
| + // The same pin bytes at another key: ReadPin accepts them, the |
| + // journal's text differs. |
| + var ref hostclaim.RecoveryPin |
| + if err := json.Unmarshal([]byte(c.Op.Journal["pin"]), &ref); err != nil { |
| + f.t.Fatal(err) |
| + } |
| + raw, _, err := f.claims.Get(context.Background(), ref.Key) |
| + if err != nil { |
| + f.t.Fatal(err) |
| + } |
| + ref.Key += ".copy" |
| + if _, err := f.claims.Put(context.Background(), ref.Key, raw, ""); err != nil { |
| + f.t.Fatal(err) |
| + } |
| + refRaw, _ := json.Marshal(ref) |
| + c.Op.Journal["pin"] = string(refRaw) |
| + }, want: hostrelocate.ErrRefused}, |
| + } { |
| + t.Run(tc.name, func(t *testing.T) { |
| + f := newBuildFixture(t, true) |
| + if tc.stored != nil { |
| + o, err := hostclaim.Read(t.Context(), f.claims, buildHost) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if f.obs, err = hostclaim.Update(t.Context(), f.claims, o, func(c *hostclaim.Claim) error { tc.stored(c); return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if tc.stale != nil { |
| + f.obs = f.staleObs(func(c *hostclaim.Claim) { tc.stale(f, c) }) |
| + } |
| + before := f.bucket.Snapshot() |
| + if err := f.build(); !errors.Is(err, tc.want) { |
| + t.Fatalf("got %v, want %v", err, tc.want) |
| + } |
| + if f.bucket.Snapshot() != before { |
| + t.Fatal("the bucket was written") |
| + } |
| + if tc.stored != nil { |
| + if entries, _ := os.ReadDir(filepath.Join(f.a.Guard.DataDir, "hostguard")); len(entries) != 0 { |
| + t.Fatalf("the guard was written: %v", entries) |
| + } |
| + } |
| + }) |
| + } |
| +} |
| + |
| +// relocationBuildP checks its authority before it takes the replica lease |
| +// (a stale build never writes the lease object on holder.location), again |
| +// once the lease is held, before P's files manifest, and before the |
| +// database build, so a refusal at each stops what follows it. |
| +func TestRelocationBuildPChecksItsAuthorityBeforeEachStep(t *testing.T) { |
| + refused := errors.New("sentinel: the authority refused") |
| + for _, tc := range []struct { |
| + name string |
| + at int // the auth call that refuses |
| + }{ |
| + {"before the replica lease", 1}, |
| + {"before P's files manifest", 2}, |
| + {"before the database build", 3}, |
| + } { |
| + t.Run(tc.name, func(t *testing.T) { |
| + f := newBuildFixture(t, true) |
| + calls := 0 |
| + auth := func(context.Context) error { |
| + if calls++; calls == tc.at { |
| + return refused |
| + } |
| + return nil |
| + } |
| + before := f.bucket.Snapshot() |
| + if err := f.a.relocationBuildP(f.cfg)(t.Context(), f.obs, auth); !errors.Is(err, refused) || calls != tc.at { |
| + t.Fatalf("got %v after %d checks", err, calls) |
| + } |
| + if tc.at == 1 && f.bucket.Snapshot() != before { |
| + t.Fatal("the replica lease was taken without authority") |
| + } |
| + _, _, err := f.files.Get(t.Context(), hostfiles.ManifestKey(f.obs.Claim.Holder.Location+"/files/p1/")) |
| + if wrote := err == nil; wrote != (tc.at > 2) { |
| + t.Fatalf("P's files manifest written=%v (%v)", wrote, err) |
| + } |
| + if _, err := os.Stat(filepath.Join(f.cfg.WorkDir, "rel-1")); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatalf("the database build started: %v", err) |
| + } |
| + if f.installManifestExists() { |
| + t.Fatal("the install manifest was written") |
| + } |
| + }) |
| + } |
| +} |
| + |
| +// lossyLease is a real replica lease whose loss the test signals. |
| +type lossyLease struct { |
| + *lease.Held |
| + lost chan struct{} |
| +} |
| + |
| +func (l lossyLease) Lost() <-chan struct{} { return l.lost } |
| + |
| +// The replica lease lost while the claim, the guard and main's Local all |
| +// still admit the build stops it before the database build. |
| +func TestRelocationBuildPStopsWhenTheReplicaLeaseIsLost(t *testing.T) { |
| + f := newBuildFixture(t, true) |
| + lost := make(chan struct{}) |
| + f.a.acquireReplicaLease = func(ctx context.Context, bucket, prefix, region string) (replicaLease, error) { |
| + h, err := lease.Acquire(ctx, bucket, prefix, region) |
| + if err != nil { |
| + return nil, err |
| + } |
| + return lossyLease{Held: h, lost: lost}, nil |
| + } |
| + calls := 0 |
| + auth := func(ctx context.Context) error { |
| + if calls++; calls == 3 { // after P's files manifest |
| + close(lost) |
| + <-ctx.Done() // the build has seen the loss |
| + } |
| + return nil |
| + } |
| + if err := f.a.relocationBuildP(f.cfg)(t.Context(), f.obs, auth); !errors.Is(err, context.Canceled) { |
| + t.Fatalf("the build went on without its replica lease: %v", err) |
| + } |
| + if _, err := os.Stat(filepath.Join(f.cfg.WorkDir, "rel-1")); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatalf("the database build started: %v", err) |
| + } |
| + if f.installManifestExists() { |
| + t.Fatal("the install manifest was written after the replica lease was lost") |
| + } |
| +} |
| + |
| +// Verify accepts only this pin's build of P: P's files manifest at |
| +// sequence 0, and an install manifest of this op and host at the pin's N, |
| +// whose integrity check passed and whose snapshot and anchor versions |
| +// exist. Anything else built at P is refused, never reported done. |
| +func TestPreparedRefusesAnythingButThisPinsBuild(t *testing.T) { |
| + for _, tc := range []struct { |
| + name string |
| + files func(*hostfiles.Manifest) |
| + im func(*buildFixture, *moves.InstallManifest) |
| + }{ |
| + {name: "P's files manifest not at sequence 0", files: func(m *hostfiles.Manifest) { m.Seq = 5 }}, |
| + {name: "another op", im: func(_ *buildFixture, m *moves.InstallManifest) { m.Op = "rel-2" }}, |
| + {name: "another host", im: func(_ *buildFixture, m *moves.InstallManifest) { m.Host = "other.example" }}, |
| + {name: "another N", im: func(f *buildFixture, m *moves.InstallManifest) { |
| + // A complete build at N-1: its snapshot and anchor exist, at |
| + // their keys for N-1. Only the pin's N tells it apart. |
| + copyAt := func(from, to string) string { |
| + body, ok := f.bucket.Current("data", from) |
| + if !ok { |
| + f.t.Fatalf("%s is not there", from) |
| + } |
| + return f.bucket.Put("data", to, body) |
| + } |
| + n := m.N - 1 |
| + m.SnapshotVersion = copyAt(m.SnapshotKey, moves.SnapshotKey(f.prefix(), n)) |
| + m.AnchorVersion = copyAt(m.AnchorKey, moves.AnchorKey(f.prefix(), n)) |
| + m.N, m.SnapshotKey, m.AnchorKey = n, moves.SnapshotKey(f.prefix(), n), moves.AnchorKey(f.prefix(), n) |
| + }}, |
| + {name: "a failed integrity check", im: func(_ *buildFixture, m *moves.InstallManifest) { m.IntegrityCheck = "corrupt" }}, |
| + {name: "a snapshot version that is not there", im: func(_ *buildFixture, m *moves.InstallManifest) { m.SnapshotVersion = "missing" }}, |
| + {name: "an anchor version that is not there", im: func(_ *buildFixture, m *moves.InstallManifest) { m.AnchorVersion = "missing" }}, |
| + } { |
| + t.Run(tc.name, func(t *testing.T) { |
| + ctx := t.Context() |
| + f := newBuildFixture(t, true) |
| + if err := f.build(); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, done, err := f.verifier.Verify(ctx, f.obs); !done || err != nil { |
| + t.Fatalf("the untouched build: done=%v %v", done, err) |
| + } |
| + if tc.files != nil { |
| + key := hostfiles.ManifestKey(f.obs.Claim.Holder.Location + "/files/p1/") |
| + raw, etag, err := f.files.Get(ctx, key) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + var m hostfiles.Manifest |
| + if err := json.Unmarshal(raw, &m); err != nil { |
| + t.Fatal(err) |
| + } |
| + tc.files(&m) |
| + if raw, err = json.Marshal(m); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := f.files.Store.Put(ctx, key, raw, etag); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if tc.im != nil { |
| + im, _, err := readInstallManifest(ctx, f.bucket, "data", f.prefix()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + tc.im(f, &im) |
| + raw, err := json.Marshal(im) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + f.bucket.Put("data", moves.InstallManifestKey(f.prefix()), raw) |
| + } |
| + if _, done, err := f.verifier.Verify(ctx, f.obs); done || !errors.Is(err, hostrelocate.ErrRefused) { |
| + t.Fatalf("done=%v err=%v", done, err) |
| + } |
| + }) |
| + } |
| +} |
diff --git a/internal/hostrelocate/pin.go b/internal/hostrelocate/pin.go| index 60ea0393b..54c72b7fc 100644 |
| --- a/internal/hostrelocate/pin.go |
| +++ b/internal/hostrelocate/pin.go |
| @@ -372,6 +372,12 @@ func ReadPin(ctx context.Context, claims release.Store, ref hostclaim.RecoveryPi |
| if (obj.DB == nil) == (obj.Empty == "") || ref.Empty != (obj.DB == nil) { |
| return PinObject{}, fmt.Errorf("%w: pin object %s does not say whether its database is empty", ErrRefused, ref.Key) |
| } |
| + // Likewise a files pin is a host that published no files only when it |
| + // names neither a digest nor a retained copy: one without the other is |
| + // never read as an empty manifest. |
| + if (obj.Files.SHA256 == "") != (obj.Files.Retained == "") { |
| + return PinObject{}, fmt.Errorf("%w: pin object %s names a files digest or a retained copy without the other", ErrRefused, ref.Key) |
| + } |
| return obj, nil |
| } |
| @@ -491,10 +497,14 @@ func (p StorePinner) adoptedKey(ctx context.Context, writer instancekey.Binding, |
| // A configured key needs the env item, present and readable, with no |
| // key of its own: a manifest without host.env is not evidence that |
| // nothing overrides the configuration (absence is never evidence). |
| - // Residual: a source with no <host>.env and a configured key refuses, |
| - // because platform items have no tombstone (StagePlatformDelete drops |
| - // the item), so a deleted env reads as one never published. That is |
| - // rare: <host>.env holds CARLOS_ADMIN_TOKEN on every exec instance. |
| + // Residual: a source with no host.env item and a configured key |
| + // refuses, because platform items have no tombstone (StagePlatformDelete |
| + // drops the item), so a deleted env reads as one never published. That |
| + // is rare only for a source that released normally: its release flush |
| + // stages <host>.env, which holds CARLOS_ADMIN_TOKEN on every exec |
| + // instance. After a box loss no release flush ran, and the item is |
| + // there only if O1' (the C8 provider's Establish) published <host>.env |
| + // at the writer's admission. |
| switch { |
| case a.Source == instancekey.SourcePlatform && own != "" && instancekey.Digest(own) == a.SHA256: |
| case a.Source == instancekey.SourceConfig && haveEnv && own == "": |
diff --git a/internal/hostrelocate/pin_envkey_test.go b/internal/hostrelocate/pin_envkey_test.go| index 8659750bd..ca2eb7f5b 100644 |
| --- a/internal/hostrelocate/pin_envkey_test.go |
| +++ b/internal/hostrelocate/pin_envkey_test.go |
| @@ -165,8 +165,23 @@ func TestStorePinnerConfigRecordNeedsTheEnvItem(t *testing.T) { |
| } |
| putManifest(t, data, "g3", m) |
| p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)} |
| - if _, err := p.Pin(ctx, obs, at); !errors.Is(err, tc.want) && (tc.want != nil || err != nil) { |
| - t.Fatalf("got %v, want %v", err, tc.want) |
| + ref, err := p.Pin(ctx, obs, at) |
| + if tc.want != nil { |
| + if !errors.Is(err, tc.want) { |
| + t.Fatalf("got %v, want %v", err, tc.want) |
| + } |
| + return |
| + } |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + obj, err := ReadPin(ctx, claims, ref) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + want := KeyPin{Source: instancekey.SourceConfig, SHA256: instancekey.Digest("configured-A")} |
| + if obj.InstanceKey == nil || *obj.InstanceKey != want { |
| + t.Fatalf("pinned key %+v, want %+v", obj.InstanceKey, want) |
| } |
| }) |
| } |
diff --git a/internal/hostrelocate/pin_test.go b/internal/hostrelocate/pin_test.go| index c75168102..4aaea8970 100644 |
| --- a/internal/hostrelocate/pin_test.go |
| +++ b/internal/hostrelocate/pin_test.go |
| @@ -626,10 +626,12 @@ func TestStorePinnerPinsTheRouteRelease(t *testing.T) { |
| } |
| // Absence is never evidence: a pin with no database is an empty bootstrap |
| -// only when the object names its evidence and the journal agrees. An |
| -// object with neither a database nor evidence, or one the journal |
| -// disagrees with about emptiness, is refused, never read as an empty |
| -// database. |
| +// only when the object names its evidence and the journal agrees, and a |
| +// files pin is a host that published no files only when it names neither |
| +// a digest nor a retained copy. An object with neither a database nor |
| +// evidence, one the journal disagrees with about emptiness, or a files pin |
| +// with one of digest and retained copy but not the other, is refused, |
| +// never read as empty. |
| func TestReadPinNeedsTheEmptyEvidence(t *testing.T) { |
| ctx := context.Background() |
| plan := &genselect.Plan{Generation: "g3", MaxTXID: 41} |
| @@ -645,6 +647,9 @@ func TestReadPinNeedsTheEmptyEvidence(t *testing.T) { |
| {"a database and empty evidence", PinObject{DB: plan, Empty: "release"}, false, false}, |
| {"an empty pin the journal calls a database", PinObject{Empty: "session-0"}, false, false}, |
| {"a database the journal calls empty", PinObject{DB: plan}, true, false}, |
| + {"a files pin with its digest and retained copy", PinObject{Empty: "session-0", Files: FilesPin{SHA256: strings.Repeat("c", 64), Retained: "control/retained.json"}}, true, true}, |
| + {"a files digest without its retained copy", PinObject{Empty: "session-0", Files: FilesPin{SHA256: strings.Repeat("c", 64)}}, true, false}, |
| + {"a retained copy without its digest", PinObject{Empty: "session-0", Files: FilesPin{Retained: "control/retained.json"}}, true, false}, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| claims := release.NewDirStore(t.TempDir()) |