Repository navigation
BUZZ-269: Cover community-ban entry points - #8239
bradseiler wants to merge 21 commits into
Conversation
🔐 Codex Security Review
|
|
|
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Thanks for this. Two blocking problems, both in what the new coverage proves rather than in production behavior: the inventory guard misses a supported router shape, and the positive controls pass when the allowed operation is broken. Both were reproduced by mutation at this head.
Blocking
1. A second direct mount of the same router escapes the inventory (correctness). check-community-ban-route-inventory.py L286–304 keeps one prefix per router builder (prefixes[candidates[0]] = prefix), and L340–363 emits that builder's routes only under that one prefix. Adding .nest("/extra", api::buzz_v1::router(state.clone())) before the existing /buzz/v1 mount exposes /extra/me/sidebar and /extra/me/read-state on the relay, but the guard still exits 0 and reports 74 routes. An ordinary added .route(...) does fail it, as intended. This is the direct, literal .nest shape TESTING.md says is supported, not one of the listed gaps (route_service, macros, opaque composition), so new endpoints can skip both classification and the signed route matrix.
Fix: collect every mount prefix per builder (or fail closed when a builder is mounted more than once), and add a guard regression with two direct mounts of the same builder.
2. Positive controls accept unrelated failures (correctness). assert_allowed_control in community_ban_routes.rs L443–465 accepts any 2xx, 400, 404, 409, 415, or 422 for every control case and only checks that the body isn't a ban denial. Keeping the /count membership/ban gate but returning 404 immediately after it leaves all three new tests passing (community_http_ban_matrix, banned_root_websocket_is_refused, ban_is_scoped_to_host_resolved_community). Removing the gate instead does fail the matrix (/count returns 200 {"count":1} to the banned member), so the ban assertions are sound. The problem is that the unbanned side doesn't prove the operation still works, and moderation reads with valid inputs could likewise return 400/404 and pass.
Fix: give each control case its expected status and response: 200 plus response shape for valid reads and submissions (e.g. /count returns a numeric count), and the exact validation or missing-resource response for fixtures that are deliberately malformed or absent. Seed an accessible resource where resource authorization is part of what the case claims.
Scores
- Minimalism 8/10: the PR also carries the mesh receive, scraper, and push-lease fixture fixes that #8237 carries. They look correct; landing them once would keep this diff to the coverage change.
- Elegance 9/10: the route table, guard script, and signed matrix are a clean split, and the public/internal classifications and the two pending invitation/webhook rows are explicit rather than silently skipped.
- Correctness 7/10: the two items above. What holds up: ban denials require 403 plus the route's specific denial, not any 401/404; an empty matrix fails the case-set comparison; the ignored tests run by name in CI (Relay E2E and PostgreSQL jobs) and pass locally. The
bridge.rsmoderation-read change is a real fix: fresh owner proofs are now evaluated at the verified NIP-98 time, like the other NIP-98 routes, with a PostgreSQL regression. Themesh_boot.rsecho loop keeps one in-flight receive across drain ticks and drops it only on shutdown. Theingest.rs/relay_admin.rsedits are comment-only, and the 200 → 403 timed-out-admin expectation matches the shared gate on main.
Nonblocking
- The banned delegated agent has no matching unbanned-agent control outside the moderation reads. On
/buzz/v1/*the denial check only looks forcode == "forbidden", on media only forrelay membership required, and HEAD is bodyless, so those cases can't tell an owner-ban denial from some other agent-specific denial. A clear owner/agent pair per case would fix that. - The guard ignores
fallback/fallback_service, including the SPA fallback inrouter.rs.TESTING.mdlistsroute_service/nest_serviceas known gaps but not this one. TESTING.mdrestates the unmerged invitation contract (72h / 30-day lifetimes, v1invalid_after), which will drift if #8237 changes.- The description doesn't mention the moderation-read fix in
bridge.rs, and still says "Draft only" while the PR is ready for review.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (systems/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/source-visible flows) lanes.
Two existing P2 coverage defects remain at this head, independently confirmed from source. They are already described in review 5511682177, so I am linking that feedback rather than duplicating inline comments:
- Preserve every direct router mount. In
scripts/check-community-ban-route-inventory.py:286–304,prefixes[candidates[0]] = prefixoverwrites an earlier mount; lines 342–363 emit only the retained prefix. A second direct mount of the same builder can therefore expose endpoints absent from the inventory without changing the guard’s result. Collect all prefixes, or fail explicitly on multiple mounts, and cover that supported composition shape with a guard regression. - Make allowed controls assert the expected outcome.
crates/buzz-test-client/tests/community_ban_routes.rs:443–465accepts any 2xx, 400, 404, 409, 415, or 422 for every control. A valid/countor moderation read returning an unrelated 404 after admission still passes. Assert each case’s expected status and response shape; use exact validation/missing-resource responses for deliberately invalid fixtures, and accessible resources where resource authorization is part of the claim.
The mesh echo retains its in-flight receive across housekeeping ticks, and the moderation-read change supplies the verified NIP-98 timestamp to owner-ban evaluation. No additional actionable defect was established in this review. The explicitly pending invitation/webhook siblings and final integrated zero-pending requirement remain unchanged.
Coverage limitation: the HTTP matrix’s CI relay has membership enforcement off; closed-relay admission is covered by separate targeted regressions, not by that full route matrix.
Minimalism 9/10; elegance 9/10; correctness 8/10 because of the two coverage defects above.
Reviewed head dda7ef8815cf7675b399bcee4512da3fd6ec7945 against base/merge-base 326e2301cb4b1edcb8a72d01ac4b19a83545365f. Source-only: no tests, guard scripts, mutation checks, builds, applications, or live services were executed. A read-only CI snapshot showed 74 successful and 33 skipped checks; GitHub also reports merge conflicts. These checks do not establish runtime, cross-pod, or deployment readiness. Public PR description and discussion were inspected; no attached images or actionable publication disclosure found. This is a non-blocking COMMENT review; humans decide what must change.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Thanks for the scanner hardening. The repeated-nest gap from my last review is fixed, and the duplicate .nest and .route_service registrations now fail the guard, which I confirmed by adding each one to a copy of this head. Two blockers remain: the positive controls are unchanged, and the guard misses routers composed with .merge.
Blocking
1. Positive controls still accept unrelated failures (correctness). This is unchanged since my last review. assert_allowed_control in community_ban_routes.rs L446–469 still accepts any 2xx, 400, 404, 409, 415, or 422 for every control and only checks that the body isn't a ban denial. A handler that admits the unbanned member and then returns 404 (the /count mutation from the last review) still passes the matrix. The new profile_search case inherits the same check, and its control body is a deliberately malformed {, so it proves only that the request got past auth.
Fix: give each control case its expected status and response: 200 plus the response shape for valid reads and writes (e.g. /count returns a numeric count), and the exact validation or missing-resource response for fixtures that are deliberately malformed or absent.
2. The guard misses router builders composed with .merge (correctness). The registration check at L392 looks at route, nest, the *_service calls, and fallback, but not merge. builder_prefixes (L317–347) only reads .nest in build_router, and L430 emits each builder's routes once, under that builder's nest prefix or "". So a builder merged into a nested router, or merged in more than one place, exposes routes the inventory never sees. To check, I added .merge(crate::api::git::git_policy_router(state.clone())) inside api::buzz_v1::router in a copy of this head. The guard still exits 0 and reports the same 77 routes, even though /buzz/v1/internal/git/policy now has no row. This is the same class of gap as the repeated .nest and the shapes in the earlier inline thread, through the one composition call the scanner doesn't read. The relay already uses it (build_router merges four routers, git_router merges settings::router).
Fix: track .merge(<builder>(...)) the way .nest is tracked, so a merged builder's routes are emitted under the merging router's effective prefix, and reject a builder that is merged or nested more than once. Alternatively, reject .merge of a builder call outside build_router. Add a fixture for each case.
Scores
- Minimalism 8/10: the PR still carries the mesh-receive, boot, scraper, and push-lease fixture fixes shared with #8237, #8241, and #8242. They look right, but landing them once would keep this diff to the coverage change.
- Elegance 9/10: keying constants by module and failing every unclassified shape through one error path is a clear improvement. The allow-listed fallbacks are pinned to their builder and exact argument, which keeps the exemptions honest.
- Correctness 7/10: the two items above. What holds up: the duplicate-nest and
route_servicemutations both fail the guard (exit 1), all 13 scanner fixtures pass, and the baseline guard reports 77 routes, 24 HTTP cases, 19 supporting regressions, and 2 pending. The two seed commits (6eb83c0e0,9a2c8704a) only adapt the test seeds to the newadd_relay_membersignature andMembershipWritereturn type.
Nonblocking
TESTING.mdL106–114 still says the scanner doesn't discover.route_service(...)or.nest_service(...), which contradicts L43–47 now that both are hard errors. It also lists "conditional/opaque router composition" as a gap. Once item 2 is fixed, that paragraph should say which composition the guard handles.- Still open from the last review: the banned delegated agent has no matching unbanned non-moderator agent control, so on
/buzz/v1/*, media, and HEAD cases the denial check can't tell an owner-ban denial from some other agent-specific denial. - Still open from the last review:
TESTING.mdL78–81 restates the unmerged invitation contract from #8237 (72-hour default, 30-day maximum,invalid_after), which will drift if #8237 changes. - The description still doesn't mention the moderation-read fix in
bridge.rs(fresh owner proofs now evaluated at the verified NIP-98 time). Luke asked about this too. It's a real security fix and should be named, or split out so it can be reverted or backported on its own.
## 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>
9a2c870 to
69edf32
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 This head adds three commits on top of the one I reviewed last time (070b77b0). They change only the route-inventory guard, its fixture tests and TESTING.md. The blocker from my last review is fixed, and I found nothing new that blocks.
What changed and what I checked
- The mixed method-chain gap is closed. The new
route_methods(L481–512) reads a route's whole method expression one call at a time instead of searching it for helper names. The first call must be a known HTTP helper, optionally written asaxum::routing::<helper>. Each later call must be another known helper or.with_state,.layeror.route_layer. Anything else fails the guard, even after a known helper:on,on_service,*_service,fallback,merge, a variable, a wrapper call, or trailing tokens. Handler and middleware arguments are skipped whole, so apost(...)inside a closure doesn't count as a method. - Masking holds. The expression comes from source with comments, strings, raw strings and char literals already blanked, so a
)inside a raw-string handler argument doesn't break the delimiter match. - Axum coverage.
HTTP_METHODScovers every named method helper in Axum 0.8.9 (the version in the lockfile) plusany. Every route expression the relay registers today still classifies. Other valid spellings such asrouting::get(...)or::axum::routing::get(...)are rejected rather than read, which is the right direction for this guard. - The new fixtures cover rejection of each mixed shape, preserved supported chains, every
HTTP_METHODSentry, and helper-named calls inside arguments. - The
TESTING.mdlines from my last review are now wrapped. The description now matches the 79-route inventory and covers the discovery fix.
Runtime verification
At this exact head:
- The guard passes against the real relay source (79 routes, 24 behavior cases, 23 supporting regressions, 0 pending), and all 23 scanner fixtures pass.
- With the production
/inforegistration inrouter.rsreplaced, the guard fails with a classification error forget(h).on(...),get(h).post_service(svc)androuting::get(h). get(|| async { r#")"# })correctly staysGET /info. Adding.post(h)after it fails for the missingPOST /inforow, and adding.on(...)or.post_service(...)fails classification, so a raw-string)can't hide a chained method.
CI passes at this head. The Codex security review job fails only because this PR targets its stack branch rather than main.
Scores
- Minimalism 9/10: one small chain walker replaces the permissive search, with no new configuration.
- Elegance 9/10: the narrow fail-closed grammar matches the rest of the guard and is documented in the docstring and
TESTING.md. - Correctness 9/10: both reported shapes and the related unknown APIs fail, and current production routes still pass.
Nonblocking
TESTING.mdsays method expressions "must start with a known HTTP helper" but doesn't say theaxum::routing::path is also accepted, or that other path spellings are rejected. One clause would cover it.- Still open from my last review:
delayed_discovery_cannot_restore_stale_metadatasigns the stale candidate and the intervening edit at the same timestamp (L1535), so the NIP-16 event-ID tiebreak alone can reject it without exercising the fence. Make the stale candidate strictly newer, and cover a 39001 conflict after 39000 commits. - Still open from my last review:
expected_revision: Option<Option<&[u8]>>inreplace_addressable_event_checked(replaceable.rsL437) would read better as a small enum. - As the description notes, the CI Rust path filter doesn't include the scanner script paths, so a future scanner-only change wouldn't run the guard in CI.
bbf27d3 to
986d6e6
Compare
986d6e6 to
5beb1fd
Compare
5beb1fd to
04d579d
Compare
Signed-off-by: seiler <seiler@squareup.com> Co-authored-by: Codex <noreply@openai.com> (cherry picked from commit dcb83eaec669ed044ab2f16c8b7612bd54ed1d9c) Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: seiler <seiler@squareup.com> Co-authored-by: Codex <noreply@openai.com> (cherry picked from commit 5bbf2b64a82115c40fdadeb981dbfdf7122241f2) Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: seiler <seiler@squareup.com> Co-authored-by: Codex <noreply@openai.com> (cherry picked from commit 2374e1c05352db37eafa42d502a148b57dc21080) Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: seiler <seiler@squareup.com> Co-authored-by: Codex <noreply@openai.com> (cherry picked from commit 31968772ebe1b30c2862966e46b75a9ad4b967b6) Signed-off-by: Brad Seiler <seiler@squareup.com>
Keep invitation and webhook-owner gaps explicit until their implementations are integrated. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Brad Seiler <seiler@squareup.com>
Co-authored-by: Codex <codex@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>
Signed-off-by: Local Builder <1c476cb1127ea270cb900bdc3891060140bdd1173f91498030ee9b4db01c2f20@buzz.block.builderlab.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
The scanner keyed `&str` route constants by bare name, so main's `api::profiles::SEARCH_PATH` collided with `api::gifs::SEARCH_PATH` and reported a false duplicate. Key constants by module and resolve qualified references against that module. Unclassified registrations are now hard errors instead of silent passes: `.route_service`, `.nest_service`, and `.fallback_service` (except the allow-listed SPA fallback in `build_router`), `.nest` outside `router::build_router`, and nest targets that do not resolve to exactly one router builder. A fixture self-test pins each shape and runs in `just test-unit` and `scripts/run-tests.sh unit`. Classify the three routes main added: `POST /profiles/search` (member gate, now in the HTTP ban matrix) and the operator-plane `GET /operator/communities/joined` and `GET /operator/communities/nip-fi-binding`. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Brad Seiler <seiler@squareup.com>
The route inventory allow-listed every `.fallback_service` inside `router::build_router`, and did not scan `.fallback` at all, so a new sub-router fallback could add an unclassified handler without changing the inventory. Allow-list entries now name the builder, call, and exact whitespace-normalized argument, and each may appear once. The existing buzz_v1 404 fallback is allow-listed explicitly. `builder_prefixes` kept one prefix per builder, so nesting a builder at a second prefix silently overwrote the first. A repeated nest is now a hard error. Five new fixtures cover a non-SPA fallback service, a repeated SPA fallback, a handler fallback inside and outside build_router, and a builder nested twice. All five fail against the previous scanner. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Brad Seiler <seiler@squareup.com>
Main (#8482) added a relay_keypair parameter to Db::add_relay_member. The merge in 60df787 kept three ban-matrix test call sites at four arguments, so the buzz-relay lib tests did not compile. Pass a generated key, as the other tests do. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Brad Seiler <seiler@squareup.com>
add_relay_member returns a #[must_use] MembershipWrite on main, so clippy -D warnings rejected the three ban-matrix seed calls. Map to the outcome before expect, as the neighboring tests do. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com> 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>
Co-authored-by: Sarnow <1894dcbafaa4df1ac607a9bf879ac0b7d77fbccb0e0f4619ecc252c8adbad8a2@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Reject the allow-listed settings merge when its parent router has a nest prefix so stale root paths cannot satisfy the route inventory. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Record the new GET and POST upgrade routes in the operator control plane alongside the existing binding endpoint. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
Classify the complete outer MethodRouter chain and ignore handler and middleware arguments when collecting HTTP methods. 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>
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>
Bind the operator invite route to its signed router regression: clear owners and admins mint, while a restricted admin and unauthorized actors are refused. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
04d579d to
47e15f7
Compare
Summary
Classify all 79 registered routes (75 relay, 4 health) and bind community-ban checks to signed requests with exact allowed controls. New or opaque router composition fails the inventory guard, including unclassified
.merge(...)calls, nested Git settings merges, and partially classified method chains such asget(...).on(...)orget(...).post_service(...). Known method chains and.with_state,.layer, and.route_layermodifiers remain supported.Replace broad positive-status allowances with case-specific statuses and response bodies or shapes. Ordinary-member routes also exercise a clear owner-delegated agent. GIF controls require no provider key. Media reads, Git reads and workflows use absent resources; malformed JPEG upload and an invalid Git-push repository name assert their exact validation errors.
The moderation-read gate now uses the signed NIP-98 creation time for owner-proof validation. Main now reads relay-signed discovery metadata and admin inputs behind the replacement lock and retains it through storage (#9035). This layer preserves that implementation and its stale-publisher regression, replacing the older optimistic retry scheme. Integrated invitation, workflow and live-session regressions replace stale pending-sibling rows. The inventory requires zero pending cases.
The moderation-read fix ships with the relay without a feature flag. The discovery lock implementation already ships on main. Verify current discovery metadata and rosters under concurrent edits and owner-linked ban denials; rollback of the moderation-read fix is a revert of this layer.
Related issue
BUZZ-269. Stack: #8529 → #8241 → #8239 → #8235. #8237 and #8242 are merged. Shared preparation fixes already on main were dropped from this layer.
Testing
Current head
47e15f7a465976c2cd3fea3caf9199c2ebbb9527in native stack 8725, rebased by Brad through GitHub onto main44b3c4823. Brad owns further ref updates.POST /operator/communities/invitesis classified as operator control with conditional membership and enforced actor restrictions. The bound PostgreSQL journey permits relay owners/admins and denies a banned admin, members and outsiders as appropriate.The inventory passes with 80 routes, 24 HTTP behavior cases, 24 supporting regressions and zero pending cases. All 23 scanner fixtures pass. Independent review clears the classification and preserves all 21 carried patches in range-diff. Removing the route row or renaming its bound test made the guard fail on the unchanged earlier candidate.
The earlier video range failure was reproduced as a shared-blob fixture race on both the earlier stack and pinned main. Fix that separately with unique MP4 blobs and review from the enforce-mode owner. That historical diagnosis is not a current-head runtime result.
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 hosted CI is terminal and green, including runtime unit and PostgreSQL jobs. 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