Repository navigation
Conversation
…(not wired yet) ocx-child answers Codex's Responses WebSocket upgrade with 426, so the saving of the link WebSocket does not reach Children that run it. This is the first piece of carrying it there: one frame reader for both legs (masked frames from Codex, unmasked from the Home) with the same bounds as the TypeScript Child's framer, and the writers. Nothing calls it yet.
…ed gate (not wired yet) - link_ws_advert: the state proof (same HMAC as the Home), exactly one state and proof header, per-link ordering by preflight number with watermarks for evicted links, memory only. - The gate is closed in every build without the link-ws-test-gate Cargo feature; only a test build with it reads OCX_CHILD_TEST_LINK_WS_READY.
… the Home advertises it (closed gate) With ocx-child's gate open (only a link-ws-test-gate build can open it) and the Home's verified on applied for the current link, the upgrade on /v1/responses now opens the Home leg on one authenticated connection and relays whole text messages both ways; every failure is today's 426. Two threads per socket, every frame write a bounded operation under a per-socket write lock, one reaper thread enforcing every deadline by shutting both sockets down, an idempotent terminal transition with a 5 s close bound, and a registry whose stop aborts setups and closes sockets with 1001. Slots are held until nothing can use the sockets.
…op before start, spawn failure A failed spawn of the Home reader thread before the local 101 now leaves Codex's connection to its owner, which answers today's 426, and only shuts the Home connection down.
… checks and the structure page
…ery and switch-off through the real binary
… the TypeScript suite CI runs
…ery setup write by the deadline
…x-child # Conflicts: # scripts/test-layout/layout-clients.json # tests/fixtures/test-layout-expected.json
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Contributor
|
✅ Deterministic PR hygiene checks passed. |
…1 s, so it ends inside detach's wait
…hrough the real stop the 426 is best effort
… the ordering tests use their own link id
…e and the Rust Child
…ters the socket and frees its slot
…Cargo runs bounded inside the batch deadline
…x-child # Conflicts: # scripts/test-layout/layout-clients.json
…turns its admission slot
… the stop test always meets a blocked write
…alone under its own process deadline
… a test never waits for the kernel to drop an orphaned connection
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The lightweight Rust Child (
native/ocx-child) answers Codex's Responses WebSocket upgrade with a fixed 426. Codex then uses HTTP for the whole session and re-sends the full conversation through the link tunnel on every model request. The TypeScript Child already carries that WebSocket over the link's authenticated connection when the Home advertises it (#8614, #8619, #8620); disposable cloud machines run ocx-child, so they kept paying the full HTTP cost.This change gives ocx-child the same path behind its own closed release gate:
link_ws_advert.rs): every relay-auth preflight of the serving process (relayed HTTP requests, the parity report, the/readyzprobe) takes a sequence number before it is written. Its answer'sx-opencodex-link-ws-stateandx-opencodex-link-ws-proofcount only as exactly one of each, with the proof an HMAC under the 32 bytes decoded from the fingerprint, equal to the Home'slinkWebsocketStateProof, compared in constant time; both sides check the same vectors intests/fixtures/link/link-ws-state-proof.json. An answer applies only to its own link id and only above the last number applied for it (8 links, per-link watermarks). Lifecycle commands run in their own process and teach the server nothing.link_ws.rs): with the gate open and a verified on for the current link, a valid Codex handshake withoutOrigintakes a slot (8 open or setting up, 4 setting up, 32 MiB of a 256 MiB budget), then makes one preflight and the upgrade on one connection inside one 8 s deadline, cancellable by stop. The upgrade proceeds only on that answer's own verified on. The Home leg carries Codex's headers minus its handshake, the hop-by-hop and Connection-nominated set,Origin,Host, framing, caller credentials and the relay's internal headers; the link key is added through the existing wiped request-head pattern. The Home's 101 counts only with Upgrade, Connection, the right accept value and no extension or subprotocol. Every failure before the local 101 is today's 426.link_ws_frame.rs) with the TypeScript Child's bounds (8 MiB from Codex, 4 MiB + 64 KiB from the Home, fragment cap, held bytes bounded). Each socket has two threads, and each frame write is one operation with an absolute deadline (data 600 s, control 30 s) registered before its write lock, plus a 300 s no-progress write timeout. One reaper thread enforces every deadline by shutting sockets down without taking a write lock. One idempotent terminal transition closes both legs within 5 s (close frames best effort): Codex's close without a status reaches the Home as an empty close, Codex's drop ends the Home connection without one, Home close codes are relayed, and binary closes both legs with 1003. Serve shutdown aborts setups, closes every socket with 1001 (best effort, each close given at most 1 s, all at once, so a stop ends well inside the 3 s that detach, restart and sync wait for the port) and waits until every slot is free. A panic in either of a socket's threads still ends both connections, unregisters the socket and frees its slot, and a relay worker or control thread that panics still returns its admission slot.relay::write_all_before), so a Home that drains slowly cannot stretch setup past 8 s. Preflights without a deadline write as before.child_busy/child_starting503s apply to upgrades as to HTTP). If the tunnel or Home drops mid-turn, Codex gets 1011 and retries on its own; nothing is replayed by the Child, so no request is duplicated.What keeps it inert:
link_ws_advert::gate_openis false in every build without thelink-ws-test-gateCargo feature, whatever the profile; only a build with the feature readsOCX_CHILD_TEST_LINK_WS_READY.build:ocx-child(cargo build --release --locked) passes no features. With the gate closed, or before the Home's verified on, the upgrade gets today's 426 byte for byte with no network I/O. A later change opens the gate.Differences from the TypeScript Child (on purpose, for this runtime):
child_busy/child_starting503s apply to upgrades as to HTTP);Unchanged: HTTP relaying, its headers and limits, and the 426 body. Lifecycle commands behave as before; their preflights now write through
write_all_before, with the same effective timeout. One shared-file edit outside the new modules:daemon.rssend_attested_shutdownis nowpubfor the shutdown test. ocx-child has no Child-local opt-out, counters or byte book for the link WebSocket, as it has none for HTTP.Verification
Tests (each behaviour commit was test-first; the later test commits cover paths that already existed):
native/ocx-child/tests/link_websocket.rs, feature build): today's 426 and no Home connection without the gate; frames both ways with it; a failed preflight teaching nothing and a later one teaching on with the parity loop stopped; a Home that switches off answered by the upgrade's own preflight with 426, the turn then completing on HTTP;/readyzteaching on; the authenticated shutdown closing both legs with 1001.tests/clients/link-ocx-child-ws-gate.test.tspins the feature wiring and runs both Cargo modes (skipped where cargo is missing, like the conformance test); it makes the run without the feature andtests/clients/link-ocx-child-ws-feature.test.tsthe run with it, one Cargo run per file; both are listed inSERIAL_FULL_SUITE_FILES, so each runs alone under the batch runner's 120 s process deadline, with its Cargo run bounded at 100 s. (scripts/ci/test-durations.tsvis generated from CI logs and is left alone.)Commands (exit codes) on f8cc664 (the T6-4-2 head 46ade1f; a merge of dev 0c0888b, which brought the ocx-child CI step from #8635; one test-only fix, below; a merge of dev 6f58f62, which brought #8612 into native/ocx-child; both merges were clean):
cargo fmt --manifest-path native/ocx-child/Cargo.toml --checkexit 0;cargo clippy --locked --manifest-path native/ocx-child/Cargo.toml --all-targets -- -D warningsexit 0;cargo test --locked --manifest-path native/ocx-child/Cargo.tomlexit 0 (54 lib tests, feat(link): recover a force-revoked ephemeral Child by its link id (ocx-child owner record /2, detach --expect-link, ocx link recover) #8612's 22 owner_link tests and all other suites green)link_websockettarget) is exercised only by the Bun wrapper testtests/clients/link-ocx-child-ws-feature.test.ts, not by that step. Run here on stable:cargo clippy ... --all-targets --features link-ws-test-gate -- -D warningsexit 0;cargo test ... --features link-ws-test-gateexit 0 (53 lib tests, 4 real-binary link WebSocket tests)cargo test --lockedexit 0, with--features link-ws-test-gateexit 0cargo build --release --locked(no features, asbuild:ocx-child): exit 0 on 46ade1f;grep -c -a OCX_CHILD_TEST_LINK_WS_READYon the binary: 0, so the shipped build cannot open the gatebun install --frozen-lockfile: exit 0;bun run typecheck: exit 0;bun run privacy:scan: exit 0;bun run structure:check: exit 0bun scripts/test.ts --parallel=2over tests/clients/link-ocx-child-ws-gate, link-ocx-child-ws-feature, link-ocx-child-conformance, link-ocx-child-e2e, link-ephemeral-child-discovery, link-child-plant-foundations, link-boundary, tests/ci-workflows/test-runner, ci-workflows/ci-scope-gaps, tests/test-layout, test-layout-tooling, ci-workflows/file-size-ratchet, ci-workflows/structure-ssot: exit 0 (248 pass, 4 skip, all Windows-only, 0 fail)A test-only fix found while running that step (2ea43f0): one plain
cargo testrun of the link_ws unit tests took 112 s instead of about 5 s. A loop reproduced it incodex_lost_while_a_write_to_it_is_blocked_ends_both_legs(about one run in eleven, 107 s, then passing). A thread dump of a stuck run showed the relay already finished and the test waiting to join its own flood thread, which was blocked writing toward a Child socket the Child had shut down with its receive window full, so the write ended only when the kernel dropped the orphaned connection. The four flood tests now shut their own end of the flooding socket before joining the flood thread; afterwards 100 looped runs of those tests had none over 3 s.Review: a gpt-6.1-terra reviewer (xhigh, read-only) reviewed the whole diff; round 1 found two P2 (stop vs the local 101; setup write deadline), both accepted and fixed with tests that failed first (
a_stop_before_the_local_101_answers_426_and_writes_codex_no_close_frame: Codex read a close frame where the 101 or 426 belongs;a_setup_write_ends_at_the_absolute_deadline_however_the_home_drains: injected clock, one byte per write); round 2 on the fix: no findings. The coordinator's fresh final review of d9c6b31 approved with nits (one P2 reachable only with the gate open, and P3s); each is fixed in its own commit, test-first where there is behaviour, and a gpt-6.1-terra round on those commits found one P2 (the gate test's two Cargo runs inside one 120 s batch process) and two P3s (the stop test's hook could fire before the closing check; a panicking relay worker leaked its admission permit), all fixed; the next round found none. A last round on the flood-test fix found none either. Open P0-P2: 0. The design addendum had nine review rounds before implementation.#8612 (owner record, detach --expect-link, recover) is now on dev and merged here without conflicts: it changed cli.rs, codex_home.rs, platform.rs and a new test, none of which this PR touches (this PR adds link_ws*.rs and changes Cargo.toml, lib.rs, relay.rs, daemon.rs). Both add entries to scripts/test-layout/layout-clients.json and tests/fixtures/test-layout-expected.json and paragraphs to structure/remote-link.md. This hand-over also edits
native/ocx-child/src/daemon.rsonly by the one visibility change already in T6-4-1.No docs-site page changes (the gate is closed; user docs come with the change that opens it).
NOT RUN: the full
bun run testsuite (4-core VM rule: focused files only); macOS/Windows; an opened gate against a live Home; tests/ci-workflows/docs-spanish-localization.test.ts. With #8630 the Rust suites also run in PR CI.Checklist