--- id: multi-user/permissions-authorize-ts-has-no-automated title: '`permissions/authorize.ts` has no automated tests' theme: multi-user status: done created: 2026-08-20 date_source: assumed order: 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.