Skip to main content

Part 1 — Resource Taxonomy & Module Layout

Tags reflect resources, and Go modules get split to match (the deferred cohesion audit, now in scope). The current organization module is the main offender — it owns org CRUD + space/room CRUD + (via the membership handler) invitations. We separate those.


Target resources → owning module → tag

Resource (tag)Owning Go module (target)Current locationMove
Recordingsrecordingrecording + start/stop in room/livekit wiringconsolidate: bring start/stop under /recordings
Spacesspace (new, split from organization)organization handler RoomCreate/Delete/Update/List/ByID/ByNameextract module (N1: Space)
Realtimerealtime / roomroom handler RoomAuth (/livekit/room-auth), realtime tokentransport tokens — A/V conference token (LiveKit today, vendor off the wire) + Centrifugo connection token (shared by chat & presence)
Invitationsinvitation (new, split from organization/membership)membership handler under /organizations/*extract module
Membersmembershipmembership handler ListMembers/UpdateRole/RemoveMembershrink to members only
Organizationsorganizationorganization handler Create/Update/Switchshrink to org-only
Calendarcalendar/googlesamede-Google paths; {provider} selector for Outlook
Auth / Sessionauthentication + sessionsamepath cleanup
Chatchatsamemessaging only (connection token → Realtime, presence → Presence)
Presence (user-status)presence (split from chat)Centrifugo status: channelscross-cutting — heartbeat + manual status + snapshot; consumed by space/members/chat
User / Avataruseruser (profile, avatar, signups)avatar = sub-resource of user
Analytics / Metrics / Webhooksanalytics / metrics / webhooksamelikely stay plain gin (internal/webhook, signature-verified) — not part of the public typed contract

Naming decisions to resolve (N)

N1 — "Office" vs "Room" vs "Space" (the triple-naming) — RESOLVED: Space (2026-06-15)

Canonical wire term = Space. Originally locked as Office (2026-06-13, matching the "virtual office" framing + the FE module), then reopened and re-decided 2026-06-15 when the product broadened beyond work offices: "office" is too narrow — a space's backdrop can be a lecture hall, lounge, or event floor, and those are just SpaceLayouts, not different entities. Space is the neutral category term for spatial collaboration (also Gather's core noun). Resource path /spaces, tag Spaces, DTO Space, sub-resources SpaceLayout/SpaceTag. The DB table stays private.rooms internal (behind the repository); only the wire term and the new module (space) change. See resources/space.md.

Renamed in lockstep so the three layers don't collide:

  • ContainerSpace (was Office on the wire / room internally).
  • Open zone "open space" → "open area" (it was an office-ism; "space" now names the container).
  • Enclosed zone stays "meeting room"; the A/V session stays the vendor-neutral conference-token.
  • Field name resolved: any wire field holding a space id is spaceId (was roomId).

Implied FE refactor (separate, large): officeLayoutStore, assets/layouts, the virtual-office module / office feature, RoomData/roomId, and the literal "open space" term → Space / open-area vocabulary. Tracked as its own migration; not part of the contract drafting.

N2 — Recording lives in two namespaces today

/livekit/start-recording, /livekit/stop-recording (room module) vs /recordings/list, /recordings/download-link, /recordings/update-presence (recording module). Target: all under /recordings, all owned by the recording module. Final paths (per resources/recording.md, which is authoritative — every path ends in a reserved verb per C1/C1b, never a bare POST /recordings):

  • POST /recordings/start · POST /recordings/{recordingId}/stop · POST /recordings/list · POST /recordings/{recordingId}/download-link · POST /recordings/presence/update.

N3 — LiveKit "room-auth" / join — LOCKED: Realtime resource (2026-06-13)

/livekit/room-auth mints a token to join a space's realtime A/V session. Two framings:

  • (a) Keep as a Realtime resource — mint a token artifact.
  • (b) Model as a space action: POST /spaces/{id}/join → returns the token. Decided: (a) — keep the token op in the realtime module; the realtime session is an infrastructure concern, not a space CRUD action. Final path is POST /realtime/conference-token (per resources/realtime.md) — a verbless artifact-noun terminal, the sanctioned C1b *-token form. The vendor name (livekit) is not on the wire (C6) — it's the A/V conference token; LiveKit is today's transport.

N4 — Invitations as their own resource

send / list / revoke / accept move from /organizations/* to /invitations. Final paths (per resources/invitations.md; every path ends in a reserved verb — no bare POST /invitations, no literal :get colon, which C1a/C1b forbid):

  • POST /invitations/send (body = emails) · POST /invitations/list · POST /invitations/{id}/revoke · POST /invitations/accept (body = token) · invite-link: POST /invitations/link/get / POST /invitations/link/reset / POST /invitations/link/extend.

N5 — Webhooks & internal ops stay out of the typed contract

/webhook, /webhook/license-expired, /webhook/reconcile-recordings, /generate-pilot-invite, /health, /metrics — signature-verified or ops-internal. LOCKED (2026-06-13): keep as plain gin handlers (migration plan §"public webhooks" open item), not Huma operations, so they don't pollute the published client contract. Confirm per-route during their resource passes.


Module-split work implied (backend)

These are real code moves, sequenced with their resource passes — not a separate big-bang:

  1. Extract space module from organization (handlers, service, models, routes, DI).
  2. Extract invitation module from organization/membership.
  3. Consolidate recording start/stop out of the room/livekit wiring into recording.
  4. organization and membership shrink to their true responsibilities.

Each extraction lands with that resource's blueprint pass so the move + reshape happen together, reviewed as one coherent unit.