mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-11 21:39:01 +00:00
1ff6158468
* feat(workspace): expose currentRole + resource-scoped permission helpers (TASK-1101)
Foundation for PLAN-1100 (client-side permission audit). Lands the primitive
that every other task in the plan consumes, with no UI behavior changes.
Server:
- new GET /api/v1/workspaces/{ws}/me — returns role, collection_access,
visible_collection_ids (computed via VisibleCollectionIDs /
GuestVisibleCollectionIDs so it covers system collections, member access,
direct collection grants, and item-grant collections), plus the user's
direct collection_grants and item_grants.
- admins normalize to "owner"; legacy workspace-scoped tokens normalize to
"editor"; non-members with no grants are rejected upstream by
RequireWorkspaceAccess and never reach the handler.
Frontend:
- new $lib/utils/permissions module exporting pure cascade functions:
canEditWorkspace / canViewCollection / canEditCollection /
canViewItem / canEditItem.
- cascade mirrors server's ResolveUserPermission exactly:
owner → item grant → collection grant → membership role + visibility
so item grant beats collection grant beats role even when less permissive
(ItemGrant.view + CollectionGrant.edit on same item → effective view).
- workspaceStore wraps the pure functions with currentMembership state
fetched in setCurrent. New getters: currentRole, currentMembership,
isOwner, canEditWorkspace; new methods: canViewCollection /
canEditCollection / canViewItem / canEditItem.
- WorkspaceMembership type added.
- api.workspaces.me(slug) added.
Refactor:
- settings/+page.svelte, [collection]/+page.svelte,
[collection]/[slug]/+page.svelte: drop open-coded role derivation
(members.find + m.role open-codes), consume workspaceStore.isOwner.
members.list calls remain — still needed for assignee dropdowns / member
rows in settings — only the role-derivation path moves to the store.
Tests:
- server: handlers_me_test.go covers 6 scenarios
(admin, editor with all-access, viewer with collection grant,
restricted member, guest with item grant, non-member with no grants).
- frontend unit tests deferred — web/ has no unit-test runner today.
Pure-function module makes them trivial to add when the runner lands.
Cascade is independently covered by store/permissions_test.go and
store/grants_test.go on the server.
Parent: PLAN-1100.
* fix(workspace): per-item visibility uses strict full-access set + setCurrent race guard per Codex review (round 1)
P1: canViewItem fell back to canViewCollection, which uses the broad nav
set (visible_collection_ids — includes collections containing
item-granted items so they appear in nav). This meant a guest with one
ItemGrant on TASK-5 in Tasks would see canViewItem(any-other-task-in-Tasks)
return true, while the server only allows direct item grants or full
collection grants.
Fix: /me now also returns full_access_collection_ids — the strict set of
collections in which every item is accessible (collection grants +
member_collection_access + system collections; item-grant collections
intentionally excluded). This mirrors guestResourceFilter's fullCollIDs
in handlers. canViewItem and canEditItem now consult full_access_collection_ids
on the membership-fallthrough path, NOT the nav set.
Test added: TestMe_GuestWithItemGrant now asserts the item-grant collection
is in visible_collection_ids (nav) but NOT in full_access_collection_ids
(strict). TestMe_RestrictedMember updated to check both sets.
P2: workspaceStore.setCurrent had no guard against stale async /me responses.
A slow /me for workspace A could clobber a freshly-fetched membership
for workspace B if the user navigated mid-flight, briefly exposing
permission-gated UI for the wrong workspace.
Fix: monotonic membershipSeq counter incremented per setCurrent / create
call. Each /me response only writes back if its captured token still
matches at resolution time. Also clears currentMembership immediately on
setCurrent so helpers don't briefly answer "yes" using the previous
workspace's grants while /me is in flight.
Parent: PLAN-1100. Refs TASK-1101 PR #415.
* fix(workspace): canEditCollection uses strict full-access set per Codex review (round 2)
Same nav-vs-strict bug pattern as round 1's canViewItem fix, but in
canEditCollection. The editor-membership fallback path previously gated
on canViewCollection (broad nav predicate using visible_collection_ids),
which incorrectly returned true for a restricted editor whose only access
to a collection was an item grant. The collection appears in nav (correct)
but the editor must NOT see collection-wide write affordances like "+ New"
because the server rejects collection-level writes there.
Fix: editor membership fallback now requires either collection_access ===
"all" or the collection to be in full_access_collection_ids.
canEditItem already used full_access_collection_ids on its fallback path
(it was added in round 1) — verified unchanged.
Parent: PLAN-1100. Refs TASK-1101 PR #415.