npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ceralive/control-protocol

v2026.7.0

Published

Canonical device/hub control-channel Zod schemas + §14 wire fixtures + strict/tolerant parse helpers for the Remote Control Plane v2.0 (openspec/specs/remote-relay-support).

Downloads

228

Readme

@ceralive/control-protocol

Canonical device/hub control-channel Zod schemas, §14 wire fixtures, and strict/tolerant parse helpers for the Remote Control Plane v2.0 (openspec/specs/remote-relay-support/spec.md).

This package is the single canonical schema source for the control channel that connects a paired CeraLive device (CeraUI/apps/backend) to the cloud platform hub (ceralive-platform). It replaces the two previously-independent, hand-written per-repo protocol.ts derivations — both consumers pin a specific CalVer version of this package rather than re-deriving their own schema by hand.

Decision record — 2026-07-07. This supersedes the prior per-repo "each side writes its own Zod from the spec, no shared package" stance for this contract only. Root AGENTS.md Rule D ("repos are self-contained; mirror, don't link") is otherwise unchanged: this is a registry-dependency exception, not a path-reference exception. Precedent: @ceralive/cerastream, @ceralive/srtla-send.

Install

bun add @ceralive/control-protocol

Usage

import {
  // constants + enums
  COMMAND_REGISTRY, STATUS_TYPES, FRAME_KINDS, PROTOCOL_VERSION,
  // schemas (see "Strict / tolerant contract" below)
  FrameSchema, CommandSchema, StatusSchema, DeviceCapsSchema,
} from "@ceralive/control-protocol/schemas";

import {
  strictParseFrame,     // HUB posture — closed registries
  tolerantParseFrame,   // DEVICE posture — open `type`
  strictParseIngestSlots, tolerantParseIngestSlots,
  strictParseSetProfilePayload, tolerantParseSetProfilePayload,
} from "@ceralive/control-protocol/parse";

import { FRAME_FIXTURES, FIXTURE_14_1 } from "@ceralive/control-protocol/fixtures";

The root export (@ceralive/control-protocol) re-exports all three subpaths.

Evolution policy

Evolution is additive-optional forever: the hub-strict parser MUST NEVER require a field that the oldest supported device firmware does not emit — a change that would break that guarantee is not a version bump of this package, it is a new protocol v.

Concretely: adding a new optional field, a new type to the command/status registry, or a new frame kind is additive and ships as a CalVer bump of this package. Making a currently-optional field required, removing a field, or repurposing one is a breaking wire change — it is a new protocol v (PROTOCOL_VERSION), negotiated at the handshake (§13), NOT a version bump here.

This invariant is enforced empirically by the v1-minimal skew test (test/skew-policy.test.ts): a frame carrying only the required envelope fields and no post-v1 optional field is accepted by the tolerant parser AND is never rejected by the strict parser.

Strict / tolerant contract

The device and the hub deliberately parse the wire with different strictness, and this package preserves that asymmetry byte-for-byte rather than collapsing it:

| Posture | Who | Helper prefix | Behaviour | |---------|-----|---------------|-----------| | strict | hub (ceralive-platform) | strictParse* | Closed type registries, closed ingest.slots slot shape, UUID-v4 commandId. Validates what the hub emits — fails loudly at the producer. | | tolerant | device (CeraUI/apps/backend) | tolerantParse* | type left open, ingest.slots descriptive fields nullable/absent, commandId any non-empty string. Applied to frames the device receives — an additively-extended hub is never rejected by an older device. |

The three asymmetry axes (each has an explicit *Strict*/*Tolerant* schema pair):

  1. Frame type — strict closes it to the relevant registry (COMMAND_REGISTRY / STATUS_TYPES / ACK_TYPES); tolerant leaves it any non-empty string.
  2. ingest.slots slot — strict requires every descriptive field (region/state/default) and bounds strings/ports; tolerant makes the descriptive fields optional, obsInstanceId freely nullable, strings/ports unbounded.
  3. device.setProfile commandId — strict is a UUID v4 (the hub mints cid === commandId); tolerant is any non-empty string.

Un-suffixed alias resolution

Both source repos export un-suffixed names. Where the two collide with different strictness, the un-suffixed canonical alias resolves to the strict (hub / spec-reference) variant; the tolerant variant is always reachable under its explicit *Tolerant* name.

| Un-suffixed alias | Resolves to | Notes | |-------------------|-------------|-------| | CommandSchema, ResultSchema, StatusSchema, AckSchema, DeliveryAckSchema, FrameSchema | *StrictSchema | Device consumers use *TolerantSchema. | | HandshakeSchema, HandshakeDeviceSchema, HandshakeHubSchema | full frame schemas (hub) | See "Handshake name collision" below. | | IngestSlotSchema | IngestSlotTolerantSchema | CeraUI's IngestSlotSchema was the tolerant single slot. | | IngestSlotPayloadSchema | IngestSlotStrictSchema | Platform's name for the strict single slot. | | IngestSlotsPayloadSchema | IngestSlotsStrictPayloadSchema | Both repos export this name; canonical = strict (what the hub emits). | | EnvelopeSchema, ResultPayloadSchema, DeviceCapsSchema | shared (no split) | Identical on both sides. |

Documented same-concept aliases (both names → same value)

| Names | Value | |-------|-------| | KINDS (platform) · FRAME_KINDS (CeraUI) | the frame-kinds array | | SELF_FENCING_COMMANDS (platform) · SELF_FENCING_TYPES (CeraUI) | the self_fencing set | | SELF_FENCING_WATCHDOG_SECONDS = 30 · SELF_FENCING_WATCHDOG_MS = 30_000 | the same 30-second watchdog window (documented equivalence: 30 s === 30_000 ms) |

Handshake name collision (recorded)

The two repos use the same three names for opposite shapes:

  • ceralive-platformHandshakeDeviceSchema / HandshakeHubSchema are full frames ({v, kind:"handshake", type, cid, payload}); HandshakeSchema is the union of the two frames.
  • CeraUIHandshakeSchema is a single body-agnostic frame; HandshakeDeviceSchema / HandshakeHubSchema are the body schemas ({v, supportedTypes, deviceCaps} and {v, role}).

The un-suffixed canonical names here take the platform / spec-fixture-aligned meaning (full frames — §14.2/14.3/14.18 are frames). The CeraUI body-schema meaning is preserved under distinct names:

  • HandshakeDeviceBodySchema / HandshakeHubBodySchema — the hello bodies (device-tolerant: supportedTypes not min(1)).
  • HandshakeEnvelopeSchema — the single body-agnostic handshake frame (CeraUI's HandshakeSchema).

Todos 6/9 (the CeraUI + platform protocol.ts migrations) decide per-symbol which name each repo adopts; this package provides all of them so either choice is possible.

device.setProfile ack — no wire payload

Per the amended REQ-RRS-028 / §5.1, the device.setProfile acknowledgement is a payload-less delivery.ack correlated purely by cid === commandId. No frame on the wire carries a { commandId, applied } delivery-ack payload.

SetProfileAckPayloadSchema is still exported only for API-compatibility with ceralive-platform's current type export of that name — it is annotated ⚠️ NON-WIRE in schemas.ts and does NOT correspond to any actual wire shape. The real per-push outcome ({commandId, status, reason?, effectiveActiveProfile, effectiveLatencyMs}) rides the subsequent result frame's applied field and is exported as SetProfileResultAckSchema (type SetProfileAck).

Conformance fixtures

The fixtures/*.json files are the §14 JSON vectors, copied verbatim (spec key-order preserved) and re-exported from src/fixtures.ts as typed consts. A parity test (test/fixtures.test.ts) asserts the TS consts are byte-for-value identical to the JSON files, so the two can never drift. 20 vectors are wire frames (FRAME_FIXTURES); the §14.15 control-channel PASETO claim set is a non-frame vector (NON_FRAME_FIXTURES) and is never routed through the frame parsers.

Divergences from the frozen spec.md §14 bytes

This package's fixtures are not byte-frozen the way spec.md's §14 bytes are, so two known spec.md fixture defects are corrected here (both documented at the top of src/fixtures.ts):

1. Fixture 14.20 (device.setProfile delivery.ack) — payload removed. spec.md §14.20 still shows a stale payload: { commandId, applied } object that contradicts the corrected REQ-RRS-028 prose (payload-less delivery.ack). It is exported here in its corrected, payload-less form, matching the amended prose and matching every other delivery.ack fixture (14.17). (See CeraUI/.omo/notepads/coherence-contract-pass/decisions.md §2 + §5.)

2. Fixtures 14.18–14.21 cid — non-RFC-4122-v4 variant nibble corrected. The profile-related fixtures added in the srt-receive-profiles effort carry cid values whose UUID variant nibble is invalid for v4 (the 4th group starts with 1/2/3 where v4 requires 8/9/a/b). Both repos validate the envelope cid with z.uuidv4(), and §3 mandates "cid MUST be a UUID v4", so those spec vectors do not conform to the very schema they are meant to validate — a defect that went unnoticed because neither repo's protocol.contract.test.ts ever parsed 14.18–14.21. Rather than loosen cid validation (which would diverge from both repos' faithful z.uuidv4()), this package corrects the single offending variant nibble to a valid v4 value (and the matching payload.commandId in 14.19/14.21, preserving the cid === commandId correlation). Every other digit is untouched.

Both corrections are deliberate and consistent with what the downstream migrations (todos 6/9/7) need: their v1-minimal / unknown-field-tolerance tests parse these vectors and must not choke on a schema-invalid fixture.

Build & test

Plain tsc (no bundler), matching the sibling @ceralive/cerastream and @ceralive/srtla-send npm packages. Emits ESM + .d.ts to dist/.

bun install       # also runs `prepare` → build
bun test          # bun:test contract suite
bun run typecheck # tsc --noEmit (source + tests)
bun run build     # tsc -p tsconfig.build.json → dist/ (ESM + types)

Versioning

CalVer (YYYY.MINOR.PATCH), matching the sibling @ceralive packages and the versions.yaml convention. The wire PROTOCOL_VERSION (currently 1) is independent of this package's CalVer — see "Evolution policy" above.

Release & prerelease (npm publish)

This package is published to the public npm registry by the root repo's .github/workflows/publish-control-protocol.yml workflow. It is CI-triggered only — never npm publish by hand. Auth is a scoped npm automation token (NPM_TOKEN secret) plus --provenance; the federation/apt signing keys are never used here.

Two lanes, one tag namespace

The package versions on its own tag namespace control-protocol-v<CalVer> (deliberately distinct from the workspace's other release tags), and the tag's version MUST equal the committed package.json "version" (the workflow refuses to publish otherwise). CalVer is YYYY.M.PATCH, matching @ceralive/cerastream and @ceralive/srtla-send.

| Tag shape | Example | npm dist-tag | Purpose | |-----------|---------|--------------|---------| | control-protocol-vYYYY.M.PATCH | control-protocol-v2026.7.0 | latest | Stable release | | control-protocol-vYYYY.M.PATCH-rc.N | control-protocol-v2026.7.0-rc.1 | next | Release candidate — the W2/W3 integration bridge |

An -rc.N version publishes under next and never moves latest. A workflow_dispatch run performs build + test + a registry --dry-run only — it never publishes.

RC hygiene — the rules (non-negotiable)

  1. Exact-pin rule. Consumers pin the exact prerelease version in their committed package.json and lockfile — e.g. "@ceralive/control-protocol": "2026.7.0-rc.1". Never the floating next dist-tag, never a semver range (^, ~, >=, *, next). A floating pin would let an unreviewed rc bump land silently on a consumer at install time; an exact pin makes every protocol-schema change a reviewed, committed diff. This rule applies identically to @ceralive/cerastream prereleases when a consumer is on the same integration bridge.

  2. Dist-tag cleanup/advancement after the final release. Once the stable YYYY.M.PATCH ships (published under latest), retire the bridge rc from next so next never dangles at a stale/older rc:

    # after control-protocol-vYYYY.M.PATCH (stable) is published under `latest`:
    npm dist-tag rm  @ceralive/control-protocol next            # drop the bridge rc
    # (optional) if a NEW integration bridge opens on the next rc, point next at it:
    npm dist-tag add @ceralive/control-protocol <next-rc> next  # e.g. 2026.8.0-rc.1

    Leaving next pointed at a superseded rc is the failure mode this step prevents.

  3. Merge gate — each consumer repo rejects rc pins on the merge-target branch. A PR into a consumer repo's canonical branch (CeraUI, ceralive-platform) MUST NOT merge while it still pins a prerelease of the shared schema/engine packages. Each consumer repo adds a CI guard (a simple grep, added in that repo's own wave — todos 6/9) that fails the build if any committed package.json/lockfile carries an -rc. pin of @ceralive/control-protocol or @ceralive/cerastream:

    # merge-gate guard (fails, i.e. exits non-zero, if ANY rc pin is present)
    ! git grep -nE '"@ceralive/(control-protocol|cerastream)"[^"]*"[^"]*-rc\.' \
        -- '**/package.json' '**/bun.lock' 'bun.lock' 'package.json'

    The rc pin is legitimate only on the short-lived integration bridge branch; the gate ensures it is swapped for the exact stable CalVer pin before the PR merges, so a stable branch can never carry a prerelease dependency.

Release procedure

# 1. Bump the version in control-protocol/package.json (CalVer YYYY.M.PATCH,
#    or YYYY.M.PATCH-rc.N for an integration-bridge prerelease).
# 2. Commit the bump.
git add control-protocol/package.json && git commit -m "release(control-protocol): vYYYY.M.PATCH"
# 3. Tag on the package's own namespace and push the tag — CI does the publish.
git tag control-protocol-vYYYY.M.PATCH        # or ...-rc.N
git push origin control-protocol-vYYYY.M.PATCH

The publish job is gated on the test job (needs: test), so a red contract suite can never publish.

License

GPL-3.0.