fix(authz): replace vacuous concurrency test, validate effect in memory store

The fix-round-1 test "a concurrent write is not lost to another
method's failure" was vacuous: a single-process Promise.all cannot
reliably land a bare write inside another method's open transaction,
so it passed against both the fixed and the (previously) defective
grant() implementation. The shared-connection rollback hazard it was
meant to catch is real (confirmed separately by forcing the
transaction open before the write), but this specific test could
never reach that state and gave false assurance either way.

Replaced it with "a rejected write leaves unrelated state intact",
which asserts a grant() call with an invalid effect is refused without
disturbing the subject's existing roles/grants, plus a NOTE
documenting that the rollback hazard is now prevented structurally (no
transactions) rather than by a dedicated concurrency test.

memoryPermissionStore.grant() had no effect validation, so it failed
the new test; added a guard mirroring the db adapter's CHECK
constraint so both adapters agree on rejecting anything other than
"allow"/"deny".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 20:52:18 +05:30
co-authored by Claude Opus 5
parent 134c5fa4bc
commit 1cc0b97a72
2 changed files with 24 additions and 8 deletions
+8
View File
@@ -77,6 +77,14 @@ export function memoryPermissionStore(): PermissionStore {
if (at !== -1) roles.splice(at, 1);
},
async grant(subjectId, permission, effect, scope) {
// The db adapter enforces this via a CHECK constraint; the memory
// adapter must agree, or a bad effect would silently vanish from both
// the grant and deny buckets on read instead of being refused up front.
if (effect !== "allow" && effect !== "deny") {
throw new TypeError(
`WRN-AUTHZ-EFFECT: effect must be "allow" or "deny", received ${JSON.stringify(effect)}`,
);
}
const key = scopeKey(scope);
const at = grants.findIndex(
(g) => g.subjectId === subjectId && g.scope === key && g.permission === permission,