feat(signaling): accept the portal's flat dialect on decode (C4) — ⚠ build in flight #54
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "c4/wire-envelope-dialects"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
C4 / Track 3. ⚠ Do not merge until the t7 build is green —
capture/cannot be compiled here, and a build is running against this branch now (currently ~54/638 targets).The plan was wrong about two things
1. Physics is not a second dialect. I'd assumed it carried serde aliases to strip. It doesn't —
SignalingEnvelopeis#[serde(tag="type", rename_all="snake_case")]over variantsOffer/Answer/Ice/…, emitting exactly"offer","answer","ice": byte-identical to this codec's accept-list. Thesdp_offerstrings in that file are log labels (lines 1072, 1096), not wire tags. The plan's "delete physics's serde aliases" step has nothing to delete.2. The portal is the real second dialect, and genuinely incompatible (
browser_screencast_webrtc.rs:48-53):Flat: no
from, payload inlined.Decode()rejected it on all three counts. It works today only because physics translates in the middle.What this does — and explicitly does not
Teaches
Decode()the flat dialect, normalizing to the sameEnvelope. Encode is untouched: we always emit canonical, because an emitter choosing a dialect per-peer would need to know which peer it's addressing, which this codec deliberately does not.It does not make
pattern_c_envelope_to_portal_msgdeletable, contrary to the plan's ordering. That translator is lossy the other way — it returnsNoneforbye,request_renegotiate,probe_result,session_unhealthy, so those four never reach the portal at all. Removing it needs the portal to learn them first. Two changes, not one; conflating them would silently drop frames.Design points worth review
PortalTagFromStringis separate fromTagFromString, and a test asserts the tag spaces stay disjoint.Decode()consults both — that's where the widening lives. Keeping it out ofTagFromStringis what stops "canonical" quietly becoming "anything we accept".restart_ice— well-formed in every respect except its tag.fromis inferred askClientfor flat frames. That's a statement (the portal is the only producer), not a default — callers switch onfrom, so leaving it indeterminate wasn't an option.ice_candidatewith nocandidateis end-of-candidates; canonical says that withdata: null, which has no flat equivalent.About the test I deleted
RejectsHistoricSdpOfferTagcalledsdp_offer"the historic v0 tag" and existed so that flipping this contract required an explicit argument rather than a quiet edit. The framing was wrong on the facts — it's neither historic nor v0, it's live — and this PR is that argument, made where the tripwire pointed.Lockstep changes
conformance/suites/signaling-wire.mjs:sdp_offermoves fromREJECTED_TAGSto a newPORTAL_TAGS. Without this the kit would assert the opposite of the code it ships beside.docs/protocols/signaling-envelope.md: new — there was no envelope spec at all, which was defensible with one dialect and an audience holding the tree. It isn't now.Verification status
make verify,cxx-include-lintclean (121 files), conformance 23/23⚠ UNVERIFIED C++ — capture/ cannot be compiled here (no local Chromium; a build is 4-8h cold). A t7 build is running against this branch; do not merge until it is green. The research first, because the original plan was wrong about two things: 1. Physics is NOT a second dialect. webrtc_signaling.rs's SignalingEnvelope is #[serde(tag="type", rename_all="snake_case")] over variants Offer / Answer / Ice / …, so it emits exactly "offer", "answer", "ice" — byte identical to this codec's accept-list. The `sdp_offer` strings in that file are LOG LABELS (lines 1072, 1096), not wire tags. The plan's "delete physics's serde aliases" step has nothing to delete. 2. The PORTAL is the real second dialect, and it is genuinely incompatible. portal/src/canvas/browser_screencast_webrtc.rs:48-53: {"type":"sdp_offer", "sdp":"v=0..."} {"type":"sdp_answer", "sdp":"v=0..."} {"type":"ice_candidate", "candidate":"...", "sdpMid":"0", …} Flat: no `from`, payload inlined as siblings of `type`. Decode() rejected it on all three counts. It works today only because physics translates in the middle (pattern_c_envelope_to_portal_msg, screencast_ws.rs:6909). This change teaches Decode() the flat dialect and normalizes it to the same Envelope struct. Encode is untouched: we still always EMIT canonical, because an emitter that picked a dialect per-peer would need to know which peer it is addressing, which this codec deliberately does not. What this does NOT do, stated because the plan implied otherwise: it does not make pattern_c_envelope_to_portal_msg deletable. That translator is LOSSY in the other direction — it returns None for bye, request_renegotiate, probe_result and session_unhealthy, so those four tags never reach the portal at all. Removing it needs the portal to learn those tags first. "Liberalize the decoder" and "delete the translator" are two changes, not one. Design notes: * PortalTagFromString is a SEPARATE function from TagFromString, and the test asserts the two tag spaces stay disjoint. Decode() consults both; that is where the widening lives. Keeping it out of TagFromString is what stops "canonical" quietly becoming "anything we accept". * The accept-list stays CLOSED — three tags wider, not open. A new negative test uses `restart_ice`, well-formed in every respect except its tag. * `from` is absent in this dialect and is inferred as kClient. That is a statement (the portal is the browser-facing UI and the only producer of these frames), not a default — a future flat-but-not-client producer must send canonical instead. Callers do switch on `from`, so leaving it indeterminate was not an option. * A flat `ice_candidate` with no `candidate` field is the end-of-candidates marker: canonical says that with `data: null`, which has no flat equivalent because the payload IS the frame. The old RejectsHistoricSdpOfferTag test called sdp_offer "the historic v0 tag" and existed so that flipping the contract required an explicit argument rather than a quiet edit. The framing was wrong on the facts — it is neither historic nor v0, it is live — and this commit is that argument, made in the place the tripwire pointed at. conformance/suites/signaling-wire.mjs moves in lockstep: sdp_offer leaves REJECTED_TAGS for a new PORTAL_TAGS export. Without that the kit would assert the opposite of the code it ships beside. Verified locally: make verify passes, cxx-include-lint clean (121 files), conformance 23/23. NOT verified: that any of this compiles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>✅ Build green — the C++ is no longer unverified
t7 compile of this branch:
All ten steps passed. The portal-dialect decoder compiles against
refs/branch-heads/7727.Two things the build surfaced that reviewers should know
1.
cb_wire_envelope_test.ccis NOT in any test target. STEP 7 runs exactly two binaries:So the tests I added — and the ones that were already there — compiled but did not execute. My earlier claim that this codec's tests "lock" the contract was true of the source and false of CI. The negative test that supposedly guarded
sdp_offerhas never actually run in the build lane.That is a separate gap from this PR and I'd rather flag it than quietly fold it in: adding a
cloud_browser_signaling_unitteststarget changes what the build lane does, and deserves its own change and its own review. Filed as follow-up work.2. Two
[chromium-style]warnings oncb_wire_envelope.h(lines 164, 217 — "Complex class/struct needs an explicit out-of-line constructor/destructor"). I checked whether they were mine: they point atIceCandidatePayloadandEnvelope, both pre-existing and untouched by this patch. The line numbers only moved because my comments shifted the file. Not introduced here, not fixed here.Verification summary
make verifycxx-include-lintThe last row is why I'm not calling this fully proven. The decoder compiles and the logic is straightforward, but the assertions I wrote have not run anywhere.
✅ Verified: the portal-dialect assertions compile, run, and pass
This took three build iterations because each fix uncovered the next defect. Final run, with the build machinery from #56 + #57 underneath:
32/32 wire-envelope tests pass, including all five new ones:
(32, not main's 28: five added, one —
RejectsHistoricSdpOfferTag— replaced.)A false positive I nearly reported
The run before this one also said "SUCCESS, cb_wire_envelope_unittests invoked, all tests passed" — and I nearly posted it here. One name in the output stopped me:
RejectsHistoricSdpOfferTag, the test this PR deletes. And noAcceptsPortalFlat*anywhere.That branch carried the two build fixes but not C4 itself, so it had verified main's codec.
grep -c PortalTagFromStringon it returns 0. The claim would have been true-but-hollow: the assertions that actually needed proving never ran.Worth stating because "the tests passed" is exactly the kind of green that has been wrong three times in this chain already.
Dependency
Do not merge before #57 and #56. Without #57 the test does not compile (
//basewas a private dep); without #56 STEP 7 never invokes it. On main alone, this PR's tests would build-and-vanish — the original problem.Verification table
make verify/ include-lint / conformance