Skip to content

Ids and errors

import {
uuidv7, uuidv5, timestampOf, isUuid, CYNAPSE_NAMESPACE,
CynapseError, EXIT_OK, EXIT_FAILURE, EXIT_USAGE, EXIT_TIMEOUT, EXIT_AMBIGUOUS_ADDRESS, EXIT_UNKNOWN_ADDRESS, errorCodeFor, exitCodeFor, helpFor, renderCliError,
} from 'cynapse'

Channels and entries are identified by UUIDs. Entries are always UUIDv7, minted by the writer; a channel is UUIDv7 unless it is derived (see createChannel).

A UUIDv7 (RFC 9562): 48 bits of Unix milliseconds, then a 12-bit counter, so ids minted in the same millisecond by one process still sort in mint order. now is milliseconds since the epoch, defaulting to Date.now(). Arrival order across writers is the job of seq, not the id.

The Unix millisecond timestamp a UUIDv7 carries. This is where an entry’s createdAt comes from.

new Date(timestampOf(entry.id)).toISOString() === entry.createdAt

A name-based UUIDv5 (SHA-1): the same name in the same namespace always gives the same id. namespace defaults to CYNAPSE_NAMESPACE. This is how --key and --anchor make channel creation idempotent.

uuidv5('dm:a:b') === uuidv5('dm:a:b') // true: two agents derive the same channel

'0199a6c4-5b1e-5c3a-9d2f-6e7c8b9a0d1e' — the namespace cynapse derives channel ids in. It is fixed forever: changing it would give every derived channel a new identity.

True for any value in canonical 8-4-4-4-12 hex form, case-insensitive. The store uses it to tell a channel or entry UUID from a handle, which is why a handle may not look like a UUID.

A failure raised deliberately, with an exit code a caller can branch on.

class CynapseError extends Error {
readonly exitCode: number
/** A stable, machine-readable reason, for callers that branch on more than the exit code. */
readonly code?: string
/** Structured facts a caller acts on, such as an ambiguous address's `candidates`. */
readonly details?: Record<string, unknown>
/** The next step to suggest; without one, `helpFor` gives the code's default. */
readonly help?: string
constructor(
message: string,
options?: { exitCode?: number; cause?: unknown; code?: string; details?: Record<string, unknown>; help?: string },
)
}

exitCode defaults to EXIT_FAILURE. The store throws CynapseError for a missing channel or entry, a taken or invalid handle, a missing view, a cross-channel parent and a non-UUID id. A missing channel, entry or view carries code: 'not_found' and a conflict code: 'id_conflict'; cynapse gui adds port_in_use and gui_not_installed. The stable codes are listed in the CLI overview.

try {
store.append('notes', { id, author: 'alice', type: 'demo.note', body: 'changed' })
} catch (error) {
if (error instanceof CynapseError && error.code === 'id_conflict') {
// the id was already used with a different payload
}
}
Constant Value Meaning
EXIT_OK 0 Success, including --help and --version.
EXIT_FAILURE 1 The command ran and failed.
EXIT_USAGE 2 Usage error: bad flag, bad option value, no participant.
EXIT_TIMEOUT 3 entry wait ran out of time with no reply. Waiting again may still get one.
EXIT_AMBIGUOUS_ADDRESS 4 resolveAddress matched more than one live participant (ambiguous_address).
EXIT_UNKNOWN_ADDRESS 5 resolveAddress matched no live participant (unknown_address).

The set is small and stable on purpose: anything new needs a reason a caller can act on differently. See the CLI overview.

The exit code for any thrown value: a CynapseError’s own, and EXIT_FAILURE for everything else (unknown throws are ordinary failures, never usage).

The machine-readable reason for any thrown value: a CynapseError’s own code, otherwise usage when its exit code is EXIT_USAGE and failure for everything else.

The suggested next step for any thrown value: a CynapseError’s own help, otherwise the default for its code. A throw cynapse did not raise on purpose is treated as a bug and points at the issue tracker. Every value gets one.

No stack. The message of an Error, with its cause’s message appended after a colon when the cause adds information; String(error) for anything else, behind an error: label, then a help: line from helpFor. With format 'json' it is { "error": { "code", "message", "help", ...details } }, pretty-printed like other --json output, the code from errorCodeFor. This is what the CLI prints to stdout on failure.