feat(service): seal the account's workspaces for following over Neon Realtime

Merged
CH
8d ago
+398-19

The service side of the live workspaces list, on the central live layer (liveRows and the sealed live procedures).

  • New live procedure live.workspaces (GET /api/live/workspaces, in live-procedures.ts). It seals the bearer's workspaces as two statements, workspaces and installations, through sealedParts. With LUKE_REALTIME off, or when polled, it answers the rows inline as a held read. It's gated by sameUser like every live read.
  • Statements (workspacesLiveStatements in workspace-store.ts):
    • same joins and the same rule for who may read as listWorkspaces
    • aliased with liveSelection, with no now() and no ORDER BY
    • the photo digest is computed in SQL with immutable functions only: decode(replace(logo, '\', '\\'), 'escape') instead of convert_to, giving the same sha256
  • Reader (workspacesOfLiveRows in the new workspace-live.ts) orders the rows the way the held read does.
  • roleOf moves to workspace-wire.ts so the desktop reads a role the same way the service does.
  • Export: sealedLiveQuerySchema is exported again, now that a second module uses it.

Tests

  • PGlite parity: the live rows equal listWorkspaces for an owner, a teammate and an outsider.
  • The procedure seals both parts for the bearer and refuses a missing bearer.
  • live-statements.test.ts passes the statements through mutableFunctionsOf and the Drizzle adapter.

The live read refuses a forUser other than the bearer (sameUser, 401). Its photo check is the same as workspacePhotoOfDataUrl: a whole base64 payload and a real PNG, JPEG or WEBP signature.

Made with Luke