Nodus
Reference

Event types

Canonical sync event types and their payload shapes.

This is the canonical list of sync event types and their payload shapes. It finalizes the "exact sync event schema" open item from Todo.md (needed by Phase 8). The source of truth is packages/protocol/src/events/event-types.ts; the generated schemas/event.schema.json is the machine-readable equivalent.

The canonical event envelope

{
  "event_id": "uuid",
  "origin_id": "node_abc",
  "origin_sequence": 1042,
  "type": "FILE_CREATED",
  "payload": { "...": "..." },
  "timestamp": "2026-09-02T12:00:00Z"
}
FieldTypeNotes
event_idstring (uuid)Globally unique; used for idempotent application (§8 dedupe)
origin_idstringThe node/device that created the event
origin_sequenceinteger ≥ 0Monotonically increasing, scoped to the origin. Together with origin_id this provides total ordering per origin and enables cursor-based sync (§18).
typeenumOne of the canonical types below
payloadobjectValidated against the schema for type
timestampISO 8601 stringWhen the event was created at the origin

Ordering guarantees

  • Within one origin: origin_sequence is strictly increasing. Events from a single origin are strictly ordered.
  • Across origins: there is no global total order. Each side tracks a cursor per origin ({ origin_id, sequence }) and computes the diff (§18). Event application is idempotent by event_id, so out-of-order or duplicated delivery across origins cannot create duplicate state.

Canonical event_type values

FILE_CREATED

A file was created.

FieldTypeRequiredNotes
file_idstringyesLogical file identifier
parent_folder_idstring | nullnoOmitted for root-level files
encrypted_namestringnoEncrypted filename (opaque to the relay)
content_hashstringnoBLAKE3 of the latest version

FILE_DELETED

A file was deleted (represented with a tombstone, §17).

Same payload shape as FILE_CREATED.

FILE_VERSION_ADDED

A new immutable version was added to an existing file.

FieldTypeRequiredNotes
file_idstringyesLogical file identifier
version_numberinteger ≥ 1yesMonotonic version for this file
shard_countinteger ≥ 1yesTotal shard count for this version
version_hashstringyesBLAKE3 content hash of this version
parent_folder_idstring | nullnoOmitted for root-level files
encrypted_namestringnoEncrypted filename
content_hashstringnoAlso accepted; redundant with version_hash

FILE_MODIFIED

A file's content changed. Same payload shape as FILE_VERSION_ADDED (immutable versions model an edit as a new version).

FILE_SHARD_STORED

A Storage Node committed an encrypted shard to its object store after receiving it directly over Path A/B WebRTC (not via the Relay buffer). The Relay projects this into file_locations as NODE_STORED so the catalog knows the shard is durably stored and downloads can resolve it; origin_id is the storing node.

FieldTypeRequiredNotes
file_idstringyesLogical file identifier
version_numberinteger ≥ 1yesVersion the shard belongs to
shard_indexinteger ≥ 0yes0-based shard position
hashstringyesBLAKE3 of the stored ciphertext
size_bytesintegernoStored ciphertext length

DEVICE_REVOKED

A device lost access (revocation removes only its key envelope, per ADR-0001 — file keys are not rotated).

FieldTypeRequiredNotes
device_idstringyesThe revoked device
revoked_atISO 8601 stringyesWhen the revocation happened

TOMBSTONE_CREATED

Records that an entity was deleted, preventing resurrection by a long-offline device (§17). Retention is governed by the GC policy (§29a).

FieldTypeRequiredNotes
entity_typeenumyesfile or folder
entity_idstringyesThe deleted entity's ID
deleted_atISO 8601 stringyesWhen the deletion happened

TOMBSTONE_REMOVED

Restore: removes a tombstone so the entity is live again. Emitted by a device from the Tombstone view; both the Relay and the Storage Node delete their tombstone row, and retained node data is no longer purged at the original retention deadline.

FieldTypeRequiredNotes
entity_typeenumyesfile or folder
entity_idstringyesThe restored entity's ID

FOLDER_CREATED

A folder was created.

FieldTypeRequiredNotes
folder_idstringyesFolder identifier
parent_folder_idstring | nullnoOmitted for root-level folders
encrypted_namestringnoEncrypted folder name

FOLDER_DELETED

A folder was deleted.

Same payload shape as FOLDER_CREATED; folder_id is the only required field.

Projection: since Phase 14 F1 both folder events are projected by the Relay (folders table) and the Rust node, and are accepted from authenticated devices. A FOLDER_CREATED whose id already has a tombstone is acknowledged but not projected, so a long-offline device cannot resurrect a deleted folder. Folders are also carried in snapshots via the "folder" chunk record type.

KEY_ENVELOPE_ADDED

A device published an opaque FEK envelope so a recipient can decrypt a file (§25, Phase 14 F2). The payload is:

FieldTypeRequiredNotes
file_idstringyesThe file the FEK belongs to
recipient_idstringyesdevice_id or node_id the FEK is sealed for
recipient_kind"device" | "node"yesWhich identity space recipient_id is in
encrypted_keystringyesOpaque client encoding of the sealed FEK

The Relay projects this into key_envelopes(file_id, recipient_id, recipient_kind, encrypted_key) and never sees the FEK. Revoking a device deletes its envelopes. Envelopes are carried in snapshots ("key_envelope" chunk record type), so a full Relay rebuild preserves them; the node stores them opaquely without being able to open them.

ACTIVITY_LOGGED

A client records a terminal activity outcome so the Activity feed is durable on both the Relay and the Storage Node and reads the same online or offline. The Relay journals it in sync_events and serves it from GET /activities; the Node stores it and serves it over the LAN (GET /nodus/activities).

Privacy: the payload carries no file name (names are E2E; the Relay/Node must never see them). file_id lets each client resolve the display name from its own decrypted catalog/tombstone; detail is limited to non-sensitive text.

FieldTypeRequiredNotes
activity_idstringyesClient-generated uuid; the feed's dedupe key
kindenumyesupload, download, delete, restore, purge, rename, move, conflict
outcomeenumyescomplete or failed
file_idstring | nullnoFile the action concerned
pathstring | nullnoTransfer path (local/relay/buffered/queued/offline)
detailstring | nullnoNon-sensitive summary or error text
created_atISO 8601 stringyesWhen the action finished

Adding a new event type

  1. Add a literal to EventTypes in src/events/event-types.ts.
  2. Add it to EventTypeSchema (the zod enum).
  3. Define a payload schema and register it in EventPayloadMap.
  4. Regenerate schemas/ and update this document.
  5. Mirror in Rust/Go per the cross-language sync rules in schema-versioning.md.

On this page