Status: Variant B is implemented and disabled by default. This proposal is retained as the design record; enabling the game remains a separate, room-specific operator decision.
Archived: 2026-10-16 (UTC). See the live feature guide for current runtime behavior and release status.
Implement an independent, room-scoped social guessing game. Participants privately answer a curated adult prompt, the bot publishes that answer without naming its author, and opted-in room members make validated accusations before a deadline. A correct accusation ends the anonymous phase; if nobody identifies the author and quorum is met, the submitter may receive a configured bonus from the shared economy.
There is a direct conflict between two requirements:
This design does not authorize persistent storage of submissions. Variant B was selected: free text is bounded and ephemeral in process memory, while session metadata and the curated prompt catalog are database-backed.
This alternative was not selected. Curate approved secret-like statements in
prototypeWhispererPrompts; players privately submit a prompt ID or choose
from the approved catalog. The bot posts the selected record and room members
accuse the opted-in submitter. This preserves DB-backed content, room config,
and durable recovery, but it is not a personal free-text confession game.
Selected and implemented. This variant accepts an explicit, game-prefixed private Whisper, but keeps its content only in a bounded process-memory buffer until the round resolves. MongoDB stores session metadata, author member number, prompt/config IDs, phase, deadline, and outcome, never the text. The anonymous feature holds an exclusive per-room owner lease; a second process cannot serve the same room. After a process restart or lease loss, the active round is cancelled, in-memory text is discarded, and no bonus/penalty is claimed. The ten curated database prompts seed the round topic; they do not replace the private free-text response.
If persistent free-text queues are essential, the current storage prohibition must be formally changed first. That requires a separate privacy/security review, explicit retention/key management, access controls, backup/restore analysis, and consent language. Do not implement this variant under the current repository rules.
@user string as identity.identified; otherwise award
the submitter’s bounded configured bonus once. A missing quorum voids the
economic result.dispatched_unconfirmed semantics. The game never claims delivery was
acknowledged merely because a send was dispatched.Use a typed per-room record in prototypePartyGameConfigs with game ID
<roomKey>:anonymous-whisperer. Missing or invalid config disables the game.
Settings are all database values with code-level bounds:
| Setting | Proposed validation / intent |
|---|---|
enabled |
Explicit room-level opt-in; false when absent. |
minPlayers, maxPlayers |
Bounded opted-in roster. |
submissionWindowMs, accusationWindowMs |
Positive bounded durations; persisted deadlines. |
maxSubmissionLength |
Positive value no higher than a small hard code-level ceiling. |
| Submission bytes | Hard code-level ceiling of 1,024 UTF-8 bytes. |
minAccusations |
Quorum required before an economic bonus can be awarded. |
submitterBonusCredits |
Bounded non-negative integer, idempotent per submission. |
followupMode |
disabled only in this implementation. |
prototypeWhispererPrompts contains reviewed, approved, non-identifying round
prompts and revisions; startup seeds ten adult-oriented conversation prompts
without replacing operator edits. Optional follow-up is disabled. User-provided
text remains memory-only. The session stores only a prompt ID, author member
number, bounded accusations, config revision, deadlines, outcome, and stable
settlement ID. It never stores the free-text submission.
prototypeWhispererSessions is room-scoped, bounded, and revisioned. Use a
unique active key so there is one session per room. Variant B acquires a
single-owner lease and keeps the ephemeral text associated with that lease in
process memory; the lease is not permission to persist or log the text. Lease
loss or process restart voids the active session and discards the buffer. The
text is not reconstructed from chat history. A separate per-room lease record
coordinates ownership.
Accusations and closure use compare-and-set revision transitions. Repeated accusations, duplicate timer callbacks, and competing process instances cannot pay twice. Persist the terminal resolution before an idempotent submitter bonus. An interrupted bonus remains pending and is retried with the same operation ID. A departing author before publication voids the round; after publication, use the configured deadline and do not infer a correct or incorrect guess from departure.
For Variant B, implement a redacted submission boundary separate from generic command tracing/debug output. The raw message must never enter a journal, debug document, structured log, error string, test snapshot, or Mongo document. Bound memory capacity, clear buffers on every terminal path, disconnect, shutdown, lease loss, and failed startup.
flowchart LR
Root["start.ts"] --> Plugin["AnonymousWhispererPlugin"]
Plugin --> Input["WhispererSubmissionHandler"]
Plugin --> Commands["WhispererCommandSuite"]
Plugin --> Service["WhispererService"]
Input --> Service
Commands --> Parser["BotCommandParser"]
Commands --> Service
Service --> Repo["WhispererRepository"]
Service --> Economy["Injected EconomyPort"]
Service --> Comms["Injected CharacterCommunicationApi"]
Repo --> Config[("prototypePartyGameConfigs<br/>room-scoped")]
Repo --> Catalog[("prototypeWhispererPrompts<br/>10 curated round prompts")]
Repo --> Sessions[("prototypeWhispererSessions<br/>metadata only")]
Repo --> Lease[("prototypeWhispererLeases")]
Service -. "bounded ephemeral text" .-> Memory["Process-memory buffer"]
Comms --> BC["Existing BcClient actions"]
classDiagram
class AnonymousWhispererPlugin {
+start()
+stop()
}
class WhispererSubmissionHandler {
+handle(message)
}
class WhispererCommandSuite {
+register()
}
class WhispererService {
+start(ownerId)
+join(memberNumber)
+leave(memberNumber)
+startRound(memberNumber)
+submit(memberNumber, text)
+accuse(memberNumber, target)
+stop()
}
class WhispererRepository {
<<Mongo adapter; metadata only>>
+loadConfig()
+loadPrompts()
+createSession(session)
+compareAndSetSession(expectedRevision)
+acquireLease(ownerId)
}
class EconomyPort {
<<injected shared contract>>
+grantOnce(operationId, member, amount, reason)
}
class CharacterCommunicationApi {
<<injected shared API>>
+chatRedacted(text)
+whisper(memberNumber, text)
}
class BotCommandParser {
<<core command router>>
+register(command, description, handler)
}
AnonymousWhispererPlugin --> WhispererSubmissionHandler
AnonymousWhispererPlugin --> WhispererCommandSuite
AnonymousWhispererPlugin --> WhispererService
WhispererSubmissionHandler --> WhispererService
WhispererCommandSuite --> WhispererService
WhispererService --> WhispererRepository
WhispererService --> EconomyPort
WhispererService --> CharacterCommunicationApi
WhispererCommandSuite --> BotCommandParser
flowchart LR
Admin((Room admin)) --> Enable([Enable room and consent rules])
Submitter((Opted-in submitter)) --> Submit([Answer curated prompt by explicit private text])
Voter((Opted-in voter)) --> Accuse([Accuse observed room member])
Timer((Deadline worker)) --> Resolve([Resolve quorum and author match])
Submit --> Session[(Metadata-only active session)]
Accuse --> Session
Resolve --> Outcome([Identify author or grant bonus once])
flowchart TD
Start([Round requested]) --> Config{Enabled config + adults-only confirmation?}
Config -->|No| Block[Do not accept submissions]
Config -->|Yes| Lobby[Require enough opted-in present members and room lease]
Lobby --> Catalog[Select reviewed DB prompt and submitter]
Catalog --> Input[Accept explicit private Whisper and publication consent]
Input --> Memory[Keep validated text in bounded process memory only]
Memory --> Publish[Publish through redacted action without author]
Publish --> Phase[CAS session to accusation phase]
Phase --> Accuse[Accept validated member-number accusations]
Accuse --> Deadline{Deadline reached?}
Deadline -->|No| Accuse
Deadline -->|Yes| Quorum{Minimum valid accusations met?}
Quorum -->|No| Void[Persist void outcome; no award]
Quorum -->|Yes| Match{Any valid accusation matches author?}
Match -->|Yes| Identified[Persist identified outcome]
Match -->|No| Bonus[Persist outcome and grant submitter bonus once]
Identified --> Clear[Clear ephemeral content]
Bonus --> Clear
Void --> Clear
Clear --> Finish([Close session])
Block --> Finish
stateDiagram-v2
[*] --> Disabled
Disabled --> Lobby: enabled room config + adults-only confirmation + lease
Lobby --> Submission: enough opted-in members; prompt privately dispatched
Submission --> Accusing: valid consented response published
Submission --> Voided: timeout / author departure / restart / disconnect / lease loss
Accusing --> Accusing: valid accusation before deadline
Accusing --> Identified: author correctly accused
Accusing --> Voided: deadline without quorum
Accusing --> BonusPending: quorum; no correct accusation
BonusPending --> Complete: idempotent bonus result recorded
BonusPending --> BonusPending: settlement failure; retry same operation ID
Identified --> Complete
Voided --> Complete: clear ephemeral buffer and close
Complete --> [*]
sequenceDiagram
participant Submitter
participant Input as Redacted private-input boundary
participant Service as WhispererService
participant DB as Metadata repository
participant Memory as Bounded process memory
participant Voter
participant Cmd as WhispererCommandSuite
participant Economy as EconomyPort
participant BC as CharacterCommunicationApi
Submitter->>Cmd: !whisperer start
Cmd->>Service: startRound(member)
Service->>DB: create metadata-only round
Service->>BC: privately dispatch selected prompt (unconfirmed)
Submitter->>Input: direct Whisper with explicit publication consent
Input->>Service: sender + transient text
Service->>Memory: hold transient content under active lease
Service->>BC: publish content without author (unconfirmed dispatch)
Service->>DB: CAS session to accusation phase
Voter->>Cmd: accuse <memberNumber>
Cmd->>Service: validated observed-room target
Service->>DB: CAS bounded accusation
Service->>DB: persist resolution before settlement
alt correct accusation
Service->>BC: announce identified outcome
else no correct accusation and quorum met
Service->>Economy: grantOnce(stable submission operation ID)
else below quorum / lease lost
Service->>DB: persist void; no economic mutation
end
Service->>Memory: clear content on every terminal path
Variant B is selected. Defaults are disabled, adults-only room confirmation is required, three minimum and twenty maximum players, two-minute phase windows, 280 characters / 1,024 UTF-8 bytes maximum submission, two accusations for quorum, a ten-credit bonus bounded to 100, and no follow-up. Participants self-affirm adult status and consent; the bot does not infer age. Prompt review remains operator-owned.
Acceptance tests cover accusation validation, observed-roster checks, revision transitions, early/duplicate timer callbacks, exclusive lease ownership, restart recovery, idempotent pending bonuses, and the absence of submission canaries from session documents and command traces. Redacted-action tests verify transport errors cannot place room submission text in the action journal. The runtime remains disabled until each target room is explicitly configured and separately qualified. Variant C remains blocked pending a formal privacy/security exception.