Add the TypeScript QUIC realtime plane migration target
Eureka target for giving cultmesh-ts a QUIC realtime provider and consumer, byte-compatible with GameCult.Mesh.Quic and the Unity native MsQuic connector. First consumer: the StreamPixels service publishing to its Unity overlay. Operator rulings 2026-09-16: overlay character, subathon window and scene intents travel as QUIC state frames and the costume catalog as an HTTPS content artifact; shared TypeScript route verification moves to a browser-safe module; Windows x64 and Linux x64 only; datagrams out of scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a0813c6eed
commit
f2cd2eb464
1 changed files with 160 additions and 0 deletions
160
docs/typescript-quic-realtime-target.md
Normal file
160
docs/typescript-quic-realtime-target.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# TypeScript QUIC Realtime Plane Target
|
||||
|
||||
Date: 2026-09-16
|
||||
|
||||
Status: target accepted by the operator (2026-09-16), ready for Eureka. No cut
|
||||
map exists yet. When the pipeline starts, Imagination maps the cut into
|
||||
`docs/typescript-quic-realtime-cut.md` against `main` at `a0813c6` or later.
|
||||
|
||||
The TypeScript runtime gains the CultMesh QUIC realtime state plane that the C#
|
||||
runtime already has: a provider that serves frames and a consumer that receives
|
||||
them, byte-compatible with `GameCult.Mesh.Quic` and the native MsQuic connector
|
||||
the Unity package ships. This document states the end state and the invariants
|
||||
that constrain getting there.
|
||||
|
||||
Operator ruling, 2026-09-16, on providing QUIC for a Node service: "Option 2 all
|
||||
the way, this is what CultLib is supposed to be, we're not half assing it." A
|
||||
separate .NET provider process and a WebSocket shortcut were both rejected.
|
||||
|
||||
## Why now
|
||||
|
||||
The first consumer is StreamPixels. Its service (Node 24, TypeScript, on
|
||||
Yggdrasil) must publish realtime state to a Unity overlay running on a
|
||||
streamer's machine. The overlay is a receive-only client using the Unity
|
||||
package's `CultMeshNativeQuicRealtimeTransportConnector`. Today no TypeScript
|
||||
path exists:
|
||||
|
||||
- Node v24.15.0 has no `node:quic`, with or without `--experimental-quic`.
|
||||
- `packages/cultmesh-ts` and `packages/cultnet-ts` contain no QUIC code.
|
||||
- `GameCult.Mesh.Quic` (.NET 10, `System.Net.Quic`) is the only QUIC provider.
|
||||
- The C# TCP control and content connectors are plaintext and restricted to
|
||||
loopback (`src/GameCult.Mesh/docs/transport-planes.md`, "Security and release
|
||||
gate"), so they cannot carry the overlay's remote session either.
|
||||
|
||||
## This touches the foundation
|
||||
|
||||
- **Wire parity is an invariant.** The C# codec is the reference:
|
||||
`src/GameCult.Mesh/CultMeshRealtimeWireProtocol.cs`. The TypeScript runtime
|
||||
must produce and accept identical bytes:
|
||||
- ALPN `cultmesh-state-v1`; connection close code `0x43554c54`, stream abort
|
||||
code `0x53544154`; reliable stream type `1`, latest-only stream type `2`.
|
||||
- Frame: magic `0x31545343`, then a 37-byte little-endian fixed header
|
||||
(delivery byte, producer epoch i64, sequence i64, channel/schema/body id
|
||||
lengths u16, payload length i32, header size i32 = 37, wire version u16 =
|
||||
1), then UTF-8 channel id, schema id, body id, then payload.
|
||||
- Limits: payload at most 64 MiB; each identity at most 65,535 UTF-8 bytes;
|
||||
decode rejects wrong magic, unknown version, out-of-range delivery, and any
|
||||
length that does not sum exactly.
|
||||
- **Delivery semantics are an invariant**
|
||||
(`src/GameCult.Mesh.Quic/README.md`, `CultMeshRealtimeTransports.cs`):
|
||||
- `ReliableOrdered`: one persistent ordered stream; broadcast is
|
||||
completion-bearing.
|
||||
- `LatestOnly`: independent streams; at most one pending frame per
|
||||
`(channel, body)`, replaced by a newer `(producer epoch, sequence)`
|
||||
generation; a keyed coalescing outbox per physical peer, so a slow or
|
||||
departed consumer never backpressures the producer.
|
||||
- `Unreliable`: fails closed until datagrams are exposed, matching the managed
|
||||
C# adapter.
|
||||
- **Trust is an invariant.** No remote session comes online without the chain
|
||||
in `transport-planes.md`: an Odin-signed `CultMeshRouteCertificate` (Verse,
|
||||
authority runtime, protocol set, endpoint, generation, validity, provider
|
||||
P-256 key) checked against consumer-pinned Odin roots, TLS 1.3, and the
|
||||
endpoint certificate bound to that route, including the
|
||||
`cultmesh-state+quic://host:port?cert-sha256=<uppercase SHA-256>` pin.
|
||||
Signatures are ECDSA P-256 over SHA-256 with P1363 `r || s` encoding and the
|
||||
length-prefixed canonical transcript. Unsigned traffic exists only under an
|
||||
explicit `LocalDevelopment` policy on loopback.
|
||||
- **One implementation of TypeScript route verification.** `cultmesh-browser`
|
||||
already verifies Odin route certificates and provider nonce possession with
|
||||
WebCrypto (`packages/cultmesh-browser/src/index.ts`, `verifyAuthorityRoute`,
|
||||
`canonicalRoute`, `canonicalSession`, `verifyP256`). The Node QUIC path must
|
||||
reuse that owner, not grow a second verifier.
|
||||
- **The realtime plane carries state frames only.** Schemas, commands,
|
||||
receipts, manifests, and immutable content do not travel through it
|
||||
(`ICultMeshRealtimeTransport` doc comment). RUDP is never installed
|
||||
implicitly in its place.
|
||||
- **Existing consumers survive.** The StreamPixels service already imports
|
||||
`cultcache-ts`, `cultnet-ts`, and `cultmesh-ts` (`verse-state.ts`,
|
||||
`idunn-rudp-health.ts`) for Odin advertisement and Idunn health over RUDP.
|
||||
Imagination audits every other TypeScript consumer across `F:\Projects`
|
||||
before sizing the cut.
|
||||
|
||||
## Target state
|
||||
|
||||
- `cultmesh-ts` exposes a QUIC realtime provider and a QUIC realtime consumer
|
||||
with the same ergonomic shape as the C# `CultMeshQuicRealtimeServer`
|
||||
(listen, broadcast with an explicit delivery mode) and
|
||||
`ICultMeshRealtimeTransportConnector` (connect by advertised candidate,
|
||||
receive frames), under the branded `CultMesh` entrypoint.
|
||||
- QUIC comes from native MsQuic bindings owned by CultLib, not from a Node
|
||||
polyfill or a third-party QUIC stack with different semantics. How the binding
|
||||
is built and shipped is the means (Imagination probes it); what it must
|
||||
deliver is TLS 1.3, ALPN, independent unidirectional or bidirectional streams,
|
||||
certificate validation with a caller hook, and clean close and abort codes.
|
||||
- Frame encoding and decoding exist once in TypeScript and are tested against
|
||||
bytes produced by the C# codec.
|
||||
- Application code never picks endpoints, ranks routes, or writes reconnect
|
||||
loops; `CultMeshSessionManager`-equivalent route selection owns that, as in C#.
|
||||
- The build produces binaries for the hosts that run them: Windows x64 for
|
||||
local development, and Linux x64 for Yggdrasil, built on Linux or the deploy
|
||||
path's builder, not cross-compiled on Starfire.
|
||||
|
||||
## Not in this migration
|
||||
|
||||
One foundation per pipeline. These are separate, named pipelines or consumer
|
||||
work:
|
||||
|
||||
- **TypeScript content plane** (a provider in `cultmesh-ts`, a verifying HTTPS
|
||||
reader in `cultmesh-browser`) for exported character artifacts. HTTPS content
|
||||
transport and single-host trust on Yggdrasil were already ruled.
|
||||
- **Authenticated remote control plane** (TLS on the TCP schema and content
|
||||
connectors). See Q1.
|
||||
- **Browser QUIC** (WebTransport). Browsers keep the verified WebSocket path.
|
||||
- **Python, Rust, and Kotlin QUIC.** The parity scope is role-scoped; this
|
||||
migration claims TypeScript and C# only.
|
||||
- **StreamPixels integration** (service publication, overlay subscription, and
|
||||
the catalog and scene-intent schemas) is consumer work in StreamPixels after a
|
||||
released `cultmesh-ts` exists.
|
||||
|
||||
## Verification the cut must reach
|
||||
|
||||
- Golden frame bytes from the C# codec decode in TypeScript, and TypeScript
|
||||
encodings decode in C#, for every delivery mode and at the length limits.
|
||||
Malformed inputs are rejected identically.
|
||||
- Separate processes, both directions: a TypeScript provider to the C# native
|
||||
connector (the one Unity uses) and to the managed connector, and a C# managed
|
||||
provider to the TypeScript consumer. Extend the existing parity harnesses
|
||||
(`CULTMESH_NATIVE_EXTERNAL_ENDPOINT`, `packages/cultnet-ts/test/interop`) rather
|
||||
than adding a parallel one.
|
||||
- `LatestOnly` coalescing: a stalled consumer does not delay the producer, and
|
||||
the consumer converges on the terminal generation.
|
||||
- Trust negatives: an expired or unsigned route, the wrong Odin root, a
|
||||
mismatched `cert-sha256` pin, and a replayed nonce are each refused, with a
|
||||
mutation per rule.
|
||||
- The Linux x64 binary runs on the Yggdrasil deploy path, verified on that host.
|
||||
|
||||
## Rulings (operator, 2026-09-16)
|
||||
|
||||
The operator accepted every recommendation below ("You got it"). Each question
|
||||
is kept with its options as the record of what was decided against.
|
||||
|
||||
- **Q1. Which remote plane carries the overlay's non-frame data?** The realtime
|
||||
plane forbids schemas, commands, and content, and the TCP planes are
|
||||
loopback-only. A: publish her character, the subathon window, and scene
|
||||
intents as QUIC state frames with their CultCache schema id and payload, and
|
||||
ship the costume catalog as a versioned immutable artifact on the HTTPS
|
||||
content plane; the overlay sends no commands, so it needs no remote control
|
||||
plane. B: add TLS to the TCP schema and content connectors as its own
|
||||
pipeline first. **Recommended: A.** It needs no new plane for M1, matches a
|
||||
receive-only client, and leaves TLS TCP to the first consumer that sends
|
||||
commands.
|
||||
- **Q2. Where does shared TypeScript route verification live?** It is inside
|
||||
`cultmesh-browser` today, and `cultmesh-ts` is Node-only. A: move it to a
|
||||
browser-safe shared module that both import (for example beside
|
||||
`cultnet-ts/contracts`). B: have `cultmesh-ts` depend on `cultmesh-browser`.
|
||||
**Recommended: A**, so neither transport package owns the other's trust.
|
||||
- **Q3. Which platforms must the native binding support at release?**
|
||||
**Recommended: Windows x64 and Linux x64 only**, the two hosts that run it
|
||||
today.
|
||||
- **Q4. Are QUIC datagrams (`Unreliable`) in scope?** **Recommended: no.** Fail
|
||||
closed like the managed C# adapter; no consumer needs them.
|
||||
Loading…
Reference in a new issue