protocol/app
packages/protocol/src/app
Purpose
Public barrel for app RPC descriptors and app-hook protocol types.Public surface
AppManifest
TypeAlias
AppManifestValidationResult
TypeAlias
appNotifications
Variable
appRpcMethods
Variable
AppsRegister
Variable
DispatchAuthorize
Variable
deny verdict at
LeaseRegistry.resolve. Manifests opt in by declaring
hooks.dispatch_authorize.
DispatchesConsumed
Variable
messages/send. Fires at Claim.finalize time, after
the durable insert lands, scoped to the moderator’s connection only
(NOT broadcast). The moderator IS the authority for the lease, so
messageId visibility is in-scope.
DispatchesExpired
Variable
DispatchesGet
Variable
moderatorConnectionId (the binding tuple recorded at mint time);
non-moderator callers fail with ForbiddenError.
DispatchId
TypeAlias
dispatches/get,
dispatches/consumed, dispatches/expired) can reference an
admission attempt by a stable handle whose lease may have been
rolled back-and-re-granted within the same dispatch.
DispatchId
Variable
dispatches/get,
dispatches/consumed, dispatches/expired) can reference an
admission attempt by a stable handle whose lease may have been
rolled back-and-re-granted within the same dispatch.
DispatchRelease
Variable
leaseId and unparks on this notification.
leaseTimeoutMs is set on the grant arm only and is the post-
grant TTL (Final Decision #9). HOLD inherits the same TTL by ageing
out via the standard EXPIRED path; no leaseTimeoutMs field needed
on the hold arm because the grant TTL has not started yet (lease
never reached GRANTED).
DispatchRequest
Variable
{leaseId, dispatchId} and emits an out-of-
band dispatch/release notification carrying the verdict.
Wire ordering: the ack and dispatch/release may race — the
recipient absorbs the race via a client-side ring buffer + per-
lease Deferred (see packages/client/src/channel-core.ts).
MessagesAuthorize
Variable
MessageService.sendCommit after the durable insert
lands and before the broadcast. Manifests opt in by declaring
hooks.message_authorize. Failure / timeout in the round-trip
synthesizes a fail-closed Block { reason: "tm_unreachable" }
verdict at the AppHost envelope (mirrors runAuthorizeDispatch’s
wrapHookEffectWithEnvelope posture).
Forward { recipients } MUST be a subset of the conversation’s
participants; the server does not re-fan to non-participants.
Forward { recipients: [] } is legal — message lands in the
sender’s transcript but is delivered to no one else.
taskCallbackMethods
Variable
TaskCreate
Variable
task/request handler after
the task row is inserted (status "waiting") and before the
requester observes any state.
The TM owns the post-accept lifecycle:
- On
acceptthe server transitions the task to"active"and firestask/createdto the requester. The TM SHOULD then calltask/conversation/createto honor the requester’sinitialConversationhint if it chose to. - On
reject(or timeout / RPC error / decode failure) the server transitions the task to"failed"and firestask/failedto the requester.
DispatchAuthorize /
MessagesAuthorize: timeout synthesizes
{ decision: "reject", reason: "timeout" }; an unknown app or
RPC/decode failure synthesizes reason: "tm_unreachable".
Durability note: the task/request handler inserts the task row
(waiting) BEFORE this callback’s network round-trip, and the
terminal setStatus runs AFTER it. The sequence is not atomic
(the callback is a network call, not a DB op), so a crash or fiber
interrupt in that window can strand a task in waiting. Stranded
waiting tasks are invisible to delivery (no conversation, no
participants observe them) and are reaped by follow-up work (the
stale-waiting-task sweep, #684).
validateAppManifest
Function
Files
methods.ts