This document is the durable handoff / tracking checklist for bringing the TypeScript implementation to parity with the Rust implementation and therefore the current Vox specification.
The goal is not to merely get a failing test green.
The goal is to make TypeScript match:
- the Rust implementation as the source of truth
- the current spec
- the intended cross-language behavior
Tests are validation, not the target.
- Treat Rust as canonical whenever semantics are unclear.
- Prefer removing stale TypeScript-era assumptions over preserving old behavior.
- Do not preserve legacy postcard-handshake logic just because old TS code/tests reference it.
- Track parity at the protocol level first, then update tests/helpers/docs.
- Avoid "compat shims" unless Rust/spec explicitly requires compatibility behavior.
A partial start has already happened on this branch:
- Raw CBOR handshake helper code was started in
typescript/packages/vox-core/src/handshake.ts. xtaskwas updated to generate/exportwireMessageSchemasCbor.SchemaTrackerwas partially updated to accept current RustTypeSchemaIdencoding.session.tswas partially moved toward raw-handshake startup for transport-based APIs.
However, the TS runtime still contains major stale assumptions that conflict with the newer Rust/spec model, especially around:
- handshake modeling
- wire types
- schema exchange
- opaque payload framing
- stable conduit integration
- retry persistence / operation storage
- channel continuity semantics
At the time of writing, TypeScript also has known compile/diagnostic failures in at least:
typescript/packages/vox-wire/src/types.tstypescript/packages/vox-core/src/session.tstypescript/packages/vox-wire/src/types.generated.ts
Canonical order:
- transport prologue selects
bareorstable - raw CBOR session handshake
- build
MessagePlanfrom exchanged protocol schemas - begin postcard
Messagetraffic - if using stable conduit, its own handshake/framing layer must be integrated in the same order Rust uses
Important consequence:
HelloandHelloYourselfare not postcardMessagePayloadvariants anymore- they are handshake types exchanged before postcard
Messagetraffic
rust/vox-types/src/handshake.rsrust/vox-core/src/handshake.rsrust/vox-core/src/lib.rs(MessagePlan::from_handshake)docs/content/spec/conn.md
The CBOR handshake exchanges the peer's schema for the postcard protocol layer.
Specifically, Rust handshake messages carry:
connection_settings- retry support
- resume key
message_payload_schema
Purpose:
- bootstrap decoding of postcard
MessagePayloadacross schema evolution
Important rule:
- protocol schemas are exchanged during handshake
- transparent reconnect via
StableConduitdoes not re-exchange protocol schemas - session resumption via new handshake does re-exchange them
rust/vox-types/src/handshake.rsrust/vox-core/src/handshake.rsdocs/content/spec/conn.md
Schemas are also used at the application payload level, not just at handshake.
Rust currently sends per-method schemas by inlining CBOR payloads into:
RequestCall.schemasRequestResponse.schemas
There is no standalone SchemaMessage in the current Rust flow.
Owned by:
rust/vox-core/src/session/mod.rs(SessionCore::send)rust/vox-types/src/schema.rs(SchemaSendTracker::prepare_send_for_method)
Rules:
- sender-driven
- no round trips
- first use per method+direction on a connection
- once per type per connection
- include transitive schema dependencies
- empty CBOR payload means nothing new to send
Owned by:
rust/vox-core/src/session/mod.rs(Session::handle_message)rust/vox-types/src/schema.rs(SchemaRecvTracker::record_received)
Rules:
- parse inlined schema CBOR from incoming request/response
- record schemas before routing/dispatch
- duplicate
TypeSchemaIdon same connection is a protocol error - bindings are direction-specific (
ArgsvsResponse) - all schema IDs are per-connection
rust/vox-core/src/session/mod.rsrust/vox-types/src/schema.rs
Opaque payloads now have uniform framing.
Wire shape for opaque payload fields:
- 4-byte little-endian length prefix (
u32le) - followed by raw postcard payload bytes
This applies to:
RequestCall.argsRequestResponse.retChannelItem.item- any other opaque adapter field
Important consequences:
- not varint length
- not CBOR bytes
- not special trailing bytes without framing
- passthrough still uses the same outer framing
rust/vox-postcard/src/decode.rs(read_opaque_bytes)rust/vox-postcard/src/deserialize.rsrust/vox-types/src/message.rs(Payload,PayloadAdapter)
Rust session resumption:
- preserves session-scoped state
- preserves operation ID scope
- does not preserve in-flight request attempts on the failed attachment
- does not preserve in-flight response deliveries on the failed attachment
- unresolved operations continue by creating a new request attempt for the same operation
On resume, Rust resets per-connection schema state:
- send-side schema state reset
- receive-side schema state reset
- protocol schemas re-exchanged through new handshake
- root settings must match
rust/vox-core/src/session/mod.rsdocs/content/spec/conn.mddocs/content/spec/retry.mddocs/content/spec/intro.md
StableConduit is a lower layer than session resumption.
It provides:
- automatic reconnect over fresh links
- packet sequence numbering
- acknowledgements
- replay of unacked frames
- duplicate suppression
- continuity of conduit traffic across link interruption
It is not the same thing as session resumption.
Important distinction:
- stable conduit continuity alone does not settle ambiguous RPC outcomes
- retry/session resumption semantics still govern operation continuity
Docs explicitly describe stable conduit reconnect as preserving state/channels across reconnect.
rust/vox-core/src/stable_conduit/mod.rsdocs/content/spec/conn.mddocs/content/guides/rust.md(stable_conduit_reconnect)
Rust has a real operation persistence model.
Important spec/Rust expectations:
- retry is defined in terms of operations, not request attempts
persistcreates a durability obligation- sealed outcomes for persist operations must be durably recorded
- operation ID scope persists across session resumption
- durable operation storage is distinct from transient in-memory tracking
TypeScript currently does not match this fully.
docs/content/spec/retry.md- Rust operation-store wiring in
rust/vox-core
typescript/packages/vox-wire/src/types.ts still assumes:
HelloHelloYourselfmessageHello(...)messageHelloYourself(...)
belong to postcard Message.
But Rust moved these to the raw CBOR handshake.
types.generated.ts also no longer exports the types that types.ts expects, causing drift and diagnostics.
- Remove stale postcard-level
Hello/HelloYourselfassumptions fromvox-wire - Stop constructing
Messagevalues with payload tag"Hello"or"HelloYourself" - Decide whether handshake types should live in a dedicated TS handshake module instead of
vox-wirepostcard message helpers - Update
vox-wire/src/index.tsexports accordingly - Regenerate
types.generated.tsonly from actual postcard message shapes - Make TS wire helpers reflect Rust's current
RequestCall/RequestResponseshapes, includingschemas
typescript/packages/vox-wire/src/types.tstypescript/packages/vox-wire/src/index.tstypescript/packages/vox-wire/src/types.generated.ts
typescript/packages/vox-core/src/session.ts still contains stale postcard-handshake assumptions in several places, including:
- message switching on
"Hello"/"HelloYourself" - use of
messageHello(...) - use of
messageHelloYourself(...) - old resume path assumptions
- stale
SchemaMessagehandling
Match Rust:
- transport-based APIs do raw CBOR handshake before postcard
Conduit<Message>traffic - session establishment from a conduit should be thought of as post-handshake
- schema receive/send should be tied to request/response
schemasfields - no standalone
SchemaMessage
- Remove
Hello/HelloYourselfcases from postcardMessageruntime handling - Remove standalone
SchemaMessagehandling from session runtime - Read/record inlined schema CBOR from
RequestCall.schemasandRequestResponse.schemas - Attach schemas when sending first request/response for method+direction on a connection
- Reset send/receive schema state correctly on session resumption
- Mirror Rust session establishment model: handshake result first, then session
- Rework or deprecate
Session.establishInitiator/Session.establishAcceptorif they still imply old postcard handshake semantics - Distinguish clearly between:
- stable conduit reconnect
- session resumption
- Remove old resume protocol based on postcard hello metadata if Rust no longer uses that path
typescript/packages/vox-core/src/session.ts- possibly
typescript/packages/vox-core/src/index.ts
typescript/packages/vox-core/src/schema_tracker.ts and cbor.ts were written around the old SchemaMessagePayload framing model.
The tracker itself may still be useful, but the integration point is stale.
- Keep the CBOR parsing logic, but integrate it with inlined request/response
schemas - Stop describing the receive path as standalone
SchemaMessageif Rust no longer does that - Ensure method bindings remain direction-specific
- Keep support for current
TypeSchemaIdencoding from Rust - Validate duplicate-type handling semantics against Rust
- Ensure schema tracker lifecycle is per connection, not global
typescript/packages/vox-core/src/schema_tracker.tstypescript/packages/vox-core/src/cbor.ts
Every request/response payload and channel item is an opaque field. TS must match Rust's u32le + bytes framing exactly.
This needs verification across all TS postcard encode/decode paths.
- Audit TS postcard implementation for opaque adapter framing
- Ensure opaque reads use 4-byte little-endian length prefix
- Ensure opaque writes emit 4-byte little-endian length prefix
- Ensure passthrough bytes still use same outer framing
- Verify request args / response ret / channel item all match Rust wire encoding
- Add/port golden-vector tests against Rust fixtures
typescript/packages/vox-postcard/...typescript/packages/vox-wire/...- any TS encode/decode helpers for payload-bearing messages
TS has a StableConduit, but current session/transport integration is not fully aligned with Rust/spec.
The current branch also explicitly throws for stable raw-handshake transport setup in some paths.
Match Rust ordering and responsibilities:
- transport prologue chooses
stable - raw CBOR session handshake
- build message plan from handshake schema exchange
- stable conduit setup / replay / reconnect semantics
- continue postcard message traffic with continuity guarantees
- Audit
typescript/packages/vox-core/src/stable_conduit.tsagainst Rust stable conduit behavior - Verify packet framing details
- Verify replay/ack trimming semantics
- Verify duplicate suppression semantics
- Verify reconnect handshake (
ClientHello/ServerHello) ordering relative to session handshake - Ensure
StableConduitcarries post-handshake postcardMessages, not pre-handshake traffic - Integrate message translation plan with stable conduit receive path if needed
- Remove current
"stable not implemented yet"transport-path placeholders once parity is implemented - Validate that stable reconnect preserves channel continuity as Rust/docs describe
typescript/packages/vox-core/src/stable_conduit.tstypescript/packages/vox-core/src/session.tstypescript/packages/vox-core/src/transport_prologue.ts
TS currently has only an in-memory OperationRegistry in driver.ts.
That is not equivalent to Rust's operation store / durability story.
TS can:
- admit operations
- attach duplicate requests
- replay sealed outcomes
- track released/indeterminate state
But only in-process and only in memory.
- durable operation persistence
- restart-surviving operation records
- durable sealing for
persist - satisfying spec durability obligations for persist methods
- Introduce a real operation-store abstraction in TS, mirroring Rust's concept
- Separate transient registry behavior from durable persistence behavior
- Ensure
persistcreates an actual durability obligation - Ensure sealed persist outcomes are durably recorded before reporting sealed
- Ensure admitted persist operations are recoverable across process restart if required by spec
- Audit current
driver.tsretry behavior againstdocs/content/spec/retry.md
typescript/packages/vox-core/src/driver.tstypescript/packages/vox-core/src/retry.ts- generated descriptors that expose retry policy
Current TS session.ts explicitly closes all channels on session resume:
this.channelRegistry.closeAll()
Then it retries requests with fresh request IDs / fresh channels if retry machinery is available.
That is not "seamless channel continuity."
According to spec/Rust:
- session resumption does not preserve in-flight request attempts
- operation continuity may continue by creating a new request attempt
- stable conduit reconnect is the place where transparent channel continuity is expected
So the parity question here is layered:
- TS session resumption semantics must match spec
- TS stable conduit must provide its continuity story correctly
- TS should not blur the two layers
- Audit current channel-closing-on-resume behavior against Rust/spec
- Clarify what must happen for:
- bare + session resumption
- stable conduit reconnect
- stable + session resumption
- Ensure TS does not incorrectly claim seamless channel continuity where only retry/new-attempt semantics apply
- Ensure TS stable conduit path preserves channels/state the way Rust/docs intend
- Update tests to distinguish stable reconnect continuity from session resumption retry semantics
typescript/packages/vox-core/src/session.tstypescript/packages/vox-core/src/stable_conduit.ts- channeling runtime pieces under
typescript/packages/vox-core/src/channeling/
typescript/packages/vox-core/src/connection.ts and related tests still model old Hello/HelloYourself postcard exchange.
That appears to be a pre-parity architecture and is now in conflict with current Rust/spec semantics.
- Determine whether
connection.tsis still part of the intended public runtime architecture - If stale, deprecate/remove it
- If retained, rewrite it around current handshake/wire semantics
- Remove use of postcard-level hello exchange from tests/helpers
- Update all dependent tests
typescript/packages/vox-core/src/connection.tstypescript/packages/vox-core/src/connection.channeling.test.tstypescript/packages/vox-core/src/connection.keepalive.test.ts
Current handwritten TS helpers in types.ts do not fully match Rust message structs.
For example, request/response helpers need to account for fields like:
schemas
and should align exactly with Rust message layout.
- Ensure
messageRequestincludesschemas - Ensure
messageResponseincludesschemas - Audit all handwritten message helper constructors against
rust/vox-types/src/message.rs - Verify discriminants and helper return types remain sound
typescript/packages/vox-wire/src/types.ts
We added wireMessageSchemasCbor, but TS generation and exports need to reflect the separation between:
- handshake schema exchange
- postcard message types
- any dedicated handshake TS types if needed
- Keep
wireMessageSchemasCborgeneration - Decide whether TS should also generate handshake type definitions/schemas from Rust
handshake.rs - Ensure codegen does not imply
Hello/HelloYourselfare postcard message payloads - Verify generated docs/comments match current architecture
xtask/src/main.rstypescript/packages/vox-wire/src/schemas.generated.ts- potentially new generated handshake artifacts if added
A number of TS tests still assume postcard hello exchange or stale runtime layering.
Those tests are useful only if rewritten to validate the current protocol.
- Update tests to use raw CBOR handshake where appropriate
- Remove tests that depend on stale postcard Hello/HelloYourself semantics
- Add/port golden-vector coverage for:
- handshake messages
- opaque payload framing
- request/response schemas
- stable conduit reconnect behavior
- Add parity tests against Rust subjects/harnesses where possible
- Ensure browser/inprocess tests use current transport/session semantics
typescript/packages/vox-core/src/connection.channeling.test.tstypescript/packages/vox-core/src/connection.keepalive.test.ts- any tests around old
connection.tshello exchange
- Fix
vox-wire/src/types.tsso it reflects current Rust postcard message shapes - Remove postcard hello helpers/types from
Message - Add or relocate handshake-specific TS types/helpers as needed
- Regenerate wire artifacts
- Get
vox-wirecompiling again
- Make transport-based APIs do raw CBOR handshake first
- Build/use handshake result to initialize session state
- Remove old postcard-level hello handling from
session.ts - Remove stale
SchemaMessagepath - Integrate request/response schema exchange properly
- Get
vox-corecompiling again
- Audit opaque framing end-to-end
- Add request/response schema send/receive logic that mirrors Rust
- Add/reset per-connection schema trackers correctly across reconnect/resume
- Verify against Rust vectors/fixtures
- Audit TS stable conduit against Rust stable conduit
- Fix ordering with transport/session handshake
- Verify replay/ack/reconnect semantics
- Verify channel continuity guarantees
- Introduce durable operation store abstraction
- Align
persistsemantics with spec durability obligations - Audit retry/indeterminate/released/sealed behavior against Rust/spec
- Remove or rewrite
connection.ts-based old architecture - Update tests to current semantics
- Update TS docs/examples once runtime is correct
These should happen first:
- Fix
typescript/packages/vox-wire/src/types.ts - Remove stale hello/schema-message assumptions from
typescript/packages/vox-core/src/session.ts - Move schema receive logic to inlined request/response
schemas - Verify opaque framing in TS postcard implementation
- Revisit
StableConduitintegration order after session handshake is corrected
- CBOR handshake exists specifically to bootstrap protocol/message schema negotiation before postcard traffic.
- Schemas are used at two levels:
- handshake-level protocol schema
- request/response payload schema
- Rust sends payload schemas from
SessionCore::send(...). - Rust receives payload schemas in
Session::handle_message(...)before routing. - Rust resets schema tracking on reconnect/resume because type IDs are per connection.
- Rust/session resumption and stable conduit reconnect are separate layers.
- TS currently has only in-memory retry operation tracking, not a durable operation store.
- TS currently closes channels on session resume instead of preserving seamless continuity there.
Do not "fix" TS by preserving old postcard hello semantics.
That would move it farther away from parity.
- Postcard
Messagetype parity - Remove stale postcard hello types
- Handshake-specific TS types/helpers
- Request/response helper field parity (schemas field added to Call/Response)
- Generated artifact parity
- Raw CBOR handshake send path
- Raw CBOR handshake receive path
- Handshake result model
- Message plan bootstrap from handshake schema
- Resume-key handling
- Send in
RequestCall.schemas(codegen-driven CBOR from Rust Facet shapes) - Send in
RequestResponse.schemas(codegen-driven CBOR from Rust Facet shapes) - Receive from incoming request/response
- Duplicate type handling
- Per-connection reset on reconnect
- Opaque
u32ledecode - Opaque
u32leencode - Passthrough framing parity
- Args/ret/item verification
- Stable handshake ordering
- Seq/ack semantics
- Replay buffer semantics
- Duplicate suppression
- Channel continuity parity
- In-memory registry audit
- Durable operation-store abstraction
- Persist durability obligations
- Resume behavior parity
- Indeterminate/sealed/released parity (persist=true + failClosedOnDrop → INDETERMINATE)
- Remove stale hello-exchange tests
- Add handshake tests
- Add schema exchange tests
- Add opaque framing tests
- Add stable reconnect continuity tests
- Add parity tests against Rust
If there is ever a choice between:
- making an old TS helper/test happy
- matching Rust/spec
the right answer is:
- match Rust/spec