quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

The 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 plan reality; 3 test files · people's passes: connectors-real-security not 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-api The SDK is not a public API of a package: quest-n-click-connectors exports its command only (./run), and the four connectors are listed by id in connectors/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.

RealityConnector

feature since 4.1.9

Form

interface RealityConnector {
  readonly id: string;
  start(ctx: ConnectorContext): Promise<void>;
  stop(): Promise<void>;
  health(): Promise<{ ok: boolean; detail: string; since: string }>;
}

Values and default

Constraints

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.

Minimal example

The Telnet connector, connectors/src/telnet/connector.ts, is the shortest of the four.

Behaviour on an unknown field

Not applicable: an interface of the code.

Since

4.1.9.

ConnectorContext

feature since 4.1.9

Form

Made by createContext({ bridge, gameId, tenantId?, limits?, manifest, connector, write?, now?, retries?, fetch? }).

Values and default

MemberWhat
bridge{ url, token }: the Bridge and the connector's attenuated Biscuit
gameId, tenantIdThe game; the tenant (default)
limitsConnectorLimits
manifestThe 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

Constraints

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.

Minimal example

const r = await ctx.propose({
  source: 'telnet', kind: 'terminal.lamp', playerId, dedupeKey: dedupeKey('telnet', `${session}:${line}`),
  payload: { command: 0, line }, receivedAt: new Date().toISOString(),
});

Behaviour on an unknown field

An answer from the Bridge that is not a JSON object reads as {}.

Since

4.1.9.

ProposedSignal

feature since 4.1.9

Form

{ source, kind, playerId, sessionId?, dedupeKey, payload, receivedAt }.

Values and default

Constraints

A nested, marked-up or oversized payload is refused (shape, too-large).

Minimal example

See ConnectorContext.

Behaviour on an unknown field

Not sent: the SDK builds the Bridge's body from the fields above only.

Since

4.1.9.

RefusalCode

feature since 4.1.9

Form

A fixed vocabulary, never the Bridge's free text.

Values and default

CodeFrom
shape, too-large, quota, not-declared, stoppedThe SDK, before the Bridge (shape also for a Bridge's 400, too-large for its 413)
deniedThe Bridge's 401 or 403
revokedThe Bridge's 401 revoked
playerThe Bridge's 404 for a player
pairingA malformed code; the Bridge's 404 for a code, 409 or 410
pendingThe Bridge's 429 pending (the player's backlog)
bridge-quotaThe Bridge's other 429
timeout, unreachableNo answer within timeoutMs; no connection
bridgeAnother status, or a 5xx after the retries

Constraints

Counted in metrics().refused and logged as signal.refused with the code.

Minimal example

if (!r.ok && r.refusal === 'pending') … (the player has too many signals waiting).

Behaviour on an unknown field

A status the table does not name is bridge.

Since

4.1.9.

ConnectorLimits

feature since 4.1.9

Form

{ maxBytes, maxPerMinute, timeoutMs }, the configuration's limits.

Values and default

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).

Constraints

A limit left out keeps its default.

Minimal example

"limits": { "maxBytes": 4096, "maxPerMinute": 60, "timeoutMs": 5000 }

Behaviour on an unknown field

Ignored.

Since

4.1.9.

Helpers

feature since 4.1.9

Form

dedupeKey(source, external), canonicalJson(value), payloadProblem(payload, maxBytes), safeLog(write, connector), within(work, ms), DRAIN_MS, DEFAULT_LIMITS.

Values and default

Constraints

None.

Minimal example

dedupeKey('email', messageId) is the email connector's key.

Behaviour on an unknown field

Not applicable.

Since

4.1.9.

Testing without a network

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

Guarantee · bridge-sdk-same-key-one-effect · referenced · executed on aff6caab7514 · 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.


Edit the source of this page · Report an error