Skip to main content

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