Files
2026-05-27 23:29:53 +01:00

3.5 KiB

OpenSpec Change: enterprise-gaming-party

  • Change ID: enterprise-gaming-party
  • Package: @suaveplan/party
  • Category: gaming/multiplayer
  • Target path: gaming/multiplayer/party
  • Status: PROPOSED
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/party.md

Summary

Deliver @suaveplan/party on the genesis branch from scratch at enterprise quality. The package provides a party/group system with invites, leadership transfer, and kick/promote mechanics. A PartyManager creates/destroys Party objects; a Party tracks members with role (leader/officer/member), handles invite expiry, and enforces maxSize. An InviteTracker manages pending invites with configurable TTL and accept/decline/expire lifecycle. Genesis adds Zod schemas, structured errors, OTel active-party gauge, injectable clock, and 100% coverage.


Current-state audit (genesis)

Package does not exist on genesis yet — fresh build.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) PartyError; PARTY.* codes
@suaveplan/types (workspace:^) Clock, IdGenerator
@suaveplan/events (workspace:^) party:invite, party:joined, party:left, party:disbanded events
zod (catalog:forms) PartyConfig, PartyMember schemas
@opentelemetry/api (catalog:observability) Active party gauge

Public API surface

type PartyRole = 'leader' | 'officer' | 'member'

interface PartyMember {
  playerId: string
  displayName: string
  role: PartyRole
  joinedAt: number
}

interface PartyConfig {
  maxSize?: number        // default 5
  inviteTtl?: number      // seconds, default 60
}

function createPartyManager(options?: { clock?: Clock }): PartyManager

interface PartyManager {
  createParty(leaderId: string, leaderName: string, config?: PartyConfig): Party
  getParty(partyId: string): Party | undefined
  getPartyByPlayer(playerId: string): Party | undefined
  disbandParty(partyId: string): void
  readonly count: number
}

interface Party {
  readonly id: string
  readonly members: ReadonlyMap<string, PartyMember>
  readonly leaderId: string
  readonly size: number
  invite(fromId: string, toId: string, toName: string): string   // returns inviteId
  acceptInvite(inviteId: string, playerId: string): void
  declineInvite(inviteId: string): void
  leave(playerId: string): void
  kick(by: string, target: string): void
  promote(by: string, target: string, role: 'officer' | 'leader'): void
  tick(dt: number): void    // expire invites
}

Error codes

type PartyErrorCode =
  | 'PARTY_NOT_FOUND'
  | 'PARTY_FULL'
  | 'PARTY_MEMBER_NOT_FOUND'
  | 'PARTY_INVITE_NOT_FOUND'
  | 'PARTY_INVITE_EXPIRED'
  | 'PARTY_PERMISSION_DENIED'
  | 'PARTY_ALREADY_IN_PARTY'

File layout

gaming/multiplayer/party/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/index.ts
    types/common.ts
    types/common.md
    core/
      party/
        party.ts
        party.md
        party.types.ts
        party.test.ts
        index.ts
      party-manager/
        party-manager.ts
        party-manager.md
        party-manager.types.ts
        party-manager.test.ts
        index.ts
  tests/
    integration.test.ts

Impact

Downstream: @suaveplan/react-social-game shows party panel; @suaveplan/lobby transfers party to lobby. Prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events.