Sidecar isolation Task 5: the per-host agent socket and the worker's prepare (D3)
agentsock gains a host-pinned listener (ListenPinned, ServePinned): mode 0600, the same verbs and token check as the shared socket, admitting only a request with exactly one Carlos-Sidecar-Host value equal to its host, checked before the token, with the uniform 401. While a host's pinned listener is open the shared socket refuses that host (Review Focus 4). The exec backend prepares a nested worker (prepareWorker): directories, the instance's env layers through instancefs, no admin token, the worker log listener, and the pinned listener bound before return; a failure after the bind closes it. newWorkerUnitProc carries the listener and closeListeners closes it after done. workerEnv's socket comes from the backend (agentSocketFor): the per-host path for a nested worker row, the shared socket otherwise. The agent wires its socket server onto the backend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9 files changed,
+998
−55
internal/activator/activator.go+20 −2internal/activator/backend_exec.go+9 −1internal/activator/backend_unit.go+12 −0internal/activator/prepare.go+115 −19internal/activator/prepare_test.go+336 −0internal/activator/unitproc.go+19 −0internal/agentsock/agentsock.go+190 −32internal/agentsock/agentsock_test.go+290 −0internal/host/agent.go+7 −1
CI failed — run details
CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (1 s)
ok amadan.net/carlos/platform.git/launchctx 0.016s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
=== vet (11 s)
=== gofmt (1 s)
gofmt -l .: clean
=== rest (163 s)
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.411s
ok github.com/carlosframework/platform/cmd/moveprobe 11.754s
ok github.com/carlosframework/platform/cmd/zddprobe 0.759s
ok github.com/carlosframework/platform/internal/activator 57.541s
ok github.com/carlosframework/platform/internal/agentsock 0.074s
ok github.com/carlosframework/platform/internal/alerting 0.006s
ok github.com/carlosframework/platform/internal/analytics 0.048s
ok github.com/carlosframework/platform/internal/appconfig 2.770s
ok github.com/carlosframework/platform/internal/apperr 0.180s
ok github.com/carlosframework/platform/internal/applog 0.380s
ok github.com/carlosframework/platform/internal/awsinv 0.006s
ok github.com/carlosframework/platform/internal/boxchoice 0.013s
ok github.com/carlosframework/platform/internal/boxid 0.718s
? 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.026s
ok github.com/carlosframework/platform/internal/caps 0.029s
ok github.com/carlosframework/platform/internal/certwarm 0.011s
ok github.com/carlosframework/platform/internal/cgroup 0.800s
ok github.com/carlosframework/platform/internal/changelog 0.499s
ok github.com/carlosframework/platform/internal/claimactivation 1.123s
ok github.com/carlosframework/platform/internal/clamd 0.068s
? github.com/carlosframework/platform/internal/cliflag [no test files]
? 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 7.655s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.032s
ok github.com/carlosframework/platform/internal/dbquery 0.015s
ok github.com/carlosframework/platform/internal/dbreads 2.165s
? github.com/carlosframework/platform/internal/dbreads/dbreadstest [no test files]
ok github.com/carlosframework/platform/internal/dbverify 0.010s
ok github.com/carlosframework/platform/internal/dnsrec 0.007s
ok github.com/carlosframework/platform/internal/docsite 0.041s
ok github.com/carlosframework/platform/internal/domains 2.861s
ok github.com/carlosframework/platform/internal/doorkey 0.020s
ok github.com/carlosframework/platform/internal/economics 0.012s
ok github.com/carlosframework/platform/internal/edge 45.075s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.008s
ok github.com/carlosframework/platform/internal/fleetapi 0.155s
ok github.com/carlosframework/platform/internal/gate 1.657s
ok github.com/carlosframework/platform/internal/genselect 0.022s
ok github.com/carlosframework/platform/internal/geo 0.022s
ok github.com/carlosframework/platform/internal/geoip 0.054s
ok github.com/carlosframework/platform/internal/historyreadback 0.265s
ok github.com/carlosframework/platform/internal/host 142.010s
ok github.com/carlosframework/platform/internal/hostactors 6.172s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.013s
ok github.com/carlosframework/platform/internal/hostbox 2.951s
ok github.com/carlosframework/platform/internal/hostclaim 0.765s
ok github.com/carlosframework/platform/internal/hostenv 0.013s
ok github.com/carlosframework/platform/internal/hostevidence 0.013s
ok github.com/carlosframework/platform/internal/hostexec 5.179s
ok github.com/carlosframework/platform/internal/hostfence 0.041s
ok github.com/carlosframework/platform/internal/hostfiles 10.291s
ok github.com/carlosframework/platform/internal/hostfloor 0.017s
ok github.com/carlosframework/platform/internal/hostguard 1.037s
ok github.com/carlosframework/platform/internal/hostops 2.015s
? 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.014s
ok github.com/carlosframework/platform/internal/hostrelocate 0.673s
ok github.com/carlosframework/platform/internal/hostresolve 0.158s
ok github.com/carlosframework/platform/internal/hostwrite 0.015s
ok github.com/carlosframework/platform/internal/infraview 0.012s
ok github.com/carlosframework/platform/internal/instancectl 6.918s
ok github.com/carlosframework/platform/internal/instanceexec 2.807s
ok github.com/carlosframework/platform/internal/instancefs 0.022s
ok github.com/carlosframework/platform/internal/instancekey 0.015s
ok github.com/carlosframework/platform/internal/instanceupdates 0.019s
ok github.com/carlosframework/platform/internal/layout 1.667s
ok github.com/carlosframework/platform/internal/lbhealth 0.017s
ok github.com/carlosframework/platform/internal/lease 10.103s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.515s
ok github.com/carlosframework/platform/internal/ledger 0.015s
ok github.com/carlosframework/platform/internal/mail 0.302s
ok github.com/carlosframework/platform/internal/maintenance 0.105s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.550s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.078s
ok github.com/carlosframework/platform/internal/metrics 0.008s
ok github.com/carlosframework/platform/internal/moves 22.073s
ok github.com/carlosframework/platform/internal/moves/archive 1.016s
ok github.com/carlosframework/platform/internal/objstore 0.272s
ok github.com/carlosframework/platform/internal/ops 0.306s
ok github.com/carlosframework/platform/internal/park 0.310s
ok github.com/carlosframework/platform/internal/plans 0.007s
ok github.com/carlosframework/platform/internal/privatefile 0.005s
2026/10/24 21:25:25 provision: listing apps/bab/blog/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:25 provision: listing apps/bab/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:25 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:25 provision: skipping record apps/bac/tito/instances/one.example.com.json: unreadable: read refused
2026/10/24 21:25:25 provision: listing apps/bac/tito/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:25 provision: an app's records could not be listed: they are skipped
2026/10/24 21:25:25 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:25 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/24 21:25:25 provision: remove retired instance route shop.example: provision: claim admission unavailable
2026/10/24 21:25:25 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/24 21:25:25 provision: remove retired sidecar route worker.sidecar.internal: provision: claim admission unavailable
2026/10/24 21:25:25 provision: claim install acme.go.tito.io: provision: claim admission unavailable
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/24 21:25:25 provision: claim install bot.eleven.bac.sidecar.internal: provision: claim admission unavailable
2026/10/24 21:25:25 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/24 21:25:25 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/24 21:25:25 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/24 21:25:25 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: no sealed config
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 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/24 21:25:25 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/24 21:25:25 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/24 21:25:25 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/24 21:25:25 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:25 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/24 21:25:26 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/24 21:25:26 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: claim install acme.go.tito.io: registry: claim row ownership changed or unknown
2026/10/24 21:25:26 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/24 21:25:26 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["run"])
2026/10/24 21:25:26 provision: claim install bot.eleven.bac.sidecar.internal: registry: claim row ownership changed or unknown
2026/10/24 21:25:26 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:26 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/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:26 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:26 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/new.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:26 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route ri.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rj.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rl.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rf.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rg.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rh.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rk.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rd.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route ra.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rb.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route rc.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/24 21:25:26 provision: skipping record apps/bac/tito/instances/rb.go.tito.io.json: read failed: read refused
2026/10/24 21:25:26 provision: minted instance route re.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appa.example (app "appa" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appb.example (app "appb" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appd.example (app "appd" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appf.example (app "appf" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.apph.example (app "apph" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appj.example (app "appj" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appc.example (app "appc" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appe.example (app "appe" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appg.example (app "appg" acct "bac")
2026/10/24 21:25:26 provision: minted instance route x.appi.example (app "appi" acct "bac")
2026/10/24 21:25:26 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:26 provision: minted instance route r0001.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0068.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0097.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0098.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0100.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0030.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0046.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0109.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0026.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0045.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0071.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0126.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0129.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0012.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0067.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0087.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0093.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0104.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0047.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0016.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0033.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0034.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0066.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0072.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0077.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0080.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0054.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0022.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0078.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0083.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0110.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0127.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0043.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0028.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0090.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0128.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0053.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0063.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0064.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0102.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0103.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0014.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0020.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0105.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0008.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0096.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0094.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0099.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0040.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0049.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0050.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0000.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0051.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0039.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0058.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0120.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0024.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0116.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0121.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0015.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0031.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0062.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0073.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0074.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0075.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0032.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0055.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0113.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0004.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0009.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0018.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0091.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0011.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0069.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0079.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0081.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0082.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0111.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0048.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0065.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0107.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0108.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0122.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0124.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0021.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0029.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0037.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0086.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0095.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0101.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0118.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0013.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0017.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0036.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0042.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0056.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0089.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0092.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0115.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0010.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0027.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0070.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0076.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0125.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0060.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0088.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0052.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0085.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0041.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0084.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0114.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0007.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0061.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0119.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0123.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0005.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0023.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0025.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0035.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0059.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0002.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0006.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0019.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0057.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0112.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0044.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0003.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0038.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0106.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:26 provision: minted instance route r0117.go.tito.io (app "tito" acct "bac")
--- FAIL: TestPlan9B8RecordWriteCensus (0.73s)
plan9_b8_census_test.go:372: record-write census rejected 2 site(s); update the explicit inventory only after reviewing the source:
changed site internal/instanceexec/exit.go:RunExit: source differs from the reviewed digest
changed site internal/ops/litestream.go:FleetDBsWithSkipped: source differs from the reviewed digest
2026/10/24 21:25:27 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 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/24 21:25:27 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted service route svc.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted instance route whole.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted instance route console.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted instance route shared.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:27 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: converged acme.go.tito.io channel edge -> canary/rehearsal
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: empty channel
2026/10/24 21:25:28 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io environment "" -> "staging"
2026/10/24 21:25:28 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io steering "" -> "latency"
2026/10/24 21:25:28 provision: converged acme.go.tito.io steering "latency" -> ""
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io hibernate false -> true
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io hibernate true -> false (replication "daily" cleared: only a hibernating route's rung is read)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io status-named false -> true
2026/10/24 21:25:28 provision: converged acme.go.tito.io status-named true -> false
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route ha.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route whole.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: skipping accounts/bac/account.json: malformed account manifest: invalid character 'o' in literal null (expecting 'u')
2026/10/24 21:25:28 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: manifest bac/tito: read failed: provision: manifest bac/tito: invalid character 'o' in literal null (expecting 'u')
2026/10/24 21:25:28 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/24 21:25:28 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/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 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/24 21:25:28 provision: not removing acme$evil.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:28 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/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 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/24 21:25:28 provision: not removing bad-service.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:28 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: skipping record apps/bac/tito/instances/no-channel.go.tito.io.json: empty channel
2026/10/24 21:25:28 provision: not removing no-channel.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: skipping record apps/bac/tito/instances/acme.go.tito.io.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:28 provision: not removing acme.go.tito.io: its record exists but could not be read cleanly this pass
2026/10/24 21:25:28 provision: removed retired instance route ghost.go.tito.io (record gone)
2026/10/24 21:25:28 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/24 21:25:28 provision: minted instance route dup.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:28 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/24 21:25:28 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:28 provision: removed retired static route site.go.tito.io (app in the trash)
2026/10/24 21:25:28 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/24 21:25:28 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:28 provision: removed retired instance route acme.go.tito.io (app in the trash)
2026/10/24 21:25:28 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:28 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/24 21:25:28 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:28 provision: converged acme.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:28 provision: converged acme.go.tito.io channel canary/rehearsal -> edge
2026/10/24 21:25:28 provision: converged acme.go.tito.io environment "staging" -> ""
2026/10/24 21:25:28 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 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/24 21:25:28 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:28 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/24 21:25:28 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/24 21:25:28 provision: dry-run: would converge adopt.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: dry-run: would converge adopt.go.tito.io environment "" -> "staging"
2026/10/24 21:25:28 provision: dry-run: would converge owned.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: dry-run: would converge owned.go.tito.io environment "" -> "staging"
2026/10/24 21:25:28 provision: dry-run: would converge owned.go.tito.io hibernate false -> true
2026/10/24 21:25:28 provision: dry-run: would converge owned.go.tito.io seed
2026/10/24 21:25:28 provision: dry-run: would mint instance route acme.go.tito.io (app "tito" acct "bac" channel "stable")
2026/10/24 21:25:28 provision: dry-run: would remove retired instance route gone.go.tito.io (record gone)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: adopted existing route adopt.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:28 provision: converged adopt.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: converged adopt.go.tito.io environment "" -> "staging"
2026/10/24 21:25:28 provision: converged owned.go.tito.io channel stable -> edge
2026/10/24 21:25:28 provision: converged owned.go.tito.io environment "" -> "staging"
2026/10/24 21:25:28 provision: converged owned.go.tito.io hibernate false -> true
2026/10/24 21:25:28 provision: removed retired instance route gone.go.tito.io (record gone)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: adopted existing route story.carlosframework.com (app "story" acct "bad") — this record now owns its channel and environment
2026/10/24 21:25:28 provision: story.carlosframework.com exists but is not the route this record describes (kind "instance"/"static") — leaving it alone
2026/10/24 21:25:28 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route pub.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route pubs.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route named.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route priv.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route unit.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route exec.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted service route loop.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted static route pisite.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted static route site.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: removed retired static route site.go.tito.io (record gone)
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:28 provision: converged acme.go.tito.io replicas "ie,uk" -> "uk"
2026/10/24 21:25:28 provision: converged acme.go.tito.io replicas "uk" -> ""
2026/10/24 21:25:29 provision: minted instance route acme-uk.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: converged acme-uk.go.tito.io replica mark true -> false
2026/10/24 21:25:29 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 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/24 21:25:29 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: minted instance route router.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:29 provision: acme.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/24 21:25:29 provision: router.go.tito.io: not minting this pass: the box's layout intent could not be read: store down
2026/10/24 21:25:29 provision: adopted existing route acme.go.tito.io (app "tito" acct "bac") — this record now owns its channel and environment
2026/10/24 21:25:29 provision: converged acme.go.tito.io channel stable -> edge
2026/10/24 21:25:29 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/24 21:25:29 provision: not removing acme.go.tito.io (record gone): participant of a running operation
2026/10/24 21:25:29 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/24 21:25:29 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/24 21:25:29 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/24 21:25:29 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/24 21:25:29 provision: acme.go.tito.io: held restoring by rb_other, not by mv_acme; leaving it
2026/10/24 21:25:29 provision: acme.go.tito.io: the row is not this pass's (app "other" acct ""); not holding it for mv_acme
2026/10/24 21:25:29 provision: acme.go.tito.io exists but is not the route this record describes (app "other"/"tito") — leaving it alone
2026/10/24 21:25:29 provision: dry-run: would mint instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac" channel "stable")
2026/10/24 21:25:29 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/24 21:25:29 provision: held acme.go.tito.io landing by mv_acme (was ready)
2026/10/24 21:25:29 provision: minted instance route acme.example.com (app "shop" acct "bac")
2026/10/24 21:25:29 provision: minted instance route plain.example.com (app "shop" acct "bac")
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/garbled.shop.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/liar.shop.example.com.json: disagrees with its own path (host="other.shop.example.com" account="bac" app="shop")
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/garbled.shop.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/liar.shop.example.com.json: disagrees with its own path (host="other.shop.example.com" account="bac" app="shop")
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/garbled.shop.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/liar.shop.example.com.json: disagrees with its own path (host="other.shop.example.com" account="bac" app="shop")
2026/10/24 21:25:29 provision: skipping record apps/bac/shop/instances/garbled.shop.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:29 provision: listing apps/bac/shop/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:30 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:30 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:30 provision: removed retired instance route acme.go.tito.io (record gone)
2026/10/24 21:25:30 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:30 provision: bac/tito is in the trash — not minting instance routes for it
2026/10/24 21:25:30 provision: minted sidecar route bot.shop.bac.sidecar.internal (app "shop" acct "bac" args ["run"])
2026/10/24 21:25:30 provision: removed retired sidecar route bot.shop.bac.sidecar.internal (record gone)
2026/10/24 21:25:30 provision: minted sidecar route bot.shop.bum.sidecar.internal (app "shop" acct "bum" args ["run"])
2026/10/24 21:25:30 provision: sidecar bot.shop.bum.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/24 21:25:30 provision: minted sidecar route bot.shop.bum.sidecar.internal (app "shop" acct "bum" args ["run"])
2026/10/24 21:25:30 provision: sidecar bot.shop.bum.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/24 21:25:30 provision: removed retired sidecar route bot.shop.bum.sidecar.internal (record gone)
2026/10/24 21:25:30 provision: sidecar bot.shop.bum.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/24 21:25:30 provision: sidecar bot.shop.bum.sidecar.internal is not placeable here: it asks for "no-inbound" placement and this box accepts inbound work (CARLOS_INBOUND="")
2026/10/24 21:25:30 provision: removed retired sidecar route bot.shop.bum.sidecar.internal (record gone)
2026/10/24 21:25:30 provision: a.shop.example.com: swapped for import sd_rei6aafzalab6yszhqyrmk6ys4 (the original route is journaled)
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_rei6aafzalab6yszhqyrmk6ys4 committed: its gate is cleared and its journal row dropped
2026/10/24 21:25:30 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_pwxuhur4gzbnnvsqildnnoxwvm rolled back to its original route
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_pwxuhur4gzbnnvsqildnnoxwvm: the host's replica is left alone: the record apps/bac/shop/instances/a.shop.example.com.json holds the host
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_pwxuhur4gzbnnvsqildnnoxwvm is cleaned up and its journal row retired
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_4bw3tf52bbzienn6rftwp2f7ge rolled back to its original route
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_4bw3tf52bbzienn6rftwp2f7ge: the host's replica is left alone: the record apps/bac/shop/instances/a.shop.example.com.json holds the host
2026/10/24 21:25:30 provision: a.shop.example.com: import sd_4bw3tf52bbzienn6rftwp2f7ge is cleaned up and its journal row retired
2026/10/24 21:25:30 provision: dry-run: would redo a.shop.example.com's swap record CAS for import sd_qwd6h2cwsdmd5ntjlcuka2xc6a
2026/10/24 21:25:30 provision: dry-run: would clear a.shop.example.com's import gate
2026/10/24 21:25:30 provision: dry-run: would converge a.shop.example.com seed source
2026/10/24 21:25:30 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:31 provision: a.shop.example.com: the import's record apps/bac/shop/instances/a.shop.example.com.json is not one this pass can use (connection reset)
2026/10/24 21:25:31 provision: a.shop.example.com: the import's record apps/bac/shop/instances/a.shop.example.com.json is not one this pass can use (provision: invalid maintenance projection or protected fields)
2026/10/24 21:25:31 provision: a.shop.example.com: the import's record apps/bac/shop/instances/a.shop.example.com.json is not one this pass can use (the record under apps/bac/shop/instances/a.shop.example.com.json is apps/bac/shop/instances/b.shop.example.com.json's)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_ubhioln5kvfh2iw46ycpf5n55i cannot roll back on this box: the activator is not wired; it stays journaled and gated
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_grm255m5wt27omusk4uma7k7ti cannot roll back on this box: the activator is not wired; it stays journaled and gated
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_in6rhtnuh3rzj4qcdqqxwdzl5a is journaled, but this box cannot act on imports; its row is left alone
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_oce7lbi2hz3ln7tioj2zkjxbay is journaled, but this box cannot act on imports; its row is left alone
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_uxx36qmsnzhzwnhdvrlicq4baa is journaled, but this box cannot act on imports; its row is left alone
2026/10/24 21:25:31 provision: the import journal could not be read: registry: import journal a.shop.example.com: original row: unexpected end of JSON input (no import is acted on and no route is removed this pass)
2026/10/24 21:25:31 provision: b.shop.example.com: import sd_b6nxbdkxueithsylwb4qtujl74 left alone this pass: the import journal could not be read
2026/10/24 21:25:31 provision: c.shop.example.com: import sd_yot3vxu7fbuo27l6korvnsrvkm left alone this pass: the import journal could not be read
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_mqcgbina4aqsohtnb6dahi52sa committed: its gate is cleared and its journal row dropped
2026/10/24 21:25:31 provision: a.shop.example.com: not converging import sd_ng5cvfll6ouvhaogq5j2jxhi54: it is swapped but not committed, and the row on this box has no import gate
2026/10/24 21:25:31 provision: a.shop.example.com: not converging import sd_t2omwmmwxsau2lhho7ay4xl754: it is swapped but not committed, and the row on this box has no import gate
2026/10/24 21:25:31 provision: a.shop.example.com: not converging import sd_ho7rthvvln2iobkl5isjwmlniy: it is placed but not committed, and the row on this box has no import gate
2026/10/24 21:25:31 provision: a.shop.example.com: not converging import sd_65ulb4tc6wsras44iuddldxtte: it is placed but not committed, and the row on this box has no import gate
2026/10/24 21:25:31 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:31 provision: converge seed source on a.shop.example.com: constraint failed: seed_source is read-only here (1811)
2026/10/24 21:25:31 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_5cqkit263esy6kuykmncidhl44 committed: its gate is cleared and its journal row dropped
2026/10/24 21:25:31 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:31 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:31 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/24 21:25:31 provision: acme.go.tito.io: not minting a seeded instance: this box does not mint nested (<nil>)
2026/10/24 21:25:31 provision: acme.go.tito.io: not minting a seeded instance: claim control is on for this box
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_vf7cgammrpbvwwssemtpwrbylu: this box cannot read seed markers, and places no seed; nothing local is removed
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_vf7cgammrpbvwwssemtpwrbylu rolled back (no box had swapped it)
2026/10/24 21:25:31 provision: acme.go.tito.io: not minting a seeded instance: its record does not describe a hibernating instance
2026/10/24 21:25:31 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:31 provision: removed instance route acme.go.tito.io (its seed sd_li346mnitdflbcqskrjtrzidpa was aborted); the next pass rolls it back
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_li346mnitdflbcqskrjtrzidpa: this box cannot read seed markers, and places no seed; nothing local is removed
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_li346mnitdflbcqskrjtrzidpa rolled back (no box had swapped it)
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_rhjyo2cdia4ypgu5iizrg6dohu has a row this pass may not remove; leaving the seed aborted
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_2l7efurghn5ukno4smetnjabjq rolled back (no box had swapped it)
2026/10/24 21:25:31 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:31 provision: removed instance route acme.go.tito.io (its seed sd_nda6c4xxilxvcsj6hmzv5ius2m was aborted); the next pass rolls it back
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_nda6c4xxilxvcsj6hmzv5ius2m has no route on this box: its local data is removed
2026/10/24 21:25:31 provision: acme.go.tito.io: aborted seed sd_nda6c4xxilxvcsj6hmzv5ius2m rolled back (no box had swapped it)
2026/10/24 21:25:31 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_n4mkvv2gcq4j7xdgjdee4rlrj4 (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_n4mkvv2gcq4j7xdgjdee4rlrj4 rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_n4mkvv2gcq4j7xdgjdee4rlrj4 is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_utdxs74a77cnnjyekqpfm5qsxa (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_utdxs74a77cnnjyekqpfm5qsxa rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_utdxs74a77cnnjyekqpfm5qsxa is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_voazvemfzegamrphxvcme4wxam (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_voazvemfzegamrphxvcme4wxam rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_voazvemfzegamrphxvcme4wxam: the host's replica is left alone: its record no longer names this seed
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_voazvemfzegamrphxvcme4wxam is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_mnk4pxo3oejxivdgthbvhjxdcm (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_mnk4pxo3oejxivdgthbvhjxdcm rollback deferred: the host is not fenced and stopped yet (<nil>)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_mnk4pxo3oejxivdgthbvhjxdcm rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_mnk4pxo3oejxivdgthbvhjxdcm is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_lusiyycmhe4zasoju4bej364de (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_lusiyycmhe4zasoju4bej364de rollback deferred: the host is not fenced and stopped yet (stop unproven)
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_wrvobaeq22om4scmkk3xb2nrle (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_wrvobaeq22om4scmkk3xb2nrle is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_lm6xae6qe7gaqu4wgirojrqlxu (the original route is journaled)
2026/10/24 21:25:31 provision: skipping record apps/bac/shop/instances/a.shop.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_lm6xae6qe7gaqu4wgirojrqlxu left alone this pass: its record could not be read
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_zeanjpr5ebul7g6dmn3y4g5cca (the original route is journaled)
2026/10/24 21:25:31 provision: skipping record apps/bac/shop/instances/a.shop.example.com.json: read failed: connection reset
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_zeanjpr5ebul7g6dmn3y4g5cca left alone this pass: its record could not be read
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_ngvdq24bu4353s4hkpn6cdnwgm (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_ngvdq24bu4353s4hkpn6cdnwgm: close the import gate: constraint failed: injected (1811)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_ngvdq24bu4353s4hkpn6cdnwgm rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_ngvdq24bu4353s4hkpn6cdnwgm is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_cyeu2zsjqg33dygbko44d5zz34 (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_cyeu2zsjqg33dygbko44d5zz34: close the import gate: constraint failed: injected (1811)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_cyeu2zsjqg33dygbko44d5zz34 rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_cyeu2zsjqg33dygbko44d5zz34 is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_lct4iow2qtvopvqae25yb2f5ge (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_lct4iow2qtvopvqae25yb2f5ge rollback deferred: the host is not fenced and stopped yet (injected failure at fence)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_lct4iow2qtvopvqae25yb2f5ge rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_lct4iow2qtvopvqae25yb2f5ge is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_oir4ae6jostuvitscm2mg7ithe (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_oir4ae6jostuvitscm2mg7ithe rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_oir4ae6jostuvitscm2mg7ithe is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_yxj4pml6v4qcfwgej3fpeilmiq (the original route is journaled)
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_yxj4pml6v4qcfwgej3fpeilmiq: retire its instance key: injected failure at retire-key
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_yxj4pml6v4qcfwgej3fpeilmiq rolled back to its original route
2026/10/24 21:25:31 provision: a.shop.example.com: import sd_yxj4pml6v4qcfwgej3fpeilmiq is cleaned up and its journal row retired
2026/10/24 21:25:31 provision: a.shop.example.com: swapped for import sd_l32xei5igoelj7jkbbzndyo73y (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_l32xei5igoelj7jkbbzndyo73y rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_l32xei5igoelj7jkbbzndyo73y is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_qfvcvwnlvrdw4ww3xggg5wmuuq (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_qfvcvwnlvrdw4ww3xggg5wmuuq: remove its local data: injected failure at remove-local
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_qfvcvwnlvrdw4ww3xggg5wmuuq rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_qfvcvwnlvrdw4ww3xggg5wmuuq is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_2jfjm4jo3d5xlvv5u7khvkt4pi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_2jfjm4jo3d5xlvv5u7khvkt4pi rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_2jfjm4jo3d5xlvv5u7khvkt4pi is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_icwuydroql2lcaydcufuymzffi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_icwuydroql2lcaydcufuymzffi: remove its local data: injected failure at remove-local-done
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_icwuydroql2lcaydcufuymzffi rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_icwuydroql2lcaydcufuymzffi is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_g4bwnaggycccsin2luwsodky3q (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_g4bwnaggycccsin2luwsodky3q rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_g4bwnaggycccsin2luwsodky3q is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_nczqeg435hwp7hhnccw7obewee (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_nczqeg435hwp7hhnccw7obewee: restore the original route: constraint failed: injected (1811)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_nczqeg435hwp7hhnccw7obewee rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_nczqeg435hwp7hhnccw7obewee is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_zcn66kz6djmpv3qssi6ta56p5i (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zcn66kz6djmpv3qssi6ta56p5i: restore the original route: constraint failed: injected (1811)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zcn66kz6djmpv3qssi6ta56p5i rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zcn66kz6djmpv3qssi6ta56p5i is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_pou7lihzmiojzmedc3ve35agey (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_pou7lihzmiojzmedc3ve35agey is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_26wfcdfgljdtlca6r4ozxs5cky (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_26wfcdfgljdtlca6r4ozxs5cky rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_26wfcdfgljdtlca6r4ozxs5cky: delete its snapshot: injected failure at delete-snapshot
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_26wfcdfgljdtlca6r4ozxs5cky is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_cfeyerosrareuolhjuxf5k262u (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_cfeyerosrareuolhjuxf5k262u rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_cfeyerosrareuolhjuxf5k262u is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_rmnve7e3qilyre2azjxp22hxsu (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_rmnve7e3qilyre2azjxp22hxsu rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_rmnve7e3qilyre2azjxp22hxsu: delete the host's replica: injected failure at delete-replica
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_rmnve7e3qilyre2azjxp22hxsu is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_775ixmzyqjabyfrwgokx26idha (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_775ixmzyqjabyfrwgokx26idha rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_775ixmzyqjabyfrwgokx26idha is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_lyjper5welkg5dq5e7xwwzsyxi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_lyjper5welkg5dq5e7xwwzsyxi rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_lyjper5welkg5dq5e7xwwzsyxi: retire its journal row: constraint failed: injected (1811)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_lyjper5welkg5dq5e7xwwzsyxi is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_hqrl3lfzobj35yajcdx2pczmxu (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hqrl3lfzobj35yajcdx2pczmxu rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hqrl3lfzobj35yajcdx2pczmxu: retire its journal row: constraint failed: injected (1811)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hqrl3lfzobj35yajcdx2pczmxu is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_hx3tzamt3y4xvnwjo75y7bghly (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hx3tzamt3y4xvnwjo75y7bghly rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hx3tzamt3y4xvnwjo75y7bghly is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: mark import sd_hx3tzamt3y4xvnwjo75y7bghly rolled back: injected failure at cas
2026/10/24 21:25:32 provision: a.shop.example.com: aborted seed sd_hx3tzamt3y4xvnwjo75y7bghly rolled back (no box had swapped it)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_4uvi2cuiszf6or4jmyfftf4r7e (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_4uvi2cuiszf6or4jmyfftf4r7e rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_4uvi2cuiszf6or4jmyfftf4r7e is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: aborted seed sd_4uvi2cuiszf6or4jmyfftf4r7e rolled back (no box had swapped it)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_hflizc76uuof2ll2kdxvjsk6e4 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hflizc76uuof2ll2kdxvjsk6e4 rollback deferred: the host is not fenced and stopped yet (<nil>)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hflizc76uuof2ll2kdxvjsk6e4 rollback deferred: the host is not fenced and stopped yet (<nil>)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hflizc76uuof2ll2kdxvjsk6e4 rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_hflizc76uuof2ll2kdxvjsk6e4 is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_ysxjrextgij3ga4zppts5usihi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_ysxjrextgij3ga4zppts5usihi rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_ysxjrextgij3ga4zppts5usihi is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_q4qwytapc2yps2sfjqkyxuwzla (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_q4qwytapc2yps2sfjqkyxuwzla rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_q4qwytapc2yps2sfjqkyxuwzla is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "shop" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_wewtnomwy24h6pbir5i44jvm2m (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_wewtnomwy24h6pbir5i44jvm2m rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_wewtnomwy24h6pbir5i44jvm2m is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "shop" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_eks5orad4qh7u2kgvow22epwnq (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_eks5orad4qh7u2kgvow22epwnq rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_eks5orad4qh7u2kgvow22epwnq is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "shop" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_ropni3cywjrjxhj2dmxrpwxc7i (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_ropni3cywjrjxhj2dmxrpwxc7i rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_ropni3cywjrjxhj2dmxrpwxc7i is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "other" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_zo572emk7apaeqk5cfjzmzoyrq (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zo572emk7apaeqk5cfjzmzoyrq rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zo572emk7apaeqk5cfjzmzoyrq is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "other" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_tjldnufzwyvhj3kody6wsjhnem (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_tjldnufzwyvhj3kody6wsjhnem rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_tjldnufzwyvhj3kody6wsjhnem is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "other" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_uw4kmwtv367ib7avxilsbafgde (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_uw4kmwtv367ib7avxilsbafgde has no route on this box: its local data is removed and no route is put back
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_uw4kmwtv367ib7avxilsbafgde is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_3ngwu7zclraylmbtw42uznaone (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_yufleqdyfwlgpcre6673sjty44 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_spz2oo2rgbgoyyreduviaelgli (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_spz2oo2rgbgoyyreduviaelgli cannot roll back on this box: the activator is not wired; it stays journaled and gated
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_rsms6r3jz736j5w4oti2kyvpfe (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_rsms6r3jz736j5w4oti2kyvpfe is not rolled back: the route changed under the fence, so nothing there is touched
2026/10/24 21:25:32 provision: a.shop.example.com: import "../not-a-seed" names no snapshot and no replica; only its journal row is cleaned up
2026/10/24 21:25:32 provision: a.shop.example.com: import ../not-a-seed is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_2jmn3jtr4yfif5h5zpjpwh6ms4 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_bhvylj6qabwfbfxm74lo7vhla4 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_vhuaintl3belbrlu7jkbe47nkq (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_ryxnfpkvx3ii5ns77n4jixs4rm (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_7yjqpxc2fnzn7xx37t7wm6bsi4 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_7yjqpxc2fnzn7xx37t7wm6bsi4 rollback deferred: the host is not fenced and stopped yet (stop evidence unreadable)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_lrkwturve7ktjno4gxayupkdmq (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_lrkwturve7ktjno4gxayupkdmq has no route on this box: its local data is removed and no route is put back
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_lrkwturve7ktjno4gxayupkdmq is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: minted instance route a.shop.example.com (app "shop" acct "bac")
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_okgx36zvu63paxdgxw33ph5454 (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_okgx36zvu63paxdgxw33ph5454: remove its local data: injected failure at remove-local
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_okgx36zvu63paxdgxw33ph5454 is not rolled back: the route at the host is not the import's, so nothing there is touched
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_5glxx2to6fxiukdo5s3u7m7maa (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_5glxx2to6fxiukdo5s3u7m7maa: a route appeared at the host, so its local database is left and the rollback waits
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_zm4bc32mdzfrycjrxvelwkb53i (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zm4bc32mdzfrycjrxvelwkb53i rollback deferred: no fence generation: registry busy
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_opowdqlzna3tdhmjgbapl3hm5u (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_opowdqlzna3tdhmjgbapl3hm5u rollback deferred: no fence generation: the generation source answered 0
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_lh6zfe235gvlgzlnjed6s7nurm (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_edq2bjqkf35625oxs4fcyjfpoi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_yce57f577iz47jw6sn4s6vnney (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import deferred: this box cannot read the instance's env file
2026/10/24 21:25:32 provision: a.shop.example.com: import deferred: the instance's env file could not be read: a symlink
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_6xsvswdasdkxqk6yof2cy4y3ou (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_6xsvswdasdkxqk6yof2cy4y3ou rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_6xsvswdasdkxqk6yof2cy4y3ou is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_eb5mrrisybicngn3djb4keqhiu (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_eb5mrrisybicngn3djb4keqhiu rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_eb5mrrisybicngn3djb4keqhiu is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_srqoqgcvhcqrmvgok6w6oive4a (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_srqoqgcvhcqrmvgok6w6oive4a rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_srqoqgcvhcqrmvgok6w6oive4a is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_zfhnc7iaj5y5wss5ndf35birbi (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zfhnc7iaj5y5wss5ndf35birbi: retire its instance key: the env file is a symlink
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zfhnc7iaj5y5wss5ndf35birbi rolled back to its original route
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_zfhnc7iaj5y5wss5ndf35birbi is cleaned up and its journal row retired
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_l3u4xis6pnpx3mbeh3zd7gci6i (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: row is on the nested layout (db "/data/instances/a.shop.example.com/a.shop.example.com.db" socket "/run/carlos/instances/a.shop.example.com/app.sock"); the record's shared paths are not drift
2026/10/24 21:25:32 provision: a.shop.example.com: swapped for import sd_j2ajr3dp2laripdpimgpfmidou (the original route is journaled)
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_6omczky2rt7zlklkfhmfvqezmy refused: there is no route at a.shop.example.com to replace
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_wm5spva76ngcxg6or4spqimkl4 refused: the route's address is 127.0.0.1:9, not 127.0.0.1:8401
2026/10/24 21:25:32 provision: a.shop.example.com: import sd_jk7xudvgdv3j2uhx4zekg6hyry refused: the route's address is 127.0.0.1:8401,127.0.0.1:8402, not 127.0.0.1:8401
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_dexbdqal2cz6o5tqz2f4w2w46e refused: the route's address is 127.0.0.1:9, not 127.0.0.1:8401
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_5xnyl2yekrwfto7se4unmk5jqy refused: the route belongs to app other
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_7i4p63poexkmkmscy2bfvm3mt4 refused: the route is filed under another account
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_hlp5xbcbseshd4swircocsllce refused: the route is filed under the operator account, and no assignment maps shop to this app
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_d7glidswjmna3cewq3jcdpfrym refused: the route is filed under the operator account, and no assignment maps shop to this app
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_azs5bbtuqtqm3ctliwvwux46am refused: the route is filed under the operator account, and no assignment maps shop to this app
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_pglrr7tbwqim7t7ph435ywy5oi refused: the route is filed under the operator account, and no assignment maps shop to this app
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_b2apqqeukhwffztzjhysew6ofu refused: the route is not an address route this import can take over (socket)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_cvmqlwofsixsupglguhrf6cl6m refused: the route is not an address route this import can take over (no address)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_4mv67jrdpwo75pzujdgrnsdo4y refused: the route is not an address route this import can take over (provisioned)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_ljt2ie3m6v47d4xtlhws7q2ueu refused: the route is not an address route this import can take over (kind static)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_5mafnint5yh6ibgvb6sz3e6j2e refused: the route is not an address route this import can take over (backing)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_o57vlrwha2yfo2jk2y3kxrwq5y refused: the route is not an address route this import can take over (tunnel)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_qryqdfbjnldvxm2uoft32nlpna refused: the route is not an address route this import can take over (caps)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_qwt55nm2u2vdx5jes3bur4ucbe refused: the route is not an address route this import can take over (platform)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_7k4qmas2xikjw6p3jqpiycsptu refused: the route is not an address route this import can take over (canary)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_4ms5prgtd62p2zjil4vv476b24 refused: the route is not an address route this import can take over (kind sidecar)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_pduxgang7g773li5nibkihnlp4 refused: the route is not an address route this import can take over (region)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_73j24kf4nunfcs5ug3jdojvfki refused: the route is not an address route this import can take over (fleet_box)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_mwonws5xcpvsael3xnjijvukwy refused: the route is not an address route this import can take over (hibernate)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_5zcj2svzadfsbhwmv6dloafraq refused: the route is not an address route this import can take over (claim_owner)
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_vhenm6myefk3l4ym47p3y3xr6u refused: this box does not run instances nested
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_cvyfqqi2uqx7rum5kbnvepuijy refused: this box does not run instances nested
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_fnkx6yhikx5glz52qiek34j66i refused: claim control is on for this box
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_s33j6bpllxwcg6pr2ckiey47au (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_ngjxdokaik2stc7xl7mhwnmxia (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: import deferred: this box cannot read the legacy route assignments
2026/10/24 21:25:33 provision: a.shop.example.com: import deferred: the legacy route assignments could not be read: store down
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_duhftv6kgsbz2tmlu2tfzeav4a: its verify hash is not a sha256
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_rg3y2sshl6xbqulyngzvaxz3na: its verify hash is not a sha256
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_txzkxhjrx2id5woniqo3vin7u4: its verify hash is not a sha256
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_../../x: its host or seed id is malformed (seeds: not a seed id)
2026/10/24 21:25:33 provision: A.shop.example.com/..: not swapping for import sd_qigedxvfmkcqbny7ym753ejuay: its host or seed id is malformed (seeds: not a host)
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_dye7rwts57rm3gp4q3wob4oomy: its record does not describe a nested, hibernating instance
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_kml3fsjnqbn5tbncih32mufllm: its record does not describe a nested, hibernating instance
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_zftxzatgu2qhsg2mmfipq7cpmu: its record does not describe a nested, hibernating instance
2026/10/24 21:25:33 provision: a.shop.example.com: not swapping for import sd_fy2sndejqd5wa7rm2hmee5wf3i: its record does not describe a nested, hibernating instance
2026/10/24 21:25:33 provision: a.shop.example.com: import deferred: the box's layout intent could not be read: store down
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_href6th24rio2lu7vb4r2uvg5i (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: import deferred: the box's nested probe did not answer: ready probe timed out
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_6ltrbyifzpz4onxpwedgwavfbu (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_l4ca4lcxuni26e7njdudkstms4 (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: import deferred: the route is held by operation op_1
2026/10/24 21:25:33 provision: dry-run: would consider swapping a.shop.example.com for import sd_e54nmbanc7fla6ulkp6piw4qqe
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_s4ljetm3ch2brwplwlyl5ya67i rolled back to its original route
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_s4ljetm3ch2brwplwlyl5ya67i: the host's replica is left alone: the record apps/bac/shop/instances/a.shop.example.com.json holds the host
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_s4ljetm3ch2brwplwlyl5ya67i is cleaned up and its journal row retired
2026/10/24 21:25:33 provision: a.shop.example.com: import sd_6icifffijigqw5vepckwq346mu left alone: the import journal holds seed sd_cv6adlnhpxncsvuckkzltd4u3e at swapped
2026/10/24 21:25:33 provision: a.shop.example.com: not minting import sd_czfjihj3luzhbyfw6rbziw7uky: it is swapped but not committed, and only its swap makes its row
2026/10/24 21:25:33 provision: a.shop.example.com: not minting import sd_lna3t3zbbj6i676gnej6477gzy: it is placed but not committed, and only its swap makes its row
2026/10/24 21:25:33 provision: a.shop.example.com: swapped for import sd_ndvv5o7yqckzbgmn3ag5ndhlm4 (the original route is journaled)
2026/10/24 21:25:33 provision: a.shop.example.com: not minting import sd_ndvv5o7yqckzbgmn3ag5ndhlm4: it is swapped but not committed, and only its swap makes its row
2026/10/24 21:25:33 provision: a.shop.example.com: not minting import sd_ndvv5o7yqckzbgmn3ag5ndhlm4: it is placed but not committed, and only its swap makes its row
2026/10/24 21:25:33 provision: minted instance route a.shop.example.com (app "shop" acct "bac")
2026/10/24 21:25:33 provision: removed retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: converged sidecar bot.eleven.bac.sidecar.internal args ["sidecar","run"] -> ["worker","run"]
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: converged sidecar bot.eleven.bac.sidecar.internal ceilings 300/3600 -> 60/120
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: converged sidecar bot.eleven.bac.sidecar.internal channel stable -> canary/eleven
2026/10/24 21:25:33 provision: converged sidecar bot.eleven.bac.sidecar.internal environment "other" -> "relay"
2026/10/24 21:25:33 provision: converged sidecar bot.eleven.bac.sidecar.internal hibernate true -> false
2026/10/24 21:25:33 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: read failed: failStore: reads disabled under apps/bac/eleven/sidecars/
2026/10/24 21:25:33 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: malformed: invalid character 'n' looking for beginning of object key string
2026/10/24 21:25:33 provision: not removing sidecar bot.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/24 21:25:33 provision: skipping sidecar record apps/bac/eleven/sidecars/bot.json: empty channel
2026/10/24 21:25:33 provision: skipping sidecar record apps/bac/eleven/sidecars/held.json: empty channel
2026/10/24 21:25:33 provision: not removing sidecar held.eleven.bac.sidecar.internal: its record exists but could not be read cleanly this pass
2026/10/24 21:25:33 provision: removed retired sidecar route old.eleven.bac.sidecar.internal (record gone)
2026/10/24 21:25:33 provision: bac/eleven has sidecar records but no sidecars manifest — not minting
2026/10/24 21:25:33 provision: bac/eleven is in the trash — not minting sidecar routes for it
2026/10/24 21:25:33 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/24 21:25:33 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/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: minted sidecar route hub.relay.bac.sidecar.internal (app "relay" acct "bac" args ["relay"])
2026/10/24 21:25:33 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/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 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/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 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/24 21:25:33 provision: removed retired sidecar route bot.eleven.bac.sidecar.internal (record gone)
2026/10/24 21:25:33 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:33 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/24 21:25:33 provision: skipping sidecar record apps/bac/relay/sidecars/hub.json: read failed: failStore: reads disabled under apps/bac/relay/sidecars/
2026/10/24 21:25:33 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/24 21:25:34 provision: minted sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" args ["sidecar","run"])
2026/10/24 21:25:34 provision: dry-run: would mint sidecar route bot.eleven.bac.sidecar.internal (app "eleven" acct "bac" channel "stable" args ["sidecar","run"])
2026/10/24 21:25:34 provision: dry-run: would remove retired sidecar route gone.eleven.bac.sidecar.internal (record gone)
2026/10/24 21:25:34 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/24 21:25:34 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:34 provision: converged acme.go.tito.io update settings "{\"updates\":\"manual\",\"channel\":\"stable\"}" -> ""
2026/10/24 21:25:34 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:34 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:34 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/24 21:25:34 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/24 21:25:34 provision: acme.go.tito.io: not minted this pass: the app's update policy could not be read
2026/10/24 21:25:34 provision: minted instance route acme.go.tito.io (app "tito" acct "bac")
2026/10/24 21:25:34 provision: manifest bac/tito: read failed: provision: manifest bac/tito: unexpected end of JSON input
2026/10/24 21:25:34 provision: acme.go.tito.io: not minted this pass: the app's update policy could not be read
2026/10/24 21:25:34 provision: minted instance route acme.go.tito.io held landing by mv_acme (app "tito" acct "bac")
2026/10/24 21:25:34 provision: record view: change index unusable (release: change index unreadable: store has no conditional read: unsupported operation): the walk records no generations
2026/10/24 21:25:34 provision: listing apps/bqd/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:34 provision: listing apps/bqd/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: listing apps/bqd/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: listing apps/bqd/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:34 provision: listing apps/bqd/: listdirs refused — its records are unseen this pass
2026/10/24 21:25:34 provision: record view: change index unusable (get refused): walking
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: record view: re-reading apps/bqc/shop failed: retrying on the next refresh
2026/10/24 21:25:34 provision: listing apps/bqc/shop/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:34 provision: listing apps/bqc/shop/instances/: list refused — its records are unseen this pass
2026/10/24 21:25:34 provision: an app's records could not be listed: they are skipped
2026/10/24 21:25:34 provision: record view: change index unusable (release: change index malformed: invalid character 'n' looking for beginning of object key string): walking
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: record view: re-reading apps/bqc/shop failed: retrying on the next refresh
2026/10/24 21:25:34 provision: listing apps/bqc/shop/instances/: context canceled — its records are unseen this pass
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping accounts/bqd/account.json: malformed account manifest: invalid character 'n' looking for beginning of object key string
2026/10/24 21:25:34 provision: skipping accounts/bqd/account.json: malformed account manifest: invalid character 'n' looking for beginning of object key string
2026/10/24 21:25:34 provision: skipping accounts/bqd/account.json: malformed account manifest: invalid character 'n' looking for beginning of object key string
2026/10/24 21:25:34 provision: record view: re-reading accounts/bqd failed: retrying on the next refresh
2026/10/24 21:25:34 provision: skipping accounts/bqd/account.json: read failed: list refused
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: minted instance route one.example.com (app "shop" acct "bum")
2026/10/24 21:25:34 provision: minted instance route one.example.com (app "shop" acct "bum")
2026/10/24 21:25:34 provision: minted instance route one.example.com (app "shop" acct "bum")
2026/10/24 21:25:34 provision: skipping record apps/bum/shop/instances/one.example.com.json: empty channel
2026/10/24 21:25:34 provision: not removing one.example.com: its record exists but could not be read cleanly this pass
2026/10/24 21:25:34 provision: skipping record apps/bum/shop/instances/one.example.com.json: empty channel
2026/10/24 21:25:34 provision: removed retired instance route one.example.com (record gone)
2026/10/24 21:25:34 provision: not removing one.example.com: its record exists but could not be read cleanly this pass
2026/10/24 21:25:34 provision: removed retired instance route one.example.com (record gone)
2026/10/24 21:25:34 provision: record view: change index unusable (context deadline exceeded): walking
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
2026/10/24 21:25:34 provision: record view: change index unusable (context deadline exceeded): the walk records no generations
2026/10/24 21:25:34 provision: skipping record apps/bqe/store/instances/bad.store.example.com.json: malformed: provision: invalid maintenance projection or protected fields
--- FAIL: TestPlan14RootReadCensus (11.68s)
plan14_root_reads_test.go:600: root read census: 40 discrepancies
changed root read internal/activator/activator.go:Service.doWake; review class, task, sites and digest
changed root read internal/activator/activator.go:Service.respawnWorkerLocked; review class, task, sites and digest
changed root read internal/activator/backend_exec.go:ExecBackend.spawnUnitFrom; review class, task, sites and digest
changed root read internal/activator/layout.go:Classify; review class, task, sites and digest
changed root read internal/activator/prepare.go:ExecBackend.prepareAt; review class, task, sites and digest
changed root read internal/activator/prepare.go:ExecBackend.prepareKeyedAt; review class, task, sites and digest
changed root read internal/host/agent.go:runAgent; review class, task, sites and digest
changed root read internal/instancectl/server.go:parseRequest; review class, task, sites and digest
changed root read internal/instancectl/server.go:type Server; review class, task, sites and digest
changed root read internal/ops/hibernate_verify.go:HibernatingHosts; review class, task, sites and digest
changed root read internal/ops/litestream.go:FleetDBsWithSkipped; review class, task, sites and digest
changed root read internal/ops/ops.go:runLitestream; review class, task, sites and digest
changed root source envelope internal/activator
changed root source envelope internal/agentsock
changed root source envelope internal/host
changed root source envelope internal/host/units
changed root source envelope internal/instancectl
changed root source envelope internal/instanceexec
changed root source envelope internal/ops
unclassified root read internal/activator/activator.go:Service.agentSocketFor whole:Route@148
unclassified root read internal/activator/backend_unit.go:SplitBackend.perHostAgentSocket file:Exec@154,registry:Backing@82,whole:Route@217
unclassified root read internal/activator/layout.go:WorkerManaged registry:Backing@71,registry:Tunnel@91
unclassified root read internal/activator/prepare.go:ExecBackend.agentSocketFor whole:Route@102
unclassified root read internal/activator/prepare.go:ExecBackend.perHostAgentSocket whole:Route@141,whole:Route@96
unclassified root read internal/activator/prepare.go:ExecBackend.prepareWorker whole:Route@210
unclassified root read internal/activator/unitproc.go:ExecBackend.newWorkerUnitProc registry:Host@244,registry:Host@286,whole:Route@167,whole:Route@171
unclassified root read internal/host/units/carlos-instance-worker@.service:external file:external@0
unclassified root read internal/ops/litestream.go:ActivatorReplicated registry:Backing@59,registry:DBPath@97,registry:Hibernate@117
unclassified root read internal/ops/litestream.go:ActivatorReplicatedPaths registry:DBPath@257,whole:Route@178,whole:Route@212
changed legacy declaration internal/activator/activator.go:Service.doWake
changed legacy declaration internal/activator/backend_exec.go:ExecBackend.spawnUnitFrom
changed legacy declaration internal/activator/layout.go:Classify
changed legacy declaration internal/activator/prepare.go:ExecBackend.prepareAt
changed legacy declaration internal/activator/prepare.go:ExecBackend.prepareKeyedAt
changed legacy declaration internal/instancectl/server.go:parseRequest
changed legacy declaration internal/instancectl/server.go:type Server
changed legacy declaration internal/ops/hibernate_verify.go:HibernatingHosts
changed legacy declaration internal/ops/litestream.go:FleetDBsWithSkipped
changed legacy declaration internal/ops/ops.go:runLitestream
changed reclassified declaration internal/activator/activator.go:Service.respawnWorkerLocked
make census-card shows what changed; make census-propose NAME=<branch> CLAIMS=<file> writes the overlay and its approval card (AGENTS.md, The census)
FAIL
FAIL github.com/carlosframework/platform/internal/provision 20.238s
ok github.com/carlosframework/platform/internal/publicrelease 0.007s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.547s
ok github.com/carlosframework/platform/internal/relay 7.237s
ok github.com/carlosframework/platform/internal/release 25.599s
ok github.com/carlosframework/platform/internal/rollout 3.780s
ok github.com/carlosframework/platform/internal/rolloutwords 0.006s
ok github.com/carlosframework/platform/internal/rootaws 0.011s
ok github.com/carlosframework/platform/internal/rootparticipation 0.059s
ok github.com/carlosframework/platform/internal/rootstate 0.030s
ok github.com/carlosframework/platform/internal/runs 0.605s
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.719s
ok github.com/carlosframework/platform/internal/seeds 0.017s
ok github.com/carlosframework/platform/internal/sim/engines 2.482s
ok github.com/carlosframework/platform/internal/sim/hostlife 9.920s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.953s
ok github.com/carlosframework/platform/internal/status 0.078s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.308s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.009s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.006s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.005s
ok github.com/carlosframework/platform/internal/usage 0.006s
ok github.com/carlosframework/platform/internal/version 0.003s
ok github.com/carlosframework/platform/internal/vet 1.665s
FAIL
=== internal_console@0 (80 s)
ok github.com/carlosframework/platform/internal/console 78.463s
=== internal_console@1 (43 s)
ok github.com/carlosframework/platform/internal/console 41.466s
=== internal_console@2 (41 s)
ok github.com/carlosframework/platform/internal/console 39.264s
=== internal_console@3 (41 s)
ok github.com/carlosframework/platform/internal/console 39.448s
=== internal_console@4 (42 s)
ok github.com/carlosframework/platform/internal/console 41.029s
=== internal_console@5 (41 s)
ok github.com/carlosframework/platform/internal/console 39.133s
=== internal_console@6 (36 s)
ok github.com/carlosframework/platform/internal/console 34.923s
=== internal_console@7 (46 s)
ok github.com/carlosframework/platform/internal/console 44.394s
=== root@0 (109 s)
ok github.com/carlosframework/platform 107.226s
=== root@1 (84 s)
ok github.com/carlosframework/platform 82.450s
=== root@2 (91 s)
using http://127.0.0.1:39523 as a@keymail.dev
using http://127.0.0.1:34877 as vicky@keymail.dev
titogo (staging):
KEY VALUE
PORT 8080
--- FAIL: TestRunBuiltCopyIsApplied (0.14s)
approved_run_copy_test.go:139: RUN-C13: internal/instanceexec/exec.go does not contain the approved text "instance-exec: role <role> is not run, seed, sidecar or cmd"
approved_run_copy_test.go:139: RUN-C14: internal/instanceexec/exit.go does not contain the approved text "instance-exit: role <role> is not run, seed, sidecar or cmd"
2026/10/24 21:25:38 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:38 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A37309": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:38 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:38 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A37309": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:38 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:38 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:38 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A43805": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:38 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:40 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:40 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A33765": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:40 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:40 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:40 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A38431": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:40 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:42 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:42 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A36409": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:42 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:44 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:44 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A46571": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:44 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
using http://127.0.0.1:40681 as paul@keymail.dev
account: ops (bab)
2026/10/24 21:25:46 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A40681": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:46 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
set 1 env var(s) for titogo (default)
using http://127.0.0.1:40681 as paul@keymail.dev
account: ops (bab)
set 1 secret(s) for titogo (default)
account: ops (bab)
account: ops (bab)
using http://127.0.0.1:40681 as paul@keymail.dev
account: ops (bab)
config sync requested for titogo the files land within seconds; programs carlos owns restart themselves, and anything else keeps its old values until you restart it
2026/10/24 21:25:46 console: delivery to mallory@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=mallory%40keymail.dev&from=console%40127.0.0.1%3A40681": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:46 console: could not notify mallory@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:48 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:25:48 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=708
2026/10/24 21:25:48 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:25:49 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:25:49 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=265
2026/10/24 21:25:49 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:25:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:49 console: delivery to member@example.com: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A35879": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:49 console: could not notify member@example.com of a CLI token issue: console: no email sender is configured
2026/10/24 21:25:49 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:25:49 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=708
2026/10/24 21:25:49 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:25:50 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:25:50 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=265
2026/10/24 21:25:50 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:25:51 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:25:51 console: delivery to member@example.com: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A40213": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:25:51 console: could not notify member@example.com of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:32 console: db read dbr_t32gwhej43mjur352n77ohhdwq: relayed 1 chunks (101 bytes) in 41.03s, ended done
2026/10/24 21:26:32 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:26:32 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=708
2026/10/24 21:26:32 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:26:33 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:26:33 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=265
2026/10/24 21:26:33 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:26:33 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:26:33 console: delivery to member@example.com: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A35757": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:33 console: could not notify member@example.com of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:34 console: db read dbr_wpncm5hdp5duhqzguci4rvztp4: relayed 1 chunks (1070 bytes) in 1.001s, ended done
2026/10/24 21:26:35 console: db read dbr_iw3jtxwuyiv2wxwa74cii2k2hm: relayed 1 chunks (805 bytes) in 1.001s, ended done
2026/10/24 21:26:35 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:26:35 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=708
2026/10/24 21:26:35 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:26:36 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:26:36 INFO ltx file uploaded system=store db=shop.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=265
2026/10/24 21:26:36 INFO replica sync system=store db=shop.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:26:36 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:26:36 console: delivery to member@example.com: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A42117": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:36 console: could not notify member@example.com of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:37 console: db read dbr_ax6g5tcxywbfz5w4vm2hbkw4ru: relayed 2 chunks (8386 bytes) in 1.043s, ended client gone
using http://127.0.0.1:36657 as paul@keymail.dev
account: ops (bab)
attached tickets.example.com -> default.go.tito.io
DNS: pointing
using http://127.0.0.1:36657 as paul@keymail.dev
account: ops (bab)
attached carloku.example.com -> default.go.tito.io
2026/10/24 21:26:38 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:26:38 console: delivery to rival@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=rival%40keymail.dev&from=console%40127.0.0.1%3A41297": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:38 console: could not notify rival@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:38 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A41297": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:38 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:46 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A33105": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:46 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:47 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:26:47 console: delivery to paul@keymail.dev: keymail lookup unreachable, trying email: keymail: lookup unreachable: Get "http://keymail.invalid/api/federation/lookup?addr=paul%40keymail.dev&from=console%40127.0.0.1%3A32985": dial tcp: lookup keymail.invalid on 127.0.0.53:53: no such host
2026/10/24 21:26:47 console: could not notify paul@keymail.dev of a CLI token issue: console: no email sender is configured
2026/10/24 21:26:48 edge: static oldsite/stable: resolve channel: dirstore: apps/bab/oldsite/channels/stable.json: file does not exist — if this deployment predates account-scoped bucket keys its objects are still at apps/oldsite/, which the runtime no longer reads; move them once with: carlos accounts migrate --app oldsite --from legacy --to bab
2026/10/24 21:26:48 domains: minted platform domain blog.mdbbndjqx.oncarlos.test (upstream blog.kass.test)
2026/10/24 21:26:48 domains: minted platform domain blog.gdpkwhwrw.oncarlos.test (upstream blog.woodstar.test)
2026/10/24 21:26:48 domains: minted platform domain oldsite.bab.oncarlos.test (upstream old.test)
2026/10/24 21:26:48 edge: static oldsite/stable: resolve channel: dirstore: apps/bab/oldsite/channels/stable.json: file does not exist — if this deployment predates account-scoped bucket keys its objects are still at apps/oldsite/, which the runtime no longer reads; move them once with: carlos accounts migrate --app oldsite --from legacy --to bab
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 21:26:48 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 21:26:48 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
FAIL
FAIL github.com/carlosframework/platform 89.250s
FAIL
FAILED: rest root@2
make: *** [Makefile:19: 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/activator/activator.go b/internal/activator/activator.go| index 8aa16e0f5..63f39416b 100644 |
| --- a/internal/activator/activator.go |
| +++ b/internal/activator/activator.go |
| @@ -2466,7 +2466,7 @@ func (a *Service) doWake(ctx context.Context, e *actEntry) (*wokenInstance, erro |
| ident release.StagedIdentity |
| pend pendingGeneration |
| ) |
| - env := workerEnv(a.agentSocket(), host, token) |
| + env := workerEnv(a.agentSocketFor(rt), host, token) |
| if plan.planned() { |
| // A planned worker (zero-downtime spec §6.5): the named build, |
| // behind planGate at each exec, with startWorker's publication |
| @@ -3311,6 +3311,24 @@ func (a *Service) agentSocket() string { |
| return a.cfg.AgentSocket |
| } |
| +// agentSocketRouter is a backend that knows a row's own agent socket: a |
| +// nested worker's listener in its run directory (sidecar isolation D3). |
| +type agentSocketRouter interface { |
| + perHostAgentSocket(rt registry.Route) (string, bool) |
| +} |
| + |
| +// agentSocketFor is the agent socket the worker for rt dials, workerEnv's |
| +// socket: the backend's per-host path for a nested worker row, the shared |
| +// socket (agentSocket) for every other row, exactly as before D3. |
| +func (a *Service) agentSocketFor(rt registry.Route) string { |
| + if r, ok := a.backend.(agentSocketRouter); ok { |
| + if path, ok := r.perHostAgentSocket(rt); ok { |
| + return path |
| + } |
| + } |
| + return a.agentSocket() |
| +} |
| + |
| // workerEnv is what a spawn hands the child: where to call back, who it is, |
| // and this incarnation's credential. The backend applies it OVER the tenant's |
| // own env file so a tenant cannot forge its own token (StartWorker's |
| @@ -3620,7 +3638,7 @@ func (a *Service) respawnWorkerLocked(e *actEntry, stopOld Proc, why string, ont |
| if refused == nil { |
| // No registry write (spec §7.9): see doWake. |
| if tok, err = newWorkerToken(); err == nil { |
| - proc, ident, pend, err = a.startWorker(ctx, rt, workerEnv(a.agentSocket(), host, tok), onto, gate) |
| + proc, ident, pend, err = a.startWorker(ctx, rt, workerEnv(a.agentSocketFor(rt), host, tok), onto, gate) |
| } |
| } |
| cancel() |
diff --git a/internal/activator/backend_exec.go b/internal/activator/backend_exec.go| index 6656e8ee9..4ed763f7b 100644 |
| --- a/internal/activator/backend_exec.go |
| +++ b/internal/activator/backend_exec.go |
| @@ -22,6 +22,7 @@ import ( |
| "syscall" |
| "time" |
| + "github.com/carlosframework/platform/internal/agentsock" |
| "github.com/carlosframework/platform/internal/applog" |
| "github.com/carlosframework/platform/internal/cgroup" |
| "github.com/carlosframework/platform/internal/edge" |
| @@ -188,6 +189,13 @@ type ExecBackend struct { |
| // channel not configured" rather than dialing nothing. A flat or |
| // foreign host never needs it, and a nil Control does not touch them. |
| Control Control |
| + // AgentSock is the agent socket's server, the one that serves the |
| + // shared socket (Cfg.AgentSocket). A nested worker's own listener, in |
| + // its run directory, is bound on it (sidecar isolation D3), so the two |
| + // share one verb set and one token check, and the shared socket knows |
| + // to refuse a host its pinned listener serves. Nil refuses every nested |
| + // worker's prepare: a worker with no channel back is not started. |
| + AgentSock *agentsock.Server |
| // UnitState answers `systemctl show` for the observer (design §6.5). |
| // Nil: systemctlUnitState, the real systemctl binary. |
| UnitState UnitState |
| @@ -2421,7 +2429,7 @@ func (b *ExecBackend) spawnUnitFrom(ctx context.Context, rt registry.Route, sock |
| return nil, "", err |
| } |
| } |
| - p, ctl, logs, fileEnv, keyLayer, err := b.prepareKeyedAt(rt, instancectl.RoleRun, sock, gen) |
| + p, ctl, logs, fileEnv, keyLayer, _, err := b.prepareKeyedAt(rt, instancectl.RoleRun, sock, gen) |
| if err != nil { |
| return nil, "", err |
| } |
diff --git a/internal/activator/backend_unit.go b/internal/activator/backend_unit.go| index e6b64abb3..29e89d1e6 100644 |
| --- a/internal/activator/backend_unit.go |
| +++ b/internal/activator/backend_unit.go |
| @@ -401,6 +401,18 @@ func (b *SplitBackend) Layout(rt registry.Route) (Layout, string) { |
| return LayoutFlat, "" |
| } |
| +// perHostAgentSocket forwards to the exec side: a unit-backed row is never |
| +// a nested worker, and dials the shared socket. |
| +func (b *SplitBackend) perHostAgentSocket(rt registry.Route) (string, bool) { |
| + if rt.Backing == registry.BackingUnit { |
| + return "", false |
| + } |
| + if s, ok := b.Exec.(agentSocketRouter); ok { |
| + return s.perHostAgentSocket(rt) |
| + } |
| + return "", false |
| +} |
| + |
| func (b *SplitBackend) StartSidecar(ctx context.Context, rt registry.Route) (Proc, error) { |
| return b.pick(rt).StartSidecar(ctx, rt) |
| } |
diff --git a/internal/activator/prepare.go b/internal/activator/prepare.go| index 39eadb475..3eef175ea 100644 |
| --- a/internal/activator/prepare.go |
| +++ b/internal/activator/prepare.go |
| @@ -17,6 +17,7 @@ import ( |
| "strings" |
| "time" |
| + "github.com/carlosframework/platform/internal/agentsock" |
| "github.com/carlosframework/platform/internal/edge" |
| "github.com/carlosframework/platform/internal/instancectl" |
| "github.com/carlosframework/platform/internal/instancefs" |
| @@ -46,25 +47,91 @@ func (b *ExecBackend) prepare(rt registry.Route, role instancectl.Role) (instanc |
| // gen is the generation unit being prepared (0 for the original templates): |
| // it keys the log socket and the exit records cleared (zero-downtime spec |
| // §7.6). |
| +// |
| +// The worker role is prepareWorker's: its agent listener must reach the |
| +// proc that closes it, and prepareAt has no way to hand it over. |
| func (b *ExecBackend) prepareAt(rt registry.Route, role instancectl.Role, sock string, gen uint64) (instancePaths, *edge.ControlListener, *logListener, map[string]string, error) { |
| - p, ctl, logs, env, _, err := b.prepareKeyedAt(rt, role, sock, gen) |
| + if role == instancectl.RoleWorker { |
| + return b.pathsFor(rt), nil, nil, nil, fmt.Errorf("prepare %s: the worker role is prepared by prepareWorker", rt.Host) |
| + } |
| + p, ctl, logs, env, _, _, err := b.prepareKeyedAt(rt, role, sock, gen) |
| return p, ctl, logs, env, err |
| } |
| +// prepareWorker is prepare for a nested worker (sidecar isolation D5): the |
| +// directories, the env layers read through instancefs, no admin token, the |
| +// log listener, and the worker's own agent listener (D3), bound and |
| +// answering before it returns. The caller hands logs and agent to the |
| +// worker's unitProc (newWorkerUnitProc), whose closeListeners closes both |
| +// after the proc's done, or closes them itself on a failed start. |
| +func (b *ExecBackend) prepareWorker(rt registry.Route) (instancePaths, *logListener, *agentsock.Pinned, map[string]string, instancekey.Layer, error) { |
| + p, _, logs, env, keyLayer, agent, err := b.prepareKeyedAt(rt, instancectl.RoleWorker, "", 0) |
| + return p, logs, agent, env, keyLayer, err |
| +} |
| + |
| +// agentSocketName is a nested worker's agent socket, in its run directory. |
| +const agentSocketName = "agent.sock" |
| + |
| +// agentSocketFor is the agent socket a worker for rt dials (sidecar |
| +// isolation D3): its own listener at <InstanceRunDir(host)>/agent.sock for |
| +// a nested worker row, the shared socket otherwise. |
| +func (b *ExecBackend) agentSocketFor(rt registry.Route) string { |
| + if path, ok := b.perHostAgentSocket(rt); ok { |
| + return path |
| + } |
| + if b.Cfg.AgentSocket != "" { |
| + return b.Cfg.AgentSocket |
| + } |
| + return DefaultAgentSocket |
| +} |
| + |
| +// perHostAgentSocket is agentSocketFor's per-host half: the pinned |
| +// listener's path and true for a nested worker row, false for every other |
| +// row, which dials the shared socket. The run directory honours |
| +// RunDirOverride as pathsFor does; in production it is |
| +// provision.InstanceRunDir(rt.Host). |
| +func (b *ExecBackend) perHostAgentSocket(rt registry.Route) (string, bool) { |
| + if !WorkerManaged(rt) { |
| + return "", false |
| + } |
| + p := b.pathsFor(rt) |
| + if p.layout != LayoutNested { |
| + return "", false |
| + } |
| + return filepath.Join(p.runDir, agentSocketName), true |
| +} |
| + |
| // prepareKeyedAt is prepareAt returning, with the env, the layer its |
| // CARLOS_INSTANCE_KEY came from, decided by the same read (instance-key |
| // ruling 29). The nested run spawn keeps both on the generation's handle, |
| // for a run's command against that generation (StartCmd). |
| -func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, sock string, gen uint64) (instancePaths, *edge.ControlListener, *logListener, map[string]string, instancekey.Layer, error) { |
| +// |
| +// For the worker role it also returns the worker's own agent listener |
| +// (sidecar isolation D3), nil for every other role. Every listener it bound |
| +// is closed before an error return. |
| +func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, sock string, gen uint64) (instancePaths, *edge.ControlListener, *logListener, map[string]string, instancekey.Layer, *agentsock.Pinned, error) { |
| p := b.pathsFor(rt) |
| + var agentPath string |
| + if role == instancectl.RoleWorker { |
| + // Checked before anything is made: a worker's prepare is the |
| + // nested arm's, and the listener needs a server to be bound on. |
| + path, ok := b.perHostAgentSocket(rt) |
| + if !ok { |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("prepare worker %s: not a nested worker row (layout %s)", rt.Host, p.layout) |
| + } |
| + if b.AgentSock == nil { |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("prepare worker %s: agent socket server not configured", rt.Host) |
| + } |
| + agentPath = path |
| + } |
| if err := os.MkdirAll(p.dataDir, 0o700); err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| if err := os.MkdirAll(p.runDir, 0o750|os.ModeSetgid); err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| if err := os.MkdirAll(filepath.Dir(b.logSocketFor(rt.Host, role, gen)), 0o700); err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| var env map[string]string |
| var keyLayer instancekey.Layer // the layer env's key came from, read with it (ruling 29) |
| @@ -74,15 +141,26 @@ func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, s |
| // (design §3.2), so the token file is minted and read through |
| // instancefs rather than a plain path-based open. |
| if err := EnsureAdminTokenBeneath(p.dataDir, rt.Host+".env"); err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, fmt.Errorf("admin token for %s: %w", rt.Host, err) |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("admin token for %s: %w", rt.Host, err) |
| } |
| if _, err := b.ensureInstanceKeyFor(context.Background(), rt, p); err != nil { // prepareAt has no ctx to bound the mint's bucket read; configReadTimeout does |
| - return p, nil, nil, nil, instancekey.LayerNone, fmt.Errorf("instance key for %s: %w", rt.Host, err) |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("instance key for %s: %w", rt.Host, err) |
| + } |
| + var err error |
| + env, keyLayer, err = b.instanceEnvLayer(rt) |
| + if err != nil { |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| + case instancectl.RoleWorker: |
| + // A worker's env is the instance's two layers (as StartWorkerStaged |
| + // reads them for a flat worker), read beneath the instance |
| + // directory for this nested row (instanceEnvParts). No admin token: |
| + // a worker has no control socket for one to open. No mint either: |
| + // a worker never mints its key (ruling 12). |
| var err error |
| env, keyLayer, err = b.instanceEnvLayer(rt) |
| if err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| case instancectl.RoleCmd: |
| // A run's environment is the one its generation launched with |
| @@ -93,7 +171,7 @@ func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, s |
| var err error |
| env, err = readEnvFileBeneath(p.dataDir, filepath.Base(p.sidecarEnv)) |
| if err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| keyLayer = sidecarKeyLayer(env) |
| } |
| @@ -132,12 +210,34 @@ func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, s |
| // is) can bind its socket there. |
| if dir := filepath.Dir(sock); dir != p.runDir { |
| if err := os.MkdirAll(dir, 0o750|os.ModeSetgid); err != nil { |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| } |
| _ = os.Remove(sock) |
| ctl = b.listenControl(rt, sock+".control", p.envFile, gen) |
| } |
| + // A nested worker's own agent listener (sidecar isolation D3), where |
| + // the run role binds its control listener: before the start request, |
| + // so the worker's first /v1/ready finds it, and closed with the proc's |
| + // other listeners after its done. A worker with no channel back is not |
| + // started, so a bind failure fails prepare (unlike the control |
| + // listener's, which an instance runs without). |
| + var agent *agentsock.Pinned |
| + if agentPath != "" { |
| + var err error |
| + agent, err = b.AgentSock.ListenPinned(agentPath, rt.Host) |
| + if err != nil { |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("agent listener for %s: %w", rt.Host, err) |
| + } |
| + } |
| + closeBound := func() { |
| + if ctl != nil { |
| + ctl.Close() |
| + } |
| + if agent != nil { |
| + agent.Close() |
| + } |
| + } |
| // A replica's roles are gated like a flat launch (ruling 26): the seed |
| // and the sidecar have no control socket, the run role has the one just |
| // bound or none. The sidecar's key, if its own file holds one, is |
| @@ -146,10 +246,8 @@ func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, s |
| // read none for it. |
| if role != instancectl.RoleCmd { |
| if err := b.replicaKeyGate(rt, env, keyLayer, ctl != nil); err != nil { |
| - if ctl != nil { |
| - ctl.Close() |
| - } |
| - return p, nil, nil, nil, instancekey.LayerNone, err |
| + closeBound() |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, err |
| } |
| } |
| // The cmd role's output goes only to its own chunks, never to the |
| @@ -159,13 +257,11 @@ func (b *ExecBackend) prepareKeyedAt(rt registry.Route, role instancectl.Role, s |
| var err error |
| logs, err = listenLogs(b.logSocketFor(rt.Host, role, gen), b.logEmitter(rt)) |
| if err != nil { |
| - if ctl != nil { |
| - ctl.Close() |
| - } |
| - return p, nil, nil, nil, instancekey.LayerNone, fmt.Errorf("log listener for %s %s: %w", rt.Host, role, err) |
| + closeBound() |
| + return p, nil, nil, nil, instancekey.LayerNone, nil, fmt.Errorf("log listener for %s %s: %w", rt.Host, role, err) |
| } |
| } |
| - return p, ctl, logs, env, keyLayer, nil |
| + return p, ctl, logs, env, keyLayer, agent, nil |
| } |
| // exitPrefix is the exit records prepare clears before a start: this |
diff --git a/internal/activator/prepare_test.go b/internal/activator/prepare_test.go| new file mode 100644 |
| index 000000000..3b21cb8c0 |
| --- /dev/null |
| +++ b/internal/activator/prepare_test.go |
| @@ -0,0 +1,336 @@ |
| +//go:build linux |
| + |
| +package activator |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "net" |
| + "net/http" |
| + "net/http/httptest" |
| + "os" |
| + "path/filepath" |
| + "strings" |
| + "sync" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/agentsock" |
| + "github.com/carlosframework/platform/internal/instancectl" |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/registry" |
| +) |
| + |
| +// pinWorkers is agentsock.Workers for the prepare tests: one live token per |
| +// host, and the hosts whose heartbeats got through. |
| +type pinWorkers struct { |
| + mu sync.Mutex |
| + tokens map[string]string |
| + contacts []string |
| +} |
| + |
| +func (w *pinWorkers) WorkerToken(host string) string { |
| + w.mu.Lock() |
| + defer w.mu.Unlock() |
| + return w.tokens[host] |
| +} |
| +func (w *pinWorkers) NoteWorkerContact(host string) { |
| + w.mu.Lock() |
| + defer w.mu.Unlock() |
| + w.contacts = append(w.contacts, host) |
| +} |
| +func (w *pinWorkers) SetWorkerNextDue(string, int64) error { return nil } |
| +func (w *pinWorkers) SleepWorker(string) {} |
| +func (w *pinWorkers) NoteWorkerReady(string, string) {} |
| + |
| +func (w *pinWorkers) contactCount() int { |
| + w.mu.Lock() |
| + defer w.mu.Unlock() |
| + return len(w.contacts) |
| +} |
| + |
| +const pinWorkerHost = "bot.shop.abc123.sidecar.internal" |
| + |
| +// nestedWorkerBackend is nestedBackend for a worker row in the nested |
| +// layout (D2: kind sidecar, no socket, the nested database path), with the |
| +// agent socket server wired as the agent wires it. |
| +func nestedWorkerBackend(t *testing.T, ctl *fakeControl, unit *fakeUnit) (*ExecBackend, registry.Route, *pinWorkers) { |
| + t.Helper() |
| + b, _ := nestedBackend(t, ctl, unit) |
| + rt := registry.Route{Host: pinWorkerHost, Kind: "sidecar", Args: `["work"]`, |
| + DBPath: provision.NestedDBPath(b.DataDir, pinWorkerHost)} |
| + if l, why := b.Layout(rt); l != LayoutNested { |
| + t.Fatalf("fixture worker row classifies as %s (%s), want nested", l, why) |
| + } |
| + w := &pinWorkers{tokens: map[string]string{pinWorkerHost: "tok"}} |
| + b.AgentSock = agentsock.New(w) |
| + return b, rt, w |
| +} |
| + |
| +// heartbeatOver posts one heartbeat for host over the unix socket at path. |
| +// A dial failure is returned as status 0. |
| +func heartbeatOver(path, host, tok string) int { |
| + cl := &http.Client{ |
| + Transport: &http.Transport{DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { |
| + return (&net.Dialer{}).DialContext(ctx, "unix", path) |
| + }}, |
| + Timeout: 2 * time.Second, |
| + } |
| + req, _ := http.NewRequest("POST", "http://agent/v1/heartbeat", nil) |
| + req.Header.Set("Carlos-Sidecar-Host", host) |
| + req.Header.Set("Authorization", "Bearer "+tok) |
| + resp, err := cl.Do(req) |
| + if err != nil { |
| + return 0 |
| + } |
| + resp.Body.Close() |
| + return resp.StatusCode |
| +} |
| + |
| +// sharedHeartbeat is a heartbeat for host on the shared socket's handler. |
| +func sharedHeartbeat(s *agentsock.Server, host, tok string) int { |
| + req := httptest.NewRequest("POST", "/v1/heartbeat", nil) |
| + req.Header.Set("Carlos-Sidecar-Host", host) |
| + req.Header.Set("Authorization", "Bearer "+tok) |
| + rec := httptest.NewRecorder() |
| + s.Handler().ServeHTTP(rec, req) |
| + return rec.Code |
| +} |
| + |
| +// TestPrepareWorkerBindsAgentSocketBeforeReturn: the worker's own agent |
| +// listener (D3) is at <run dir>/agent.sock, the path workerEnv hands the |
| +// worker, mode 0600, and answering the moment prepareWorker returns, with |
| +// no wait: the worker's first /v1/ready may arrive as soon as root starts |
| +// the unit. The log listener is bound too. |
| +func TestPrepareWorkerBindsAgentSocketBeforeReturn(t *testing.T) { |
| + b, rt, w := nestedWorkerBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) |
| + p, logs, agent, _, _, err := b.prepareWorker(rt) |
| + if err != nil { |
| + t.Fatalf("prepareWorker: %v", err) |
| + } |
| + defer logs.Close() |
| + defer agent.Close() |
| + |
| + want := filepath.Join(p.runDir, "agent.sock") |
| + if agent == nil || agent.Path() != want || agent.Host() != rt.Host { |
| + t.Fatalf("agent listener = %+v, want %s for %s", agent, want, rt.Host) |
| + } |
| + if got := b.agentSocketFor(rt); got != want { |
| + t.Fatalf("agentSocketFor = %s, want the listener's path %s", got, want) |
| + } |
| + // No waitFor: bound before return is the property. |
| + if code := heartbeatOver(want, rt.Host, "tok"); code != http.StatusNoContent { |
| + t.Fatalf("heartbeat on the worker's listener straight after prepare: %d, want 204", code) |
| + } |
| + if w.contactCount() != 1 { |
| + t.Fatalf("contacts = %d, want 1", w.contactCount()) |
| + } |
| + fi, err := os.Lstat(want) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if fi.Mode()&os.ModeSocket == 0 || fi.Mode().Perm() != 0o600 { |
| + t.Fatalf("agent socket mode = %v, want a 0600 socket", fi.Mode()) |
| + } |
| + c, err := net.Dial("unix", b.logSocket(rt.Host, instancectl.RoleWorker)) |
| + if err != nil { |
| + t.Fatalf("worker log listener not bound: %v", err) |
| + } |
| + c.Close() |
| + if !strings.HasSuffix(b.logSocket(rt.Host, instancectl.RoleWorker), ".worker.sock") { |
| + t.Fatalf("log socket %s is not the worker template's", b.logSocket(rt.Host, instancectl.RoleWorker)) |
| + } |
| +} |
| + |
| +// TestPrepareWorkerHasNoAdminToken: a worker has no control socket, so its |
| +// prepare mints no admin token (the run role's does), and its env is the |
| +// instance's two layers read beneath the instance directory, own over |
| +// config, as a flat worker's are. A symlink planted at the env file is not |
| +// followed. |
| +func TestPrepareWorkerHasNoAdminToken(t *testing.T) { |
| + b, rt, _ := nestedWorkerBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) |
| + inst := provision.InstanceDir(b.DataDir, rt.Host) |
| + if err := os.MkdirAll(inst, 0o700); err != nil { |
| + t.Fatal(err) |
| + } |
| + own := filepath.Join(inst, rt.Host+".env") |
| + if err := os.WriteFile(filepath.Join(inst, rt.Host+".config.env"), []byte("SHARED=cfg\nONLY_CFG=1\n"), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(own, []byte("SHARED=own\n"), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + _, logs, agent, env, _, err := b.prepareWorker(rt) |
| + if err != nil { |
| + t.Fatalf("prepareWorker: %v", err) |
| + } |
| + logs.Close() |
| + agent.Close() |
| + if _, ok := env["CARLOS_ADMIN_TOKEN"]; ok { |
| + t.Fatal("a worker's env carries CARLOS_ADMIN_TOKEN") |
| + } |
| + if env["SHARED"] != "own" || env["ONLY_CFG"] != "1" { |
| + t.Fatalf("env = %v, want the two layers with own over config", env) |
| + } |
| + raw, err := os.ReadFile(own) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if string(raw) != "SHARED=own\n" { |
| + t.Fatalf("prepareWorker wrote the env file (an admin token?): %q", raw) |
| + } |
| + |
| + // Through instancefs: a link at the env file is refused, not read. |
| + outside := filepath.Join(t.TempDir(), "elsewhere.env") |
| + if err := os.WriteFile(outside, []byte("STOLEN=1\n"), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Remove(own); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Symlink(outside, own); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, logs, agent, env, _, err := b.prepareWorker(rt); err == nil { |
| + logs.Close() |
| + agent.Close() |
| + t.Fatalf("prepareWorker followed a symlinked env file: env %v", env) |
| + } |
| + if _, err := os.Lstat(filepath.Join(b.pathsFor(rt).runDir, "agent.sock")); !os.IsNotExist(err) { |
| + t.Fatalf("a failed prepare left the agent socket bound: %v", err) |
| + } |
| +} |
| + |
| +// TestPrepareWorkerAgentListenerClosesAfterDone: the worker's unitProc |
| +// owns the agent listener. An unproven stop leaves it answering (a worker |
| +// that may still run keeps its channel), and once the unit is proven gone |
| +// closeListeners closes it with no second Stop: the socket file is gone and |
| +// the host is the shared socket's again. |
| +func TestPrepareWorkerAgentListenerClosesAfterDone(t *testing.T) { |
| + ctl := &fakeControl{inv: "inv1"} |
| + unit := &fakeUnit{} |
| + b, rt, _ := nestedWorkerBackend(t, ctl, unit) |
| + p, logs, agent, _, _, err := b.prepareWorker(rt) |
| + if err != nil { |
| + t.Fatalf("prepareWorker: %v", err) |
| + } |
| + if code := sharedHeartbeat(b.AgentSock, rt.Host, "tok"); code != http.StatusUnauthorized { |
| + t.Fatalf("the moved worker's token on the shared socket: %d, want 401", code) |
| + } |
| + unit.set("active", "inv1", true, true) |
| + up := b.newWorkerUnitProc(rt, p, "inv1", logs, agent) |
| + if up.role != instancectl.RoleWorker || up.unit != instancectl.UnitName(instancectl.RoleWorker, rt.Host) { |
| + t.Fatalf("proc = role %s unit %s", up.role, up.unit) |
| + } |
| + |
| + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) |
| + defer cancel() |
| + if err := up.Stop(ctx); !errors.Is(err, ErrStillRunning) { |
| + t.Fatalf("Stop = %v, want ErrStillRunning", err) |
| + } |
| + if code := heartbeatOver(agent.Path(), rt.Host, "tok"); code != http.StatusNoContent { |
| + t.Fatalf("agent listener after an unproven stop: %d, want 204", code) |
| + } |
| + |
| + unit.set("inactive", "inv1", false, false) |
| + waitFor(t, "the worker unit to exit", func() bool { return up.Exited() }) |
| + waitFor(t, "the agent listener to close", func() bool { |
| + _, err := os.Lstat(agent.Path()) |
| + return os.IsNotExist(err) |
| + }) |
| + if code := heartbeatOver(agent.Path(), rt.Host, "tok"); code != 0 { |
| + t.Fatalf("agent listener still answers after done: %d", code) |
| + } |
| + if code := sharedHeartbeat(b.AgentSock, rt.Host, "tok"); code != http.StatusNoContent { |
| + t.Fatalf("the host on the shared socket once its listener closed: %d, want 204", code) |
| + } |
| +} |
| + |
| +// TestPrepareWorkerFailedGateClosesTheAgentListener: a refusal after the |
| +// listener is bound (here the replica key gate: a replica worker with no |
| +// verified home key) closes it before prepare returns. |
| +func TestPrepareWorkerFailedGateClosesTheAgentListener(t *testing.T) { |
| + b, rt, _ := nestedWorkerBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) |
| + rt.Replica = true |
| + if _, logs, agent, _, _, err := b.prepareWorker(rt); err == nil { |
| + logs.Close() |
| + agent.Close() |
| + t.Fatal("a keyless replica worker was prepared") |
| + } |
| + if _, err := os.Lstat(filepath.Join(b.pathsFor(rt).runDir, "agent.sock")); !os.IsNotExist(err) { |
| + t.Fatalf("the refused prepare left the agent socket: %v", err) |
| + } |
| + if code := sharedHeartbeat(b.AgentSock, rt.Host, "tok"); code != http.StatusNoContent { |
| + t.Fatalf("the host stayed pinned after a refused prepare: %d", code) |
| + } |
| +} |
| + |
| +// TestPrepareWorkerRefusesWhatItCannotServe: a flat worker row, a backend |
| +// with no agent socket server, and the generic prepare entry point are all |
| +// refused before anything is bound. |
| +func TestPrepareWorkerRefusesWhatItCannotServe(t *testing.T) { |
| + b, rt, _ := nestedWorkerBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) |
| + |
| + flat := rt |
| + flat.DBPath = provision.DefaultDBPath(b.DataDir, rt.Host) |
| + if _, _, _, _, _, err := b.prepareWorker(flat); err == nil { |
| + t.Fatal("prepareWorker bound a pinned listener for a flat worker row") |
| + } |
| + |
| + if _, _, _, _, err := b.prepare(rt, instancectl.RoleWorker); err == nil { |
| + t.Fatal("prepare(RoleWorker) succeeded; its agent listener would have no owner") |
| + } |
| + |
| + b.AgentSock = nil |
| + if _, _, _, _, _, err := b.prepareWorker(rt); err == nil { |
| + t.Fatal("prepareWorker with no agent socket server succeeded") |
| + } |
| + if _, err := os.Lstat(filepath.Join(b.pathsFor(rt).runDir, "agent.sock")); !os.IsNotExist(err) { |
| + t.Fatalf("a refused prepare left an agent socket: %v", err) |
| + } |
| +} |
| + |
| +// TestAgentSocketForNestedWorkerIsPerHost is workerEnv's socket argument |
| +// (D3, D5): the per-host path for a nested worker row, and for every other |
| +// row the shared socket exactly as before, through the composed backend |
| +// the agent runs. |
| +func TestAgentSocketForNestedWorkerIsPerHost(t *testing.T) { |
| + b, rt, _ := nestedWorkerBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) |
| + perHost := filepath.Join(b.RunDirOverride, "instances", provision.InstanceID(rt.Host), "agent.sock") |
| + |
| + flatWorker := rt |
| + flatWorker.DBPath = provision.DefaultDBPath(b.DataDir, rt.Host) |
| + _, instance := nestedBackend(t, &fakeControl{}, &fakeUnit{}) |
| + |
| + if got := b.agentSocketFor(rt); got != perHost { |
| + t.Errorf("nested worker: %s, want %s", got, perHost) |
| + } |
| + if got := b.agentSocketFor(flatWorker); got != DefaultAgentSocket { |
| + t.Errorf("flat worker, default config: %s, want %s", got, DefaultAgentSocket) |
| + } |
| + b.Cfg.AgentSocket = "/s/agent.sock" |
| + if got := b.agentSocketFor(flatWorker); got != "/s/agent.sock" { |
| + t.Errorf("flat worker: %s, want the configured shared socket", got) |
| + } |
| + |
| + a := New(Config{AgentSocket: "/shared/agent.sock"}, &SplitBackend{Exec: b, Unit: &UnitBackend{}}, nil) |
| + for _, tc := range []struct { |
| + name string |
| + rt registry.Route |
| + want string |
| + }{ |
| + {"nested worker", rt, perHost}, |
| + {"flat worker", flatWorker, "/shared/agent.sock"}, |
| + {"instance row", instance, "/shared/agent.sock"}, |
| + {"unit-backed worker", registry.Route{Host: rt.Host, Kind: "sidecar", Backing: registry.BackingUnit, DBPath: rt.DBPath}, "/shared/agent.sock"}, |
| + } { |
| + if got := a.agentSocketFor(tc.rt); got != tc.want { |
| + t.Errorf("Service %s: %s, want %s", tc.name, got, tc.want) |
| + } |
| + } |
| + // A backend that knows nothing of per-host sockets keeps the shared one. |
| + if got := New(Config{AgentSocket: "/shared/agent.sock"}, &fakeBackend{}, nil).agentSocketFor(rt); got != "/shared/agent.sock" { |
| + t.Errorf("plain backend: %s, want the shared socket", got) |
| + } |
| +} |
diff --git a/internal/activator/unitproc.go b/internal/activator/unitproc.go| index b69a2eab4..8e877b142 100644 |
| --- a/internal/activator/unitproc.go |
| +++ b/internal/activator/unitproc.go |
| @@ -11,6 +11,7 @@ import ( |
| "sync/atomic" |
| "time" |
| + "github.com/carlosframework/platform/internal/agentsock" |
| "github.com/carlosframework/platform/internal/edge" |
| "github.com/carlosframework/platform/internal/instancectl" |
| "github.com/carlosframework/platform/internal/instancefs" |
| @@ -73,6 +74,10 @@ type unitProc struct { |
| // from, decided by the read that composed launchEnv (instance-key |
| // ruling 29). Written with launchEnv, never again. |
| launchKeyLayer instancekey.Layer |
| + // agent is a nested worker's own agent listener (sidecar isolation |
| + // D3), nil for every other role. closeListeners closes it with ctl and |
| + // logs, after done. |
| + agent *agentsock.Pinned |
| } |
| // launchEnvProc is a generation handle that kept the file environment its |
| @@ -106,6 +111,17 @@ func (b *ExecBackend) newUnitProcFor(rt registry.Route, role instancectl.Role, g |
| return up |
| } |
| +// newWorkerUnitProc is a nested worker's proc (sidecar isolation D5): the |
| +// worker role's unit, with the log listener and the worker's own agent |
| +// listener prepareWorker bound, both closed by closeListeners after done. |
| +// The listener is set before observe starts, so no close can miss it. |
| +func (b *ExecBackend) newWorkerUnitProc(rt registry.Route, p instancePaths, inv string, logs *logListener, agent *agentsock.Pinned) *unitProc { |
| + up := &unitProc{b: b, rt: rt, role: instancectl.RoleWorker, unit: unitName(instancectl.RoleWorker, rt.Host, 0), slice: instancectl.SliceName(rt.Host), |
| + invocation: inv, runDir: p.runDir, dataDir: p.dataDir, logs: logs, agent: agent, done: make(chan struct{})} |
| + go b.observe(up) |
| + return up |
| +} |
| + |
| // unitName is the unit a role of host runs as: the original template's, or |
| // generation gen's (zero-downtime spec §7.6). |
| func unitName(role instancectl.Role, host string, gen uint64) string { |
| @@ -210,6 +226,9 @@ func (p *unitProc) closeListeners() { |
| if p.logs != nil { |
| p.logs.Close() |
| } |
| + if p.agent != nil { |
| + p.agent.Close() |
| + } |
| } |
| // observe is the goroutine of design §6.5. It proves termination from two |
diff --git a/internal/agentsock/agentsock.go b/internal/agentsock/agentsock.go| index 4158ee387..d2d0d3c5c 100644 |
| --- a/internal/agentsock/agentsock.go |
| +++ b/internal/agentsock/agentsock.go |
| @@ -39,6 +39,7 @@ import ( |
| "net/http" |
| "os" |
| "strings" |
| + "sync" |
| "time" |
| ) |
| @@ -74,6 +75,11 @@ const ( |
| // tell one child from another. The token does that. |
| socketMode = 0o660 |
| + // pinnedMode is a pinned listener's mode (sidecar isolation D3): the |
| + // agent's alone. The run directory it sits in is group-traversable by |
| + // enrolled cross-user consumers; the socket is not theirs to dial. |
| + pinnedMode = 0o600 |
| + |
| // Local calls, all of them. Nothing here should ever be slow, and a |
| // child that stalls mid-request must not pin a connection. |
| readHeaderTimeout = 2 * time.Second |
| @@ -88,32 +94,53 @@ const ( |
| ) |
| // Server serves the agent socket. Zero per-request state: one instance |
| -// answers every child on the box concurrently. |
| +// answers every child on the box concurrently. Its one piece of state is |
| +// which hosts are currently served by a pinned listener of their own |
| +// (ListenPinned), which the shared socket then refuses. |
| type Server struct { |
| w Workers |
| + |
| + mu sync.Mutex |
| + pinned map[string]int // host -> live pinned listeners for it |
| } |
| // New builds the socket server over w. |
| func New(w Workers) *Server { return &Server{w: w} } |
| -// Handler is the routing table, exported so tests can exercise it without |
| -// binding anything. Every verb is POST-only: the method pattern means a GET |
| -// is turned away by the router, before authorize and before any effect. |
| -func (s *Server) Handler() http.Handler { |
| +// Handler is the shared socket's routing table, exported so tests can |
| +// exercise it without binding anything. Every verb is POST-only: the method |
| +// pattern means a GET is turned away by the router, before authorize and |
| +// before any effect. |
| +func (s *Server) Handler() http.Handler { return s.handler("") } |
| + |
| +// handler is the routing table for one listener: the shared socket's when |
| +// pin is "", or the pinned listener's for host pin. The verbs and their |
| +// bodies are identical; only guard's admission differs. |
| +func (s *Server) handler(pin string) http.Handler { |
| mux := http.NewServeMux() |
| - mux.Handle("POST /v1/ready", s.guard(s.ready)) |
| - mux.Handle("POST /v1/next-due", s.guard(s.nextDue)) |
| - mux.Handle("POST /v1/idle", s.guard(s.idle)) |
| - mux.Handle("POST /v1/heartbeat", s.guard(s.heartbeat)) |
| + mux.Handle("POST /v1/ready", s.guard(pin, s.ready)) |
| + mux.Handle("POST /v1/next-due", s.guard(pin, s.nextDue)) |
| + mux.Handle("POST /v1/idle", s.guard(pin, s.idle)) |
| + mux.Handle("POST /v1/heartbeat", s.guard(pin, s.heartbeat)) |
| return mux |
| } |
| // guard is the single gate. Nothing reaches a verb without passing it, and a |
| // verb never sees the request's claimed host — only the authorized one, so |
| // there is no way for a handler to act on a host it was not entitled to. |
| -func (s *Server) guard(fn func(http.ResponseWriter, *http.Request, string)) http.Handler { |
| +// |
| +// On a pinned listener (pin != "") the pin is checked first: the request |
| +// must carry exactly one Carlos-Sidecar-Host value, equal to pin. Anything |
| +// else (absent, repeated, another host) gets the same refusal as a wrong |
| +// token, before the token is compared. The pin is not a secret (the caller |
| +// dialled the listener's path), so the early return leaks nothing. |
| +func (s *Server) guard(pin string, fn func(http.ResponseWriter, *http.Request, string)) http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| - host, ok := s.authorize(r) |
| + if pin != "" && !pinnedTo(r, pin) { |
| + refuse(w) |
| + return |
| + } |
| + host, ok := s.authorize(r, pin != "") |
| if !ok { |
| refuse(w) |
| return |
| @@ -122,6 +149,38 @@ func (s *Server) guard(fn func(http.ResponseWriter, *http.Request, string)) http |
| }) |
| } |
| +// pinnedTo reports whether r names exactly host, once. Header.Get would |
| +// take the first of several values; a request that names two hosts is |
| +// refused instead, so which value "counts" is never a question. |
| +func pinnedTo(r *http.Request, host string) bool { |
| + v := r.Header.Values(hostHeader) |
| + return len(v) == 1 && v[0] == host |
| +} |
| + |
| +// isPinned reports whether host is currently served by a pinned listener. |
| +func (s *Server) isPinned(host string) bool { |
| + s.mu.Lock() |
| + defer s.mu.Unlock() |
| + return s.pinned[host] > 0 |
| +} |
| + |
| +func (s *Server) pin(host string) { |
| + s.mu.Lock() |
| + defer s.mu.Unlock() |
| + if s.pinned == nil { |
| + s.pinned = map[string]int{} |
| + } |
| + s.pinned[host]++ |
| +} |
| + |
| +func (s *Server) unpin(host string) { |
| + s.mu.Lock() |
| + defer s.mu.Unlock() |
| + if s.pinned[host]--; s.pinned[host] <= 0 { |
| + delete(s.pinned, host) |
| + } |
| +} |
| + |
| // authorize resolves the caller to the one host it may speak about. |
| // |
| // The dangerous shape here is WorkerToken returning "" — unknown host, asleep, |
| @@ -141,12 +200,18 @@ func (s *Server) guard(fn func(http.ResponseWriter, *http.Request, string)) http |
| // The compare itself is constant-time, and both failure paths — no live token, |
| // wrong live token — run the same code, so the structure carries no timing |
| // signal beyond the length leak inherent to any comparison. |
| -func (s *Server) authorize(r *http.Request) (string, bool) { |
| +// |
| +// onPinned says the request arrived on a host's pinned listener (guard has |
| +// already checked the pin). On the shared socket a host that is served by a |
| +// pinned listener has no token here (sidecar isolation D3, Review Focus 4): |
| +// a nested worker's token, leaked or not, is useless on the shared socket, |
| +// and the refusal runs the same compare against an unmatchable value. |
| +func (s *Server) authorize(r *http.Request, onPinned bool) (string, bool) { |
| host := r.Header.Get(hostHeader) |
| presented := bearer(r.Header.Get("Authorization")) |
| want := "" |
| - if host != "" { |
| + if host != "" && (onPinned || !s.isPinned(host)) { |
| want = s.w.WorkerToken(host) |
| } |
| if want == "" { |
| @@ -277,15 +342,44 @@ func displayable(s string) bool { |
| // reach. So we probe first, and refuse rather than steal — and, for the same |
| // reason, this function unlinks nothing it did not create (see below). |
| func (s *Server) Serve(ctx context.Context, path string) error { |
| + ln, err := bind(path, socketMode) |
| + if err != nil { |
| + return err |
| + } |
| + srv := newHTTPServer(s.Handler()) |
| + |
| + errc := make(chan error, 1) |
| + go func() { errc <- srv.Serve(ln) }() |
| + |
| + select { |
| + case err := <-errc: |
| + if errors.Is(err, http.ErrServerClosed) { |
| + return nil |
| + } |
| + return err |
| + case <-ctx.Done(): |
| + // ctx is already cancelled, so Shutdown gets its own deadline. |
| + sctx, cancel := context.WithTimeout(context.Background(), shutdownGrace) |
| + defer cancel() |
| + err := srv.Shutdown(sctx) |
| + <-errc |
| + return err |
| + } |
| +} |
| + |
| +// bind takes path for a new listener with mode, Serve's stale-file rules: |
| +// a live listener is refused, a stale file is replaced. The mode is set |
| +// before bind returns, so nothing is served on the umask's mode. |
| +func bind(path string, mode os.FileMode) (net.Listener, error) { |
| if listening(path) { |
| - return fmt.Errorf("agentsock: %s is already served by a live listener", path) |
| + return nil, fmt.Errorf("agentsock: %s is already served by a live listener", path) |
| } |
| if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { |
| - return fmt.Errorf("agentsock: remove stale socket %s: %w", path, err) |
| + return nil, fmt.Errorf("agentsock: remove stale socket %s: %w", path, err) |
| } |
| ln, err := net.Listen("unix", path) |
| if err != nil { |
| - return fmt.Errorf("agentsock: listen on %s: %w", path, err) |
| + return nil, fmt.Errorf("agentsock: listen on %s: %w", path, err) |
| } |
| // Cleanup is the LISTENER's job and nothing else's. Go unlinks a unix |
| // socket it created when the listener closes, and that unlink happens |
| @@ -304,37 +398,101 @@ func (s *Server) Serve(ctx context.Context, path string) error { |
| ul.SetUnlinkOnClose(true) // the default; pinned because it is load-bearing |
| } |
| - if err := os.Chmod(path, socketMode); err != nil { |
| + // A path-based chmod follows a symlink. The shared socket's directory |
| + // is the agent's own; a pinned listener's run directory is on the |
| + // nosymfollow mount (sidecar isolation D3), so a link planted between |
| + // the bind and this call is not followed there either. |
| + if err := os.Chmod(path, mode); err != nil { |
| ln.Close() // unlinks the socket we just created, and only that one |
| - return fmt.Errorf("agentsock: chmod %s: %w", path, err) |
| + return nil, fmt.Errorf("agentsock: chmod %s: %w", path, err) |
| } |
| + return ln, nil |
| +} |
| - srv := &http.Server{ |
| - Handler: s.Handler(), |
| +func newHTTPServer(h http.Handler) *http.Server { |
| + return &http.Server{ |
| + Handler: h, |
| ReadHeaderTimeout: readHeaderTimeout, |
| ReadTimeout: readTimeout, |
| WriteTimeout: writeTimeout, |
| IdleTimeout: idleTimeout, |
| MaxHeaderBytes: 1 << 14, |
| } |
| +} |
| - errc := make(chan error, 1) |
| - go func() { errc <- srv.Serve(ln) }() |
| +// Pinned is a listener bound for one host (sidecar isolation D3): a nested |
| +// worker's own agent socket, in its run directory. It is served by the same |
| +// Server as the shared socket, with the same verbs and the same token check, |
| +// and admits only requests that name its host (guard). While it is open the |
| +// shared socket refuses that host. |
| +type Pinned struct { |
| + s *Server |
| + path string |
| + host string |
| + srv *http.Server |
| + errc chan error |
| - select { |
| - case err := <-errc: |
| - if errors.Is(err, http.ErrServerClosed) { |
| - return nil |
| - } |
| - return err |
| - case <-ctx.Done(): |
| - // ctx is already cancelled, so Shutdown gets its own deadline. |
| + once sync.Once |
| + closeErr error |
| +} |
| + |
| +// ListenPinned binds path for host, mode 0600, and starts serving it. The |
| +// socket answers by the time ListenPinned returns; Close stops it and |
| +// removes the socket file. A path a live listener serves is refused, as |
| +// Serve refuses one. |
| +func (s *Server) ListenPinned(path, host string) (*Pinned, error) { |
| + if host == "" { |
| + return nil, errors.New("agentsock: a pinned listener needs a host") |
| + } |
| + ln, err := bind(path, pinnedMode) |
| + if err != nil { |
| + return nil, err |
| + } |
| + // Pinned before the first request can arrive: from here on the shared |
| + // socket refuses host. |
| + s.pin(host) |
| + p := &Pinned{s: s, path: path, host: host, srv: newHTTPServer(s.handler(host)), errc: make(chan error, 1)} |
| + go func() { p.errc <- p.srv.Serve(ln) }() |
| + return p, nil |
| +} |
| + |
| +// Path is the socket's path. |
| +func (p *Pinned) Path() string { return p.path } |
| + |
| +// Host is the host the listener answers for. |
| +func (p *Pinned) Host() string { return p.host } |
| + |
| +// Close stops serving, closes the listener (which unlinks the socket file |
| +// it created, and only that one) and returns host to the shared socket. |
| +// Safe to call more than once and concurrently; every call returns the |
| +// first call's result. |
| +func (p *Pinned) Close() error { |
| + p.once.Do(func() { |
| sctx, cancel := context.WithTimeout(context.Background(), shutdownGrace) |
| defer cancel() |
| - err := srv.Shutdown(sctx) |
| - <-errc |
| + p.closeErr = p.srv.Shutdown(sctx) |
| + if err := <-p.errc; !errors.Is(err, http.ErrServerClosed) && p.closeErr == nil { |
| + p.closeErr = err |
| + } |
| + p.s.unpin(p.host) |
| + }) |
| + return p.closeErr |
| +} |
| + |
| +// ServePinned is ListenPinned for host at path, served until ctx is done |
| +// (or the server fails), then closed: it returns with the socket file gone. |
| +func (s *Server) ServePinned(ctx context.Context, path, host string) error { |
| + p, err := s.ListenPinned(path, host) |
| + if err != nil { |
| return err |
| } |
| + select { |
| + case <-ctx.Done(): |
| + case err := <-p.errc: |
| + // The server stopped on its own; hand the error back to Close. |
| + p.errc <- err |
| + } |
| + return p.Close() |
| } |
| // listening reports whether something is currently accepting on path. A stale |
diff --git a/internal/agentsock/agentsock_test.go b/internal/agentsock/agentsock_test.go| index ea46b0fe0..111a7887d 100644 |
| --- a/internal/agentsock/agentsock_test.go |
| +++ b/internal/agentsock/agentsock_test.go |
| @@ -3,6 +3,7 @@ package agentsock |
| import ( |
| "context" |
| "errors" |
| + "io" |
| "net" |
| "net/http" |
| "net/http/httptest" |
| @@ -585,3 +586,292 @@ func TestAgentSockConcurrentCallers(t *testing.T) { |
| t.Fatalf("contacts = %d, want 25 — only a.x.internal's own token may stamp it", contacts) |
| } |
| } |
| + |
| +// --- the host-pinned listener (sidecar isolation D3) --- |
| + |
| +// shortSockDir keeps a unix socket path under the kernel's length limit: |
| +// t.TempDir() nests the test's name, which these long names overrun. |
| +func shortSockDir(t *testing.T) string { |
| + t.Helper() |
| + dir, err := os.MkdirTemp("", "as") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { os.RemoveAll(dir) }) |
| + return dir |
| +} |
| + |
| +// pinnedCall is one POST over the unix socket at path, with the host |
| +// headers exactly as given (none, one, or several) and the bearer token. |
| +// It returns the status and the body, so a refusal's wire shape can be |
| +// compared with the shared socket's. |
| +func pinnedCall(t *testing.T, path, verb string, hosts []string, tok, body string) (int, string) { |
| + t.Helper() |
| + cl := &http.Client{ |
| + Transport: &http.Transport{ |
| + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { |
| + return (&net.Dialer{}).DialContext(ctx, "unix", path) |
| + }, |
| + }, |
| + Timeout: 5 * time.Second, |
| + } |
| + req, err := http.NewRequest("POST", "http://agent"+verb, strings.NewReader(body)) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, h := range hosts { |
| + req.Header.Add("Carlos-Sidecar-Host", h) |
| + } |
| + if tok != "" { |
| + req.Header.Set("Authorization", "Bearer "+tok) |
| + } |
| + resp, err := cl.Do(req) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer resp.Body.Close() |
| + raw, _ := io.ReadAll(resp.Body) |
| + return resp.StatusCode, string(raw) |
| +} |
| + |
| +// servePinned runs ServePinned for host at a fresh path and waits until it |
| +// answers. The returned stop cancels it and reports ServePinned's error. |
| +func servePinned(t *testing.T, s *Server, host string) (path string, stop func() error) { |
| + t.Helper() |
| + path = filepath.Join(shortSockDir(t), "agent.sock") |
| + ctx, cancel := context.WithCancel(context.Background()) |
| + done := make(chan error, 1) |
| + go func() { done <- s.ServePinned(ctx, path, host) }() |
| + waitFor(t, "pinned socket to answer", func() bool { |
| + c, err := net.Dial("unix", path) |
| + if err != nil { |
| + return false |
| + } |
| + c.Close() |
| + return true |
| + }) |
| + var once sync.Once |
| + var err error |
| + stop = func() error { |
| + once.Do(func() { cancel(); err = <-done }) |
| + return err |
| + } |
| + t.Cleanup(func() { stop() }) |
| + return path, stop |
| +} |
| + |
| +// sharedRefusal is the shared socket's 401 body, the one every pinned |
| +// refusal must match. |
| +func sharedRefusal(t *testing.T) string { |
| + t.Helper() |
| + rec := post(New(&fakeWorkers{}).Handler(), "/v1/heartbeat", "ghost.x.internal", "nope", "") |
| + if rec.Code != http.StatusUnauthorized { |
| + t.Fatalf("shared refusal code = %d", rec.Code) |
| + } |
| + return rec.Body.String() |
| +} |
| + |
| +// TestPinnedSocketRefusesOtherHost is Review Focus 4's first half: a token |
| +// leaked from worker B is useless on worker A's listener, whether the |
| +// caller names B (the token's own host) or A (the listener's), and the |
| +// refusal is the shared socket's uniform 401 with no effect at all. |
| +func TestPinnedSocketRefusesOtherHost(t *testing.T) { |
| + const a, b = "a.x.internal", "b.x.internal" |
| + f := &fakeWorkers{tokens: map[string]string{a: "tok-a", b: "tok-b"}} |
| + path, stop := servePinned(t, New(f), a) |
| + want := sharedRefusal(t) |
| + |
| + for _, tc := range []struct { |
| + name, host, tok string |
| + }{ |
| + {"B names itself with its own live token", b, "tok-b"}, |
| + {"B's token naming A", a, "tok-b"}, |
| + {"an unknown host", "ghost.x.internal", "tok-a"}, |
| + {"A's token naming B", b, "tok-a"}, |
| + } { |
| + for _, verb := range allVerbs { |
| + body := "" |
| + switch verb { |
| + case "/v1/ready": |
| + body = `{"version":"v1"}` |
| + case "/v1/next-due": |
| + body = `{"at":1756}` |
| + } |
| + code, got := pinnedCall(t, path, verb, []string{tc.host}, tc.tok, body) |
| + if code != http.StatusUnauthorized || got != want { |
| + t.Fatalf("%s %s: %d %q, want the uniform 401 %q", tc.name, verb, code, got, want) |
| + } |
| + } |
| + } |
| + f.assertNoEffects(t, "pinned listener, another host") |
| + |
| + // The listener's own host with its own token still gets through: the |
| + // pin narrows the audience, it does not replace the token check. |
| + if code, _ := pinnedCall(t, path, "/v1/heartbeat", []string{a}, "tok-a", ""); code != http.StatusNoContent { |
| + t.Fatalf("A on its own listener: %d, want 204", code) |
| + } |
| + if code, _ := pinnedCall(t, path, "/v1/heartbeat", []string{a}, "wrong", ""); code != http.StatusUnauthorized { |
| + t.Fatalf("A with a wrong token on its own listener: %d, want 401", code) |
| + } |
| + contacts, _, _, _ := f.effects() |
| + if contacts != 1 { |
| + t.Fatalf("contacts = %d, want exactly A's one authorized heartbeat", contacts) |
| + } |
| + |
| + if err := stop(); err != nil { |
| + t.Fatalf("ServePinned returned %v on a clean shutdown", err) |
| + } |
| + if _, err := os.Lstat(path); !os.IsNotExist(err) { |
| + t.Fatalf("the pinned socket outlived its listener: %v", err) |
| + } |
| +} |
| + |
| +// TestPinnedSocketRefusesRepeatedHostHeader: the pin is one exact header |
| +// value. Two values, even two copies of the listener's own host, are not |
| +// "this host", and refusing them keeps Header.Get's first-value choice from |
| +// ever being what decides whom a request speaks for. |
| +func TestPinnedSocketRefusesRepeatedHostHeader(t *testing.T) { |
| + const a = "a.x.internal" |
| + f := &fakeWorkers{tokens: map[string]string{a: "tok-a", "b.x.internal": "tok-b"}} |
| + path, _ := servePinned(t, New(f), a) |
| + want := sharedRefusal(t) |
| + for _, hosts := range [][]string{ |
| + {a, a}, |
| + {a, "b.x.internal"}, |
| + {"b.x.internal", a}, |
| + {a + ", " + a}, |
| + {"A.x.internal"}, |
| + } { |
| + if code, got := pinnedCall(t, path, "/v1/heartbeat", hosts, "tok-a", ""); code != http.StatusUnauthorized || got != want { |
| + t.Fatalf("hosts %q: %d %q, want the uniform 401", hosts, code, got) |
| + } |
| + } |
| + f.assertNoEffects(t, "repeated host header") |
| +} |
| + |
| +// TestPinnedSocketRefusesAbsentHostHeader: a caller that names no host is |
| +// refused on a pinned listener too, though the listener knows which host |
| +// it serves. Filling the host in for it would make the header optional, |
| +// and the shared socket's contract (name yourself, then prove it) would |
| +// differ between the two listeners. |
| +func TestPinnedSocketRefusesAbsentHostHeader(t *testing.T) { |
| + const a = "a.x.internal" |
| + f := &fakeWorkers{tokens: map[string]string{a: "tok-a"}} |
| + path, _ := servePinned(t, New(f), a) |
| + want := sharedRefusal(t) |
| + for _, verb := range allVerbs { |
| + if code, got := pinnedCall(t, path, verb, nil, "tok-a", `{"version":"v1","at":1}`); code != http.StatusUnauthorized || got != want { |
| + t.Fatalf("%s with no host header: %d %q, want the uniform 401", verb, code, got) |
| + } |
| + } |
| + if code, got := pinnedCall(t, path, "/v1/heartbeat", []string{""}, "tok-a", ""); code != http.StatusUnauthorized || got != want { |
| + t.Fatalf("an empty host header: %d %q, want the uniform 401", code, got) |
| + } |
| + f.assertNoEffects(t, "absent host header") |
| +} |
| + |
| +// TestPinnedSocketMode0600: the pinned socket is the agent's alone (the |
| +// run directory is group-traversable by enrolled consumers, D3), and the |
| +// mode is set before ListenPinned returns, so a caller holding the |
| +// listener never sees the umask's mode. |
| +func TestPinnedSocketMode0600(t *testing.T) { |
| + path := filepath.Join(shortSockDir(t), "agent.sock") |
| + pl, err := New(&fakeWorkers{}).ListenPinned(path, "a.x.internal") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer pl.Close() |
| + fi, err := os.Lstat(path) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if fi.Mode()&os.ModeSocket == 0 { |
| + t.Fatalf("%s is %v, not a socket", path, fi.Mode()) |
| + } |
| + if perm := fi.Mode().Perm(); perm != 0o600 { |
| + t.Fatalf("pinned socket mode = %o, want 600", perm) |
| + } |
| + if err := pl.Close(); err != nil { |
| + t.Fatalf("Close: %v", err) |
| + } |
| + if err := pl.Close(); err != nil { |
| + t.Fatalf("a second Close must be harmless: %v", err) |
| + } |
| + if _, err := os.Lstat(path); !os.IsNotExist(err) { |
| + t.Fatalf("Close left the socket file behind: %v", err) |
| + } |
| +} |
| + |
| +// TestPinnedSocketRefusesALiveSocket: a path a listener is already serving |
| +// is not stale, on the pinned listener as on the shared one. |
| +func TestPinnedSocketRefusesALiveSocket(t *testing.T) { |
| + path := filepath.Join(shortSockDir(t), "agent.sock") |
| + s := New(&fakeWorkers{}) |
| + first, err := s.ListenPinned(path, "a.x.internal") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer first.Close() |
| + if second, err := s.ListenPinned(path, "a.x.internal"); err == nil { |
| + second.Close() |
| + t.Fatal("ListenPinned took over a path a live listener serves") |
| + } |
| + if _, err := os.Lstat(path); err != nil { |
| + t.Fatalf("the live pinned socket was unlinked: %v", err) |
| + } |
| +} |
| + |
| +// TestWorkerTokenRefusedOnSharedSocketAfterMove is Review Focus 4's second |
| +// half. Once a host is served by its own pinned listener (it moved to |
| +// nested), its token is refused on the shared socket with the uniform 401: |
| +// the shared socket is not a second door for a nested worker's leaked |
| +// token. When the pinned listener closes the host is the shared socket's |
| +// again, as a host that moved back to flat must be. |
| +func TestWorkerTokenRefusedOnSharedSocketAfterMove(t *testing.T) { |
| + const a, b = "a.x.internal", "b.x.internal" |
| + f := &fakeWorkers{tokens: map[string]string{a: "tok-a", b: "tok-b"}} |
| + s := New(f) |
| + shared := s.Handler() |
| + want := sharedRefusal(t) |
| + |
| + if rec := post(shared, "/v1/heartbeat", a, "tok-a", ""); rec.Code != http.StatusNoContent { |
| + t.Fatalf("flat A on the shared socket: %d, want 204", rec.Code) |
| + } |
| + |
| + pl, err := s.ListenPinned(filepath.Join(shortSockDir(t), "agent.sock"), a) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + before, _, _, _ := f.effects() |
| + for _, verb := range allVerbs { |
| + body := "" |
| + switch verb { |
| + case "/v1/ready": |
| + body = `{"version":"v1"}` |
| + case "/v1/next-due": |
| + body = `{"at":1756}` |
| + } |
| + rec := post(shared, verb, a, "tok-a", body) |
| + if rec.Code != http.StatusUnauthorized || rec.Body.String() != want { |
| + t.Fatalf("moved A's token on the shared socket %s: %d %q, want the uniform 401", verb, rec.Code, rec.Body.String()) |
| + } |
| + } |
| + if after, slept, due, ready := f.effects(); after != before || slept != 0 || len(due) != 0 || len(ready) != 0 { |
| + t.Fatalf("a refused shared-socket call had an effect: contacts %d->%d slept=%d due=%v ready=%v", before, after, slept, due, ready) |
| + } |
| + // A flat neighbour is untouched by A's move. |
| + if rec := post(shared, "/v1/heartbeat", b, "tok-b", ""); rec.Code != http.StatusNoContent { |
| + t.Fatalf("flat B on the shared socket while A is pinned: %d, want 204", rec.Code) |
| + } |
| + // And A is still answered where it lives now. |
| + if code, _ := pinnedCall(t, pl.Path(), "/v1/heartbeat", []string{a}, "tok-a", ""); code != http.StatusNoContent { |
| + t.Fatalf("A on its pinned listener: %d, want 204", code) |
| + } |
| + |
| + if err := pl.Close(); err != nil { |
| + t.Fatal(err) |
| + } |
| + if rec := post(shared, "/v1/heartbeat", a, "tok-a", ""); rec.Code != http.StatusNoContent { |
| + t.Fatalf("A on the shared socket after its pinned listener closed: %d, want 204", rec.Code) |
| + } |
| +} |
diff --git a/internal/host/agent.go b/internal/host/agent.go| index ef5e93ddf..05c00277c 100644 |
| --- a/internal/host/agent.go |
| +++ b/internal/host/agent.go |
| @@ -492,6 +492,13 @@ func runAgent(args []string, buildClaims ClaimFactory) { |
| Exec: eb, |
| Unit: &activator.UnitBackend{OptDir: release.OptDir()}, |
| }, reg) // the registry satisfies activator.DueStore directly |
| + // The agent socket's server, made here (it holds no state until a |
| + // listener is bound) so the backend has it before anything wakes: |
| + // a nested worker's own listener (sidecar isolation D3) is bound on |
| + // this same server, which is what lets the shared socket, served |
| + // below, refuse a host its pinned listener serves. |
| + agentSrv := agentsock.New(svc) |
| + eb.AgentSock = agentSrv |
| // doWake's own re-read of the committed row before it spawns |
| // (design §6.8). The registry satisfies RouteLookup with its own |
| // Get, exactly as DueStore above. |
| @@ -662,7 +669,6 @@ func runAgent(args []string, buildClaims ClaimFactory) { |
| // lostcancel check. |
| agentSockCtx, agentSockCancel := context.WithCancel(context.Background()) |
| defer agentSockCancel() |
| - agentSrv := agentsock.New(svc) |
| go serveAgentSocket(agentSockCtx, cfg.AgentSocket, agentSrv.Serve) |
| log.Printf("agent: agent socket armed at %s", cfg.AgentSocket) |