test(alerts): lock the role rank order across rank(), PartialOrd, and VISIBLE - #907
Merged
WaylandYang merged 3 commits intoSep 24, 2026
Merged
Conversation
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
approved these changes
Sep 24, 2026
WaylandYang
left a comment
Contributor
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lock the alert role-rank order across Rust, SQL, and the enum
The comment at
crates/utopia-store/src/alerts.rs:340names the invariant:— i.e.
rank()(Rust),Role's derivedPartialOrd(Rust), and theCASE a.min_role WHEN 'viewer' THEN 0 ...block inside theVISIBLESQL constant (Postgres) all agree onViewer < 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_sqlwalks three places and asserts agreement:rank(Role::Viewer) < rank(Role::Editor) < rank(Role::Admin) < rank(Role::Owner)— the Rust function's monotonic order.Role::Viewer < Role::Editor < Role::Admin < Role::Owner— derivedPartialOrdfrom the enum's declaration order.VISIBLESQL constant contains theWHEN 'viewer' THEN 0,WHEN 'editor' THEN 1,WHEN 'admin' THEN 2clauses in that order, plusELSE 3for Owner.rank(Role::Owner) == 3— the Rust fallback for the SQLELSE.The first two stop someone reordering the
Roleenum. The third stops someone editing the SQL CASE without updatingrank(). The fourth stops someone changingrank()and forgetting to update the SQL.What it doesn't catch
Adding a brand-new role between
AdminandOwner. But that's covered by an even stronger guard:fn rank(r: Role)is a non-exhaustivematch, so adding a variant without updatingrank()becomes a compile error before any test runs.Whitespace handling
The source SQL uses double-space alignment (
'admin' THEN), so the test normalises viasplit_whitespacebefore 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 testsinsidealerts.rsThe 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