diff --git a/.imgbotconfig b/.imgbotconfig new file mode 100644 index 00000000..8c051b18 --- /dev/null +++ b/.imgbotconfig @@ -0,0 +1,13 @@ +{ + "schedule": "monthly", + "minKBReduced": 50, + "prTitle": "chore(assets): lossless image compression", + "ignoredFiles": [ + "**/*.xcassets/**", + "*/*.xcassets/*", + "**/Assets.xcassets/**", + "**/screenshots/**", + "*/screenshots/*", + "**/fastlane/**" + ] +} diff --git a/HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift b/HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift index 55ed5e74..78754bb2 100644 --- a/HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift +++ b/HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift @@ -65,6 +65,36 @@ public enum RemoteSessionPlane { isEnabled && isPaired } + /// Whether the helper may run the PRIVATE `pterm:` terminal producer. + /// + /// Added 2026-09-09, after shipping that producer without this check. + /// + /// `pterm:` exists to stream a terminal to the phone. This enum's own + /// docstring says the app "offers no remote sessions, terminals or + /// approvals", and the app-side consumer was removed with the rest of the + /// plane — so with the config flag on and this predicate absent, the helper + /// would have redacted, batched and POSTed a session's output to a topic + /// nothing subscribes to, for a plane both copies of this file declare + /// retired. Not a leak (the relay authorizes, and the READ policy still + /// scopes subscribers to their own sessions), but real work and real + /// egress in service of nothing. + /// + /// The conjunction is the point, and it is the same shape as + /// `shouldStartCloudTask`: the ops flag can only ever turn the producer + /// OFF sooner, never on past the retirement. Un-retiring is one edit, in + /// one place, reviewed on its own — which is what the flag is for. + /// + /// Lives here rather than inline in `main.swift` for the reason stated + /// above `shouldStartCloudTask`: main.swift is an executable target with no + /// test bundle, and a predicate a test cannot reach is a predicate nobody + /// has checked. That is exactly how this one shipped unchecked. + public static func shouldRunPrivateTerminalProducer( + configEnabled: Bool, + isPaired: Bool + ) -> Bool { + isEnabled && configEnabled && isPaired + } + /// Why the cloud task is not running, for an operator reading the log. /// "Retired" and "unpaired" are different facts and must not print the /// same line — confusing them is how a real outage gets read as a config diff --git a/HelperSwift/Sources/cli_pulse_helper/main.swift b/HelperSwift/Sources/cli_pulse_helper/main.swift index 24893bd8..0ab54f92 100644 --- a/HelperSwift/Sources/cli_pulse_helper/main.swift +++ b/HelperSwift/Sources/cli_pulse_helper/main.swift @@ -366,7 +366,17 @@ case "daemon": let pairingWithoutContainer: () -> AppGroupConfigReader.AppPairing? = { nil } let bootCloudCfg = configStore.cloudConfigSnapshot(appGroupReader: pairingWithoutContainer) - let privateBroadcastOn = configStore.privateTerminalBroadcastEnabled + // ⛔ ANDed with the RETIREMENT, not just the ops flag. `pterm:` streams a + // terminal to the phone, and `RemoteSessionPlane` — mirrored in both + // packages with a drift test — says the app offers no remote terminals and + // the consumer was removed with the rest of that plane. Producing for it + // would be redaction, batching and egress in service of nothing. + // + // This check was MISSING when the producer shipped (#549). The flag alone + // would have turned it on. + let privateBroadcastOn = RemoteSessionPlane.shouldRunPrivateTerminalProducer( + configEnabled: configStore.privateTerminalBroadcastEnabled, + isPaired: bootCloudCfg.isPaired) let broadcastPublisher: TerminalBroadcastPublisher? if bootCloudCfg.isPaired && configStore.remoteRealtimeEnabled { let provider: @Sendable () -> HelperConfigStore.CloudConfig = { diff --git a/HelperSwift/Tests/HelperKitTests/RemoteSessionPlaneTests.swift b/HelperSwift/Tests/HelperKitTests/RemoteSessionPlaneTests.swift index 8b5ee2e2..d8a6f810 100644 --- a/HelperSwift/Tests/HelperKitTests/RemoteSessionPlaneTests.swift +++ b/HelperSwift/Tests/HelperKitTests/RemoteSessionPlaneTests.swift @@ -37,6 +37,40 @@ final class RemoteSessionPlaneTests: XCTestCase { /// "Retired" and "unpaired" are different facts. Printing the same line for /// both is how a deliberate withdrawal gets read as a pairing problem — /// the confidently-wrong-status class this project keeps paying for. + func test_thePrivateTerminalProducerIsGatedOnTheRETIREMENT_notJustItsOwnFlag() { + // #549 shipped the `pterm:` producer gated only on + // `remote_private_terminal_broadcast_enabled`. Flipping that flag would + // have produced terminal output for a plane this very file declares + // retired, whose consumer was removed with it. + // + // All four combinations, because the conjunction is the whole point. + for paired in [true, false] { + for cfg in [true, false] { + XCTAssertEqual( + RemoteSessionPlane.shouldRunPrivateTerminalProducer( + configEnabled: cfg, isPaired: paired), + RemoteSessionPlane.isEnabled && cfg && paired, + "cfg=\(cfg) paired=\(paired)") + } + } + // And concretely, while the plane is retired: nothing turns it on. + XCTAssertFalse(RemoteSessionPlane.shouldRunPrivateTerminalProducer( + configEnabled: true, isPaired: true), + "the ops flag must not be able to outvote the retirement") + } + + func test_theProducerGateMatchesTheCloudTaskGateShape() { + // Same shape as its sibling, so un-retiring flips both together rather + // than leaving a producer running against a plane with no consumer. + for paired in [true, false] { + XCTAssertEqual( + RemoteSessionPlane.shouldRunPrivateTerminalProducer( + configEnabled: true, isPaired: paired), + RemoteSessionPlane.shouldStartCloudTask(isPaired: paired), + "with the ops flag on, the producer gate must track the cloud-task gate") + } + } + func test_theStartupNoticeDistinguishesRetiredFromUnpaired() { let paired = RemoteSessionPlane.startupNotice(isPaired: true) let unpaired = RemoteSessionPlane.startupNotice(isPaired: false) diff --git a/backend/supabase/functions/broadcast-terminal/index.ts b/backend/supabase/functions/broadcast-terminal/index.ts index c5da6b8c..3d4b8599 100644 --- a/backend/supabase/functions/broadcast-terminal/index.ts +++ b/backend/supabase/functions/broadcast-terminal/index.ts @@ -43,10 +43,17 @@ // authorized to write a revoked session's topic, and this function — which // the paragraph above calls the entire write-side boundary — would let it. // -// Closing it means a migration adding a status/consent predicate to that -// RPC, which is owner-gated. Until then, do not describe revocation as -// server-enforced anywhere: it is client-enforced, with a server that does -// not disagree. +// HALF-CLOSED 2026-09-09 by migrate_v0.83: the RPC now also requires +// `rs.status in ('pending','running')`, so a session retired to +// status='stopped' — which is exactly what revocation posts — stops +// authorizing. Verified against real rows: the old predicate set matched 1, +// the new one matches 0. +// +// STILL OPEN: consent. `cloudShared` is an in-memory helper flag never +// mirrored to the database, so a session that was never shared but is +// running would still authorize if a helper asked. Closing that needs a +// consent column the client maintains. So revocation is now enforced on +// BOTH sides; consent is still client-only. Do not conflate them. // The READ side is unaffected and still RLS-governed (migrate_v0.81), so // subscribers are still restricted to their own sessions. // diff --git a/backend/supabase/migrate_v0.82_r0_broadcast_insert_grant.sql b/backend/supabase/migrate_v0.82_r0_broadcast_insert_grant.sql index 600bb455..f40c4eae 100644 --- a/backend/supabase/migrate_v0.82_r0_broadcast_insert_grant.sql +++ b/backend/supabase/migrate_v0.82_r0_broadcast_insert_grant.sql @@ -1,6 +1,44 @@ -- ============================================================ -- v0.82 — the one grant v0.81 could not issue, and the policy that needs it --- Date: 2026-09-08 · *** NOT APPLIED — REQUIRES A supabase_admin-CLASS ROLE *** +-- Date: 2026-09-08 · *** SUPERSEDED 2026-09-09 — DO NOT APPLY, DO NOT ASK *** +-- +-- ╔══════════════════════════════════════════════════════════════════════╗ +-- ║ READ THIS BEFORE OPENING A SUPABASE SUPPORT TICKET. Everything below ║ +-- ║ is still TRUE and still worth reading for the measurements — but the ║ +-- ║ ask it recommends is no longer the right one. ║ +-- ╚══════════════════════════════════════════════════════════════════════╝ +-- +-- Three things changed on 2026-09-08/09, in the order that matters: +-- +-- 1. THE WRITE PATH WAS BUILT WITHOUT THIS GRANT. The `broadcast-terminal` +-- edge function (deployed v1, 2026-09-09) takes v0.65's own recorded +-- fallback — helper → edge fn → service_role — and service_role ALREADY +-- holds INSERT on realtime.messages. Proven end to end before it shipped: a +-- service-role publish to a private topic is delivered; an anon publish +-- gets the same HTTP 202 and is silently dropped. So the grant this file +-- exists to obtain buys nothing the product needs. +-- +-- 2. THE FEATURE IT SERVES IS RETIRED. `RemoteSessionPlane.isEnabled = false` +-- in three packages now (Swift helper, CLIPulseCore, and as of 2026-09-09 +-- helper/remote_session_plane.py, with a drift gate across all three). The +-- app offers no remote terminals; nothing subscribes to `pterm:` on any +-- platform. Both producers are gated on that flag. +-- +-- 3. THE SUPPORT ASK WAS THE EXPENSIVE PART. This file's own header calls it +-- a one-shot favour and says not to spend it before knowing the design +-- survives. It did not survive: it was replaced (1) and then retired (2). +-- +-- WHAT TO DO IF THE PLANE IS EVER UN-RETIRED: start from the relay, not from +-- here. The custom-role design needs a privilege the project owner cannot +-- obtain and support may decline; the relay needs none and is already deployed +-- and tested. Re-read `broadcast-terminal/index.ts` — including its stated cost, +-- that service_role is rolbypassrls so the edge function IS the write-side +-- boundary — before reviving anything in this file. +-- +-- KEPT, NOT DELETED, for the same reason the retirement was: the measurements +-- below (silent no-op GRANT, reserved membership, the NOINHERIT probe trap, +-- the definer/bypassrls trap) are the expensive part and a deleted dead end +-- gets rediscovered. -- -- ⚠️ RUN AS `postgres` AND THIS FILE DOES NOT ERROR. It warns and does -- nothing. The assertion block at the foot turns that silence into an diff --git a/backend/supabase/migrate_v0.83_authorize_broadcast_status_predicate.sql b/backend/supabase/migrate_v0.83_authorize_broadcast_status_predicate.sql new file mode 100644 index 00000000..1fbc5ac0 --- /dev/null +++ b/backend/supabase/migrate_v0.83_authorize_broadcast_status_predicate.sql @@ -0,0 +1,169 @@ +-- ============================================================ +-- v0.83 — close the server half of R0 revoke +-- Date: 2026-09-09 +-- +-- ── WHAT IS WRONG ───────────────────────────────────────────── +-- `remote_helper_authorize_broadcast` is the ENTIRE write-side boundary for the +-- private terminal relay: `broadcast-terminal` runs as service_role, which is +-- rolbypassrls, so no RLS policy is consulted on that path. Its own header says +-- so, and then names this gap. +-- +-- The function authorizes on exactly four predicates: +-- rs.id = p_session_id +-- rs.device_id = p_device_id +-- rs.user_id = v_user +-- rs.realtime_private is true +-- There is no status predicate and no consent column. M4.4d's `cloudShared` is +-- an IN-MEMORY helper flag never mirrored to the database, and revocation +-- (`unshareAttachedSession` -> `retireMintedRow`) only posts `status='stopped'`, +-- which this function does not read. +-- +-- So after a user revokes sharing, the row survives with realtime_private=true +-- and the server keeps authorizing writes to that session's topic. Revocation +-- has been enforced entirely client-side, by a helper that could be stale, +-- buggy, or replaced. +-- +-- ── WHAT THIS CHANGES ───────────────────────────────────────── +-- One predicate: the session must not be in a TERMINAL state. +-- +-- and rs.status in ('pending', 'running') +-- +-- Deliberately an allowlist of live states rather than `<> 'stopped'`: a future +-- terminal state (say 'errored', which `RemoteSessionStatus` already defines) +-- would otherwise keep authorizing. And deliberately including 'pending' rather +-- than requiring 'running': a helper that begins broadcasting in the window +-- between row creation and the first status post would otherwise earn a 42501, +-- which the Swift sink treats as an authoritative denial and suppresses the +-- session for a 60 s backoff. Denying a live session is a worse failure than +-- briefly admitting a pending one. +-- +-- ── BLAST RADIUS, MEASURED 2026-09-09 ───────────────────────── +-- remote_sessions 3 rows +-- realtime_private = true 3 +-- status 'stopped' for all 3 +-- status = 'running' 0 +-- So this authorizes strictly less than before and denies nothing that is +-- currently allowed-and-live: there is no live session to break. The three +-- rows it newly refuses are exactly the class this migration exists to refuse. +-- +-- Two callers, both fine with the narrowing: +-- * `mint-realtime-token` — mints for a live session; a stopped one has no +-- terminal to mirror. +-- * `broadcast-terminal` — same, and it maps 42501 to 403, which the Swift +-- sink suppresses for a bounded backoff rather than permanently. +-- +-- ── NOT A SUBSTITUTE FOR CONSENT ────────────────────────────── +-- This closes the REVOKE gap, not the consent gap. `cloudShared` is still not +-- in the database, so a session that was never shared but is running would +-- still authorize if a helper asked. Closing that needs a consent column the +-- helper writes, which is a bigger change and a schema the client must +-- maintain. Recorded here so the next reader does not mistake this for the +-- whole fix. +-- +-- Body below is the LIVE definition read back with pg_get_functiondef on +-- 2026-09-09, plus the one predicate — not reconstructed from the repo, because +-- production function bodies drift. +-- +-- Runs as ONE transaction. +-- ============================================================ + +create or replace function public.remote_helper_authorize_broadcast( + p_device_id uuid, p_helper_secret text, p_session_id uuid +) +returns uuid +language plpgsql +security definer +set search_path to 'pg_catalog', 'public', 'extensions' +as $function$ +declare + v_user uuid; + v_owner uuid; +begin + v_user := public._remote_authenticate_helper_gated(p_device_id, p_helper_secret); + if v_user is null then + raise exception 'unauthorized' using errcode = '42501'; + end if; + + select rs.user_id into v_owner + from public.remote_sessions rs + where rs.id = p_session_id + and rs.device_id = p_device_id + and rs.user_id = v_user + and rs.realtime_private is true + -- v0.83: a retired/ended session must stop authorizing. Revocation posts + -- status='stopped'; without this the server kept saying yes. + and rs.status in ('pending', 'running'); + if v_owner is null then + raise exception 'session not authorized for private broadcast' using errcode = '42501'; + end if; + + return v_owner; +end; +$function$; + +-- ------------------------------------------------------------ +-- In-transaction assertions. Each ABORTS, and each would have FAILED against +-- the pre-apply body measured above. +-- ------------------------------------------------------------ +do $$ +declare def text; +begin + select pg_get_functiondef(p.oid) into def + from pg_proc p join pg_namespace n on n.oid = p.pronamespace + where n.nspname = 'public' and p.proname = 'remote_helper_authorize_broadcast'; + + if def is null then + raise exception 'the function vanished'; + end if; + + -- Would have failed before: the predicate did not exist. + if def not like '%rs.status in (''pending'', ''running'')%' then + raise exception 'the status predicate is not in the deployed body'; + end if; + + -- The four original predicates must all survive. Narrowing is the point; + -- accidentally DROPPING one would widen authorization instead. + if def not like '%rs.realtime_private is true%' + or def not like '%rs.device_id = p_device_id%' + or def not like '%rs.user_id = v_user%' + or def not like '%rs.id = p_session_id%' then + raise exception 'an original predicate was lost — this would WIDEN authorization'; + end if; + + -- Shape must be unchanged: SECURITY DEFINER with a pinned search_path. + -- `create or replace` keeps these, but a future edit that retypes the header + -- could drop them silently, and this function is a write-side boundary. + if not exists ( + select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace + where n.nspname = 'public' and p.proname = 'remote_helper_authorize_broadcast' + and p.prosecdef + and p.proconfig::text like '%search_path=pg_catalog, public, extensions%' + ) then + raise exception 'SECURITY DEFINER or the pinned search_path was lost'; + end if; + + -- And EXECUTE must not have widened. anon reaching this would matter: the + -- helper paths are anon-reachable by design and gated on helper_secret, so + -- this only pins that the grant set did not CHANGE under us. + if has_function_privilege('anon', 'public.remote_helper_authorize_broadcast(uuid,text,uuid)', 'EXECUTE') + is distinct from true then + raise exception 'anon EXECUTE changed — the helper path is anon-reachable by design; re-read before proceeding'; + end if; +end +$$; + +-- ============================================================ +-- Post-apply, by hand — a green apply is not evidence: +-- select pg_get_functiondef(oid) from pg_proc +-- where proname='remote_helper_authorize_broadcast'; -- has the predicate +-- +-- -- The behaviour this exists for, with a real helper_secret (owner only): +-- -- a session retired to status='stopped' must now raise 42501 where it +-- -- previously returned the owner uuid. +-- +-- select count(*) filter (where status in ('pending','running')) as still_authorizable, +-- count(*) as total +-- from public.remote_sessions where realtime_private; +-- -- 2026-09-09: 0 / 3. Every private row is 'stopped', so this migration +-- -- newly refuses all three — which is the point, not a regression. +-- ============================================================ diff --git a/helper/cli_pulse_helper.py b/helper/cli_pulse_helper.py index 1130c8fd..0086c3cb 100644 --- a/helper/cli_pulse_helper.py +++ b/helper/cli_pulse_helper.py @@ -18,6 +18,7 @@ from system_collector import CollectedAlert, collect_alerts, collect_device_snapshot, collect_sessions, estimate_provider_quotas from git_collector import GitCollector, project_paths_from_sessions import user_secret as _user_secret_module +from remote_session_plane import should_run_terminal_broadcast logger = logging.getLogger("cli_pulse.helper") @@ -627,8 +628,16 @@ def daemon(args: argparse.Namespace) -> None: # broadcasts, zero edge-fn calls). Even on, only PRIVATE sessions # broadcast: the local gate in `_post_stdout_chunk` skips public/unknown # sessions entirely, and the mint edge fn denies public. + # ⛔ ANDed with the RETIREMENT, not just the ops flag. `pterm:` streams a + # terminal to a plane that was withdrawn in #499-#514; nothing + # subscribes to that topic on any platform, and this producer's own + # write path (v0.65's direct mint+POST) has been refused by Realtime + # since 2026-08-30 because `r0_broadcast` holds no INSERT — invisibly, + # since the endpoint answers 202 either way. See remote_session_plane.py. broadcast_publisher = None - if getattr(config_for_manager, "remote_realtime_broadcast_enabled", False): + if should_run_terminal_broadcast( + getattr(config_for_manager, "remote_realtime_broadcast_enabled", False) + ): try: from realtime_broadcast import ( # type: ignore RealtimeBroadcastSink, diff --git a/helper/remote_session_plane.py b/helper/remote_session_plane.py new file mode 100644 index 00000000..29c5c51f --- /dev/null +++ b/helper/remote_session_plane.py @@ -0,0 +1,57 @@ +"""Whether the PYTHON helper offers the remote **session** plane. + +MIRRORS `HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift` and +`CLI Pulse Bar/CLIPulseCore/Sources/CLIPulseCore/RemoteSessionPlane.swift`. +Three packages that cannot import each other now carry this constant, and +`test_remote_session_plane.py` reads the Swift source and fails if they +disagree — a copied constant without a drift gate is how a "retired" feature +comes back on one side only. + +WHY RETIRED +----------- +Measured against production 2026-08-30 (see the Swift file for the full table): +no non-owner ever started a remote session, `remote_session_commands` and +`remote_permission_requests` were a durable zero, and the app-side surfaces +were removed across PRs #499-#514. + +WHY THIS FILE EXISTS AT ALL +--------------------------- +The Python helper still constructs a `pterm:` terminal-broadcast producer when +`remote_realtime_broadcast_enabled` is set, and that flag has DEFAULTED ON since +helper 1.24.0. `pterm:` streams a terminal to the phone; the phone stopped +having anywhere to put it when the plane was retired. + +Two independent reasons that producer cannot deliver anything today, both +measured 2026-09-08: + + * NO CONSUMER. Nothing in the product subscribes to `pterm:` or `term:` — + no Realtime WebSocket client exists on macOS, iOS or Android. + * NO WRITE PATH. It takes v0.65's direct route (mint a token, POST to + /realtime/v1/api/broadcast as `r0_broadcast`). `r0_broadcast` holds no + INSERT on `realtime.messages`, and the owner of a hosted Supabase project + cannot grant it, so Realtime refuses the write. The endpoint returns + HTTP 202 either way, so the failure has always been invisible. + +It has therefore not delivered a byte since at least 2026-08-30, while still +minting a token and issuing an HTTPS request per coalesced chunk. + +NOT DELETED, GATED +------------------ +Same reasoning as the Swift file: behaviour change and source deletion are two +reversible steps, not one. Flipping this back to True restores the previous +behaviour exactly. +""" + +#: ``False`` — the helper offers no remote sessions, terminals or approvals. +IS_ENABLED = False + + +def should_run_terminal_broadcast(config_enabled: bool) -> bool: + """Whether to construct the ``pterm:`` terminal-broadcast producer. + + The conjunction is the point: the ops flag + (``remote_realtime_broadcast_enabled``) can only ever turn the producer OFF + sooner, never on past the retirement. Un-retiring is one edit, in one place, + reviewed on its own. + """ + return IS_ENABLED and bool(config_enabled) diff --git a/helper/test_remote_session_plane.py b/helper/test_remote_session_plane.py new file mode 100644 index 00000000..587bafc0 --- /dev/null +++ b/helper/test_remote_session_plane.py @@ -0,0 +1,61 @@ +"""The retirement flag agrees across all three packages, and gates the producer. + +The Swift copies already have a drift gate between themselves +(`RemoteSessionPlaneRetirementTests.test_theHelperCopyOfTheFlagAgrees`). This +adds the third: a copied constant without a drift gate is how a "retired" +feature comes back on one side only, and the Python helper is the copy most +likely to be forgotten because it ships as a separate .pkg. +""" +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +from remote_session_plane import IS_ENABLED, should_run_terminal_broadcast + +REPO = Path(__file__).resolve().parents[1] +SWIFT_COPIES = [ + REPO / "HelperSwift/Sources/HelperKit/RemoteSessionPlane.swift", + REPO / "CLI Pulse Bar/CLIPulseCore/Sources/CLIPulseCore/RemoteSessionPlane.swift", +] + + +@pytest.mark.parametrize("path", SWIFT_COPIES, ids=lambda p: p.parent.name) +def test_the_python_copy_agrees_with_each_swift_copy(path: Path) -> None: + assert path.exists(), f"the Swift copy moved: {path}" + line = next( + (ln for ln in path.read_text(encoding="utf-8").splitlines() + if "static let isEnabled" in ln), + None, + ) + assert line is not None, f"the isEnabled declaration moved in {path.name}" + m = re.search(r"static let isEnabled\s*=\s*(true|false)", line) + assert m is not None, f"could not parse: {line!r}" + swift_value = m.group(1) == "true" + assert swift_value == IS_ENABLED, ( + f"{path.name} says isEnabled={swift_value} but the Python copy says " + f"{IS_ENABLED} — the retirement came back on one side only" + ) + + +def test_the_ops_flag_cannot_outvote_the_retirement() -> None: + # The conjunction, all four ways. + for cfg in (True, False): + assert should_run_terminal_broadcast(cfg) is (IS_ENABLED and cfg) + # Concretely, while retired: nothing turns the producer on. + assert should_run_terminal_broadcast(True) is False, ( + "remote_realtime_broadcast_enabled defaults ON since helper 1.24.0, so " + "without this the producer runs for a retired plane" + ) + + +def test_the_producer_construction_actually_calls_the_gate() -> None: + # A predicate nothing calls is a predicate that gates nothing — this file + # family has shipped that mistake twice. + src = (REPO / "helper/cli_pulse_helper.py").read_text(encoding="utf-8") + assert "from remote_session_plane import should_run_terminal_broadcast" in src + assert "if should_run_terminal_broadcast(" in src, ( + "the producer construction no longer routes through the retirement gate" + )