gRPC API Reference
Table of Contents
Section titled “Table of Contents”-
-
holomush/channel/v1/channel.proto
-
holomush/plugin/host/v1/focus.proto
-
holomush/plugin/host/v1/world.proto
-
holomush/plugin/v1/plugin.proto
-
-
holomush/sceneaccess/v1/sceneaccess.proto
-
holomush/core/v1/core.proto
Section titled “holomush/core/v1/core.proto”AuthenticatePlayerRequest
Section titled “AuthenticatePlayerRequest”AuthenticatePlayerRequest carries phase-one login credentials.
| Field | Type | Label | Description |
|---|---|---|---|
| username | string | username identifies the player account. | |
| password | string | password is the plaintext password to verify (over the secured transport). | |
| captcha_token | string | captcha_token is an optional anti-automation token. | |
| remember_me | bool | remember_me requests a longer-lived session per the gateway’s cookie policy. |
AuthenticatePlayerResponse
Section titled “AuthenticatePlayerResponse”AuthenticatePlayerResponse returns the minted player session token and the roster needed to drive phase-two character selection.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when credentials verified. | |
| player_session_token | string | player_session_token is the bearer token for subsequent post-auth RPCs; present only on success. | |
| error_message | string | error_message is a sanitized, generic failure message (“invalid username or password”) on failure. | |
| characters | CharacterSummary | repeated | characters is the player’s roster for the character-select screen. |
| default_character_id | string | default_character_id is the player’s preferred character to pre-select, if set. | |
| session_ttl_seconds | int64 | session_ttl_seconds is the session lifetime in seconds. The web gateway uses it to set the cookie MaxAge so the cookie expires with the underlying session (preventing stale cookies outliving short guest sessions). |
AvailableCommand
Section titled “AvailableCommand”AvailableCommand is one command’s metadata in a ListAvailableCommands result.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the canonical command name. | |
| help | string | help is the one-line description. | |
| usage | string | usage is the usage pattern. | |
| source | string | source is “core” or the owning plugin name. |
CharacterDirectoryEntry
Section titled “CharacterDirectoryEntry”CharacterDirectoryEntry is one directory row: identity + display name only.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the character’s ULID. | |
| name | string | name is the character’s display name. |
CharacterSummary
Section titled “CharacterSummary”CharacterSummary is the roster view of one character: enough to render a character-select screen, enriched with live session status and last location.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the character’s ULID. | |
| character_name | string | character_name is the character’s display name. | |
| has_active_session | bool | has_active_session is true when this character has a session in the Active state right now. | |
| session_status | string | session_status is the string form of the character’s current session status (e.g. “active”, “detached”); empty when no session exists. | |
| last_location | string | last_location is the resolved name of the character’s last-known location; empty when unknown or unresolvable. | |
| last_played_at | int64 | last_played_at is an epoch timestamp of last play (unset/zero when never played). |
CheckPlayerSessionRequest
Section titled “CheckPlayerSessionRequest”CheckPlayerSessionRequest validates a session token, typically the value from a web auth cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token is the token to validate. |
CheckPlayerSessionResponse
Section titled “CheckPlayerSessionResponse”CheckPlayerSessionResponse returns the player identity behind a valid token. The failure path returns an Unauthenticated status with no body, so these fields are absent for unknown/expired sessions — preserving the enumeration- safety contract documented in internal/auth (session ownership).
| Field | Type | Label | Description |
|---|---|---|---|
| player_name | string | player_name is the account username. | |
| player_id | string | player_id is the player’s ULID. | |
| is_guest | bool | is_guest is true when the session belongs to an ephemeral guest player. | |
| characters | CharacterSummary | repeated | characters is the player’s roster (enriched with session status). |
ConfirmPasswordResetRequest
Section titled “ConfirmPasswordResetRequest”ConfirmPasswordResetRequest completes a reset using the emailed token.
| Field | Type | Label | Description |
|---|---|---|---|
| token | string | token is the single-use reset token from the reset email. | |
| new_password | string | new_password is the plaintext replacement password. |
ConfirmPasswordResetResponse
Section titled “ConfirmPasswordResetResponse”ConfirmPasswordResetResponse reports the outcome with a sanitized error.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the password was reset. | |
| error_message | string | error_message is a sanitized failure message on failure (never echoes the token). |
ControlFrame
Section titled “ControlFrame”ControlFrame is a non-event control message delivered on the Subscribe stream.
| Field | Type | Label | Description |
|---|---|---|---|
| signal | ControlSignal | signal classifies the control message. | |
| message | string | message is optional human-readable context for the signal. | |
| attach_moment_ms | int64 | attach_moment_ms is the server’s wall-clock epoch-ms at the moment the Subscribe handler attached its durable consumer. It is carried ONLY on CONTROL_SIGNAL_REPLAY_COMPLETE; clients reading other signals MUST ignore it. The client passes this value as not_after_ms on subsequent backfill (QueryStreamHistory) calls so backfill returns ONLY events with timestamp <= attach_moment_ms — eliminating the race where a post-attach event could appear both as a dimmed backfill row and a live Subscribe delivery. It is 0 on legacy servers; clients MUST treat 0 as “no upper bound” (back-compat). | |
| scene_id | string | scene_id identifies the scene that produced a SCENE_ACTIVITY signal; the bare scene ULID (not a subject). Set ONLY on CONTROL_SIGNAL_SCENE_ACTIVITY; clients reading other signals MUST ignore it. |
CreateCharacterRequest
Section titled “CreateCharacterRequest”CreateCharacterRequest adds a character to the authenticated player’s roster.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token proves the caller’s authenticated player identity. | |
| character_name | string | character_name is the desired name for the new character. |
CreateCharacterResponse
Section titled “CreateCharacterResponse”CreateCharacterResponse returns the newly created character.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the character was created. | |
| character_id | string | character_id is the new character’s ULID. | |
| character_name | string | character_name is the new character’s name as stored. | |
| error_message | string | error_message is a sanitized failure message on failure. |
CreateGuestRequest
Section titled “CreateGuestRequest”CreateGuestRequest is empty: a guest provisioning takes no parameters.
CreateGuestResponse
Section titled “CreateGuestResponse”CreateGuestResponse returns an ephemeral guest player session plus the starter character that was provisioned alongside it.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the guest was provisioned. | |
| error_message | string | error_message is a generic failure message on failure. | |
| player_session_token | string | player_session_token is the bearer token for the guest session. | |
| characters | CharacterSummary | repeated | characters holds the single starter character provisioned for the guest. |
| default_character_id | string | default_character_id is the starter character to pre-select. | |
| session_ttl_seconds | int64 | session_ttl_seconds is the session lifetime in seconds (see AuthenticatePlayerResponse). For guest sessions this is the shorter guest TTL, not the regular-player TTL. |
CreatePlayerRequest
Section titled “CreatePlayerRequest”CreatePlayerRequest carries new-account registration details.
| Field | Type | Label | Description |
|---|---|---|---|
| username | string | username is the desired account name. | |
| password | string | password is the desired plaintext password. | |
| string | email is the contact email for the account (used by password reset). | ||
| captcha_token | string | captcha_token is an optional anti-automation token. |
CreatePlayerResponse
Section titled “CreatePlayerResponse”CreatePlayerResponse returns the new account’s session token; the new player is logged in immediately but has an empty character roster.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the account was created. | |
| player_session_token | string | player_session_token is the bearer token for the newly created, logged-in player. | |
| characters | CharacterSummary | repeated | characters is always empty for a freshly created player. |
| error_message | string | error_message is a sanitized failure message on failure. | |
| session_ttl_seconds | int64 | session_ttl_seconds is the session lifetime in seconds (see AuthenticatePlayerResponse). |
DisconnectRequest
Section titled “DisconnectRequest”DisconnectRequest detaches a connection, or the whole session, from the game.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the session to disconnect. | |
| connection_id | string | connection_id, when set, removes only that specific connection; empty disconnects the session as a whole. | |
| player_session_token | string | player_session_token proves the caller owns session_id. Required for all post-auth RPCs. It must match the player_id of session_id or the request is rejected with SESSION_NOT_FOUND. |
DisconnectResponse
Section titled “DisconnectResponse”DisconnectResponse reports the outcome. Disconnect is idempotent: a session that is already gone returns success.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data. | |
| success | bool | success is true on a completed (or already-complete) disconnect. |
EventFrame
Section titled “EventFrame”EventFrame is one delivered game event. The same shape is produced by both the live Subscribe path and the QueryStreamHistory backfill path.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the event’s ULID — its identity and dedup key, NOT its ordering key. | |
| stream | string | stream is the fully-qualified JetStream subject the event belongs to (e.g. “events.main.location.<ULID>”). Producers and clients exchange domain-relative dot references (e.g. “location.<ULID>”); the server qualifies them on the way in, so delivered frames carry the qualified form. | |
| type | string | type is the event type string (e.g. say, pose, command_response). | |
| timestamp | google.protobuf.Timestamp | timestamp is the server-stamped event time. | |
| actor_type | string | actor_type names the kind of actor that produced the event (character, plugin, etc.). | |
| actor_id | string | actor_id identifies the specific actor that produced the event. | |
| payload | bytes | payload is the type-specific event body, opaque at this layer. Empty when metadata_only is true. | |
| cursor | bytes | cursor is the opaque pagination cursor for this event. The server populates it on QueryStreamHistory responses and Subscribe deliveries so clients can resume without re-delivering events they already processed. | |
| rendering | RenderingMetadata | rendering is the cleartext rendering band, populated by RenderingPublisher at emit time. It MUST be present on every frame this server produces (INV-EVENTBUS-2); the gateway treats absence as a contract violation (drops + metric + log per INV-EVENTBUS-6). | |
| metadata_only | bool | metadata_only flags a delivery whose plaintext was withheld by the host’s AuthGuard (Phase 3b decrypt path). When true, payload is empty bytes and the recipient was either not in the DEK’s participant set, lacked the requisite plugin manifest declaration / ABAC grant, or hit the audit-emit backpressure throttle. It is false on every legitimate delivery (including legitimately empty-payload events such as a presence event with no content). Set by the Subscribe / QueryStreamHistory handler at fan-out time; NEVER set by emitters and NEVER persisted to events_audit (storage rows always carry the sender’s payload, ciphertext or cleartext). | |
| no_plaintext_reason | NoPlaintextReason | no_plaintext_reason classifies why metadata_only=true was stamped. It is UNSPECIFIED on metadata_only=false deliveries and one of the typed reasons when metadata_only=true. |
GetCommandHistoryRequest
Section titled “GetCommandHistoryRequest”GetCommandHistoryRequest asks for the recent command lines recorded for a session (the per-session command ring buffer, not event history).
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the session whose command history is requested. | |
| player_session_token | string | player_session_token proves the caller owns session_id. Required for all post-auth RPCs. It must match the player_id of session_id or the request is rejected with SESSION_NOT_FOUND. |
GetCommandHistoryResponse
Section titled “GetCommandHistoryResponse”GetCommandHistoryResponse returns the recorded command lines.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data. | |
| success | bool | success is true when history was retrieved. | |
| commands | string | repeated | commands lists the recent command lines, oldest-to-newest within the ring. |
| error | string | error carries a failure message when success is false. |
HandleCommandRequest
Section titled “HandleCommandRequest”HandleCommandRequest carries one player-issued command to dispatch within the caller’s game session.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data (see RequestMeta). | |
| session_id | string | session_id names the game session in whose context the command runs. | |
| command | string | command is the raw command line as typed by the player; the dispatcher parses and routes it. | |
| player_session_token | string | player_session_token proves the caller owns session_id. Required for all post-auth RPCs. It must match the player_id of session_id or the request is rejected with SESSION_NOT_FOUND. | |
| connection_id | string | connection_id is the ULID of the originating gateway connection (Phase 5). Populated by telnet and web gateways; empty for non-gateway callers. The server uses it to route scene-focus autofocus to the correct connection (T20-T23). An empty string is accepted (parsed as the zero ULID). |
HandleCommandResponse
Section titled “HandleCommandResponse”HandleCommandResponse reports only whether dispatch succeeded. All player- visible command output is delivered out of band as command_response events on the character’s stream, not in this reply.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data back to the caller. | |
| success | bool | success is true when the command dispatched without a transport/ownership error. User-facing command errors are still reported via command_response events with success=true here. | |
| error | string | error carries a transport/ownership failure message when success is false. |
ListAllCharactersRequest
Section titled “ListAllCharactersRequest”ListAllCharactersRequest authenticates the caller and names the acting alt (the ABAC subject). No pagination — the directory is returned in full.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token authenticates the caller; any valid session (guest or registered) is accepted. Required. | |
| character_id | string | character_id is the acting alt; the handler verifies the session owns it and uses it as the ABAC subject for the list_character_directory action. Required. |
ListAllCharactersResponse
Section titled “ListAllCharactersResponse”ListAllCharactersResponse carries the directory page.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | CharacterDirectoryEntry | repeated | characters is the id+name list, name-ascending. No connection state. |
ListAvailableCommandsRequest
Section titled “ListAvailableCommandsRequest”ListAvailableCommandsRequest asks for the session character’s executable set.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| player_session_token | string | player_session_token proves the caller owns session_id; failures collapse to SESSION_NOT_FOUND. | |
| session_id | string | session_id names the session whose character’s command set is enumerated. |
ListAvailableCommandsResponse
Section titled “ListAvailableCommandsResponse”ListAvailableCommandsResponse returns the filtered set + alias map.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data. | |
| commands | AvailableCommand | repeated | commands is the ABAC-filtered set the session character may execute. |
| aliases | ListAvailableCommandsResponse.AliasesEntry | repeated | aliases maps alias → canonical command name (system/manifest aliases for visible commands). |
| incomplete | bool | incomplete is true when engine errors hid some commands. |
ListAvailableCommandsResponse.AliasesEntry
Section titled “ListAvailableCommandsResponse.AliasesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
ListCharactersRequest
Section titled “ListCharactersRequest”ListCharactersRequest asks for the authenticated player’s character roster.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token proves the caller’s authenticated player identity. |
ListCharactersResponse
Section titled “ListCharactersResponse”ListCharactersResponse returns the player’s roster with session-status enrichment.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | CharacterSummary | repeated | characters is the player’s roster. |
ListFocusPresenceRequest
Section titled “ListFocusPresenceRequest”ListFocusPresenceRequest asks for the current-state presence snapshot of the session’s focus context.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| player_session_token | string | player_session_token proves the caller owns session_id; failures collapse to SESSION_NOT_FOUND. | |
| session_id | string | session_id names the session whose focus context is queried. |
ListFocusPresenceResponse
Section titled “ListFocusPresenceResponse”ListFocusPresenceResponse returns the presence snapshot. For a session with no location yet, entries is empty under the LOCATION context rather than an error.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data. | |
| context | PresenceContext | context names the focus context the snapshot describes (LOCATION today). | |
| context_id | string | context_id is the identifier of the context: a location_id for LOCATION (and a scene_id for the future SCENE context). | |
| entries | PresenceEntry | repeated | entries is the deduplicated set of characters present in the context. |
ListPlayerSessionsRequest
Section titled “ListPlayerSessionsRequest”ListPlayerSessionsRequest asks for the caller’s own active PlayerSessions.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token identifies the caller; the response lists that player’s sessions. |
ListPlayerSessionsResponse
Section titled “ListPlayerSessionsResponse”ListPlayerSessionsResponse returns the caller’s PlayerSessions. An empty list is also the enumeration-safe response on any auth failure.
| Field | Type | Label | Description |
|---|---|---|---|
| sessions | PlayerSessionInfo | repeated | sessions is the caller’s active PlayerSessions; never includes tokens. |
ListSessionStreamsRequest
Section titled “ListSessionStreamsRequest”ListSessionStreamsRequest asks which streams a session is currently subscribed to.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the session whose subscribed streams are listed. | |
| player_session_token | string | player_session_token proves the caller owns session_id; failures collapse to SESSION_NOT_FOUND (closing the stream-enumeration IDOR). |
ListSessionStreamsResponse
Section titled “ListSessionStreamsResponse”ListSessionStreamsResponse returns the session’s subscribed stream names.
| Field | Type | Label | Description |
|---|---|---|---|
| streams | string | repeated | streams lists the subscribed stream names as domain-relative dot references (e.g. “character.<ULID>”, “location.<ULID>”, plugin streams) — the form the client passes back to Subscribe/QueryStreamHistory unchanged, which the server qualifies. Delivered EventFrames carry the fully-qualified subject (see EventFrame.stream), not this relative form. |
| meta | ResponseMeta | meta echoes request correlation data. |
LogoutRequest
Section titled “LogoutRequest”LogoutRequest ends the player session identified by the supplied token.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token identifies the PlayerSession to end. |
LogoutResponse
Section titled “LogoutResponse”LogoutResponse is empty: logout reports success solely by returning without an error status.
PlayerSessionInfo
Section titled “PlayerSessionInfo”PlayerSessionInfo describes one of the caller’s PlayerSessions for device- management UX. It never carries the session token — only safe-to-display metadata.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the PlayerSession’s ULID. Safe to show the user — this is a resource handle, not a secret — and is the value passed as target_session_id to RevokePlayerSession. | |
| created_at | google.protobuf.Timestamp | created_at is when the session was established. | |
| last_active | google.protobuf.Timestamp | last_active is sourced from player_sessions.updated_at, bumped whenever the session is refreshed. | |
| user_agent | string | user_agent is the client user-agent recorded at session creation. | |
| ip_address | string | ip_address is the client IP recorded at session creation. | |
| is_current | bool | is_current is true for exactly the PlayerSession that made the ListPlayerSessions request — supports a “this device” indicator. |
PresenceEntry
Section titled “PresenceEntry”PresenceEntry describes one character present in a focus context.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the present character’s ULID. | |
| character_name | string | character_name is the resolved display name; entries whose name cannot be resolved are dropped rather than returned empty. | |
| state | PresenceState | state is the character’s presence state (ACTIVE for the location resolver). |
QueryStreamHistoryRequest
Section titled “QueryStreamHistoryRequest”QueryStreamHistoryRequest reads a page of event history from one stream.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the requesting session; its identity drives authorization. | |
| stream | string | stream is the domain-relative dot reference whose history is read (e.g. “location.<ULID>”, “character.<ULID>”); the server qualifies it to the fully-qualified JetStream subject before authorization and the bus fetch. | |
| count | int32 | count is the requested page size. 0 selects the server default (150); the server caps it at 500; a negative value is rejected with INVALID_ARGUMENT. | |
| not_before_ms | int64 | not_before_ms is an epoch-ms time floor; 0 means no lower bound. | |
| cursor | bytes | cursor is the opaque pagination cursor from a previous response. Events older than the cursor position are returned; empty starts from the latest. | |
| not_after_ms | int64 | not_after_ms is an epoch-ms time ceiling; 0 means no upper bound (back-compat). INCLUSIVE: events with timestamp == not_after_ms are returned. The web client sets it from ControlFrame.attach_moment_ms at connect time to bound backfill to events that existed before the Subscribe stream attached, eliminating the connect-time race where a user-emitted event could appear both as a dimmed backfill row and a live Subscribe delivery. |
QueryStreamHistoryResponse
Section titled “QueryStreamHistoryResponse”QueryStreamHistoryResponse returns one page of history plus pagination state.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta echoes request correlation data. | |
| events | EventFrame | repeated | events is the page of history frames, newest-first within the page. |
| has_more | bool | has_more is true when older events remain beyond this page. | |
| next_cursor | bytes | next_cursor is the opaque cursor for the next (older) page; empty when has_more is false. |
RefreshConnectionRequest
Section titled “RefreshConnectionRequest”RefreshConnectionRequest asks core to bump the lease for one connection.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the game session owning the connection. | |
| connection_id | string | connection_id is the connection whose lease to refresh. | |
| player_session_token | string | player_session_token proves the caller owns session_id. |
RefreshConnectionResponse
Section titled “RefreshConnectionResponse”RefreshConnectionResponse is empty on success; failures are gRPC status codes.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | ResponseMeta | meta carries response correlation data. |
RenderingMetadata
Section titled “RenderingMetadata”RenderingMetadata carries cleartext rendering instructions for an event. It is populated by RenderingPublisher.Publish at emit time from the verb registry — one schema with two transports (the gRPC Subscribe EventFrame and the JetStream envelope). See docs/superpowers/specs/2026-04-26-gateway-verb-registry-sourcing.md.
| Field | Type | Label | Description |
|---|---|---|---|
| category | string | category drives client-side renderer routing and must be non-empty. | |
| format | string | format drives within-category presentation and must be non-empty. | |
| label | string | label provides type-specific display text. Required when format == “speech”. | |
| display_target | EventChannel | display_target routes the event to TERMINAL, STATE, or BOTH on the client. It must be a defined, non-zero EventChannel. | |
| source_plugin | string | source_plugin names the plugin that owns this event type, or “builtin” for host-owned types. Recorded for historical/audit fidelity. | |
| source_plugin_version | string | source_plugin_version is the manifest’s version field, or “host-<binary version>” for builtins. Recorded for historical/audit fidelity. |
RequestMeta
Section titled “RequestMeta”RequestMeta travels on every request so the server can correlate a single RPC across logs, traces, and audit. The CoreServer handlers read meta.request_id into the slog “request_id” field and emit it as an OTel span attribute.
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | string | request_id is a client-supplied ULID used only for log/trace correlation. It is not an identity or ownership token; an empty value is accepted and simply suppresses the per-request correlation attribute. | |
| timestamp | google.protobuf.Timestamp | timestamp records when the client issued the request. Advisory only — the server does not gate on it. |
RequestPasswordResetRequest
Section titled “RequestPasswordResetRequest”RequestPasswordResetRequest begins a password-reset flow by email.
| Field | Type | Label | Description |
|---|---|---|---|
| string | email is the account email to send the reset to. |
RequestPasswordResetResponse
Section titled “RequestPasswordResetResponse”RequestPasswordResetResponse always reports success regardless of whether the email exists — an intentional account-enumeration-prevention measure.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is always true (enumeration-safe; reveals nothing about whether the email is registered). |
ResponseMeta
Section titled “ResponseMeta”ResponseMeta is the response-side counterpart to RequestMeta. CoreServer echoes the originating request_id back via responseMeta() so a client can match an asynchronous-feeling reply to the call that produced it.
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | string | request_id is the value echoed from the originating RequestMeta.request_id. | |
| timestamp | google.protobuf.Timestamp | timestamp records when the server produced the response. |
RevokeOtherPlayerSessionsRequest
Section titled “RevokeOtherPlayerSessionsRequest”RevokeOtherPlayerSessionsRequest bulk-revokes the caller’s other sessions.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token identifies the caller; the current session is preserved and all others are revoked. |
RevokeOtherPlayerSessionsResponse
Section titled “RevokeOtherPlayerSessionsResponse”RevokeOtherPlayerSessionsResponse reports how many sessions were revoked.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the bulk revoke completed. | |
| revoked_count | int32 | revoked_count is the number of PlayerSessions deleted (excluding the current one). |
RevokePlayerSessionRequest
Section titled “RevokePlayerSessionRequest”RevokePlayerSessionRequest deletes one of the caller’s PlayerSessions.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token identifies the caller; only the caller’s own sessions may be revoked. | |
| target_session_id | string | target_session_id is the PlayerSession.id (ULID) to revoke — NOT the game session_id. A revoke targeting another player’s session collapses to “session not found”. |
RevokePlayerSessionResponse
Section titled “RevokePlayerSessionResponse”RevokePlayerSessionResponse reports the outcome with an enumeration-safe error.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the target session was deleted. | |
| error_message | string | error_message is “session not found” on any failure, including cross-player attempts (enumeration-safe). |
SelectCharacterRequest
Section titled “SelectCharacterRequest”SelectCharacterRequest carries phase-two character selection.
| Field | Type | Label | Description |
|---|---|---|---|
| player_session_token | string | player_session_token proves the caller’s authenticated player identity. | |
| character_id | string | character_id names the character to enter the game as; it must belong to the authenticated player. | |
| client_type | string | client_type declares the surface establishing the session (terminal/comms_hub/telnet — the session_connections vocabulary). When “comms_hub”, a FRESH session creation skips the grid arrive emission: scenes-workspace sessions must not announce the character on the grid (spec 2026-06-07 §V2). Empty preserves the legacy behavior (arrive). Reattach paths never re-emit arrive regardless of this field. |
SelectCharacterResponse
Section titled “SelectCharacterResponse”SelectCharacterResponse returns the game session created or reattached for the chosen character.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when a game session was established. | |
| session_id | string | session_id is the game session id to use for Subscribe/HandleCommand. | |
| character_name | string | character_name is the selected character’s display name. | |
| reattached | bool | reattached is true when an existing detached session was resumed (preserving scrollback) rather than a new one created. | |
| error_message | string | error_message is a sanitized failure message on failure. |
SubscribeRequest
Section titled “SubscribeRequest”SubscribeRequest opens the per-session event stream. The server, not the client, decides which streams to deliver and the replay policy.
| Field | Type | Label | Description |
|---|---|---|---|
| meta | RequestMeta | meta carries request correlation data. | |
| session_id | string | session_id names the game session whose events are streamed. | |
| player_session_token | string | player_session_token proves the caller owns session_id. | |
| connection_id | string | connection_id identifies this specific client attachment. The gateway generates a fresh ULID per stream. Required so core can register and deregister the connection atomically with the stream lifecycle. When set, client_type must also be set or the request is rejected. | |
| client_type | string | client_type describes the connecting client for observability and routing: “terminal”, “telnet”, or future client types. |
SubscribeResponse
Section titled “SubscribeResponse”SubscribeResponse is one item on the Subscribe stream: either a game event or a control frame.
| Field | Type | Label | Description |
|---|---|---|---|
| event | EventFrame | event carries one delivered game event. | |
| control | ControlFrame | control carries an out-of-band control signal (e.g. replay-complete). |
ControlSignal
Section titled “ControlSignal”ControlSignal classifies an out-of-band control frame interleaved into the Subscribe stream alongside event frames.
| Name | Number | Description |
|---|---|---|
| CONTROL_SIGNAL_UNSPECIFIED | 0 | CONTROL_SIGNAL_UNSPECIFIED is the zero value; never sent. |
| CONTROL_SIGNAL_REPLAY_COMPLETE | 1 | CONTROL_SIGNAL_REPLAY_COMPLETE marks the boundary between replayed history and live deliveries on a Subscribe stream. |
| CONTROL_SIGNAL_STREAM_CLOSED | 2 | CONTROL_SIGNAL_STREAM_CLOSED tells the client the server is ending the stream (e.g. the session was disconnected or booted). |
| CONTROL_SIGNAL_SCENE_ACTIVITY | 3 | CONTROL_SIGNAL_SCENE_ACTIVITY notifies the client that a scene it is a member of received an event while this connection was NOT focused on it. Carries scene_id only — never event content (the payload may be encrypted; the ping requires no decryption). Drives workspace unread badges; lossy by design (clients re-sync via ListMyScenes snapshots). |
EventChannel
Section titled “EventChannel”EventChannel identifies the destination channel for event delivery. This is the canonical internal definition; webv1.EventChannel is kept in lockstep for the web wire format (INV-EVENTBUS-16).
| Name | Number | Description |
|---|---|---|
| EVENT_CHANNEL_UNSPECIFIED | 0 | EVENT_CHANNEL_UNSPECIFIED is the zero value; rendering metadata validation rejects it (display_target must be a defined non-zero channel). |
| EVENT_CHANNEL_TERMINAL | 1 | EVENT_CHANNEL_TERMINAL routes the event to the scrolling text terminal surface. |
| EVENT_CHANNEL_STATE | 2 | EVENT_CHANNEL_STATE routes the event to the client’s structured state surface (e.g. presence / status panels) rather than the terminal. |
| EVENT_CHANNEL_BOTH | 3 | EVENT_CHANNEL_BOTH routes the event to both the terminal and the state surface. |
| EVENT_CHANNEL_AUDIT_ONLY | 4 | EVENT_CHANNEL_AUDIT_ONLY tags host-emit security/audit events that MUST persist to events_audit but MUST NOT be delivered to client surfaces (telnet, web). The gRPC Subscribe handler drops these before send; the audit projection persists them like any other event. Used by crypto.totp_*, crypto.policy_set, and similar host-emitted audit types. |
NoPlaintextReason
Section titled “NoPlaintextReason”NoPlaintextReason enumerates the causes for a metadata_only=true delivery so clients can distinguish, for example, a destroyed/stale DEK from an authorization denial or backpressure-driven withholding. The Go-side mirror is internal/eventbus.NoPlaintextReason (a 1:1 bijection, asserted by types_proto_sync_test.go); the reasons map to wire strings in the eventbus history readback layer. This enum backs the EventFrame / read-stream path only — the separate plugin own-audit-row decrypt path (RowResult in audit.proto) uses string reasons and adds a “not_owner” value that has no counterpart here.
| Name | Number | Description |
|---|---|---|
| NO_PLAINTEXT_REASON_UNSPECIFIED | 0 | NO_PLAINTEXT_REASON_UNSPECIFIED is the zero value and MUST hold when metadata_only=false. A client that sees it together with metadata_only=true MUST treat the delivery as a contract violation (host stamped without classifying). |
| NO_PLAINTEXT_REASON_AUTHGUARD_DENY | 1 | NO_PLAINTEXT_REASON_AUTHGUARD_DENY means the recipient was not in the DEK’s participant set or lacked the requisite plugin manifest declaration / ABAC grant. Phase 3b AuthGuard deny. |
| NO_PLAINTEXT_REASON_STALE_DEK | 2 | NO_PLAINTEXT_REASON_STALE_DEK means both the hot and cold tier DEKs were indecipherable — a production-real outcome after a sub-epic E rekey plus DEK destruction (INV-CRYPTO-108 double miss). |
| NO_PLAINTEXT_REASON_AUDIT_QUEUE_FULL | 3 | NO_PLAINTEXT_REASON_AUDIT_QUEUE_FULL means a plugin audit-emit hit backpressure (queue full) — a host-side TOCTOU defense. |
| NO_PLAINTEXT_REASON_DEK_MISSING | 4 | NO_PLAINTEXT_REASON_DEK_MISSING means the cold-tier audit row had no dek_ref (DEK reference column missing or NULL). Stamped exclusively by sub-epic F’s operator-read classifier (INV-CRYPTO-66). |
| NO_PLAINTEXT_REASON_DEK_BAD_COLUMNS | 5 | NO_PLAINTEXT_REASON_DEK_BAD_COLUMNS means a cold-tier audit row references a DEK whose column set does not match the event’s AAD declaration. Stamped exclusively by sub-epic F’s classifier. |
| NO_PLAINTEXT_REASON_INTERNAL | 6 | NO_PLAINTEXT_REASON_INTERNAL is the catch-all for unexpected decrypt failures not covered by the specific cases above. Stamped exclusively by sub-epic F’s classifier. |
| NO_PLAINTEXT_REASON_DOWNGRADE_REFUSED | 7 | NO_PLAINTEXT_REASON_DOWNGRADE_REFUSED is a Phase 7 PluginDowngradeFence layer (1) refusal — the host’s read-side fence rejected the row before decrypt, either because the type is in the always-sensitive manifest set and the plugin returned an identity codec (INV-CRYPTO-42), or because the dek_ref is unknown / absent for a non-identity codec (INV-CRYPTO-50). The original event_id is preserved; payload is empty per master INV-CRYPTO-15. |
PresenceContext
Section titled “PresenceContext”PresenceContext names the kind of focus context a presence snapshot describes, returned in ListFocusPresenceResponse.
| Name | Number | Description |
|---|---|---|
| PRESENCE_CONTEXT_UNSPECIFIED | 0 | PRESENCE_CONTEXT_UNSPECIFIED is the zero value; not returned on success. |
| PRESENCE_CONTEXT_LOCATION | 1 | PRESENCE_CONTEXT_LOCATION means the snapshot lists active sessions at a location (the only context implemented today). |
| PRESENCE_CONTEXT_SCENE | 2 | PRESENCE_CONTEXT_SCENE is wire-reserved for scene-focus presence; the resolver lands in a follow-up bead and the RPC currently returns UNIMPLEMENTED for scene-focused sessions. |
PresenceState
Section titled “PresenceState”PresenceState describes a character’s presence status within a focus context.
| Name | Number | Description |
|---|---|---|
| PRESENCE_STATE_UNSPECIFIED | 0 | PRESENCE_STATE_UNSPECIFIED is the zero value; not emitted on success. |
| PRESENCE_STATE_ACTIVE | 1 | PRESENCE_STATE_ACTIVE means the character has an active session in the context. This is the only state the location resolver emits today. |
| PRESENCE_STATE_DETACHED | 2 | PRESENCE_STATE_DETACHED is reserved for the future scene resolver (character present in the scene but with a detached transport). |
| PRESENCE_STATE_INACTIVE | 3 | PRESENCE_STATE_INACTIVE is reserved for the future scene resolver. |
CoreService
Section titled “CoreService”CoreService is the game-core gRPC surface served by internal/grpc.CoreServer (registered in cmd/holomush/sub_grpc.go via RegisterCoreServiceServer). It is the single entry point through which gateways (telnet, web) drive gameplay, authentication, session management, and event streaming. The gateway is a pure protocol translator (see .claude/rules/gateway-boundary.md); all game state and business logic lives behind these RPCs.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| HandleCommand | HandleCommandRequest | HandleCommandResponse | HandleCommand validates session ownership, records the command in session history, and dispatches it through the unified command dispatcher. Handler output is NOT returned inline — it is emitted as command_response events on the character’s own stream. The RPC reply carries only success/failure. Auth: requires a player_session_token matching session_id; ownership failures collapse to a generic “session not found” in the response body. |
| Subscribe | SubscribeRequest | SubscribeResponse stream | Subscribe opens the long-lived server-streaming event feed for a session. The server (not the client) determines which streams to deliver and the replay policy via FocusCoordinator.RestoreFocus; it registers the caller’s connection, replays history, then forwards live events. Ownership is validated up front and collapses to SESSION_NOT_FOUND on any failure. |
| Disconnect | DisconnectRequest | DisconnectResponse | Disconnect detaches a connection (or the whole session) and is idempotent: an already-gone session returns success. It validates ownership first, then removes the named connection and tears down session state. |
| GetCommandHistory | GetCommandHistoryRequest | GetCommandHistoryResponse | GetCommandHistory returns the recent commands recorded for a session (the per-session ring buffer maintained by sessionStore.AppendCommand). Ownership is validated; this is distinct from event history (QueryStreamHistory). |
| AuthenticatePlayer | AuthenticatePlayerRequest | AuthenticatePlayerResponse | AuthenticatePlayer is phase one of two-phase login: it verifies username and password, enforces the per-player session cap, mints a PlayerSession, and returns the bearer token plus the player’s character roster. No game session exists yet — that requires a follow-up SelectCharacter call. |
| SelectCharacter | SelectCharacterRequest | SelectCharacterResponse | SelectCharacter is phase two of two-phase login: given a valid player session token, it reattaches an existing detached game session (preserving scrollback) or creates a fresh one for the chosen character, emitting an arrive event. The character must belong to the authenticated player. |
| CreatePlayer | CreatePlayerRequest | CreatePlayerResponse | CreatePlayer registers a new player account and immediately returns a player session token (the new account is logged in). The returned character roster is empty — a freshly created player has no characters until CreateCharacter. |
| CreateGuest | CreateGuestRequest | CreateGuestResponse | CreateGuest provisions an ephemeral guest player plus one starter character and returns a short-lived (guest TTL) player session token. Used by the “play as guest” entry path; no credentials are required. |
| CreateCharacter | CreateCharacterRequest | CreateCharacterResponse | CreateCharacter adds a character to the authenticated player’s roster. When a transactor and bindings service are configured, the character row and its ownership binding are created atomically in one transaction. |
| ListCharacters | ListCharactersRequest | ListCharactersResponse | ListCharacters returns the authenticated player’s character roster enriched with per-character session status and last-known location. |
| ListAllCharacters | ListAllCharactersRequest | ListAllCharactersResponse | ListAllCharacters returns the id+name of every character in the game for the directory picker (fetch-all, no pagination). The handler verifies the acting character is owned by the session, then ABAC-gates on action list_character_directory (resource character_directory), seeded default-permit for any authenticated character (registered OR guest). Connection/online state is NOT included; that is a separately-permissioned attribute. |
| RequestPasswordReset | RequestPasswordResetRequest | RequestPasswordResetResponse | RequestPasswordReset begins a password-reset flow for the given email. The reply is ALWAYS success regardless of whether the email exists — this is an intentional enumeration-prevention measure; delivery is stubbed (logged). |
| ConfirmPasswordReset | ConfirmPasswordResetRequest | ConfirmPasswordResetResponse | ConfirmPasswordReset completes the flow by validating the reset token and setting the new password. Failure messages are sanitized so token/internal detail does not leak to the client. |
| Logout | LogoutRequest | LogoutResponse | Logout deletes the caller’s PlayerSession and, before doing so, fans out disconnect + session_ended + delete + hooks to every child game session so no Subscribe stream is left orphaned. Per-session signals complete before the PlayerSession row is deleted to avoid ownership-validation flapping. |
| CheckPlayerSession | CheckPlayerSessionRequest | CheckPlayerSessionResponse | CheckPlayerSession validates a player session token and returns the player identity plus character roster. Used by the web gateway for cookie-based auth checks. The failure path returns an Unauthenticated status (not a body flag), preserving the enumeration-safety contract for unknown/expired sessions. |
| ListPlayerSessions | ListPlayerSessionsRequest | ListPlayerSessionsResponse | ListPlayerSessions returns the caller’s active PlayerSessions (the rows in player_sessions for the caller’s player_id). Tokens are never returned — only metadata useful for user-visible session management (“you are signed in on these devices”). Any auth failure returns an empty list, so callers cannot distinguish an invalid token from a player with zero sessions. |
| RevokePlayerSession | RevokePlayerSessionRequest | RevokePlayerSessionResponse | RevokePlayerSession deletes one specific PlayerSession. Ownership is verified: a player cannot revoke another player’s session, and cross-player attempts collapse to “session not found” (logged WARN for security audit). |
| RevokeOtherPlayerSessions | RevokeOtherPlayerSessionsRequest | RevokeOtherPlayerSessionsResponse | RevokeOtherPlayerSessions deletes all of the caller’s PlayerSessions except the current one. Convenience bulk operation equivalent to listing and calling RevokePlayerSession for each — useful after a suspected compromise. |
| QueryStreamHistory | QueryStreamHistoryRequest | QueryStreamHistoryResponse | QueryStreamHistory reads paginated event history from a single stream. It is a pure read that does NOT mutate session cursors (invariant I-13). Two-layer authorization applies: private streams (character / scene) use a hard membership gate (I-17, no ABAC, no admin override); public streams (location, global) are evaluated by the ABAC engine. History transparently spans the recent JetStream tier and the older PostgreSQL audit tier. |
| ListSessionStreams | ListSessionStreamsRequest | ListSessionStreamsResponse | ListSessionStreams returns the stream names the session is currently subscribed to, derived from FocusCoordinator.RestoreFocus (with the same ambient-stream fallback Subscribe uses). Web clients use it to enumerate streams for backfill on reload. Pure read; ownership-validated and enumeration-safe (failures collapse to SESSION_NOT_FOUND), closing the IDOR where one player could enumerate another’s subscribed streams. |
| ListFocusPresence | ListFocusPresenceRequest | ListFocusPresenceResponse | ListFocusPresence returns the current-state presence snapshot for the session’s focus context. It reads session.Store.ListActiveByLocation directly (NOT event history — see .claude/rules/event-interfaces.md) and is gated by the ABAC list_presence action on the location resource. Scene-focus contexts currently return UNIMPLEMENTED. Pure read — no session mutation. |
| ListAvailableCommands | ListAvailableCommandsRequest | ListAvailableCommandsResponse | ListAvailableCommands returns the commands the session’s own character may execute, with the system/manifest alias map for those commands. SERVED: CoreServer.ListAvailableCommands, delegating to commandquery.Querier.Available. Self-scoped: the subject is the session’s character (ownership-validated), never an arbitrary character_id. Pure read. |
| RefreshConnection | RefreshConnectionRequest | RefreshConnectionResponse | RefreshConnection bumps a connection’s liveness lease. Called periodically by the gateway while the client socket is open (holomush-rsoe6). SERVED by CoreServer.RefreshConnection; ownership-validated and enumeration-safe. |
holomush/admin/v1/read_stream.proto
Section titled “holomush/admin/v1/read_stream.proto”AdminReadStreamRequest
Section titled “AdminReadStreamRequest”AdminReadStreamRequest is the operator break-glass read request for the AdminReadStream RPC. The handler (internal/admin/readstream/handler.go) validates and canonicalises this into a domestic Request via protoToDomesticRequest before passing it to ResolveBounds. Fields fall into three categories: identity (session_token), query shape (subject_pattern, type_filter, context, since, until, limit), and authorization metadata (dual_control, dual_control_timeout_seconds, justification). The justification field is REQUIRED at the application layer: ResolveBounds returns DENY_OPERATOR_READ_JUSTIFICATION_EMPTY when it is absent or whitespace-only.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token is the bearer token identifying the operator. The handler resolves it to an OperatorSession via SessionStore.GetOperatorSession and then checks that the resolved player holds the crypto.operator ABAC grant (INV-CRYPTO-55) before any data read or audit publish occurs. | |
| subject_pattern | string | subject_pattern is an optional additional NATS subject filter applied server-side on top of the context-derived subjects. An empty string means no additional filter; the handler uses the context-derived subjects alone. | |
| type_filter | string | type_filter is an optional event type prefix filter. When non-empty, only events whose type string has this prefix are returned. An empty string means no type filtering. | |
| context | ContextRef | repeated | context scopes the read to one or more event streams. Each entry maps to a NATS wildcard subject “events.<game>.<type>.<id…>.>” via BuildSubjects (internal/admin/readstream/subjects.go). When empty, a single game-wide wildcard “events.<game>.>” is used. Up to 64 context entries are accepted; ResolveBounds validates type, arity, and ID format per sensitiveTypes. |
| since | google.protobuf.Timestamp | since is the inclusive lower bound of the query window. When absent (nil), the server defaults to now minus the configured DefaultWindow (INV-CRYPTO-56). ResolveBounds rejects since >= until with DENY_OPERATOR_READ_TIME_INVERTED. | |
| until | google.protobuf.Timestamp | until is the exclusive upper bound of the query window. When absent (nil), the server defaults to now (INV-CRYPTO-56). ResolveBounds rejects until more than 5 seconds in the future with DENY_OPERATOR_READ_FUTURE_BOUND. | |
| limit | uint32 | limit caps the maximum number of EventFrame responses the client wants to receive. A value of 0 means no client-imposed limit; the server enforces its own window-size ceiling independently via MaxWindow. | |
| dual_control | bool | dual_control requires a second operator to approve the request before the stream begins. When true, the server sends a PendingApproval frame and blocks until approval.Repo.WaitForApproval resolves or the ApprovalTTL elapses (INV-CRYPTO-61/INV-CRYPTO-67). When false, the fast single-control path runs immediately after the capability check. | |
| dual_control_timeout_seconds | uint32 | dual_control_timeout_seconds overrides the server’s configured ApprovalTTL for this request. A value of 0 uses the server’s default. | |
| justification | string | justification is the operator’s plain-text reason for the read. REQUIRED: ResolveBounds rejects empty or whitespace-only values with DENY_OPERATOR_READ_JUSTIFICATION_EMPTY. Maximum 4096 UTF-8 bytes. Captured verbatim in the pre-data audit payload (INV-CRYPTO-53/INV-CRYPTO-57). |
AdminReadStreamResponse
Section titled “AdminReadStreamResponse”AdminReadStreamResponse is the server-streaming response envelope for the AdminReadStream RPC. Exactly one payload variant is populated per frame. The stream follows a fixed lifecycle: an optional PendingApproval frame (only when dual_control=true), exactly one ReadStarted frame once streaming begins, zero or more EventFrame frames, and exactly one ReadFinished frame as the terminal message. The handler (internal/admin/readstream/handler.go handleInternal) enforces the audit invariants: the pre-data audit is emitted before the first frame (INV-CRYPTO-53/INV-CRYPTO-54) and the post-data audit is emitted after the final frame (INV-CRYPTO-60).
| Field | Type | Label | Description |
|---|---|---|---|
| pending_approval | PendingApproval | pending_approval is sent when dual_control=true and a second operator must approve before streaming begins. Present at most once, before started. | |
| started | ReadStarted | started is sent once the capability check, optional dual-control approval, and pre-data audit publish all succeed. Carries the resolved request parameters so the client can confirm the effective query window. | |
| event | holomush.core.v1.EventFrame | event is a single event frame from the cold-tier audit log. Each frame carries either the decrypted payload (metadata_only=false) or, when decryption fails, only event metadata (metadata_only=true) with no_plaintext_reason set. Uses corev1.EventFrame for typed redaction (metadata_only + no_plaintext_reason), not eventbusv1.Event (ADR-0017). | |
| finished | ReadFinished | finished is the terminal frame, sent after all events have been delivered or on any error or timeout. Always present as the last frame. |
ContextRef
Section titled “ContextRef”ContextRef is a typed, variable-arity scope reference that maps to a NATS subject wildcard. The type selects the event domain (e.g. “scene”, “location”, “character”, “dm”) and ids supplies the entity identifiers. The handler validates type membership and arity against sensitiveTypes (internal/admin/readstream/filter.go) and rejects unknown types with DENY_OPERATOR_READ_TYPE_UNKNOWN and wrong-arity entries with DENY_OPERATOR_READ_ARITY_MISMATCH. For order-insensitive types (e.g. “dm”), IDs are lex-sorted during canonicalisation so that A→B and B→A are treated as the same context.
| Field | Type | Label | Description |
|---|---|---|---|
| type | string | type names the event domain being scoped. Recognised values are “scene”, “location”, “character”, and “dm”. ResolveBounds rejects unrecognised types with DENY_OPERATOR_READ_TYPE_UNKNOWN. | |
| ids | string | repeated | ids are the entity identifiers for this context, each a 26-char Crockford Base32 ULID. The required count (arity) depends on the type: “scene”, “location”, and “character” each require exactly one ID; “dm” requires exactly two (the pair of participant character IDs, lex-sorted by the handler for canonicalisation). |
PendingApproval
Section titled “PendingApproval”PendingApproval is sent when dual_control=true and a second operator must approve before streaming begins. The handler emits this frame after opening a new approval row in approval.Repo and before calling WaitForApproval (internal/admin/readstream/handler.go acquireApproval). The client should display the request_id so the approving operator can locate the pending row. If WaitForApproval times out before approval, the stream closes with ReadFinished{TERMINATED_BY_DUAL_CONTROL_TIMEOUT}.
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | bytes | request_id is the raw 16-byte ULID of the pending approval row. Clients display or log this for the second operator to use when looking up the pending approval. Wire format is raw bytes (not Base32 string). | |
| expires_at | google.protobuf.Timestamp | expires_at is the wall-clock deadline by which a second operator must approve. Derived from server clock plus the configured ApprovalTTL at the moment the approval row was opened. |
ReadFinished
Section titled “ReadFinished”ReadFinished is the terminal frame sent after the last EventFrame (or immediately when an error, timeout, or disconnect terminates the stream before any events). Always present as the final message in the stream. The handler builds this frame in buildFinishedFrame and emits it best-effort even after send failures (internal/admin/readstream/handler.go).
| Field | Type | Label | Description |
|---|---|---|---|
| terminated_by | ReadFinished.TerminatedBy | terminated_by reports why the stream ended. Mapped from the streamErr by classifyTerminator (internal/admin/readstream/handler.go). CLIENT_EOF indicates a clean completion; all other values indicate some form of interruption or failure. | |
| events_scanned | int64 | events_scanned is the total count of cold-tier audit rows that the handler processed during the stream (including rows that failed decryption and became metadata-only frames). | |
| decrypt_fail_count | int64 | decrypt_fail_count is the count of rows where decryption failed and a metadata-only EventFrame was emitted instead of a plaintext frame. | |
| finished_at | google.protobuf.Timestamp | finished_at is the server wall-clock time when the ReadFinished frame was built, stamped by handler Config.Clock. |
ReadStarted
Section titled “ReadStarted”ReadStarted is sent exactly once when the operator’s read clears all gates (capability check, optional dual-control approval, and pre-data audit publish) and the stream is about to deliver EventFrame messages. Carries the resolved query parameters so the client can confirm the effective window and contexts. The handler builds this frame in buildStartedFrame (internal/admin/readstream/handler.go).
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | string | request_id is the 26-character Crockford Base32 ULID for this read operation, generated fresh by the handler via idgen.New(). Stamped in both the pre-data and post-data audit payloads for correlation. | |
| policy_hash | bytes | policy_hash is the raw 32-byte SHA-256 of the active site policy at the time the read was authorised. Decoded from the “sha256:<hex>” string held in handler Config.PolicyHash (a required config — an empty hash is rejected at startup). The audit payload stores the canonical “sha256:<hex>” form; this field delivers the raw bytes, and is empty only if that configured string fails to decode (a defensive guard). | |
| resolved_since | google.protobuf.Timestamp | resolved_since is the effective lower bound of the query window after ResolveBounds defaulting. Always populated; equals since from the request when the client supplied a value, otherwise derived as now-DefaultWindow. | |
| resolved_until | google.protobuf.Timestamp | resolved_until is the effective upper bound of the query window after ResolveBounds defaulting. Always populated; equals until from the request when the client supplied a value, otherwise derived as now. | |
| resolved_contexts | ContextRef | repeated | resolved_contexts are the canonicalised context entries after ResolveBounds validation, deduplication, and lex-sorting of order-insensitive IDs (e.g. “dm” participants). May differ from the request context when the client submitted duplicates or unsorted “dm” IDs. |
ReadFinished.TerminatedBy
Section titled “ReadFinished.TerminatedBy”TerminatedBy enumerates the reason the AdminReadStream stream ended. Mapped from the internal streamErr by classifyTerminator (internal/admin/readstream/handler.go). The labels are also written to the post-data audit payload’s “terminated_by” string field via terminatedByLabel.
| Name | Number | Description |
|---|---|---|
| TERMINATED_BY_UNSPECIFIED | 0 | TERMINATED_BY_UNSPECIFIED is the zero/default value; not used in production — classifyTerminator always resolves to a specific variant. |
| TERMINATED_BY_CLIENT_EOF | 1 | TERMINATED_BY_CLIENT_EOF indicates the cold-tier scan finished cleanly with no error (streamErr == nil). All requested events were delivered. |
| TERMINATED_BY_CLIENT_DISCONNECT | 2 | TERMINATED_BY_CLIENT_DISCONNECT indicates the client disconnected mid-stream. Mapped from context.Canceled by classifyTerminator. |
| TERMINATED_BY_DEADLINE_EXCEEDED | 3 | TERMINATED_BY_DEADLINE_EXCEEDED indicates either the request context deadline was exceeded (context.DeadlineExceeded) or a per-frame write deadline fired (ErrWriteDeadlineExceeded, INV-CRYPTO-64) during streaming. |
| TERMINATED_BY_SERVER_ERROR | 4 | TERMINATED_BY_SERVER_ERROR indicates an unexpected server-side failure (cold-reader error, codec failure, or other unclassified error). Mapped by the classifyTerminator catch-all branch. |
| TERMINATED_BY_DUAL_CONTROL_TIMEOUT | 5 | TERMINATED_BY_DUAL_CONTROL_TIMEOUT indicates the ApprovalTTL elapsed before a second operator approved the request (INV-CRYPTO-61/INV-CRYPTO-67). Mapped from READSTREAM_DUAL_CONTROL_TIMEOUT oops code. |
| TERMINATED_BY_AUDIT_EMIT_FAILURE | 6 | TERMINATED_BY_AUDIT_EMIT_FAILURE indicates the pre-data audit publish (EmitStart) failed before any event data was read or sent. Mapped from DENY_AUDIT_PRE_DATA_PUBLISH oops code (INV-CRYPTO-54). No event data was delivered when this value appears. |
holomush/admin/v1/rekey.proto
Section titled “holomush/admin/v1/rekey.proto”Phase3Progress
Section titled “Phase3Progress”Phase3Progress reports incremental progress during Phase 3, the bulk cold-tier re-encryption phase. The orchestrator rewrites events_audit rows in batches of up to 1000, decrypting each under the old DEK and re-encrypting under the new DEK with AAD rebound to the new (dek_ref, dek_version) — INV-CRYPTO-95. Clients may use these messages to render a progress bar; the stream is terminated by RekeyCompleted or RekeyError.
| Field | Type | Label | Description |
|---|---|---|---|
| rows_rewritten | int64 | rows_rewritten is the cumulative count of events_audit rows re-encrypted by this Phase 3 invocation so far. Resets to zero on a fresh resume; the checkpoint row’s phase3_rows_rewritten column holds the cross-resume total. | |
| rows_remaining_estimate | int64 | rows_remaining_estimate is a best-effort count of events_audit rows whose dek_ref still points at the old DEK. Not guaranteed to be exact (rows may be written concurrently); use for display only. | |
| last_processed_event_id | bytes | last_processed_event_id is the ULID bytes of the most recently committed batch’s last row. Stored as the Phase 3 resume cursor in the checkpoint row (INV-CRYPTO-94); a crash and resume picks up exactly where this cursor points. |
Phase5Attempt
Section titled “Phase5Attempt”Phase5Attempt is emitted each time the orchestrator retries the Phase 5 cluster cache-invalidation fan-out. Phase 5 requests every replica to evict the old DEK from its in-memory cache; it succeeds only when all members acknowledge. Timeout surfaces missing_members; the operator may retry (RekeyResume) or bypass quorum via force_destroy (RekeyResumeRequest).
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_count | int32 | attempt_count is the 1-based index of this invalidation attempt. The checkpoint row’s phase5_attempt_count column is incremented before each attempt and is authoritative; this field mirrors it for live display. | |
| missing_members | string | repeated | missing_members lists the node identifiers that have not yet acknowledged the cache-invalidation request. Empty on a successful attempt. |
PhaseCompleted
Section titled “PhaseCompleted”PhaseCompleted is emitted when an orchestrator phase finishes without error. Paired with PhaseStarted for bracketing display; the phase string matches the CheckpointStatus FSM constant of the phase that just finished.
| Field | Type | Label | Description |
|---|---|---|---|
| phase | string | phase is the FSM status name of the phase that finished successfully, matching the CheckpointStatus constants in checkpoint_fsm.go. |
PhaseStarted
Section titled “PhaseStarted”PhaseStarted is emitted at the beginning of each named orchestrator phase. The phase string matches the CheckpointStatus FSM constants (e.g. “phase1_auth”, “phase3_reencrypt_cold”) so clients can display a phase-by-phase progress indicator.
| Field | Type | Label | Description |
|---|---|---|---|
| phase | string | phase is the FSM status name of the phase that is starting, matching the CheckpointStatus constants in checkpoint_fsm.go (e.g. “phase2_mint_dek”). |
RekeyAbortRequest
Section titled “RekeyAbortRequest”RekeyAbortRequest cancels a non-terminal rekey operation, transitioning its checkpoint to the aborted state. Abort is single-control (INV-CRYPTO-104): any session holding crypto.operator capability may abort any non-terminal checkpoint, regardless of site dual-control policy or which operator initiated the rekey. Once aborted the checkpoint is terminal; a new Rekey call is required to restart.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token authenticates the aborting operator. Only crypto.operator capability is required — no admin role re-check (INV-CRYPTO-104). | |
| request_id | bytes | request_id is the 16-byte ULID of the checkpoint to abort. The handler rejects zero bytes with REKEY_INVALID_REQUEST_ID. If the checkpoint is already terminal (complete or aborted), the handler returns DEK_REKEY_CHECKPOINT_TERMINAL. |
RekeyAbortResponse
Section titled “RekeyAbortResponse”RekeyAbortResponse confirms that the rekey checkpoint has been transitioned to the aborted terminal state.
| Field | Type | Label | Description |
|---|---|---|---|
| aborted_at | google.protobuf.Timestamp | aborted_at is the server timestamp at which the checkpoint was marked aborted in crypto_rekey_checkpoints. | |
| audit_event_id | bytes | audit_event_id is the 16-byte ULID of the abort audit event emitted to events_audit. Operators can use this to correlate the abort with the full rekey operation history via AdminReadStream. |
RekeyCompleted
Section titled “RekeyCompleted”RekeyCompleted is the terminal success event emitted at the end of a successful rekey operation. All 7 phases have completed: a new DEK has been minted, all cold-tier events_audit rows re-encrypted under it, the old DEK destroyed, and a chained audit event emitted (Phase 7). The stream ends after this message.
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | bytes | request_id is the 16-byte ULID of the checkpoint row that tracked this rekey operation, matching the value returned by Phase 1 and stored in crypto_rekey_checkpoints.request_id. | |
| audit_event_id | bytes | audit_event_id is the 16-byte ULID of the Phase 7 chained rekey audit event emitted to events_audit. Operators can retrieve this event via AdminReadStream for an end-to-end verification trace. | |
| duration_ms | int64 | duration_ms is the wall-clock time in milliseconds from Phase 1 checkpoint open (started_at) to Phase 7 completion (completed_at), measured using the server’s local clock. Used for operational observability. | |
| phase3_rows_rewritten | int64 | phase3_rows_rewritten is the cumulative count of events_audit rows that were re-encrypted during Phase 3 across all resume attempts. This value is read from the checkpoint row’s phase3_rows_rewritten column at completion, which is incremented atomically inside each batch transaction. | |
| phase5_attempts | int32 | phase5_attempts is the total number of cluster cache-invalidation attempts made during Phase 5, including retries due to missing members. A value of 1 means Phase 5 succeeded on the first try. | |
| force_destroy_used | bool | force_destroy_used is true when the operator passed force_destroy=true on the final RekeyResume call, bypassing Phase 5 quorum by skipping the cluster invalidation step and proceeding directly to Phase 6 (old DEK soft-delete). Recorded for audit traceability. | |
| resumed | bool | resumed is true when this completion resulted from a RekeyResume call (i.e. the checkpoint was already non-terminal when Run was invoked), as opposed to a fresh Rekey call that drove to completion without interruption. |
RekeyError
Section titled “RekeyError”RekeyError is the terminal failure event emitted when the orchestrator cannot proceed. The stream ends after this message. The checkpoint may remain non-terminal (e.g. after a Phase 5 timeout), in which case the operator may call RekeyResume to continue. If the checkpoint has already transitioned to aborted, RekeyResume will surface DEK_REKEY_CHECKPOINT_TERMINAL.
| Field | Type | Label | Description |
|---|---|---|---|
| code | string | code is the oops error code string from the orchestrator, e.g. “DEK_REKEY_PHASE5_TIMEOUT” or “DEK_REKEY_ALREADY_IN_PROGRESS”. Used by operator tooling to branch on specific failure modes. “UNKNOWN” is emitted when the error has no structured code. | |
| message | string | message is the human-readable error description. Not intended for programmatic branching; use code instead. | |
| details | bytes | details carries structured context for specific error codes as JSON-encoded bytes, e.g. {“missing_members”:[“node-a”]} for DEK_REKEY_PHASE5_TIMEOUT. Absent (zero-length) when the error code carries no structured detail. |
RekeyListRequest
Section titled “RekeyListRequest”RekeyListRequest queries the operator’s view of active and optionally terminal rekey checkpoints. Results are streamed as RekeyStatusResponse messages. By default only non-terminal checkpoints are included (pending through phase7_audit); set include_terminal=true to also receive complete and aborted rows.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token authenticates the querying operator. Only crypto.operator capability is required for this read-only RPC. | |
| include_terminal | bool | include_terminal, when true, includes checkpoints in the complete and aborted terminal states alongside the non-terminal ones. Defaults to false so operators see only in-progress work by default. | |
| context_pattern | string | optional | context_pattern, when present, filters results to checkpoints whose context_type or context_id contains this substring. Absent means no context filter. |
| since | google.protobuf.Timestamp | optional | since, when present, restricts results to checkpoints whose started_at is at or after this timestamp. Absent means no lower time bound. |
| limit | int32 | limit caps the number of rows returned. Values ≤0 or >100 are clamped to 100 by the handler (CheckpointListFilter cap in rekey_handler.go). |
RekeyProgress
Section titled “RekeyProgress”RekeyProgress is the streaming event envelope shared by the Rekey and RekeyResume RPCs. Each message carries exactly one event variant via the oneof. In the current MVP the server emits a single terminal event (RekeyCompleted or RekeyError); PhaseStarted, Phase3Progress, Phase5Attempt, and PhaseCompleted are pre-defined for richer per-phase streaming in a future enhancement.
| Field | Type | Label | Description |
|---|---|---|---|
| phase_started | PhaseStarted | phase_started signals the beginning of a named orchestrator phase. | |
| phase3_progress | Phase3Progress | phase3_progress reports incremental re-encryption progress during Phase 3 (bulk cold-tier rewrite). | |
| phase5_attempt | Phase5Attempt | phase5_attempt reports each cluster cache-invalidation attempt during Phase 5, including which replica members have not yet acknowledged. | |
| phase_completed | PhaseCompleted | phase_completed signals that a named orchestrator phase finished successfully. | |
| completed | RekeyCompleted | completed is the terminal success event emitted once all 7 phases have finished. Receiving this message means the old DEK has been destroyed and the audit chain updated. | |
| error | RekeyError | error is the terminal failure event emitted when the orchestrator cannot proceed. The stream ends after this message; the operator may resume via RekeyResume if the checkpoint is non-terminal. |
RekeyRequest
Section titled “RekeyRequest”RekeyRequest initiates a fresh DEK rekey operation for a single encryption context (context_type + context_id). The caller must hold an authenticated operator session (session_token from AdminService.Authenticate) and the crypto.operator in-game capability plus the admin role — both are re-asserted at dispatch time (INV-CRYPTO-83 defense-in-depth). Justification is recorded on the checkpoint row for audit; approval_request_id links a pending admin_approvals row when dual-control is required by site policy.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token authenticates the issuing operator. Must be a non-expired token returned by AdminService.Authenticate; the handler re-validates crypto.operator capability and admin role on every call (INV-CRYPTO-83). | |
| context_type | string | context_type identifies the encryption domain, e.g. “scene”. Together with context_id it resolves the active DEK row (old_dek_id) that the orchestrator’s Phase 1 reads from crypto_keys. | |
| context_id | string | context_id is the entity identifier within context_type, e.g. a scene ULID. The orchestrator uses (context_type, context_id) to locate the active DEK and enforce INV-CRYPTO-92 (at most one non-terminal checkpoint per context at a time). | |
| justification | string | justification is a free-text operator rationale stored on the checkpoint row and included in the Phase 7 chained audit event. Required for accountability; non-empty values are enforced by the handler. | |
| approval_request_id | string | optional | approval_request_id, when present, links a pending admin_approvals row created by the first operator under dual-control policy. Absent for single-control sites. The orchestrator validates the approval row before advancing past Phase 1. |
RekeyResumeRequest
Section titled “RekeyResumeRequest”RekeyResumeRequest resumes a paused or interrupted rekey operation identified by request_id. The orchestrator determines the resume entry point from the checkpoint’s current FSM status and drives forward from there. INV-CRYPTO-103: resuming a complete checkpoint is a no-op that re-emits RekeyCompleted. Resuming an aborted checkpoint surfaces DEK_REKEY_CHECKPOINT_TERMINAL.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token authenticates the resuming operator. The handler re-asserts crypto.operator capability and admin role (INV-CRYPTO-83). | |
| request_id | bytes | request_id is the 16-byte ULID identifying the checkpoint to resume. Must be non-zero; zero bytes are rejected with REKEY_INVALID_REQUEST_ID. | |
| force_destroy | bool | force_destroy, when true, instructs the orchestrator to bypass Phase 5 quorum on this resume attempt. If the checkpoint is stuck in phase5_invalidate with missing_members populated, setting this true skips the cluster invalidation and advances directly to Phase 6 (old DEK soft-delete). Irreversible: the old DEK material is destroyed without full cluster acknowledgement. Recorded in force_destroy_used on RekeyCompleted. |
RekeyStatusRequest
Section titled “RekeyStatusRequest”RekeyStatusRequest fetches the current FSM state and associated fields of a single rekey checkpoint by its request_id. Requires crypto.operator capability (read-only; no admin role re-check).
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token authenticates the querying operator. Only crypto.operator capability is required for this read-only RPC. | |
| request_id | bytes | request_id is the 16-byte ULID of the checkpoint to fetch. Returns DEK_REKEY_CHECKPOINT_NOT_FOUND if no row exists for this ID. |
RekeyStatusResponse
Section titled “RekeyStatusResponse”RekeyStatusResponse describes the current state of one rekey checkpoint. Returned by RekeyStatus (unary) and streamed by RekeyList (one message per matching checkpoint). Fields are populated directly from the crypto_rekey_checkpoints row.
| Field | Type | Label | Description |
|---|---|---|---|
| request_id | bytes | request_id is the 16-byte ULID uniquely identifying this checkpoint, matching the value returned by the Phase 1 open and stored in crypto_rekey_checkpoints.request_id. | |
| context_type | string | context_type is the encryption domain for which this rekey was initiated, e.g. “scene”. Together with context_id it identifies the DEK being rekeyed. | |
| context_id | string | context_id is the entity identifier within context_type, e.g. a scene ULID. | |
| status | string | status is the current FSM state of this checkpoint. Values match the CheckpointStatus constants: “pending”, “phase1_auth”, “phase2_mint_dek”, “phase3_reencrypt_cold”, “phase5_invalidate”, “phase6_destroy_old”, “phase7_audit”, “complete”, or “aborted”. | |
| primary_player_id | string | primary_player_id is the player ID of the operator who initiated the rekey (the first operator under dual-control). Used for accountability and is included in the Phase 7 audit event. | |
| started_at | google.protobuf.Timestamp | started_at is the server timestamp when the checkpoint row was opened (Phase 1 INSERT). Combined with completed_at it bounds the total rekey wall-clock time. | |
| last_heartbeat_at | google.protobuf.Timestamp | last_heartbeat_at is the server timestamp of the most recent heartbeat written by Phase 3. The sweep worker uses this to TTL-abort stalled checkpoints (INV-CRYPTO-105/INV-CRYPTO-106). A value far in the past indicates a stalled or crashed orchestrator run. | |
| completed_at | google.protobuf.Timestamp | completed_at is the server timestamp when the checkpoint reached a terminal state (complete or aborted). Zero if not yet terminal. | |
| phase5_attempt_count | int32 | phase5_attempt_count is the total number of cluster cache-invalidation attempts made during Phase 5 for this checkpoint. Incremented before each attempt; zero means Phase 5 has not started yet. | |
| phase5_missing_members | string | repeated | phase5_missing_members lists the node identifiers that failed to acknowledge the most recent Phase 5 cache-invalidation request. Non-empty indicates a Phase 5 timeout; the operator may resume or use force_destroy. Empty when Phase 5 has not yet run or succeeded. |
| force_destroy | bool | force_destroy records whether force_destroy was set on the last RekeyResume call for this checkpoint, bypassing Phase 5 quorum. | |
| old_dek_id | int64 | optional | old_dek_id is the primary key of the crypto_keys row being replaced. Absent until Phase 1 resolves the active DEK for the context. |
| new_dek_id | int64 | optional | new_dek_id is the primary key of the freshly-minted crypto_keys row created by Phase 2. Absent until Phase 2 completes. |
holomush/admin/v1/admin.proto
Section titled “holomush/admin/v1/admin.proto”ApproveRequest
Section titled “ApproveRequest”ApproveRequest carries the approver’s session token and the ID of the pending approval row to sign off.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token is the approving operator’s bearer token from Authenticate. Used to resolve the approver’s player identity for the self-approval check (INV-CRYPTO-73) and for capability/role re-assertion (INV-CRYPTO-83). | |
| request_id | bytes | request_id is the 16-byte ULID of the admin_approvals row to approve. Must be non-zero; the all-zero sentinel is rejected as an invalid forgery shape even though ulid.Parse accepts it. |
16-byte ULID |
ApproveResponse
Section titled “ApproveResponse”ApproveResponse is empty; a nil error is the success signal.
AuthenticateRequest
Section titled “AuthenticateRequest”AuthenticateRequest carries the operator credentials and TOTP code for the two-factor authentication step that precedes all other admin operations.
| Field | Type | Label | Description |
|---|---|---|---|
| username | string | username is the in-game operator account name for credential lookup. | |
| password | string | password is the operator account password (plaintext over the UNIX socket; the socket path is a trust boundary and the connection is never exposed to the network). | |
| totp_code | string | totp_code is the current TOTP one-time password from the operator’s authenticator app. The provider rejects expired, reused, and locked codes. |
AuthenticateResponse
Section titled “AuthenticateResponse”AuthenticateResponse is returned on successful operator authentication.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token is the opaque short-lived bearer token (10-minute TTL) to supply in session_token fields of subsequent admin RPCs. | |
| expires_at | google.protobuf.Timestamp | expires_at is the UTC timestamp after which session_token will be rejected with DENY_SESSION_EXPIRED. | |
| player_id | string | player_id is the ULID of the authenticated operator’s player record, included so callers can display or log the operator identity. |
ResetTOTPRequest
Section titled “ResetTOTPRequest”ResetTOTPRequest identifies the target player whose TOTP enrollment should be cleared by an authenticated admin operator.
| Field | Type | Label | Description |
|---|---|---|---|
| session_token | string | session_token is the operator’s bearer token from Authenticate. | |
| target_player_id | string | target_player_id is the ULID of the player whose TOTP enrollment will be cleared. Must be a valid non-zero ULID; the handler rejects both malformed strings and the all-zero sentinel. |
ResetTOTPResponse
Section titled “ResetTOTPResponse”ResetTOTPResponse reports whether the TOTP enrollment was actually present.
| Field | Type | Label | Description |
|---|---|---|---|
| cleared | bool | cleared is true when the player was TOTP-enrolled and the enrollment was removed. False when the player had no active TOTP enrollment (no-op); mirrors ClearResult.WasEnrolled from internal/admin/auth/reset_handler.go. |
StatusRequest
Section titled “StatusRequest”StatusRequest carries no fields; the Status RPC requires no input.
StatusResponse
Section titled “StatusResponse”StatusResponse reports the admin socket server’s health and build identity.
| Field | Type | Label | Description |
|---|---|---|---|
| version | string | version is the server binary version string (set via -X ldflag at build time). | |
| healthy | bool | healthy is true when the admin socket HTTP server is accepting requests. compositeHandler.Status always returns true; false would only appear if the handler itself were somehow called during shutdown. |
AdminService
Section titled “AdminService”AdminService is the break-glass operator administration service. It is served exclusively over a UNIX domain socket (admin.sock) and is never exposed over the network. The compositeHandler implementation delegates each RPC to a registered handler; unregistered RPCs return Unimplemented, allowing incremental feature deployment without breaking callers.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Status | StatusRequest | StatusResponse | Status returns the admin-socket server’s liveness state and the binary version string stamped at build time. No authentication is required; it is intended as a health-check endpoint for operators and monitoring. Implemented directly in compositeHandler; never returns an error. |
| Authenticate | AuthenticateRequest | AuthenticateResponse | Authenticate verifies operator credentials (username + password) and a TOTP one-time code, then issues a short-lived (10-minute) opaque session token. The token is returned in session_token and must be supplied in subsequent Approve, ResetTOTP, and Rekey* RPCs. Requires the caller to hold the crypto.operator capability and the admin role (validated inside OperatorAuthProvider via AssertOperatorAdmin, internal/admin/auth). Returns DENY_INVALID_CREDENTIALS / DENY_BAD_TOTP / DENY_NOT_OPERATOR / DENY_NOT_ADMIN_ROLE on rejection; DENY_LOCKED when TOTP is rate-limited. |
| Approve | ApproveRequest | ApproveResponse | Approve is the second-operator signoff on a pending admin_approvals row. The caller supplies their session_token (proving identity and live operator status) and the request_id of the approval row to sign off. Repo.MarkApproved atomically enforces three invariants: INV-CRYPTO-72 (the row must not be expired), INV-CRYPTO-73 (the approver cannot be the same player as the primary operator who opened the row), and INV-CRYPTO-74 (each row may only be approved once). Requires the crypto.operator capability and admin role re-checked at call time (INV-CRYPTO-83); handler in internal/admin/approval. |
| ResetTOTP | ResetTOTPRequest | ResetTOTPResponse | ResetTOTP clears a target player’s TOTP enrollment, allowing them to re-enroll on next login. On success, AuditingService.ClearTOTP emits a crypto.totp_cleared audit event with cleared_by=“admin_reset” (T13). Response.cleared is false when the player was not enrolled (no-op). Requires a valid session_token with the crypto.operator capability and admin role re-checked at call time (INV-CRYPTO-83); handler in internal/admin/auth (reset_handler.go). |
| Rekey | RekeyRequest | RekeyProgress stream | Rekey initiates a fresh DEK rekey for the given context. Requires the crypto.operator capability and admin role (re-checked at call time, INV-CRYPTO-83). Streams a single terminal RekeyProgress event: RekeyCompleted on success or RekeyError on orchestrator failure. Per-phase progress updates are pre-defined in the proto but not yet emitted (follow-up). Uses the shared RekeyProgress stream type (also used by RekeyResume) — the buf RPC_REQUEST_RESPONSE_UNIQUE / RPC_RESPONSE_STANDARD_NAME exemptions are intentional (jxo8.7.27). Handler: internal/admin/socket/rekey_handler.go. |
| RekeyResume | RekeyResumeRequest | RekeyProgress stream | RekeyResume resumes a paused or interrupted rekey identified by request_id. Requires the crypto.operator capability and admin role (INV-CRYPTO-83). Idempotency (INV-CRYPTO-103) and same-args invariant (INV-CRYPTO-91) are enforced inside the orchestrator, not here. The handler validates that request_id is a non-zero 16-byte ULID and forwards it to the orchestrator adapter, which looks up the checkpoint to resolve ContextType/ContextID. Streams a terminal RekeyProgress event — same shared type as Rekey. Handler: internal/admin/socket/rekey_handler.go. |
| RekeyAbort | RekeyAbortRequest | RekeyAbortResponse | RekeyAbort cancels an in-progress rekey checkpoint. Requires the crypto.operator capability only; no admin role re-check and no dual-control approval — abort is single-control regardless of site policy (INV-CRYPTO-104). Any crypto.operator session may abort any non-terminal checkpoint, not just the primary operator who started it. Handler: internal/admin/socket/rekey_handler.go. |
| RekeyStatus | RekeyStatusRequest | RekeyStatusResponse | RekeyStatus returns the current state of a single rekey operation identified by request_id. Requires the crypto.operator capability; no admin role re-check. Reads from the crypto_rekey_checkpoints table via CheckpointStatusReader.GetCheckpoint. Handler: internal/admin/socket/rekey_handler.go. |
| RekeyList | RekeyListRequest | RekeyStatusResponse stream | RekeyList streams status records for rekey operations. By default only non-terminal checkpoints are returned; set include_terminal to include completed and aborted rows. Results are capped at 100 rows (any limit above 100 or zero is silently clamped to 100). Requires the crypto.operator capability; no admin role re-check. Handler: internal/admin/socket/rekey_handler.go. |
| AdminReadStream | AdminReadStreamRequest | AdminReadStreamResponse stream | AdminReadStream is the operator break-glass streaming read RPC. Streams EventFrame payloads for the requested context(s) and time bounds, with typed metadata_only and no_plaintext_reason redaction fields for destroyed-DEK and plaintext-suppressed events. When dual_control is set in the request, the handler blocks until a second operator approves via the admin_approvals table before emitting any event frames (INV-CRYPTO-61/INV-CRYPTO-67). Handler: internal/admin/socket/handlers.go (delegated to ReadStreamRPCHandler). |
holomush/channel/v1/channel.proto
Section titled “holomush/channel/v1/channel.proto”BanMemberRequest
Section titled “BanMemberRequest”BanMemberRequest identifies the acting owner-or-admin, the channel, and the member to ban.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character (owner or admin per ABAC); required. | |
| channel_id | string | The channel the target is banned from; required. | |
| target_character_id | string | The member to ban (must not be the owner); required. |
BanMemberResponse
Section titled “BanMemberResponse”BanMemberResponse is intentionally empty — a successful ban carries no body.
ChannelHistoryEntry
Section titled “ChannelHistoryEntry”ChannelHistoryEntry is one rendered line of channel history, projected from a channel_log audit row. Only content and membership-notice events are surfaced; the payload is plaintext.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The event’s ULID identity/dedup key (never an ordering key — ordering is the audit store’s sequence). | |
| type | string | The qualified wire type of the source event (for example “core-channels:channel_say”), letting a renderer pick the display grammar. | |
| actor_id | string | The acting character’s ID; empty for host-originated notices. | |
| actor_name | string | The acting character’s display name (best-effort; falls back to the actor ID when no name resolver is wired). | |
| content | string | The rendered line content. | |
| created_at | google.protobuf.Timestamp | The wall-clock time the event was recorded. |
ChannelInfo
Section titled “ChannelInfo”ChannelInfo is the wire projection of a channel row plus its member roster, returned by the create, join, and list RPCs. The type field is the plugin’s lowercase string enum, not a proto enum, matching the SceneInfo convention.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | Channel primary key (“channel-<ULID>”), stable for the channel’s lifetime. | |
| name | string | Human-facing channel name, unique case-insensitively across all channels; matches ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$ and is the token players type in the “=name message” shorthand. | |
| type | string | Visibility type: “public” (listed, open join), “private” (invitation-only), or “admin” (invitation-only, operator-scoped). Governs join eligibility and read/post policy evaluation. | |
| owner_id | string | Character ID of the current owner — the sole authority for moderation and transfer, and the only member who cannot leave, be kicked, or be banned. | |
| archived | bool | True once the channel has been soft-deleted (archived); archived channels are hidden from listings and reject new posts but retain their history for the retention window. | |
| retention_days | int32 | Per-channel retention window in days; 0 means the plugin’s configured default applies. Admin channels may carry an unlimited (never-pruned) window. Governs the background prune sweep. | |
| created_at | google.protobuf.Timestamp | Wall-clock creation time, the host clock at create for CreateChannel responses and the persisted row timestamp elsewhere. | |
| members | MemberInfo | repeated | The current member roster (owner and members), excluding invitation records that have not yet been accepted. |
CreateChannelRequest
Section titled “CreateChannelRequest”CreateChannelRequest is the new-channel definition. The calling character becomes the owner. An empty type defaults to “public” at the handler.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The creating character, who becomes the channel owner; required. | |
| name | string | Channel name; required, 1-32 chars, must start alphanumeric and contain only letters, digits, underscore, or hyphen. Rejected with codes.AlreadyExists if a channel with the same name (case-insensitive) already exists. | |
| type | string | Visibility type; empty selects the “public” default. Constrained to "" | |
| retention_days | int32 | Optional per-channel retention window in days; 0 selects the plugin’s configured default. Must be non-negative. |
CreateChannelResponse
Section titled “CreateChannelResponse”CreateChannelResponse carries the freshly created channel (active, owner seeded).
| Field | Type | Label | Description |
|---|---|---|---|
| channel | ChannelInfo | The new channel’s projection. |
InviteToChannelRequest
Section titled “InviteToChannelRequest”InviteToChannelRequest identifies the inviting owner-or-admin, the channel, and the character being granted an invitation.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The inviting character (owner or admin per ABAC); required. | |
| channel_id | string | The channel to invite into; required. | |
| target_character_id | string | The character receiving the invitation; required. |
InviteToChannelResponse
Section titled “InviteToChannelResponse”InviteToChannelResponse is intentionally empty — a successful invite carries no body.
JoinChannelRequest
Section titled “JoinChannelRequest”JoinChannelRequest identifies the channel to join and the joining character, plus the session receiving live delivery.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The joining character’s ID; required. | |
| channel_id | string | The channel to join; required. | |
| session_id | string | The joining character’s game session ULID; required — the host subscribes this session’s live stream to the channel subject on a successful join. |
JoinChannelResponse
Section titled “JoinChannelResponse”JoinChannelResponse is intentionally empty — a successful join carries no body; the caller refetches roster or history if needed.
KickMemberRequest
Section titled “KickMemberRequest”KickMemberRequest identifies the acting owner-or-admin, the channel, and the member to remove.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character (owner or admin per ABAC); required. | |
| channel_id | string | The channel to remove the target from; required. | |
| target_character_id | string | The member to remove (must not be the owner); required. |
KickMemberResponse
Section titled “KickMemberResponse”KickMemberResponse is intentionally empty — a successful kick carries no body.
LeaveChannelRequest
Section titled “LeaveChannelRequest”LeaveChannelRequest identifies the channel to leave, the leaving character, and the session to unsubscribe.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The leaving character’s ID; required (must not be the owner). | |
| channel_id | string | The channel to leave; required. | |
| session_id | string | The leaving character’s game session ULID; required — the host unsubscribes this session’s live stream from the channel subject. |
LeaveChannelResponse
Section titled “LeaveChannelResponse”LeaveChannelResponse is intentionally empty — a successful leave carries no body.
ListChannelsRequest
Section titled “ListChannelsRequest”ListChannelsRequest is the channel-discovery query, scoped to the calling character’s visibility, with pagination.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The listing character’s ID; required. The result includes public channels plus the private and admin channels this character is a member of. | |
| limit | int32 | Maximum channels to return; 0 means server default, capped at 200. | |
| offset | int32 | Number of leading results to skip for pagination. |
ListChannelsResponse
Section titled “ListChannelsResponse”ListChannelsResponse is the channel-discovery result page.
| Field | Type | Label | Description |
|---|---|---|---|
| channels | ChannelInfo | repeated | The channels visible to the caller for this page. |
MemberInfo
Section titled “MemberInfo”MemberInfo is one entry in a channel’s roster — a character’s membership relationship to the channel at read time.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | Character ID of the member. | |
| character_name | string | Display name of the character (best-effort; falls back to the ID when no name resolver is wired). | |
| role | string | Membership role: “owner” or “member”. The owner holds moderation authority and is protected from leave, kick, and ban. | |
| muted | bool | True when the member is muted: their posts are suppressed while their read access and roster membership remain intact. | |
| banned | bool | True when the member is banned: removed from the channel and blocked from rejoining until unbanned. | |
| joined_at | google.protobuf.Timestamp | When the character joined the channel; also the floor below which QueryChannelHistory will not return rows for this member. |
MuteMemberRequest
Section titled “MuteMemberRequest”MuteMemberRequest identifies the acting owner-or-admin, the channel, and the member to mute.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character (owner or admin per ABAC); required. | |
| channel_id | string | The channel the target is muted in; required. | |
| target_character_id | string | The member to mute; required. |
MuteMemberResponse
Section titled “MuteMemberResponse”MuteMemberResponse is intentionally empty — a successful mute carries no body.
PostToChannelRequest
Section titled “PostToChannelRequest”PostToChannelRequest carries one line of member-authored content to publish to a channel. It intentionally has no channel-name field: identity is the channel ID plus a live name lookup, so a rename cannot alter authorization.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The posting character, who MUST be a non-muted member; required. | |
| channel_id | string | The channel to post to; required. | |
| kind | string | Content kind; empty selects the “say” default. Constrained to "" | |
| text | string | The raw message text; required, size-capped to bound a single event payload. The handler sanitizes untrusted UTF-8 and renders it through the comm builder for the selected kind. |
PostToChannelResponse
Section titled “PostToChannelResponse”PostToChannelResponse is intentionally empty — the durable effect is the emitted, audited content event; live members receive it via their subscribed stream and history readers see it through QueryChannelHistory.
QueryChannelHistoryRequest
Section titled “QueryChannelHistoryRequest”QueryChannelHistoryRequest asks for recent channel content as a member, bounded by a scrollback limit and the caller’s join floor.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The reading character, who MUST be a member; required. | |
| channel_id | string | The channel whose history to read; required. | |
| limit | int32 | Maximum content entries to return; 0 means server default, capped at the 500-row scrollback ceiling. Entries older than the caller’s most-recent join are never returned regardless of this value. |
QueryChannelHistoryResponse
Section titled “QueryChannelHistoryResponse”QueryChannelHistoryResponse carries the requested content entries, oldest first.
| Field | Type | Label | Description |
|---|---|---|---|
| entries | ChannelHistoryEntry | repeated | The channel content entries for this read, oldest first. |
TransferOwnershipRequest
Section titled “TransferOwnershipRequest”TransferOwnershipRequest identifies the current owner, the channel, and the member who will become the new owner.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The current owner; required. | |
| channel_id | string | The channel whose ownership transfers; required. | |
| new_owner_character_id | string | The new owner, who MUST already be a member of the channel; required. |
TransferOwnershipResponse
Section titled “TransferOwnershipResponse”TransferOwnershipResponse is intentionally empty — a successful transfer carries no body.
WhoInChannelRequest
Section titled “WhoInChannelRequest”WhoInChannelRequest identifies the channel whose roster is requested and the requesting character (who MUST be a member; the gate is plugin-code, not ABAC).
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The requesting character; MUST be a member of the channel. | |
| channel_id | string | The channel to read the roster of; required. |
WhoInChannelResponse
Section titled “WhoInChannelResponse”WhoInChannelResponse carries the channel’s current member roster.
| Field | Type | Label | Description |
|---|---|---|---|
| members | MemberInfo | repeated | The channel’s members (owner and members). |
ChannelService
Section titled “ChannelService”ChannelService is the gRPC contract for the core-channels binary plugin
(plugins/core-channels/). A channel is a persistent, location-independent
named conversation space with an owner, a member roster, a visibility type
(public, private, or admin), and a retention window. The plugin owns the
channel ABAC resource type and persists to its own plugin_core_channels
schema; it emits content and membership-notice events on
events.<game_id>.channel.<channel_id> and audits them to its plugin-owned
channel_log table.
Authorization model mirrors SceneService: every mutating RPC trusts that the host’s ABAC engine has already authorized the command-execute action at dispatch time (owner-or-admin for create/moderation, member for post). The plugin performs no implicit allow. The membership-gated reads (WhoInChannel, QueryChannelHistory) additionally enforce a direct plugin-code membership check before any content is read, precisely because channel visibility is a privacy boundary that must not be delegable; a caller who is not a member of a private or admin channel receives an opaque not-found so existence cannot be inferred.
All payloads are plaintext: channels declare no crypto.emits and carry no sensitive fields. Channel identity flows by ID and a live name lookup, never by a channel-name field on any content message, so a rename cannot leak stale authorization context.
Implemented by the channel service in plugins/core-channels/service.go (create/join/leave/list in 01-05; post/who/history and the moderation RPCs invite/mute/ban/kick/transfer in 01-05b), before the 01-07 command layer delegates to any of these methods.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| CreateChannel | CreateChannelRequest | CreateChannelResponse | CreateChannel allocates a new channel owned by the calling character, seeded active with the supplied name, visibility type, and retention window. The creating character becomes the owner member in the same transaction, a create ops-journal entry is recorded, and a channel-created notice is emitted. Creation authority is admin-gated by the host’s ABAC policy at dispatch; the handler enforces case-insensitive name uniqueness and rejects a duplicate with codes.AlreadyExists. |
| JoinChannel | JoinChannelRequest | JoinChannelResponse | JoinChannel adds the calling character to a channel as a member. Public channels accept any join; private and admin channels require a pre-existing invitation. A banned character is refused. Idempotent: a repeat join by an existing member succeeds without re-emitting a join notice. On success the host subscribes the joining session’s live stream for the channel subject. |
| LeaveChannel | LeaveChannelRequest | LeaveChannelResponse | LeaveChannel removes the calling character’s membership from a channel and unsubscribes their session’s live stream. The channel owner cannot leave (codes.FailedPrecondition) — they must transfer ownership first. Emits a leave notice. |
| ListChannels | ListChannelsRequest | ListChannelsResponse | ListChannels returns the channels visible to the calling character: all public channels plus any private or admin channels the character is a member of, paginated. Channels the caller cannot see are omitted rather than surfaced as hidden, so the listing cannot be used to probe for the existence of restricted channels. |
| PostToChannel | PostToChannelRequest | PostToChannelResponse | PostToChannel publishes one line of member-authored content (say, pose, or ooc) to a channel. The handler self-enforces that the caller is a non-muted member before building the CommunicationContent payload and emitting it on the channel subject; a muted member’s post is suppressed and a non-member’s post is refused. The durable effect is the emitted event, which the host audits to channel_log for history. |
| WhoInChannel | WhoInChannelRequest | WhoInChannelResponse | WhoInChannel returns the current member roster of a channel to a member. The plugin-code membership gate runs before any roster read, so a non-member of a private or admin channel is denied before the channel’s existence is confirmed. |
| QueryChannelHistory | QueryChannelHistoryRequest | QueryChannelHistoryResponse | QueryChannelHistory returns recent content for a channel to a member, newest last, capped at the server-side scrollback ceiling. The handler enforces the membership gate as its first step (before any store read) and never returns rows older than the caller’s most-recent join, so leaving and rejoining does not expose messages sent while the caller was absent. |
| InviteToChannel | InviteToChannelRequest | InviteToChannelResponse | InviteToChannel records an invitation permitting a target character to join a private or admin channel (owner-or-admin only via ABAC). Rejected with codes.AlreadyExists when the target is already a member. |
| MuteMember | MuteMemberRequest | MuteMemberResponse | MuteMember marks a target member as muted, suppressing their subsequent posts to the channel while leaving their membership and read access intact (owner-or-admin only via ABAC). Emits a mute notice. |
| BanMember | BanMemberRequest | BanMemberResponse | BanMember marks a target member as banned, removing them from the channel and preventing any future rejoin until unbanned (owner-or-admin only via ABAC). The channel owner cannot be banned (codes.FailedPrecondition). |
| KickMember | KickMemberRequest | KickMemberResponse | KickMember removes a target member from the channel and unsubscribes their live stream, without the permanent block a ban imposes (owner-or-admin only via ABAC). The channel owner cannot be kicked (codes.FailedPrecondition). Emits a leave notice recording the removal. |
| TransferOwnership | TransferOwnershipRequest | TransferOwnershipResponse | TransferOwnership reassigns channel ownership from the calling owner to a target who MUST already be a member (owner-only via ABAC). The former owner is demoted to member in the same transaction. |
holomush/comm/v1/comm.proto
Section titled “holomush/comm/v1/comm.proto”CommunicationContent
Section titled “CommunicationContent”CommunicationContent is the canonical instance-level payload body for every real-time conversational-content event (say/pose/ooc/emit; page/whisper/pemit arrive in Slice 2). It is the per-emit body the type-level rendering hints stamped by RenderingPublisher (kind via Format, label) render over. Producers build it via pkg/plugin/comm; the ContentValidationPublisher validates it.
| Field | Type | Label | Description |
|---|---|---|---|
| actor_id | string | actor_id is the stable character ULID of the author. Scene replay/export/ publish-snapshot decoders read it self-contained from the payload to populate PublishedSceneEntry.Speaker (plugins/core-scenes/commands.go decodeReplayEntries; publish_snapshot.go decodeSnapshotEntry). Empty only for the actorless emit verb; never validated as required (emit). | |
| actor_display_name | string | actor_display_name is the resolved author name for rendering. Empty when name resolution is deferred (scenes today) or for actorless emit; the renderer then falls back to actor_id. | |
| text | string | text is the raw, unrendered content (“waves”, “Hello there.”). Required non-empty. The renderer produces the surface form; producers MUST NOT pre-render (e.g. no “Alaric waves” here). | |
| no_space | bool | no_space renders the actor and text with no separating space (the ”;” semipose form -> “Alaric’s eyes narrow”). Default false. | |
| ooc_style | string | ooc_style selects the OOC surface form for ooc events: "" (default, treated as “say”) / “say” / “pose” / “semipose”. Empty for non-ooc kinds. |
holomush/content/v1/content.proto
Section titled “holomush/content/v1/content.proto”ContentItem
Section titled “ContentItem”ContentItem is a single managed content record retrieved from the store.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | key is the storage key identifying this item; callers conventionally use dot-delimited names such as “landing.hero”, though the store enforces none. | |
| content_type | string | content_type is the IANA media type of the body, for example “text/markdown” or “application/json”. | |
| body | bytes | body is the raw content bytes; interpret according to content_type. | |
| metadata | ContentItem.MetadataEntry | repeated | metadata holds arbitrary string key/value annotations attached to the item, such as “title”, “icon”, “order”, or “alt”. |
ContentItem.MetadataEntry
Section titled “ContentItem.MetadataEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
GetContentRequest
Section titled “GetContentRequest”GetContentRequest selects a single content item by its exact storage key.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | key is the exact content-store key to retrieve; no prefix matching is performed. |
GetContentResponse
Section titled “GetContentResponse”GetContentResponse carries the content item for the requested key. A missing key yields no response message — the RPC fails with a gRPC NotFound status.
| Field | Type | Label | Description |
|---|---|---|---|
| item | ContentItem | item is the content item for the requested key. |
ListContentRequest
Section titled “ListContentRequest”ListContentRequest selects a page of content items whose keys share a common prefix.
| Field | Type | Label | Description |
|---|---|---|---|
| prefix | string | prefix restricts results to keys that begin with this string; pass an empty string to match all keys. | |
| limit | int32 | limit is the maximum number of items to return per page; zero means no limit. The server does not impose its own cap — callers should set a reasonable bound. | |
| cursor | string | cursor is the next_cursor value from a prior ListContentResponse; pass an empty string to start from the beginning. The value is the key of the last item on the previous page, used for keyset pagination. |
ListContentResponse
Section titled “ListContentResponse”ListContentResponse carries one page of content items and a pagination token.
| Field | Type | Label | Description |
|---|---|---|---|
| items | ContentItem | repeated | items is the slice of content items matching the request prefix, ordered by key. |
| next_cursor | string | next_cursor is the key of the last returned item; pass it as cursor in a subsequent request to fetch the next page. An empty string means there are no further items. |
ContentService
Section titled “ContentService”ContentService provides read access to the content store.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetContent | GetContentRequest | GetContentResponse | GetContent retrieves a single content item by key. |
| ListContent | ListContentRequest | ListContentResponse | ListContent returns all content items matching a key prefix. |
holomush/control/v1/control.proto
Section titled “holomush/control/v1/control.proto”ShutdownRequest
Section titled “ShutdownRequest”Parameters for a shutdown request. The graceful field is currently logged but does not alter shutdown behavior — both values invoke the shutdown hook identically (mismatch tracked in holomush-4gchp).
| Field | Type | Label | Description |
|---|---|---|---|
| graceful | bool | When true, the caller intends a drain-and-exit (in-flight requests allowed to complete before the process stops). Currently only logged for observability; the shutdown hook is a parameterless func() and is not yet differentiated on this value. |
ShutdownResponse
Section titled “ShutdownResponse”Confirmation that the shutdown sequence has been triggered.
| Field | Type | Label | Description |
|---|---|---|---|
| message | string | Human-readable confirmation string; currently always “shutdown initiated”. Callers SHOULD NOT parse this value; it exists for operator logs only. |
StatusRequest
Section titled “StatusRequest”Empty carrier for a status poll. No parameters are required; the server derives all response fields from its own runtime state.
StatusResponse
Section titled “StatusResponse”A point-in-time snapshot of the process’s health and identity.
| Field | Type | Label | Description |
|---|---|---|---|
| running | bool | True while the process’s internal running flag is set. Set to false only after GracefulStop completes (internal/control/grpc_server.go::Stop). | |
| pid | int32 | Operating-system process ID as returned by os.Getpid(), cast to int32. Safe on all supported platforms; values never exceed int32 range. | |
| uptime_seconds | int64 | Elapsed seconds since the GRPCServer was constructed via NewGRPCServer. Derived from a monotonic time.Time captured at construction. | |
| component | string | Identifies which process component reported this status, e.g. “core” or “gateway”. Set at construction time; never empty (enforced by NewGRPCServer which returns an error for an empty component string). |
ControlService
Section titled “ControlService”The mTLS-protected admin surface for a running HoloMUSH process. Both the core server and the gateway register an instance on startup (see cmd/holomush/deps.go and cmd/holomush/gateway.go). Callers must present a valid client certificate issued by the game’s root CA.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Shutdown | ShutdownRequest | ShutdownResponse | Triggers an asynchronous process exit via the registered shutdown hook. The RPC returns immediately with a confirmation message; the shutdown callback runs in a background goroutine. Callers should not expect the connection to remain open after the response arrives. Grounded in: internal/control/grpc_server.go::Shutdown |
| Status | StatusRequest | StatusResponse | Returns a snapshot of the process’s liveness and identity without requiring authentication beyond the mTLS channel. Reads from an atomic running flag, os.Getpid(), a monotonic start timestamp, and the component label supplied at construction time. Grounded in: internal/control/grpc_server.go::Status |
holomush/eventbus/v1/eventbus.proto
Section titled “holomush/eventbus/v1/eventbus.proto”Actor identifies who caused an event.
| Field | Type | Label | Description |
|---|---|---|---|
| kind | ActorKind | kind classifies the entity that caused the event; drives downstream audit routing (e.g. plugin_router.go selects the audit sink by kind). | |
| id | bytes | id is the actor’s 16-byte ULID identity, letting downstream audit attribute the event to a concrete entity. Character and plugin actors carry a real ULID; system- and unknown-origin events MAY leave it as the zero ULID, in which case the field is omitted on the wire (see coreActorToEventbusActor). |
Event is the host-side envelope. Wire encoding is proto bytes in the JetStream message data; headers carry routing/codec/version metadata.
| Field | Type | Label | Description |
|---|---|---|---|
| id | bytes | id is the event’s 16-byte ULID; it is the identity and JetStream dedup key (set as Nats-Msg-Id), stable across rebuilds. Ordering is owned by the JetStream per-stream sequence, not by this ULID’s lexical order. | |
| subject | string | subject is the NATS dot-delimited routing address for this event, of the form events.<game_id>.<domain>.<entity-id>[.<facet>…], validated by NewSubject (must start with “events.”). | |
| type | string | type is the event-type discriminator, e.g. “say” or “scene.pose”; used by subscribers to route and render events without decoding the payload. | |
| timestamp | google.protobuf.Timestamp | timestamp records when the event occurred according to the host clock, at nanosecond precision. | |
| actor | Actor | actor identifies the entity that caused the event; host-stamped and never directly settable by plugins. | |
| payload | bytes | payload is the codec.Encode output for the event body; opaque at the envelope layer and decoded by subscribers according to the codec/version metadata carried in the JetStream message headers. | |
| rendering | holomush.core.v1.RenderingMetadata | Rendering metadata, populated by RenderingPublisher.Publish before marshaling for JetStream. Mirrors the corev1.RenderingMetadata used on the gRPC Subscribe wire (one schema, two transports). |
ActorKind
Section titled “ActorKind”ActorKind identifies what type of entity caused an event.
| Name | Number | Description |
|---|---|---|
| ACTOR_KIND_UNSPECIFIED | 0 | ACTOR_KIND_UNSPECIFIED is the zero value; a well-formed envelope never carries it — emitters MUST set a concrete kind. |
| ACTOR_KIND_CHARACTER | 1 | ACTOR_KIND_CHARACTER marks an event caused by an in-game character action. |
| ACTOR_KIND_PLAYER | 2 | ACTOR_KIND_PLAYER attributes an event to a human player account rather than a character. It is a recognized wire/audit value preserved across serialization and history round-trips, but no current emit path produces it: host and plugin emits resolve only to CHARACTER, SYSTEM, or PLUGIN (see validateResolvedActor / bridgeActorKind in event_emitter.go). |
| ACTOR_KIND_SYSTEM | 3 | ACTOR_KIND_SYSTEM marks an event the host itself originated (internal infrastructure, not a character or plugin). |
| ACTOR_KIND_PLUGIN | 4 | ACTOR_KIND_PLUGIN marks an event a plugin emitted; gated by the manifest’s actor_kinds_claimable list at event_emitter.go::Emit. |
holomush/plugin/v1/audit.proto
Section titled “holomush/plugin/v1/audit.proto”AuditEventRequest
Section titled “AuditEventRequest”AuditEventRequest carries a single audit row forwarded by the host per-plugin consumer for the plugin to persist. The row is built from the JetStream message by buildAuditRow, which reads projection fields from the unmarshaled envelope and crypto metadata from NATS headers.
| Field | Type | Label | Description |
|---|---|---|---|
| row | AuditRow | row is the audit row to persist. MUST be non-nil and MUST pass field validation (non-empty codec, non-nil timestamp, 16-byte id, non-empty type and subject) or the plugin returns an error and the host relies on JetStream redelivery. |
AuditEventResponse
Section titled “AuditEventResponse”AuditEventResponse is the empty acknowledgement returned by the plugin after a successful idempotent INSERT. The host acks the JetStream message on receipt.
AuditRow
Section titled “AuditRow”AuditRow is the canonical wire shape for plugin-owned audit rows. Used in both directions: dispatcher → plugin (AuditEventRequest) and plugin → host (QueryHistoryResponse). Mirrors the events_audit row shape so the proto wire format and the storage shape are coupled.
Cleartext projection fields
| Field | Type | Label | Description |
|---|---|---|---|
| id | bytes | id holds the 16-byte binary ULID that uniquely identifies this event. Set from the Nats-Msg-Id header. Used as the primary key for idempotent INSERT (ON CONFLICT (id) DO NOTHING). MUST be exactly 16 bytes; the plugin rejects rows with wrong length. | |
| subject | string | subject is the NATS dot-delimited event subject, e.g. “events.<game_id>.scene.<scene_id>.ic”. Used by the plugin to route scene_pose events and as the WHERE clause in queryLog. | |
| type | string | type is the application-level event type string extracted from the App-Event-Type header, e.g. “scene_pose” or “scene_join”. The plugin dispatches on this value to route scene_pose rows through the transactional InsertScenePose path. | |
| timestamp | google.protobuf.Timestamp | timestamp is the event wall-clock time stamped at publish. Stored as nanosecond-precision TIMESTAMPTZ in scene_log. MUST be non-nil; the plugin rejects nil timestamps at ingest to prevent SQL NULL from corrupting subsequent queryLog scans. | |
| actor | holomush.eventbus.v1.Actor | actor identifies the entity that caused the event. Nil when the event was system-originated (no actor header). Kind is stored as the enum’s String() representation (e.g. “ACTOR_KIND_CHARACTER”). | |
| codec | string | codec names the encryption codec applied to payload. “identity” means payload is plaintext; “xchacha20poly1305-v1” means payload is ciphertext. Sourced from the App-Codec header. MUST be non-empty. | |
| payload | bytes | payload holds the event body. For identity codec this is cleartext; for xchacha20poly1305-v1 this is the AEAD ciphertext, forwarded byte-equal without decryption (INV-CRYPTO-46). Plugins store the bytes opaquely; decryption occurs at read-back via DecryptOwnAuditRows. | |
| dek_ref | uint64 | optional | dek_ref is the numeric key reference into the host’s crypto_keys table identifying which DEK encrypted this payload. Absent for identity-codec rows; MUST be present for AEAD-codec rows. The host enforces the agreement: identity codec ⇔ both dek_ref and dek_version absent. |
| dek_version | uint32 | optional | dek_version is the 1-based rotation counter of the DEK at the time of encryption, stored for key-rotation audit. Absent for identity-codec rows; MUST be present for AEAD-codec rows alongside dek_ref (INV-EVENTBUS-25). |
| schema_ver | int32 | schema_ver is the application schema version stamped at publish via the App-Schema-Version header. Valid range 0–32767 (SMALLINT). The plugin rejects rows outside this range at ingest. |
DecryptOwnAuditRowsRequest
Section titled “DecryptOwnAuditRowsRequest”DecryptOwnAuditRowsRequest carries the calling plugin’s OWN audit rows for host-side read-back decryption (host.v1 AuditService.DecryptOwnAuditRows). The host enforces OwnerMap subject ownership (g1) per row; rows whose subject is owned by a different plugin are refused with not_owner and never decrypted. The batch is REJECTED (not clamped) when it exceeds the server-side cap of 500.
| Field | Type | Label | Description |
|---|---|---|---|
| rows | AuditRow | repeated | rows is the batch of audit rows to decrypt. Each row MUST have been previously stored by this plugin (subject ownership enforced by the host’s OwnerMap g1 gate). A batch exceeding 500 rows is rejected outright rather than partially processed. |
DecryptOwnAuditRowsResponse
Section titled “DecryptOwnAuditRowsResponse”DecryptOwnAuditRowsResponse returns one RowResult per request row, in the same order (1:1 positional correspondence, INV-CRYPTO-37).
| Field | Type | Label | Description |
|---|---|---|---|
| results | RowResult | repeated | results contains one outcome per request row, in the same order as DecryptOwnAuditRowsRequest.rows. Positional correspondence is guaranteed (INV-CRYPTO-37); callers correlate by index or by RowResult.id. |
QueryHistoryRequest
Section titled “QueryHistoryRequest”QueryHistoryRequest specifies the page of audit rows to stream back from the plugin’s own audit store. The host’s PluginHistoryRouter populates this from the eventbus.HistoryQuery and the authenticated session record.
| Field | Type | Label | Description |
|---|---|---|---|
| subject | string | subject is the fully-qualified NATS dot-delimited event subject to query, e.g. “events.main.scene.<scene_id>.ic”. MUST be non-empty and MUST NOT contain wildcard tokens (* or >). The plugin parses this to extract the entity identifier for membership checks. | |
| after | bytes | after is an exclusive lower-bound cursor encoded as a 16-byte ULID. Rows with id > after are returned. Empty means start from the beginning of the log. ULIDs are time-ordered, so this is equivalent to a chronological lower bound within the subject. | |
| before | bytes | before is an exclusive upper-bound cursor encoded as a 16-byte ULID. Rows with id < before are returned. Empty means no upper bound. | |
| page_size | int32 | page_size caps the number of rows returned in this response stream. The host clamps to 200; the plugin MUST also cap at 200 and apply a default of 50 when the value is <= 0. | |
| direction | int32 | direction controls row ordering: 1 = forward (ascending by id, oldest first), 2 = backward (descending by id, newest first). Zero is treated as forward by the plugin. | |
| not_before | google.protobuf.Timestamp | not_before filters out rows whose timestamp is strictly before this value. Applied as a SQL “timestamp >= not_before” predicate. Nil means no lower time bound. | |
| not_after | google.protobuf.Timestamp | not_after filters out rows whose timestamp is strictly after this value. Applied as a SQL “timestamp <= not_after” predicate. Nil means no upper time bound. | |
| caller | holomush.eventbus.v1.Actor | caller identifies the principal on whose behalf the host is reading. Plugins implementing PluginAuditService MUST enforce domain-specific authz (e.g., membership) against this identity before returning rows. An absent caller, a zero identity, or an unsupported Actor.Kind MUST be rejected with gRPC PERMISSION_DENIED. The host populates this field from the authenticated session record; clients never supply it. |
QueryHistoryResponse
Section titled “QueryHistoryResponse”QueryHistoryResponse wraps one audit row in the server-streaming response. The host’s PluginHistoryRouter reads rows from the stream and adapts them to the eventbus.HistoryStream contract.
| Field | Type | Label | Description |
|---|---|---|---|
| row | AuditRow | row is a single audit row from the plugin’s store. Fields match the AuditRow shape used at ingest so the host can reconstruct a full eventbus.Event, including crypto envelope fields for read-back. |
RowResult
Section titled “RowResult”RowResult is the per-row outcome of DecryptOwnAuditRows. Exactly one of plaintext / no_plaintext_reason is populated: plaintext is set iff the row decrypted; no_plaintext_reason is set iff the row was refused (e.g. “not_owner”, “downgrade_refused”, “dek_missing”, “internal”).
| Field | Type | Label | Description |
|---|---|---|---|
| id | bytes | id echoes AuditRow.id so the caller can correlate results back to their source rows without relying solely on positional ordering. | |
| plaintext | bytes | plaintext holds the decrypted event payload bytes when decryption succeeded. May be empty bytes for zero-length payloads; callers MUST distinguish this from no_plaintext_reason by which oneof arm is set, not by length. | |
| no_plaintext_reason | string | no_plaintext_reason is a short ASCII token describing why decryption was refused. It is a stable wire contract (the values MUST NOT drift; SDKs switch on them — see readback.go). The full set: “not_owner” (g1 OwnerMap gate — subject belongs to a different plugin), “auth_guard_deny” (recipient not authorized by manifest declaration / ABAC grant — Phase 3b AuthGuard deny), “downgrade_refused” (INV-CRYPTO-42 fence — sensitive event stored under identity codec), “dek_missing” (INV-CRYPTO-50 fence — no DEK exists for this row’s context), “stale_dek” (INV-CRYPTO-108 — both hot and cold DEK tiers gone), “audit_queue_full” (plugin audit-emit backpressure), and “internal” (host-side error, details logged server-side only). |
PluginAuditService
Section titled “PluginAuditService”PluginAuditService is implemented by plugins that declare audit subjects in their manifest. The host owns the JetStream durable consumer and forwards each delivered event to the plugin via AuditEvent. The plugin INSERTs into its own schema and acks.
QueryHistory is invoked by host’s bus.QueryHistory when the queried subject prefix is owned by this plugin.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| AuditEvent | AuditEventRequest | AuditEventResponse | AuditEvent is the per-message ingestion RPC. The host per-plugin JetStream consumer calls this for every event delivered on subjects declared in the plugin’s manifest audit block. The plugin MUST INSERT idempotently (ON CONFLICT DO NOTHING) and return a success response; the host then acks the JetStream message. On error the host does NOT nak — JetStream AckWait + MaxDeliver handle retry with natural backoff. The AuditRow payload is forwarded byte-equal (ciphertext is never decrypted before forwarding, INV-CRYPTO-46). |
| QueryHistory | QueryHistoryRequest | QueryHistoryResponse stream | QueryHistory streams audit rows for a single subject prefix owned by this plugin. The host’s bus.QueryHistory routes the call here when the OwnerMap maps the requested subject to this plugin. The plugin MUST enforce domain-specific authorization against req.Caller before returning any rows (e.g., scene membership for core-scenes). Rows are ordered by id (ULID lex = chronological) in the direction specified by req.Direction; the page is bounded by req.PageSize (host caps at 200; plugin MUST NOT exceed that cap). |
holomush/plugin/host/v1/audit.proto
Section titled “holomush/plugin/host/v1/audit.proto”DecryptOwnAuditRowsRequest
Section titled “DecryptOwnAuditRowsRequest”DecryptOwnAuditRowsRequest carries the calling plugin’s OWN audit rows for host-side read-back decryption. The host enforces OwnerMap subject ownership (g1) per row; rows whose subject is owned by a different plugin are refused with not_owner and never decrypted. The batch is REJECTED (not clamped) when it exceeds the server-side cap of 500.
| Field | Type | Label | Description |
|---|---|---|---|
| rows | holomush.plugin.v1.AuditRow | repeated | rows is the batch of audit rows to decrypt. Each row MUST have been previously stored by this plugin (subject ownership enforced by the host’s OwnerMap g1 gate). A batch exceeding 500 rows is rejected outright rather than partially processed. |
DecryptOwnAuditRowsResponse
Section titled “DecryptOwnAuditRowsResponse”DecryptOwnAuditRowsResponse returns one RowResult per request row, in the same order (1:1 positional correspondence, INV-CRYPTO-37).
| Field | Type | Label | Description |
|---|---|---|---|
| results | holomush.plugin.v1.RowResult | repeated | results contains one outcome per request row, in the same order as DecryptOwnAuditRowsRequest.rows. Positional correspondence is guaranteed (INV-CRYPTO-37); callers correlate by index or by RowResult.id. |
AuditService
Section titled “AuditService”AuditService is the host-brokered audit capability: a plugin asks the host
to decrypt a batch of its OWN encrypted audit rows host-side; the plugin
never holds a DEK. Carved from the former PluginHostService
(holomush-eykuh.1). Served by auditServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| DecryptOwnAuditRows | DecryptOwnAuditRowsRequest | DecryptOwnAuditRowsResponse | DecryptOwnAuditRows decrypts a batch of the calling plugin’s OWN encrypted audit rows host-side; the plugin never holds a DEK, via ReadbackDecryptor. DecryptOwnRows. Authorization is two-gate (INV-CRYPTO-27): OwnerMap subject ownership (g1) plus the crypto.emits[].readback manifest flag (g2). Each input row gets an independent RowResult (INV-CRYPTO-37) carrying either plaintext or a stable snake_case no_plaintext_reason (“not_owner”, “auth_guard_deny”, “dek_missing”, “downgrade_refused”, “stale_dek”, “audit_queue_full”, “internal”; readback.go reasonToWire). |
holomush/plugin/host/v1/command_registry.proto
Section titled “holomush/plugin/host/v1/command_registry.proto”CommandInfo
Section titled “CommandInfo”CommandInfo is per-command metadata returned by ListCommands.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the canonical command name (e.g. “scene”). | |
| help | string | help is the one-line description from the command registry. | |
| usage | string | usage is the usage pattern (e.g. “scene <subcommand>“). | |
| source | string | source is “core” or the owning plugin name. |
GetCommandHelpRequest
Section titled “GetCommandHelpRequest”GetCommandHelpRequest names a command and the character whose access is checked before returning detail.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the canonical command name to describe. | |
| character_id | string | character_id is the ULID of the character whose access is checked. |
GetCommandHelpResponse
Section titled “GetCommandHelpResponse”GetCommandHelpResponse returns full help detail.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the canonical command name. | |
| help | string | help is the one-line description. | |
| usage | string | usage is the usage pattern. | |
| help_text | string | help_text is the detailed markdown help body. | |
| source | string | source is “core” or the owning plugin name. |
ListCommandsRequest
Section titled “ListCommandsRequest”ListCommandsRequest names the character whose executable command set to enumerate.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the ULID of the character whose capabilities filter the list. |
ListCommandsResponse
Section titled “ListCommandsResponse”ListCommandsResponse returns the filtered command set.
| Field | Type | Label | Description |
|---|---|---|---|
| commands | CommandInfo | repeated | commands is the ABAC-filtered set the character may execute. |
| incomplete | bool | incomplete is true when engine errors hid some commands from the result. |
CommandRegistryService
Section titled “CommandRegistryService”CommandRegistryService is the host-brokered command_registry capability: a
plugin enumerates the ABAC-filtered command set a character may execute and
fetches per-command help. Carved from the former PluginHostService
(holomush-eykuh.1). Served by commandRegistryServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| ListCommands | ListCommandsRequest | ListCommandsResponse | ListCommands enumerates the commands the named character may execute, ABAC-filtered by the host, delegating to commandquery.Querier.Available. The subject is the request’s character_id (parity with the Lua holomush.list_commands(character_id) host function — not the dispatch-token actor, since this is read-only metadata, not an actor-gated mutation). incomplete is true when engine errors hid some commands. |
| GetCommandHelp | GetCommandHelpRequest | GetCommandHelpResponse | GetCommandHelp returns full help detail for one command after an access check for character_id, delegating to commandquery.Querier.Help. Mirrors the Lua holomush.get_command_help(name, character_id) host function. |
holomush/plugin/host/v1/emit.proto
Section titled “holomush/plugin/host/v1/emit.proto”EmitEventRequest
Section titled “EmitEventRequest”EmitEventRequest is the wire form of a plugin emit. The caller’s identity is NOT on this message — it is recovered host-side from the x-holomush-emit-token header (see EmitService.EmitEvent).
| Field | Type | Label | Description |
|---|---|---|---|
| stream | string | Target stream (legacy “prefix:id” form); its namespace must be declared in the manifest’s emits list or the fence rejects the emit. | |
| event_type | string | Event-type discriminator for the emitted event. | |
| payload | bytes | Raw event payload bytes (validated as JSON at the fence). | |
| sensitive | bool | sensitive declares per-event sensitivity at emit time. Phase 3a’s host-side fence at internal/plugin/event_emitter.go::Emit validates this against the plugin manifest’s declared sensitivity: - manifest sensitivity=never: sensitive=true rejected (INV-PLUGIN-29). - manifest sensitivity=may: sensitive=true/false honored. - manifest sensitivity=always: sensitive=false rejected (INV-PLUGIN-30). Default false (proto3 zero) for older plugins compiled before this field existed — matching pre-Phase-3d behavior. |
EmitEventResponse
Section titled “EmitEventResponse”EmitEventResponse is the empty acknowledgement that an emit passed the fence and was published.
RegisterEmitTypeRequest
Section titled “RegisterEmitTypeRequest”RegisterEmitTypeRequest carries the one plugin-owned event type to add to the caller’s registered emit-type set, mirroring the single string argument of the Lua holomush.register_emit_type(type) host function.
| Field | Type | Label | Description |
|---|---|---|---|
| event_type | string | Bare plugin-owned event type (e.g. “scene_pose”) to register; the host validates the accumulated set against the manifest’s crypto.emits (INV-PLUGIN-32). |
RegisterEmitTypeResponse
Section titled “RegisterEmitTypeResponse”RegisterEmitTypeResponse is the empty acknowledgement that the event type was recorded in the caller’s emit-type set.
RequestEmitTokenRequest
Section titled “RequestEmitTokenRequest”RequestEmitTokenRequest carries no fields. The host derives the calling plugin’s identity from the mTLS-bound server struct. Future evolution: do NOT add actor fields here — that would re-open the G1 forgery surface this RPC is designed to close.
RequestEmitTokenResponse
Section titled “RequestEmitTokenResponse”RequestEmitTokenResponse returns the issued self-token.
| Field | Type | Label | Description |
|---|---|---|---|
| token | string | Opaque self-token. Plugins MUST treat this as opaque; only the host’s emitTokenStore can interpret it. The token is bound to ActorPlugin + the calling plugin’s name and is single-use-friendly (TTL-revoked). |
EmitService
Section titled “EmitService”EmitService is the host-brokered emit capability: a plugin publishes
events through the host emit fence, requests the self-token a plugin-served
gRPC handler needs to do so, and registers the emit-type set the host
validates against the manifest. Carved from the former PluginHostService
(holomush-eykuh.1). Served by emitServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| EmitEvent | EmitEventRequest | EmitEventResponse | EmitEvent publishes one plugin-originated event onto the bus through the host emit fence. The caller’s identity is NOT trusted from the wire — the plugin presents a host-issued dispatch token in the x-holomush-emit-token metadata header, the host recovers the vouched-for actor from tokenStore.Lookup(pluginName, token), and a missing/foreign token is rejected (EMIT_TOKEN_MISSING / EMIT_TOKEN_REJECTED). The recovered actor then flows through PluginEventEmitter.Emit, which enforces the manifest gates: emits (subject namespace must be declared), actor_kinds_claimable (actor kind must be listed — EMIT_ACTOR_KIND_NOT_CLAIMABLE), and the crypto.emits sensitivity fence. These gates fire identically for Lua and binary plugins (plugin runtime symmetry); the token mechanism is the binary-side forgery fence. |
| RequestEmitToken | RequestEmitTokenRequest | RequestEmitTokenResponse | RequestEmitToken issues a self-token bound to {ActorPlugin, pluginName} so a plugin-served gRPC handler (e.g. SceneService.CreateScene) — which is NOT reached via DeliverEvent/DeliverCommand and so holds no dispatch token — can still call EmitEvent. The plugin’s identity is taken from the mTLS-bound server struct (s.pluginName); the request carries no identity fields, so a plugin cannot impersonate another actor or escalate to a character actor through this RPC. The actor_kinds_claimable manifest gate still fires when the issued token is later spent at EmitEvent. (Spec §3.3.5 / §5.4 two-token pattern.) |
| RegisterEmitType | RegisterEmitTypeRequest | RegisterEmitTypeResponse | RegisterEmitType records one plugin-owned event type the plugin may later emit, validated by the host against the manifest’s crypto.emits per INV-PLUGIN-32. Promotes the Lua holomush.register_emit_type(type) host function (internal/plugin/hostfunc/stdlib_emit_registry.go) to the binary surface so both runtimes declare their emit-type set through one channel. |
holomush/plugin/host/v1/eval.proto
Section titled “holomush/plugin/host/v1/eval.proto”EvaluateRequest
Section titled “EvaluateRequest”EvaluateRequest names the action and resource to evaluate. The subject is NOT here — it is recovered host-side from the dispatch token (spec §2, INV-PLUGIN-22).
| Field | Type | Label | Description |
|---|---|---|---|
| action | string | ABAC action verb to authorize (e.g. “read”, “write”). | |
| resource | string | resource is a typed instance ref: “scene:01ABC…”. |
EvaluateResponse
Section titled “EvaluateResponse”EvaluateResponse returns the ABAC engine’s decision.
| Field | Type | Label | Description |
|---|---|---|---|
| allowed | bool | Whether the action is permitted on the resource for the recovered subject. | |
| reason | string | Human-readable rationale for the decision (e.g. the deny reason). | |
| matched_policy | string | Identifier of the policy that produced the decision, when one matched. |
EvalService
Section titled “EvalService”EvalService is the host-brokered eval capability: a plugin asks the host
ABAC engine to authorize one action against one resource instance it owns.
Carved from the former PluginHostService (holomush-eykuh.1). Served by
evalServer in internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Evaluate | EvaluateRequest | EvaluateResponse | Evaluate runs the host ABAC engine for one action against one resource instance owned by the calling plugin. The subject is derived host-side from the dispatch token exactly as EmitEvent does (token→actor recovery) — there is no subject field on the wire (spec §2, INV-PLUGIN-22). Fails closed on nil engine, missing/rejected token, empty actor subject, or a resource type the plugin does not own. |
holomush/plugin/host/v1/focus.proto
Section titled “holomush/plugin/host/v1/focus.proto”AutoFocusOnJoinRequest
Section titled “AutoFocusOnJoinRequest”AutoFocusOnJoinRequest names the character and scene to fan-out focus across the character’s connections.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | bytes | ULID bytes of the character whose connections are being focused. | |
| scene_id | bytes | ULID bytes of the scene to focus those connections on. |
AutoFocusOnJoinResponse
Section titled “AutoFocusOnJoinResponse”AutoFocusOnJoinResponse reports per-connection fan-out outcomes.
| Field | Type | Label | Description |
|---|---|---|---|
| focused_connection_ids | bytes | repeated | Connection ULIDs newly focused on the scene by this call. |
| total_connection_count | uint32 | Total terminal/telnet connections the character had (the fan-out denominator). | |
| skipped_connection_ids | bytes | repeated | Connection ULIDs skipped because they were already explicitly focused elsewhere (D8). |
| failed_connection_ids | FocusFailure | repeated | Connections that failed to focus, each with a structured reason. |
FocusFailure
Section titled “FocusFailure”FocusFailure carries the connection_id and reason for an AutoFocusOnJoin failure.
| Field | Type | Label | Description |
|---|---|---|---|
| connection_id | bytes | ULID bytes of the connection that failed to focus. | |
| reason | FocusFailureReason | Why the focus attempt failed for that connection. |
FocusKey
Section titled “FocusKey”FocusKey identifies a focus membership within a session. A session’s focus memberships are unique by (kind, target_id) pair.
| Field | Type | Label | Description |
|---|---|---|---|
| kind | FocusKind | Which kind of focused context this key names. | |
| target_id | string | ULID of the focused target (e.g. the scene id) within that kind. |
GetConnectionFocusRequest
Section titled “GetConnectionFocusRequest”GetConnectionFocusRequest carries the connection ID whose focus is being read. Read-only counterpart of SetConnectionFocus.
| Field | Type | Label | Description |
|---|---|---|---|
| connection_id | bytes | ULID bytes of the connection whose focus is being read. |
GetConnectionFocusResponse
Section titled “GetConnectionFocusResponse”GetConnectionFocusResponse returns the connection’s current focus key, if any. Absent when the connection is grid-focused or unknown.
| Field | Type | Label | Description |
|---|---|---|---|
| focus_key | FocusKey | optional | The connection’s focus; absent for grid focus or unknown connection. |
IsAnyConnFocusedRequest
Section titled “IsAnyConnFocusedRequest”IsAnyConnFocusedRequest names the character and scene to test for any focused connection.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | bytes | ULID bytes of the character to check. | |
| scene_id | bytes | ULID bytes of the scene the connections might be focused on. |
IsAnyConnFocusedResponse
Section titled “IsAnyConnFocusedResponse”IsAnyConnFocusedResponse reports the focus check result.
| Field | Type | Label | Description |
|---|---|---|---|
| focused | bool | True iff at least one of the character’s connections focuses the scene. |
JoinFocusRequest
Section titled “JoinFocusRequest”JoinFocusRequest names the session and the focus target to add a membership for.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | Session to add the focus membership to. | |
| target | FocusKey | The (kind, target_id) membership to add. |
JoinFocusResponse
Section titled “JoinFocusResponse”JoinFocusResponse is the empty ack that the membership was added.
LeaveFocusByTargetRequest
Section titled “LeaveFocusByTargetRequest”LeaveFocusByTargetRequest names a focus target to remove from every session that holds it (cross-session fan-out).
| Field | Type | Label | Description |
|---|---|---|---|
| target | FocusKey | The (kind, target_id) membership to sweep out of all holding sessions. |
LeaveFocusByTargetResponse
Section titled “LeaveFocusByTargetResponse”LeaveFocusByTargetResponse reports the aggregate result of a cross-session leave sweep.
| Field | Type | Label | Description |
|---|---|---|---|
| succeeded | int32 | Number of sessions successfully left. Zero is a valid result (target had no members or every member was already non-a-member — per-session idempotent no-ops count as successes). Callers comparing succeeded + len(failed_session_ids) against total_scanned can distinguish total, partial, and empty-sweep outcomes without parsing any error string. | |
| total_scanned | int32 | Number of non-expired sessions the sweep scanned. Always >= succeeded + len(failed_session_ids). | |
| failed_session_ids | string | repeated | Session IDs for which the per-session leave failed. Empty means every scanned session succeeded (idempotent no-ops included). Per-session error details are not serialized; callers should treat these IDs as the authoritative partial-failure signal and re-issue LeaveFocus against them if retry is desired. The RPC error is reserved for enumeration/list failures (e.g., the session store could not list members). |
LeaveFocusRequest
Section titled “LeaveFocusRequest”LeaveFocusRequest names the session and the focus target to remove a membership for (idempotent on non-member).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | Session to remove the focus membership from. | |
| target | FocusKey | The (kind, target_id) membership to remove. |
LeaveFocusResponse
Section titled “LeaveFocusResponse”LeaveFocusResponse is the empty ack for LeaveFocus.
PresentFocusRequest
Section titled “PresentFocusRequest”PresentFocusRequest names the session and the existing membership to set as its PresentingFocus.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | Session whose PresentingFocus pointer is being set. | |
| target | FocusKey | Existing membership to present; validated against FocusMemberships. |
PresentFocusResponse
Section titled “PresentFocusResponse”PresentFocusResponse is the empty ack for PresentFocus.
SetConnectionFocusRequest
Section titled “SetConnectionFocusRequest”SetConnectionFocusRequest selects one connection and the focus to set on it (Phase 5).
| Field | Type | Label | Description |
|---|---|---|---|
| connection_id | bytes | ULID bytes of the connection whose focus is being set. | |
| focus_key | FocusKey | optional | The focus to set; absent (unset) clears the connection’s focus. |
| is_scene_grid | bool | is_scene_grid signals that this call originated from a scene grid command — substrate skips the D9 PresentingFocus write per D10. |
SetConnectionFocusResponse
Section titled “SetConnectionFocusResponse”SetConnectionFocusResponse echoes the resulting focus.
| Field | Type | Label | Description |
|---|---|---|---|
| focus_key | FocusKey | optional | The connection’s focus after the write; absent if the connection was cleared/unfocused. |
FocusFailureReason
Section titled “FocusFailureReason”FocusFailureReason is the closed set of per-connection AutoFocusOnJoin failure causes.
| Name | Number | Description |
|---|---|---|
| FOCUS_FAILURE_REASON_UNSPECIFIED | 0 | Zero value; not a real failure reason. |
| FOCUS_FAILURE_REASON_MEMBERSHIP_ABSENT | 1 | The session lacked the focus membership (JoinFocus was not completed first). |
| FOCUS_FAILURE_REASON_CONNECTION_NOT_FOUND | 2 | The connection could not be found (e.g. it dropped during the sweep). |
FocusKind
Section titled “FocusKind”FocusKind enumerates the types of focused contexts a character can participate in. Adding a new kind requires: (a) a new constant here, (b) a matching session.FocusKind constant in Go, (c) a new FocusKindPolicy implementation registered in the coordinator.
| Name | Number | Description |
|---|---|---|
| FOCUS_KIND_UNSPECIFIED | 0 | Zero value; not a real focus kind — a well-formed FocusKey sets a concrete kind. |
| FOCUS_KIND_SCENE | 1 | A roleplay scene focus. |
FocusService
Section titled “FocusService”FocusService is the host-brokered focus capability: a plugin granted
capability: focus may mutate and read session/connection focus state through
the host focus coordinator. Carved from the former PluginHostService
(holomush-eykuh.1). Served by focusServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| JoinFocus | JoinFocusRequest | JoinFocusResponse | JoinFocus adds a focus membership (e.g. a scene) to a session via the host focus coordinator. The plugin declares intent; the coordinator applies the kind-specific replay policy. Fails if the focus coordinator is not configured. |
| LeaveFocus | LeaveFocusRequest | LeaveFocusResponse | LeaveFocus removes one focus membership from a session. Idempotent — leaving a target the session does not hold is a successful no-op. |
| LeaveFocusByTarget | LeaveFocusByTargetRequest | LeaveFocusByTargetResponse | LeaveFocusByTarget removes the given focus membership from every non-expired session that holds it — cross-session fan-out. Partial success is normal: per-session failures aggregate into the response, not an RPC error. |
| PresentFocus | PresentFocusRequest | PresentFocusResponse | PresentFocus repoints a session’s PresentingFocus to an existing membership. The target MUST already be in the session’s FocusMemberships. |
| SetConnectionFocus | SetConnectionFocusRequest | SetConnectionFocusResponse | SetConnectionFocus is the explicit focus mutation for a single Connection; validates the membership, then writes Connection.FocusKey and (D9-gated) Info.PresentingFocus atomically under one Store-lock acquisition. |
| GetConnectionFocus | GetConnectionFocusRequest | GetConnectionFocusResponse | GetConnectionFocus returns the named connection’s current per-connection focus, or absent when grid-focused (FocusKey nil) or unknown. Read-only. |
| AutoFocusOnJoin | AutoFocusOnJoinRequest | AutoFocusOnJoinResponse | AutoFocusOnJoin focuses all of a character’s terminal/telnet connections on a scene at once. Connections already explicitly focused elsewhere are skipped. The caller MUST have completed JoinFocus first. |
| IsAnyConnFocused | IsAnyConnFocusedRequest | IsAnyConnFocusedResponse | IsAnyConnFocused reports whether any of the character’s connections focuses the given scene, so callers can decide whether to emit a notification. |
holomush/plugin/host/v1/kv.proto
Section titled “holomush/plugin/host/v1/kv.proto”DeleteRequest
Section titled “DeleteRequest”DeleteRequest is the (currently unserved, holomush-l6std) request to delete a key from the calling plugin’s KV namespace. The namespace is bound host-side from the authenticated plugin identity, never carried on the wire.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | Key to delete within the caller’s namespace. |
DeleteResponse
Section titled “DeleteResponse”DeleteResponse is the empty ack for Delete (unserved, holomush-l6std).
GetRequest
Section titled “GetRequest”GetRequest is the (currently unserved, holomush-l6std) request to read a key from the calling plugin’s KV namespace. The namespace is bound host-side from the authenticated plugin identity, never carried on the wire.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | Key to read within the caller’s namespace. |
GetResponse
Section titled “GetResponse”GetResponse returns a KV lookup result (unserved, holomush-l6std).
| Field | Type | Label | Description |
|---|---|---|---|
| value | string | The stored value, or empty when not found. | |
| found | bool | Whether the key existed; distinguishes a stored empty value from a miss. |
SetRequest
Section titled “SetRequest”SetRequest is the (currently unserved, holomush-l6std) request to write a key in the calling plugin’s KV namespace. The namespace is bound host-side from the authenticated plugin identity, never carried on the wire.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | Key to write within the caller’s namespace. | |
| value | string | Value to store under the key. |
SetResponse
Section titled “SetResponse”SetResponse is the empty ack for Set (unserved, holomush-l6std).
KVService
Section titled “KVService”KVService is the host-brokered kv capability: a plugin reads, writes, and
deletes keys in its own namespaced key-value store. The namespace is the
CALLING plugin’s identity, bound host-side from the authenticated transport
(mirroring the sibling host services) — it is NOT a request field, so one
plugin can never target another plugin’s KV partition. Carved from the former
PluginHostService (holomush-eykuh.1). All three RPCs are DECLARED BUT UNSERVED
today (holomush-l6std): no server impl, no production client; they return
codes.Unimplemented.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Get | GetRequest | GetResponse | Get reads a value from the plugin’s namespaced key-value store. DECLARED BUT UNSERVED (holomush-l6std): no server impl, no client; returns codes.Unimplemented today. |
| Set | SetRequest | SetResponse | Set writes a value into the plugin’s namespaced key-value store. DECLARED BUT UNSERVED (holomush-l6std): no server impl, no client; returns codes.Unimplemented today. |
| Delete | DeleteRequest | DeleteResponse | Delete removes a key from the plugin’s namespaced key-value store. DECLARED BUT UNSERVED (holomush-l6std): no server impl, no client; returns codes.Unimplemented today. |
holomush/plugin/host/v1/property.proto
Section titled “holomush/plugin/host/v1/property.proto”GetPropertyRequest
Section titled “GetPropertyRequest”GetPropertyRequest names the entity and property to read.
| Field | Type | Label | Description |
|---|---|---|---|
| entity_type | string | Registered entity type (e.g. “location”, “object”). | |
| entity_id | string | ULID of the entity to read the property from. | |
| property | string | Registry-defined property name to read (e.g. “name”, “description”). |
GetPropertyResponse
Section titled “GetPropertyResponse”GetPropertyResponse returns the property’s current value as a string.
| Field | Type | Label | Description |
|---|---|---|---|
| value | string | String value of the requested property. |
SetPropertyRequest
Section titled “SetPropertyRequest”SetPropertyRequest names the entity, property, and value to write.
| Field | Type | Label | Description |
|---|---|---|---|
| entity_type | string | Registered entity type (e.g. “location”, “object”). | |
| entity_id | string | ULID of the entity to write the property on. | |
| property | string | Registry-defined property name to write (e.g. “name”, “description”). | |
| value | string | New string value to store for the property. |
SetPropertyResponse
Section titled “SetPropertyResponse”SetPropertyResponse is the empty acknowledgement returned on a successful property write.
PropertyService
Section titled “PropertyService”PropertyService is the host-brokered property capability: a plugin reads
and writes registry-validated properties on world entities (locations,
objects, and any registered type). Promotes the Lua holomush.get_property /
set_property host functions (internal/plugin/hostfunc/world_write.go) to the
binary surface. Served by propertyServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetProperty | GetPropertyRequest | GetPropertyResponse | GetProperty reads one registry-defined property from an entity, mirroring the Lua holomush.get_property(entity_type, entity_id, property) host function (getEntityProperty via property.Definition.Get). The host validates the entity type, entity ULID, and property name before reading. |
| SetProperty | SetPropertyRequest | SetPropertyResponse | SetProperty writes one registry-defined property on an entity, mirroring the Lua holomush.set_property(entity_type, entity_id, property, value) host function (setEntityProperty via property.Definition.Set). The host validates the entity type, entity ULID, and property name before writing. |
holomush/plugin/host/v1/session.proto
Section titled “holomush/plugin/host/v1/session.proto”BroadcastRequest
Section titled “BroadcastRequest”BroadcastRequest carries the system message to send to all active sessions.
| Field | Type | Label | Description |
|---|---|---|---|
| message | string | System message body to broadcast to every active session. |
BroadcastResponse
Section titled “BroadcastResponse”BroadcastResponse is the empty acknowledgement that the broadcast was sent.
DisconnectRequest
Section titled “DisconnectRequest”DisconnectRequest names the session to disconnect and the reason to record.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | ULID of the session to forcibly disconnect. | |
| reason | string | Human-readable reason surfaced for the disconnect. |
DisconnectResponse
Section titled “DisconnectResponse”DisconnectResponse is the empty acknowledgement that the session was disconnected.
FindByNameRequest
Section titled “FindByNameRequest”FindByNameRequest names the character whose active session to resolve.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | Character display name to match (case-insensitive). |
FindByNameResponse
Section titled “FindByNameResponse”FindByNameResponse returns the matched session, or an absent session when no active session matches the name.
| Field | Type | Label | Description |
|---|---|---|---|
| session | SessionInfo | The matched active session; absent when no session was found. |
ListActiveRequest
Section titled “ListActiveRequest”ListActiveRequest carries no fields; it requests every active session.
ListActiveResponse
Section titled “ListActiveResponse”ListActiveResponse returns all currently active sessions.
| Field | Type | Label | Description |
|---|---|---|---|
| sessions | SessionInfo | repeated | Every currently active session, in store order. |
SessionInfo
Section titled “SessionInfo”SessionInfo is the flat, string-keyed session projection the host hands to plugins, mirroring hostfunc.SessionInfo. It avoids coupling plugins to internal session types.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the active session. | |
| character_id | string | ULID of the character the session belongs to. | |
| character_name | string | Display name of the session’s character. | |
| location_id | string | ULID of the character’s current location. | |
| grid_present | bool | Whether the character is visible on the grid (has a terminal/telnet connection). | |
| last_whispered | string | Display name of the last character this session whispered to; empty when none. |
SetLastWhisperedRequest
Section titled “SetLastWhisperedRequest”SetLastWhisperedRequest names the session and the whisper target to record.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | ULID of the session to update. | |
| name | string | Display name of the last character whispered to. |
SetLastWhisperedResponse
Section titled “SetLastWhisperedResponse”SetLastWhisperedResponse is the empty acknowledgement that the whisper target was recorded.
SessionAdminService
Section titled “SessionAdminService”SessionAdminService is the host-brokered administrative half of the session
capability: a plugin broadcasts system messages to all sessions and forcibly
disconnects a session. Promotes the Lua session.broadcast / session.disconnect
host functions (internal/plugin/hostfunc/cap_session.go) to the binary
surface. Served by sessionAdminServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Broadcast | BroadcastRequest | BroadcastResponse | Broadcast sends a system message to all active sessions, mirroring the Lua session.broadcast(message) host function (SessionAccess.BroadcastSystemMessage). |
| Disconnect | DisconnectRequest | DisconnectResponse | Disconnect forcibly disconnects a session with a reason, mirroring the Lua session.disconnect(session_id, reason) host function (SessionAccess.DisconnectSession). |
SessionService
Section titled “SessionService”SessionService is the host-brokered read/write half of the session
capability: a plugin looks up sessions by character name, lists active
sessions, and records the last-whispered target. Promotes the Lua session.*
host functions (internal/plugin/hostfunc/cap_session.go) to the binary
surface. Served by sessionServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| FindByName | FindByNameRequest | FindByNameResponse | FindByName returns the active session for a character looked up by name (case-insensitive), mirroring the Lua session.find_by_name(name) host function (SessionAccess.FindSessionByName). The response session is absent when no active session matches. |
| ListActive | ListActiveRequest | ListActiveResponse | ListActive returns every currently active session, mirroring the Lua session.list_active() host function (SessionAccess.ListActiveSessions). |
| SetLastWhispered | SetLastWhisperedRequest | SetLastWhisperedResponse | SetLastWhispered records the last whisper target on a session, mirroring the Lua session.set_last_whispered(session_id, name) host function (SessionAccess.SetLastWhispered). |
holomush/plugin/host/v1/settings.proto
Section titled “holomush/plugin/host/v1/settings.proto”GetSettingRequest
Section titled “GetSettingRequest”GetSettingRequest reads one owner-partitioned key. The owner is NOT on the wire — the host binds it from the authenticated plugin name (structural cross-plugin isolation).
| Field | Type | Label | Description |
|---|---|---|---|
| scope | SettingScope | Scope to read from. | |
| principal_id | string | Principal ULID: player ID for PLAYER, character ID for CHARACTER, empty for GAME. | |
| key | string | Plugin-owned dot-key to read (e.g. “content.cw_block”). |
GetSettingResponse
Section titled “GetSettingResponse”GetSettingResponse returns a typed list-or-scalar value read from the resolved scope/partition.
| Field | Type | Label | Description |
|---|---|---|---|
| found | bool | Whether the key resolved in the requested scope/partition. | |
| string_list | string | repeated | String-list value (Phase 8 settings are list-valued). |
| string_value | string | Scalar string value (for non-list keys). |
SetSettingRequest
Section titled “SetSettingRequest”SetSettingRequest writes one key in the caller’s owner partition. Owner is bound host-side, not supplied here.
| Field | Type | Label | Description |
|---|---|---|---|
| scope | SettingScope | Target scope to write. | |
| principal_id | string | Principal ULID (empty for GAME). | |
| key | string | Plugin-owned dot-key to write. | |
| string_list | string | repeated | String-list value to store. |
SetSettingResponse
Section titled “SetSettingResponse”SetSettingResponse is the empty acknowledgement returned on a successful write.
SettingScope
Section titled “SettingScope”SettingScope selects which settings scope a Get/Set targets. There is no chained mode — callers compose scopes themselves (e.g. a CW-block union read across game+player+character). The iokti.7 handler maps each value to its backing store and rejects the unspecified value (fail closed).
| Name | Number | Description |
|---|---|---|
| SETTING_SCOPE_UNSPECIFIED | 0 | Unspecified scope — rejected by the handler (fail closed). |
| SETTING_SCOPE_GAME | 1 | Server-wide scope, backed by holomush_system_info. |
| SETTING_SCOPE_PLAYER | 2 | Per-player scope, backed by players.preferences. |
| SETTING_SCOPE_CHARACTER | 3 | Per-character scope, backed by characters.preferences. |
SettingsService
Section titled “SettingsService”SettingsService is the host-brokered settings capability: a plugin reads
and writes owner-partitioned settings in the GAME/PLAYER/CHARACTER scopes.
Carved from the former PluginHostService (holomush-eykuh.1). Served by
settingsServer in internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetSetting | GetSettingRequest | GetSettingResponse | GetSetting reads a single-scope setting in the calling plugin’s owner partition (owner bound host-side from the authenticated plugin name, never from the request). The handler resolves SettingScope to its backing store; a missing key returns a successful response with found=false, never a codes.NotFound status error. |
| SetSetting | SetSettingRequest | SetSettingResponse | SetSetting writes a single-scope setting in the calling plugin’s partition; GAME scope requires an operator authorization decision (host-enforced, not trusted from the wire). The owner partition is bound host-side from the authenticated plugin name. |
holomush/plugin/host/v1/stream.proto
Section titled “holomush/plugin/host/v1/stream.proto”AddSessionStreamRequest
Section titled “AddSessionStreamRequest”AddSessionStreamRequest is the (currently unserved, holomush-l6std) request to subscribe an active session to one more stream.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | Active session identifier. | |
| stream | string | Stream name to subscribe to (format: “prefix:id”). | |
| replay_mode | StreamReplayMode | replay_mode controls initial replay. Optional; defaults to FROM_CURSOR if unspecified for backwards compatibility. |
AddSessionStreamResponse
Section titled “AddSessionStreamResponse”AddSessionStreamResponse is the empty ack for AddSessionStream (unserved, holomush-l6std).
Event is the host→plugin delivery shape for one game event (the proto mirror of pkg/plugin.Event), returned in a QueryStreamHistory page.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID string uniquely identifying the event; also its bus dedup key. | |
| stream | string | Source stream this event belongs to, in legacy “prefix:id” form (e.g. “location:loc_abc123”). Translated to the dot-delimited NATS subject at the emit boundary. | |
| type | string | Event-type discriminator (e.g. “say”, “pose”, “arrive”, “leave”, “system”) that the plugin handler switches on. | |
| timestamp | int64 | Event occurrence time in Unix milliseconds (host clock). | |
| actor_kind | string | Actor kind as a string (e.g. “character”, “system”, “plugin”). Carried as a string rather than an enum for forward-compat; the SDK maps it to its ActorKind type. Distinct from the bus-internal ActorKind enum. | |
| actor_id | string | ULID of the actor that caused the event (the character/system/plugin id). | |
| payload | string | JSON-encoded event payload (max 64 KiB); the plugin decodes it per the event type’s schema. | |
| cursor | bytes | cursor is the opaque pagination token for this event. Pass as QueryStreamHistoryRequest.cursor on the next call to page backward from this position. Empty on events received via delivery (not history). Treat as an opaque blob. |
QueryStreamHistoryRequest
Section titled “QueryStreamHistoryRequest”QueryStreamHistoryRequest selects a backward-paginated tail of a stream for plugin-side display.
| Field | Type | Label | Description |
|---|---|---|---|
| stream | string | Stream to read history from (legacy “prefix:id” form). | |
| count | int32 | Page size; negative is rejected, values above 500 are clamped to 500. | |
| not_before_ms | int64 | Epoch milliseconds. Events before this time are excluded. 0 means no lower bound. | |
| cursor | bytes | cursor is the opaque pagination cursor from a previous response. Events older than the cursor position are returned. Empty = start from latest. |
QueryStreamHistoryResponse
Section titled “QueryStreamHistoryResponse”QueryStreamHistoryResponse returns one history page plus the cursor for the next (older) page.
| Field | Type | Label | Description |
|---|---|---|---|
| events | Event | repeated | The page of events in ascending (oldest→newest) order; each carries its own backward-paging cursor. |
| next_cursor | bytes | next_cursor is the opaque cursor for the next page. Empty if no more pages. |
RemoveSessionStreamRequest
Section titled “RemoveSessionStreamRequest”RemoveSessionStreamRequest is the (currently unserved, holomush-l6std) request to unsubscribe an active session from a stream.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | Active session identifier. | |
| stream | string | Stream name to unsubscribe from (format: “prefix:id”). |
RemoveSessionStreamResponse
Section titled “RemoveSessionStreamResponse”RemoveSessionStreamResponse is the empty ack for RemoveSessionStream (unserved, holomush-l6std).
StreamReplayMode
Section titled “StreamReplayMode”StreamReplayMode controls how a stream subscription’s initial replay behaves when added via AddSessionStream.
| Name | Number | Description |
|---|---|---|
| STREAM_REPLAY_MODE_UNSPECIFIED | 0 | Zero value; treated as FROM_CURSOR for backward compatibility. |
| STREAM_REPLAY_MODE_FROM_CURSOR | 1 | Replay from the session’s saved cursor for the stream (catch-up). |
| STREAM_REPLAY_MODE_LIVE_ONLY | 2 | Skip historical replay and deliver only newly-arriving events. |
StreamHistoryService
Section titled “StreamHistoryService”StreamHistoryService is the host-brokered stream_history capability: a
plugin reads the tail of a stream for plugin-side display, backward-paginated
by opaque cursor. Carved from the former PluginHostService (holomush-eykuh.1).
Served by streamHistoryServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| QueryStreamHistory | QueryStreamHistoryRequest | QueryStreamHistoryResponse | QueryStreamHistory reads the tail of a stream for plugin-side display, backward-paginated by opaque cursor, via HistoryReader.ReplayTail. Read-only: it does not advance session cursors or mutate session state. A negative count is rejected (INVALID_ARGUMENT); count is CLAMPED to maxQueryStreamHistoryCount (500), not rejected, when too large. |
StreamSubscriptionService
Section titled “StreamSubscriptionService”StreamSubscriptionService is the host-brokered stream_subscription
capability: a plugin subscribes or unsubscribes an active session to a stream
mid-session. Carved from the former PluginHostService (holomush-eykuh.1).
Both RPCs are DECLARED BUT UNSERVED today (holomush-l6std): no server impl,
no production client; they return codes.Unimplemented.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| AddSessionStream | AddSessionStreamRequest | AddSessionStreamResponse | AddSessionStream subscribes an active session to an additional stream mid-session. DECLARED BUT UNSERVED (holomush-l6std): no server impl, no production client; returns codes.Unimplemented today. (The wire comment about SESSION_NOT_FOUND describes the intended-but-unimplemented contract.) |
| RemoveSessionStream | RemoveSessionStreamRequest | RemoveSessionStreamResponse | RemoveSessionStream unsubscribes an active session from a stream. DECLARED BUT UNSERVED (holomush-l6std): no server impl, no production client; returns codes.Unimplemented today. |
holomush/plugin/host/v1/world.proto
Section titled “holomush/plugin/host/v1/world.proto”CharacterSummary
Section titled “CharacterSummary”CharacterSummary is the lightweight (id, name) projection returned for each character at a location. Full detail requires QueryCharacter per id.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the character. | |
| name | string | Display name of the character. |
CreateExitRequest
Section titled “CreateExitRequest”CreateExitRequest carries the endpoints, name, and optional bidirectional settings for a new exit.
| Field | Type | Label | Description |
|---|---|---|---|
| from_id | string | ULID of the source location the exit leaves from. | |
| to_id | string | ULID of the destination location the exit leads to. | |
| name | string | Display name of the exit (e.g. “north”). | |
| bidirectional | bool | When true, the host also creates the reverse exit. | |
| return_name | string | Display name of the reverse exit when bidirectional; empty otherwise. |
CreateExitResponse
Section titled “CreateExitResponse”CreateExitResponse returns the created exit’s id and name.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID assigned to the new exit. | |
| name | string | Display name of the new exit. |
CreateLocationRequest
Section titled “CreateLocationRequest”CreateLocationRequest carries the fields for a new location.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | Display name for the new location. | |
| description | string | Long-form description for the new location. | |
| type | string | Location type string; the host validates it against world.LocationType. |
CreateLocationResponse
Section titled “CreateLocationResponse”CreateLocationResponse returns the created location’s id and name.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID assigned to the new location. | |
| name | string | Display name of the new location. |
CreateObjectRequest
Section titled “CreateObjectRequest”CreateObjectRequest carries the name, containment placement, and optional description for a new object. The placement oneof enforces at the wire boundary that exactly one of location_id / character_id / container_id is set, matching the Lua handler’s containment validation.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | Display name for the new object. | |
| location_id | string | ULID of the location to place the object in; set iff location-contained. | |
| character_id | string | ULID of the character to hand the object to; set iff character-held. | |
| container_id | string | ULID of the object to nest this object inside; set iff object-contained. | |
| description | string | Optional long-form description for the new object. |
CreateObjectResponse
Section titled “CreateObjectResponse”CreateObjectResponse returns the created object’s id and name.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID assigned to the new object. | |
| name | string | Display name of the new object. |
FindLocationRequest
Section titled “FindLocationRequest”FindLocationRequest names the location to resolve by display name.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | Name to match a location against, within the plugin subject’s scope. |
FindLocationResponse
Section titled “FindLocationResponse”FindLocationResponse returns the matched location’s id and name.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the matched location. | |
| name | string | Display name of the matched location. |
QueryCharacterRequest
Section titled “QueryCharacterRequest”QueryCharacterRequest names the character to query by ULID.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | ULID of the character to fetch. |
QueryCharacterResponse
Section titled “QueryCharacterResponse”QueryCharacterResponse returns the queried character’s fields.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the character. | |
| player_id | string | ULID of the player account behind the character. | |
| name | string | Display name of the character. | |
| description | string | Long-form description of the character. | |
| location_id | string | ULID of the character’s current location; empty when the character has no location set (the Lua handler omits the field in that case). |
QueryLocationCharactersRequest
Section titled “QueryLocationCharactersRequest”QueryLocationCharactersRequest names the location and optional pagination bounds for listing the characters present there.
| Field | Type | Label | Description |
|---|---|---|---|
| location_id | string | ULID of the location whose characters are listed. | |
| limit | int32 | Maximum number of characters to return; 0 applies the host default (100). | |
| offset | int32 | Number of leading results to skip for pagination; 0 starts at the first. |
QueryLocationCharactersResponse
Section titled “QueryLocationCharactersResponse”QueryLocationCharactersResponse returns the lightweight character list.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | CharacterSummary | repeated | The characters present at the location, in store order. |
QueryLocationRequest
Section titled “QueryLocationRequest”QueryLocationRequest names the location to query by ULID.
| Field | Type | Label | Description |
|---|---|---|---|
| location_id | string | ULID of the location to fetch. |
QueryLocationResponse
Section titled “QueryLocationResponse”QueryLocationResponse returns the queried location’s fields.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the location. | |
| name | string | Display name of the location. | |
| description | string | Long-form description of the location. | |
| type | string | Location type string (e.g. the world.LocationType value). |
QueryObjectRequest
Section titled “QueryObjectRequest”QueryObjectRequest names the object to query by ULID.
| Field | Type | Label | Description |
|---|---|---|---|
| object_id | string | ULID of the object to fetch. |
QueryObjectResponse
Section titled “QueryObjectResponse”QueryObjectResponse returns the queried object’s fields, including its optional containment placement and owner.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the object. | |
| name | string | Display name of the object. | |
| description | string | Long-form description of the object. | |
| is_container | bool | Whether the object can contain other objects. | |
| location_id | string | ULID of the location holding the object; empty unless location-contained. | |
| held_by_character_id | string | ULID of the character holding the object; empty unless character-held. | |
| contained_in_object_id | string | ULID of the object containing this object; empty unless object-contained. | |
| owner_id | string | ULID of the object’s owner; empty when no owner is set. | |
| containment_type | string | Containment discriminator string (the Containment.Type() value). |
WorldMutationService
Section titled “WorldMutationService”WorldMutationService is the host-brokered write half of the world
capability: a plugin creates locations, exits, and objects through the host
world mutator under its plugin subject. Promotes the Lua
holomush.create_location / create_exit / create_object host functions
(internal/plugin/hostfunc/world_write.go) to the binary surface. Served by
worldMutationServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| CreateLocation | CreateLocationRequest | CreateLocationResponse | CreateLocation creates a location with the given name, description, and validated location type, mirroring the Lua holomush.create_location(name, description, type) host function (mutator.CreateLocation). Returns the new location’s id and name. |
| CreateExit | CreateExitRequest | CreateExitResponse | CreateExit creates an exit from one location to another, optionally bidirectional with a return name, mirroring the Lua holomush.create_exit(from_id, to_id, name, opts) host function (mutator.CreateExit). Returns the new exit’s id and name. |
| CreateObject | CreateObjectRequest | CreateObjectResponse | CreateObject creates an object with exactly one containment placement (location, holding character, or containing object) and optional description, mirroring the Lua holomush.create_object(name, opts) host function (mutator.CreateObject). Returns the new object’s id and name. |
WorldQueryService
Section titled “WorldQueryService”WorldQueryService is the host-brokered read half of the world capability:
a plugin queries locations, characters, objects, and finds locations by name
through the host world querier. Promotes the Lua holomush.query_* /
find_location host functions (internal/plugin/hostfunc/world.go,
world_write.go) to the binary surface. Served by worldQueryServer in
internal/plugin/goplugin/host_capability_servers.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| QueryLocation | QueryLocationRequest | QueryLocationResponse | QueryLocation returns a location’s identity, name, description, and type by ULID, mirroring the Lua holomush.query_location(location_id) host function (WorldQuerierAdapter.GetLocation). A missing location is reported as a sanitized not-found error. |
| QueryCharacter | QueryCharacterRequest | QueryCharacterResponse | QueryCharacter returns a character’s identity, player, name, description, and optional current location by ULID, mirroring the Lua holomush.query_character(character_id) host function (WorldQuerierAdapter.GetCharacter). |
| QueryLocationCharacters | QueryLocationCharactersRequest | QueryLocationCharactersResponse | QueryLocationCharacters returns the lightweight (id, name) set of characters at a location, with optional limit/offset pagination, mirroring the Lua holomush.query_location_characters(location_id, opts) host function (WorldQuerierAdapter.GetCharactersByLocation). |
| QueryObject | QueryObjectRequest | QueryObjectResponse | QueryObject returns an object’s identity, description, container flag, containment placement, and owner by ULID, mirroring the Lua holomush.query_object(object_id) host function (WorldQuerierAdapter.GetObject). |
| FindLocation | FindLocationRequest | FindLocationResponse | FindLocation resolves a location by name within the calling plugin’s subject scope, mirroring the Lua holomush.find_location(name) host function (worldMutator.FindLocationByName). Returns the matched location’s id and name, or a sanitized not-found error. |
holomush/plugin/v1/attribute.proto
Section titled “holomush/plugin/v1/attribute.proto”AttributeValue
Section titled “AttributeValue”AttributeValue is a discriminated union carrying the runtime value of a single resolved attribute. Exactly one kind field should be set; the variant chosen SHOULD match the AttributeType declared in GetSchemaResponse for the same attribute name.
| Field | Type | Label | Description |
|---|---|---|---|
| string_value | string | String value for ATTRIBUTE_TYPE_STRING attributes, e.g. owner ULID, state name, or visibility label. | |
| number_value | double | Floating-point value for ATTRIBUTE_TYPE_FLOAT attributes. Stored as double (float64) to cover the full range needed by numeric policy comparisons. | |
| bool_value | bool | Boolean value for ATTRIBUTE_TYPE_BOOL attributes, e.g. witness flags such as has_location. | |
| string_list_value | StringList | String-list value for ATTRIBUTE_TYPE_STRING_LIST attributes, e.g. participant character IDs or invitee lists. Use StringList rather than repeated fields to fit within the oneof constraint. |
GetSchemaRequest
Section titled “GetSchemaRequest”GetSchemaRequest carries no parameters. The plugin responds with the full schema for all resource types it owns; there is no per-type filtering at the protocol level.
GetSchemaResponse
Section titled “GetSchemaResponse”GetSchemaResponse describes the attribute schema for every resource type the plugin owns. The host validates that every resource type declared in the manifest’s resource_types list appears as a key here; a missing key causes plugin load to fail. The schema is used by the host to build a types.NamespaceSchema per resource type for the policy engine’s attribute registry.
| Field | Type | Label | Description |
|---|---|---|---|
| resource_types | GetSchemaResponse.ResourceTypesEntry | repeated | Attribute schema keyed by resource type name matching the manifest’s resource_types list (e.g., “scene”, “channel”). Each value describes the attributes resolvable for that type. |
GetSchemaResponse.ResourceTypesEntry
Section titled “GetSchemaResponse.ResourceTypesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | ResourceTypeSchema |
ResolveResourceRequest
Section titled “ResolveResourceRequest”ResolveResourceRequest identifies the single resource whose attributes the host needs for an in-flight ABAC policy evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| resource_type | string | The resource type to resolve, matching one of the keys in the plugin’s GetSchemaResponse. Must be a non-empty string; buf validate enforces min_len = 1. Plugins MUST reject types they do not own with INVALID_ARGUMENT. | |
| resource_id | string | The resource instance identifier, stripped of the “type:” prefix. Must be non-empty; buf validate enforces min_len = 1. For the scene plugin this is the raw scene ULID (e.g., “01JXYZ…”). Plugins SHOULD return NOT_FOUND when this ID is unknown. |
ResolveResourceResponse
Section titled “ResolveResourceResponse”ResolveResourceResponse carries the resolved attribute bag for the requested resource instance. The host converts this into a map[string]any via internal/plugin/attribute_proxy.go::convertProtoAttributes and passes it to the policy engine’s evaluator.
Optional attributes MUST be omitted from the map entirely when unresolved — do not include a key with an empty-string or zero value as a sentinel. The DSL evaluator treats absent keys as fail-safe false; a present empty-string value is NOT the same as absent and may match an unintended policy condition.
| Field | Type | Label | Description |
|---|---|---|---|
| attributes | ResolveResourceResponse.AttributesEntry | repeated | Resolved attribute values keyed by attribute name, matching the names declared in GetSchemaResponse for the requested resource type. Attributes whose values are unknown or not applicable for this resource instance MUST be omitted from the map rather than included with a zero or empty-string sentinel. |
ResolveResourceResponse.AttributesEntry
Section titled “ResolveResourceResponse.AttributesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | AttributeValue |
ResourceTypeSchema
Section titled “ResourceTypeSchema”ResourceTypeSchema describes all resolvable attributes for one resource type. Every attribute that ResolveResource may ever return for this type MUST appear here; attributes returned at resolution time but absent from the schema are ignored by the host’s policy engine.
| Field | Type | Label | Description |
|---|---|---|---|
| attributes | ResourceTypeSchema.AttributesEntry | repeated | Attribute names for this resource type, each mapped to its declared type. Names are dot-free strings (e.g., “owner”, “state”, “visibility”). The set MUST be a superset of all keys that ResolveResource can return for this resource type — schema and response MUST be consistent. |
ResourceTypeSchema.AttributesEntry
Section titled “ResourceTypeSchema.AttributesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | AttributeType |
StringList
Section titled “StringList”StringList wraps a repeated string so it can appear inside the AttributeValue.kind oneof. Proto3 does not allow repeated fields directly inside a oneof; this wrapper bridges that constraint.
| Field | Type | Label | Description |
|---|---|---|---|
| values | string | repeated | The string elements of the list. Order is preserved; the policy engine treats the list as an ordered sequence for membership checks. |
AttributeType
Section titled “AttributeType”AttributeType declares the type of a single attribute in ResourceTypeSchema. The host uses this declaration to build the policy engine’s type registry, which drives DSL type-checking and comparison semantics. Mismatches between declared and returned types produce DSL evaluation errors at runtime.
| Name | Number | Description |
|---|---|---|
| ATTRIBUTE_TYPE_UNSPECIFIED | 0 | Zero value sentinel. Attributes with UNSPECIFIED type are mapped to AttrTypeString by the host’s schema converter (internal/plugin/attribute_proxy.go::protoAttrTypeToAttrType). Prefer an explicit value in new schemas. |
| ATTRIBUTE_TYPE_STRING | 1 | String-typed attribute. Returned as AttributeValue.string_value in ResolveResourceResponse. Maps to types.AttrTypeString in the host’s policy engine. Used for identifier-like fields such as owner ULID, state enum names, and visibility labels. |
| ATTRIBUTE_TYPE_BOOL | 2 | Boolean-typed attribute. Returned as AttributeValue.bool_value. Maps to types.AttrTypeBool. Useful for binary state flags and witness attributes (has_location, is_archived). |
| ATTRIBUTE_TYPE_FLOAT | 3 | 64-bit floating-point numeric attribute. Returned as AttributeValue.number_value (double). Maps to types.AttrTypeFloat. Used for numeric comparisons in policies, e.g. vote tallies or capacity counts. |
| ATTRIBUTE_TYPE_STRING_LIST | 4 | Ordered list of strings. Returned as AttributeValue.string_list_value. Maps to types.AttrTypeStringList in the policy engine. Used for multi-value fields such as participant character IDs or tag sets. |
AttributeResolverService
Section titled “AttributeResolverService”AttributeResolverService lets binary plugins expose ABAC attribute resolution
to the host’s policy engine for resource types the plugin owns. The host
auto-registers this service name and calls each plugin that declares
resource_types in its manifest. Plugins MUST NOT list
holomush.plugin.v1.AttributeResolverService in their manifest provides:
field — doing so causes SERVICE_ALREADY_REGISTERED at startup. Declare
resource_types in the manifest instead; the host wires the gRPC client
automatically during plugin load (see internal/plugin/manager.go::discoverAndRegisterAttributes).
The interaction has two phases:
- Load-time schema discovery: the host calls GetSchema once after Init returns and registers a PluginAttributeProvider per declared resource type. If the schema does not cover every declared resource type, plugin load is rolled back (internal/plugin/manager.go::discoverAndRegisterAttributes).
- Per-request attribute resolution: the host calls ResolveResource during ABAC policy evaluation whenever a policy references an attribute for one of the plugin’s owned resource types. The call is made via the host’s PluginAttributeProvider proxy (internal/plugin/attribute_proxy.go::ResolveResource).
Reference implementation: plugins/core-scenes/resolver.go::SceneResolver.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetSchema | GetSchemaRequest | GetSchemaResponse | GetSchema returns the full attribute schema for every resource type this plugin owns. The host calls this exactly once per plugin load, after Init returns, and caches the result for the lifetime of the plugin process. The response MUST include an entry for every resource type declared in the manifest’s resource_types list — missing entries cause load to fail with a hard error and trigger plugin unload rollback. The schema MUST be deterministic across calls; the host does not re-query after the initial load. |
See: internal/plugin/manager.go::discoverAndRegisterAttributes (caller), plugins/core-scenes/resolver.go::GetSchema (reference implementation). | | ResolveResource | ResolveResourceRequest | ResolveResourceResponse | ResolveResource returns the current attribute values for a single resource instance identified by type and ID. The host calls this during ABAC policy evaluation when the active policy references an attribute belonging to one of the plugin’s owned resource types. It is invoked per authorization check, not cached. The plugin MUST reject resource_type values it does not own with INVALID_ARGUMENT so host-side misrouting is visible immediately. The plugin SHOULD return NOT_FOUND when the resource ID is unknown.
Optional attributes MUST be omitted from the response map rather than emitted with an empty-string or zero sentinel value. The DSL evaluator treats missing map keys as fail-safe false for every operator; an empty-string value would match any other unresolved empty-string peer and create a fail-open condition. See .claude/rules/abac-providers.md for the full contract.
See: internal/plugin/attribute_proxy.go::ResolveResource (caller), plugins/core-scenes/resolver.go::ResolveResource (reference implementation). |
holomush/plugin/v1/plugin.proto
Section titled “holomush/plugin/v1/plugin.proto”api/proto/holomush/plugin/v1/plugin.proto
AuditDecisionHint
Section titled “AuditDecisionHint”AuditDecisionHint is a partial audit event emitted by a plugin handler. The plugin provides decision-specific fields (id, name, message, effect, resource, attributes, action qualifier); the host stamps identity fields (subject from dispatch context, source = SourcePlugin, component = plugin name, timestamp, duration).
Plugins MUST NOT set subject, source, or component — the dispatcher overwrites those fields to prevent spoofing.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | Stable slug identifying the plugin’s internal rule, e.g., “not_member”. | |
| name | string | Human-readable label for the rule, e.g., “channels: not a member”. | |
| message | string | Per-firing description, e.g., “player not in channel members”. | |
| effect | AuditEffect | Effect the plugin decided. Closed enum at the proto boundary — unknown effects can never round-trip the wire. | |
| action_qualifier | string | Action qualifier appended to the dispatcher-known base action. E.g., the dispatcher knows the command is “channel”; the plugin supplies “speak”, producing final action “channel:speak”. | |
| resource | string | Resource reference in <type>:<id> form, e.g., “channel:01XYZ”. Plugin-provided, host-validated for shape. | |
| attributes | AuditDecisionHint.AttributesEntry | repeated | Plugin-provided context. Keys SHOULD be namespaced (e.g., “channel.type” rather than “type”) to avoid collision with host-overlay keys. |
AuditDecisionHint.AttributesEntry
Section titled “AuditDecisionHint.AttributesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
CommandRequest
Section titled “CommandRequest”CommandRequest carries the full dispatch context for a plugin command invocation. pluginServerAdapter.HandleCommand maps it to the SDK CommandRequest type.
| Field | Type | Label | Description |
|---|---|---|---|
| command | string | Parsed command verb the plugin registered (e.g. “say”, “dig”). | |
| args | string | Argument text following the verb (max 8 KiB). | |
| raw_input | string | The raw line the player typed, preserving aliases; surfaced to the SDK as InvokedAs (max 8 KiB). | |
| character_id | string | ULID of the character invoking the command. | |
| character_name | string | Display name of the invoking character. | |
| location_id | string | ULID of the invoking character’s current location. | |
| session_id | string | ULID of the active session the command was issued in. | |
| player_id | string | ULID of the player account behind the character. | |
| connection_id | string | Originating connection ULID (Phase 5). Empty for server-side dispatch paths that do not have a specific connection (e.g., non-gateway callers). |
CommandResponse
Section titled “CommandResponse”CommandResponse is the result of a plugin command execution returned from HandleCommand.
| Field | Type | Label | Description |
|---|---|---|---|
| status | CommandStatus | Outcome category of the command. | |
| output | string | Synchronous text shown to the invoking player (max 8 KiB). | |
| events | EmitEvent | repeated | Events the command wants emitted; routed through the host emit fence like HandleEvent emits. |
| audit_hints | AuditDecisionHint | repeated | Audit decision hints accumulated by the plugin handler during this command dispatch. The dispatcher extracts these after the response is returned, stamps host-controlled fields (subject, action base, source, component, timestamp, duration), and flushes them through the audit logger. |
EmitEvent
Section titled “EmitEvent”EmitEvent is one event a plugin wants to emit, returned from HandleEvent / HandleCommand (the proto mirror of pkg/plugin.EmitEvent). It is NOT published directly — the host routes it through the PluginEventEmitter.Emit fence.
| Field | Type | Label | Description |
|---|---|---|---|
| stream | string | Target stream the event is published to (legacy “prefix:id” form). | |
| type | string | Event-type discriminator for the emitted event; gated by the manifest’s emits / crypto.emits declarations at the fence. | |
| payload | string | JSON-encoded payload (max 64 KiB); validated as well-formed JSON at the fence before publish. | |
| sensitive | bool | Per-event sensitivity claim for a return-value emit, validated against the plugin manifest by event_emitter.go::Emit via EnforceSensitivity (internal/plugin/sensitivity_fence.go) — INV-PLUGIN-29: a sensitivity=never manifest rejects true; INV-PLUGIN-30: a sensitivity=always manifest rejects false. Carries the same semantics as the active EmitEvent RPC’s sensitive field so a binary plugin’s return-value emit cannot silently downgrade to plaintext where the Lua runtime would encrypt (holomush-av954). Default false for backward compatibility. |
Event is the host→plugin delivery shape for one game event (the proto mirror of pkg/plugin.Event). pluginServerAdapter.HandleEvent converts it to the SDK Event before invoking the author’s handler.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID string uniquely identifying the event; also its bus dedup key. | |
| stream | string | Source stream this event belongs to, in legacy “prefix:id” form (e.g. “location:loc_abc123”). Translated to the dot-delimited NATS subject at the emit boundary. | |
| type | string | Event-type discriminator (e.g. “say”, “pose”, “arrive”, “leave”, “system”) that the plugin handler switches on. | |
| timestamp | int64 | Event occurrence time in Unix milliseconds (host clock). | |
| actor_kind | string | Actor kind as a string (e.g. “character”, “system”, “plugin”). Carried as a string rather than an enum for forward-compat; the SDK maps it to its ActorKind type. Distinct from the bus-internal ActorKind enum. | |
| actor_id | string | ULID of the actor that caused the event (the character/system/plugin id). | |
| payload | string | JSON-encoded event payload (max 64 KiB); the plugin decodes it per the event type’s schema. | |
| cursor | bytes | cursor is the opaque pagination token for this event. Pass as the QueryStreamHistory request cursor on the next call to page backward from this position. Empty on events received via delivery (not history). Treat as an opaque blob. |
HandleCommandRequest
Section titled “HandleCommandRequest”HandleCommandRequest wraps a command dispatch for the PluginService HandleCommand call.
| Field | Type | Label | Description |
|---|---|---|---|
| command | CommandRequest | The command (verb, args, and dispatch context) to handle. |
HandleCommandResponse
Section titled “HandleCommandResponse”HandleCommandResponse wraps the plugin’s command result.
| Field | Type | Label | Description |
|---|---|---|---|
| response | CommandResponse | The command outcome (status, output, response emits, audit hints). |
HandleEventRequest
Section titled “HandleEventRequest”HandleEventRequest wraps a single delivered event for the PluginService HandleEvent call.
| Field | Type | Label | Description |
|---|---|---|---|
| event | Event | The event being delivered to the plugin handler. |
HandleEventResponse
Section titled “HandleEventResponse”HandleEventResponse returns the events the plugin chose to emit in reaction to the delivered event.
| Field | Type | Label | Description |
|---|---|---|---|
| emit_events | EmitEvent | repeated | Events the plugin wants emitted; each is run through the host emit fence. |
InitRequest
Section titled “InitRequest”InitRequest is the host’s first call to a freshly connected plugin process.
| Field | Type | Label | Description |
|---|---|---|---|
| config | ServiceConfig | The initialization payload (DSN, required-service addresses, runtime config) the plugin needs before it can serve events or commands. |
InitResponse
Section titled “InitResponse”InitResponse is the plugin’s reply to Init, advertising what it serves and what it may emit.
| Field | Type | Label | Description |
|---|---|---|---|
| provided_services | string | repeated | gRPC service names this plugin implements on the go-plugin transport, so the host’s service registry can route requires→provides between plugins. |
| registered_emit_types | string | repeated | Set of plugin-owned event types this plugin may emit. Host validates set-equality against manifest’s crypto.emits per INV-PLUGIN-32. Plugins without crypto.emits leave empty and skip validation; plugins WITH crypto.emits MUST populate (mismatch fails load). |
QuerySessionStreamsRequest
Section titled “QuerySessionStreamsRequest”QuerySessionStreamsRequest provides session context for stream contribution.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | Identifier of the character entering the session. | |
| player_id | string | Identifier of the player owning the character. | |
| session_id | string | Session identifier. |
QuerySessionStreamsResponse
Section titled “QuerySessionStreamsResponse”QuerySessionStreamsResponse returns stream names and an optional error.
| Field | Type | Label | Description |
|---|---|---|---|
| streams | string | repeated | Stream names the plugin wants added to this session’s subscription. |
| error | string | Non-empty indicates a plugin-reported error. Host degrades (logs + skips). |
ServiceConfig
Section titled “ServiceConfig”ServiceConfig carries the host→plugin initialization payload delivered in InitRequest. It is consumed by pluginServerAdapter.Init / the provider’s own Init.
| Field | Type | Label | Description |
|---|---|---|---|
| connection_string | string | PostgreSQL DSN the plugin uses for its own storage. Populated only when the plugin declares storage: postgres in its manifest; empty otherwise. | |
| required_services | ServiceConfig.RequiredServicesEntry | repeated | Network addresses of the proto services the plugin declared in requires, keyed by service name, for the plugin to dial. (Reserved for future service-to-service wiring.) |
| plugin_config | ServiceConfig.PluginConfigEntry | repeated | Opaque plugin-owned runtime config: the effective (manifest-default < server-override) map the host delivers at init. The host does NOT interpret keys/values; the plugin decodes them per its own schema. |
| declared_capabilities | string | repeated | Capability tokens the plugin declared in its manifest requires: (manifest.RequiredCapabilities()). The plugin SDK validates, at Init, that every non-exempt host capability its code can consume (via an implemented *Aware interface) appears here, failing load otherwise (INV-PLUGIN-54). |
ServiceConfig.PluginConfigEntry
Section titled “ServiceConfig.PluginConfigEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
ServiceConfig.RequiredServicesEntry
Section titled “ServiceConfig.RequiredServicesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
AuditEffect
Section titled “AuditEffect”AuditEffect is the closed set of decision outcomes a plugin handler may emit through an AuditDecisionHint. Plugin denials and plugin allows are the only meaningful outcomes — engine-specific effects (default_deny, system_bypass) are not exposed to plugins because plugins do not produce those decisions.
| Name | Number | Description |
|---|---|---|
| AUDIT_EFFECT_UNSPECIFIED | 0 | Zero value; never a meaningful plugin decision. |
| AUDIT_EFFECT_DENY | 1 | The plugin denied the action — the hint records why. |
| AUDIT_EFFECT_ALLOW | 2 | The plugin allowed the action. |
CommandStatus
Section titled “CommandStatus”CommandStatus is the outcome category of a plugin command, mirroring pkg/plugin.CommandStatus.
| Name | Number | Description |
|---|---|---|
| COMMAND_STATUS_UNSPECIFIED | 0 | Zero value; a well-formed CommandResponse sets a concrete status. |
| COMMAND_STATUS_OK | 1 | The command succeeded. |
| COMMAND_STATUS_ERROR | 2 | The command failed in a recoverable, user-facing way (e.g. bad arguments). |
| COMMAND_STATUS_FAILURE | 3 | The command could not complete due to a runtime failure short of fatal. |
| COMMAND_STATUS_FATAL | 4 | The command hit an unrecoverable condition. |
SettingScope
Section titled “SettingScope”SettingScope selects which settings scope a Get/Set targets. There is no chained mode — callers compose scopes themselves (e.g. a CW-block union read across game+player+character). The iokti.7 handler maps each value to its backing store and rejects the unspecified value (fail closed).
| Name | Number | Description |
|---|---|---|
| SETTING_SCOPE_UNSPECIFIED | 0 | Unspecified scope — rejected by the handler (fail closed). |
| SETTING_SCOPE_GAME | 1 | Server-wide scope, backed by holomush_system_info. |
| SETTING_SCOPE_PLAYER | 2 | Per-player scope, backed by players.preferences. |
| SETTING_SCOPE_CHARACTER | 3 | Per-character scope, backed by characters.preferences. |
PluginService
Section titled “PluginService”PluginService is the plugin-IMPLEMENTED half of the binary-plugin contract: the gRPC server runs inside the plugin subprocess (hashicorp/go-plugin) and the host dials it. The host adapter is pkg/plugin.pluginServerAdapter, which bridges these RPCs to a plugin author’s Go handler. The host-implemented callback surface plugins dial in return is the per-capability holomush.plugin.host.v1 services (api/proto/holomush/plugin/host/v1).
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Init | InitRequest | InitResponse | Init is the first call the host makes after the go-plugin handshake. It hands the plugin its ServiceConfig (DB connection string, required-service addresses, opaque runtime config) and returns the gRPC service names the plugin provides plus the emit-type set the host validates against the manifest’s crypto.emits (INV-PLUGIN-32). Bridged by pluginServerAdapter.Init, which also lazily dials the plugin-host connection for any host-facing facade (sink/focus/evaluator/decryptor) the provider opts into. |
| HandleEvent | HandleEventRequest | HandleEventResponse | HandleEvent delivers one subscribed event to the plugin and collects the events the plugin wants to emit in response. Bridged by pluginServerAdapter.HandleEvent, which converts the proto Event to the SDK Event type, invokes the author’s handler, and converts returned EmitEvents back to the wire. Response emits flow through the host emit fence, not straight to the bus. |
| HandleCommand | HandleCommandRequest | HandleCommandResponse | HandleCommand delivers a parsed player command to the plugin and returns the command result (output text, status, response emits, and audit hints). Bridged by pluginServerAdapter.HandleCommand; if the plugin registered no command handler the adapter returns an empty response rather than erroring. |
| QuerySessionStreams | QuerySessionStreamsRequest | QuerySessionStreamsResponse | QuerySessionStreams asks the plugin which stream names it wants subscribed for a session being established, before LISTEN/subscription setup. Called exactly once at session establishment, and only for plugins that declare session_streams: true in their manifest. A plugin-reported error degrades gracefully (the host logs and skips that plugin’s contribution). |
holomush/scene/v1/scene.proto
Section titled “holomush/scene/v1/scene.proto”CastPublishSceneVoteRequest
Section titled “CastPublishSceneVoteRequest”CastPublishSceneVoteRequest records a roster member’s vote on a specific publication attempt (keyed by published_scene_id, unlike the legacy scene-keyed CastPublishVoteRequest).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The voting character, who MUST be on the attempt’s frozen roster; required. | |
| published_scene_id | string | The publication attempt being voted on; required. | |
| vote | bool | The yes (true) / no (false) ballot. |
CastPublishSceneVoteResponse
Section titled “CastPublishSceneVoteResponse”CastPublishSceneVoteResponse acknowledges a recorded vote.
| Field | Type | Label | Description |
|---|---|---|---|
| is_change | bool | True only when this cast flipped a previously cast, differing vote; the first cast and a re-affirmation of the same value both report false. |
CastPublishVoteRequest
Section titled “CastPublishVoteRequest”CastPublishVoteRequest is the legacy (unserved) scene-keyed publish-vote request, superseded by CastPublishSceneVoteRequest. Retained for the unserved CastPublishVote RPC’s contract.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The voting character; required. | |
| scene_id | string | The scene being voted on; required. | |
| vote | bool | The yes (true) / no (false) ballot. |
CastPublishVoteResponse
Section titled “CastPublishVoteResponse”CastPublishVoteResponse is the legacy (unserved) publish-vote acknowledgment.
CharacterSceneInfo
Section titled “CharacterSceneInfo”CharacterSceneInfo pairs a scene with the requesting character’s role and recent-activity metadata for workspace badge rendering.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The scene’s metadata projection via the standard row conversion; the roster fields (participants, observers) are unset on this surface — workspace clients fetch rosters via GetScene/scene-load paths. | |
| role | string | This character’s participant role in the scene (owner/member/observer). | |
| last_activity_ms | int64 | Epoch-ms timestamp of the newest scene_log row on the scene’s IC subject; 0 when the log is empty. | |
| entry_count | int64 | Total scene_log rows on the IC subject (workspace activity panel). |
CreateSceneRequest
Section titled “CreateSceneRequest”CreateSceneRequest is the new-scene definition. The calling character becomes the owner. Empty visibility/pose-order default to “open”/“free” at the handler; whitespace-only titles are rejected after trimming.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The creating character, who becomes the scene owner; required. | |
| title | string | Scene title; required, 1-200 chars (whitespace-only also rejected by the handler’s post-trim check). | |
| description | string | Optional synopsis, up to 4096 chars. | |
| location_id | string | Optional world location to anchor the scene to. | |
| visibility | string | Discoverability mode; empty selects the “open” default. Constrained to "" | |
| pose_order_mode | string | Pose-order discipline; empty selects the “free” default. Constrained to "" | |
| tags | string | repeated | Discovery tags, max 32. |
| content_warnings | string | repeated | Content advisories, max 32. |
CreateSceneResponse
Section titled “CreateSceneResponse”CreateSceneResponse carries the freshly created scene (active, owner seeded).
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The new scene’s projection. |
DownloadPublicSceneArchiveRequest
Section titled “DownloadPublicSceneArchiveRequest”DownloadPublicSceneArchiveRequest fetches a published scene rendered to a file format WITHOUT authentication (status==PUBLISHED gate only).
| Field | Type | Label | Description |
|---|---|---|---|
| published_scene_id | string | The publication attempt to download; required. Same opacity contract as GetPublicSceneArchiveRequest. | |
| format | string | The render format; required. Supported: “markdown”, “plain_text”, “jsonl”. |
DownloadPublicSceneArchiveResponse
Section titled “DownloadPublicSceneArchiveResponse”DownloadPublicSceneArchiveResponse carries the rendered public-archive bytes and their MIME type.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | The rendered file content. | |
| mime_type | string | The content’s MIME type. |
DownloadPublishedSceneRequest
Section titled “DownloadPublishedSceneRequest”DownloadPublishedSceneRequest fetches a PUBLISHED attempt rendered to a file format, as a participant (participant-gated, INV-SCENE-60).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The downloading character, who MUST be a participant; required. | |
| published_scene_id | string | The PUBLISHED attempt to download; required. | |
| format | string | The render format; required. Supported: “markdown”, “plain_text”, “jsonl” (publishRenderMime in publish_service.go); any other value is rejected. |
DownloadPublishedSceneResponse
Section titled “DownloadPublishedSceneResponse”DownloadPublishedSceneResponse carries the rendered scene bytes and their MIME type.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | The rendered file content. | |
| mime_type | string | The content’s MIME type (text/markdown, text/plain, or application/jsonl). |
EndSceneRequest
Section titled “EndSceneRequest”EndSceneRequest identifies the scene to end and the acting character (the owner, per the ABAC end-own-scene policy).
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character’s ID; required. | |
| scene_id | string | The scene to end; required. |
EndSceneResponse
Section titled “EndSceneResponse”EndSceneResponse carries the scene as of the ended transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The post-transition scene row (state == “ended”). |
ExportSceneLogRequest
Section titled “ExportSceneLogRequest”ExportSceneLogRequest asks for a scene’s IC log rendered to a downloadable document. Participant-gated in plugin code (any role); see ExportSceneLog.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The exporting character; required; must hold a participant row (any role). | |
| scene_id | string | The scene to export; required. Works for active, paused, and ended scenes. | |
| format | string | The render format; required: “markdown” or “jsonl”. |
ExportSceneLogResponse
Section titled “ExportSceneLogResponse”ExportSceneLogResponse carries the rendered scene-log document and download metadata.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | The rendered document bytes. | |
| mime_type | string | The content’s MIME type (text/markdown or application/jsonl) — mirrors DownloadPublishedScene’s MIME vocabulary. | |
| filename | string | Suggested download filename (slugified title + extension). |
ExtendScenePublishVoteAttemptsRequest
Section titled “ExtendScenePublishVoteAttemptsRequest”ExtendScenePublishVoteAttemptsRequest raises a scene’s publish-attempt budget (admin-only, ABAC-gated at dispatch).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The acting admin’s character ID; required (admin authority is ABAC-gated, not checked in-plugin). | |
| scene_id | string | The scene whose budget to raise; required. | |
| additional | int32 | How many additional attempts to grant; MUST be positive. |
ExtendScenePublishVoteAttemptsResponse
Section titled “ExtendScenePublishVoteAttemptsResponse”ExtendScenePublishVoteAttemptsResponse reports the scene’s new attempt budget.
| Field | Type | Label | Description |
|---|---|---|---|
| new_max | int32 | The scene’s max-publish-attempts budget after the extension. |
GetPoseOrderRequest
Section titled “GetPoseOrderRequest”GetPoseOrderRequest identifies the scene whose pose order is requested and the requesting character (who MUST be a participant; the gate is plugin-code, not ABAC).
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The requesting character; MUST be an owner or member of the scene. | |
| scene_id | string | The scene to compute pose order for; required. |
GetPoseOrderResponse
Section titled “GetPoseOrderResponse”GetPoseOrderResponse carries the scene’s pose-order mode and the computed per-participant standings.
| Field | Type | Label | Description |
|---|---|---|---|
| mode | string | The scene’s pose-order mode: “strict”, “3pr”, “5pr”, or “free”. | |
| total_pose_count | uint32 | Total poses recorded in the scene (the rolling denominator for the poses_since_last gaps). | |
| entries | PoseOrderEntry | repeated | Per-participant pose-order standings. |
GetPublicSceneArchiveRequest
Section titled “GetPublicSceneArchiveRequest”GetPublicSceneArchiveRequest reads a published scene WITHOUT authentication. No caller identity is required — the only gate is status==PUBLISHED.
| Field | Type | Label | Description |
|---|---|---|---|
| published_scene_id | string | The publication attempt to read; required. A missing id or any non-PUBLISHED attempt returns one opaque NOT_FOUND (INV-SCENE-35). |
GetPublicSceneArchiveResponse
Section titled “GetPublicSceneArchiveResponse”GetPublicSceneArchiveResponse is the public-safe view of a published scene — only the published artifact, never vote state, per-voter data, or failure_reason (the §5.1 two-pair separation).
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The publication attempt’s ID. | |
| title_snapshot | string | The scene title snapshotted at publish time. | |
| participants_snapshot | string | repeated | The participant character names snapshotted at publish time. |
| content_entries | PublishedSceneEntry | repeated | The frozen published content. |
| published_at_unix_ns | int64 | Epoch-nanosecond publish time. |
GetPublishedSceneRequest
Section titled “GetPublishedSceneRequest”GetPublishedSceneRequest reads a publication attempt’s full state as a scene participant (participant-gated, INV-SCENE-60).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The reading character, who MUST be a participant of the scene; required. | |
| published_scene_id | string | The publication attempt to read; required. |
GetPublishedSceneResponse
Section titled “GetPublishedSceneResponse”GetPublishedSceneResponse is the participant-visible view of a publication attempt: its state-machine status, vote tally, snapshots, lifecycle timestamps, and (only when PUBLISHED) its frozen content.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The publication attempt’s ID. | |
| scene_id | string | The scene this attempt belongs to. | |
| attempt_number | int32 | The attempt’s ordinal within the scene’s budget (1-based). | |
| status | string | State-machine status: “COLLECTING”, “COOLOFF”, “PUBLISHED”, or “ATTEMPT_FAILED” (PublishedSceneStatus in publish_types.go). | |
| failure_reason | string | The failure cause; empty unless status is ATTEMPT_FAILED. One of ANY_NO, TIMEOUT, WITHDRAWN, SNAPSHOT_DECRYPT_FAILED, SNAPSHOT_RENDER_FAILED, or COOLOFF_INVARIANT_BROKEN (PublishFailureReason in publish_types.go). | |
| tally | PublishedSceneVoteSummary | The current yes/no/pending vote tally. | |
| content_entries | PublishedSceneEntry | repeated | The frozen published content; populated ONLY when status is PUBLISHED. |
| title_snapshot | string | The scene title snapshotted at publish time. | |
| participants_snapshot | string | repeated | The participant character names snapshotted at publish time. |
| initiated_at_unix_ns | int64 | Epoch-nanosecond time the attempt was opened. | |
| cooloff_started_at_unix_ns | int64 | Epoch-nanosecond time the cool-off window began; 0 if cool-off never started. | |
| resolved_at_unix_ns | int64 | Epoch-nanosecond time the attempt reached a terminal status; 0 if still active. | |
| published_at_unix_ns | int64 | Epoch-nanosecond time the attempt was published; 0 unless PUBLISHED. |
GetSceneRequest
Section titled “GetSceneRequest”GetSceneRequest identifies the scene to read and the character reading it (the latter scoping the host’s ABAC read policy).
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The reading character’s ID; required. | |
| scene_id | string | The scene to load; required. |
GetSceneResponse
Section titled “GetSceneResponse”GetSceneResponse carries the requested scene’s full projection.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The loaded scene; unset is never returned (a miss is codes.NotFound). |
InviteToSceneRequest
Section titled “InviteToSceneRequest”InviteToSceneRequest identifies the inviting owner, the scene, and the character being granted an invitation.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The inviting character (the owner per ABAC); required. | |
| scene_id | string | The scene to invite into; required. | |
| target_character_id | string | The character receiving the invitation; required. |
InviteToSceneResponse
Section titled “InviteToSceneResponse”InviteToSceneResponse is intentionally empty — a successful invite carries no body.
JoinSceneRequest
Section titled “JoinSceneRequest”JoinSceneRequest identifies the scene to join and the joining character.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The joining character’s ID; required. | |
| scene_id | string | The scene to join; required. |
JoinSceneResponse
Section titled “JoinSceneResponse”JoinSceneResponse is intentionally empty — a successful join carries no body; the caller refetches scene state if needed.
KickFromSceneRequest
Section titled “KickFromSceneRequest”KickFromSceneRequest identifies the acting owner, the scene, and the target character to remove.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character (the owner per ABAC); required. | |
| scene_id | string | The scene to remove the target from; required. | |
| target_character_id | string | The character to remove (must not be the owner); required. |
KickFromSceneResponse
Section titled “KickFromSceneResponse”KickFromSceneResponse is intentionally empty — a successful kick carries no body.
LeaveSceneRequest
Section titled “LeaveSceneRequest”LeaveSceneRequest identifies the scene to leave and the leaving character.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The leaving character’s ID; required (must not be the owner). | |
| scene_id | string | The scene to leave; required. |
LeaveSceneResponse
Section titled “LeaveSceneResponse”LeaveSceneResponse is intentionally empty — a successful leave carries no body.
ListCharacterScenesRequest
Section titled “ListCharacterScenesRequest”ListCharacterScenesRequest requests the non-archived scene participations for a single character.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The character whose participations to list; required (host-trusted). |
ListCharacterScenesResponse
Section titled “ListCharacterScenesResponse”ListCharacterScenesResponse carries the character’s scene participations, most recently active first.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | CharacterSceneInfo | repeated | The character’s scenes, most recently active first. |
ListPublishedScenesRequest
Section titled “ListPublishedScenesRequest”ListPublishedScenesRequest pages through PUBLISHED scene archives.
| Field | Type | Label | Description |
|---|---|---|---|
| limit | int32 | Page size; 0 means server default, capped at 200 (mirrors ListScenes). | |
| offset | int32 | Leading results to skip. | |
| tags | string | repeated | Restrict to archives whose scene carries all of these tags. |
ListPublishedScenesResponse
Section titled “ListPublishedScenesResponse”ListPublishedScenesResponse carries the public-safe archive summaries, newest first.
| Field | Type | Label | Description |
|---|---|---|---|
| archives | PublicSceneArchive | repeated | Public-safe published-archive summaries, newest first. |
ListScenePublishAttemptsRequest
Section titled “ListScenePublishAttemptsRequest”ListScenePublishAttemptsRequest lists a scene’s publication attempts as a participant (participant-gated, INV-SCENE-60).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The reading character, who MUST be a participant; required. | |
| scene_id | string | The scene whose attempts to list; required. |
ListScenePublishAttemptsResponse
Section titled “ListScenePublishAttemptsResponse”ListScenePublishAttemptsResponse carries the attempt summaries (header only, no content), ordered by attempt number.
| Field | Type | Label | Description |
|---|---|---|---|
| attempts | PublishedSceneSummary | repeated | The scene’s publication attempts. |
ListScenesRequest
Section titled “ListScenesRequest”ListScenesRequest is the scene-board discovery query. It supports pagination, tag filtering, and content-warning exclusion via the union of scope-based cw_block settings, served by SceneServiceImpl.ListScenes.
| Field | Type | Label | Description |
|---|---|---|---|
| limit | int32 | Maximum scenes to return; 0 means server default, capped at 200. | |
| offset | int32 | Number of leading results to skip for pagination. | |
| tags | string | repeated | Restrict results to scenes carrying all of these tags. |
| exclude_content_warnings | string | repeated | Extra content-warning categories to hide for this one query, on top of the caller’s stored block. The board query unions these with the game/player/character content.cw_block lists and drops any scene tagged with a blocked category. Applied server-side so the page limit/offset stay correct after filtering. |
| character_id | string | Acting character ULID. The board query reads this principal’s character-scope content.cw_block to assemble the effective block set. Optional — omit to browse without character-scope CW filtering. | |
| player_id | string | Owning player ULID. The board query reads this principal’s player-scope content.cw_block, which applies across all of the player’s characters, into the effective block set. Optional — omit to browse without player-scope CW filtering. |
ListScenesResponse
Section titled “ListScenesResponse”ListScenesResponse is the scene-board discovery result page.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | SceneInfo | repeated | The matching scenes for this page. |
ParticipantInfo
Section titled “ParticipantInfo”ParticipantInfo is one entry in a scene’s roster — a character’s relationship to the scene at read time.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | Character ID of the participant. | |
| character_name | string | Display name of the character (best-effort; falls back to the ID when no name resolver is wired). | |
| role | string | Participant role: “owner”, “member”, “observer”, or the transient “invited” (the last exists only on private scenes and is promoted to member on join). See ParticipantRole in participants.go. | |
| joined_at | google.protobuf.Timestamp | When the participant joined (for invited rows, when the invitation was recorded; reset to join time on promotion). |
PauseSceneRequest
Section titled “PauseSceneRequest”PauseSceneRequest identifies the scene to pause and the acting owner.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character’s ID; required. | |
| scene_id | string | The scene to pause; required. |
PauseSceneResponse
Section titled “PauseSceneResponse”PauseSceneResponse carries the scene as of the paused transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The post-transition scene row (state == “paused”). |
PoseOrderEntry
Section titled “PoseOrderEntry”PoseOrderEntry is one participant’s standing in the computed pose order, produced by poseorder.go::Compute.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The participant’s character ID. | |
| character_name | string | The participant’s display name (falls back to the character ID when no name resolver is wired). | |
| eligible | bool | Whether this participant is currently eligible to pose under the scene’s pose-order mode. | |
| last_posed_at | google.protobuf.Timestamp | When the participant last posed in this scene; unset if they never have. | |
| poses_since_last | uint32 | optional | Count of poses by other characters since this participant’s last pose (or since scene start if never posed). Meaningful for 3pr/5pr modes. |
PublicSceneArchive
Section titled “PublicSceneArchive”PublicSceneArchive is the public-safe view of a published scene archive, carrying only the published artifact — never vote state, per-voter data, or failure_reason. Covers the GetPublicSceneArchive field set (INV-SCENE-35 status gate) plus current scene tags, which that response does not carry. Used as the element type in ListPublishedScenes.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The publication attempt’s ID. | |
| title_snapshot | string | The scene title snapshotted at publish time. | |
| participants_snapshot | string | repeated | The participant character names snapshotted at publish time. |
| content_entries | PublishedSceneEntry | repeated | The frozen published content entries. |
| published_at_unix_ns | int64 | Epoch-nanosecond publish time. | |
| tags | string | repeated | The CURRENT tags on the source scene row (read at list time, not snapshotted at publish): post-publish tag edits are reflected here. Tags are public metadata (already exposed by ListScenes). |
PublishedSceneEntry
Section titled “PublishedSceneEntry”PublishedSceneEntry is one rendered line of a published scene’s frozen content. Only IC pose/say/emit content survives into a published scene; OOC and ops events are excluded (EntryKind in publish_types.go).
| Field | Type | Label | Description |
|---|---|---|---|
| speaker | string | The speaking character’s display label for this line. | |
| kind | string | The content kind: “pose”, “say”, or “emit”. | |
| content | string | The rendered line content. |
PublishedSceneSummary
Section titled “PublishedSceneSummary”PublishedSceneSummary is the content-free header view of one publication attempt, used in the audit list.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The attempt’s ID. | |
| attempt_number | int32 | The attempt’s ordinal within the scene’s budget (1-based). | |
| status | string | State-machine status (COLLECTING/COOLOFF/PUBLISHED/ATTEMPT_FAILED). | |
| failure_reason | string | Failure cause; empty unless status is ATTEMPT_FAILED. | |
| initiated_at_unix_ns | int64 | Epoch-nanosecond time the attempt was opened. | |
| resolved_at_unix_ns | int64 | Epoch-nanosecond time the attempt resolved; 0 if still active. |
PublishedSceneVoteSummary
Section titled “PublishedSceneVoteSummary”PublishedSceneVoteSummary is the yes/no/pending tally across a publication attempt’s frozen roster (VoteTally in publish_store.go). Pending counts roster members who have not yet cast.
| Field | Type | Label | Description |
|---|---|---|---|
| yes | int32 | Number of yes votes cast. | |
| no | int32 | Number of no votes cast. | |
| pending | int32 | Number of roster members who have not yet voted. |
ResumeSceneRequest
Section titled “ResumeSceneRequest”ResumeSceneRequest identifies the scene to resume and the acting owner.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting character’s ID; required. | |
| scene_id | string | The scene to resume; required. |
ResumeSceneResponse
Section titled “ResumeSceneResponse”ResumeSceneResponse carries the scene as of the resumed transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The post-transition scene row (state == “active”). |
SceneInfo
Section titled “SceneInfo”SceneInfo is the wire projection of a scene row plus its roster, returned by the read and lifecycle RPCs (rowToProto in service.go). The state, pose_order_mode, and visibility fields are the plugin’s lowercase string enums, not proto enums.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | Scene primary key (“scene-<ULID>”), stable for the scene’s lifetime. | |
| title | string | Human-facing scene name; trimmed of surrounding whitespace at creation. | |
| description | string | Optional free-text scene synopsis (up to 4096 chars). | |
| location_id | string | Optional world location the scene is anchored to; empty when unanchored. | |
| owner_id | string | Character ID of the current owner — the sole authority for end/pause/ resume/update/invite/kick and the only member who cannot leave. | |
| state | string | Lifecycle state: one of “active”, “paused”, “ended”, or “archived” (see SceneState in types.go). Transitions are forward-only. | |
| pose_order_mode | string | Pose-order discipline: one of “free”, “strict”, “3pr”, or “5pr” (see PoseOrderMode in types.go); governs GetPoseOrder eligibility computation. | |
| content_warnings | string | repeated | Operator-facing content advisories for the scene (max 32 entries). |
| tags | string | repeated | Discovery/categorization tags (max 32 entries). |
| visibility | string | Discoverability mode: “open” (listed, any character may join) or “private” (unlisted, invitation required). See SceneVisibility in types.go. | |
| created_at | google.protobuf.Timestamp | Wall-clock creation time. For CreateScene responses this is the host clock at create; for GetScene it is the persisted row timestamp. | |
| ended_at | google.protobuf.Timestamp | Set only once the scene has reached the ended state; otherwise unset. | |
| participants | ParticipantInfo | repeated | The current participant roster (owners and members; invited rows are not surfaced as participants here). |
| observers | ParticipantInfo | repeated | The watching (role=observer) participants, listed separately from the acting roster and excluded from pose order and publish votes (INV-SCENE-61). Not yet populated by any RPC; the scene-watch read path (E9.5 Task 3+) fills it from the store’s observers query. |
| last_activity_ms | int64 | Epoch-milliseconds of the newest scene_log row on the scene’s IC subject (events.<gameID>.scene.<sceneID>.ic). 0 when the log is empty or the read path does not compute the value (e.g. GetScene). Mirrors CharacterSceneInfo.last_activity_ms. | |
| active_publish_attempt_id | string | The in-flight publication attempt’s ID, or empty when no attempt is active. Populated by GetScene from activeAttemptID (commands.go) over the scene’s published_scenes rows. Active-only: reflects an attempt in COLLECTING or COOLOFF and clears once the attempt resolves. The portal uses it to gate the publish panel’s Start-vs-in-progress affordance and to key the participant-gated tally read (GetPublishedScene). Carries NO tally (SceneInfo is broadly readable; INV-SCENE-60/61). | |
| publish_status | string | The active attempt’s state-machine phase (“COLLECTING” or “COOLOFF”), or empty when no attempt is active. Non-sensitive phase signal; the counts stay behind GetPublishedScene’s participant gate. |
ScenePublishCoolOffStartedEvent
Section titled “ScenePublishCoolOffStartedEvent”ScenePublishCoolOffStartedEvent announces that an attempt entered the cool-off window (all roster members voted yes). Emitted as scene_publish_cooloff_started.
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_id | string | The publication attempt’s ID. | |
| cooloff_ends_at_unix_ns | int64 | Epoch-nanosecond deadline at which cool-off ends (derived from the persisted cool-off start plus the window, for retry determinism). |
ScenePublishResolvedEvent
Section titled “ScenePublishResolvedEvent”ScenePublishResolvedEvent announces that an attempt reached a terminal status (PUBLISHED or ATTEMPT_FAILED). Emitted as scene_publish_resolved.
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_id | string | The publication attempt’s ID. | |
| outcome | string | The terminal outcome: “PUBLISHED” or “ATTEMPT_FAILED”. | |
| failure_reason | string | The failure cause; empty unless outcome is ATTEMPT_FAILED. | |
| tally_yes | int32 | Final yes-vote count. | |
| tally_no | int32 | Final no-vote count. | |
| tally_pending | int32 | Final pending (never-cast) count. |
ScenePublishStartedEvent
Section titled “ScenePublishStartedEvent”ScenePublishStartedEvent announces a newly opened publication attempt and its frozen vote roster. Emitted as scene_publish_started.
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_id | string | The publication attempt’s ID. | |
| attempt_number | int32 | The attempt’s ordinal within the scene’s budget (1-based). | |
| initiated_by | string | Character ID that initiated the attempt. | |
| vote_window_seconds | int64 | The voting-window duration in seconds. | |
| cooloff_window_seconds | int64 | The cool-off-window duration in seconds. | |
| roster_character_ids | string | repeated | The frozen voter roster (character IDs eligible to vote on this attempt). |
ScenePublishVoteAttemptsExtendedEvent
Section titled “ScenePublishVoteAttemptsExtendedEvent”ScenePublishVoteAttemptsExtendedEvent announces an admin raising a scene’s publish-attempt budget. Emitted as scene_publish_vote_attempts_extended.
| Field | Type | Label | Description |
|---|---|---|---|
| scene_id | string | The scene whose budget was raised. | |
| additional | int32 | The number of additional attempts granted. | |
| new_max | int32 | The scene’s new max-publish-attempts budget. | |
| admin_id | string | Character ID of the admin who extended the budget. |
ScenePublishVoteCastEvent
Section titled “ScenePublishVoteCastEvent”ScenePublishVoteCastEvent announces a vote cast on a publication attempt. Emitted as scene_publish_vote_cast.
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_id | string | The publication attempt’s ID. | |
| character_id | string | The voting character’s ID. | |
| vote | bool | The yes (true) / no (false) ballot just recorded. | |
| is_change | bool | True only when this cast flipped a previously cast, differing vote. |
ScenePublishWithdrawnEvent
Section titled “ScenePublishWithdrawnEvent”ScenePublishWithdrawnEvent announces that the scene owner withdrew an active attempt (a companion to the resolved event so renderers can distinguish a withdrawal from a vote failure). Emitted as scene_publish_withdrawn.
| Field | Type | Label | Description |
|---|---|---|---|
| attempt_id | string | The publication attempt’s ID. | |
| withdrawn_by | string | Character ID of the owner who withdrew the attempt. |
StartScenePublishRequest
Section titled “StartScenePublishRequest”StartScenePublishRequest opens a publication attempt for an ended scene.
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The character initiating the attempt; required. | |
| scene_id | string | The ended scene to publish; required. |
StartScenePublishResponse
Section titled “StartScenePublishResponse”StartScenePublishResponse identifies the newly created publication attempt.
| Field | Type | Label | Description |
|---|---|---|---|
| published_scene_id | string | ID of the new publication attempt row (the published_scene_id used by the vote/withdraw/read RPCs). | |
| attempt_number | int32 | The attempt’s ordinal within the scene’s attempt budget (1-based). |
TransferOwnershipRequest
Section titled “TransferOwnershipRequest”TransferOwnershipRequest identifies the current owner, the scene, and the member who will become the new owner.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The current owner; required. | |
| scene_id | string | The scene whose ownership transfers; required. | |
| new_owner_character_id | string | The new owner, who MUST already be a member of the scene; required. |
TransferOwnershipResponse
Section titled “TransferOwnershipResponse”TransferOwnershipResponse is intentionally empty — a successful transfer carries no body.
UpdateSceneRequest
Section titled “UpdateSceneRequest”UpdateSceneRequest applies a partial update to mutable scene metadata using google.protobuf.FieldMask as the canonical proto3 partial-update pattern (per Google AIP-134). The mask is the single source of truth for “which fields to apply” — fields listed in the mask are updated to the request value (even if empty/zero); fields not in the mask are left unchanged.
Per-field constraint semantics:
- max_len limits apply to all string fields regardless of mask membership
- min_len IS NOT used at the proto layer because the mask gates whether the field is applied; per-field semantic validation (e.g. “title cannot be empty when in the mask”) happens in the service handler’s mask-iteration switch statement (buildSceneUpdate in service.go)
- enum-style fields use
in:constraints that include the empty string so that “field not set” doesn’t trip the validator
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The acting owner’s ID; required. | |
| scene_id | string | The scene to update; required. | |
| title | string | New title (applied only when “title” is in the mask; whitespace-only is rejected by the handler). Max 200 chars. | |
| description | string | New description (applied only when “description” is in the mask; empty clears it). Max 4096 chars. | |
| visibility | string | New visibility (applied only when “visibility” is in the mask; empty is rejected by the handler when masked). Constrained to "" | |
| pose_order_mode | string | New pose-order mode (applied only when “pose_order_mode” is in the mask; empty is rejected when masked). A real change auto-emits a pose-order- changed IC notice. Constrained to "" | |
| location_id | string | New location anchor (applied only when “location_id” is in the mask; empty clears the anchor). | |
| content_warnings | string | repeated | Replacement content warnings (applied only when “content_warnings” is in the mask). Max 32. |
| tags | string | repeated | Replacement tags (applied only when “tags” is in the mask). Max 32. |
| update_mask | google.protobuf.FieldMask | The set of field paths to apply. An empty mask is a no-op success. |
UpdateSceneResponse
Section titled “UpdateSceneResponse”UpdateSceneResponse carries the scene after the partial update.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | SceneInfo | The post-update scene row. |
WatchSceneRequest
Section titled “WatchSceneRequest”WatchSceneRequest identifies the watcher, target scene, and the watcher’s game session (host-supplied; the session receives the FocusMembership).
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | The watching character’s ID; required. Trusted host-supplied identity per the SceneService caller contract (service.go’s ABAC note). | |
| scene_id | string | The scene to watch; required. Must be visibility=open and active/paused. | |
| session_id | string | The watcher’s game session ULID; required — JoinFocus registers the scene FocusMembership on this session. |
WatchSceneResponse
Section titled “WatchSceneResponse”WatchSceneResponse confirms the observer row.
| Field | Type | Label | Description |
|---|---|---|---|
| participant | ParticipantInfo | The resulting participant entry (role=observer, or the pre-existing row when the character was already a participant of any role). |
WithdrawScenePublishRequest
Section titled “WithdrawScenePublishRequest”WithdrawScenePublishRequest abandons an active publication attempt (owner only).
| Field | Type | Label | Description |
|---|---|---|---|
| caller_character_id | string | The acting character, who MUST be the scene owner; required. | |
| published_scene_id | string | The active attempt to withdraw; required. |
WithdrawScenePublishResponse
Section titled “WithdrawScenePublishResponse”WithdrawScenePublishResponse is intentionally empty — a successful withdrawal carries no body; the attempt transitions to ATTEMPT_FAILED(WITHDRAWN).
SceneService
Section titled “SceneService”SceneService is the gRPC contract for the core-scenes binary plugin
(plugins/core-scenes/). A scene is a structured roleplay encounter with an
owner, a participant roster, a privacy mode, a pose-order discipline, and an
optional published archive. The plugin owns the scene ABAC resource type
and persists to its own plugin_core_scenes schema; it emits IC notice
events on events.<game_id>.scene.<scene_id>.ic and audits lifecycle
operations to its plugin-owned audit table.
Authorization model: every mutating RPC trusts that the host’s ABAC engine has already authorized the command-execute action at dispatch time (owner-only for end/pause/resume/update/invite/kick/transfer, admin-only for the publish-attempt-budget extension). The plugin itself runs NO ABAC engine (SceneServiceImpl holds no policy engine). The sole exceptions are the participant-gate reads (GetPoseOrder and the publish reads), which enforce a direct plugin-code participation check (INV-SCENE-60) precisely because it is a hard privacy boundary that must not be delegable.
Implemented by SceneServiceImpl in plugins/core-scenes/service.go and plugins/core-scenes/publish_service.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| ListScenes | ListScenesRequest | ListScenesResponse | ListScenes returns the public scene board: open scenes in state active or paused, paginated and optionally tag-filtered, with content-warning- blocked scenes excluded via the union of game/player/character scope cw_block settings plus any per-query exclude_content_warnings. Served by SceneServiceImpl.ListScenes and surfaced through the plugin’s scenes board command. See service.go::ListScenes. |
| GetScene | GetSceneRequest | GetSceneResponse | GetScene loads one scene’s metadata and participant roster by ID. The host’s read-scene ABAC policy gates access before the call reaches the handler, so the handler performs no additional ownership check; it returns codes.NotFound when the scene does not exist. See service.go::GetScene. |
| CreateScene | CreateSceneRequest | CreateSceneResponse | CreateScene allocates a new scene owned by the calling character, seeded active with the supplied title/description/visibility/pose-order/tags. The creating character becomes the owner participant in the same transaction, a lifecycle.created audit event is recorded, and a scene-created event is emitted. See service.go::CreateScene. |
| EndScene | EndSceneRequest | EndSceneResponse | EndScene transitions a scene to the terminal ended state (owner-only via ABAC). Rejected with codes.FailedPrecondition when the scene is already ended or archived. Returns the post-transition scene row. See service.go::EndScene. |
| PauseScene | PauseSceneRequest | PauseSceneResponse | PauseScene transitions an active scene to paused (owner-only via ABAC). Rejected with codes.FailedPrecondition from any non-active state. See service.go::PauseScene. |
| ResumeScene | ResumeSceneRequest | ResumeSceneResponse | ResumeScene transitions a paused scene back to active (participant-wide via ABAC — any participant may resume (spec D6)). Rejected with codes.FailedPrecondition from any non-paused state. See service.go::ResumeScene. |
| UpdateScene | UpdateSceneRequest | UpdateSceneResponse | UpdateScene applies a partial update to mutable scene metadata, driven by the request’s FieldMask (owner-only via ABAC). An empty mask is a no-op success. A pose-order-mode change auto-emits a pose-order-changed IC notice. See service.go::UpdateScene. |
| JoinScene | JoinSceneRequest | JoinSceneResponse | JoinScene adds the calling character to a scene as a member. Open scenes accept any join; private scenes require a pre-existing invitation (the invited row is promoted to member). Idempotent: a repeat join by an existing member succeeds without re-emitting a join notice. See service.go::JoinScene. |
| WatchScene | WatchSceneRequest | WatchSceneResponse | WatchScene auto-joins the requesting character into an OPEN scene as a role=observer participant and registers the focus membership for the supplied session, so focus/Subscribe/history gates admit the watcher. Gate order is fail-closed per INV-SCENE-61: the plugin-code visibility==open and state checks run BEFORE the ABAC spectate action is evaluated; non-open scenes are rejected without consulting ABAC. See service.go::WatchScene. |
| LeaveScene | LeaveSceneRequest | LeaveSceneResponse | LeaveScene removes the calling character from a scene. The scene owner cannot leave (codes.FailedPrecondition) — they must end the scene or transfer ownership first. Emits a leave IC notice with reason=left. See service.go::LeaveScene. |
| InviteToScene | InviteToSceneRequest | InviteToSceneResponse | InviteToScene records an invited participant row for a target character (owner-only via ABAC), granting them permission to join a private scene. Rejected with codes.AlreadyExists when the target is already a member. See service.go::InviteToScene. |
| KickFromScene | KickFromSceneRequest | KickFromSceneResponse | KickFromScene removes a target character from a scene (owner-only via ABAC). The scene owner cannot be kicked (codes.FailedPrecondition, enforced both at the service layer and by a store WHERE filter). Emits a leave IC notice with reason=kicked. See service.go::KickFromScene. |
| TransferOwnership | TransferOwnershipRequest | TransferOwnershipResponse | TransferOwnership reassigns scene ownership from the calling owner to a target who MUST already be a member (owner-only via ABAC). The former owner is demoted to member. See service.go::TransferOwnership. |
| CastPublishVote | CastPublishVoteRequest | CastPublishVoteResponse | CastPublishVote is DECLARED BUT NOT SERVED. It is the legacy scene-keyed publish-vote shape, superseded by CastPublishSceneVote (which is keyed by published_scene_id and is the served vote RPC). The plugin provides no handler, so a call returns codes.Unimplemented. |
| GetPoseOrder | GetPoseOrderRequest | GetPoseOrderResponse | GetPoseOrder returns the computed pose-order roster for a scene. Enforces the INV-SCENE-60 plugin-code participant gate (caller MUST be an owner or member, NOT merely invited; NO ABAC engine is consulted). The PermissionDenied gate fires before any existence check so a non-participant cannot distinguish a missing scene from one they may not see. See service.go::GetPoseOrder. |
| StartScenePublish | StartScenePublishRequest | StartScenePublishResponse | StartScenePublish opens a publication attempt for an ended scene (publish.go §5 precondition ladder). The scene must be ended, must not already have a published archive (one-and-done) nor an active attempt, and must not have exhausted its attempt budget. Seeds a COLLECTING attempt with a frozen vote roster. See publish_service.go::StartScenePublish. |
| CastPublishSceneVote | CastPublishSceneVoteRequest | CastPublishSceneVoteResponse | CastPublishSceneVote records a roster member’s yes/no vote on an active publication attempt and runs the §4.3 resolution check, which may transition the attempt (COLLECTING→COOLOFF on all-yes, COLLECTING→ ATTEMPT_FAILED on any-no-after-all-voted, or COOLOFF→COLLECTING on a flip to no). A vote on a terminal attempt is rejected. The recorded vote is the durable effect; a failed resolution or emit is logged but does not fail the cast. See publish_service.go::CastPublishSceneVote. |
| WithdrawScenePublish | WithdrawScenePublishRequest | WithdrawScenePublishResponse | WithdrawScenePublish lets the scene owner abandon an active publication attempt (COLLECTING or COOLOFF), transitioning it to ATTEMPT_FAILED with failure_reason WITHDRAWN. Owner-gated by ABAC AND a defense-in-depth in-handler owner check (the plugin holds the owner attribute, so this closes the direct-RPC gap). See publish_service.go::WithdrawScenePublish. |
| GetPublishedScene | GetPublishedSceneRequest | GetPublishedSceneResponse | GetPublishedScene returns a publication attempt’s full state to a scene participant. Enforces the INV-SCENE-60 plugin-code participant gate with a load-bearing step order (INV-SCENE-32): header read → participant gate → content read (only for PUBLISHED rows, only after the gate passes). A non-participant is denied with the §10 triple-signal before any content is read. See publish_service.go::GetPublishedScene. |
| DownloadPublishedScene | DownloadPublishedSceneRequest | DownloadPublishedSceneResponse | DownloadPublishedScene returns a PUBLISHED attempt rendered in the requested format (markdown/plain_text/jsonl) to a participant. Same load-bearing participant-gate ordering as GetPublishedScene; only PUBLISHED attempts are downloadable. See publish_service.go::DownloadPublishedScene. |
| ListScenePublishAttempts | ListScenePublishAttemptsRequest | ListScenePublishAttemptsResponse | ListScenePublishAttempts returns the audit list of a scene’s publication attempts (header summaries, no content) to a participant. Participant-gated (INV-SCENE-60) so a non-participant cannot enumerate attempts. See publish_service.go::ListScenePublishAttempts. |
| GetPublicSceneArchive | GetPublicSceneArchiveRequest | GetPublicSceneArchiveResponse | GetPublishedScene’s PUBLIC counterpart: GetPublicSceneArchive is the unauthenticated read of a published scene. Structurally separate — NO caller validation, NO participant gate, NO ABAC. The only gate is status==PUBLISHED; a missing id OR any non-PUBLISHED attempt returns one opaque NOT_FOUND so existence/progress of an attempt cannot be inferred (INV-SCENE-35). Carries only public-safe fields. See publish_service.go::GetPublicSceneArchive. |
| DownloadPublicSceneArchive | DownloadPublicSceneArchiveRequest | DownloadPublicSceneArchiveResponse | DownloadPublicSceneArchive is the PUBLIC, unauthenticated download of a published scene in the requested format. Same status-gate and opacity contract (INV-SCENE-35) as GetPublicSceneArchive; shares the renderer with DownloadPublishedScene. See publish_service.go::DownloadPublicSceneArchive. |
| ExtendScenePublishVoteAttempts | ExtendScenePublishVoteAttemptsRequest | ExtendScenePublishVoteAttemptsResponse | ExtendScenePublishVoteAttempts raises a scene’s max-publish-attempts budget by a positive amount and emits the extension notice. Admin-only, enforced by the host’s ABAC policy at dispatch — there is deliberately NO in-plugin role check (the inverse of INV-SCENE-60’s plugin-code privacy gate). See publish_service.go::ExtendScenePublishVoteAttempts. |
| ListCharacterScenes | ListCharacterScenesRequest | ListCharacterScenesResponse | ListCharacterScenes returns every non-archived scene the character has a participant row in (any role, including observer), with the character’s role and per-scene activity metadata for workspace badges. Serves the web workspace’s “my scenes” list; intended for use by the host facade fanning this out across a player’s owned characters. See service.go::ListCharacterScenes. |
| ListPublishedScenes | ListPublishedScenesRequest | ListPublishedScenesResponse | ListPublishedScenes pages through PUBLISHED scene archives (public-safe fields only, same status gate as GetPublicSceneArchive / INV-SCENE-35), newest first, with optional tag filtering. Powers the archive browse page. See publish_service.go::ListPublishedScenes. |
| ExportSceneLog | ExportSceneLogRequest | ExportSceneLogResponse | ExportSceneLog renders a scene’s IC log to a downloadable document for a participant of ANY role (observers may export what they may read; INV-SCENE-60’s participant gate is plugin-code-enforced — non-participants fail before ABAC, which is never consulted here). Decryption flows through the host-mediated snapshot decrypt seam; supported formats are “markdown” and “jsonl”. Scenes whose IC log exceeds the server-side row ceiling (exportLogMaxRows = 10 000) return FAILED_PRECONDITION / SCENE_EXPORT_TOO_LARGE rather than silently truncating the document. See export.go::ExportSceneLog. |
holomush/sceneaccess/v1/sceneaccess.proto
Section titled “holomush/sceneaccess/v1/sceneaccess.proto”CastPublishSceneVoteRequest
Section titled “CastPublishSceneVoteRequest”CastPublishSceneVoteRequest carries the attempt id + the Yes/No choice.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers and verifies character ownership. | |
| character_id | string | character_id is the acting alt; ownership verified by ownedCharacter. | |
| published_scene_id | string | The publication attempt to vote on. | |
| vote | bool | The vote: true = yes, false = no. |
CastPublishSceneVoteResponse
Section titled “CastPublishSceneVoteResponse”CastPublishSceneVoteResponse reports whether this changed an existing vote.
| Field | Type | Label | Description |
|---|---|---|---|
| is_change | bool | True when the caller had already voted and this flipped the choice. |
CreateSceneRequest
Section titled “CreateSceneRequest”CreateSceneRequest is the facade request to create a scene. player_session_token authenticates the calling player (session_id is a reserved client hint, not consulted); the facade resolves the acting character SERVER-SIDE from the player’s owned-character set (INV-SCENE-63) and overrides any client identity.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID, retained as a forward-looking hint; authentication is performed SOLELY against player_session_token (holomush-5rh.8.23 decision). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade looks up the session by token hash and rejects unauthenticated callers (codes.Unauthenticated) and guests (codes.PermissionDenied, INV-SCENE-64). | |
| character_id | string | character_id selects which owned alt becomes the scene owner; the facade verifies ownership before passing the server-verified ID downstream (codes.NotFound when not owned, INV-SCENE-63). | |
| title | string | title is the scene title; required. The plugin handler rejects empty or whitespace-only titles after trim. | |
| description | string | description is an optional scene synopsis; empty omits it. |
CreateSceneResponse
Section titled “CreateSceneResponse”CreateSceneResponse wraps the created scene’s full metadata projection.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the newly created scene; the verified character is its owner and first participant. |
DownloadPublicSceneArchiveRequest
Section titled “DownloadPublicSceneArchiveRequest”DownloadPublicSceneArchiveRequest is the facade request for downloading a PUBLISHED scene archive without participant authentication. session_id and player_session_token authenticate the calling player; guest players are rejected (INV-SCENE-64). Same opacity contract as GetPublicSceneArchiveRequest (INV-SCENE-35).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. Guest players are denied with codes.PermissionDenied (INV-SCENE-64). | |
| published_scene_id | string | published_scene_id identifies the publication attempt to download; required. Same opacity contract as GetPublicSceneArchiveRequest (INV-SCENE-35). | |
| format | string | format is the render format; required. Supported: “markdown”, “plain_text”, “jsonl”. |
DownloadPublicSceneArchiveResponse
Section titled “DownloadPublicSceneArchiveResponse”DownloadPublicSceneArchiveResponse wraps the plugin’s rendered public-archive bytes and their MIME type.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | content is the rendered file content. | |
| mime_type | string | mime_type is the content’s MIME type (text/markdown, text/plain, or application/jsonl). |
EndSceneRequest
Section titled “EndSceneRequest”EndSceneRequest is the facade request to end a scene. player_session_token authenticates the caller; the facade resolves the acting character SERVER-SIDE (INV-SCENE-63) and forwards the verified id to SceneService.EndScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (forward-looking hint; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers (Unauthenticated) and guests (PermissionDenied). | |
| character_id | string | character_id selects which owned alt acts; the facade verifies ownership (NotFound when not owned). | |
| scene_id | string | scene_id identifies the scene to end; required. |
EndSceneResponse
Section titled “EndSceneResponse”EndSceneResponse wraps the scene as of the ended transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the post-transition scene row (state == “ended”). |
ExportSceneRequest
Section titled “ExportSceneRequest”ExportSceneRequest is the facade request for scene IC log export. player_session_token authenticates the calling player (session_id is a reserved client hint, not yet consulted); the facade resolves the acting character SERVER-SIDE and ignores/overrides any client-supplied identity (INV-SCENE-63).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. The client-supplied character_id is then verified as owned by this session’s player (INV-SCENE-63). | |
| character_id | string | character_id is the “act as this alt” selector. The facade verifies ownership against the authenticated player before passing the server-verified ID downstream; a character not owned by the player returns codes.NotFound (INV-SCENE-63). | |
| scene_id | string | scene_id identifies the scene to export; required. | |
| format | string | format is the render format; required. Supported: “markdown” or “jsonl”. |
ExportSceneResponse
Section titled “ExportSceneResponse”ExportSceneResponse wraps the plugin’s rendered scene-log document.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | content is the rendered document bytes. | |
| mime_type | string | mime_type is the content’s MIME type (text/markdown or application/jsonl). | |
| filename | string | filename is the suggested download filename (slugified title + extension). |
GetPublicSceneArchiveRequest
Section titled “GetPublicSceneArchiveRequest”GetPublicSceneArchiveRequest is the facade request for reading a single published scene archive without participant authentication. session_id and player_session_token authenticate the calling player; guest players are rejected (INV-SCENE-64). The only gate on the plugin call is status==PUBLISHED (INV-SCENE-35).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. Guest players are denied with codes.PermissionDenied (INV-SCENE-64). | |
| published_scene_id | string | published_scene_id identifies the publication attempt to read; required. A missing id or any non-PUBLISHED attempt returns one opaque NOT_FOUND (INV-SCENE-35). |
GetPublicSceneArchiveResponse
Section titled “GetPublicSceneArchiveResponse”GetPublicSceneArchiveResponse wraps the plugin’s public archive view.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the publication attempt’s ID. | |
| title_snapshot | string | title_snapshot is the scene title snapshotted at publish time. | |
| participants_snapshot | string | repeated | participants_snapshot is the participant character names snapshotted at publish time. |
| content_entries | holomush.scene.v1.PublishedSceneEntry | repeated | content_entries is the frozen published content. |
| published_at_unix_ns | int64 | published_at_unix_ns is the epoch-nanosecond publish time. |
GetPublishedSceneRequest
Section titled “GetPublishedSceneRequest”GetPublishedSceneRequest reads the attempt’s status + tally (participant-gated).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers and verifies character ownership. | |
| character_id | string | character_id is the acting alt; ownership verified by ownedCharacter. | |
| published_scene_id | string | published_scene_id identifies the attempt whose status + tally to read. |
GetPublishedSceneResponse
Section titled “GetPublishedSceneResponse”GetPublishedSceneResponse is the trimmed participant view of an attempt: status + tally, no frozen content (that is GetPublicSceneArchive).
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | The attempt id. | |
| scene_id | string | The scene the attempt belongs to. | |
| attempt_number | int32 | The attempt’s 1-based ordinal. | |
| status | string | State-machine status: COLLECTING / COOLOFF / PUBLISHED / ATTEMPT_FAILED. | |
| failure_reason | string | Failure cause when status is ATTEMPT_FAILED; empty otherwise. | |
| vote_summary | holomush.scene.v1.PublishedSceneVoteSummary | The current yes/no/pending tally. |
GetSceneForViewerRequest
Section titled “GetSceneForViewerRequest”GetSceneForViewerRequest is the facade request for loading one scene. player_session_token authenticates the calling player (session_id is a reserved client hint, not yet consulted); the facade resolves the acting character SERVER-SIDE and ignores/overrides any client-supplied identity (INV-SCENE-63).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. The client-supplied character_id is then verified as owned by this session’s player (INV-SCENE-63). | |
| character_id | string | character_id is the “act as this alt” selector. The facade verifies ownership against the authenticated player before passing the server-verified ID downstream; a character not owned by the player returns codes.NotFound (INV-SCENE-63). | |
| scene_id | string | scene_id identifies the scene to load; required. |
GetSceneForViewerResponse
Section titled “GetSceneForViewerResponse”GetSceneForViewerResponse wraps the plugin’s single-scene response.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the loaded scene’s full metadata projection. |
InviteToSceneRequest
Section titled “InviteToSceneRequest”InviteToSceneRequest authenticates the inviter and names the invitee.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller; guests are rejected. | |
| character_id | string | character_id selects the acting owned alt (ownership verified server-side). | |
| scene_id | string | scene_id identifies the scene to invite into; required. | |
| target_character_id | string | target_character_id is the invitee (from the directory picker); required. |
InviteToSceneResponse
Section titled “InviteToSceneResponse”InviteToSceneResponse is empty; success is signaled by the absence of error.
KickFromSceneRequest
Section titled “KickFromSceneRequest”KickFromSceneRequest authenticates the acting owner and names the target.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller; guests are rejected. | |
| character_id | string | character_id selects the acting owned alt (ownership verified server-side). | |
| scene_id | string | scene_id identifies the scene; required. | |
| target_character_id | string | target_character_id is the member to remove (not the owner); required. |
KickFromSceneResponse
Section titled “KickFromSceneResponse”KickFromSceneResponse is empty.
LeaveSceneRequest
Section titled “LeaveSceneRequest”LeaveSceneRequest authenticates the leaving participant.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller; guests are rejected. | |
| character_id | string | character_id selects the acting owned alt (the leaver); verified. | |
| scene_id | string | scene_id identifies the scene to leave; required. |
LeaveSceneResponse
Section titled “LeaveSceneResponse”LeaveSceneResponse is empty.
ListMyScenesRequest
Section titled “ListMyScenesRequest”ListMyScenesRequest is the facade request for a character’s scene participations. session_id and player_session_token authenticate the calling player; the facade resolves the acting character SERVER-SIDE and ignores/overrides any client-supplied identity (INV-SCENE-63).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. The client-supplied character_id is then verified as owned by this session’s player (INV-SCENE-63). | |
| character_id | string | character_id is the “act as this alt” selector. The facade verifies ownership against the authenticated player before passing the server-verified ID downstream; a character not owned by the player returns codes.NotFound (INV-SCENE-63). |
ListMyScenesResponse
Section titled “ListMyScenesResponse”ListMyScenesResponse wraps the plugin’s character-scene participations list.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | holomush.scene.v1.CharacterSceneInfo | repeated | scenes is the character’s scene participations, most recently active first. |
ListPublishedScenesRequest
Section titled “ListPublishedScenesRequest”ListPublishedScenesRequest is the facade request to page through publicly visible PUBLISHED scene archives. session_id and player_session_token authenticate the calling player; guest players are rejected (INV-SCENE-64).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. Guest players are denied with codes.PermissionDenied (INV-SCENE-64). | |
| limit | int32 | limit is the page size; 0 means server default, capped at 200. | |
| offset | int32 | offset is the number of leading results to skip. | |
| tags | string | repeated | tags restricts results to archives whose source scene carries all of these tags. |
ListPublishedScenesResponse
Section titled “ListPublishedScenesResponse”ListPublishedScenesResponse wraps the plugin’s public archive list.
| Field | Type | Label | Description |
|---|---|---|---|
| archives | holomush.scene.v1.PublicSceneArchive | repeated | archives is the public-safe published-archive summaries, newest first. |
ListScenesForViewerRequest
Section titled “ListScenesForViewerRequest”ListScenesForViewerRequest is the facade request for the public scene board. player_session_token authenticates the calling player (session_id is a reserved client hint, not yet consulted); the facade resolves the acting character SERVER-SIDE and ignores/overrides any client-supplied identity (INV-SCENE-63).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. The client-supplied character_id is then verified as owned by this session’s player (INV-SCENE-63). | |
| character_id | string | character_id is the “act as this alt” selector. The facade verifies ownership against the authenticated player before passing the server-verified ID downstream; a character not owned by the player returns codes.NotFound (INV-SCENE-63). | |
| limit | int32 | limit is the maximum number of scenes to return; 0 means server default, capped at 200 by the plugin. | |
| offset | int32 | offset is the number of leading results to skip for pagination. | |
| tags | string | repeated | tags restricts results to scenes carrying all of these tags. |
| exclude_content_warnings | string | repeated | exclude_content_warnings adds extra content-warning categories to hide for this query, on top of the player’s and character’s stored block sets. |
ListScenesForViewerResponse
Section titled “ListScenesForViewerResponse”ListScenesForViewerResponse wraps the plugin’s scene-board result page.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | holomush.scene.v1.SceneInfo | repeated | scenes is the matching public scenes for this page. |
PauseSceneRequest
Section titled “PauseSceneRequest”PauseSceneRequest is the facade request to pause a scene; fields mirror EndSceneRequest.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller (see EndSceneRequest). | |
| character_id | string | character_id selects the acting owned alt (ownership verified server-side). | |
| scene_id | string | scene_id identifies the scene to pause; required. |
PauseSceneResponse
Section titled “PauseSceneResponse”PauseSceneResponse wraps the scene as of the paused transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the post-transition scene row (state == “paused”). |
ResumeSceneRequest
Section titled “ResumeSceneRequest”ResumeSceneRequest is the facade request to resume a scene; fields mirror EndSceneRequest.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller (see EndSceneRequest). | |
| character_id | string | character_id selects the acting owned alt (ownership verified server-side). | |
| scene_id | string | scene_id identifies the scene to resume; required. |
ResumeSceneResponse
Section titled “ResumeSceneResponse”ResumeSceneResponse wraps the scene as of the resumed transition.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the post-transition scene row (state == “active”). |
SetSceneFocusRequest
Section titled “SetSceneFocusRequest”SetSceneFocusRequest is the facade request to set per-connection scene focus for a web portal connection. session_id and player_session_token authenticate the calling player; the facade verifies that the connection belongs to a game session owned by one of the player’s characters before calling SetConnectionFocus (INV-SCENE-63).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated (INV-SCENE-63). | |
| connection_id | string | connection_id is the ULID of the web portal connection whose focus to set; required. The facade verifies the connection belongs to a game session owned by one of this player’s characters. | |
| scene_id | string | scene_id is the scene to focus on; optional. When empty, the focus is cleared (grid default). |
SetSceneFocusResponse
Section titled “SetSceneFocusResponse”SetSceneFocusResponse is intentionally empty — a successful focus set carries no body; the client may update its local focus state optimistically.
StartScenePublishRequest
Section titled “StartScenePublishRequest”StartScenePublishRequest carries the resolved session + acting character and the scene to publish.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers and verifies character ownership. | |
| character_id | string | character_id is the acting alt; ownership verified by ownedCharacter. | |
| scene_id | string | The scene to start a publish vote on; MUST be ended. |
StartScenePublishResponse
Section titled “StartScenePublishResponse”StartScenePublishResponse returns the new attempt’s id + ordinal.
| Field | Type | Label | Description |
|---|---|---|---|
| published_scene_id | string | The publication attempt id (published_scenes.id). | |
| attempt_number | int32 | The attempt’s 1-based ordinal within the scene’s budget. |
TransferOwnershipRequest
Section titled “TransferOwnershipRequest”TransferOwnershipRequest authenticates the current owner and names the heir.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only). | |
| player_session_token | string | player_session_token authenticates the caller; guests are rejected. | |
| character_id | string | character_id selects the acting owned alt (the current owner); verified. | |
| scene_id | string | scene_id identifies the scene; required. | |
| new_owner_character_id | string | new_owner_character_id is the existing member who becomes owner; required. |
TransferOwnershipResponse
Section titled “TransferOwnershipResponse”TransferOwnershipResponse is empty.
UpdateSceneRequest
Section titled “UpdateSceneRequest”UpdateSceneRequest is the facade request to edit scene settings. player_session_token authenticates the caller; the facade resolves the acting character SERVER-SIDE (INV-SCENE-63) and forwards the verified id, the six editable fields, and update_mask to SceneService.UpdateScene. Fields absent from update_mask are ignored by the handler (their values here are immaterial).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers (Unauthenticated) and guests (PermissionDenied). | |
| character_id | string | character_id selects which owned alt acts; ownership verified (NotFound). | |
| scene_id | string | scene_id identifies the scene to update; required. | |
| title | string | New title (applied only when “title” is in the mask). Max 200 chars. | |
| description | string | New description (applied only when “description” is in the mask; empty clears it). Max 4096 chars. | |
| visibility | string | New visibility (applied only when “visibility” is in the mask). Constrained to "" | |
| pose_order_mode | string | New pose-order mode (applied only when “pose_order_mode” is in the mask). Constrained to "" | |
| tags | string | repeated | Replacement tags (applied only when “tags” is in the mask). Max 32. |
| content_warnings | string | repeated | Replacement content warnings (applied only when “content_warnings” is in the mask). Max 32. |
| update_mask | google.protobuf.FieldMask | The set of field paths to apply (snake_case proto names). An empty mask is a no-op success at the handler; the web client avoids the round-trip entirely. |
UpdateSceneResponse
Section titled “UpdateSceneResponse”UpdateSceneResponse wraps the scene after the partial update.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the post-update scene row. |
WatchSceneRequest
Section titled “WatchSceneRequest”WatchSceneRequest is the facade request for observer auto-join. session_id and player_session_token authenticate the calling player; the facade resolves the acting character SERVER-SIDE and ignores/overrides any client-supplied identity (INV-SCENE-63). The character’s existing game session is looked up server-side via FindByCharacter; the caller MUST have selected the character (via SelectCharacter) before calling WatchScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID. Authentication is performed SOLELY against player_session_token (below, the authoritative bearer); session_id is NOT consulted. A session↔token cross-check was deliberately NOT wired (holomush-5rh.8.23 decision): it adds no security over the authoritative token and would risk rejecting valid clients whose session_id semantics differ. The field is retained as a forward-looking hint — clients SHOULD send the player session they authenticated with. | |
| player_session_token | string | player_session_token is the raw bearer token for the player session. The facade looks up the session by token hash and rejects unauthenticated callers with codes.Unauthenticated. The client-supplied character_id is then verified as owned by this session’s player (INV-SCENE-63). | |
| character_id | string | character_id is the “act as this alt” selector. The facade verifies ownership against the authenticated player before passing the server-verified ID downstream; a character not owned by the player returns codes.NotFound (INV-SCENE-63). | |
| scene_id | string | scene_id identifies the scene to watch; required. Must be visibility=open and active or paused. |
WatchSceneResponse
Section titled “WatchSceneResponse”WatchSceneResponse wraps the plugin’s observer-join confirmation.
| Field | Type | Label | Description |
|---|---|---|---|
| participant | holomush.scene.v1.ParticipantInfo | participant is the resulting participant entry (role=observer), or the pre-existing row when the character was already a participant. |
WithdrawScenePublishRequest
Section titled “WithdrawScenePublishRequest”WithdrawScenePublishRequest aborts the attempt (owner-gated in the plugin).
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID (hint only; authentication is solely via player_session_token). | |
| player_session_token | string | player_session_token is the raw bearer token; the facade rejects unauthenticated callers and verifies character ownership. | |
| character_id | string | character_id is the acting alt; ownership verified by ownedCharacter. | |
| published_scene_id | string | published_scene_id identifies the attempt to abort. |
WithdrawScenePublishResponse
Section titled “WithdrawScenePublishResponse”WithdrawScenePublishResponse is empty.
SceneAccessService
Section titled “SceneAccessService”SceneAccessService is the host-side facade that owns player authentication, identity resolution, and guest-player rejection for all scene-surface RPCs exposed through the web portal. It wraps the plugin SceneService, ensuring that every downstream call carries a server-verified, player-owned character identity rather than a client-supplied one (INV-SCENE-63). Guests are denied at this layer before any plugin call is attempted (INV-SCENE-64).
Implemented by SceneAccessServer in internal/grpc/sceneaccess_service.go. Registered on the core gRPC server (not the plugin proxy) in cmd/holomush/sub_grpc.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| ListScenesForViewer | ListScenesForViewerRequest | ListScenesForViewerResponse | ListScenesForViewer returns the public scene board filtered by the verified player’s character-scope and player-scope content-warning blocks. The facade resolves the acting character from the player session (INV-SCENE-63) and forwards a ListScenes call to the plugin SceneService with the server-verified character_id. |
| GetSceneForViewer | GetSceneForViewerRequest | GetSceneForViewerResponse | GetSceneForViewer loads one scene’s metadata for the verified player’s owned character. The facade resolves the acting character from the player session (INV-SCENE-63) and forwards a GetScene call to the plugin SceneService with the server-verified character_id. |
| ListMyScenes | ListMyScenesRequest | ListMyScenesResponse | ListMyScenes returns every non-archived scene the verified player’s owned character participates in (any role), with activity metadata for workspace badge rendering. The facade resolves the acting character from the player session (INV-SCENE-63) and forwards a ListCharacterScenes call to the plugin SceneService with the server-verified character_id. |
| WatchScene | WatchSceneRequest | WatchSceneResponse | WatchScene auto-joins the verified player’s owned character into an OPEN, active scene as a role=observer participant and registers a FocusMembership on the character’s existing game session (which must already exist — use SelectCharacter first). The facade resolves the acting character from the player session (INV-SCENE-63), looks up the character’s game session via FindByCharacter, and forwards a WatchScene call to the plugin SceneService with the server-verified character_id and session_id. Returns FailedPrecondition when no game session exists for the character (select the character first). |
| CreateScene | CreateSceneRequest | CreateSceneResponse | CreateScene creates a new scene owned by the verified player’s owned character and returns its full metadata. The facade resolves the acting character from the player session (INV-SCENE-63) and rejects guests (INV-SCENE-64), then forwards a CreateScene call to the plugin SceneService with the server-verified character_id. Unlike WatchScene it requires no existing game session — creation does not touch focus. |
| EndScene | EndSceneRequest | EndSceneResponse | EndScene transitions the verified owner’s scene to ended. The facade resolves the acting character from the player session (INV-SCENE-63), rejects guests (INV-SCENE-64), then forwards to SceneService.EndScene, which self-enforces the ABAC end policy (INV-SCENE-65). Returns the post-transition scene row. |
| PauseScene | PauseSceneRequest | PauseSceneResponse | PauseScene transitions the verified owner’s active scene to paused. Same identity/guest gating as EndScene; forwards to SceneService.PauseScene which self-enforces the ABAC pause policy (INV-SCENE-65). |
| ResumeScene | ResumeSceneRequest | ResumeSceneResponse | ResumeScene transitions the verified participant’s paused scene to active. Same identity/guest gating as EndScene; forwards to SceneService.ResumeScene which self-enforces the ABAC resume policy (participant-wide, INV-SCENE-65). |
| UpdateScene | UpdateSceneRequest | UpdateSceneResponse | UpdateScene applies an owner’s partial edit to mutable scene metadata. The facade resolves the acting character from the player session (INV-SCENE-63), rejects guests (INV-SCENE-64), then forwards to SceneService.UpdateScene, which self-enforces the ABAC update policy (owner-only, INV-SCENE-65) and applies only the fields named in update_mask (AIP-134). Returns the post-update scene row. |
| InviteToScene | InviteToSceneRequest | InviteToSceneResponse | InviteToScene resolves the verified acting character from the player session (INV-SCENE-63), rejects guests (INV-SCENE-64), then forwards to SceneService.InviteToScene, which self-enforces the ABAC invite policy (participant-wide per the relaxation, INV-SCENE-65). |
| KickFromScene | KickFromSceneRequest | KickFromSceneResponse | KickFromScene forwards to SceneService.KickFromScene, which self-enforces the owner-only kick policy (INV-SCENE-65). Same identity/guest gating as above. |
| TransferOwnership | TransferOwnershipRequest | TransferOwnershipResponse | TransferOwnership forwards to SceneService.TransferOwnership, which self-enforces the owner-only transfer-ownership policy (INV-SCENE-65). |
| LeaveScene | LeaveSceneRequest | LeaveSceneResponse | LeaveScene forwards to SceneService.LeaveScene, which self-enforces the participant leave policy (INV-SCENE-65). The owner cannot leave. |
| StartScenePublish | StartScenePublishRequest | StartScenePublishResponse | StartScenePublish starts a publication vote on an ended scene. Participant- gated inside the plugin (INV-SCENE-33: no ABAC engine on this path); the facade resolves session/character and dispatches without an engine call. |
| CastPublishSceneVote | CastPublishSceneVoteRequest | CastPublishSceneVoteResponse | CastPublishSceneVote casts or changes the caller’s Yes/No vote on the active attempt. Frozen-roster participant gate enforced in the plugin store. |
| WithdrawScenePublish | WithdrawScenePublishRequest | WithdrawScenePublishResponse | WithdrawScenePublish aborts the active attempt (scene owner only; gate in the plugin handler). |
| GetPublishedScene | GetPublishedSceneRequest | GetPublishedSceneResponse | GetPublishedScene reads the active attempt’s status + vote tally for a participant cold-start snapshot. Participant-gated (INV-SCENE-60); the facade passes the plugin’s status; the response is trimmed (no frozen content — that is GetPublicSceneArchive’s job). |
| ExportScene | ExportSceneRequest | ExportSceneResponse | ExportScene renders the verified player’s owned character’s scene IC log to a downloadable document. The facade resolves the acting character from the player session (INV-SCENE-63) and forwards an ExportSceneLog call to the plugin SceneService with the server-verified character_id. |
| SetSceneFocus | SetSceneFocusRequest | SetSceneFocusResponse | SetSceneFocus sets the per-connection focus for a web portal connection belonging to the verified player’s character. The facade verifies that the connection belongs to a session owned by one of the player’s characters (INV-SCENE-63) before calling the focus coordinator’s SetConnectionFocus. |
| ListPublishedScenes | ListPublishedScenesRequest | ListPublishedScenesResponse | ListPublishedScenes pages through publicly visible PUBLISHED scene archives, newest first, with optional tag filtering. No character identity is required for the underlying query, but the player session token + guest gate are still enforced (INV-SCENE-64). The facade forwards a ListPublishedScenes call to the plugin SceneService. |
| GetPublicSceneArchive | GetPublicSceneArchiveRequest | GetPublicSceneArchiveResponse | GetPublicSceneArchive reads a published scene archive without participant authentication. The only gate on the plugin call is status==PUBLISHED (INV-SCENE-35); the facade adds the player-session token check and guest-player denial (INV-SCENE-64). Forwards a GetPublicSceneArchive call to the plugin SceneService. |
| DownloadPublicSceneArchive | DownloadPublicSceneArchiveRequest | DownloadPublicSceneArchiveResponse | DownloadPublicSceneArchive returns a PUBLISHED scene archive rendered in the requested format. Same status-gate and opacity contract as GetPublicSceneArchive (INV-SCENE-35); the facade enforces the player-session token check and guest-player denial (INV-SCENE-64). Forwards a DownloadPublicSceneArchive call to the plugin SceneService. |
holomush/web/v1/web.proto
Section titled “holomush/web/v1/web.proto”CharacterSummary
Section titled “CharacterSummary”CharacterSummary is the web-facing roster row for one of a player’s characters, mirroring corev1.CharacterSummary and used across the auth and character-management responses.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the character’s ULID identity. | |
| character_name | string | character_name is the character’s display name. | |
| has_active_session | bool | has_active_session is true when the character currently has a live game session. | |
| session_status | string | session_status is a human-readable status string for any active session (e.g. attached/detached state). | |
| last_location | string | last_location is the display label of the character’s most recent location. | |
| last_played_at | int64 | last_played_at is the epoch-seconds timestamp of the character’s most recent play activity. |
ControlFrame
Section titled “ControlFrame”ControlFrame is the out-of-band stream-lifecycle message carried in the
control arm of StreamEventsResponse. It conveys open/replay/close
transitions and the per-stream routing identity, distinct from the in-band
GameEvent traffic.
| Field | Type | Label | Description |
|---|---|---|---|
| signal | ControlSignal | signal selects which lifecycle transition this frame represents and governs which of the remaining fields are populated. | |
| message | string | message is an optional human-readable detail string accompanying the signal (e.g. a close reason). May be empty. | |
| connection_id | string | connection_id is populated on the first ControlFrame after a successful StreamEvents open so the client can include it in subsequent SendCommand requests. Per-stream identity for multi-tab routing (Phase 5 scene-focus autofocus). Empty on non-open frames. | |
| attach_moment_ms | int64 | attach_moment_ms is the server’s wall-clock epoch-ms at the moment the Subscribe handler attached its durable consumer. Carried ONLY on CONTROL_SIGNAL_REPLAY_COMPLETE; clients reading other signals MUST ignore this field. The client passes this value as not_after_ms on subsequent backfill (WebQueryStreamHistory) calls so backfill returns ONLY events with timestamp <= attach_moment_ms — eliminating the connect-time replay/backfill race where a post-attach event could appear both as a dimmed backfill row and a live Subscribe delivery (holomush-iu8j; fujt Fix B). 0 on legacy/pre-iu8j servers; clients MUST treat 0 as “no upper bound” (back-compat). | |
| scene_id | string | scene_id identifies the scene that produced a SCENE_ACTIVITY signal; the bare scene ULID (not a subject). Set ONLY on CONTROL_SIGNAL_SCENE_ACTIVITY; clients reading other signals MUST ignore it. |
DisconnectRequest
Section titled “DisconnectRequest”DisconnectRequest names the session to tear down out of band.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the in-game session to end. |
DisconnectResponse
Section titled “DisconnectResponse”DisconnectResponse is the empty acknowledgement of a Disconnect call; the gateway always returns it even when the upstream best-effort RPC failed.
GameEvent
Section titled “GameEvent”GameEvent is the web-facing rendering of a single core EventFrame, flattened for direct display by the client. The gateway derives every field from the core EventFrame and its RenderingMetadata in internal/web/translate.go::translateEvent; events lacking rendering metadata are dropped at the gateway (INV-EVENTBUS-6) and never reach this message.
| Field | Type | Label | Description |
|---|---|---|---|
| type | string | type is the event-type discriminator (e.g. “say”, “pose”, “arrive”, “location_state”), forwarded from corev1.EventFrame.type. | |
| category | string | category is the rendering category from RenderingMetadata (e.g. “communication”, “movement”, “state”); it drives client-side grouping and the gateway’s own state-vs-narrative branch. | |
| format | string | format is the rendering format hint from RenderingMetadata (e.g. “speech”, “action”) guiding how the client styles the line. | |
| display_target | EventChannel | display_target is the surface the event renders on (terminal, state, or both), copied from RenderingMetadata.display_target. | |
| timestamp | int64 | timestamp is the event time as epoch SECONDS, taken from the seconds component of corev1.EventFrame.timestamp. | |
| actor | string | actor is the DISPLAY NAME of the acting character, extracted from the event payload (character_name, falling back to sender_name) for rendering. For stable identity use actor_id, not this field. | |
| text | string | text is the rendered line shown to the player. Extracted from the payload (message → text → action → notice) or synthesized for arrive/leave movement events. Empty for state events whose content lives in metadata. | |
| metadata | google.protobuf.Struct | metadata carries type-specific extras (label, style, channel, no_space, target_name) for narrative events, or the entire decoded payload for state-category events. | |
| event_id | string | event_id is the originating event’s ULID, forwarded from corev1.EventFrame.id; the client uses it for dedup keying. | |
| cursor | bytes | cursor is the opaque pagination cursor for this event. Mirrors corev1.EventFrame.cursor for reconnect-with-backfill support. | |
| actor_id | string | actor_id is the ULID identity of the actor (character/plugin/system), forwarded from corev1.EventFrame.actor_id. Distinct from actor above which is the display name extracted from the JSON payload — name is for rendering; actor_id is for stable cross-event keying (e.g., presence list dedup, self-message detection, ABAC correlation). Empty for events without a typed actor. Added by holomush-5b2j.13. |
GetCommandHistoryRequest
Section titled “GetCommandHistoryRequest”GetCommandHistoryRequest names the session whose recent command lines to retrieve.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the in-game session whose command history to fetch. |
GetCommandHistoryResponse
Section titled “GetCommandHistoryResponse”GetCommandHistoryResponse returns the session’s recent command lines, or an empty list when the lookup failed or was unauthorized.
| Field | Type | Label | Description |
|---|---|---|---|
| commands | string | repeated | commands is the ordered list of recent raw command lines for the session. |
SendCommandRequest
Section titled “SendCommandRequest”SendCommandRequest carries one raw command line for a game session, optionally tagged with the stream connection it originated from.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the in-game session whose character issues the command. | |
| text | string | text is the raw command line exactly as the player typed it; parsing and dispatch happen core-side in HandleCommand. | |
| connection_id | string | connection_id identifies the originating StreamEvents stream for per-connection command routing (Phase 5 scene-focus autofocus). Clients set this from the connection_id they receive in the STREAM_OPENED ControlFrame after StreamEvents opens. Empty means “no specific connection origin” (scripted / admin paths). |
SendCommandResponse
Section titled “SendCommandResponse”SendCommandResponse reports the outcome of a dispatched command. Note this is the dispatch acknowledgement; any narrative output the command produces arrives asynchronously over the StreamEvents feed, not here.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the command was accepted and dispatched without a transport-level error. | |
| output | string | output is an optional synchronous text result; most game output is delivered out of band over StreamEvents rather than in this field. | |
| error_message | string | error_message is a human-readable failure detail; the gateway sets a generic “command error” when the upstream HandleCommand RPC fails. |
StreamEventsRequest
Section titled “StreamEventsRequest”StreamEventsRequest opens the server-streaming event feed for a session.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the in-game session whose event stream to attach. |
StreamEventsResponse
Section titled “StreamEventsResponse”StreamEventsResponse is one frame in the StreamEvents server stream: either an in-band game event or an out-of-band control message, never both.
| Field | Type | Label | Description |
|---|---|---|---|
| event | GameEvent | event is an in-band game event for display. | |
| control | ControlFrame | control is an out-of-band stream-lifecycle signal (open, replay boundary, close). |
WebAuthenticatePlayerRequest
Section titled “WebAuthenticatePlayerRequest”WebAuthenticatePlayerRequest carries login credentials for the WebAuthenticatePlayer RPC.
| Field | Type | Label | Description |
|---|---|---|---|
| username | string | username identifies the player account to authenticate. | |
| password | string | password is the plaintext password verified core-side (never logged at the gateway). | |
| remember_me | bool | remember_me requests a longer-lived session/cookie; forwarded to core to determine session TTL. |
WebAuthenticatePlayerResponse
Section titled “WebAuthenticatePlayerResponse”WebAuthenticatePlayerResponse reports the login outcome and, on success, the player’s character roster. The session token itself is delivered as a Set-Cookie header by the gateway, not in this body.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when credentials were accepted. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. | |
| characters | CharacterSummary | repeated | characters is the player’s roster, returned so the client can prompt for character selection after login. |
| default_character_id | string | default_character_id names the character the client should preselect, if the player has a default. | |
| error_code | string | error_code is a machine-readable error discriminator. Values: "" on success, “ALREADY_AUTHENTICATED” when the cookie-collision gate fires; others reserved for future use. | |
| current_player_name | string | current_player_name is populated only when error_code = “ALREADY_AUTHENTICATED”. Holds the existing player’s display name so the client renders the right “you are already signed in as X” UI without a second round trip. |
WebAvailableCommand
Section titled “WebAvailableCommand”WebAvailableCommand is one command’s metadata for the web composer.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the canonical command name shown on the chip. | |
| help | string | help is the one-line description (for future tooltip use). | |
| usage | string | usage is the usage pattern. | |
| source | string | source is “core” or the owning plugin name. |
WebCastPublishSceneVoteRequest
Section titled “WebCastPublishSceneVoteRequest”WebCastPublishSceneVoteRequest casts/changes a vote from the web portal.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| published_scene_id | string | published_scene_id identifies the attempt to vote on. | |
| vote | bool | vote is the ballot choice: true = yes, false = no. |
WebCastPublishSceneVoteResponse
Section titled “WebCastPublishSceneVoteResponse”WebCastPublishSceneVoteResponse mirrors the facade response.
| Field | Type | Label | Description |
|---|---|---|---|
| is_change | bool | is_change is true when the caller had already voted and this flipped the choice. |
WebCheckSessionRequest
Section titled “WebCheckSessionRequest”WebCheckSessionRequest is the empty request that validates the cookie-borne player session; the token is read from the cookie header, not the body.
WebCheckSessionResponse
Section titled “WebCheckSessionResponse”WebCheckSessionResponse returns the authenticated player’s identity and roster when the cookie session is valid. An invalid/expired session yields a CodeUnauthenticated error instead of this message.
| Field | Type | Label | Description |
|---|---|---|---|
| player_name | string | player_name is the authenticated player’s display name. | |
| player_id | string | player_id is the authenticated player’s ULID identity. Additive on the success path; the failure path still returns CodeUnauthenticated so the web client’s authed layout continues to redirect on throw (no contract break). | |
| is_guest | bool | is_guest is true when the session belongs to an ephemeral guest player. | |
| characters | CharacterSummary | repeated | characters is the player’s character roster, returned so the client can restore character-selection state on reload. |
WebConfirmPasswordResetRequest
Section titled “WebConfirmPasswordResetRequest”WebConfirmPasswordResetRequest carries the emailed reset token and the replacement password.
| Field | Type | Label | Description |
|---|---|---|---|
| token | string | token is the single-use reset token delivered by email. | |
| new_password | string | new_password is the replacement plaintext password (hashed core-side). |
WebConfirmPasswordResetResponse
Section titled “WebConfirmPasswordResetResponse”WebConfirmPasswordResetResponse reports whether the reset completed.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the password was changed. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. |
WebContentItem
Section titled “WebContentItem”WebContentItem is the web-facing representation of a single content-store entry, forwarded field-for-field from contentv1.ContentItem.
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | key is the item’s storage key. | |
| content_type | string | content_type is the item’s MIME type, guiding how the client renders the body. | |
| body | bytes | body is the raw item content bytes. | |
| metadata | WebContentItem.MetadataEntry | repeated | metadata is a free-form string→string map of item attributes. |
WebContentItem.MetadataEntry
Section titled “WebContentItem.MetadataEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
WebCreateCharacterRequest
Section titled “WebCreateCharacterRequest”WebCreateCharacterRequest names the new character to add to the authenticated player. The player is identified by the cookie session token, not a body field.
| Field | Type | Label | Description |
|---|---|---|---|
| character_name | string | character_name is the desired display name for the new character. Field number is 2 (field 1 retired with the cookie cutover). |
WebCreateCharacterResponse
Section titled “WebCreateCharacterResponse”WebCreateCharacterResponse reports the result of creating a character.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the character was created. | |
| character_id | string | character_id is the new character’s ULID identity. | |
| character_name | string | character_name is the created character’s display name. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. |
WebCreateGuestRequest
Section titled “WebCreateGuestRequest”WebCreateGuestRequest is the empty request for provisioning an ephemeral guest player; guest identity is generated server-side.
WebCreateGuestResponse
Section titled “WebCreateGuestResponse”WebCreateGuestResponse reports the guest-provisioning outcome and the generated guest character roster. The accompanying guest session cookie (shorter TTL) is set via Set-Cookie by the gateway.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when a guest player and character were provisioned. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. | |
| characters | CharacterSummary | repeated | characters is the generated guest character roster. |
| default_character_id | string | default_character_id names the guest character to preselect. | |
| error_code | string | error_code mirrors WebAuthenticatePlayerResponse.error_code semantics. | |
| current_player_name | string | current_player_name mirrors WebAuthenticatePlayerResponse.current_player_name. |
WebCreatePlayerRequest
Section titled “WebCreatePlayerRequest”WebCreatePlayerRequest carries the fields for new-account registration.
| Field | Type | Label | Description |
|---|---|---|---|
| username | string | username is the desired account name. | |
| password | string | password is the desired plaintext password (hashed core-side). | |
| string | email is the account’s email address, used for password-reset delivery. |
WebCreatePlayerResponse
Section titled “WebCreatePlayerResponse”WebCreatePlayerResponse reports the registration outcome and the initial character roster. As with login, the session token is delivered via Set-Cookie, not in this body.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the account was created. | |
| characters | CharacterSummary | repeated | characters is the new account’s initial character roster. |
| error_message | string | error_message is a human-readable failure detail on the non-success path. | |
| error_code | string | error_code mirrors WebAuthenticatePlayerResponse.error_code semantics (e.g. “ALREADY_AUTHENTICATED” from the cookie-collision gate). | |
| current_player_name | string | current_player_name mirrors WebAuthenticatePlayerResponse.current_player_name; set only on the ALREADY_AUTHENTICATED path. |
WebCreateSceneRequest
Section titled “WebCreateSceneRequest”WebCreateSceneRequest proxies to SceneAccessService.CreateScene. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt becomes the scene owner, forwarded to the facade for server-side ownership verification. | |
| title | string | title is the scene title; required. | |
| description | string | description is an optional scene synopsis; empty omits it. |
WebCreateSceneResponse
Section titled “WebCreateSceneResponse”WebCreateSceneResponse re-exports the created scene’s metadata from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the newly created scene’s full metadata projection. |
WebDownloadPublicSceneArchiveRequest
Section titled “WebDownloadPublicSceneArchiveRequest”WebDownloadPublicSceneArchiveRequest proxies to SceneAccessService.DownloadPublicSceneArchive. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| published_scene_id | string | published_scene_id identifies the publication attempt to download; required. | |
| format | string | format is the render format; required. Supported: “markdown”, “plain_text”, “jsonl”. |
WebDownloadPublicSceneArchiveResponse
Section titled “WebDownloadPublicSceneArchiveResponse”WebDownloadPublicSceneArchiveResponse re-exports the rendered public-archive bytes from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | content is the rendered file content. | |
| mime_type | string | mime_type is the content’s MIME type. |
WebEndSceneRequest
Section titled “WebEndSceneRequest”WebEndSceneRequest proxies to SceneAccessService.EndScene. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| scene_id | string | scene_id identifies the scene to end; required. |
WebEndSceneResponse
Section titled “WebEndSceneResponse”WebEndSceneResponse re-exports the post-transition scene from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the scene row after the ended transition. |
WebExportSceneRequest
Section titled “WebExportSceneRequest”WebExportSceneRequest proxies to SceneAccessService.ExportScene. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id is the “act as this alt” selector forwarded to the facade. | |
| scene_id | string | scene_id identifies the scene to export; required. | |
| format | string | format is the render format; required. Supported: “markdown” or “jsonl”. |
WebExportSceneResponse
Section titled “WebExportSceneResponse”WebExportSceneResponse re-exports the rendered scene-log document from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| content | bytes | content is the rendered document bytes. | |
| mime_type | string | mime_type is the content’s MIME type (text/markdown or application/jsonl). | |
| filename | string | filename is the suggested download filename. |
WebGetContentRequest
Section titled “WebGetContentRequest”WebGetContentRequest selects one content-store item by exact key. Served by the public, unauthenticated ContentService proxy (NOT CoreService).
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | key is the exact content-store key; no prefix matching is applied. |
WebGetContentResponse
Section titled “WebGetContentResponse”WebGetContentResponse returns the requested content item, or an empty message when no item exists for the key.
| Field | Type | Label | Description |
|---|---|---|---|
| item | WebContentItem | item is the matched content item; unset when the key was not found. |
WebGetPublicSceneArchiveRequest
Section titled “WebGetPublicSceneArchiveRequest”WebGetPublicSceneArchiveRequest proxies to SceneAccessService.GetPublicSceneArchive. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| published_scene_id | string | published_scene_id identifies the publication attempt to read; required. |
WebGetPublicSceneArchiveResponse
Section titled “WebGetPublicSceneArchiveResponse”WebGetPublicSceneArchiveResponse re-exports the public archive view from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the publication attempt’s ID. | |
| title_snapshot | string | title_snapshot is the scene title snapshotted at publish time. | |
| participants_snapshot | string | repeated | participants_snapshot is the participant character names snapshotted at publish time. |
| content_entries | holomush.scene.v1.PublishedSceneEntry | repeated | content_entries is the frozen published content. |
| published_at_unix_ns | int64 | published_at_unix_ns is the epoch-nanosecond publish time. |
WebGetPublishedSceneRequest
Section titled “WebGetPublishedSceneRequest”WebGetPublishedSceneRequest reads the cold-start tally snapshot.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| published_scene_id | string | published_scene_id identifies the attempt whose status + tally to read. |
WebGetPublishedSceneResponse
Section titled “WebGetPublishedSceneResponse”WebGetPublishedSceneResponse is the trimmed status + tally view.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the publication attempt id. | |
| scene_id | string | scene_id is the scene the attempt belongs to. | |
| attempt_number | int32 | attempt_number is the attempt’s 1-based ordinal. | |
| status | string | status is the state-machine phase: COLLECTING / COOLOFF / PUBLISHED / ATTEMPT_FAILED. | |
| failure_reason | string | failure_reason is the cause when status is ATTEMPT_FAILED; empty otherwise. | |
| vote_summary | holomush.scene.v1.PublishedSceneVoteSummary | vote_summary is the current yes/no/pending tally. |
WebGetSceneRequest
Section titled “WebGetSceneRequest”WebGetSceneRequest proxies to SceneAccessService.GetSceneForViewer. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id is the “act as this alt” selector forwarded to the facade. | |
| scene_id | string | scene_id identifies the scene to load; required. |
WebGetSceneResponse
Section titled “WebGetSceneResponse”WebGetSceneResponse re-exports the single-scene metadata from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the loaded scene’s full metadata projection. |
WebInviteToSceneRequest
Section titled “WebInviteToSceneRequest”WebInviteToSceneRequest proxies to SceneAccessService.InviteToScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is forwarded to the facade. | |
| character_id | string | character_id selects the acting owned alt (the inviter). | |
| scene_id | string | scene_id identifies the scene; required. | |
| target_character_id | string | target_character_id is the invitee from the directory picker; required. |
WebInviteToSceneResponse
Section titled “WebInviteToSceneResponse”WebInviteToSceneResponse is empty.
WebKickFromSceneRequest
Section titled “WebKickFromSceneRequest”WebKickFromSceneRequest proxies to SceneAccessService.KickFromScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is forwarded to the facade. | |
| character_id | string | character_id selects the acting owned alt (the owner). | |
| scene_id | string | scene_id identifies the scene; required. | |
| target_character_id | string | target_character_id is the member to remove; required. |
WebKickFromSceneResponse
Section titled “WebKickFromSceneResponse”WebKickFromSceneResponse is empty.
WebLeaveSceneRequest
Section titled “WebLeaveSceneRequest”WebLeaveSceneRequest proxies to SceneAccessService.LeaveScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is forwarded to the facade. | |
| character_id | string | character_id selects the acting owned alt (the leaver). | |
| scene_id | string | scene_id identifies the scene to leave; required. |
WebLeaveSceneResponse
Section titled “WebLeaveSceneResponse”WebLeaveSceneResponse is empty.
WebListAllCharactersRequest
Section titled “WebListAllCharactersRequest”WebListAllCharactersRequest proxies to CoreService.ListAllCharacters; token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the acting alt (forwarded as the ABAC subject). Required. |
WebListAllCharactersResponse
Section titled “WebListAllCharactersResponse”WebListAllCharactersResponse re-exports the directory page from core.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | holomush.core.v1.CharacterDirectoryEntry | repeated | characters is the id+name list from CoreService.ListAllCharacters. |
WebListCharactersRequest
Section titled “WebListCharactersRequest”WebListCharactersRequest is the empty request for the authenticated player’s roster; the player is identified by the cookie session token.
WebListCharactersResponse
Section titled “WebListCharactersResponse”WebListCharactersResponse returns the authenticated player’s character roster.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | CharacterSummary | repeated | characters is the player’s full character roster. |
WebListCommandsRequest
Section titled “WebListCommandsRequest”WebListCommandsRequest names the session whose character’s commands to list.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the requesting session; core resolves the character and enforces ownership. |
WebListCommandsResponse
Section titled “WebListCommandsResponse”WebListCommandsResponse returns the command set + alias map for the composer.
| Field | Type | Label | Description |
|---|---|---|---|
| commands | WebAvailableCommand | repeated | commands is the set the session character may execute. |
| aliases | WebListCommandsResponse.AliasesEntry | repeated | aliases maps alias → canonical command name for visible commands. |
| incomplete | bool | incomplete is true when engine errors hid some commands. |
WebListCommandsResponse.AliasesEntry
Section titled “WebListCommandsResponse.AliasesEntry”| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
WebListContentRequest
Section titled “WebListContentRequest”WebListContentRequest selects a page of content-store items under a key prefix. Served by the public ContentService proxy.
| Field | Type | Label | Description |
|---|---|---|---|
| prefix | string | prefix restricts results to keys beginning with this string. | |
| limit | int32 | limit requests a page size; the bound is forwarded to and applied by the content service. | |
| cursor | string | cursor is the opaque next_cursor from a prior page; empty starts at the top of the prefix. |
WebListContentResponse
Section titled “WebListContentResponse”WebListContentResponse returns one page of content items plus the cursor for the following page.
| Field | Type | Label | Description |
|---|---|---|---|
| items | WebContentItem | repeated | items is the page of matched content items. |
| next_cursor | string | next_cursor is the opaque cursor for the next page; empty when no further pages remain. |
WebListFocusPresenceRequest
Section titled “WebListFocusPresenceRequest”WebListFocusPresenceRequest names the session whose current focus-context presence to snapshot.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the requesting session; core resolves the session’s current focus (location or scene) and enforces ownership. |
WebListFocusPresenceResponse
Section titled “WebListFocusPresenceResponse”WebListFocusPresenceResponse returns the presence snapshot for the session’s current focus context.
| Field | Type | Label | Description |
|---|---|---|---|
| context | WebPresenceContext | context indicates whether the snapshot describes a location or a scene. | |
| context_id | string | context_id is the ULID of the focus location or scene the snapshot describes. | |
| entries | WebPresenceEntry | repeated | entries is the set of characters present in the focus context. |
WebListMyScenesRequest
Section titled “WebListMyScenesRequest”WebListMyScenesRequest proxies to SceneAccessService.ListMyScenes. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id is the “act as this alt” selector forwarded to the facade. |
WebListMyScenesResponse
Section titled “WebListMyScenesResponse”WebListMyScenesResponse re-exports the character’s scene participations from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | holomush.scene.v1.CharacterSceneInfo | repeated | scenes is the character’s scene participations, most recently active first. |
WebListPlayerSessionsRequest
Section titled “WebListPlayerSessionsRequest”WebListPlayerSessionsRequest is the empty request listing the caller’s PlayerSessions; the caller is identified by the cookie token.
WebListPlayerSessionsResponse
Section titled “WebListPlayerSessionsResponse”WebListPlayerSessionsResponse returns all of the caller’s active PlayerSessions.
| Field | Type | Label | Description |
|---|---|---|---|
| sessions | WebPlayerSessionInfo | repeated | sessions is the caller’s list of active PlayerSessions, one flagged is_current. |
WebListPublishedScenesRequest
Section titled “WebListPublishedScenesRequest”WebListPublishedScenesRequest proxies to SceneAccessService.ListPublishedScenes. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| limit | int32 | limit is the page size; 0 means server default. | |
| offset | int32 | offset is the number of leading results to skip. | |
| tags | string | repeated | tags restricts results to archives whose source scene carries all of these tags. |
WebListPublishedScenesResponse
Section titled “WebListPublishedScenesResponse”WebListPublishedScenesResponse re-exports the public archive list from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| archives | holomush.scene.v1.PublicSceneArchive | repeated | archives is the public-safe published-archive summaries, newest first. |
WebListScenesRequest
Section titled “WebListScenesRequest”WebListScenesRequest proxies to SceneAccessService.ListScenesForViewer. session_id is the client-declared player-session ULID; player_session_token is injected from the X-Session-Token cookie by gateway middleware and is NOT a body field — it is forwarded as the facade’s player_session_token.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id is the “act as this alt” selector forwarded to the facade. | |
| limit | int32 | limit is the maximum number of scenes to return; 0 means server default. | |
| offset | int32 | offset is the number of leading results to skip for pagination. | |
| tags | string | repeated | tags restricts results to scenes carrying all of these tags. |
| exclude_content_warnings | string | repeated | exclude_content_warnings adds extra content-warning categories to suppress. |
WebListScenesResponse
Section titled “WebListScenesResponse”WebListScenesResponse re-exports the scene board result from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scenes | holomush.scene.v1.SceneInfo | repeated | scenes is the matching public scenes for this page. |
WebListSessionStreamsRequest
Section titled “WebListSessionStreamsRequest”WebListSessionStreamsRequest names the session whose subscribed stream set to enumerate.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the session whose subscriptions to list. |
WebListSessionStreamsResponse
Section titled “WebListSessionStreamsResponse”WebListSessionStreamsResponse returns the stream names a session is subscribed to, used by the client to drive reload-backfill across each stream.
| Field | Type | Label | Description |
|---|---|---|---|
| streams | string | repeated | streams is the list of event-store stream names the session is subscribed to. Values are domain-relative dot references (e.g. “location.<id>”, “character.<id>”) — the same form the client passes back to WebQueryStreamHistory, which the server qualifies. |
WebLogoutRequest
Section titled “WebLogoutRequest”WebLogoutRequest is the empty request to end the current session; the session to end is identified by the cookie token.
WebLogoutResponse
Section titled “WebLogoutResponse”WebLogoutResponse is the empty acknowledgement of logout. The meaningful effect — clearing the session cookie — is delivered as a response header by the gateway, so this body carries no fields.
WebPauseSceneRequest
Section titled “WebPauseSceneRequest”WebPauseSceneRequest proxies to SceneAccessService.PauseScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| scene_id | string | scene_id identifies the scene to pause; required. |
WebPauseSceneResponse
Section titled “WebPauseSceneResponse”WebPauseSceneResponse re-exports the post-transition scene from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the scene row after the paused transition. |
WebPlayerSessionInfo
Section titled “WebPlayerSessionInfo”WebPlayerSessionInfo describes one of a player’s PlayerSessions — a device/tab login record (distinct from an in-game game session) shown in the account’s “active sessions” management UI.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the PlayerSession.id (ULID). Used as target_session_id when revoking. Safe to show - resource handle, not a secret. | |
| created_at | google.protobuf.Timestamp | created_at is when the PlayerSession was first established (login time). | |
| last_active | google.protobuf.Timestamp | last_active is the timestamp of the PlayerSession’s most recent activity. | |
| user_agent | string | user_agent is the browser User-Agent recorded at login, shown to help the player recognize the device. | |
| ip_address | string | ip_address is the client IP recorded at login, shown for the same recognition purpose. | |
| is_current | bool | is_current is true for the PlayerSession that made this request. |
WebPresenceEntry
Section titled “WebPresenceEntry”WebPresenceEntry is one character in a presence snapshot, carrying identity and activity state for the client’s presence list.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the present character’s ULID identity (used for dedup keying). | |
| character_name | string | character_name is the present character’s display name. | |
| state | WebPresenceState | state is the character’s activity state in this context. |
WebQueryStreamHistoryRequest
Section titled “WebQueryStreamHistoryRequest”WebQueryStreamHistoryRequest selects a page of historical events for a session stream, with time-window and cursor bounds. Proxies to CoreService.QueryStreamHistory.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id identifies the requesting session (used core-side for authorization). | |
| stream | string | stream names the event stream to read history from. Clients pass domain-relative dot-style references (e.g. “location.<id>”); the server qualifies them into fully-qualified JetStream subjects on the way in. | |
| count | int32 | count is the requested page size; 0 means the server default (150), values are capped at the server maximum (500), and negative values are rejected. | |
| not_before_ms | int64 | not_before_ms is the epoch-ms time floor; 0 means no lower bound. | |
| cursor | bytes | cursor is the opaque pagination cursor from a previous WebQueryStreamHistoryResponse. Events older than the cursor position are returned. Empty = start from latest. | |
| not_after_ms | int64 | not_after_ms is the epoch-ms time ceiling. 0 = no upper bound (back-compat). INCLUSIVE: events with timestamp == not_after_ms are returned. Set by the web client to the Subscribe attach_moment_ms (carried on the REPLAY_COMPLETE ControlFrame) so backfill returns only events that existed before the live stream attached — eliminating the connect-time replay/backfill race (holomush-iu8j; holomush-fujt Fix B). |
WebQueryStreamHistoryResponse
Section titled “WebQueryStreamHistoryResponse”WebQueryStreamHistoryResponse returns one page of historical events translated for the web client, plus pagination state.
| Field | Type | Label | Description |
|---|---|---|---|
| events | GameEvent | repeated | events is the page of historical events (translated from core EventFrames; events without rendering metadata are dropped at the gateway). |
| has_more | bool | has_more is true when older events remain beyond this page. | |
| next_cursor | bytes | next_cursor is the opaque cursor for the next page. Empty if has_more is false. |
WebRequestPasswordResetRequest
Section titled “WebRequestPasswordResetRequest”WebRequestPasswordResetRequest names the email address to begin a reset for.
| Field | Type | Label | Description |
|---|---|---|---|
| string | email is the account email to send the reset link to. |
WebRequestPasswordResetResponse
Section titled “WebRequestPasswordResetResponse”WebRequestPasswordResetResponse acknowledges the reset request. success is reported true even when the underlying account does not exist, to avoid leaking account existence.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the request was accepted; it does NOT confirm the email exists (existence is deliberately not disclosed). |
WebResumeSceneRequest
Section titled “WebResumeSceneRequest”WebResumeSceneRequest proxies to SceneAccessService.ResumeScene.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| scene_id | string | scene_id identifies the scene to resume; required. |
WebResumeSceneResponse
Section titled “WebResumeSceneResponse”WebResumeSceneResponse re-exports the post-transition scene from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the scene row after the resumed transition. |
WebRevokeOtherPlayerSessionsRequest
Section titled “WebRevokeOtherPlayerSessionsRequest”WebRevokeOtherPlayerSessionsRequest is the empty “log out everywhere else” request; the surviving (current) session is identified by the cookie token.
WebRevokeOtherPlayerSessionsResponse
Section titled “WebRevokeOtherPlayerSessionsResponse”WebRevokeOtherPlayerSessionsResponse reports the bulk-revocation outcome and how many PlayerSessions were revoked.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the bulk revocation completed. | |
| revoked_count | int32 | revoked_count is the number of other PlayerSessions revoked. |
WebRevokePlayerSessionRequest
Section titled “WebRevokePlayerSessionRequest”WebRevokePlayerSessionRequest names a single PlayerSession to revoke; the caller (and thus ownership) is identified by the cookie token.
| Field | Type | Label | Description |
|---|---|---|---|
| target_session_id | string | target_session_id is the PlayerSession.id (ULID) to revoke. |
WebRevokePlayerSessionResponse
Section titled “WebRevokePlayerSessionResponse”WebRevokePlayerSessionResponse reports whether the targeted PlayerSession was revoked.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the targeted PlayerSession was revoked. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. |
WebSelectCharacterRequest
Section titled “WebSelectCharacterRequest”WebSelectCharacterRequest names the character to bind to a game session.
| Field | Type | Label | Description |
|---|---|---|---|
| character_id | string | character_id is the ULID of the character to select. Field number is 2; field 1 was retired with the cookie cutover (the token now travels in the cookie header, not the request body). | |
| client_type | string | client_type declares the surface establishing the session (terminal/comms_hub/telnet — the session_connections vocabulary). When “comms_hub”, a FRESH session creation skips the grid arrive emission: the web portal’s scenes workspace must not announce the character on the grid (spec 2026-06-07 §V2). Empty preserves the legacy behavior (arrive). |
WebSelectCharacterResponse
Section titled “WebSelectCharacterResponse”WebSelectCharacterResponse reports the result of binding a character, including the resulting game session and whether an existing session was reattached rather than freshly created.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when a session was created or reattached. | |
| session_id | string | session_id is the resulting in-game session’s id, used by StreamEvents and SendCommand. | |
| character_name | string | character_name is the selected character’s display name. | |
| reattached | bool | reattached is true when an existing detached session was resumed instead of a new one being created. | |
| error_message | string | error_message is a human-readable failure detail on the non-success path. |
WebSetSceneFocusRequest
Section titled “WebSetSceneFocusRequest”WebSetSceneFocusRequest proxies to SceneAccessService.SetSceneFocus. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| connection_id | string | connection_id is the ULID of the web portal connection whose focus to set. | |
| scene_id | string | scene_id is the scene to focus on; empty clears the focus (grid default). |
WebSetSceneFocusResponse
Section titled “WebSetSceneFocusResponse”WebSetSceneFocusResponse is intentionally empty — a successful focus set carries no body; the client may update its local focus state optimistically.
WebStartScenePublishRequest
Section titled “WebStartScenePublishRequest”WebStartScenePublishRequest starts a publish vote from the web portal.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| scene_id | string | scene_id identifies the scene to start a publish vote on; MUST be ended. |
WebStartScenePublishResponse
Section titled “WebStartScenePublishResponse”WebStartScenePublishResponse mirrors the facade response.
| Field | Type | Label | Description |
|---|---|---|---|
| published_scene_id | string | published_scene_id is the new publication attempt’s id. | |
| attempt_number | int32 | attempt_number is the attempt’s 1-based ordinal within the scene’s budget. |
WebTransferOwnershipRequest
Section titled “WebTransferOwnershipRequest”WebTransferOwnershipRequest proxies to SceneAccessService.TransferOwnership.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is forwarded to the facade. | |
| character_id | string | character_id selects the acting owned alt (the current owner). | |
| scene_id | string | scene_id identifies the scene; required. | |
| new_owner_character_id | string | new_owner_character_id is the existing member who becomes owner; required. |
WebTransferOwnershipResponse
Section titled “WebTransferOwnershipResponse”WebTransferOwnershipResponse is empty.
WebUpdateSceneRequest
Section titled “WebUpdateSceneRequest”WebUpdateSceneRequest proxies to SceneAccessService.UpdateScene. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| scene_id | string | scene_id identifies the scene to update; required. | |
| title | string | New title (applied only when “title” is in the mask). Max 200 chars. | |
| description | string | New description (applied only when “description” is in the mask). Max 4096. | |
| visibility | string | New visibility (applied only when “visibility” is in the mask). | |
| pose_order_mode | string | New pose-order mode (applied only when “pose_order_mode” is in the mask). | |
| tags | string | repeated | Replacement tags (applied only when “tags” is in the mask). Max 32. |
| content_warnings | string | repeated | Replacement content warnings (applied only when “content_warnings” is in the mask). Max 32. |
| update_mask | google.protobuf.FieldMask | The set of field paths to apply (snake_case proto names). |
WebUpdateSceneResponse
Section titled “WebUpdateSceneResponse”WebUpdateSceneResponse re-exports the post-update scene from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| scene | holomush.scene.v1.SceneInfo | scene is the scene row after the partial update. |
WebWatchSceneRequest
Section titled “WebWatchSceneRequest”WebWatchSceneRequest proxies to SceneAccessService.WatchScene. player_session_token is injected from the X-Session-Token cookie.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id is the “act as this alt” selector forwarded to the facade. | |
| scene_id | string | scene_id identifies the scene to watch; required. |
WebWatchSceneResponse
Section titled “WebWatchSceneResponse”WebWatchSceneResponse re-exports the observer-join confirmation from the facade.
| Field | Type | Label | Description |
|---|---|---|---|
| participant | holomush.scene.v1.ParticipantInfo | participant is the resulting participant entry (role=observer), or the pre-existing row when the character was already a participant. |
WebWithdrawScenePublishRequest
Section titled “WebWithdrawScenePublishRequest”WebWithdrawScenePublishRequest withdraws an attempt from the web portal.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the client-declared player-session ULID forwarded to the facade. | |
| character_id | string | character_id selects which owned alt acts (server-side ownership verified). | |
| published_scene_id | string | published_scene_id identifies the attempt to abort. |
WebWithdrawScenePublishResponse
Section titled “WebWithdrawScenePublishResponse”WebWithdrawScenePublishResponse is empty.
ControlSignal
Section titled “ControlSignal”ControlSignal is the discriminator on a ControlFrame — an out-of-band stream-lifecycle message multiplexed into the StreamEvents feed alongside game events. The gateway forwards core control signals (internal/web/handler.go::forwardFrame) and also synthesizes STREAM_OPENED itself at attach time.
| Name | Number | Description |
|---|---|---|
| CONTROL_SIGNAL_UNSPECIFIED | 0 | CONTROL_SIGNAL_UNSPECIFIED is the zero value; a well-formed ControlFrame always carries a concrete signal, so this value SHOULD never appear on the wire. |
| CONTROL_SIGNAL_REPLAY_COMPLETE | 1 | CONTROL_SIGNAL_REPLAY_COMPLETE marks the boundary between the historical backfill the core Subscribe handler replays at attach and the live event tail. The accompanying ControlFrame.attach_moment_ms is the upper time bound the client feeds back into WebQueryStreamHistory backfill calls. |
| CONTROL_SIGNAL_STREAM_CLOSED | 2 | CONTROL_SIGNAL_STREAM_CLOSED signals the server is ending the stream; the gateway treats it as a terminal frame (forwardFrame returns errStreamClosed) and runs its session-cleanup Disconnect. |
| CONTROL_SIGNAL_STREAM_OPENED | 3 | STREAM_OPENED is emitted as the first frame after a successful StreamEvents subscription. The accompanying ControlFrame.connection_id is the per-stream ULID — clients SHOULD store it and pass it back via SendCommandRequest.connection_id so the gateway routes per-connection commands (Phase 5 scene-focus autofocus) correctly under multi-tab. |
| CONTROL_SIGNAL_RECONNECTING | 4 | CONTROL_SIGNAL_RECONNECTING: the gateway lost its core stream but is holding the client and reconnecting; the UI shows a reconnecting indicator (holomush-rsoe6). |
| CONTROL_SIGNAL_RECONNECTED | 5 | CONTROL_SIGNAL_RECONNECTED: the gateway re-established the core stream; the client may clear the reconnecting indicator. |
| CONTROL_SIGNAL_SCENE_ACTIVITY | 6 | CONTROL_SIGNAL_SCENE_ACTIVITY notifies the client that a scene it is a member of received an event while this connection was NOT focused on it. Carries scene_id only — no event content. Drives workspace unread badges; lossy by design (clients re-sync via ListMyScenes snapshots). |
EventChannel
Section titled “EventChannel”EventChannel is the web mirror of corev1.EventChannel: it tells the web client which surface a GameEvent should render on. The gateway forwards the core RenderingMetadata.display_target verbatim onto GameEvent.display_target (internal/web/translate.go::translateEvent), so the value semantics are identical to the core enum — no remapping happens at the boundary.
| Name | Number | Description |
|---|---|---|
| EVENT_CHANNEL_UNSPECIFIED | 0 | EVENT_CHANNEL_UNSPECIFIED is the zero value; rendering metadata that omits a display target lands here. The web client SHOULD treat it as a safe default rather than a routing instruction. |
| EVENT_CHANNEL_TERMINAL | 1 | EVENT_CHANNEL_TERMINAL routes the event to the scrolling terminal/log pane — the conversational stream (say, pose, arrive, leave, channel). |
| EVENT_CHANNEL_STATE | 2 | EVENT_CHANNEL_STATE routes the event to the structured state surfaces (location panel, exits, presence) rather than the terminal log. Used by state-category events such as location_state and exit_update. |
| EVENT_CHANNEL_BOTH | 3 | EVENT_CHANNEL_BOTH routes the event to the terminal AND the state surfaces simultaneously. |
| EVENT_CHANNEL_AUDIT_ONLY | 4 | EVENT_CHANNEL_AUDIT_ONLY mirrors corev1.EventChannel for INV-EVENTBUS-16 lockstep parity. These events are dropped at the gRPC Subscribe boundary and MUST NOT appear on the web wire format in practice. |
WebPresenceContext
Section titled “WebPresenceContext”WebPresenceContext is the web mirror of corev1.PresenceContext: it tells the client which kind of focus context a presence snapshot describes. The gateway maps core values 1:1 (internal/web/handler.go::translatePresenceContext).
| Name | Number | Description |
|---|---|---|
| WEB_PRESENCE_CONTEXT_UNSPECIFIED | 0 | WEB_PRESENCE_CONTEXT_UNSPECIFIED is the zero value / fallback for an unrecognized core context. |
| WEB_PRESENCE_CONTEXT_LOCATION | 1 | WEB_PRESENCE_CONTEXT_LOCATION indicates the snapshot is the presence at a location. |
| WEB_PRESENCE_CONTEXT_SCENE | 2 | WEB_PRESENCE_CONTEXT_SCENE indicates the snapshot is the participants of a scene. |
WebPresenceState
Section titled “WebPresenceState”WebPresenceState is the web mirror of corev1.PresenceState: the activity state of one present character. Mapped 1:1 from core (internal/web/handler.go::translatePresenceState).
| Name | Number | Description |
|---|---|---|
| WEB_PRESENCE_STATE_UNSPECIFIED | 0 | WEB_PRESENCE_STATE_UNSPECIFIED is the zero value / fallback for an unrecognized core state. |
| WEB_PRESENCE_STATE_ACTIVE | 1 | WEB_PRESENCE_STATE_ACTIVE means the character has a live, attached connection (grid present). |
| WEB_PRESENCE_STATE_DETACHED | 2 | WEB_PRESENCE_STATE_DETACHED means the session exists but currently has no attached connection. |
| WEB_PRESENCE_STATE_INACTIVE | 3 | WEB_PRESENCE_STATE_INACTIVE means the character is present but idle/not actively connected. |
WebService
Section titled “WebService”WebService is the ConnectRPC surface the SvelteKit web client speaks to. Per the gateway-boundary invariant (.claude/rules/gateway-boundary.md) it is a protocol-translation layer ONLY: every game-state operation proxies to the corresponding CoreService gRPC RPC (content RPCs proxy to ContentService), and the gateway computes no business logic. The web-specific concerns it owns are HTTP↔gRPC framing, cookie↔token translation (internal/web/cookie.go::CookieMiddleware), and per-stream connection identity. Implemented by internal/web.Handler; registered via webv1connect.NewWebServiceHandler in internal/web/server.go.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| SendCommand | SendCommandRequest | SendCommandResponse | SendCommand submits a player’s raw command line (say, pose, quit, …) for parsing and dispatch. Proxies to CoreService.HandleCommand; command-history persistence happens core-side, so the gateway does no extra work. The connection_id ties the command to its originating stream for per-connection routing. |
| StreamEvents | StreamEventsRequest | StreamEventsResponse stream | StreamEvents opens the server-streaming game-event feed for a session. Proxies to CoreService.Subscribe; the gateway generates a per-stream connection_id, emits a synthetic STREAM_OPENED ControlFrame carrying it, forwards translated frames, and runs a best-effort Disconnect on stream exit (client disconnect, cancel, error, or STREAM_CLOSED). Session-store registration/deregistration is owned entirely by core’s Subscribe path. |
| Disconnect | DisconnectRequest | DisconnectResponse | Disconnect ends the game session out of band (not via stream teardown). Proxies to CoreService.Disconnect on a best-effort basis — RPC errors are logged but never surfaced to the caller. Forwards the session-token cookie header so core can enforce ownership. |
| GetCommandHistory | GetCommandHistoryRequest | GetCommandHistoryResponse | GetCommandHistory retrieves the recent command lines a session has entered. Proxies to CoreService.GetCommandHistory; ownership is enforced core-side via the forwarded session-token header, and any failure (error or success=false) collapses to an empty history at the gateway. |
| WebAuthenticatePlayer | WebAuthenticatePlayerRequest | WebAuthenticatePlayerResponse | WebAuthenticatePlayer validates username/password and returns the player’s character roster. Proxies to CoreService.AuthenticatePlayer; on success the gateway translates the core session token into a Set-Cookie signal. First runs the cookie-collision gate, short-circuiting with ALREADY_AUTHENTICATED if a valid session cookie is already present. |
| WebSelectCharacter | WebSelectCharacterRequest | WebSelectCharacterResponse | WebSelectCharacter binds a character to a new or reattached game session. Proxies to CoreService.SelectCharacter using the session token read from the X-Session-Token cookie header; returns the resulting session_id. |
| WebCreatePlayer | WebCreatePlayerRequest | WebCreatePlayerResponse | WebCreatePlayer registers a new player account. Proxies to CoreService.CreatePlayer; on success the gateway sets the session cookie. Runs the cookie-collision gate first (ALREADY_AUTHENTICATED short-circuit). |
| WebCreateGuest | WebCreateGuestRequest | WebCreateGuestResponse | WebCreateGuest provisions an ephemeral guest player and character. Proxies to CoreService.CreateGuest; on success the gateway sets a session cookie whose MaxAge matches the guest session’s shorter TTL. Runs the cookie-collision gate first. |
| WebCreateCharacter | WebCreateCharacterRequest | WebCreateCharacterResponse | WebCreateCharacter adds a character to the authenticated player. Proxies to CoreService.CreateCharacter using the cookie-derived session token. |
| WebListCharacters | WebListCharactersRequest | WebListCharactersResponse | WebListCharacters returns the authenticated player’s character roster. Proxies to CoreService.ListCharacters; an RPC failure is surfaced as CodeUnauthenticated (session expired or invalid). |
| WebListAllCharacters | WebListAllCharactersRequest | WebListAllCharactersResponse | WebListAllCharacters proxies to CoreService.ListAllCharacters. The gateway reads player_session_token from the X-Session-Token cookie; any authenticated caller (guest included) may list character names. |
| WebLogout | WebLogoutRequest | WebLogoutResponse | WebLogout ends the player session and clears the session cookie. Proxies to CoreService.Logout (best-effort) when a token is present, then always emits the cookie-clear signal regardless of the RPC outcome. |
| WebRequestPasswordReset | WebRequestPasswordResetRequest | WebRequestPasswordResetResponse | WebRequestPasswordReset initiates the email-based reset flow. Proxies to CoreService.RequestPasswordReset; to avoid leaking account existence the gateway reports success=true even when the underlying RPC errors. |
| WebConfirmPasswordReset | WebConfirmPasswordResetRequest | WebConfirmPasswordResetResponse | WebConfirmPasswordReset completes a reset using the emailed token and a new password. Proxies to CoreService.ConfirmPasswordReset. |
| WebCheckSession | WebCheckSessionRequest | WebCheckSessionResponse | WebCheckSession validates the player session carried in the cookie and returns the player identity plus character roster, or a CodeUnauthenticated error. Proxies to CoreService.CheckPlayerSession; the web client’s authed layout uses this to gate page loads. |
| WebGetContent | WebGetContentRequest | WebGetContentResponse | WebGetContent fetches a single content-store item by exact key. Proxies to ContentService.GetContent (a separate gRPC service, NOT CoreService); public, no auth. Returns CodeUnimplemented if the gateway was built without a content client. |
| WebListContent | WebListContentRequest | WebListContentResponse | WebListContent lists content-store items under a key prefix. Proxies to ContentService.ListContent (NOT CoreService); public, no auth. Returns CodeUnimplemented if no content client is configured. |
| WebQueryStreamHistory | WebQueryStreamHistoryRequest | WebQueryStreamHistoryResponse | WebQueryStreamHistory reads paginated event history for the web client. Proxies to CoreService.QueryStreamHistory — authorization is enforced by core. |
| WebListSessionStreams | WebListSessionStreamsRequest | WebListSessionStreamsResponse | WebListSessionStreams returns the stream names the session is subscribed to. Proxies to CoreService.ListSessionStreams — authorization is enforced by core. Used by the web client to enumerate streams for reload-backfill. |
| WebListPlayerSessions | WebListPlayerSessionsRequest | WebListPlayerSessionsResponse | WebListPlayerSessions returns the caller’s active PlayerSessions (the device/tab login records, distinct from in-game game sessions), each flagged is_current for the calling session. Proxies to CoreService.ListPlayerSessions; the caller is identified via the X-Session-Token cookie header injected by CookieMiddleware — there is no token field in the request body. |
| WebRevokePlayerSession | WebRevokePlayerSessionRequest | WebRevokePlayerSessionResponse | WebRevokePlayerSession revokes one of the caller’s PlayerSessions by id. Proxies to CoreService.RevokePlayerSession; caller identity comes from the X-Session-Token cookie header. |
| WebRevokeOtherPlayerSessions | WebRevokeOtherPlayerSessionsRequest | WebRevokeOtherPlayerSessionsResponse | WebRevokeOtherPlayerSessions revokes all of the caller’s PlayerSessions except the current one (“log out everywhere else”). Proxies to CoreService.RevokeOtherPlayerSessions; caller identity comes from the X-Session-Token cookie header. |
| WebListFocusPresence | WebListFocusPresenceRequest | WebListFocusPresenceResponse | WebListFocusPresence returns the presence snapshot for the session’s current focus context (location or scene). Proxies to CoreService.ListFocusPresence — authorization is enforced by core. player_session_token is read from the HTTP cookie by gateway middleware. |
| WebListCommands | WebListCommandsRequest | WebListCommandsResponse | WebListCommands returns the recognized-command set + alias map for the session’s character, for the composer’s command chip. Proxies to CoreService.ListAvailableCommands; player_session_token is read from the cookie by gateway middleware. |
| WebListScenes | WebListScenesRequest | WebListScenesResponse | WebListScenes returns the public scene board filtered by the verified player’s content-warning preferences. Proxies to SceneAccessService.ListScenesForViewer; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebGetScene | WebGetSceneRequest | WebGetSceneResponse | WebGetScene loads one scene’s metadata for the verified player’s owned character. Proxies to SceneAccessService.GetSceneForViewer; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebListMyScenes | WebListMyScenesRequest | WebListMyScenesResponse | WebListMyScenes returns every non-archived scene the verified player’s owned character participates in. Proxies to SceneAccessService.ListMyScenes; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebWatchScene | WebWatchSceneRequest | WebWatchSceneResponse | WebWatchScene auto-joins the verified player’s owned character into an open active scene as an observer. Proxies to SceneAccessService.WatchScene; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebCreateScene | WebCreateSceneRequest | WebCreateSceneResponse | WebCreateScene creates a new scene owned by the verified player’s owned character and returns its metadata. Proxies to SceneAccessService.CreateScene; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebEndScene | WebEndSceneRequest | WebEndSceneResponse | WebEndScene proxies to SceneAccessService.EndScene. The gateway reads player_session_token from the X-Session-Token cookie; the facade owns authorization. Returns the post-transition scene. |
| WebPauseScene | WebPauseSceneRequest | WebPauseSceneResponse | WebPauseScene proxies to SceneAccessService.PauseScene (see WebEndScene). |
| WebResumeScene | WebResumeSceneRequest | WebResumeSceneResponse | WebResumeScene proxies to SceneAccessService.ResumeScene (see WebEndScene). |
| WebUpdateScene | WebUpdateSceneRequest | WebUpdateSceneResponse | WebUpdateScene proxies to SceneAccessService.UpdateScene. The gateway reads player_session_token from the X-Session-Token cookie; the facade owns authorization. Returns the post-update scene. |
| WebInviteToScene | WebInviteToSceneRequest | WebInviteToSceneResponse | WebInviteToScene proxies to SceneAccessService.InviteToScene (cookie token). |
| WebKickFromScene | WebKickFromSceneRequest | WebKickFromSceneResponse | WebKickFromScene proxies to SceneAccessService.KickFromScene. |
| WebTransferOwnership | WebTransferOwnershipRequest | WebTransferOwnershipResponse | WebTransferOwnership proxies to SceneAccessService.TransferOwnership. |
| WebLeaveScene | WebLeaveSceneRequest | WebLeaveSceneResponse | WebLeaveScene proxies to SceneAccessService.LeaveScene. |
| WebExportScene | WebExportSceneRequest | WebExportSceneResponse | WebExportScene renders the verified player’s owned character’s scene IC log to a downloadable document. Proxies to SceneAccessService.ExportScene; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebSetSceneFocus | WebSetSceneFocusRequest | WebSetSceneFocusResponse | WebSetSceneFocus sets the per-connection focus for a web portal connection. Proxies to SceneAccessService.SetSceneFocus; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebListPublishedScenes | WebListPublishedScenesRequest | WebListPublishedScenesResponse | WebListPublishedScenes pages through publicly visible PUBLISHED scene archives. Proxies to SceneAccessService.ListPublishedScenes; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebGetPublicSceneArchive | WebGetPublicSceneArchiveRequest | WebGetPublicSceneArchiveResponse | WebGetPublicSceneArchive reads a published scene archive without participant authentication. Proxies to SceneAccessService.GetPublicSceneArchive; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebDownloadPublicSceneArchive | WebDownloadPublicSceneArchiveRequest | WebDownloadPublicSceneArchiveResponse | WebDownloadPublicSceneArchive returns a PUBLISHED scene archive rendered in the requested format. Proxies to SceneAccessService.DownloadPublicSceneArchive; player_session_token is read from the HTTP cookie by gateway middleware. |
| WebStartScenePublish | WebStartScenePublishRequest | WebStartScenePublishResponse | WebStartScenePublish proxies StartScenePublish to the facade. |
| WebCastPublishSceneVote | WebCastPublishSceneVoteRequest | WebCastPublishSceneVoteResponse | WebCastPublishSceneVote proxies CastPublishSceneVote to the facade. |
| WebWithdrawScenePublish | WebWithdrawScenePublishRequest | WebWithdrawScenePublishResponse | WebWithdrawScenePublish proxies WithdrawScenePublish to the facade. |
| WebGetPublishedScene | WebGetPublishedSceneRequest | WebGetPublishedSceneResponse | WebGetPublishedScene proxies GetPublishedScene (cold-start tally snapshot). |
holomush/world/v1/world.proto
Section titled “holomush/world/v1/world.proto”CharacterInfo
Section titled “CharacterInfo”CharacterInfo carries the public attributes of a player character. A character is the in-game entity controlled by a player account. One player may have multiple characters. The location_id is empty when the character is not currently placed in the world (i.e., not logged in or not yet in a location).
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the character, generated by idgen.New() at creation time. | |
| player_id | string | ULID of the player account that owns this character. | |
| name | string | Human-readable display name used for emotes, location descriptions, and character lists. | |
| description | string | Prose description shown when another character looks at this character. | |
| location_id | string | ULID of the location where the character is currently placed, or empty string if the character is not in the world. Corresponds to the nullable world.Character.LocationID field in internal/world/character.go. |
ExitInfo
Section titled “ExitInfo”ExitInfo carries the public attributes of a directional connection between two locations. An exit has a source location (from_location_id) and a destination (to_location_id). When bidirectional is true, traversing the exit in the reverse direction uses return_name as the visible direction label. The locked field reflects the current lock state; the lock mechanism details (key, password, condition) are not exposed through this RPC surface.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the exit, generated by idgen.New() at creation time. | |
| name | string | Direction or display name for the exit as seen from from_location_id (e.g., “north”, “out”). | |
| from_location_id | string | ULID of the location from which this exit originates. | |
| to_location_id | string | ULID of the location this exit leads to. | |
| bidirectional | bool | Whether a corresponding reverse exit exists. When true, return_name holds the direction label used when travelling from to_location_id back to from_location_id. | |
| return_name | string | Direction label for the reverse leg of a bidirectional exit (e.g., “south”). Empty string when bidirectional is false. | |
| locked | bool | Whether the exit is currently locked. When locked, movement through this exit is blocked unless the character satisfies the lock condition evaluated by the movement subsystem. |
GetCharacterRequest
Section titled “GetCharacterRequest”GetCharacterRequest identifies a character to fetch and the querying character for access-control evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| subject_id | string | ULID of the character performing the query. Passed to the ABAC engine as subject “character:<subject_id>” to check the “read” action on the character resource. | |
| character_id | string | ULID of the character to retrieve. Must be a valid strict ULID; malformed values result in codes.InvalidArgument. |
GetCharacterResponse
Section titled “GetCharacterResponse”GetCharacterResponse wraps the character returned by GetCharacter. The character field is always populated on a successful response.
| Field | Type | Label | Description |
|---|---|---|---|
| character | CharacterInfo | The requested character. |
GetLocationRequest
Section titled “GetLocationRequest”GetLocationRequest identifies a location to fetch and the character performing the lookup for access-control evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| subject_id | string | ULID of the character performing the query. Passed to the ABAC engine as subject “character:<subject_id>” to check the “read” action on the location resource. | |
| location_id | string | ULID of the location to retrieve. Must be a valid strict ULID; malformed values result in codes.InvalidArgument. |
GetLocationResponse
Section titled “GetLocationResponse”GetLocationResponse wraps the location returned by GetLocation. The location field is always populated on a successful response.
| Field | Type | Label | Description |
|---|---|---|---|
| location | LocationInfo | The requested location. |
ListCharactersAtLocationRequest
Section titled “ListCharactersAtLocationRequest”ListCharactersAtLocationRequest identifies the location to query and the character performing the query for access-control evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| subject_id | string | ULID of the character performing the query. Passed to the ABAC engine as subject “character:<subject_id>” to check the “list_characters” action on the location resource (per ADR #76 decomposition). | |
| location_id | string | ULID of the location whose character roster is requested. Must be a valid strict ULID; malformed values result in codes.InvalidArgument. |
ListCharactersAtLocationResponse
Section titled “ListCharactersAtLocationResponse”ListCharactersAtLocationResponse carries all characters currently placed at the requested location. The characters list is empty (not absent) when no characters are present; the field is never null.
| Field | Type | Label | Description |
|---|---|---|---|
| characters | CharacterInfo | repeated | Characters currently placed at the queried location. Order is not guaranteed; callers that need a stable display order should sort by name. |
ListExitsRequest
Section titled “ListExitsRequest”ListExitsRequest identifies the location whose exits are requested and the character performing the query for access-control evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| subject_id | string | ULID of the character performing the query. Passed to the ABAC engine as subject “character:<subject_id>” to check the “read” action on the location resource. | |
| location_id | string | ULID of the location whose exits are requested. Must be a valid strict ULID; malformed values result in codes.InvalidArgument. |
ListExitsResponse
Section titled “ListExitsResponse”ListExitsResponse carries all exits originating from the requested location. The exits list is empty (not absent) when the location has no exits; the field is never null.
| Field | Type | Label | Description |
|---|---|---|---|
| exits | ExitInfo | repeated | Exits originating from the queried location. Order is not guaranteed; callers that need a stable display order should sort by name. |
LocationInfo
Section titled “LocationInfo”LocationInfo carries the public attributes of a world location. A location is a named place that can contain characters, objects, and exits. The type field distinguishes persistent grid locations from temporary scene locations and instanced copies. The owner_id is empty for unowned locations; ownership affects exit visibility rules evaluated server-side.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | ULID of the location, generated by idgen.New() at creation time. | |
| name | string | Human-readable display name shown in location descriptions and exit listings. | |
| description | string | Prose description shown to characters entering or looking at the location. | |
| type | string | Spatial category of the location. One of “persistent” (permanent grid location), “scene” (ephemeral RP space cloned from a persistent location), or “instance” (private copy). Corresponds to world.LocationType in internal/world/location.go. | |
| owner_id | string | ULID of the player account that owns this location, or empty string if the location is unowned. Ownership influences exit visibility when the exit’s visibility is set to “owner”. |
WorldService
Section titled “WorldService”WorldService provides read-only world model queries for binary plugins. It is served on an in-process gRPC connection registered in the plugin service registry as “holomush.world.v1.WorldService” (see internal/plugin/setup/world_conn.go::newWorldInProcessConn). Every RPC enforces ABAC by passing subject_id through world.Service, which delegates to the configured access.PolicyEngine before touching any repository. Errors that indicate missing records map to codes.NotFound; denied access maps to codes.PermissionDenied; all other failures map to codes.Internal with no internal detail leaked to callers.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetLocation | GetLocationRequest | GetLocationResponse | GetLocation fetches a single location by ULID. The caller must hold the “read” permission on the location resource. Returns codes.NotFound if the location does not exist and codes.PermissionDenied if access is denied. |
| GetCharacter | GetCharacterRequest | GetCharacterResponse | GetCharacter fetches a single character by ULID. The caller must hold the “read” permission on the character resource. Returns codes.NotFound if the character does not exist and codes.PermissionDenied if access is denied. |
| ListCharactersAtLocation | ListCharactersAtLocationRequest | ListCharactersAtLocationResponse | ListCharactersAtLocation returns all characters whose current location matches location_id. The caller must hold the “list_characters” permission on the location resource (action=list_characters, resource=location:<id>, per ADR #76 compound-resource decomposition). Returns an empty list when no characters are present; never returns codes.NotFound for an empty location. |
| ListExits | ListExitsRequest | ListExitsResponse | ListExits returns all exits originating from a location. The caller must hold the “read” permission on the location resource. Returns an empty list when the location has no exits; never returns codes.NotFound for an empty exit set. |
Scalar Value Types
Section titled “Scalar Value Types”| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | double | float | Float | |
| float | float | float | float | float32 | float | float | Float | |
| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| bool | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |