SPEC §23: the Event Feed Connector contract - #614
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c8a450bd41
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR adds SPEC.md §23 "Event Feed Connector", a cross-language, spec-normative contract for BC3's account-wide event feed (push lane via Action Cable WebSocket + poll lane via GET /events.json). It is documentation-only — no code, Smithy, or openapi.json surface — and is the first of a three-PR program (registry entry #606; fixtures and Go reference connector follow as PRs 2–3). It defines the connector's 11-state/26-transition state machine, reason-level disconnect dispatch, present-class entry sequencing with the conjunctive save-ordering invariant, semantic-signal handlers, continuation/resume-URL validation, retry/backoff/timer discipline, dedupe, seam contracts, and checkpoint identity, all classified by BC3 provenance (provisional until BC3's merge-time gate).
Changes:
- Adds §23, a fully-specified Event Feed Connector contract (state machine, signals, retry, URL validation, checkpoint identity, seam contracts, verification tiers).
- Narrows §22's WebSocket / synchronous-first exclusions to carve out this one sanctioned Action Cable lane.
- Extends Appendix A with the
EVENT_FEED_*constant block (and fixes the stale 13→14 preamble row tally) and adds an Appendix F table recording per-SDK tier-2 scenario-lane divergences.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Suppressed comments (1)
SPEC.md:2860
- This line uses Unicode curly apostrophes (U+2019) in "PR’s" and "§23’s", inconsistent with the straight ASCII apostrophes used everywhere else in SPEC.md (including the rest of §23 added in this PR, e.g. SPEC.md:2818). Together with line 2044, these are the only two curly-apostrophe lines in the file. Consider replacing them with straight apostrophes.
Only `API_VERSION` is gated (`<!-- @api-version -->`, checked by `make doc-constants-check`). The other 14 pre-§23 rows are hand-maintained: 13 were read against their cited sources on 2026-08-03 — all 13 matched — and `MAX_BACKOFF_DELAY` joined with #592 under that PR’s own six-SDK verification (the sentence previously said 13 rows while the table carried 14). The `EVENT_FEED_*` block below them is different in kind and marked so: those rows are contract-first — their source is §23’s normative text, connector code ships in later PRs, and the two server-owned values are provisional until bc3’s merge-time gate; when the connector lands, they join the read-against-source discipline. They are not gated because each is asserted of several SDKs at once in a different spelling per language (Go `1 * time.Second`, Python `1.0`, Ruby `1.0`, Kotlin `30.seconds`, Swift `1_000`), so a checker would need a per-row, per-language extraction rule rather than the one-value-one-source substitution the marker convention is built on. The name in the table is the concept, not a symbol to grep: `MAX_ERROR_MESSAGE_LENGTH` is `MaxErrorMessageBytes` in Go and `MAX_ERROR_MESSAGE_BYTES` in Ruby, and `TOKEN_REFRESH_BUFFER` is the literal `300` in `creds.ExpiresAt-300` (`go/pkg/basecamp/auth.go`) rather than a named constant at all. If one of these starts moving, gate that row rather than the appendix.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…or, bounded pump Codex found two P1 holes and a P2. Ordinary generated-operation poll errors outside the feed's 400/409/410 matrix now have defined outcomes: 401/403 (post-refresh) is a new unauthorized poll-error kind riding the reconnect cycle on the shared authorization counter, and anything else non-retryable is a new unrecoverable kind passing through as Terminal(poll_failed) with the generated error attached. The 400/409 reset cursor is pinned poll-lane-only (last poll-served id, never a live-delivered id — a live id above the durable position would skip the un-polled gap behind it; empty pages don't advance it). The frame pump's hand-off queue is specified as bounded and blocking (back-pressure to TCP, never a drop), so the live buffer remains the only drop point and connector memory is bounded under a slow consumer. Copilot's two curly apostrophes are straightened.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c51f9a4619
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…sthrough Three follow-on findings from the re-review. The shared authorization counter now resets only on a successful poll page — never on confirm_subscription, which proves the ticket worked but not the bearer, and would let alternating poll-401/reconnect/confirm cycles hold the counter below threshold forever (a connect blip still clears promptly via the post-confirmation catch-up poll). The present-class fallback for 400/409 re-entries is keyed to the absence of a poll-served id, matching the poll-lane-only reset cursor. And CreateStreamTicket gains the same unrecoverable(error) passthrough polls got: non-retryable non-auth mint outcomes are Terminal(mint_failed) with the generated error attached.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98c3ae348b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
SPEC.md:2553
- The
authorization_failedtrigger here lists only two of the three failure shapes that increment the shared authorization counter. The rest of §23 is consistent that the counter spans three shapes: transition 5 ("shared counter across unauthorized mints,unauthorizeddisconnects, and unauthorized polls"), the "Connection-level authorization failures" bullet, and the Continuable list just below this table (which counts "unauthorized-kind poll errors below the shared-counter threshold"). Since this is a[conformance]-tagged normative summary, omitting the unauthorized-poll shape here could lead an implementer reading only this table to miss that poll 401/403s also count towardauthorization_failed. Consider adding the poll shape for consistency.
| `authorization_failed` | 3rd consecutive connection-level authorization failure (unauthorized mint or `unauthorized` disconnect) |
The blessed connector for BC3's account-wide event feed (BC3 #9646/#9659), specified ahead of the generated layer: the 11-state/26-transition machine with reason-level disconnect dispatch and a four-row matrix; present-class entry sequencing against a defined ownership cut, with the conjunctive save-ordering invariant and its first-usable-checkpoint exclusion; the semantic-signal handler contract (BufferOverflow/FeedGap as distinct types, Accept|Terminate dispositions, no handler means a typed terminal, a registered handler is invoked exactly once per signal); delivered-id dedupe; five seam contracts with seam-call-count semantics; continuation and 410 resume URLs validated with §8's same-origin algorithm before any authenticated follow (terminal invalid_continuation on failure); one pinned poll-retry algorithm (Retry-After exact and cap-exempt per §7, else full-jitter on a separate consecutive-poll-failure index); six timer kinds and the normative virtual-advance algorithm; srv1 checkpoint identity with the published vectors; per-language option naming; security invariants. §22 narrows its WebSocket and synchronous-first bullets to carve out this one sanctioned lane. Appendix A gains the EVENT_FEED_* block with a preamble amendment sanctioning contract-first rows (and correcting the stale 13-row tally to 14); Appendix F records the per-SDK scenario-lane divergences. No §19/§21/Appendix-D rows for the parallel fixture families, matching the oauth-family precedent. Everything bc3-derived is verified at bc3 branch head 8be5c67de5 (lineage ee19670c02) and classified in the Provenance block — wire literals frozen on regenerated transcripts, semantic behavior re-verified row-by-row at bc3's merge-time gate — with the SDK-owned contract ungated. The wire operations stay tracked in spec/api-gaps/event-feed.md until the BC3 contract merges.
…or, bounded pump Codex found two P1 holes and a P2. Ordinary generated-operation poll errors outside the feed's 400/409/410 matrix now have defined outcomes: 401/403 (post-refresh) is a new unauthorized poll-error kind riding the reconnect cycle on the shared authorization counter, and anything else non-retryable is a new unrecoverable kind passing through as Terminal(poll_failed) with the generated error attached. The 400/409 reset cursor is pinned poll-lane-only (last poll-served id, never a live-delivered id — a live id above the durable position would skip the un-polled gap behind it; empty pages don't advance it). The frame pump's hand-off queue is specified as bounded and blocking (back-pressure to TCP, never a drop), so the live buffer remains the only drop point and connector memory is bounded under a slow consumer. Copilot's two curly apostrophes are straightened.
…sthrough Three follow-on findings from the re-review. The shared authorization counter now resets only on a successful poll page — never on confirm_subscription, which proves the ticket worked but not the bearer, and would let alternating poll-401/reconnect/confirm cycles hold the counter below threshold forever (a connect blip still clears promptly via the post-confirmation catch-up poll). The present-class fallback for 400/409 re-entries is keyed to the absence of a poll-served id, matching the poll-lane-only reset cursor. And CreateStreamTicket gains the same unrecoverable(error) passthrough polls got: non-retryable non-auth mint outcomes are Terminal(mint_failed) with the generated error attached.
98c3ae3 to
34948f3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
SPEC.md:2553
- The
authorization_failedtrigger here lists only two of the three failure shapes that feed the shared authorization counter. Elsewhere §23 is explicit that the counter increments on three shapes — unauthorized mints,unauthorizeddisconnects, and unauthorized polls (transition 5: "shared counter across unauthorized mints,unauthorizeddisconnects, and unauthorized polls"; and the pinned interpretation above, which concludes "Only these three failure shapes ever increment it"). Since this is a normative reference table that a reader may consult in isolation, the omission of the unauthorized-poll shape is an internal inconsistency. Consider listing all three.
| `authorization_failed` | 3rd consecutive connection-level authorization failure (unauthorized mint or `unauthorized` disconnect) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34948f351b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…, governance Six findings. The handshake deadline arms on entry to Connecting, before dial, and CableTransport.dial must be cancellable — a stalled TCP connect or HTTP upgrade can no longer hang the connector or block close(). A dial refused by cable-URL policy (non-wss, redirect, unparseable) is the new Terminal(invalid_cable_url) rather than a Backoff loop that re-mints into the same unusable URL forever. The protocol-fatal disconnect is pinned terminal from every socket-open state (the pump runs through CatchingUp and Draining), not just the two numbered rows. Both jitter formulas inherit §7's saturate-before-exponentiating rule. The authorization_failed terminal row lists all three counter shapes. And AGENTS.md Hard Rule 2 now names the §23 cable dial as the second sanctioned wire exception, so the connector PRs don't land in conflict with repository governance.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 708f31276c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
AGENTS.md:68
- The PR description states this change is "SPEC.md only — no code, no Smithy/openapi surface" and its "Companion edits" list only enumerates SPEC.md sections (§22, Appendix A, Appendix F). However, this PR also modifies
AGENTS.md, amending a hard rule ("NEVER add hand-written service methods that touch the wire") to sanction a second exception for the §23 cable dial. The AGENTS.md edit itself is correct and necessary to keep the hard rule consistent with §23 — it's the description that is inaccurate. Consider updating the description to disclose the AGENTS.md change, since it modifies a "NEVER" rule rather than being an incidental doc tweak.
2. **NEVER add hand-written service methods that touch the wire** — all wire operations come from generators. Two sanctioned exceptions: a conformance-tested composite that only calls generated wire methods and satisfies SPEC.md §18 "Hand-Written Composite Methods"; and the SPEC.md §23 Event Feed connector's cable dial — `CableTransport.dial(mint.url)`, connecting verbatim to the URL a generated `CreateStreamTicket` call returned. That dial is the connector's one non-HTTP wire act; every HTTP exchange it makes still flows through generated operations behind its `TicketMinter`/`PollSource` seams (§23 "Classification: Infrastructure, Not a Composite")
The ownership cut becomes one bounded admission pass (non-blocking receives until momentarily-empty or liveBufferCapacity admissions, whichever first): the previous first-empty-receive definition never completes under sustained arrival at or above the admission rate, so the entry position could never save — and the capacity bound is finite without weakening the retained set, since further admissions would evict retained events anyway. The dial seam gains an explicit cancellation channel (language-native; triggered on handshake-deadline expiry and close(), with prompt return required) and a typed error taxonomy — transient versus policy(reason) — so a redirecting or otherwise policy-refused mint URL is dispatchable to Terminal(invalid_cable_url) instead of being indistinguishable from a transient dial failure and looping through Backoff forever.
…it note Staleness suspension is realized at evaluation, not arming: the timer stays armed in every socket-open state (the exact-set invariants are unchanged), and a firing whose window overlapped a pump-blocked interval is disregarded and re-armed rather than dispatched. Appendix F records the TypeScript lane's honest divergence: the global WebSocket exposes no read limit, so the frame cap is enforced at message receipt — before any parse, decode, or queueing, though after materialization — with true bounded reads available via an injected transport.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 654959ebcd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The seam cancellation channel now also triggers on any teardown of the attempt a call belongs to — mid-walk socket failure, staleness, a terminal — not only close() and caller cancellation, and the teardown-discipline paragraph names the step: a stalled poll must not delay the reconnect cycle or return into a superseded attempt.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db2ef02d54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…pacities The every-delay-through-Clock rule is scoped to the connector's own delays — a seam call's internal §7 retry backoff is the generated operation's native machinery, which is why conformance counts seam calls rather than wire attempts. close() and cancellation now unblock an in-progress write_frame, with write failures taking the current state's socket-failure path. And the capacity options are construction-validated positive: a zero dedupe capacity would silently break the deduplicated-surface promise, so there is no dedupe-disabled mode.
|
Merging on green. Twelve review rounds absorbed (38 findings, every one fixed in a dedicated commit and its thread resolved with the fix reference — none declined): the contract holes (poll/mint error passthrough, poll-only reset cursor, shared-counter reset semantics), the lifecycle holes (pre-dial handshake deadline, cancellable dial/writes/seam calls, teardown cancellation), the liveness holes (bounded ownership cut by dequeued frames, staleness suspension as an evaluation rule), the security tendrils (in-read frame cap, hostile-continuation validation reaching §18/§20/Appendix C), and the governance alignment (AGENTS.md architecture + Hard Rule 2). The final three rounds were single-digit P2 interaction polish on text the PR-2/PR-3 cycle re-submits to these same reviewers alongside the Go implementation that will test it empirically — remaining findings land there. Copilot's reviewer job errored on the last several heads (its known flake today); its substantive reviews at earlier heads are all addressed. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7fb45af7da
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Correction to the merge comment above, for the record: this PR was NOT merged on green — npm Audit had failed at 19:52:50Z on the fast-uri advisory (the day's third supply-chain advisory, distinct from #616's brace-expansion; since repaired by #624), with the merge at 19:57:12Z, and the final Codex review arrived 69 seconds post-merge carrying two real findings (the CheckpointStore error contract and redirect-following after prevalidation — threads above, left open deliberately). Both are bound into PR 2 as a small SPEC §23 repair plus fixtures 28–30 and two new kill-matrix mutations, before any PR 3 implementation. |
…#645) * Event-feed conformance families, their gates, and the §23 G-SD repair The tier-2 scenario family (conformance/event-feed/): a 22-fixture set against its own schema — the four acceptance behaviors, reason-level disconnect dispatch, present-class entry sequencing against the bounded ownership cut, the semantic-signal handler contract with exact handler-invocation records, hostile-continuation and redirect coverage, and checkpoint-store failure coverage — with the README carrying the PROVISIONAL(8be5c67de5) dependency table, per-action strictness semantics, the PR-T true-up checklist, and the fifteen-mutation kill matrix. The sibling digest family (conformance/event-feed-digest/) carries BC3's published srv1 five-vector table (each digest independently recomputed from the canonicalization) plus checkpoint flat-key cases covering origin canonicalization. Both wire into make check via event-feed-fixtures-check and event-feed-digest-fixtures-check as conformance prerequisites. SPEC §23 gets the G-SD-reopening repair (#614's two open threads): CheckpointStore becomes tri-state — load → Loaded|Missing|Failed(error), save → Saved|Failed(error) — because a boolean/void shape cannot express the failures §23 dispatches on; and the poll seam must suppress automatic redirect-following (or per-hop validate every resolved Location under §8's rule), because prevalidating a continuation does not validate its redirects and the HTTP stacks' auto-follow falsified the zero-foreign-egress guarantee. Plus three review-hardening pins: semantic signals dispatch at the first consumer-context opportunity, the in-memory position is authoritative within a run (the store is write-through durability only), and the terminal table's invalid_continuation row covers failing redirect Locations. * Address the #645 review: a redirect-refusal kind on the poll seam The redirect repair required Terminal(invalid_continuation) on a failing redirect Location, but the PollSource taxonomy offered an adapter no kind to surface it — anything unclassified collapses to unrecoverable and hence poll_failed, contradicting the required outcome. The seam gains redirect_refused(location_origin) with the explicit adapter mapping: a 3xx whose Location fails the per-hop validation surfaces as redirect_refused (origin-redacted) and dispatches to invalid_continuation, never unrecoverable; a same-origin Location may be followed inside the seam under the same per-hop rule. * Allowlist srv1 checkpoint filter keys in gitleaks The digest fixtures carry srv1-prefixed filter keys — the SDK's checkpoint-lineage namespace over BC3's published filter-digest vectors, documented and recomputable from public canonical JSON. Their entropy shape trips the generic-api-key rule (two findings in srv1-vectors.json), but they are contract vocabulary, not credentials. The new .gitleaks.toml extends the default rules with one allowlist entry anchored to the exact srv1-<16 hex> shape; a local run over the full tree reports no leaks. * Run the event-feed fixture gates in PR CI The two new validators rode only the aggregate conformance target, which PR CI never invokes — the Python job's fixture-validation step is the actual enforcement point, so both gates now run there alongside conformance-fixtures-check, and the step's comment says why.
PR 1 of the Event Feed Connector program (#606 filed the registry entry; this specifies the Layer-2 connector contract ahead of the generated layer). SPEC.md only — no code, no Smithy/openapi surface.
What §23 specifies
reconnect:false-flag dispatch) and the four-row disconnect matrix, including theremote/reconnect:true server-initiated-disconnect row: revocation is not wire-distinguishable — re-mint, and a genuinely revoked user's failing mint is the detection path (threshold-3authorization_failedon one shared counter).save(P)only after all retained pre-cut events are accepted AND every pre-cut loss condition is explicitly accepted — and its explicitly stated exclusion (crash before the first usable checkpoint). No global delivery-completeness claim is made anywhere; the entry-boundary semantics follow BC3's resolvedsince=nowcontract (an in-flight lower id that commits after entry falls permanently behind the cursor; the live buffer is its only carrier).BufferOverflow/FeedGapas distinct types, synchronousAccept | Terminatedispositions, no handler ⇒ typed terminal (buffer_overflow/feed_gap— a 410 never silently auto-continues), and a registered handler invoked exactly once per signal before its disposition takes effect.nextand 410resumeURLs pass §8's same-origin algorithm + downgrade rejection before any authenticated poll; failure is the typed terminalinvalid_continuationwith zero requests to the hostile URL. The mint's cableurlis exempt by credential class (the ticket rides in the URL; noAuthorizationheader) under its own invariants.Retry-Afterwaited exactly and exempt from local caps (§7's rule), else full-jitter on a separate consecutive-poll-failure index with defined reset semantics; the reconnect lane keeps Retry-After-as-floor with the same cap exemption.{origin, account_id, consumer_namespace, filter_key}with the published five-vector table; the per-language options table; security invariants.Companion edits
EVENT_FEED_*block plus a preamble amendment sanctioning contract-first rows — and correcting the preamble's stale row tally (13 → 14; the 14th row arrived with Give the backoff formula a ceiling in all six SDKs, and retry a Swift Transport's own network error #592 under that PR's own verification).conformance/event-feed/,conformance/event-feed-digest/): those tables enumerateconformance/tests/operation-dispatch content only, and the existing parallel families (oauth, oauth-token) follow the same pattern — inventory lives in the family README and §23's Verification subsection. The tier-1event-feed-poll.jsontakes its rows when it lands with the generated layer.Provenance posture
Everything BC3-derived is verified at BC3 branch head
8be5c67de5(lineageee19670c02) and classified in §23's Provenance block: class-1 wire literals freeze when BC3 regenerates transcripts at its merge-time gate (theremotereason string is source-verified — its one missing capture frame is requested); class-2 semantic behavior (entry-boundary semantics, the safety bound, the frozen-headnextpredicate, 409/410/400 recovery semantics, the srv1 algorithm, ticket statelessness/TTL, heartbeat cadence, and more) is re-verified row-by-row at that gate; the SDK-owned contract is normative as written. Branch SHAs are verification records, never the provenance pin —doc-constants-checkis untouched and passes.Verification
make checkgreen on exactly this head (c8a450bd4), run uninterrupted:REAL_EXIT=0, "All checks passed".invalid_continuationedge and the poll-retry floor-vs-exact reconciliation — resolved in this text.The conformance fixture family (including the hostile-continuation pair and the handler-invocation observability mechanics) and the Go reference connector follow as PR 2 and PR 3; fixture freezing and Layer-1 absorption stay gated on BC3's merge-time gate.
Summary by cubic
Defines §23: a cross‑language Event Feed Connector contract for BC3’s account‑wide event feed — the 11‑state/26‑transition machine, reason‑level disconnects, present‑class entry with a bounded ownership cut, semantic signals, retry/backoff, continuation validation, checkpoint identity, and bounded memory with a transport‑enforced frame cap. Liveness resets on frame receipt; protocol‑fatal disconnects are terminal; invalid frames are recoverable socket failures.
WebSockethas no read limit, soEVENT_FEED_MAX_FRAME_BYTESis enforced at message receipt; true bounded reads are available via an injectedtransport.mint_stream_ticketandpolltake a cancellation channel and must return promptly, never racing back into a disposed attempt.write_frame; write failures take the current state's socket‑failure path.dedupeCapacitycannot be 0 (no dedupe‑disabled mode).Written for commit 7fb45af. Summary will update on new commits.