Skip to content

test(alerts): lock the role rank order across rank(), PartialOrd, and VISIBLE - #907

Merged
WaylandYang merged 3 commits into
deeplethe:devfrom
rollroyces:test/alert-role-rank-drift-guard
Sep 24, 2026
Merged

WaylandYang merged 3 commits into
deeplethe:devfrom
rollroyces:test/alert-role-rank-drift-guard

Conversation

@rollroyces

Copy link
Copy Markdown
Contributor

Lock the alert role-rank order across Rust, SQL, and the enum

The comment at crates/utopia-store/src/alerts.rs:340 names the invariant:

跟 [Role] 的 PartialOrd 同序,也跟 [VISIBLE] 里那个 CASE 同序——三处必须一致

— i.e. rank() (Rust), Role's derived PartialOrd (Rust), and the CASE a.min_role WHEN 'viewer' THEN 0 ... block inside the VISIBLE SQL constant (Postgres) all agree on Viewer < Editor < Admin < Owner (ranks 0, 1, 2, 3).

The drift shape is the same as the_backstop_can_be_raised: a single concept expressed in three places that can move out of sync silently. Consequences here are sharper than the worker-concurrency case — a rank off by one lets a Viewer see Editor-only alerts (information leak) or Owner-only alerts (escalation noise).

There was no test guarding this. Adding one.

What it catches

The new test role_rank_order_is_the_same_in_rust_and_sql walks three places and asserts agreement:

  1. rank(Role::Viewer) < rank(Role::Editor) < rank(Role::Admin) < rank(Role::Owner) — the Rust function's monotonic order.
  2. Role::Viewer < Role::Editor < Role::Admin < Role::Owner — derived PartialOrd from the enum's declaration order.
  3. The VISIBLE SQL constant contains the WHEN 'viewer' THEN 0, WHEN 'editor' THEN 1, WHEN 'admin' THEN 2 clauses in that order, plus ELSE 3 for Owner.
  4. rank(Role::Owner) == 3 — the Rust fallback for the SQL ELSE.

The first two stop someone reordering the Role enum. The third stops someone editing the SQL CASE without updating rank(). The fourth stops someone changing rank() and forgetting to update the SQL.

What it doesn't catch

Adding a brand-new role between Admin and Owner. But that's covered by an even stronger guard: fn rank(r: Role) is a non-exhaustive match, so adding a variant without updating rank() becomes a compile error before any test runs.

Whitespace handling

The source SQL uses double-space alignment ('admin' THEN), so the test normalises via split_whitespace before substring checks. If a future maintainer re-aligns the CASE, the test stays green for the wrong reason only if they change the content of the WHEN clauses — which is what the test is actually watching for.

Why I split it into #[cfg(test)] mod tests inside alerts.rs

The constants and helpers (rank, VISIBLE) are private to that file. Moving them out would break layering for what's a one-file invariant. Inline test is the simplest fit.

Signed-off-by: rollroyces royce@rollroyces.com

rollroyces added 2 commits September 24, 2026 23:04
The backend corpus at crates/utopia-server/src/docs_corpus.rs only
indexed ingest.md. The frontend Docs page (web/src/pages/Docs.tsx)
listed both ingest.md and mcp.md, so users reading the docs saw MCP
documentation that the chat's search_docs tool could never find.
A user asking the chat about MCP tokens, agent access, or write
permissions would get nothing back even though the answer is in the
docs.

Fix: add mcp to ARTICLES.

Drift guard: new test every_corpus_md_file_is_indexed walks
web/src/docs/ and asserts the .md files match the slugs in ARTICLES
one-to-one. The invariant is "on-disk → indexed" (not the other way
round) so a new file dropped in without being indexed fails the test.
This is the same shape as the_backstop_can_be_raised — a number defined
in two places, drift between them is the bug, the test locks the
invariant.

Signed-off-by: rollroyces <royce@rollroyces.com>
… VISIBLE

The comment at alerts.rs:340 names the invariant: `rank()`,
`Role`'s derived `PartialOrd`, and the CASE inside `VISIBLE`
all agree on Viewer<Editor<Admin<Owner (0,1,2,3).

The drift shape is the same as the_backstop_can_be_raised: a
single concept expressed in three places that can move out of
sync silently. Consequences here are sharper than the worker
concurrency case — a rank off by one lets viewers see editor-only
alerts (information leak) or owner-only alerts (escalation noise).

Add a static-analysis test that catches:
- `rank()` not in the same order as the enum's PartialOrd
- The CASE in VISIBLE missing WHEN 'viewer'/'editor'/'admin'
- The CASE in VISIBLE not having ELSE 3 (Owner falls through)
- `rank(Owner) != 3` (drift between Rust and SQL rank)

Adding a new role between Admin and Owner causes `rank()` to
fail to compile (non-exhaustive match) — that's the first guard.
This test catches the subtler drift where someone changes one of
the three places without touching the other two.

Whitespace-safe: VISIBLE is normalised via split_whitespace before
the substring checks, since the source uses double-space alignment
("'admin'  THEN") that varies.

Signed-off-by: rollroyces <royce@rollroyces.com>

@WaylandYang WaylandYang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right invariant to pin, and pinned at the three places the comment names; the whitespace folding keeps the SQL check honest. The docs_corpus.rs hunk is #905, already on dev, so the squash carries only the alerts test. CI green; merging.

@WaylandYang
WaylandYang merged commit 6dda0b1 into deeplethe:dev Sep 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants