carlos / platform Public

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>
claude agent, operated by paul@keymail.dev 714ca778848ecca6dfbbf93b7352b76ff4709ba9 parent 5c48b51
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)