Repository navigation
BUZZ-270: Prepare community access verification runbook - #8235
bradseiler wants to merge 6 commits into
Conversation
🔐 Codex Security Review
|
|
This runbook depends on unmerged siblings. Its local-rehearsal step runs |
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Two blocking issues, both in what the runbook tells an operator to run or expect. Following the local rehearsal as written skips the route behavior tests, and one of its commands fails on a fresh database. The timeout section also expects every write to fail, which isn't what the relay enforces. The rest checks out against source.
Blocking
1. The local rehearsal never runs the route behavior tests, and the cargo test step fails as written. community-access-verification.md L57–65
- The HTTP and root-WebSocket ban matrix that #8239 adds lives in
buzz-test-client, as#[ignore]d tests (community_ban_routes.rsL475–477, L609–611, L650–652 at #8239 headdda7ef88). It needs a running relay, and CI starts that relay withBUZZ_V1_ENABLED=trueand then runscargo test -p buzz-test-client --test community_ban_routes -- --ignored. Neither printed test command selects it.cargo test -p buzz-db -p buzz-relaynames other packages and skips ignored tests.scripts/postgres-test-run.shonly selects ignoredpostgres_tests::/postgres_targets (L79–82). The Python guard checks that inventory rows and test registrations line up. It doesn't exercise a single denial. So an operator can finish every step without testing one ingress gate. cargo test --no-fail-fast -p buzz-db -p buzz-relayisn't one of the repo's test lanes, and theJustfiletest-unitcomments explain why. Many non-ignoredbuzz-relaytests need a database with the schema loaded. For example,api::media'stest_stateseeds a community throughconfig.database_url(media.rsL1674–1686). Pointed at an empty disposable database as the runbook says, it failed in a live run: exit 101, 1,670 passed / 8 failed / 910 ignored, and all 8 failures were missingcommunities. Left unset, those tests fall back topostgres://buzz:buzz_dev@localhost:5432/buzz(test_support.rsL1), which is the shared dev database the runbook says never to use.
Fix: replace the cargo test line with just test-unit (with #8239 it runs the route guard and the infra-free relay lanes) or the guard alone. Keep scripts/postgres-test-run.sh. Then add a separate route-E2E step: start a task-owned relay on a non-default port with BUZZ_V1_ENABLED=true against the isolated PostgreSQL, Redis and media backing, set RELAY_URL / RELAY_HTTP_URL / DATABASE_URL explicitly, and run the two buzz-test-client commands from #8239's TESTING.md. Don't point to the shared-service startup script. Record source-test results and relay behavior results separately.
2. The timeout section expects writes that the relay allows to fail. community-access-verification.md L139–140
"Writes fail until expiry or untimeout" doesn't hold for the routes listed just above it:
- Reports and the unban/untimeout moderation commands are allowed by design during a timeout (
ingest.rsL2205–2235). - Git admission checks bans, not timeouts (
transport.rsL249–265).receive_packthrough the policy hook and publish path has no timeout gate (transport.rsL1214–1312,policy.rsL303–435). - Personal read-state updates stay allowed (
auth.rsL138–161).
An operator following the runbook would log these as failures, or come away believing a timeout contains more than it does. Fix: name the content and ephemeral writes that should fail, list the allowed exceptions as positive controls, and keep the read and existing root/audio session controls. Ephemeral restrictions on other pods are only bounded by a 30-second cache (state.rs L1709–1732), not enforced immediately, so the expected result should say that.
Checked and fine
- The four
buzz moderationcommands and their flags matchlib.rsL2053–2099 andmoderation.rsL24–85, andtimeoutcorrectly requires an expiry. BUZZ_POSTGRES_ADMIN_URL,PGHOST/PGPORT/PGUSER/PGPASSWORD,REDIS_URL,PG_BIN_DIR, the privilege text, and both doc anchors matchCONTRIBUTING.md,crates/buzz-db/TESTING.md, andpostgres-test-run.sh.- The 30-day v1 drain matches
MAX_INVITE_TTL_SECS, and measuring it from the last serving or rollback issuance rather than from the merge is right. The cutoff behavior matches #8237, and the webhook owner-ban behavior (point-in-time check, generic denial, deny on lookup failure, timeout allowed, existing runs untouched) matches #8242. The runbook correctly treats both as prerequisites, not current main behavior. - A timeout doesn't block root or audio admission or disconnect existing sessions, and reads stay available.
- Isolation, secret handling, staging fault authorization, the rule against disabling shared Redis, cleanup, and keeping NOT RUN separate from deployed evidence are all sound.
Nonblocking
- L111–117, L122: spell out the two boundaries separately. V1 expiry includes the expiry second (
invite_token.rsL181 rejects onlyexpiry < now), while #8237's cutoff rejectsnow >= cutoff. Deterministic boundary cases need both. - The rehearsal candidate is described as including only #8239, but the invitation, live-session and webhook cases also need #8237, #8241 and #8242 where they apply.
- BUZZ-271 and BUZZ-272 are referenced in the body without links. Readers who can't resolve those IDs need the PR links.
- Nothing links to the new
docs/testing/directory yet. - The description doesn't have
buzz-review-completedyet (AGENTS.md step 4).
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot coverage.
Reviewed head eb3b03afc3238de7a46916ca8d0d40a9fd6353da against base 326e2301cb4b1edcb8a72d01ac4b19a83545365f (merge-base 19aa619fdd70e916269b343232276d9ee68276df).
Two P2 documentation corrections remain, both already described in the existing review. We independently confirmed them and are linking that feedback instead of duplicating inline comments:
- Local rehearsal omits the route behavior matrix and uses an unprepared database lane (
docs/testing/community-access-verification.md:57–64). The printed Cargo command selects only buzz-db/buzz-relay, while #8239’s HTTP/root-WebSocket matrix is an ignored buzz-test-client target; the PostgreSQL wrapper’s filter does not select it either. Non-ignored media tests also require a populated database before that wrapper runs. Use the established unit/PostgreSQL lanes and add an explicit route-E2E step against task-owned, isolated services with the accessory API enabled. Record these results separately. - Timeout expectations are broader than the enforced policy (same file, lines 139–140; evidence template line 32). Reports and unban/untimeout commands are explicitly allowed by
allowed_while_timed_out; personal read-state writes are also allowed. Specify the content/ephemeral operations that must be denied and retain permitted operations as positive controls, including the documented cross-pod cache bound.
No additional actionable finding from this review. The runbook’s NOT RUN status, deployment prerequisites, isolated fixtures, authorized fault-injection boundary and cleanup requirements should remain.
Validation: read-only source/documentation inspection, including the public PR description (no attached images) and #8239’s pinned route-test source. No commands from the runbook, PR tests, applications, staging operations or fault injection were executed. CI snapshot: 11 successful checks, 32 skipped; it does not establish runtime, cross-pod, deployment or human-verification results. This is a non-blocking COMMENT review, not approval or deployment authorization.
## Summary A workflow whose saved owner is banned from the community can no longer create runs through any trigger: webhook, channel-event, cron, or signed command trigger. Every one of those doors already calls `WorkflowEngine::check_owner_authority`, which re-checks the owner's channel role. That gate now also reads the owner's authoritative restriction state. A ban denies the run, and a failed lookup fails closed. Unbanning restores admission. On the webhook door the denial is the same generic 404 as before, and no run is queued. On the event and cron paths, a ban denial is logged at WARN at most once per workflow every ten minutes, and at DEBUG in between. A failed lookup and every other denial still warn every time. The handler-local ban lookup in `api/bridge.rs` is removed because the shared gate covers it. This is a point-in-time check: a ban committed after the lookup can race with run creation. It does not cancel existing runs. Community timeouts do not block runs in this change. This PR also carries the shared mesh receive fix needed for independent validation (see Shared carry). ### Related BUZZ-272. - Invitations: [#8237](#8237). - Route coverage: [#8239](#8239). - Live-session revalidation: [#8241](#8241). - Staging runbook: [#8235](#8235). ### Testing Postgres lane: - `on_event_denies_run_while_owner_banned`: a banned owner's channel-event trigger creates no run, and after unban it creates one. - `cron_tick_skips_banned_owners_without_consuming_the_claim`: one tick skips a banned owner and an agent whose human owner is banned, while an unbanned workflow on the same tick still fires. After unban, a tick for the same scheduled instant fires both, so the denial did not consume their scheduled-fire claim. - `on_event_denies_run_when_restriction_lookup_fails`: the event path fails closed when the restriction read errors, and fires once the read succeeds. - `webhook_admission_enforces_owner_ban_tenant_scope_and_lookup_failure`: the HTTP door returns the same generic 404 for a wrong secret, a banned owner, a forbidden role and a failed restriction lookup, with no run created. It also covers another tenant's allowed control and unban. Unit lane: - `ban_denial_warns_once_per_interval_while_other_denials_always_warn`. Results: - At `81c367f48`, CI passed: Unit Tests 572/572, PostgreSQL Tests 1169/1169 and Relay E2E. A local-relay E2E also passed: - unbanned webhook: 202 and one run; - banned webhook: generic 404 and no run; - banned event trigger: no run; - after unban: one run. - At `72c8d8bbc` (merged with main), CI passed: Unit Tests (including all 171 buzz-workflow unit tests), PostgreSQL Tests 1227/1227 (all four ban tests listed by name), and both `Build (linux/amd64|arm64)` image jobs, which build with `--locked`. A Blox run at the same head also passed `cargo metadata --locked`, fmt and clippy, and the buzz-workflow unit tests passed there with no database reachable. - Not covered: the signed trigger has no dedicated test. It calls the same gate, which was confirmed by reading the code. ### Shared carry #8237, #8239, #8241 and #8242 each carry the same mesh-receive fix, boot and scraper regressions, and push-lease fixture correction. Whichever lands first owns them, and the others drop them on their next main merge. ### What got simpler The ban check now lives in the one gate every run-creation door already shares, so there is one fewer handler-local branch. Cron, event and signed triggers get the same protection without per-door code. The cron loop body is now `cron_tick(now)`, so a test can drive one tick without the 60-second sleep. buzz-workflow's infra-free unit tests ran in no CI lane. `just test-unit` and its cargo fallback in `scripts/run-tests.sh` now run them. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: seiler <seiler@squareup.com> Signed-off-by: Brad Seiler <seiler@squareup.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
eb3b03a to
3909943
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Both blocking issues from the last review are mostly fixed. The timeout section now matches what the relay enforces. The local rehearsal now runs just test-unit, the PostgreSQL wrapper, and the route matrix against a private relay. One relay test the rehearsal needs is still missing, so it doesn't cover everything CI does.
Blocking
1. The local rehearsal still skips the relay-admin ban/timeout regression test. community-access-verification.md L71–88
CI's Relay E2E lane runs two community-ban targets against the running relay: community_ban_routes and regression_relay_admin_ban_gate (_ci-relay.yml L633–634). The runbook prints only the first. The second is the inventoried timeout_admin_denial case (community-ban-regressions.tsv L19): banned and timed-out administrators must be refused on relay-admin kinds 9030–9033, and an unrestricted administrator must still succeed. The test is #[ignore]d (L128–130), and neither just test-unit nor scripts/postgres-test-run.sh selects it. The large-roster script linked below the matrix checks discovery and reconciliation, not this. So an operator can finish the rehearsal without ever checking that a banned or timed-out admin is refused.
Fix: after the community_ban_routes command, add
REPRO_RELAY_HTTP=<private relay HTTP URL> REPRO_HOST=<private relay host:port> \
cargo test -p buzz-test-client --test regression_relay_admin_ban_gate -- --ignored --nocapturewith DATABASE_URL pointing at the same disposable database. This target doesn't read RELAY_URL or RELAY_HTTP_URL. Without the two REPRO_* variables it falls back to localhost:3999 (L21–28), so the runbook has to set them.
Checked and fine
- The timeout exceptions at L161–167 match
allowed_while_timed_out(ingest.rsL2336–2343): reports (1984), unban (9041) and untimeout (9043). They also match the ban-only Git gate (deny_banned_git_principal) and the 30-second cached check on the ephemeral path (enforce_cached_write_restriction). - The workflow-owner section matches
check_owner_authority(buzz-workflow/src/lib.rsL164–197) and all four places that call it: signed triggers (command_executor.rs), webhooks with a generic 404 (bridge.rs), channel events, and cron, which checks before it claims the fire slot. Bans are denied, timeouts aren't, and a failed lookup denies. The signed trigger separately goes through the ingest timeout gate, as the runbook says. just test-unitis the right replacement for the barecargo test. The private-relay setup (BUZZ_V1_ENABLED=true,BUZZ_KLIPY_API_KEYunset, explicitRELAY_URL/RELAY_HTTP_URL/DATABASE_URL, a warning offstart-relay-for-tests.sh) matches thecommunity_ban_routesheader andTESTING.md.- The prerequisites now ask for the whole integrated stack and #8529's repair order. Every merged fix the PR description names is an ancestor of this head. The
psqlrequirement matchesupgrade_postgres_tests.rs. All relative links and anchors resolve.
Nonblocking
- L66:
just test-unitalready runscheck-community-ban-route-inventory.pyand its fixture test (TESTING.mdL57), so the separate guard line runs it twice. Drop it. - L133–139: say outright that a v1 token is still accepted at
now == expiry(payload.e < nowrejects,invite_token.rsL202), and that the cutoff rejects atnow >= cutoff. Deterministic boundary tests need both edges. - L180–184: "If BUZZ-271 is deployed" and "30 seconds in the proposed implementation" conflict with prerequisite 2, which already requires #8241 (BUZZ-271) in every serving revision. Make the step unconditional, link #8241, and drop "proposed".
- L205–207: the cron case "after unban, the same due cron instant must remain available" holds only if the unban lands inside
cron_fire_instant's 60-second window (lib.rsL600). The ignored PostgreSQL test at L2302 already pins it. File it with the deterministic local checks, like the cutoff boundaries, rather than as a staging expectation. - L12–17 and L180: link #8529, #8241, #8239, #8237 and #8242 instead of leaving bare numbers and BUZZ IDs. Nothing in the repo links to
docs/testing/yet either. A pointer fromTESTING.md's community-ban section would make the runbook findable. - L90 and L166 aren't wrapped like the rest of the file.
Units 6-9 of the 23-30 Oct desktop Playwright flake audit. All are test-only. origin/main (1eb67bb, now including #8680) hasn't touched these specs since fae3f03. No open PR touches the tests changed here: #6134 (conflicting since 10-19) only swaps page.goto calls in three of these files, #8187's hunk starts just after workflow-local-controls:151, #5778 (draft, conflicting) creates projects-v3-screenshots from an older base, and #8622, #7452, #7812, #8244 and #3342 edit other tests in the same files. Unit 6: measuring before an animation finishes onboarding.spec.ts:1487, "hosted community address line stays within the card for a long name" Run: #8715 (Integration shard 1), which doesn't touch onboarding. Signature: the centring check at :1545 expects <= 2 and receives 5.13. Cause: "Connect and continue" ends in showPage("owned") (WelcomeSetup.tsx:323), which mounts an OnboardingSlideTransition. Its content slides in from translateX(48px) over 650 ms (components.css:521, :676). fill() doesn't wait for stability, and the surface, input and suffix boxes come from three boundingBox round trips, so they can be read at different points of the slide. Change: waitForAnimations(page) after the fill, before measuring. onboarding.spec.ts:2289, "connected first-community profile keeps navigation inside the card and balances the avatar editor" Runs: main 40746677244 and 40925172215, and #8666 (Integration shard 1). Signature: the spacing check at :2485 expects <= 4 and receives 4.255. Cause: the avatar dialog opens with MODAL_CONTENT_MOTION_CLASS, a 200 ms zoom-in-95 (modalMotion.ts:5), and has a 250 ms height transition. dialogBox is read in one round trip and the preview and editor boxes in a later one, so a scale change between them skews the gap comparison. Change: waitForAnimations(page) once the dialog is visible. projects-v3-screenshots.spec.ts:280, "projects v3 workspace screenshot states", failing in the expectInsetSection call at :893 Runs: main 39643531998, 40759209995 and 40969909906 (Smoke shard 8), each passing on retry. Signature: the header's right inset at :326 expects 16 and receives 8. Four more runs fail the menu edge check at :328 with fractional values (expected 984, received 975.6875): 39622053330, 39641311877, 40281448810 and 40856574572. Cause: switching the context rail back from chat to repository narrows it from the thread panel's 380 px to the repository's 280 px (useProjectPanelWidths.ts:5, :17), animated by transition-[width] duration-200 (ProjectContextRail.tsx:31). The workspace widens with it, and expectInsetSection reads the workspace, header and menu boxes in three separate round trips. The fractional widths are mid-transition values. Change: waitForAnimations(page) before that call, the only one that follows a rail width change. Unit 7: fill() racing a caret move the app makes a frame later mention-recipients.spec.ts:376, "editing to a longer typed member drops the original shorter reference", and :97, "ambiguous added mention blocks editing before clearing the draft" Runs: main 40969965981 (Smoke shard 5, a run with every earlier fix) for :376. 40969858676 for :97, before the fixes. Signature: the edit_message poll at :428 receives null. The error context shows the edited message as "@scout hello @scout Jones hello": fill() appended. Cause: entering edit mode sets the content, then focuses its end in a requestAnimationFrame (MessageComposer.tsx:413). toHaveText passes as soon as the content lands, before that frame. If the frame lands between fill()'s select-all and its typing, the selection collapses and the text is appended. #8682's replaceComposerText handles this, but these two call sites (:115 and :403) were missed. Change: replaceComposerText at both. workflow-local-controls.spec.ts:151, "inserts template variables with keyboard control and restores the caret" Runs: main 40926393897 and 40957616146 (before the fixes), and #8239, which changes no desktop files. Signature: :194 expects "Keep {{auth" and receives "Hello {{trigger.text}}Keep {{auth". Cause: selectVariable restores the caret in a requestAnimationFrame (WorkflowTemplateTextarea.tsx:66). Setting the controlled value has already put the caret at the end, so the selectionStart poll passes before that frame runs. The frame then collapses fill()'s select-all. Change: after the poll, wait two animation frames, then fill. This is a frame barrier, not a timer. The app queued its frame before the evaluate, so it runs first. Unit 8: clicks that hit the wrong element profile.spec.ts:1128, "renders agent profile ingress subviews from the Playwright mock bridge", failing at :1543 Run: #8523, which changes no desktop files. Its base (ef4130d) already includes #8681. Signature: archiveAgentRow.click() times out because three stacked Sonner success toasts (data-expanded="true") intercept the pointer. Cause: the test's Stop and Start steps raise success toasts (useAgentLifecycleActions.ts:51, :61). Sonner stacks them bottom right, over the profile panel's action rows, and pauses their dismissal while the pointer is over them. #8681 fixed this test's motion window, and fixed the toast problem only in agent-availability:602. Change: press the five action rows (:1528-:1547) through a local pressActionRow helper: toBeEnabled, focus, Enter, as agent-availability.spec.ts:744-745 does. toBeEnabled keeps the wait click() gave, since these rows are disabled while a delete or archive is pending. config-bridge-screenshots.spec.ts:314, "07 — profile side panel — Configuration section" Runs: main 40967147375 (before the fixes), and #8235, which changes no desktop files. Signature: "Model" isn't visible within 10 s at :352. The error context shows nadia's panel ("Not managed on this device"), not Charlie's. Cause: the spec clicked .last() of the rows with a [data-testid^="message-avatar-"] descendant. That matches only UserAvatar's -image and -fallback children. Radix renders the image only once it loads, and each fallback only after its own 200 ms delayMs timer (UserAvatar.tsx:67, :119). Neither avatar in #agents has a URL, so the matching set fills in row by row as the timers fire. Resolved in between, .last() is nadia's row (seeded at -85 s), just above Charlie's "reporting in." row (-80 s). The note blamed a misplaced click; the code points at locator resolution instead. Change: pick Charlie's row by its text, as profile.spec.ts does for Charlie's other #agents message. Then check the panel's name row reads charlie before opening Runtime, so a wrong panel fails there and says so. Unit 9: the createWorkflow copies (preventive) navigation.spec.ts:153, "direct workflow detail links close back to workflows" Runs: 7 failures on main, the last at 29 Oct 16:12Z, none after #8681. Signature: the Edit workflow dialog has no "Trigger: Message Posted" at :176. Runs: 14 first-attempt failures in Smoke shard 7, all passing on retry; on main 39827234118, 40718018673, 40789486699, 40802051064, 40840949170, 40891643205 and 40909291143. Cause: the same race #8681 fixed in workflows.spec's createWorkflow. Picking a channel opens the trigger pane (WorkflowDialog.tsx:702), and for Message Posted the conditions editor expands "Message text" by default (WorkflowTriggerConditions.tsx:157), rendering an input with that label. After "Send Message", AnimatePresence mode="wait" (WorkflowFormBuilder.tsx:907) keeps the trigger body mounted through its exit and holds the step body back. An unscoped getByLabel("Message text") can fill the trigger's filter. The step then has no text, which yamlToFormState rejects (workflowFormTypes.ts:348), so the Edit dialog opens in YAML mode (WorkflowDialog.tsx:88). That is the signature the earlier audit recorded for navigation:153. Change: wait for the step inspector's "Action" header and "Step details" control, then fill inside the inspector, as workflows.spec does. workflows.spec.ts:716 ("captures the built editor at desktop and narrow widths", fill at :726) and :820 ("pane routes use stable IDs and Form/YAML changes stay synchronized", fill at :831) pick a channel and then add a Send Message step the same way, with no failures in the window. So the wait moves into one addSendMessageStep helper in tests/helpers/workflows.ts, rather than four copies. Both specs' createWorkflow and those two tests call it. A change to the inspector's structure then needs one fix. Left out: workflow-reaction-picker.spec.ts:203-204. The note listed it, but its trigger is Reaction Added, whose condition fields are "Reaction emoji", "Author" and "Message" (workflowConditionExpression.ts:40). No trigger control there is labelled "Message text", so the unscoped fill can only reach the step's textarea. Verification Not run locally: Playwright bogs this machine down, and the Flake Check lane will repeat the touched specs. pnpm typecheck and pnpm check pass. biome's 3 warnings are in untouched files. An ad hoc tsc ran over the eight touched specs, using the desktop tsconfig with only those files, and over HEAD's copies of them. Both gave the same errors: onboarding 16, navigation 7, profile 7, workflow-local-controls 7, projects-v3-screenshots 2, and none in mention-recipients, config-bridge-screenshots or workflows. The messages are identical once line numbers are stripped, and all are pre-existing (no Node types, import.meta.env, MockBridgeOptions drift and the like). Once the helper moved to tests/helpers/workflows.ts, a rerun over navigation, workflows and the helper gave navigation's same 7 and none in the other two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…kspace (#8744) ## Summary Units 6–9 and 14 of the 23–30 Oct desktop Playwright flake audit, in two commits. The specs now settle before they measure or act, and remote-owned-mentions clears its draft reliably. Everything is test-only. This is one of six PRs split from the reviewed `flakey-tests` branch (siblings below). ### Unit 6: measuring before an animation finishes Each spec now calls `waitForAnimations(page)` before measuring. - `onboarding.spec.ts:1487` (#8715, Integration shard 1; the centring check at :1545 receives 5.13): `showPage("owned")` slides content in from `translateX(48px)` over 650 ms. The spec reads its three boxes in separate `boundingBox` round trips, so they can come from different points of the slide. - `onboarding.spec.ts:2289` (main 40746677244 and 40925172215, and #8666; the spacing check at :2485 receives 4.255): the avatar dialog has a 200 ms zoom-in and a 250 ms height transition. - `projects-v3-screenshots.spec.ts:280`, at :893 (main 39643531998, 40759209995 and 40969909906, plus four runs with fractional menu-edge values): the context rail narrows from 380 to 280 px over 200 ms. ### Unit 7: `fill()` racing a caret move the app makes a frame later - `mention-recipients.spec.ts:376` and `:97` (main 40969965981 and 40969858676): edit mode focuses the end of the composer in a `requestAnimationFrame`. If that frame lands mid-`fill()`, the edit is appended instead of replacing the text. **Change:** use #8682's `replaceComposerText` at both call sites. - `workflow-local-controls.spec.ts:151` (main 40926393897 and 40957616146, and #8239): `selectVariable` restores the caret in a `requestAnimationFrame` after the spec's poll has passed. **Change:** wait two animation frames before filling. ### Unit 8: clicks that hit the wrong element - `profile.spec.ts:1128`, at :1543 (#8523): stacked Sonner success toasts intercept the pointer. **Change:** a local `pressActionRow` helper presses the five action rows with `toBeEnabled`, focus and Enter. - `config-bridge-screenshots.spec.ts:314` (main 40967147375, and #8235): `.last()` resolved while avatar fallbacks were still filling in on 200 ms timers, so it opened nadia's panel instead of Charlie's. **Change:** pick Charlie's row by its text, and check the panel's name before opening Runtime. ### Unit 9: the `createWorkflow` copies (preventive) - `navigation.spec.ts:153` (7 failures on main, none since #8681; 14 first-attempt failures in Smoke shard 7): an unscoped `getByLabel("Message text")` can fill the trigger's filter while `AnimatePresence` keeps it mounted. **Change:** wait for the step inspector, then fill inside it, as `workflows.spec` already does. The wait now lives in one `addSendMessageStep` helper in `tests/helpers/workflows.ts`, which `navigation.spec.ts` and `workflows.spec.ts` (`createWorkflow`, :716, :820) all call. - `workflow-reaction-picker.spec.ts:203-204` is left out. Its Reaction Added trigger has no control labelled "Message text". ### Unit 14 (second commit): `remote-owned-mentions.spec.ts:566` - **Run:** main 40998438103 (Smoke shard 9, job 123877347301). It passed on retry and failed 1 of the 6 attempts it got in the window. `sourceRecord()` at :673 returned thread A's draft instead of null. - **Cause:** on ProseMirror, `fill("")` selects through the DOM, then presses Delete. If ProseMirror hasn't seen the selection yet, it blocks the Delete. Navigating to B then saves A's live text as its draft. This is not a product bug: had the editor really been empty, the cleanup would have cleared A's draft. - **Change:** delete the text with one Backspace per character from the end, then wait for the composer to read empty. The guard assertions are unchanged. ### Related issue None found. Sibling #8743 also edits `navigation.spec.ts`, in a separate hunk. The two merge cleanly in either order. No open PR touches the tests changed here. #6134, #8187, #5778, #8622, #7452, #7812, #8244 and #3342 edit other tests in the same files. ### Testing Not run locally, because Playwright bogs this machine down. Flake Check will repeat the touched specs. `pnpm typecheck` and `pnpm check` pass, and biome's 3 warnings are in untouched files. Ad hoc `tsc` runs over the touched specs and the helper found the same pre-existing errors as on their `main` copies. Pushed with `git push --no-verify`, because the pre-push suites fail when the laptop is under load. ### Readiness An agent review of the combined `flakey-tests` branch (b432453af) found no blockers. `buzz-review-completed` is not added, because the human test step (AGENTS.md "Before opening a PR", step 3) hasn't happened yet. ### Siblings - #8739 ci: Hermit fetch deadline and sccache fallback - #8740 test(relay): thread_window aux-page hold and `api::media` Postgres tests - #8741 test(mobile): RunnerTests title fade and alert wait - #8742 fix(desktop): deep-linked message hold (product change) - #8743 test(desktop): four specs racing scroll settles and focus 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
message-target-highlight.spec.ts:79 is the most frequent unfixed desktop flake. It failed in all 7 main runs whose SHA carries every earlier flake fix (40969965981, 40970005965, 40970053997, 40980258899, 40980404335, 40980513346, 40983029157), and in 26 jobs since those fixes landed, on main and on PRs #8235, #8239, #8241, #8666, #8680, attempts failed. Only the two cases that pick mounted[2] fail: "same channel, row loaded off screen" (15 jobs) and "other channel, row further up" (11 jobs). The target scrolls in and flashes, then the timeline lands on the newest message, so `stillOnScreen` at :179 reads false (twice it was `flashedOnScreen` at :153). Cause The triage note blamed the thread panel opening. The failure videos from run 40983029157 (Smoke shard 6) show that is a precondition, not the trigger: - Other channel: the panel opens at +0.36 s, the target flashes at +0.7 s, the older page prepends by +1.04 s, the target is still on screen at +1.47 s, and the timeline sits at the exact floor by +1.97 s. - Same channel: the panel and the flash land in the same frame. The snap follows about 0.5 s later, with the load-older spinner still showing. The panel opening never moved the target. A later floor report did: TimelineMessageList's onScroll reporting within 32 px of the bottom, during or after the older-page load that only a mounted[2] target sets off. onVirtualizerAtBottomStateChange(true) reset the anchor to at-bottom and set virtualizerAtBottomRef. With the panel open, the split-panel settle, the viewport-resize settle or the next append settle then pinned the timeline to the floor. The exact-floor landing points at our own settleAtBottom rather than restored scroll. Which Virtua layout pass emits the floor report is still unidentified. Change Accepting a virtual route target now sets targetHoldRef in useAnchoredScroll. Only the two targetMessageId paths set it, on first mount and in the later target effect, by passing holdTarget to scrollToMessage. The public scrollToMessage, which MessageTimeline's oldest-unread pill and find-in-channel stepping call, doesn't hold. It also replaces an earlier hold, so a find jump after a deep link follows the floor as on main. While the hold is set: - shouldSettleForSplitPanel and shouldSettleVirtualizedViewportResize return false; - a floor report still updates isAtBottom and the new-message count, but keeps the message anchor, so live appends don't settle either; - a commit that changes the newest message sets isAtBottom false, clears virtualizerAtBottomRef and adds the rows after the previous newest one to the new-message count. Virtua reports bottom state only from scroll events, and an append below the viewport emits none. Without this, a floor report left isAtBottom true, so MessageTimeline hid the Jump to latest / N new pill while rows piled up out of sight (Review-Proven Rule 6). A realistic trigger is a mention or search hit on one of the last few messages in a busy channel: scrollToIndex clamps at the floor and emits that report. Clearing virtualizerAtBottomRef means a later release doesn't snap past the new rows either. The hold only acts on an arrival, so a reader at the floor with nothing new sees no pill. Counting rows rather than the length change matters: a cold target is merged in older than the head window, and the history page that fills the gap keeps both ends of the list. classifyTimelineMessageDelta calls that an append, but nothing arrived below, so it raises no pill. Neither does deleting the newest message; - viewport reflow re-centres the target through virtualScrollToMessage, from the viewport-resize observer and when the split panel opens or closes. Rows rewrap around the target without a scroll event, so the target can drift, and a floor report from before the reflow can keep isAtBottom true while the newest rows drop below the fold. The re-centring scroll refreshes the bottom state. Virtua's scrollToIndex cancels any earlier one still in flight, so a burst of resizes only restarts its measure loop. The split-panel call fires only on a transition, not on mount or when the virtualizer API registers. The hold is released by reader scroll input (a wheel, a touch drag, a pointerdown on the scroller itself, a scroll key aimed at it), Jump to latest, the reader's own send, and a channel change. The input listeners are the ones that already cancel bottom intent in useVirtualizedBottomSettle, now shared as listenForReaderScroll. A release at the floor restores the at-bottom anchor, so the timeline follows live messages again. onTargetReached does not release it. The artifacts never show the panel pushing the target off screen, so skipping the settle is the fix aimed at the CI failure. The re-centring keeps the target's place and the pill right after a reflow. Known limits: - Programmatic scrolls (focus moves, scrollTop writes) don't release the hold. A find or unread-pill jump replaces it. A resize after such a scroll (Tab focus into a lower row, say) re-centres the target, away from the focused row. - ChannelPane's composer-height settle still calls scrollToBottom within 32 px of the floor, which releases the hold. - Live messages that arrive while the hold is set near the floor are not followed until reader input, Jump to latest or the reader's own send. The pill shows them, with their count. - Under the hold, only rows after the previous newest message count. A live message that sorts just ahead of it (a same-second timestamp) raises no pill until a later row lands. Unit before/after useAnchoredScroll.lifecycle.test.mjs replays the CI sequence: accept a route target, clear it, open the split panel, report the floor, resize the viewport and append a live message. It asserts no bottom settle, and that the panel opening and the resize each re-centre the target. Then it asserts the append shows the pill (isAtBottom false, one new message). A scrollbar pointerdown after that releases the hold, and a resize neither settles nor re-centres. One case per release path asserts the resize settle resumes and the re-centring stops. One of them is a find jump: a public scrollToMessage on a held timeline, then a floor report. A descendant pointerdown (row interaction) keeps the hold, and a resize still re-centres. Reader input at the floor resumes following a live append. Under a hold, a history page filling the gap below the target, and then deleting the newest message, leave isAtBottom true and the count at 0. A commit that adds more history and one live row counts 1. The split-panel and viewport-resize policy tables gain a held row. Mutations over the lifecycle file and both policy tables (29 tests, all pass intact): - hold never set on accept (the old behaviour): 12 fail, including the CI-shaped hold case - split-panel settle ignores the hold: 13 fail - viewport-resize settle ignores the hold: 13 fail - floor report resets a held anchor: 12 fail (the reset anchor is never re-centred) - reader input does not release: 6 fail - Jump to latest, own send, channel change don't release: 1 each - release skips the at-bottom resync: 1 fails - a held append doesn't raise the pill: 2 fail - a held append leaves virtualizerAtBottomRef set: 1 fails - every scrollToMessage holds (this commit's first draft): 1 fails - a public jump keeps an earlier hold: 1 fails - the held-append branch as first drafted (any non-prepend growth, counted by length): 1 fails, the gap-fill case - a newest-id gate but a length-delta count: 1 fails - a missing previous newest counts every row: 1 fails - no rowsBelow > 0 guard: 1 fails - rows counted from the previous newest, off by one: 2 fail - no split-panel re-centre: 12 fail - no viewport-resize re-centre: 12 fail - re-centre ignores the hold: 1 fails pnpm typecheck and pnpm check pass. All 46 tests in the files that import the touched modules pass. Only hosted CI can show Playwright was not run locally. Whether the spec now holds in Smoke shard 6 and in Flake Check's repeats is unverified, and so is the unidentified Virtua floor report. scroll-history.spec.ts and the other timeline specs need the full desktop smoke run to show the hold doesn't regress bottom-follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
## Summary A product fix for the most frequent unfixed desktop E2E flake, `message-target-highlight.spec.ts:79`. A deep-linked message now holds its place against bottom settles until the reader scrolls. This is one of six PRs split from the reviewed `flakey-tests` branch (siblings below). - **Runs:** it failed in all 7 main runs whose SHA carries every earlier flake fix (40969965981, 40970005965, 40970053997, 40980258899, 40980404335, 40980513346, 40983029157). It also failed in 26 jobs since those fixes landed, on main and on PRs #8235, #8239, #8241, #8666 and #8680. Only the two cases that pick `mounted[2]` fail: "same channel, row loaded off screen" (15 jobs) and "other channel, row further up" (11 jobs). The target scrolls in and flashes, then the timeline lands on the newest message. `stillOnScreen` at :179 reads false (twice it was `flashedOnScreen` at :153). ### Cause The failure videos (run 40983029157, Smoke shard 6) show that the thread panel opening is a precondition, not the trigger. A later floor report moved the target: `TimelineMessageList`'s `onScroll` reported within 32 px of the bottom, during or after the older-page load that only a `mounted[2]` target sets off. `onVirtualizerAtBottomStateChange(true)` reset the anchor to at-bottom. Then the split-panel settle, the viewport-resize settle or the next append settle pinned the timeline to the floor. Which Virtua layout pass emits the floor report is still unidentified. ### Change Accepting a virtual route target sets `targetHoldRef` in `useAnchoredScroll`. Only the two `targetMessageId` paths set it, via `holdTarget`. The public `scrollToMessage`, which the oldest-unread pill and find-in-channel use, doesn't hold, and it replaces any earlier hold. While the hold is set: - The split-panel and viewport-resize settles are skipped. - A floor report still updates `isAtBottom` and the new-message count, but keeps the message anchor, so live appends don't settle either. - A commit that changes the newest message sets `isAtBottom` false and clears `virtualizerAtBottomRef`. It adds the rows after the previous newest one to the new-message count. So the "Jump to latest" / "N new messages" pill still appears for rows below the fold (Review-Proven Rule 6). A history page that fills the gap below a cold target raises no pill, and neither does deleting the newest message. - Viewport reflow re-centres the target through `virtualScrollToMessage`. It runs from the resize observer and when the split panel opens or closes. The hold is released by reader scroll input: a wheel, a touch drag, a pointerdown on the scroller itself, or a scroll key. Those listeners already cancel bottom intent in `useVirtualizedBottomSettle`, and this PR shares them as `listenForReaderScroll`. Jump to latest, the reader's own send and a channel change also release it. A release at the floor restores the at-bottom anchor, so the timeline follows live messages again. `onTargetReached` does not release the hold. ### Known limits - Programmatic scrolls (focus moves, `scrollTop` writes) don't release the hold, but a find or unread-pill jump replaces it. A resize after such a scroll re-centres the target. - ChannelPane's composer-height settle still calls `scrollToBottom` within 32 px of the floor, which releases the hold. - Live messages that arrive under a hold near the floor aren't followed until reader input, Jump to latest or the reader's own send. The pill shows them, with their count. - Under the hold, only rows after the previous newest message count. A same-second row that sorts just ahead of it raises no pill until a later row lands. - Agent review raised two optional gaps, and scrolling recovers from both: - If the previous newest message is deleted and a new row arrives in the same deferred commit, 0 rows are counted. - A thread reply sent with "also send to channel" doesn't arm the bottom intent, so it shows as "1 new message" instead of being followed. ### Related issue None found. ### Testing `useAnchoredScroll.lifecycle.test.mjs` replays the CI sequence: accept a route target, clear it, open the split panel, report the floor, resize and append a live message. It asserts no bottom settle, a re-centre on the panel opening and on the resize, and the pill on the append. Further cases cover: - each release path; - a find jump on a held timeline; - a descendant pointerdown keeping the hold; - a gap-fill page and deleting the newest message, neither of which raises the pill. The split-panel and viewport-resize policy tables each gain a held row. Mutations were run over the lifecycle file and both policy tables (29 tests, all pass intact). Each of the 20 mutations fails at least one test: | Mutation | Tests that fail | |---|---| | Hold never set (the old behaviour) | 12 | | Split-panel settle ignores the hold | 13 | | Viewport-resize settle ignores the hold | 13 | | Floor report resets a held anchor | 12 | | No split-panel re-centre | 12 | | No viewport-resize re-centre | 12 | | Reader input doesn't release the hold | 6 | | Held append raises no pill | 2 | The commit message lists the other 12, each failing 1 or 2 tests. `pnpm typecheck` and `pnpm check` pass. All 46 tests in the files that import the touched modules pass. Only hosted CI can show whether the spec now holds in Smoke shard 6 and in Flake Check's repeats, because Playwright was not run locally. It also has to show that `scroll-history.spec.ts` and the other timeline specs keep bottom-follow working. Pushed with `git push --no-verify`, because the pre-push suites fail when the laptop is under load. ### Readiness and human check An agent review of the combined `flakey-tests` branch (b432453af) found no blockers. This changes desktop app behaviour, so it needs a human in-app check before `buzz-review-completed` is added. Suggested check, in `just dev` against a channel with a few pages of history: 1. Open a message link (a mention or search hit) to a message several screens up, once in the same channel and once from another channel. The message should flash and stay on screen after older history loads, including when the thread panel opens or closes, or the window is resized. 2. With the target still held, have someone post in the channel. The timeline stays put, and the pill reads "1 new message". 3. Wheel-scroll to the bottom, or click Jump to latest. The next message that arrives is followed. ### Siblings - #8739 ci: Hermit fetch deadline and sccache fallback - #8740 test(relay): thread_window aux-page hold and `api::media` Postgres tests - #8741 test(mobile): RunnerTests title fade and alert wait - #8743 test(desktop): four specs racing scroll settles and focus - #8744 test(desktop): five settle classes and remote-owned-mentions 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
3909943 to
842df2e
Compare
842df2e to
a7f58ec
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Requesting changes at a7f58ec8. The two runbook files are unchanged since the last review at 39099435 (review); this head only rebases onto the moved stack base. I re-checked each point against this head, and they all still apply.
Blocking
1. The local rehearsal still skips the relay-admin ban/timeout regression test. community-access-verification.md L71–82
CI's Relay E2E lane runs two community-ban targets against the running relay: community_ban_routes and regression_relay_admin_ban_gate (_ci-relay.yml L669–670). The runbook prints only the first. The second is the inventoried timeout_admin_denial case (community-ban-regressions.tsv L19): banned and timed-out administrators must be refused on relay-admin kinds 9030–9033, and an unrestricted administrator must still succeed. It is #[ignore]d, and neither just test-unit nor scripts/postgres-test-run.sh selects it. So an operator can finish the rehearsal without ever checking that a banned or timed-out admin is refused.
Fix: after the community_ban_routes command, add
REPRO_RELAY_HTTP=<private relay HTTP URL> REPRO_HOST=<private relay host:port> \
cargo test -p buzz-test-client --test regression_relay_admin_ban_gate -- --ignored --nocapturewith DATABASE_URL pointing at the same disposable database. This target doesn't read RELAY_URL or RELAY_HTTP_URL; without the two REPRO_* variables it falls back to localhost:3999 (L21–26), so the runbook has to set them.
Nonblocking (still open from the last review)
- L66:
just test-unitalready runscheck-community-ban-route-inventory.pyand its fixture test (JustfileL400–401), so the separate guard line runs it twice. Drop it. - L133–139: say outright that a v1 token is still accepted at
now == expiry(payload.e < nowrejects) and that the cutoff rejects atnow >= cutoff(invite_token.rsL202). Deterministic boundary tests need both edges. - L180–184: "If BUZZ-271 is deployed" and "the proposed implementation" conflict with prerequisite 2, which already requires #8241 (BUZZ-271) in every serving revision. Make the step unconditional, link #8241, and drop "proposed".
- L205–207: "after unban, the same due cron instant must remain available" holds only if the unban lands inside the cron loop's 60-second catch-up window (
buzz-workflow/src/lib.rsL525–530). File it with the deterministic local checks, like the cutoff boundaries, rather than as a staging expectation. - L12–17 and L180: link #8529, #8241, #8239, #8237 and #8242 instead of leaving bare numbers and BUZZ IDs. A pointer from
TESTING.md's community-ban section would also make the runbook findable; nothing in the repo links todocs/testing/yet. - L90 and L166 aren't wrapped like the rest of the file.
a7f58ec to
2c00ed0
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Requesting changes at 2c00ed0b. The blocker from the last review is fixed: the local rehearsal now runs regression_relay_admin_ban_gate with REPRO_RELAY_HTTP, REPRO_HOST and DATABASE_URL, which are exactly what the test reads (L21–28), and the ${VAR:?} guards fail loudly instead of falling back to localhost:3999. One timing statement in the timeout section is now wrong, though, and it could let a regression pass.
Blocking
1. The timeout section allows 30 seconds for cross-pod ephemeral denial, but the restriction cache is 1 second. community-access-verification.md L176–177
Ephemeral kinds and observer frames take the write-restriction check before ingest (event.rs L717–725). That check goes through restriction_state_cached (ingest.rs L2380–2408), which serves a cached row only while it is younger than RESTRICTION_CACHE_MAX_AGE. On this base that is 1 second (state.rs L1913–1916, L2584–2601). An operator who allows 30 seconds would record a timeout that takes 20 seconds to reach another pod as a pass.
Fix: refer to the deployed RESTRICTION_CACHE_MAX_AGE (1 second at this revision) instead of a fixed number, and say that the measured delay includes request and measurement latency on top of the cache age. Keep the separate 30-second live-session revalidation budget in the existing-sessions section; that one is still right (state.rs L92–113).
Checked and fine
- The added command matches CI's Relay E2E step (
_ci-relay.ymlL669–670). The target is#[ignore]d, and neitherjust test-unitnorscripts/postgres-test-run.shselects it, so the "do not omit it" note is accurate. - The PostgreSQL setup, the private-relay setup, the four
buzz moderationcommands, the timeout exceptions (reports, unban, untimeout, personal read state, Git), the invitation and v1 cutoff behavior, the workflow owner-ban checks across all four trigger sources, and the evidence template all still match source at this head.
Nonblocking
- L89–91: the regression covers ban denials on all of 9030–9033 (with a banned owner for owner-only 9032), but checks the timed-out admin and the unrestricted admin only on 9031 (L203–248, L290–350). Something like "ban denials across 9030–9033, plus timed-out and unrestricted 9031 controls" would describe it exactly.
- L66:
just test-unitalready runscheck-community-ban-route-inventory.py(JustfileL400–401), so this line runs it twice. Drop it. - L144–150: say outright that a v1 token is still accepted at
now == expiryand that the cutoff rejects atnow >= cutoff(invite_token.rsL202). Deterministic boundary tests need both edges. - L191–195: "If BUZZ-271 is deployed" and "the proposed implementation" conflict with prerequisite 2, which already requires #8241 in every serving revision. Make the step unconditional, link #8241, and drop "proposed".
- L217–218: "after unban, the same due cron instant must remain available" only holds if the unban lands inside the cron loop's 60-second catch-up window (
buzz-workflow/src/lib.rsL525–530). Put it with the deterministic local checks rather than the staging expectations. - L12–17: link #8237, #8529, #8241, #8239 and #8242 instead of leaving bare numbers. A pointer from
TESTING.md's community-ban section would also make the runbook findable, since nothing in the repo links todocs/testing/yet. - L101 and L177 aren't wrapped like the rest of the file.
2c00ed0 to
446cca1
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 No blocking findings at 446cca11. The blocker from the last review is fixed: L176–181 now measures cross-pod ephemeral denial against the deployed RESTRICTION_CACHE_MAX_AGE, which is 1 second at this revision (state.rs L1927), and keeps request and measurement latency separate.
Checked and fine
- The new outage step (L202–207) matches the revalidator in this tree: 5-second base grace, a stable per-connection offset under 9 seconds, 5-second cadence and scan budget, a 1-second query deadline, and the 30-second worst case the compile-time assertion enforces (
state.rsL92–123). Confirmed denials skip the outage grace, as the step says. - The writer-stall step (L208–215) matches
live_authorization.rs: the 900 ms cap is the 1-second deadline minus 100 ms, a tighter operator cap wins, the setting is transaction-local so the writer's own timeout survives, and an unsafe connection is closed rather than reused (L10–63). The query joinsusers, so a users-table lock is the right stall.query_timeout,statement_cancelledandpool_timeoutare the labelsbuzz_live_authorization_batches_totalemits (state.rsL1110–1138, L1332–1338). - Every command in the local rehearsal resolves at this head.
community_ban_routesandregression_relay_admin_ban_gatecompile, and the twoREPRO_*variables match what the second target reads. - Invitation, timeout-exemption, existing-session and workflow owner-ban statements still match source. All relative links and anchors resolve.
Nonblocking
- L66:
just test-unitalready runscheck-community-ban-route-inventory.py(JustfileL400), so this line runs it twice. Drop it. - L144–150: say that a v1 token is still accepted at
now == expirywhile the cutoff rejects atnow >= cutoff(invite_token.rsL201). The deterministic boundary table (L449–456) covers cutoff equality but has nonow == expiryrow, so the "exercise boundary cases in deterministic local tests" instruction has nothing to point at for that edge. - L194–202: "If BUZZ-271 is deployed", "the proposed implementation" and the new "The proposed worker" conflict with prerequisite 2, which requires #8241 in every serving revision, and the worker is in this tree. Make step 5 unconditional and say "this implementation"; keep the requirement to prove the deployed revision contains it.
- L234–235: "after unban, the same due cron instant must remain available" only holds if the unban lands inside
cron_fire_instant's 60-second window (buzz-workflow/src/lib.rsL600, L841–851). Put same-instant recovery with the deterministic local checks and use a later due instant for the staging unban control. - L89–91: the regression checks ban denials across 9030–9033 but the timed-out and unrestricted administrator controls only on 9031 (L203–248, L290–350). Describe those exact cases.
- L12–17: link #8237, #8529, #8241, #8239 and #8242 instead of leaving bare numbers. A pointer from
TESTING.md's community-ban section would make the runbook findable; nothing outsidedocs/testing/links to it yet. - L101 and L213 aren't wrapped like the rest of the file.
019b984 to
040ad30
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 No blocking findings at 040ad308. Both runbook files are byte-identical to the head of the last review, 446cca11 (review). This head only rebases onto a newer codex/buzz-269-draft-prep.
The base moved in three files: scripts/check-community-ban-route-inventory.py (it now classifies the whole route method chain and fails unknown helpers such as on and *_service), its fixture test, and the scanner wording in TESTING.md. None of it changes anything the runbook says:
- The guard is still invoked as printed,
python3 scripts/check-community-ban-route-inventory.py(L66). - The
TESTING.md#community-ban-route-coverageand#5-verify-a-roster-beyond-1000-memberslinks, pluscrates/buzz-db/TESTING.md#run-the-lane-locally, still resolve.
The nonblocking points from the last review still apply unchanged.
040ad30 to
ede9d79
Compare
565f0d9 to
7774a19
Compare
7774a19 to
7ee3cf9
Compare
Add a repeatable staging runbook and versioned evidence template. Execution remains gated on deployed prerequisites and a disposable community owner. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: Local Builder <1c476cb1127ea270cb900bdc3891060140bdd1173f91498030ee9b4db01c2f20@buzz.block.builderlab.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Co-authored-by: Sarnow <1894dcbafaa4df1ac607a9bf879ac0b7d77fbccb0e0f4619ecc252c8adbad8a2@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Run the ignored ban and timeout regression against the same private relay and disposable database, with its required REPRO endpoint inputs. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Record the 30-second grace and jitter formula, five-second sweep granularity, and isolated writer-stall rehearsal. Include server cancellation, acquisition timeout, and pool/session recovery evidence. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
7ee3cf9 to
5b10c52
Compare
Summary
Add a community-access verification runbook and an evidence template for an exact deployed revision. Cover invitation repair/revocation, selective timeouts, bans, existing root/audio sessions, tenant isolation, cross-pod limits and all workflow trigger sources.
The local rehearsal uses the integrated stack, isolated services, the PostgreSQL wrapper and a running private relay for the signed route matrix. Timeout expectations name the permitted operations and distinguish signed/manual workflow writes from webhook, event and cron owner controls.
Related issue
BUZZ-270. Top of #8529 → #8241 → #8239 → #8235. Source remediation #8005, #7706, #7828, #8128, #8130, #8237 and #8242 is merged. The repair and lower stack layers still need their own approval and rollout.
Testing
Current head
5b10c52f161d4b09581d83e42907513337d67ba0in native stack 8725, rebased by Brad through GitHub onto main44b3c4823. Brad owns further ref updates.All six documentation patches are unchanged in range-diff. Independent source review finds no restack blockers. Hosted documentation-layer CI may skip runtime domains; skipped jobs supply no runtime result.
Staging: NOT RUN. The evidence template remains NOT RUN. Deployment-owner authorization, disposable fixtures, exact deployed revisions and cleanup evidence are required before a passing verdict. This runbook does not authorize deployment or security-finding closure.
Normal pre-push hooks passed on integrated head
7ee3cf92f59a96349099d48fc115e5b8bb6a9716, including the branch-skew guard, unit tests, desktop checks/tests, Tauri checks, mobile checks/tests and file-size checks. These hooks validate the checked-out top tree; hosted CI remains the gate for each individual PR head.Current-head documentation CI succeeded; runtime jobs were skipped and provide no runtime execution result. McKeon independently cleared all four current heads: carried patches range-diff unchanged (8/20/21/6), with no overlap between the newer main delta and stack files. The separate main-only security-review authorization check remains failed for this stacked base; no bypass or retarget was performed.
Historical local validation at integrated head
7ee3cf92f5: buzz-db/buzz-relay clippy passed with all targets and warnings denied; package tests passed 2,035/2,035; desktop build, Tauri check/tests, web build and mobile tests passed. The full PostgreSQL lane finished 1,503/1,505, with two failures still unresolved in that run:cluster_global_sample_writer_fails_closed_when_activity_is_maskedandhanging_redis_peer_times_out_before_the_health_listener_binds. The runner constructed apostgresql://URI; the fence test requires literalpostgres://, explaining its prefix panic. The Redis test exceeded its scheduling bound at 8.03 seconds; that local failure remains unexplained. No full localjust cipass is claimed. These receipts remain attributed to the historical head.The separate security-review workflow only authorizes PRs targeting main. Do not retarget or retry this stacked PR to bypass that restriction.
Human review and verification remain required. Existing review decisions are preserved. No merge, deployment, production repair or human acceptance was performed.
Generated with Codex