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. Atask/* 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
AnyAgentClientRpcDefinition
TypeAlias
AnyNotificationDefinition
TypeAlias
AnyServerRpcDefinition
TypeAlias
AnyTaskCallbackRpcDefinition
TypeAlias
AnyTaskMasterRpcDefinition
TypeAlias
brandedId
Function
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
TNumber TypeBox schema whose static type is
BrandedNumber<BrandName>. Same shape as brandedString for the
numeric case.
BrandedNumber
TypeAlias
number carrying a nominal Brand.Brand<BrandName> tag.
brandedString
Function
TString TypeBox schema whose static type is
BrandedString<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
string carrying a nominal Brand.Brand<BrandName> tag. Prevents
a string from accidentally type-fitting a slot expecting the brand.
checkProtocolRange
Function
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. Tworeasondiscriminants in the wire error’sdatafield:server-above-client-max—compareProtocolVersion(serverVersion, params.maxProtocol) > 0. The server is newer than the client knows how to talk to.server-below-client-min—compareProtocolVersion(serverVersion, params.minProtocol) < 0. The client is newer than the server supports.
InvalidProtocolVersionError—params.minProtocolorparams.maxProtocolis not a well-formed numeric version string. Untrusted client input crosses the boundary here, so the sync throw in compareProtocolVersion is wrapped inEffect.tryand surfaces as a typed channel error. Callers (thenetwork/connecthandler) catch this and map toInvalidParamsError(JSON-RPC-32602).
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
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
date-time
checker registered at module load (regex plus Date.parse finiteness).
dateTimeStringSchema
Function
DateTimeStringSchema singleton. Functioned so
callers can keep as const references stable while the schema body
is owned here.
decodeClientInbound
Function
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
DecodedResponseError
Class
error
sub-object carries code/message/data, no Effect machinery).
DecodedResponseSuccess
Class
DecodedServerInbound
TypeAlias
decodeServerInbound
Function
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
InvalidProtocolVersionError
Class
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
Echannel 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).
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
JsonValueSchema
Variable
ListCursor
TypeAlias
listCursorSchema
Function
ListLimitSchema
Variable
MAX_PAGE_LIMIT
Variable
notificationDefinitions
Variable
PROTOCOL_VERSION
Variable
RegisteredTaggedError
TypeAlias
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
stringEnum
Function
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
Files
pagination.tsrpc-registry.tsschema-primitives.tsversion.ts