Files

115 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Moderation runtime
T5 and T6 use the same protocol and native policy core, with separate native
chat opcodes and existing engine symbols. Install the game's new moderation
script 1.0.0, common 1.2.0, and native plugin 1.3.0. The controller must implement
`POST /api/moderation/runtime` before enabling the scripts.
## Private configuration
Each instance receives its own non-shared credential. Keep this configuration
out of shared scripts, release artifacts, ordinary config exports, and logs:
```cfg
set tws_server_id "registered-server"
set tws_moderation_url "https://controller.example/api/moderation/runtime"
set tws_moderation_token "ec_moderation_REPLACE_WITH_INSTANCE_SECRET"
set tws_moderation_generation "1"
```
Set `tws_moderation_enabled "0"` to disable the runtime. Missing or invalid
configuration leaves it waiting; it retries every three seconds so private configuration can be installed without a map restart. Disabling stops synchronization and enforcement; it does not revoke controller sanctions or promise to restore existing engine mutes. HTTPS is mandatory; redirects are not followed.
The generation must match the controller's live instance. Only that instance's
credential authorizes its roster, receipts and policy; the bank and telemetry
credentials cannot authorize moderation.
## Synchronization
Native HTTP runs outside the game thread with 3-second connection and 5-second
transfer deadlines. Polls start at most once per 3 seconds; only one poll is in
flight. Responses are limited to 1 MiB at the transport (oversize fails without
truncation), 1000 sanctions and 100 actions. The native cache never crosses a GSC
string boundary. Requests include protocol 1, server, generation, runtime session, sessionSequence,
players `{guid,name,connection}`, capabilities, appliedRevision and up to 256
receipts. Sessions change on map/process startup. A positive safe-integer sessionSequence is incremented and atomically persisted before each new runtime starts; the controller rejects older sequences and conflicting sessions at the same sequence. Repeated unchanged configuration preserves the current sequence. Never share an instance credential or its cache directory between simultaneous game-server processes. Connections change on every
native connect/disconnect event, even when the same GUID reuses the same slot.
GUIDs remain strings; pure decimal IDs have leading zeroes removed without a numeric conversion; the T5 script reads getGuid and the T6 script reads the
stock persistent guid field. GUIDs are limited to 128 ASCII identifier bytes. Roster names are bounded to 255 UTF-8 bytes; malformed name bytes become question marks and ASCII controls become spaces. Blank names, including Unicode whitespace-only names, become Unknown player. Names never select an action target.
Responses contain `{ok,revision,serverTime,sanctions,actions,pollAfterMs}`.
Sanctions contain `{id,revision,guid,kind,reason,expiresAt}`, with `kind` ban/mute
and nullable expiry. Actions contain `{id,guid,connection,kind,reason,expiresAt}`;
kind is warn/message/kick/announce. Only announce uses null GUID and connection.
Deadlines and policy expiry are UTC ISO timestamps. Clock skew over two minutes
rejects an update. Received server time is anchored to monotonic elapsed time without moving backward between polls, conservatively including the full transfer age and a one-second timestamp rounding margin, so a slow or backward-jumping host clock cannot extend one-time deadlines. This may expire an action or timed policy slightly early. Deadlines are checked again after writing the action journal and before dispatch. Revision rollback, inconsistent policy at the same revision,
malformed data, duplicate IDs and changed action content reject an update.
Removing sanctions already expired according to the validated serverTime is permitted without a revision increment.
The current client uses its fixed 3-second poll interval after validating the
server's pollAfterMs bounds (1000–60000).
## Enforcement and recovery
Keep `tws-moderation/t5/` or `tws-moderation/t6/`, relative to the game process
working directory, on persistent writable storage. Files are scoped to server,
endpoint, instance generation and SHA-256 credential fingerprint and do not contain the credential. Complete
snapshots replace files atomically. Every write verifies ownership of the current session sequence so a superseded runtime cannot roll the counter or policy backward. Corrupt cache disables startup and preserves
the file; storage failures are logged and block new effectful actions. Cache loss must be recovered by rebuilding the controller instance generation and issuing a new private credential; resetting or deleting a cache under an existing credential cannot reset the controller's highest session sequence. Retired
instance generations cannot inherit a newer generation's policy cache.
Cached bans and mutes continue during controller outages and expire using the
host wall clock until synchronized, then the later of host time and the monotonic server-time estimate. After a restart without connectivity, host clock drift remains an operator responsibility. Ban enforcement is a kick after connection, checked through the
shared connected dispatcher and a 500 ms loop; it is not a pre-authentication
admission hook. Still-connected banned players are checked again every five
seconds. Removing the last applicable mute restores clients muted by this
runtime; unrelated manually applied mutes are not deliberately cleared. Managed
mute ownership survives process/map restart so revoked cached mutes can be
removed when the affected player returns. No native ban.txt is written.
One-time actions require the exact current connection and unexpired deadline.
The native game-thread adapter validates the target and executes immediately;
GSC's buffered executeCommand is never used. Only fixed kick/mute/unmute command
templates are available; untrusted text cannot add delimiters, quotes, line
breaks or backslash escapes. Visible text is bounded to 240 UTF-8 bytes without splitting codepoints. Messages use direct per-game server message calls.
Before one-time dispatch, the action and an unknown receipt are saved durably.
Duplicate IDs never execute again, including after restart. A crash between
journal write and dispatch may therefore lose a one-time action; its result
stays unknown instead of risking a duplicate kick. Journal entries expire from
storage only after receipt acknowledgment and at least a day past their action
deadline. The journal is bounded at 10000 entries; saturation stops new actions
and requires controller delivery/storage investigation.
A confirmed private-message receipt means the game server dispatched it, not
that the player read it. A kick is confirmed only if its synchronous dispatch
observes the native disconnect callback; otherwise the result is unknown.
Native mute commands have no readback, so appliedRevision means the policy is
cached and being enforced, not independently verified voice/text suppression.
Do not describe a cached policy revision as proven enforcement.
## Validation and rollout
Portable checks:
```sh
node tools/check_moderation.mjs
python3 tools/test_plugin_moderation.py
python3 tools/test_plugin_http.py
python3 tools/test_plugin_component.py
```
The native moderation runner accepts `--json-header '/path/{game}-json.hpp'`
when dependencies have not been restored. Use each plugin's pinned JSON revision.
It compiles the actual core and registration/HTTP bridge against minimal engine
shims with ASan/UBSan, including separate process persistence tests. These checks
do not validate Windows linking or the Plutonium ABI.
Build both x86 DLLs using the existing Windows build workflow. Deploy the new
plugins and scripts together, then fully restart each game-server process; a map
restart alone cannot load the native methods. Test in isolated T5 Zombies and T6
Zombies instances before fleet rollout: duplicate names, same-account reconnect,
slot reuse, kick command/reason behavior, text and voice mute/unmute, reconnect
and map/process restart, expiry, revocation, controller outage, credential
revocation, truncated/oversized responses and disk failure. Verify clientkick and
muteclient command support in the deployed Plutonium revisions. If unsupported,
keep moderation disabled; this change has no automatic production deployment.