Aller au contenu principal

Implementation Plan: Work Members

Feature ID: work-members Spec: ./spec.md Status: Shipped (Retrospective) Last updated: 2026-05-21

Note (EW-632 close-out). This plan reflects the original direct-invite flow only. The tokenised-claim flow added by PR #687 (EW-600) is documented in ./spec.md §5.1 — its architecture differs from the diagram below (token issuance + sha256 hash at rest + public claim preview endpoint + CAS-based status transitions). Treat the spec as authoritative; this plan is left intact as a historical record of phase 1.


1. Architecture​

2. Tech Choices​

ConcernChoiceRationale
Permission modelStatic role matrix in codeClosed enum; matrix is small
Auth checkWorkOwnershipService.ensureCan*Single source of truth across features
NotificationMail facadeExtensible to in-app notifications
Invite mechanismDirect (no pending tokens)Simpler UX for collaborators

3. Data Model​

@Entity('work_memberships')
@Index(['workId', 'userId'], { unique: true })
export class WorkMembership {
@PrimaryGeneratedColumn('uuid') id: string;
@Column() workId: string;
@Column() userId: string;
@Column({ type: 'varchar' }) role: 'manager' | 'editor' | 'viewer';
@Column() invitedBy: string;
@CreateDateColumn() createdAt: Date;
}

Migration: additive, with composite unique index (workId, userId).

4. API Surface​

MethodEndpointRequired role
GET/api/works/:id/membersviewer
POST/api/works/:id/membersmanager / owner
GET/api/works/:id/members/:memberIdviewer
PUT/api/works/:id/members/:memberIdmanager / owner
DELETE/api/works/:id/members/:memberIdmanager / owner
POST/api/works/:id/members/leavenon-owner member

5. Plugin / Web / CLI​

  • Plugins: none.
  • Web: Settings → Members UI with the role matrix and invite form.
  • CLI: not exposed.

6. Background Jobs​

None — invite emails are sent inline.

7. Security & Permissions​

  • Every endpoint runs through WorkOwnershipService's ensureCanRead / ensureCanEdit / ensureCanManage helpers.
  • The Owner role is checked separately because it's implicit on works.userId.

8. Observability​

Activity log: member_invited, member_role_updated, member_removed, member_left with the affected user id and role.

9. Risks & Mitigations​

RiskMitigation
Race: two managers invite same userUnique index (workId, userId) rejects the second
Owner accidentally locked outOwner role is implicit and immutable
Permission cache lagPermission checks read fresh from DB

10. Constitution Reconciliation​

See spec.md §9.

11. References​

  • Spec: ./spec.md
  • Implementation:
    • apps/api/src/works/members/
    • packages/agent/src/services/work-members.service.ts
    • packages/agent/src/services/work-ownership.service.ts