authorize.ts had none, and its own header is what says why that mattered: every deny path in the platform ends there, and the failure mode of a permissive default is not a bug report, it is someone else's session. It had none for a structural reason rather than an oversight. Every path needed two database calls, and the only file here that mocks officerdb leaks its stub into whatever runs after it — so the obvious approach would have traded one untested file for a flaky suite. So the decisions are split from the fetching. resolveEffectivePermissions, decideApiRequest and decideWsProvider are pure functions of their arguments; the async wrappers keep the role cache, the fail-closed catches and the shape callers use. No behaviour change, and the live gate still answers 401 on file-browser, users and tasks. REAL registry keys throughout, deliberately: a suite built on invented permissions keeps passing after a rename, which is the failure an authorization table can least afford. If a key here disappears this file goes red. Pinned beyond the happy paths — the owner carries no grants to consult; core is write unconditionally; a confined grant is dropped without a Linux user and all three drop together; execution and admin grants in the table are ignored even though no CHECK stops the row existing; an unknown key is ignored without disturbing what is legitimately held; self-service is exact in method AND path; a socket needs no level; an unauthenticated caller is refused every door. Two gaps are stated in the file rather than left to be found: ownerOnly has no declaring permission yet, so testing it would test the fixtures; and the grant cache's contract is a claim about call sites, not logic. Found while writing them: readOnlyWrites paths are relative to the permission's api prefix, and getting that wrong fails as "no permission covers this path" rather than as a level error — a misleading message for a misleading path, now commented at the test that hit it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.4 KiB
id, title, theme, status, created, date_source, order
| id | title | theme | status | created | date_source | order |
|---|---|---|---|---|---|---|
| multi-user/permissions-authorize-ts-has-no-automated | `permissions/authorize.ts` has no automated tests | multi-user | done | 2026-08-20 | assumed | 11 |
permissions/authorize.ts has no automated tests. registry.test.ts covers the pure
registry functions and the totality check; the resolver that does the owner bypass, the grant
lookup, the role cache and the fail-closed catches is exercised only by hand. It is the file
standing between a Member and a shell.
Done 2026-08-22. 27 tests, no mock and no database.
The reason it had none was structural: every path through the resolver needed two database calls, and the
only file in this repository that mocks officerdb leaks its stub into whatever runs after it — so the
obvious approach would have traded one untested file for a flaky suite.
So the decisions were split from the fetching. resolveEffectivePermissions(user, roleGrants),
decideApiRequest(effective, method, path) and decideWsProvider(effective, provider) are pure; the async
functions keep the caching, the fail-closed catches and the shape callers use. The tests use REAL registry
keys throughout, deliberately — a suite built on invented permissions keeps passing after a rename, which is
the failure an authorization table can least afford.
What is pinned, beyond the happy paths: the owner carries no grants to consult; core is write
unconditionally; a confined grant is dropped without a Linux user, and all three confined permissions drop
together; execution and admin grants in the table are ignored even though no CHECK stops the row existing;
an unknown key is ignored without disturbing what is legitimately held; self-service is exact in both method
and path; readOnlyWrites is relative to the permission's prefix; a socket needs no level; and an
unauthenticated caller is refused every door.
Two things deliberately NOT covered, and said so in the file rather than left to be noticed: ownerOnly,
because no permission declares it yet and covering it would test the fixtures rather than the registry; and
the grant cache, whose contract is a claim about call sites rather than logic.
Found while writing them: readOnlyWrites paths are relative to the permission's api prefix, and getting
that wrong fails as "no permission covers this path" rather than as a level error — a misleading message for
a misleading path.