Skip to main content

protocol/testing/models

packages/protocol/src/testing/models

Purpose

Public barrel for protocol reference-model helpers.

Public surface

applyCall

Function
export function applyCall(
  state: ReferenceState,
  call: ArbitraryRpcCall,
):
Pure reducer: given state + call, yield the next state and the observable outcome. No I/O. No clocks. No exceptions — every failure flows through _tag: "error". Exhaustiveness: the switch has a branch for every method name in serverRpcMethods. A missing branch becomes a compile error at absurd. Behaviour is intentionally conservative — the model predicts the server’s observable outcome (success vs typed error), not its full result shape. Tier B canonicalizers downgrade server responses to the same projection before comparing.

authorizationOutcome

Function
export function authorizationOutcome(
  state: ReferenceState,
  call: ArbitraryRpcCall,
  agentId: AgentId,
): "allow" | "deny-unauthenticated" | "deny-forbidden"
Authorization oracle (B2 / B3). Returns the expected typed outcome for a call made by agentId. Property code compares the real server’s error to this. Rules (mirrored from packages/server/src/app/authz.ts contract):
  • Unregistered agent + non-connect method → deny-unauthenticated.
  • Conversation-scoped method + authz entry “denied” → deny-forbidden.
  • Otherwise allow.

initialReferenceState

Variable
export const initialReferenceState: ReferenceState =

isIdempotent

Function
export function isIdempotent(method: string): boolean

LogicalTick

TypeAlias
export type LogicalTick = number & Brand.Brand<"LogicalTick">;
Monotonic logical clock — the model does not read wall time.

mkTick

Function
export function mkTick(n: number): LogicalTick
Construct a LogicalTick from a raw number. Only call in this module.

ReferenceState

Interface
export interface ReferenceState {
  readonly tick: LogicalTick;
  /** Registered agents, keyed by `agentId`. */
  readonly agents: ReadonlyMap<AgentId, Agent>;
  /** Conversations, keyed by `conversationId`. */
  readonly conversations: ReadonlyMap<ConversationId, Conversation>;
  /** Messages per conversation, append-only, ordered. */
  readonly messages: ReadonlyMap<ConversationId, ReadonlyArray<Message>>;
  /** Per-agent outbox of events the model predicts the server will emit. */
  readonly pendingEvents: ReadonlyMap<
    AgentId,
    ReadonlyArray<NotificationFrame>
  >;
  /** Authorization table — (agentId, conversationId) → role. */
  readonly authz: ReadonlyMap<
    AgentId,
    ReadonlyMap<ConversationId, "owner" | "participant" | "denied">
  >;
  /** Request-ids the model has observed, for uniqueness assertions (B4). */
  readonly seenRequestIds: ReadonlySet<string>;
}
Every kind of entity the model tracks.

RpcModelResult

TypeAlias
export type RpcModelResult =
  | {
      readonly _tag: "ok";
      readonly result: unknown;
      readonly events: ReadonlyArray<NotificationFrame>;
    }
Observable outcome of one RPC against the model, in the same shape the real server puts on the wire. Tier B’s B1 asserts deepEqual(serverResponse, modelResponse) modulo opaque fields (IDs, tokens — extracted to a named canonicalizer in the implementer step).

Files

  • dispatch.ts
  • state.ts