console: the self-serve gate matrix's carried minors
A positive control at the head of requireUnauthorisedCostNothing: a plain instance token's poll through the legacy moveGate is shown to cost bucket reads and evidence SQL on the same counters. The binding SQL is an allow-list of the five exact statements that authenticate the token and resolve the account and the app; any other statement on those tables is evidence. The first rail is named for what it is (the deployment bucket in place), not fresh.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+33
−10
internal/console/selfserve_gate_test.go+33 −10
diff --git a/internal/console/selfserve_gate_test.go b/internal/console/selfserve_gate_test.go| index 901e4bf1f..b5326d29b 100644 |
| --- a/internal/console/selfserve_gate_test.go |
| +++ b/internal/console/selfserve_gate_test.go |
| @@ -8,7 +8,6 @@ import ( |
| "io" |
| "net/http" |
| "net/http/httptest" |
| - "regexp" |
| "strings" |
| "sync" |
| "sync/atomic" |
| @@ -85,10 +84,18 @@ func (c *countingRelStore) DeleteIfMatch(ctx context.Context, key, etag string) |
| return c.DirStore.DeleteIfMatch(ctx, key, etag) |
| } |
| -// bindingSQL is the SQL the gate may run: authenticating the token |
| -// (cli_tokens) and resolving the account and the app (accounts, apps). |
| -// Everything else is evidence. |
| -var bindingSQL = regexp.MustCompile(`\b(cli_tokens|accounts|apps)\b`) |
| +// bindingSQL is the SQL the gate may run, statement by statement: |
| +// authenticating the token (its row, and the last-used stamp) and resolving |
| +// the account and the app (the account by id or by the caller's |
| +// memberships, and the app's row count). Everything else, any other |
| +// statement on these tables included, is evidence. |
| +var bindingSQL = map[string]bool{ |
| + "SELECT token_hash, address, kind, account_id, app, role, name, expires_at, revoked_at, rungs, moves FROM cli_tokens WHERE token_hash = ?": true, |
| + "UPDATE cli_tokens SET last_used = ? WHERE token_hash = ?": true, |
| + "SELECT id, sqid, name, created_at FROM accounts WHERE id = ? AND deleted_at = 0": true, |
| + "SELECT a.id, a.sqid, a.name, a.created_at, m.role FROM accounts a JOIN memberships m ON m.account_id = a.id WHERE m.address = ? AND a.deleted_at = 0 ORDER BY a.created_at, a.id": true, |
| + "SELECT COUNT(*) FROM apps WHERE account_id = ? AND app = ? AND deleted_at = 0": true, |
| +} |
| // countingSQL classifies every statement the console store runs. A |
| // transaction is counted as evidence when it begins: its statements do not |
| @@ -103,11 +110,12 @@ type countingSQL struct { |
| func (c *countingSQL) note(q string) { |
| c.mu.Lock() |
| defer c.mu.Unlock() |
| - if bindingSQL.MatchString(q) { |
| + stmt := strings.Join(strings.Fields(q), " ") |
| + if bindingSQL[stmt] { |
| c.binding++ |
| return |
| } |
| - c.evidence = append(c.evidence, strings.Join(strings.Fields(q), " ")) |
| + c.evidence = append(c.evidence, stmt) |
| } |
| func (c *countingSQL) reset() (binding int, evidence []string) { |
| @@ -325,8 +333,9 @@ func (sc moveScene) counting(t *testing.T) (*countingRelStore, costedCall) { |
| } |
| // rails are the three conditions an unauthorised caller's answer must not |
| -// depend on, applied in this order: a fresh limiter with a bucket, no |
| -// bucket, and every caller's budget spent. |
| +// depend on, applied in this order: the deployment bucket in place (the |
| +// limiter as earlier requests left it), no bucket, and every caller's |
| +// budget spent. |
| func (sc moveScene) rails(counted *countingRelStore) []struct { |
| name string |
| apply func() |
| @@ -336,7 +345,7 @@ func (sc moveScene) rails(counted *countingRelStore) []struct { |
| name string |
| apply func() |
| }{ |
| - {"fresh", func() { fx.srv.cfg.Releases = counted }}, |
| + {"with a deployment bucket", func() { fx.srv.cfg.Releases = counted }}, |
| {"no deployment bucket", func() { fx.srv.cfg.Releases = nil }}, |
| {"limiter exhausted", func() { |
| fx.srv.cfg.Releases = counted |
| @@ -361,6 +370,20 @@ func requireUnauthorisedCostNothing(t *testing.T, sc moveScene, counted *countin |
| // move-capable token, and an operator has the operator doors. |
| "the owner's own CLI token": sc.l.operator, |
| } |
| + // The positive control: the counters see what the legacy path reads. |
| + // A plain instance token's poll goes through moveGate, which reads the |
| + // record, so the same call shape that must cost nothing below is shown |
| + // able to see a bucket read and evidence SQL. |
| + sc.l.fx.srv.cfg.Releases = counted |
| + setMoveRecord(t, sc.l.fx.rel, "eligible") |
| + control, err := sc.l.fx.st.NewAppToken(OpsAccountID, ltApp, "paul@keymail.dev", "control", false, time.Now()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if code, _, body, reads, binding, evidence := call(http.MethodGet, sc.url(ltOne, "/move"), control, ""); code != http.StatusOK || reads == 0 || binding == 0 || len(evidence) == 0 { |
| + t.Fatalf("the positive control: a plain token's poll = %d %s with %d store calls, %d binding statements, evidence %q; want 200 with reads and evidence", |
| + code, body, reads, binding, evidence) |
| + } |
| for _, rail := range sc.rails(counted) { |
| rail.apply() |
| for _, state := range moveRecordStates { |