Repository navigation
EN Bridge Connector SDK
Français · Type: reference · Audience: contributor · Context: Reality Bridge · Status: ready · Applies from: 4.2.5
The connectors (email, Telnet, SSH, Open Badges) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Node 22 on Ubuntu (Telnet and SSH not on Windows) · automated checks: jobs
connectors,connector-archive-e2e; CI planreality; 3 test files · people's passes:connectors-real-securitynot run (#79) · known limits:bridge-sdk-not-a-package-api
What every connector shares (connectors/src/sdk.ts, ADR 0008): the interface a connector implements, the context
it is given, the vocabulary of refusals, the limits, the helpers, and how the connectors are tested without a network.
For contributors who change a connector or add one; operators read Connectors overview.
Limit ·
bridge-sdk-not-a-package-apiThe SDK is not a public API of a package:quest-n-click-connectorsexports its command only (./run), and the four connectors are listed by id inconnectors/src/registry.ts. A new connector is a change to the engine repository, with its threat model and its tests, not a plugin loaded from outside.
feature since 4.1.9
interface RealityConnector {
readonly id: string;
start(ctx: ConnectorContext): Promise<void>;
stop(): Promise<void>;
health(): Promise<{ ok: boolean; detail: string; since: string }>;
}-
start: open its port or begin polling, with the context below. -
stop: stop taking input, let what is in flight finish (at mostDRAIN_MS, 5 000 ms), close every socket. -
health: whatGET /healthanswers (200 whenok, 503 otherwise; ahealththat throws is unhealthy).
A connector receives, validates and normalises its input, treats it as hostile, binds it to a player, gives it a
dedupeKey, and proposes; it never runs what it reads, and never sends the Bridge what it saw.
The Telnet connector, connectors/src/telnet/connector.ts, is the shortest of the four.
Not applicable: an interface of the code.
4.1.9.
feature since 4.1.9
Made by createContext({ bridge, gameId, tenantId?, limits?, manifest, connector, write?, now?, retries?, fetch? }).
| Member | What |
|---|---|
bridge |
{ url, token }: the Bridge and the connector's attenuated Biscuit |
gameId, tenantId
|
The game; the tenant (default) |
limits |
ConnectorLimits |
manifest |
The game's Reality manifest: its signals and connectors declarations |
log(event, fields?) |
A JSON line that cannot carry content (see safeLog) |
propose(signal) |
ProposedSignal → { ok: true, sequence, duplicate } or { ok: false, refusal }
|
pair(code) |
Confirms a pairing code (the token must carry pair(true)) → { ok: true, playerId } or a refusal |
reject(reason, fields?) |
An input turned down by the connector itself: counted and logged |
metrics() |
{ proposed, accepted, duplicates, refused, rejectedInputs, retries }, for /metrics
|
propose refuses before the Bridge a kind or a player that is not an identifier, a dedupeKey over 256 characters
(shape), a signal the manifest does not declare (not-declared), a payload that is not flat or is too large, and a
proposal over the local quota (quota). A call that times out, loses its connection, answers 5xx or answers an
acceptance without its sequence is sent again with the same key, retries times (2 by default); a refusal is never
retried. After close(), every call is refused stopped.
const r = await ctx.propose({
source: 'telnet', kind: 'terminal.lamp', playerId, dedupeKey: dedupeKey('telnet', `${session}:${line}`),
payload: { command: 0, line }, receivedAt: new Date().toISOString(),
});An answer from the Bridge that is not a JSON object reads as {}.
4.1.9.
feature since 4.1.9
{ source, kind, playerId, sessionId?, dedupeKey, payload, receivedAt }.
-
source: the connector family, informative; the Bridge checks the manifest's. -
kind: the signal, as the game declares it. -
dedupeKey: the same fact twice has the same key,dedupeKey(source, external id). -
payload: flat JSON (strings, numbers, booleans, null), no markup (<or>), at mostlimits.maxBytes. It stays in the connector; the Bridge receives its SHA-256 over canonical JSON asevidenceHash. -
receivedAt: ISO time on the connector's clock, sent asoccurredAt(the Bridge stamps its own).
A nested, marked-up or oversized payload is refused (shape, too-large).
See ConnectorContext.
Not sent: the SDK builds the Bridge's body from the fields above only.
4.1.9.
feature since 4.1.9
A fixed vocabulary, never the Bridge's free text.
| Code | From |
|---|---|
shape, too-large, quota, not-declared, stopped
|
The SDK, before the Bridge (shape also for a Bridge's 400, too-large for its 413) |
denied |
The Bridge's 401 or 403 |
revoked |
The Bridge's 401 revoked
|
player |
The Bridge's 404 for a player |
pairing |
A malformed code; the Bridge's 404 for a code, 409 or 410 |
pending |
The Bridge's 429 pending (the player's backlog) |
bridge-quota |
The Bridge's other 429 |
timeout, unreachable
|
No answer within timeoutMs; no connection |
bridge |
Another status, or a 5xx after the retries |
Counted in metrics().refused and logged as signal.refused with the code.
if (!r.ok && r.refusal === 'pending') … (the player has too many signals waiting).
A status the table does not name is bridge.
4.1.9.
feature since 4.1.9
{ maxBytes, maxPerMinute, timeoutMs }, the configuration's limits.
maxBytes 4 096 (a payload as canonical JSON, and the largest input read at once), maxPerMinute 60 (proposals per
sliding minute, all players together; the Bridge has its own quota per token), timeoutMs 5 000 (one call to the
Bridge).
A limit left out keeps its default.
"limits": { "maxBytes": 4096, "maxPerMinute": 60, "timeoutMs": 5000 }Ignored.
4.1.9.
feature since 4.1.9
dedupeKey(source, external), canonicalJson(value), payloadProblem(payload, maxBytes), safeLog(write, connector), within(work, ms), DRAIN_MS, DEFAULT_LIMITS.
-
dedupeKey: SHA-256 of<source>:<its id outside>, in hexadecimal. -
canonicalJson: JSON with sorted keys, so a payload's hash does not depend on the order it was built in. -
payloadProblem:'shape','too-large'ornull. -
safeLog: numbers and booleans as they are; a string only when it is an identifier (letters, digits,.,_,:,-, at most 64), never with an@, a space or a slash; anything else is[redacted]. Field names that are not identifiers are dropped. -
within: resolvesdoneortimeout, whichever comes first (a drain never holds a stop longer).
None.
dedupeKey('email', messageId) is the email connector's key.
Not applicable.
4.1.9.
All in the engine repository, one suite at a time:
# in the engine repository
npx vitest run tests/connectors-*.test.ts
npm run fuzz:connectors -- --cases=200 --seconds=30 --seed=1-
tests/connectors-*.test.tsrun the same contract on the four connectors against a real Bridge on a free port (tests/connectors-sdk.test.ts), their hostile fixtures (connectors/test-vectors/), Telnet and SSH under floods, slow clients and a thousand connections, and the four driven under--disallow-code-generation-from-strings. -
npm run fuzz:connectorsfeeds the connectors' parsers seeded mutations of their corpus (--connector=email|telnet|ssh|open-badge|all,--cases10 000 by default,--seconds60,--seed,--json). - A game's recorded replays (
games/<id>/replays/*.json: connector inputs and the signals they made) are replayed through the real connector code bytests/connectors-replays.test.ts, and proved finishable bynpm run solve:reality. -
npm run connector-archive-e2eplays the four from their release archive against a Bridge from its own (Linux).
Guarantee ·
bridge-sdk-same-key-one-effect· referenced · scope self-checked by the author (AI), not reviewed Two connectors sending the same key three times each make one sequence and one logical effect.
-
quest-n-click-connector,npm run connector: Connectors overview. - The Bridge's side of a proposal: Guarantees.
- How tests are chosen and run: Which tests run when; mutation testing of the connectors: Mutation testing.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits