Skip to content

permissionEqual() ignores role — role-only ACL tightenings silently no-op #107

Description

@cuibonobo

Problem

#58 added role?: 'admin' to group permission entries; #61 made setPermissions() a versioned, no-op-detecting mutation (skip the write when the new set is deep-equal to the current one). The no-op detector predates the role field and never learned about it: permissionEqual (packages/core/src/stack.ts:1627-1637) compares access/groupId/read/write for a group entry but not role.

Consequence: tightening { access: 'group', groupId, read: true, write: true } to the same entry plus role: 'admin' is judged deep-equal → Stack.setPermissions() returns early (stack.ts:949) → the restriction is silently not applied, while the call reports success and does not bump version. A failed tightening of access with no signal — the worst direction for a permission bug to fail in. The reverse (removing role to widen from admins-only back to any-member) fails the same way.

The SQLite layer stores permissions as an opaque JSON blob and does no equality check of its own, so permissionEqual/permissionsEqual in core is the only place this is decided — the fix is localized.

Fix

Include role in the group branch of permissionEqual:

if (a.access === 'group' && b.access === 'group') {
  return (
    a.groupId === b.groupId &&
    a.role === b.role &&        // <-- add
    a.read === b.read &&
    a.write === b.write
  );
}

(The entity and public branches are already complete.) While here, confirm no other comparator/serializer predates roleassociationEqual is unrelated, and the wire path serializes permissions verbatim, so this should be the only site.

Tests

  • Adding role: 'admin' to an existing group entry (otherwise identical) actually persists and bumps version — regression for the silent no-op
  • Removing role (widening admins-only → any-member) likewise persists
  • A genuinely-identical set (including matching role) still correctly no-ops (no spurious version bump)

Refs

#58 (introduced role), #61 (no-op-detecting setPermissions). From docs/design-assessment-2026-07.md §A2 (PR #105).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions