Skip to content

runtime: implement the path hand-written adapters use, ported from nightseam v0.6.0 - #9

Merged
jm9e merged 39 commits into
mainfrom
feat/runtime-transports
Sep 26, 2026
Merged

jm9e merged 39 commits into
mainfrom
feat/runtime-transports

Conversation

@jm9e

@jm9e jm9e commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Current delivery status — 2026-09-26

Merged as e28bb1adefbc8a5cb1b03f172b4e7212584bd7a2 and published in v0.2.0. The description below records the candidate implementation and its evidence at review time; candidate tests are not proof that every downstream released-consumer gate has completed. See #1 for those remaining gates.

Delivers bitruntime#1: the path hand-written adapters use, in Go and TypeScript, over the public bitwire 0.3.0 contract, speaking bitwire/1. Release candidate for v0.2.0.

What it adds

  • core: At, Mount and Forward (addressed operators on AddressedWire); the local pair (NewPair/pair); the public invocation lifecycle; Respond, PublicError, publication evidence, meta and trace propagation. All of this sits beside v0.1.0's tree core.
  • transports: the frame seam, the in-memory pipe and WebSocket. The package also defines:
    • ErrClosed, the one classification of a closed carrier;
    • Sendable, which separates sendable close codes from observe-only ones.
  • engine: the bitwire/1 peer, presented only through its root Endpoint, and WebSocket setup (Accept/NewHandler/Dial; Peer.connect/Peer.attach).
  • dispatch: NewDispatcher/createDispatcher with selected endpoints, plus Call/Emit/Handle/Register (call, emit, handle, register, onEvent).
  • Coordinates: one root Go module with six packages, and one TypeScript package, @bitspark/bitruntime, with subpaths /core, /transports, /engine and /dispatch. The received context shared across components stays private (a Go internal package, a TS module no subpath exports). This follows the pending maintainer decision on the TS package layout.

Provenance

Ported from nightseam v0.6.0 only, commit 5cc9723a24646c40ed1861f892b2b23eb6d785d7; none of its 41 unreleased commits is included.

  • The Go and TS sources were each imported verbatim in their own commit, so every adaptation is a reviewable diff.
  • NOTICE maps each source file to its destination.
  • docs/port-from-nightseam.md lists every name mapping, every behavior change and what was kept as it was.
  • There are no aliases or re-exports.

Fixed rather than ported

Each fix has a regression test that fails without it.

  • nightseam#722: a closing pair answers its queued refusals. The peer's root also answers requests still queued when it ends (R28).
  • nightseam#658: a pair response frees the call's slot before the caller holds the answer.
  • R26: every ended carrier is ErrClosed and keeps its cause, and forwarding answers disconnected, not internal. Also, a call cut off by the peer's end, or by its parent context, is disconnected, not cancelled.
  • R27: observe-only close codes are never sent. A peer closing from outside its reader now delivers its code (v0.6.0 could lose it, and the far side saw 1006).
  • Research 0001, row 14: a forwarder fails only the refused message. Row 28: messages are copied before validation.
  • An unencodable reply to a bridged request no longer strands its caller until the deadline. A failed Prepare releases what it attached.
  • In TS, the handler context no longer exposes peer (R19/R23).

Evidence

  • Native tests. The ported v0.6.0 suites keep their expectations. Every dropped or changed assertion is listed in the port record or the test. Go is green under -race, and TypeScript runs 221 tests.
  • Interop with nightseam v0.6.0 peers (node scripts/interop.mjs, new CI job): all 16 pairings of bitruntime and nightseam, Go and TypeScript, pass over real WebSockets. A raw recorder shows each bitruntime server's transcript is byte-identical to nightseam's in the same language.
  • bitwire/1 tables. v0.6.0's tables, vendored byte for byte in vectors/bitwire-1, are judged by a running peer of each role.
  • bitwire's independent cases (conformance: run bitwire's independent cases against bitruntime. bitwire#52, run from bitwire's own test-only module against this branch's candidate):
    • lifecycle 5/5;
    • composition 6/6 on the local pair and WebSockets in both directions;
    • declared reference 39/39 on each carrier;
    • trees 14/14;
    • production Mount: 20 conform, and the other 19 match nightseam v0.6.0's recorded gaps exactly.
  • Fresh public Go consumer. It installs the pushed commit and runs a pair, a dispatcher and a WebSocket call. The TS package smoke test installs the packed tarball and confirms the private modules are not importable.
  • Consumer. bitsystem3's Go tests pass on this candidate. Its strengthened overload test fails on every run under v0.6.0 (#722) and passes on every run here. Its Docker stack passes the browser suite and the persistence smoke test.

Tracks #1. This merged source change does not close its remaining released-artifact acceptance and bitsystem3 #7 consumer gate. Those are verified separately through bitwire PR52.

🤖 Generated with Claude Code

jm9e and others added 30 commits September 26, 2026 11:40
Byte-identical copies from github.com/Bitspark/nightseam at
5cc9723a24646c40ed1861f892b2b23eb6d785d7 (tag v0.6.0), placed at their
bitruntime destinations so later commits show every adaptation as a diff.
This commit does not build; the next commits adapt it to Bitwire 0.3.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…velope

Move Nightseam's duplex seam into transports/go and its WebSocket adapter
into transports/websocket/go, and the envelope codec, canonical path
encoding and Unicode guard into internal/profile/go.

Carrier-contract rules the port adds (Bitwire carrier draft, research R26
and R27):
- one closed classification: transports.ErrClosed, which a remote
  CloseError and a dropped WebSocket also satisfy;
- Sendable separates sendable close codes from observe-only 1005, 1006
  and 1015, which Close refuses with ErrUnsendableCode before any
  library sees them;
- a frame over the receive limit ends the pipe with 1009 on both sides,
  and ends a WebSocket promptly instead of leaving a half-closed socket.

The envelope accepts and refuses exactly what v0.6.0 does. The bitwire/1
tables are vendored byte for byte under vectors/bitwire-1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
core/go gains the addressed operators (At, Mount, Forward), the local
pair (NewPair, with its own PairOptions), the invocation lifecycle,
Respond, publication evidence, meta and trace propagation. dispatch/go
holds the Dispatcher and the Call/Emit/Handle/Register helpers.
engine/go is the bitwire/1 peer, presented only through its root
Endpoint; engine/websocket/go sets up connections (Accept, NewHandler,
Dial). Received context stays private to bitruntime in
internal/delivery/go, which Bitwire 0.3 permits for Go.

Fixed rather than ported:
- nightseam#722: a closing pair answers every queued refusal, and the
  peer's root answers queued requests instead of dropping them (R28);
- nightseam#658: a pair response frees its call's slot before the caller
  can hold the answer;
- R26: every ended carrier's error satisfies transports.ErrClosed and
  keeps its cause, so a forwarded closed or overloaded destination answers
  disconnected rather than internal;
- research 0001 row 14: Forward fails only the refused message instead of
  detaching both directions;
- R27: an observe-only close code aborts the peer instead of being sent.

Removed: the raw method-name API (Handle, Call, Emit on the peer), the
observer hooks and family labels, pending their design with Bitwire's
received-context revision. bitwire/1 frames are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s verbatim

Byte-identical copies from github.com/Bitspark/nightseam at
5cc9723a24646c40ed1861f892b2b23eb6d785d7 (tag v0.6.0), placed at their
bitruntime destinations. This commit does not build; the next commits
adapt it to Bitwire 0.3.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ckets

A scenario every implementation serves and calls (conformance/interop):
echo of awkward JSON, slash and empty segments, method_not_found, public
errors with data, events both ways, cancellation reaching the server,
meta, a reverse call, a 3 MiB frame under a 4 MiB budget and twenty
concurrent calls. Programs for bitruntime Go and for Nightseam v0.6.0 Go
and TypeScript, each pinned in its own test-only module; the Nightseam
ones compile against Bitwire 0.2.0 and never enter a published package.

A raw recorder replays fixed envelopes and captures what each server
sends. bitruntime Go's transcript equals Nightseam v0.6.0 Go's byte for
byte, masking only random span identifiers.

node scripts/interop.mjs: all nine pairings pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NOTICE names Nightseam v0.6.0 (5cc9723) as the only source of the ported
files, maps each to its bitruntime destination and records the vendored
bitwire/1 tables. The changelog gains the missing 0.1.0 heading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Research 0001 row 28: v0.6.0's Go carriers validated a caller's frame and
then copied it, so a concurrent mutation during Send could admit bytes that
were never validated. The pair, the root and the call helper's reply now
copy first and validate the copy. The byte-level interop transcript is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ported from Nightseam 5cc9723a (v0.6.0) runtime/go: dispatcher_test.go,
dispatcher_ownership_test.go, wire_test.go, wire_event_context_test.go,
wire_namespace_test.go, wire_options_test.go, wire_validation_test.go and
bitwire_test.go, with names translated per docs/port-from-nightseam.md.

What used the removed raw peer API goes through the root Endpoint: a
dispatcher over peer.Wire() with Handle/Register, and Call/Emit through
peer.Wire(). Raw non-path method names are sent as raw bitwire/1 frames
over a pipe and must be answered method_not_found, "ordinary" included,
now that no raw handler can take it; a canonical encoding reaches the
root namespace. Observer assertions are dropped (HandlerPanic, RequestEnded)
or re-instrumented where they checked traffic rather than the hook:
FrameSent order and the ConnectionClosed code/reason are read from a
recording pipe connection. The pure ForwardWire test stays with core.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ported from Nightseam 5cc9723a (v0.6.0) runtime/go/wire_send_test.go.
The raw Emit that filled the destination's queue is the root's Emit;
since the root admits asynchronously, a propagator marks when the root
has taken each frame so the queue is where v0.6.0's synchronous Emit
left it. The observer's Backpressure count is dropped, and so is the
raw, paced call that was withdrawn before admission: a root call is
never paced.

The earlier admitted call now reaches its caller through the root as a
public error, so it is asserted disconnected (R26) rather than
ErrBackpressure. It is answered cancelled instead: the root's waiter
runs under a context derived from the peer's own, which the peer's end
cancels before releasing it. That is an engine/go defect, as in v0.6.0's
root; the test skips on exactly that answer and fails on any other.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ported from Nightseam 5cc9723a (v0.6.0)
runtime/go/wire_cancel_reservation_test.go. The tests build a peer
whose root alone runs, read its outgoing queue and complete its pending
calls directly, so they are internal to engine/go rather than beside
the dispatcher. The root is started explicitly, as v0.6.0's Wire()
started it lazily; pending results are request.Result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d the close code

Two defects the ported tests found, both present in Nightseam v0.6.0:

- A request the root had handed to the peer waited under a context derived
  from the peer's, which the peer's end cancels before releasing waiters,
  so its caller was answered cancelled instead of disconnected (R26). The
  waiter now prefers the peer's terminal error and owes no cancellation.
- Peer.end cancelled the reader's context before closing the connection.
  A WebSocket whose pending read is cancelled drops the socket, so a peer
  ended from another goroutine let the far side observe 1006 instead of
  its code. The connection now closes first.

TestAClosingPeerDeliversItsCodeOverAWebSocket fails on the first of 50
runs without the fix and passes all of them with it; the send test's skip
is removed and now requires disconnected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fresh consumer now also calls through a local pair, a dispatcher and a
WebSocket peer, installing the pushed commit or tag from the public proxy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port duplex/go/wire_test.go from Nightseam v0.6.0 (5cc9723a) to
core/go/addressed_test.go. Names follow docs/port-from-nightseam.md:
duplex.At and Mount are core.At and Mount, ErrNoRoute and ErrPath are
core.ErrMissingPath and ErrInvalidPath, ErrClosed is transports.ErrClosed,
and the canonical path encoding is internal/profile's. Every assertion is
kept as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/wire_pair_test.go from Nightseam v0.6.0 (5cc9723a) to
core/go/pair_test.go as an external test. NewWirePair is core.NewPair;
HandleWire, CallWire and EmitWire are dispatch.Handle, Call and Emit; the
unexported wireDispatchContext and callWire are internal/delivery.Context
and internal/request.Call; sendWireResponse is core.Respond.

TestLocalWirePairStalledEventDeadlineIsObserved drops its one assertion on
the removed Observer hook: the Backpressure event with Stalled set and a
Deadline of 15ms. The stall is still observed as the pair's ending and a
closed carrier afterwards.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regression tests for the three pair behaviors docs/port-from-nightseam.md
records as fixed rather than ported:

- nightseam#722: with the receiving side's delivery held, a refusal still
  queued when the pair closes is answered busy at once instead of at the
  caller's deadline, and the call the pair held is answered disconnected.
- nightseam#658: a return capability that issues the next request while it
  is handed the answer finds the pending slot free at a limit of one, and
  500 sequential calls at that limit are never refused busy.
- R26: the send that overflows a pair reports both ErrBackpressure and
  transports.ErrClosed, the ended pair is closed on both ends, and a request
  core.Forward hands into the overflow is answered disconnected, not
  internal.

Each fails against the v0.6.0 behavior it guards: completing a call after
the hand-off, dropping queued refusals at close, or returning a bare
ErrBackpressure. TestLocalWirePairRetainsPendingUntilResponse passes with
-race -count=200.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/invocation_test.go and invocation_experiment_test.go from
Nightseam v0.6.0 (5cc9723a), complete: the two independently implemented
lifecycle participants (one composing core.Invocation, one answering the
vocabulary from its own ledger), the opaque forwarding wrapper, the pure
conduit route, and every assertion. They use public facilities only:
core's vocabulary and helpers, dispatch.NewDispatcher, Handle and Register,
and core.Forward for ForwardWire. Path-taking duplex.Wire sinks are
wire.AddressedWire.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/publication_test.go and trace_test.go from Nightseam v0.6.0
(5cc9723a). The raw peer API they used is removed (R20), so handlers are
attached with a dispatcher in engine.Options.Prepare and calls and events go
through dispatch.Call and dispatch.Emit on peer.Wire(). Frames a test
writes or reads name the canonical path encoding ("5:outer").

Changed, with the reason in each test:
- TestQueuedWriteFailureHasNoUnpublishedProof and
  TestReadFailureHasNoNestedUnpublishedProof: a call through the root is
  answered with a public error, so the transport cause (and the nested
  context.Canceled) is asserted on Peer.Err, which keeps it (R26); the
  no-proof assertions stay on the call and are added for Peer.Err.
- TestACustomPropagatorSeesTheMembersVerbatim: the outgoing trace is
  injected by the call's CallOptions.Propagator, as v0.6.0's root did.
- TestRefusedReverseReplyCannotProveDeliveredCallUnpublished runs with a
  250ms client RequestTimeout: the root refuses the oversized reply and its
  fallback at the return capability and leaves the request to its deadline.

Two v0.6.0 assertions the root Endpoint does not meet are kept, skipped,
in tests of their own: TestCallerBoundRefusalIsUnpublished (the pending
bound's busy refusal is not unpublished through the root) and
TestOversizedReverseReplyFallbackEndsTheCarrier (the carrier does not end).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port the two ForwardWire tests of Nightseam v0.6.0's
runtime/go/wire_namespace_test.go (5cc9723a) to core/go/forward_test.go;
the rest of that file concerns the dispatcher.

TestForwardWirePreservesMessagesAndOwnsOnlyRegistrations asserts the
documented forwarding change (research 0001, row 14) in its last section:
a refused request is answered busy and forwarding goes on, where v0.6.0
detached both directions. A new test holds a request refused by an ended
or overflowed destination to the disconnected answer R26 requires.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… caller

The ported publication tests showed that a reply to a request the engine
admitted, when neither it nor its bounded fallback fit the frame limit,
was refused at the reply capability and left the remote caller waiting
for its deadline. v0.6.0's raw response path failed the connection
instead. The reply now settles as the bounded internal error, which the
engine's response path sends or fails the connection over. The test's
skip is removed.

Also tidy go.mod: coder/websocket and go-json-experiment are direct
dependencies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/peer_test.go, backpressure_test.go, peer_pacing_test.go
and unicode_peer_test.go from Nightseam v0.6.0 (5cc9723a). The raw
method-name API is removed, so each handler is a dispatcher route at
[name] attached in Prepare, and calls and events go through the root
with the dispatch helpers. Pairs run over transports.Pipe; the delayed
TCP writes become a gate on the pipe's Send. Hand-written frames name
paths canonically ("5:outer"), and a new test holds that a raw method
name is answered method_not_found without ending the connection.

Dropped: OnEvent's listener and idempotent unsubscribe (raw API), and
the pacing of the raw Emit and Call with its observer events; what
remains of that rule is held instead: a response waits for a full
queue and resumes, and root traffic ends the carrier at once, as
v0.6.0's root did. The authentication, origin, subprotocol and dial
tests move to engine/websocket/go with the connection setup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/carriage_test.go and serial_test.go from Nightseam
v0.6.0 (5cc9723a). Every row of vectors/bitwire-1/frames.json and
serials.json is sent as raw text over a pipe to a peer of each role,
and the far side holds that the peer ended with 4011 or served on: a
request after the row, above every row's serial, is answered. A row
addressed to the server reaches a client with its id prefixes
swapped; a row addressed to either reaches both unchanged. v0.6.0's
decode-level judgement of frames.json is kept beside it.

The meta tests decode with internal/profile and send through the root;
the refused-meta test reads 4011 at the far side instead of the removed
observer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port runtime/go/seam_test.go and the pipe half of prepare_test.go from
Nightseam v0.6.0 (5cc9723a). How a peer ends a connection is held at
the far side's reading, since observers are removed; a far side's abort
is held on the peer's error instead. An ended peer's error is a closed
carrier that keeps its cause (R26), so the oversized-frame refusal is
the error's suffix, and the far side reads 4011 with it.

The tunnel installed in Prepare becomes a dispatcher at the root, which
meets a request already waiting on the pipe when the peer is made.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
R27: the root asked to close with 1005, 1006 or 1015 aborts, and the
far side reads 1006 with no reason; a sendable code travels with its
reason. R28: with the root held inside the answer to a refusal, a
request it admitted and never handed on, a call through the dispatch
helper, and a queued busy refusal are each answered when the peer ends
(disconnected, disconnected, busy) and none reaches the wire.

The request already in flight is answered cancelled or disconnected at
random: its waiter's context derives from the peer's, which ends with
it. v0.6.0's root derived it the same way; the test accepts either.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Port the authentication, origin, subprotocol, dial-deadline and Prepare
tests of runtime/go/peer_test.go and prepare_test.go from Nightseam
v0.6.0 (5cc9723a). What a peer serves is a dispatcher attached in
Prepare; the tunnel whose first channel.open met it becomes a first
request over 3000 dials (250 with -short).

A new test reads the close frame over a real WebSocket: 4011 and the
refusal for a refused frame, and no close frame at all for 1005 and
1006 (R27). The rows for a sendable code chosen outside the read loop
(Wire().Close(1008), Close()) are skipped: Peer.end cancels the peer's
context before the close handshake, coder/websocket drops the socket
when a pending Read's context ends, and the close frame is often lost.
v0.6.0's end and WebSocket transport do the same with the same library.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ported engine tests skipped the WebSocket close of a peer ended from
outside and accepted cancelled for a call the peer's end cut off, both
inherited from v0.6.0. With the peer now closing its connection before
cancelling its context and answering such a call disconnected, the tests
require exactly that: 20 runs under -race deliver the chosen code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
README, charter and layout name the delivered components, the Go packages
of the one root module and the TypeScript package's subpaths, why they are
released together, and the interoperability evidence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…me to Bitwire 0.3.0

One package, @bitspark/bitruntime 0.2.0, built at the repository root,
replaces core/ts's own manifest. It exports the subpaths ./core,
./transports, ./engine and ./dispatch; sources stay under
<component>/ts/src and import each other by relative path. The received
context, frame validation, the envelope codec, path encoding, the request
primitive and the Unicode guard live in core/ts/src/internal, which no
subpath exports, so received context stays unforgeable.

- transports: the seam, pipe and WebSocket adapter, the close-code
  constants and sendable; close refuses an observe-only code (R27).
- core: at, mount, forward, pair (PairOptions of its own), the invocation
  lifecycle, PublicError (was DuplexError), UnpublishedError, respond,
  trace propagation; MissingPathError, InvalidPathError and
  ReceiverExistsError replace the duplex wire error codes.
- dispatch: call, emit, handle, register, onEvent, Dispatcher,
  SelectedEndpoint; handler contexts no longer reach the carrier.
- engine: Peer, presented only through wire(); a name that encodes no
  path is answered method_not_found, as a v0.6.0 peer without that
  handler answers. bitwire/1 frames are unchanged.

Fixed rather than ported, as in Go: nightseam#722 (a closing pair and the
peer's root answer queued refusals), nightseam#658 (a pair response frees
its slot before the caller holds it), R26 (every ended carrier reports
PublicError 'disconnected' with its cause), research 0001 row 14 (forward
fails only the refused message). Since every request now takes the root,
local frames omit empty trace members and the root forwards the trace a
request arrived with; v0.6.0's root refused its own answer to a request
carrying a tracestate alone.

Removed: the peer's raw handle/onEvent/call/emit and options.dispatch,
context.peer, observers and family labels.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ported from Nightseam v0.6.0 at 5cc9723a: duplex's pipe and WebSocket
adapter tests with their conformance suite, at/mount and path encoding,
wire-pair, invocation, invocation-experiment (two independent lifecycle
integrations and an opaque wrapper, public facilities only), dispatcher,
dispatcher-ownership, wire, wire-forward, wire-event-context, peer,
peer-pacing, serial (vectors/bitwire-1/serials.json), carriage (every row
of vectors/bitwire-1/frames.json), unicode (vectors/bitwire-1/unicode.json),
trace and publication. Raw-API tests go through wire() and the dispatch
helpers, or raw frames whose names are path encodings.

Not ported: observer and family assertions, the tests of the observer
alone, the pacing of the removed raw emit and call, the 'peer' route of
the cancellation matrix and the validator rows of the unicode test. Each
changed expectation is a documented change, noted where it stands.

Added: regressions of nightseam#722 and #658 (200 chained calls), the
queued-cancellation slot, R26, R27, forward's row 14, R28 on the peer's
root, a request cut off by its peer's end answering disconnected, close
codes transmitted on every ending, method names that encode no path, and
two peers over a real socket (Node's WebSocket against a ws server)
calling, emitting and cancelling both ways.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jm9e and others added 8 commits September 26, 2026 12:28
…om the root

CI and the release workflow run npm ci, check, build and test at the
repository root and pack the root package. The package smoke installs the
packed @bitspark/bitruntime into a fresh consumer, runs the tree smoke
through /core, a pair and dispatcher call through /core and /dispatch and
a peer over a pipe through /engine and /transports, and holds that no
import reaches the private internals.

RELEASING.md, docs/RELEASE.md, the READMEs and LAYOUT.md record the one
package and its subpaths; nothing is released. docs/port-from-nightseam.md
gains the TypeScript names and the TypeScript-specific behavior changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bitruntime TypeScript program joins the scenario. All sixteen
pairings of bitruntime and Nightseam v0.6.0, Go and TypeScript, pass over
real WebSockets, and bitruntime TypeScript's transcript equals Nightseam
v0.6.0 TypeScript's byte for byte, as Go's equals Go's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The wait handler now emits a waiting event first, and the recorder sends
its cancellation only after it. A cancellation that raced the handler's
start was answered with the runtime's own message in TypeScript
("Request was cancelled.") and the handler's otherwise, in Nightseam v0.6.0
as in bitruntime, so the TypeScript transcripts could differ by timing.
Three consecutive runs of all sixteen pairings and both byte comparisons
pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g its caller

As in Go: a reply to a request a carrier admitted that cannot travel, not
even as its bounded fallback, now settles as the bounded internal error,
and the peer's own response path fails the connection when that does not
fit. The delivered call ends with the connection at once instead of at its
deadline; the updated test fails after 10 s without the fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… found

- A failed Prepare left the root unstarted, so what Prepare attached was
  never told the peer ended (v0.6.0 started the root when Prepare selected
  it). The root now runs before Prepare.
- A peer ended by the context it was made with could answer a call its
  root had handed on as cancelled; Respond now classifies a closed carrier
  before a context error, and the waiter reads the peer's end.
- Malformed-frame close reasons use v0.6.0's decoding type names again,
  and the pre-profile refusal keeps its v0.6.0 wording; the remaining
  difference is documented.
- An invalid path from a pair or the root is core.ErrInvalidPath, and a
  transport that ends itself on an over-limit frame, an unknown message
  kind or a write past its context reports a closed carrier.

Regression tests fail without the engine fixes. Interoperability and both
byte-level comparisons are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Apply the organization's naming rule (Bitspark/.github NAMING.md): bitwire,
nightseam, bitstore, deixis and the other projects are written exactly as
their repositories are named in prose. Code spans, identifiers and
protocol names (bitwire/1) are unchanged. Checked with the organization's
naming.mjs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jm9e jm9e changed the title runtime: implement the path hand-written adapters use, ported from Nightseam v0.6.0 runtime: implement the path hand-written adapters use, ported from nightseam v0.6.0 Sep 26, 2026
…Script port found

- A close code the browser WebSocket API refuses (anything but 1000 and
  3000-4999) threw inside the peer's close and left the socket open; the
  connection now closes without the code instead.
- A connected peer whose connection is closing refused sends with
  not_connected, and an emit racing the close ended the peer and lost the
  far side's close code; it now reports disconnected and leaves the close
  to arrive.
- A reply a pair admitted but its caller's return capability refused,
  such as one over a smaller frame limit before a forwarder, stranded the
  caller until its deadline, in Go and TypeScript; the caller now gets the
  bounded internal error.
- A handler could reach the endpoint behind its dispatcher through a
  TypeScript-private field; it is now #private.

Each has a regression test that fails without its fix. The Go lifecycle
test waiting on onRetired now waits for the callback, which runs after
Retired reports true outside the lock (a race inherited from v0.6.0's test).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant