Protocol And Server Organization
This document is the source of truth for the protocol and server-core organization. Update this document first when the package layout or wire surface changes. The goal is a protocol package whose folders describe protocol domains, whose wire names make the caller principal explicit, and whose public barrels expose only the stable surface for each domain.Layering
transport is the lowest layer. It provides generic RPC, notification, error,
wire-string, pagination, mux, and dispatch machinery. It must not import
identity, network, task, conversation, message, socket, or testing.
Protocol layers build upward:
identity owns principals, identities, credentials, and principal
requirements. network owns socket handshake and presence protocol. Task,
conversation, and message are peer domains. Dispatch is message moderation, so
it stays under the message domain. socket owns the runtime factories that
open agent/app clients and the server.
Protocol Tree
Domain Rules
Top-level domain folders are protocol concepts, not principals.app is not a
top-level domain folder because app is a principal. App-specific identity data
belongs under identity/apps; app callbacks belong beside the domain they
authorize.
Examples:
- app connect belongs in
network/connect.ts - app key and app manifest belong in
identity/apps/ app/message/authorizebelongs inmessage/messages.tsapp/dispatch/authorizebelongs inmessage/dispatch.tsapp/task/createbelongs intaskbecause it is a task callback
message/dispatch.ts. Do not nest conversation or message
under task just because their requirements include taskId.
Wire Naming
Wire method names use this shape:- Principal is explicit:
agentorapp. - Domain is the primary resource domain.
- Requirements do not appear in the namespace.
- Use kebab-case, not camelCase.
- RPC names end with an action such as
connect,list,request,send,update,authorize, orget. - Notifications use past-tense event names such as
received,changed,released,consumed, orexpired. - HTTP schemas can share the same naming scheme but are not catalog members.
Wire Surface
Network
network/connect.ts also owns PROTOCOL_VERSION, range checks, and protocol
mismatch errors because those are handshake concerns. Do not create a standalone
version.ts.
Identity
agent/identity/agents/list is the discovery surface. Id/name filtering should
be represented by list parameters or performed by the client over list results;
do not add separate agent lookup RPCs.
agent/identity/contacts/list is the contacts read surface. Do not add a
separate contact-by-id/get RPC unless there is a server-side authorization or
consistency boundary that list cannot represent.
Task
app/task/update owns task participant changes and task close semantics. Its
schema owns validation for which changes are allowed together.
Conversation
app/conversation/update owns archive, unarchive, participant add, and
participant remove semantics. Requirements still enforce task ownership and
conversation membership.
Message
Message Dispatch
app/dispatch/lease/get is a method. Lease consumed and expired are
server-pushed events, not method siblings. These descriptors live under
message/dispatch; there is no top-level dispatch package export.
Credentials
Credentials live beside the identity they authenticate. There is no rootcredentials.ts.
Principals And Requirements
Principals are identity concepts, not transport concepts.requirements/ folder even if there is only one
requirement.
- its name
- the middleware it maps to
- the error classes it can raise
- the requirement value referenced by RPC descriptors
requirements.ts.
Barrels And Public Surface
Each domain gets a narrowindex.ts barrel. Barrels expose only the public
domain surface: ids, public schemas, public types, descriptors, notifications,
and requirements intended for consumers.
Descriptor arrays live in the same domain implementation file when the domain
is small, or in the domain barrel when they only assemble local descriptors.
Do not create catalog files just to avoid a few imports.
Public package exports should be limited to:
./requirements./rpc-method-groups./transport, unless we explicitly support third-party protocol extensions
./rpc is the deliberate public replacement for ./transport. It exports
stable call-site support such as ParamsOf, ResultOf, notification delivery
types, typed dispatch helpers, pagination cursor schemas, and shared wire error
classes. It must not export descriptor construction APIs such as defineRpc or
defineNotification.
The root export is a small runtime surface:
./socket/catalog is the explicit first-party integration surface for closed
RPC catalogs, Effect RPC groups, and the derived handler/definition union types
used by @moltzap/client, @moltzap/server-core, conformance, and generated
reference docs. Do not re-export those catalog symbols from ./socket.
Import Rules
Imports should go through barrels, and source files should use absolute import specifiers. Relative imports are only for barrels that assemble their own local folder surface, files importing private helpers from the same folder, and same-domain implementation files that would create an initialization cycle by importing through the domain barrel. The conformance suite is a private testing harness: property files may import../_shared helpers and ../../toxics
helpers relatively; _shared modules may import testing fixtures/errors from
testing/; and _shared/suite.ts may assemble sibling property groups.
Conformance code still imports protocol domains through aliases such as
#identity, #network, #task, #conversation, #message, and #socket.
Allowed:
- Implementation files import from package-local absolute aliases such as
#identity/agents,#task,#message, or#socket. - Domain code imports lower-layer domain barrels, never the package root barrel.
- Same-domain private implementation imports may stay relative when the barrel imports the file being edited.
- Conformance property files may use relative imports only for private
conformance harness modules under
testing/conformance/_sharedandtesting/toxics; conformance shared modules may also import testing fixture and error helpers fromtesting/. - Cross-package consumers import public package subpaths such as
@moltzap/protocol/identity,@moltzap/protocol/task, or@moltzap/protocol/socket. - Do not deep-import implementation files across folder boundaries.
- Do not use
export *; barrels must list the symbols they expose. - Configure TypeScript
pathsfor package-local source aliases and runtsc-aliasafter package builds so emitted ESM and declaration files do not contain unresolved#...specifiers. - Configure tests and source-runner scripts to resolve the same alias table.
tsc-aliasfixes emitted output; it does not by itself make source-mode Vitest ortsxruns resolve aliases.
Rebalance Order
Use this order when a package needs another domain rebalance:- Document the desired tree and public package exports.
- Add package-local aliases, narrow barrels, and source-runner alias resolution before moving code.
- Move code domain by domain while imports go through the new barrels.
- Move credentials, ids, principals, requirements, and descriptors beside the domain that owns them.
- Move socket lifecycle factories under
socket/and build composition from domain barrels. - Narrow package exports and regenerate reference docs from the moved descriptors.
Server-Core Organization
@moltzap/server-core mirrors the protocol domains while keeping runtime and
infrastructure concerns out of domain folders.
Server folders describe either:
- runtime infrastructure (
core,socket,http,db) - the server-side protocol adapter (
moltzap) - a protocol domain (
identity,network,task,conversation,message,dispatch) - tests and published test utilities
Server Tree
Server Domain Rules
core/ owns server boot and wiring: the CoreApp API, Effect service graph,
handler catalog, request-scoped runtime helpers, and tracing.
socket/ owns WebSocket connection lifecycle, request-scoped principal
context, principal gating, and requirement middleware layers. Keep transport
as a protocol-package concept, not a server-core top-level folder, unless a
server file truly implements generic transport machinery independent of
sockets.
http/ owns HTTP routing and Node HTTP server construction. Registration HTTP
handlers can be implemented in domain folders but composed by http/routes.ts.
identity/apps/ owns app identity and live app endpoint registration:
auth.service.ts, default-app.ts, registry.ts, and
endpoint-registry.ts. Domain callback behavior lives with the domain that
uses it: message/authorization.ts, task/authorization.ts, and
dispatch/admission.service.ts.
task/, conversation/, message, and dispatch/ are peer folders.
Pagination helpers should not become per-domain standalone files by default.
The protocol transport/pagination.ts owns shared cursor/page schemas,
db/list-cursor.ts owns database cursor encoding, and each domain service owns
the query-specific pagination logic it needs.
Server-side requirement implementations live beside the service that can prove
the invariant. For example:
- task read/app ownership checks ->
task/requirements/ - conversation membership/send access ->
conversation/requirements/ - contact reachability ->
identity/contacts/requirements/if needed
Server Barrel Rules
Server-core root exports are intentionally empty. Production users consume the server through themoltzap-server bin and standalone config, while tests and
runtime harnesses use the published ./test-utils subpath. Domain barrels are
internal import boundaries.
Use:
export *.
Server Import Rules
Server-core uses the same import discipline as protocol: implementation files import absolute package-local barrels, and relative imports are limited to barrels/catalogs that assemble their own folder. Allowed:Server Layout Status
Server-core follows the same domain split as protocol:- Identity owns agent/app/contact registration, auth, and contact policy.
- Network owns connect handlers, presence, and outbound notification send.
- Task, conversation, message, and dispatch each expose a flat
domain/handlers.tsentry point for RPC handlers. - Domain services sit directly beside their handlers as
domain/*.service.ts; helper shapes that only support one service stay private to that implementation. - Requirement checks live beside the domain they prove, and socket middleware imports them through domain requirement barrels.
- The server handler catalog imports only domain handler barrels; the package
root remains empty and the published
./test-utilssubpath is the only secondary package surface.