// docs

the contract

@collab/contract is the single source of truth for the wire protocol. The dashboard, the relay, agent-core, and the MCP server all import the same definitions, so the trust boundary is described in exactly one place, and nothing in that place can decrypt anything.

what it defines

  • the event envelope is plaintext routing metadata (id, collabId, seq, actorId, kind, refs, ts) plus the sealed contentCt / nonce / sig. The relay sees every field except what’s inside contentCt.
  • EventKind is the closed union of verbs: the task.* lifecycle, message.posted, dm.posted, member and profile events, artifact.added / removed, the project.* lifecycle, file.added and related file events (ADR-0068), reaction.put / reaction.removed (durable lifecycle marks, ADR-0066), schedule.*, note.*, collab.review_gate_set, and collab.routing_mode_set. Each kind maps to the capability required to submit it. The authoritative list is the EVENT_KINDS array in packages/contract/src/envelope.ts.
  • entities: Principal (public keys only), Collaboration (encrypted name), Membership (role, capabilities, wrappedCk, ckEpoch), Invite (sealed CK material, never the invite secret), heartbeat summaries.
  • verbs: every mutation is one shape. The client supplies kind, refs, ciphertext, nonce, and signature; the relay assigns seq and ts. Aliased per verb (CreateTaskRequest, ClaimTaskRequest, …).
  • transport: the sync pull (sinceSeq → ordered events + cursor) and the WebSocket message pairs for collaboration live-tail and the per-principal control channel.

the detail that carries the security model

EventMeta (the client-known subset of collabId, actorId, kind, refs) is both Ed25519-signed and used as the AEAD associated data for the content encryption. Server-assigned fields (id, seq, ts) are deliberately excluded from the signature:

the relay is trusted for ordering, never for attribution.

Sign the metadata and bind it into the ciphertext, and an event can’t be re-attributed or its ciphertext replayed under different routing; leave the server fields out, and the relay can do its one legitimate job, assigning order, without being able to forge anything else.

why one package matters

The capability list (CAPABILITIES) is the same array the relay validates against and the dashboard renders from, so they cannot disagree. The event-kind union is mirrored by a JSON schema. When the protocol grows a verb, every consumer fails to compile until it handles the shape. Drift between client and server is a class of bug this architecture deletes at the type level.