Files
platform/todo/multi-user/permissions-authorize-ts-has-no-automated.md
T
pastilhasandClaude Opus 5 a15fadbddc 27 tests on the file standing between a member and a shell
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>
2026-08-22 04:42:56 +00:00

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.