Repository navigation
v4.1.10
"Constellation": the programme's third release, the first since 4.1.8 with a release candidate. The Bridge reads and
writes through one store interface (RealityStore): SQLite locally, Postgres for several instances, the 4.1.9 journal
still served and migrated; instances without state of their own, sharing one durable journal that wakes the streams;
tenants isolated by key and by row, each with its own quotas, rotation and revocations; a signal that names its
context (SignalV2, the threat model's answer, V1 still accepted by the player until 4.1.12); health routes, metrics,
backup and restore, quarantine of rows that no longer verify, a load bench of three instances. Measured against 4.1.9
in docs/dev/baselines/4.1.10.md; what this release does not do is in the LOG and the passes sheet
(docs/dev/passes/4.1.10.md).
Breaking
SignalV2, the signal that names its context (4.1.10, ADR 0010).WorldSignalV2addstenantId,
environment,audience(the origin the player paired from),sessionIdandkeyIdto the signed payload;
verifySignalaccepts the versions its expectation allows (both by default), refuses a V2 signed for another
tenant, environment, origin or link (audience-mismatch) and akeyIdthat is not the header'skid(key). A
multi-tenant Bridge signs V2 only; a single-tenant Bridge signs V1 by default until 4.1.12, when V2 becomes the only
version (announced,docs/en/UPGRADING.md§22). A key bound to a tenant signs V2 only: a V1 signal under it is
refused (schema); a V1 Bridge's keys bind no tenant. A V2 signal may name the Bridge's own audience when the
Bridge did not see the player's origin (its keys declare it). The Studio's simulator signs V2 by default;
RealityClientchecks the page's origin by default and the shipped player passes the link'ssessionId. The Rust
cross-check verifies V2 with the same codes (bridge/test-vectors/signal-v2/).- The Bridge's methods are asynchronous (4.1.10, ADR 0009):
startPairing,claimPairing,revoke,
forgetPlayer,exportPlayer,ack,unlink,subscribe,streamAliveandplayerOfreturn promises; the
routes/v1/*are unchanged.--trust-proxyalone trusts the loopback only; the client is the rightmost
X-Forwarded-Foraddress that is not a listed proxy. Behind a proxy elsewhere than on the loopback (a PaaS's
router), every client shares one rate bucket until--trust-proxy=<its network>names it.
Changes
- A durable, replicable, multi-tenant Bridge (4.1.10 "Constellation", D20,
docs/dev/threat-models/constellation.md).
The Bridge reads and writes throughRealityStore, every method taking the tenant first;appendSignaldecides the
deduplication, the sequence (MAX + 1), the quotas and the signature in one transaction. Stores: SQLite through
node:sqlite(thelocalprofile; Node 22.13+, no native dependency), Postgres throughpg(thedistributed
profile,experimentaluntil a real deployment), the 4.1.9 journal (still served;npm run bridge -- migrate --from=jsonl --to=sqlitemoves it). The schema is versioned (bridge/migrations/, up and down). One server serves
several tenants (serve --tenants=…, routed byHost), each with its own keys, root, quotas, rotation and
revocations, and connector tokens bound to their tenant; instances are stateless (a stream on one instance receives
what another accepted, woken byNOTIFYor a short poll). New:/livez,/readyz,/healthz; OpenTelemetry
metrics when@opentelemetry/apiis installed;tenant export|delete,backup,restore; quarantine of rows that
no longer verify (or name another player, sequence or tenant than their row), listed bydoctor;
streamsPerInstance; a store busy beyond 5 s answers 503 withRetry-After; the SQLite files are mode 0600.
Tested: the store contract on memory, SQLite and
Postgres with fast-check properties (concurrent proposals, two tenants crossed), three processes with one killed
during 1 000 proposals, backup and restore rehearsed.npm run bridge:loadmeasures three instances, 1 000 players
and 50 000 proposals (docs/dev/BENCH-BRIDGE.md; nightly on SQLite and Postgres); CI runs abridge-postgresjob.
Manual passes (D12: reported, not blocking)
0 of 13 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
| A real multi-instance deployment behind HTTPS (Postgres), a connector delivering, a signal received after a disconnection | not done | |||
A Bridge behind a PaaS router with --trust-proxy=<its network>, the rate limit per client observed |
not done | |||
| A 4.1.9 journal migrated to SQLite on a real server, the players' streams resumed | not done | |||
| A real email provider (webhook mode) delivering to a game | not done | |||
| A real Open Badge (OB2 hosted, OB3 VC-JWT) from a real issuer verified | not done | |||
| SSH and Telnet exposed in a controlled environment, attacked by a person | not done | |||
Screen reader (docs/dev/SCREEN-READER.md) |
not done | |||
Safari offline (docs/dev/SAFARI-OFFLINE.md), iPhone install and update |
not done | |||
| Firefox offline on a real machine | not done | |||
Windows: npm run doctor, npm run dev, npm run build by hand |
not done | |||
| Real phone, frame rate of the heaviest scene (≥ 30 FPS) | not done | |||
Playtesters who do not know the puzzles (npm run verify:field) |
not done | |||
Recorded voices; listening; a signed tag (git tag -s) |
not done |