console: TestMoveCapableGateAtTheRoutes quiesces the CLI-token notifier before counting SQL
Issuing a person's CLI token starts notifyCLIIssued on its own goroutine, and its grant lookup could land inside a later counted request (CI shard internal_console@2: "SELECT token FROM grants" counted as evidence). The route test's fixture now installs the server's existing cliNotified hook before any token is issued and waits for each notification to finish, so the counter sees only the requests' own statements. No production change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+42
−1
internal/console/selfserve_gate_test.go+42 −1
CI failed — run details
CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (2 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 (40 s)
=== gofmt (2 s)
gofmt -l .: clean
=== rest (208 s)
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 7.817s
ok github.com/carlosframework/platform/cmd/moveprobe 14.151s
ok github.com/carlosframework/platform/cmd/zddprobe 0.730s
ok github.com/carlosframework/platform/internal/activator 64.413s
ok github.com/carlosframework/platform/internal/agentsock 0.121s
ok github.com/carlosframework/platform/internal/alerting 0.019s
ok github.com/carlosframework/platform/internal/analytics 0.121s
ok github.com/carlosframework/platform/internal/appconfig 4.842s
ok github.com/carlosframework/platform/internal/apperr 0.271s
ok github.com/carlosframework/platform/internal/applog 0.395s
ok github.com/carlosframework/platform/internal/awsinv 0.018s
ok github.com/carlosframework/platform/internal/boxchoice 0.014s
ok github.com/carlosframework/platform/internal/boxid 1.162s
? 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.050s
ok github.com/carlosframework/platform/internal/caps 0.087s
ok github.com/carlosframework/platform/internal/certwarm 0.018s
ok github.com/carlosframework/platform/internal/cgroup 1.164s
ok github.com/carlosframework/platform/internal/changelog 1.544s
ok github.com/carlosframework/platform/internal/claimactivation 2.186s
ok github.com/carlosframework/platform/internal/clamd 0.105s
? 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 6.566s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.093s
ok github.com/carlosframework/platform/internal/dbverify 0.029s
ok github.com/carlosframework/platform/internal/dnsrec 0.023s
ok github.com/carlosframework/platform/internal/docsite 0.030s
ok github.com/carlosframework/platform/internal/domains 6.151s
ok github.com/carlosframework/platform/internal/doorkey 0.020s
ok github.com/carlosframework/platform/internal/economics 0.021s
ok github.com/carlosframework/platform/internal/edge 47.351s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.011s
ok github.com/carlosframework/platform/internal/fleetapi 0.194s
ok github.com/carlosframework/platform/internal/gate 1.764s
ok github.com/carlosframework/platform/internal/genselect 0.043s
ok github.com/carlosframework/platform/internal/geo 0.026s
ok github.com/carlosframework/platform/internal/geoip 0.063s
ok github.com/carlosframework/platform/internal/historyreadback 0.388s
ok github.com/carlosframework/platform/internal/host 154.599s
ok github.com/carlosframework/platform/internal/hostactors 8.595s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.011s
ok github.com/carlosframework/platform/internal/hostbox 5.009s
ok github.com/carlosframework/platform/internal/hostclaim 1.839s
ok github.com/carlosframework/platform/internal/hostenv 0.032s
ok github.com/carlosframework/platform/internal/hostevidence 0.010s
ok github.com/carlosframework/platform/internal/hostexec 8.994s
ok github.com/carlosframework/platform/internal/hostfence 0.147s
ok github.com/carlosframework/platform/internal/hostfiles 12.481s
ok github.com/carlosframework/platform/internal/hostfloor 0.024s
ok github.com/carlosframework/platform/internal/hostguard 1.180s
ok github.com/carlosframework/platform/internal/hostops 3.637s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.007s
ok github.com/carlosframework/platform/internal/hostquarantine 0.003s
ok github.com/carlosframework/platform/internal/hostrelocate 1.723s
ok github.com/carlosframework/platform/internal/hostresolve 0.719s
ok github.com/carlosframework/platform/internal/hostwrite 0.003s
ok github.com/carlosframework/platform/internal/infraview 0.006s
ok github.com/carlosframework/platform/internal/instancectl 5.297s
ok github.com/carlosframework/platform/internal/instanceexec 2.957s
ok github.com/carlosframework/platform/internal/instancefs 0.033s
ok github.com/carlosframework/platform/internal/instancekey 0.009s
ok github.com/carlosframework/platform/internal/instanceupdates 0.018s
ok github.com/carlosframework/platform/internal/layout 3.344s
ok github.com/carlosframework/platform/internal/lbhealth 0.022s
ok github.com/carlosframework/platform/internal/lease 10.289s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.868s
ok github.com/carlosframework/platform/internal/ledger 0.061s
ok github.com/carlosframework/platform/internal/mail 0.678s
ok github.com/carlosframework/platform/internal/maintenance 0.158s
ok github.com/carlosframework/platform/internal/maintenanceexec 6.442s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.212s
ok github.com/carlosframework/platform/internal/metrics 0.007s
ok github.com/carlosframework/platform/internal/moves 35.365s
ok github.com/carlosframework/platform/internal/moves/archive 2.320s
ok github.com/carlosframework/platform/internal/objstore 0.713s
ok github.com/carlosframework/platform/internal/ops 0.399s
ok github.com/carlosframework/platform/internal/park 0.759s
ok github.com/carlosframework/platform/internal/plans 0.039s
ok github.com/carlosframework/platform/internal/privatefile 0.004s
ok github.com/carlosframework/platform/internal/provision 45.529s
ok github.com/carlosframework/platform/internal/publicrelease 0.013s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 8.323s
ok github.com/carlosframework/platform/internal/relay 7.298s
ok github.com/carlosframework/platform/internal/release 34.043s
ok github.com/carlosframework/platform/internal/rollout 5.363s
ok github.com/carlosframework/platform/internal/rolloutwords 0.006s
ok github.com/carlosframework/platform/internal/rootaws 0.039s
ok github.com/carlosframework/platform/internal/rootparticipation 0.080s
ok github.com/carlosframework/platform/internal/rootstate 0.030s
ok github.com/carlosframework/platform/internal/runs 0.854s
ok github.com/carlosframework/platform/internal/s3compat 0.012s
ok github.com/carlosframework/platform/internal/safefile 0.013s
ok github.com/carlosframework/platform/internal/schedule 3.628s
ok github.com/carlosframework/platform/internal/seeds 0.013s
ok github.com/carlosframework/platform/internal/sim/hostlife 28.006s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.566s
ok github.com/carlosframework/platform/internal/status 0.126s
ok github.com/carlosframework/platform/internal/steering 0.007s
ok github.com/carlosframework/platform/internal/stopevidence 0.511s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.006s
ok github.com/carlosframework/platform/internal/termfence 0.005s
ok github.com/carlosframework/platform/internal/tunnelclient 0.014s
ok github.com/carlosframework/platform/internal/unixdial 0.021s
ok github.com/carlosframework/platform/internal/updatewords 0.008s
ok github.com/carlosframework/platform/internal/usage 0.013s
ok github.com/carlosframework/platform/internal/version 0.010s
ok github.com/carlosframework/platform/internal/vet 2.197s
=== internal_console@0 (88 s)
ok github.com/carlosframework/platform/internal/console 85.496s
=== internal_console@1 (55 s)
ok github.com/carlosframework/platform/internal/console 51.804s
=== internal_console@2 (49 s)
ok github.com/carlosframework/platform/internal/console 46.082s
=== internal_console@3 (59 s)
ok github.com/carlosframework/platform/internal/console 56.676s
=== internal_console@4 (58 s)
ok github.com/carlosframework/platform/internal/console 55.394s
=== internal_console@5 (57 s)
ok github.com/carlosframework/platform/internal/console 54.760s
=== internal_console@6 (72 s)
ok github.com/carlosframework/platform/internal/console 68.799s
=== internal_console@7 (63 s)
ok github.com/carlosframework/platform/internal/console 59.822s
=== root@0 (47 s)
using http://127.0.0.1:38423 as a@keymail.dev
shipped titesite abc1234
--- FAIL: TestMovesApprovedCopyIsApplied (5.46s)
approved_moves_copy_test.go:836: MV-O30: internal/console/apiinstancemove_offline.go does not contain the approved text "<other> is moving offline. move one instance at a time"
--- FAIL: TestMovesGateBitesOnTheRealRecord (11.43s)
approved_moves_copy_test.go:1981: an approved row whose text is not in its file: problems = ["MV-N6: internal/console/apiinstancemove.go does not contain the approved text \"cut must be \\\"automatic\\\" or \\\"manual\\\"\"" "MV-O30: internal/console/apiinstancemove_offline.go does not contain the approved text \"<other> is moving offline. move one instance at a time\""]
approved_moves_copy_test.go:1990: an approved row whose text is in its file: problems = ["MV-O30: internal/console/apiinstancemove_offline.go does not contain the approved text \"<other> is moving offline. move one instance at a time\""]
2026/10/23 08:16:33 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:33 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:33 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:34 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:34 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:34 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:34 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:34 console: api instances get bab/titogo/probe-titogo.bab.oncarlos.test: read timeout
2026/10/23 08:16:34 console: api instances get bab/titogo/probe-titogo.bab.oncarlos.test: read timeout
2026/10/23 08:16:34 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:35 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:35 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:37 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:37 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:37 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:45 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
FAIL
FAIL github.com/carlosframework/platform 45.256s
FAIL
=== root@1 (53 s)
ok github.com/carlosframework/platform 51.001s
=== root@2 (55 s)
using http://127.0.0.1:44503 as a@keymail.dev
--- FAIL: TestMovesStringsAreRecorded (8.68s)
approved_moves_copy_test.go:1714: no row of the record carries internal/console/apiinstancemove_offline.go:761 (jsonError) "the instance changed while the move was being started. try again"
approved_moves_copy_test.go:1714: no row of the record carries internal/console/apiinstancemove_offline.go:814 (jsonError) "the instance changed while the move was being started. try again"
2026/10/23 08:16:37 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:41 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:47 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/23 08:16:47 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/23 08:16:47 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/23 08:16:47 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/23 08:16:47 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/23 08:16:47 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/23 08:16:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/23 08:16:49 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=888
2026/10/23 08:16:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/23 08:16:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
using http://127.0.0.1:34259 as paul@keymail.dev
using http://127.0.0.1:34259 as paul@keymail.dev
using http://127.0.0.1:34259 as paul@keymail.dev
Querying a copy of acme.go.tito.io's database, up to transaction 3, written 2026-10-23 08:16:49 UTC. Nothing you run here reaches acme.go.tito.io.
2026/10/23 08:16:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:16:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
using http://127.0.0.1:36865 as paul@keymail.dev
account: ops (bab)
attached tickets.example.com -> default.go.tito.io
DNS: pointing
using http://127.0.0.1:36865 as paul@keymail.dev
account: ops (bab)
attached carloku.example.com -> default.go.tito.io
2026/10/23 08:16:58 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:17:03 domains: minted platform domain echoapp.bab.oncarlos.test (upstream app.local)
2026/10/23 08:17:03 domains: minted platform domain site.bab.oncarlos.test (upstream site.local)
2026/10/23 08:17:03 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/23 08:17:03 domains: minted platform domain blog.mdbbndjqx.oncarlos.test (upstream blog.kass.test)
2026/10/23 08:17:03 domains: minted platform domain blog.gdpkwhwrw.oncarlos.test (upstream blog.woodstar.test)
2026/10/23 08:17:03 domains: minted platform domain oldsite.bab.oncarlos.test (upstream old.test)
2026/10/23 08:17:03 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
FAIL
FAIL github.com/carlosframework/platform 53.494s
FAIL
FAILED: root@0 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/console/selfserve_gate_test.go b/internal/console/selfserve_gate_test.go| index d2cf46c1a..128071277 100644 |
| --- a/internal/console/selfserve_gate_test.go |
| +++ b/internal/console/selfserve_gate_test.go |
| @@ -214,9 +214,50 @@ type moveScene struct { |
| ownerFresh string // a second owner token, for the limiter's own count |
| } |
| +// newQuietLifetimesFx is newLifetimesFx with the CLI-token notifier |
| +// quiesced. Issuing a person's CLI token starts notifyCLIIssued on a |
| +// goroutine of its own, which looks the address's grant up in SQL; left |
| +// running, that statement can land inside a later counted request. Here the |
| +// fixture installs the server's notification hook before any token is issued |
| +// and waits for each token's notification attempt to finish, so the counting |
| +// SQL handle only ever sees the requests' own statements. |
| +func newQuietLifetimesFx(t *testing.T) lifetimesFx { |
| + t.Helper() |
| + clock := useLifetimesClock(t) |
| + fx := newConsoleFixture(t) |
| + fx.srv.cliNotified = make(chan struct{}, 1) |
| + quietToken := func(address, code string) string { |
| + t.Helper() |
| + tok := cliToken(t, fx, address, code) |
| + select { |
| + case <-fx.srv.cliNotified: |
| + case <-time.After(10 * time.Second): |
| + t.Fatalf("the CLI-token notification for %s never finished", address) |
| + } |
| + return tok |
| + } |
| + now := time.Now() |
| + if _, err := fx.st.CreateAppIn(OpsAccountID, ltApp, "paul@keymail.dev", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := fx.st.AddMember(OpsAccountID, "alice@keymail.dev", RoleMember, now); err != nil { |
| + t.Fatal(err) |
| + } |
| + enableInstances(t, fx, ltApp, provision.Manifest{Schema: 1, App: ltApp, Domain: "shop.example.com", Channel: "stable"}) |
| + member := quietToken("alice@keymail.dev", "code-lifetimes-member") |
| + if err := fx.st.SetOperator("paul@keymail.dev", true); err != nil { |
| + t.Fatal(err) |
| + } |
| + operator := quietToken("paul@keymail.dev", "code-lifetimes-operator") |
| + if got := fx.srv.cutover.status().State; got != "before" { |
| + t.Fatalf("a console over a bucket with no marker starts %s, want before", got) |
| + } |
| + return lifetimesFx{fx: fx, clock: clock, member: member, operator: operator} |
| +} |
| + |
| func newMoveScene(t *testing.T) moveScene { |
| t.Helper() |
| - l := newLifetimesFx(t) |
| + l := newQuietLifetimesFx(t) |
| st, now := l.fx.st, time.Now() |
| if _, err := st.CreateAppIn(OpsAccountID, "other", "paul@keymail.dev", now); err != nil { |
| t.Fatal(err) |