Transport
Internal
The module is not re-exported through require(Operator) and is not part of the public API. Start builds one and puts the instance on the handle as handle.transport, which is how you reach it. Prefer handle.refresh and handle.refreshAll over calling it directly. The internal path is ServerStorage.Operator.Server.Transport.
Delivers a private console bundle to each authorised player, and carries dispatches back.
Nothing sits in ReplicatedStorage and nothing is replicated to everyone. See Delivery for what a player actually receives.
Functions
Transport.new
Transport.new(options: Options): Transportlocal transport = Transport.new({
packageRoot = ServerStorage.Operator,
registry = registry,
dispatcher = dispatcher,
canRun = function(player, command)
return roles:canRun(player, command)
end,
roles = roles,
})
transport:start()Methods
:start
transport:start(): ()Begins delivering. Existing players are evaluated immediately and joiners as they arrive.
:stop
transport:stop(): ()Tears everything down: every bundle removed, every remote destroyed, every connection dropped.
:refresh
transport:refresh(player: Player): ()Re-runs canRun, rebuilds that player's manifest and sends it.
- lost access entirely → the bundle is removed and the remote destroyed
- just gained access → the bundle is delivered
- already had it → only the manifest is resent
The bundle is never delivered twice, including across respawn, repeated refreshes, and regaining access.
With roles passed to new, this invalidates that player's cached roles first, so one call re-resolves, rebuilds and sends.
:refreshAll
transport:refreshAll(): ():isDelivered
transport:isDelivered(player: Player): booleanWhether that player currently holds a bundle.
Types
Options
type Options = {
packageRoot: Instance,
registry: any,
dispatcher: any,
canRun: Predicate,
consumerTypes: Instance?,
consumerMount: Instance?,
roles: any?,
clientSettings: any?,
serializer: Serializer?,
logger: Log.Logger?,
actionLog: ActionLog?,
canViewLogs: ((player: Player) -> boolean)?,
clientCommands: Instance?,
audit: Audit?,
}| Field | Meaning |
|---|---|
packageRoot | the Operator instance to clone Shared and Client from |
registry | the Registry the manifest is built from |
dispatcher | the Dispatcher a dispatch is run through |
canRun | (player, command) -> boolean, asked once per command per player |
consumerTypes | your argument type modules, delivered to authorised clients |
consumerMount | your own interface module, for Client.Mount |
roles | a Roles so refresh can invalidate its cache |
clientSettings | the Client config table sent with the bundle |
serializer | serialize / deserialize for remote payloads |
logger | a Logger |
actionLog | an ActionLog the log panel reads from |
canViewLogs | (player) -> boolean, gating log reads; without it, nobody can read logs |
clientCommands | your client command modules, cloned into the bundle |
audit | an Audit that client-reported records are written into |
canRun decides both what goes in that player's manifest and whether a dispatch is allowed, so there is a single answer to "may this player use this command".
Predicate
type Predicate = (player: Player, command: Command) -> booleanWhat the server enforces
The client parse is a convenience and is never trusted. A dispatch carries raw text only; the server re-tokenizes, re-binds, re-transforms and re-runs every guard from scratch, and re-resolves player arguments at execution time so a target who left is caught.
Every remote message is checked for shape and type, and each player is rate limited.
Rate limits
Three independent token buckets per player, so one kind of traffic can never starve another:
| Traffic | Burst | Refill | Exceeding it |
|---|---|---|---|
| Command dispatches | 10 | 2 / second | RateLimited result, player logged |
| Log queries | 4 | 1 per 4 seconds | RateLimited page |
| Client command reports | 12 | 2 / second | report dropped; the command still ran |
Log queries are deliberately the tightest, because one query can read the whole buffer. Client command reports get their own bucket rather than sharing the dispatch one, so heavy client command use cannot exhaust a moderator's ability to run real commands.
The manifest is display, not permission
A command the player is not allowed to run comes back as NotFound, not Denied, so probing the remote with guessed names cannot enumerate commands the player was never shown. Permission is re-checked on every dispatch independently of what was delivered.