Skip to main content

protocol/src

packages/protocol/src

Purpose

Public barrel — protocol layer DAG. The protocol package is the leaf in the workspace dependency graph, and internally it is split into five layers with their own one-way dependency order. Re-exports below are arranged in DAG order so the file itself is the manifest. A task/* method may reference identity/* types (e.g. AgentId); the reverse import is forbidden. The server’s Tag-allowlist hierarchy in @moltzap/server-core mirrors this DAG: a handler may pull services only from layers at-or-below its own home layer.

Public surface

agentClientRpcMethods

Variable
export const agentClientRpcMethods = [
  ...identityRpcMethods,
  ...networkRpcMethods,
  ...nonTmAuthorityTaskRpcMethods,
  ...appRpcMethods,
] as const

AnyAgentClientRpcDefinition

TypeAlias
export type AnyAgentClientRpcDefinition =
  (typeof agentClientRpcMethods)[number] & RpcDefinition<string, any, any>;

AnyNotificationDefinition

TypeAlias
export type AnyNotificationDefinition =
  (typeof notificationDefinitions)[number];

AnyServerRpcDefinition

TypeAlias
export type AnyServerRpcDefinition = (typeof serverRpcMethods)[number] &

AnyTaskCallbackRpcDefinition

TypeAlias
export type AnyTaskCallbackRpcDefinition = (typeof taskCallbackMethods)[number];

AnyTaskMasterRpcDefinition

TypeAlias
export type AnyTaskMasterRpcDefinition = (typeof taskMasterRpcMethods)[number] &

brandedId

Function
export function brandedId<const BrandName extends string>(brand: BrandName)
Convenience over brandedString that adds format: "uuid". The canonical way to define wire id types in this package (AgentId = brandedId("AgentId"), TaskId = brandedId("TaskId"), etc.). The format check runs against the FormatRegistry’s UUID regex registered at module load.

brandedNumber

Function
export function brandedNumber<const BrandName extends string>(
  brand: BrandName,
  options: Parameters<typeof Type.Number>[0] = {},
)
Build a TNumber TypeBox schema whose static type is BrandedNumber&lt;BrandName>. Same shape as brandedString for the numeric case.

BrandedNumber

TypeAlias
export type BrandedNumber<BrandName extends string> = number &
A number carrying a nominal Brand.Brand&lt;BrandName> tag.

brandedString

Function
export function brandedString<const BrandName extends string>(
  brand: BrandName,
  options: Parameters<typeof Type.String>[0] = {},
)
Build a TString TypeBox schema whose static type is BrandedString&lt;BrandName>. The brand exists only at the type level — the AJV validator runs against the underlying string. Passes options through to Type.String so callers can add format, minLength, maxLength, pattern, etc.

BrandedString

TypeAlias
export type BrandedString<BrandName extends string> = string &
A string carrying a nominal Brand.Brand&lt;BrandName> tag. Prevents a string from accidentally type-fitting a slot expecting the brand.

checkProtocolRange

Function
export function checkProtocolRange(
  params: { readonly minProtocol: string; readonly maxProtocol: string },
  serverVersion: string,
): Effect.Effect<void, ProtocolMismatchError | InvalidProtocolVersionError>
Range-check the client’s protocol-version interval against an injected server version. Raised by network/connect BEFORE auth resolution; the server-side handler in @moltzap/server-core/identity/handlers/connect.handlers.ts yields this Effect as the FIRST step of handleConnect. Architect plan #706 v10 (codex r9 P2 #1) — relocated from connect.handlers.ts to here. v9 made the function’s signature testable (parameterized over serverVersion); v10 makes the function itself importable from @moltzap/protocol so regression tests can call it without an illegal test seam through the server-internal handler module. Two error channels, both typed (codex PR review #1 P2).
  • ProtocolMismatchError — versions are well-formed, just outside the supported range. Two reason discriminants in the wire error’s data field:
    • server-above-client-maxcompareProtocolVersion(serverVersion, params.maxProtocol) > 0. The server is newer than the client knows how to talk to.
    • server-below-client-mincompareProtocolVersion(serverVersion, params.minProtocol) < 0. The client is newer than the server supports.
  • InvalidProtocolVersionErrorparams.minProtocol or params.maxProtocol is not a well-formed numeric version string. Untrusted client input crosses the boundary here, so the sync throw in compareProtocolVersion is wrapped in Effect.try and surfaces as a typed channel error. Callers (the network/connect handler) catch this and map to InvalidParamsError (JSON-RPC -32602).
Production callers (handleConnect) pass the live PROTOCOL_VERSION constant; tests inject future-version values to exercise rejection paths against an unbumped branch. Example test usage: Effect.runSync(Effect.either(checkProtocolRange({ minProtocol: "2026.526.0", maxProtocol: "2026.526.0" }, "2026.527.0"))) resolves to a Left carrying a ProtocolMismatchError whose data.reason is "server-above-client-max".

compareProtocolVersion

Function
export function compareProtocolVersion(a: string, b: string): -1 | 0 | 1
Numeric comparator for PROTOCOL_VERSION strings, ordered by their dotted numeric segments (NOT lexicographically). Architect plan #706 v5 (codex r4 P2 #1) — required because CalVer values of the form YYYY.NNNN.M carry variable-digit middle components and "2026.1001.0".localeCompare("2026.527.0") === -1 (lex: 1001 < 527), opposite of the chronological/numeric truth. The v4 plan’s checkProtocolRange originally compared client.maxProtocol < PROTOCOL_VERSION via raw string ordering; v5 routes it through this helper so the “old client rejected at network/connect” gate stays correct as the publish workflow rolls the middle component past 999. Returns -1 | 0 | 1 with conventional semantics: compareProtocolVersion(“2026.527.0”, “2026.527.0”) → 0 compareProtocolVersion(“2026.526.0”, “2026.527.0”) → -1 compareProtocolVersion(“2026.1001.0”, “2026.527.0”) → 1 // numeric, NOT lex compareProtocolVersion(“2025.999.0”, “2026.1.0”) → -1 // year boundary compareProtocolVersion(“2026.527.0”, “2026.527.1”) → -1 Each input MUST be a dotted n.n.n (or wider) numeric string. The function is intentionally strict — it does NOT accept SemVer pre-release suffixes (2026.527.0-rc.1) or build metadata (2026.527.0+abc). Empty segments (e.g., "2026..0") and non-digit characters ("abc") also reject — Number("") === 0 would otherwise silently coerce, contradicting the “strict / fail-closed” JSDoc claim (review-senior P3 #2). Synchronous throw shape. Throws InvalidProtocolVersionError (a Data.TaggedError) on any malformed segment. Untrusted client input MUST be funnelled through checkProtocolRange, which wraps this call in Effect.try so the parse error flows through the Effect channel — never as a sync throw escaping into the JSON-RPC handler (codex PR review #1 P2).

DateTimeString

TypeAlias
export type DateTimeString = Static<typeof DateTimeStringSchema>;
ISO-8601 date-time string. Validated by the FormatRegistry date-time checker registered at module load (regex plus Date.parse finiteness).

dateTimeStringSchema

Function
export function dateTimeStringSchema(): typeof DateTimeStringSchema
Returns the shared DateTimeStringSchema singleton. Functioned so callers can keep as const references stable while the schema body is owned here.

decodeClientInbound

Function
export function decodeClientInbound(
  parsed: unknown,
): Effect.Effect<DecodedClientInbound, MalformedFrameError>
Typed entry point for server-inbound frames (used by the server to decode what a client sends). Same shape as decodeServerInbound but admits the FULL rpcMethods set on the request arm. Fails closed with MalformedFrameError on any mismatch, including a response frame whose id is null (no pending call to settle).

DecodedClientInbound

TypeAlias
export type DecodedClientInbound =
  | ({
      readonly _tag: "ClientRequest";
    } & DecodedRpcRequest<AnyServerRpcDefinition>)
Decoded shape of a frame inbound to the server (from client): a client RPC request, a response (success XOR error) to a server-initiated callback, or a notification.

DecodedResponseError

Class
export class DecodedResponseError extends Data.TaggedClass("ResponseError")<{
  readonly frame: ResponseFrame;
  readonly id: JsonRpcId;
  readonly error: Extract<ResponseFrame, { error: unknown }>["error"];
}> {}
Discriminated error arm of a decoded JSON-RPC response — wire-frame decoder discriminator, not an Effect tagged error (the wire error sub-object carries code/message/data, no Effect machinery).

DecodedResponseSuccess

Class
export class DecodedResponseSuccess extends Data.TaggedClass(
  "ResponseSuccess",
)<{
  readonly frame: ResponseFrame;
  readonly id: JsonRpcId;
  readonly result: unknown;
}> {}
Discriminated success arm of a decoded JSON-RPC response.

DecodedServerInbound

TypeAlias
export type DecodedServerInbound =
  | DecodedResponseSuccess
  | DecodedResponseError
  | ({
      readonly _tag: "ServerRequest";
    } & DecodedRpcRequest<AnyTaskCallbackRpcDefinition>)
Decoded shape of a frame inbound to the client (from server): a response (success XOR error), a server-initiated task-callback request, or a notification.

decodeServerInbound

Function
export function decodeServerInbound(
  parsed: unknown,
): Effect.Effect<DecodedServerInbound, MalformedFrameError>
Typed entry point for client-inbound frames (used by the client to decode what the server sends). Fails closed with MalformedFrameError on any wire-level mismatch. Client-inbound Request frames are restricted to taskCallbackMethods (the subset the server is allowed to call back into the client — dispatch/authorize, etc.). Response frames with id === null fail closed since a null id has no pending call to resolve. Sibling: decodeClientInbound — same pipeline, but admits the full rpcMethods set on the request arm (server-side use).

DEFAULT_PAGE_LIMIT

Variable
export const DEFAULT_PAGE_LIMIT = 50

InvalidProtocolVersionError

Class
export class InvalidProtocolVersionError extends Data.TaggedError(
  "InvalidProtocolVersionError",
)<{ readonly version: string; readonly segment: string }> {
  override get message(): string {
    return `compareProtocolVersion: invalid segment ${JSON.stringify(this.segment)} in ${JSON.stringify(this.version)}`;
  }
}
Raised by compareProtocolVersion (and surfaced through the Effect channel of checkProtocolRange) when an input string carries a non-numeric or empty segment — e.g., SemVer pre-release suffixes like 2026.527.0-rc.1, leading/trailing dots like "2026..0", or non-digit characters like "abc.def". Data.TaggedError shape (codex PR review P2 + review-senior P3 convergence). Was a plain Error subclass in the first impl-staff drop; switched to Data.TaggedError so:
  • The error flows through Effect’s typed E channel cleanly (Effect.catchTag("InvalidProtocolVersionError", ...) works in checkProtocolRange’s caller).
  • It matches the sibling ProtocolMismatchError convention in network/methods.ts (both tagged, both registered if a wire code is needed).
This error is NOT a wire-protocol error — it is INPUT-VALIDATION for untrusted client-supplied version strings. The network/connect handler catches it and maps to InvalidParamsError (JSON-RPC -32602) so the client gets a typed malformed-input response, not a defect.

JsonValue

TypeAlias
export type JsonValue =
  | null
  | boolean
  | number
  | string
  | ReadonlyArray<JsonValue>

JsonValueSchema

Variable
export const JsonValueSchema = Type.Recursive(
  (Self) =>
    Type.Union([
      Type.Null(),
      Type.Boolean(),
      Type.Number(),
      Type.String(),
      Type.Array(Self),
      Type.Record(Type.String(), Self),
    ]),
  { $id: "JsonValue" },
)

ListCursor

TypeAlias
export type ListCursor = BrandedString<"ListCursor">;

listCursorSchema

Function
export function listCursorSchema(): TString &

ListLimitSchema

Variable
export const ListLimitSchema = Type.Optional(
  Type.Integer({ minimum: 1, maximum: MAX_PAGE_LIMIT }),
)

MAX_PAGE_LIMIT

Variable
export const MAX_PAGE_LIMIT = 200

notificationDefinitions

Variable
export const notificationDefinitions = [
  ...networkNotifications,
  ...identityNotifications,
  ...taskNotifications,
  ...appNotifications,
] as const

PROTOCOL_VERSION

Variable
export const PROTOCOL_VERSION = "2026.529.0"

RegisteredTaggedError

TypeAlias
export type RegisteredTaggedError =
  | UnauthorizedError
  | ForbiddenError
  | NotFoundError
  | ConflictError
  | InvalidParamsError
  | NotInContactsError
  | TaskClosedError
  | TaskRejectedError
  | ConversationArchivedError
  | ConversationFullError
  | HookBlockedError
  // v11 (codex r10 P2 #2): protocol-version mismatch on
  // `network/connect`. Architect plan #706 v8 declared the class +
  // self-registered the wire code; v11 closes the type-narrowing
  // gap so `Effect.catchTag("ProtocolMismatchError", ...)` works.
  | ProtocolMismatchError;

// Spec D3 R11 — per-kind outbound catalogs.
//   `agentClientRpcMethods` — callable from `MoltZapAgentClient`.
//   `taskMasterRpcMethods`  — superset; adds TM-only operations.
//   `serverRpcMethods`      — server inbound; full union (still
//     includes the legacy `Conversations*` / plural `Tasks*` that
//     retire across Commits 6-10).
export const agentClientRpcMethods = [
  ...identityRpcMethods,
  ...networkRpcMethods,
  ...nonTmAuthorityTaskRpcMethods,
  ...appRpcMethods,
] as const;
Closed union of every wire-registered tagged-error class instance. Drives RpcCallError so consumers can Effect.catchTag(...) against concrete tags (e.g. “Forbidden”, “NotInContacts”). Mirrors the static registry built by registerErrorClass — keep in sync if a new class lands.

serverRpcMethods

Variable
export const serverRpcMethods = [
  ...identityRpcMethods,
  ...networkRpcMethods,
  ...taskRpcMethods,
  ...appRpcMethods,
] as const

stringEnum

Function
export function stringEnum<T extends string[]>(values: [...T])
Type.String({ enum: values }) typed as the union of the literal values. Use instead of Type.Union([Type.Literal("a"), Type.Literal("b")]) — same wire shape, simpler schema, single AJV enum keyword.

taskMasterRpcMethods

Variable
export const taskMasterRpcMethods = [
  ...agentClientRpcMethods,
  ...tmOnlyTaskRpcMethods,
] as const

Files

  • pagination.ts
  • rpc-registry.ts
  • schema-primitives.ts
  • version.ts