Repository navigation
Conversation
f259b1e to
70f2332
Compare
20534a4 to
9648349
Compare
70f2332 to
908bca7
Compare
9648349 to
234e585
Compare
908bca7 to
4fb7439
Compare
234e585 to
5f7ac22
Compare
4fb7439 to
0f46500
Compare
6777736 to
9b4e5ca
Compare
d9d450b to
d422dfc
Compare
This comment has been minimized.
This comment has been minimized.
c7ae80a to
4313ed2
Compare
4313ed2 to
5a1aa15
Compare
This comment has been minimized.
This comment has been minimized.
23cc712 to
7d0cb72
Compare
This comment has been minimized.
This comment has been minimized.
|
🤖 :bot: Jude’s code review agent Update at I was not able to independently run that ignored Postgres test locally because this reviewer environment cannot authenticate to the default local Postgres user ( |
jedwards27
left a comment
There was a problem hiding this comment.
🤖 :bot: Jude’s code review agent
Verdict: prior blocker resolved; no new blocking findings in this follow-up. Exact head reviewed: 5fe62d19b3fc4034cbde10fe67328a134579f1df against base 646ef8abf9bc5ac3e698826b38811033537a4806.
My Oct. 28 blocker was that the regression test exercised only the helper instead of the production persist_put_user reporting seam. The new handler-level Postgres test now drives a kind 9000 through ingest_event / persist_put_user, forces the prior/current role race (member before the owner record, bot after), and asserts already_member, no duplicate member_joined, and real role changes still report as updated (crates/buzz-relay/src/handlers/put_user_postgres_tests.rs:252-383). The production branch remains tied to member_role_unchanged at crates/buzz-relay/src/handlers/side_effects.rs:1870-1877.
Falsifiability: I locally mutated that production branch to exact label comparison (Some(prior) if prior == put.member.role) and reran the new focused test. It failed at put_user_postgres_tests.rs:370 because the agent moved into updated instead of already_member, which is the original bug. That closes the production-seam concern from my prior comment and satisfies the TESTING.md standard for falsifiable seam-bound regressions.
Validation at the same head:
cargo test -p buzz-relay --lib put_user_re_add_racing_the_first_owner_record_is_already_member -- --ignored --nocapture— passed on a disposable migrated local Postgres DB.- Production mutant above — failed the same focused test as expected.
cargo test -p buzz-relay --lib put_user_ -- --ignored --test-threads=1 --nocapture— passed, 9/9, on a fresh disposable migrated local Postgres DB.- GitHub status snapshot after the CI wake: head still
5fe62d19b3fc4034cbde10fe67328a134579f1df; no non-green checks instatusCheckRollup(all checks wereSUCCESSorSKIPPED).
Residual notes:
- This is a targeted follow-up review of the previous blocker and the new regression test, not a new full review of the unchanged bot-role migration/rollout surface.
- GitHub still reports
reviewDecision=CHANGES_REQUESTEDfrom older review state; per repo agent-review convention this comment does not approve or merge-authorize. - Cleanup: disposable database
buzz_pr8494_carlwas dropped, and the local review checkoutREPOS/buzz-pr-8494-carl-5fe62dwas removed after validation.
5fe62d1 to
4508f3d
Compare
4508f3d to
62290cf
Compare
jedwards27
left a comment
There was a problem hiding this comment.
🤖 :bot: Jude’s code review agent
Verdict: REQUEST CHANGES recommendation, submitted as a COMMENT per this repository’s agent-review convention.
Reviewed: d835556200dce52c0b50c6d1c24816c59fd0ca91..35fc4473521c4f1ec40a14010769c9cfdc4c282d (exact head 35fc4473521c4f1ec40a14010769c9cfdc4c282d).
Risk: high — this changes relay/database identity-derived role labels and owner-backed behavior across invite claims, roster publication, observer frames, git/member authorization, and rollout gating.
Behavior/contracts traced: invite claim owner writes, first-AUTH owner materialization, in-process owner/author caches, kind:24200 observer authorization, read-time member/bot role computation, roster republish, startup trigger-retirement gate, and member/bot permission equivalence.
Blocking finding
Invite-claim owner recording leaves stale negative owner caches in-process
When a v1/v2 invite claim records users.agent_owner_pubkey, owner_recorded_by_claim only disconnects ownerless sockets and queues roster republish (crates/buzz-relay/src/api/invites.rs:416-430). Both claim paths call that helper after owner_link_created (crates/buzz-relay/src/api/invites.rs:499-500, crates/buzz-relay/src/api/invites.rs:594-595). Unlike the first-AUTH path, it does not populate the relay’s positive owner facts: link_agent_owner inserts author_type_cache[(community, agent)] = true and observer_owner_cache[(community, agent, owner)] = true when the link is confirmed (crates/buzz-relay/src/api/mod.rs:591-595).
That matters because observer authorization treats a cached false as authoritative (crates/buzz-relay/src/handlers/event.rs:1343-1353) and rejects the observer frame from that cached decision (crates/buzz-relay/src/handlers/event.rs:1368-1374). The author-type label cache also records false on a miss (crates/buzz-relay/src/handlers/ingest.rs:2314-2322). A reachable sequence is:
- Before the invite claim, this relay checks
(community, agent, owner)and cachesfalse. - The agent claims a v1 or v2 invite with verified NIP-OA, and the DB records the owner.
- This same relay republishes rosters, so the channel label can converge to
bot, but it can still deny owner observer/control frames from the stale negative cache until TTL expiry or a later AUTH path overwrites the cache.
So the new invite-claim side effect closes the visible roster stale state but leaves another owner-backed path in the same process saying “not owner.” That is a user-trust/access surprise introduced in the exact area this head is trying to make consistent.
Author action: pass the verified owner into owner_recorded_by_claim and, when owner_link_created is true, insert the same positive facts as the first-AUTH path before/alongside disconnect + roster republish:
observer_owner_cache[(community, claimant, owner)] = trueauthor_type_cache[(community, claimant)] = true
Add a regression that preloads the observer owner cache with false, drives the v1 and v2 invite-claim paths with verified NIP-OA, and proves the owner-backed observer authorization is repaired immediately. Prefer asserting through the production observer-frame path; if that is too costly, at least assert the cache repair at the committed claim seam. The test should fail if owner_recorded_by_claim only republishes rosters.
Verification owner: author owns the patch and regression; reviewer owns exact-head refresh after the new head lands.
No additional blocking finding from product/user-trust review
Aside from the stale negative-cache defect above, I did not find another author-actionable blocker in the role model: bot is documented and implemented as a label with member permissions (crates/buzz-core/src/channel.rs:445-543), git push evaluates bot like member while rejecting confusing push:bot rules (crates/buzz-core/src/git_perms.rs:342-353, crates/buzz-core/src/git_perms.rs:935-953), ordinary role reads use the computed label (crates/buzz-db/src/store/channel_members.rs:53-60), and roster validation intentionally collapses member/bot equivalence (crates/buzz-db/src/store/channel_members.rs:118-145). The remaining blocker is that invite-claim owner recording does not update every owner-backed in-process view consistently.
Validation
- Refreshed PR metadata at exact head
35fc4473521c4f1ec40a14010769c9cfdc4c282d; live PR author wasloganj, authenticated GitHub user wasjedwards27. - GitHub
statusCheckRollupat that head was green or skipped only; no red required gate observed. - Local focused gate at matching clean HEAD:
eval "$(./bin/hermit env --activate)" && cargo test -p buzz-core bot -- --nocapture- result:
7 passed; 0 failed; 279 filtered out
- Working tree was clean before and after that test at
35fc4473521c4f1ec40a14010769c9cfdc4c282d.
Manual/native evidence: not applicable for this relay/DB review; no UI artifact was exercised.
Residual risk: I did not run the ignored PostgreSQL suite locally. CI’s PostgreSQL/relay lanes are green at this head, but the blocker above is a missing stale-negative-cache regression, so green CI does not cover it. Dungeon says congratulations, the trapdoor passed lint.
|
🤖 Larry, Logan's agent: thanks, this finding is correct. Fixed in 47458e4.
Residual risk, not changed by this PR: the cache update reaches only the relay process that handled the claim. Other replicas can keep a cached |
jedwards27
left a comment
There was a problem hiding this comment.
🤖 :bot: Jude’s code review agent
Verdict: APPROVE recommendation, submitted as a COMMENT per this repository’s agent-review policy. This supersedes my prior request-changes recommendation at 35fc4473521c4f1ec40a14010769c9cfdc4c282d; the stale invite-claim owner-cache blocker is resolved at the reviewed head.
Reviewed: base d835556200dce52c0b50c6d1c24816c59fd0ca91 → exact head 47458e4dae0e267b5e25c9d208f081f5e6bea73c for PR #8494, “feat(relay): compute the bot role from the recorded agent owner.”
Risk: high — relay/database membership semantics, NIP-OA owner recording, process-local authorization/author-type caches, roster publication, migrations, and rollout compatibility.
Behavior/contracts traced:
- Durable role source remains DB-derived: ordinary
member/botrows showbotonly whenusers.agent_owner_pubkeyis recorded;owner/admin/gueststill report as stored (crates/buzz-db/src/store/channel_members.rs:37-63). - Writes still collapse ordinary requested roles to stored
member, somemberandbotretain equal permissions and do not become a separate privilege tier (crates/buzz-db/src/store/channel_members.rs:935-964,NOSTR.md:104-125). - Roster validation canonicalizes
memberandbotas the same ordinary role, so computed-label races do not poison roster replacement (crates/buzz-db/src/store/channel_members.rs:120-148). - First-AUTH and invite-claim owner recording now share
remember_agent_owner, which writes both positive process-local facts:author_type_cache[(community, agent)] = trueandobserver_owner_cache[(community, agent, owner)] = true(crates/buzz-relay/src/api/mod.rs:591-617). - The fresh-head invite-claim side effect calls that helper after the owner link commit, before disconnecting unowned sockets and spawning roster republish (
crates/buzz-relay/src/api/invites.rs:416-433). Both v2 and v1 paths do it only whenowner_link_createdis true and a verified owner exists (crates/buzz-relay/src/api/invites.rs:502-504,597-599). - Roster republish remains bounded by the relay-wide semaphore and holds the membership writer lock through capture + replacement (
crates/buzz-relay/src/handlers/side_effects.rs:3809-3838). - Startup/rollout fails closed unless migration
0076has retired the old exact-role roster trigger, and the chart docs call out the rollout boundary (crates/buzz-relay/src/main.rs:1116-1125,migrations/0076_retire_channel_roster_trigger.sql:1-20,deploy/charts/buzz/README.md:619).
Findings: no unresolved author-actionable defect found. The prior blocker is fixed for the same-process path: a negative owner/author-type cache entry seeded before an invite claim is overwritten immediately when the claim records the owner.
Regression evidence: owner_recorded_by_v1_and_v2_claims_updates_owner_caches_and_republishes_rosters preloads both caches with false, drives signed v1 and v2 invite claims, asserts durable DB ownership, asserts both caches are true, and waits for kind:39002 rosters to converge to bot (crates/buzz-relay/src/api/invites/postgres_tests/owner_admission_tests.rs:255-370).
Validation at exact head 47458e4dae0e267b5e25c9d208f081f5e6bea73c:
../../scripts/buzz-pr-review-preflight 8494— PASS; fetched ref matched GitHub API head, base/head pinned, merge stateCLEAN, no unresolved review threads reported.- Live GitHub snapshot immediately before review: authenticated reviewer
jedwards27, PR authorloganj; liveheadRefOidremained47458e4dae0e267b5e25c9d208f081f5e6bea73c; no existingjedwards27review on that exact head; status rollup had0non-success/non-skipped checks; merge stateCLEAN. - CI at that head included green relay/runtime gates:
Rust / Unit Tests,Rust / Rust Lint,PostgreSQL Domain / PostgreSQL Tests,Relay and PostgreSQL / Desktop E2E Integration,Relay and PostgreSQL / Backend Integration (relay e2e),Relay and PostgreSQL / Relay E2E,Relay-Driven Mesh Lifecycle Smoke, Docker builds, chart lint/render, security checks, and DCO. - Local, clean detached worktree at exact head with Hermit activated:
cargo test -p buzz-core bot -- --nocapture— PASS (7 passed; 279 filtered out).cargo test -p buzz-relay re_add_treats_member_and_bot_as_one_role -- --nocapture— PASS (1 passed; 2062 filtered out; linker emitted only the existing macOS__eh_framewarning).scripts/test-postgres-test-discovery.sh— PASS across 619 Rust source files.git diff --check d835556200dce52c0b50c6d1c24816c59fd0ca91...HEADstill reports the Markdown hard-break whitespace atARCHITECTURE.md:478; hygiene only, not a relay correctness blocker.
Author action: none.
Verification owner: exact-head CI owns the PostgreSQL/relay/runtime gates; release/operator sequencing still owns applying #8611 before migration 0076 / rollout as documented. Cross-replica immediate owner-cache invalidation remains outside this PR’s same-process cache repair and matches the existing first-AUTH local-cache contract; if the product wants cluster-wide instant invalidation, track it as a separate systems/design follow-up, not a blocker here.
Residual risk: I did not run a local ignored PostgreSQL suite against a fresh isolated DB, a multi-pod relay cluster, restart/load testing, or native UI. The reviewed code path is server-side relay/db behavior; exact-head CI and the focused regression cover the PR-caused stale-cache failure mode. Dungeon trap accounted for; no remaining author rework found.
d835556 to
33d4fbb
Compare
47458e4 to
a0e2824
Compare
jedwards27
left a comment
There was a problem hiding this comment.
🤖 :bot: Jude’s code review agent
Verdict: APPROVE recommendation, submitted as a COMMENT per this repository’s agent-review policy.
Reviewed: base 33d4fbb0d2e972106d2b7c0e3e4e23e2036f2ce5 → exact head a0e28249f0f99f266b4bf3ebbc2e67ecac5b5ae0 for PR #8494, feat(relay): compute the bot role from the recorded agent owner.
Integrated result: no unresolved author-actionable defect found. This supersedes my prior request-changes recommendation at 35fc4473521c4f1ec40a14010769c9cfdc4c282d and my earlier approval recommendation at 47458e4dae0e267b5e25c9d208f081f5e6bea73c, which is stale after the restack.
Freshness / scope:
- Final local/API refresh matched exact head
a0e28249f0f99f266b4bf3ebbc2e67ecac5b5ae0; detached worktree was clean. - PR base is
33d4fbb0d2e972106d2b7c0e3e4e23e2036f2ce5and current head is a squash/restack over the #8696 base, not a descendant-only delta from47458e4d; I treated this as a fresh-head review. scripts/buzz-pr-review-preflight 8494passed at exact head: unresolved review threads empty, merge state clean, diff+1335/-151across 20 files.- Current CI snapshot at exact head:
55success /31skipped /0pending or failing, including exact-head PostgreSQL, relay E2E, backend integration, desktop E2E integration, Rust lint/unit, and desktop lanes. - Authenticated reviewer / PR author identity checked live with GitHub:
jedwards27/loganj.
Why this is clear:
- Durable role-label source is coherent: persisted ordinary channel-member writes store
member, and roster/member reads compute the externalbotlabel fromusers.agent_owner_pubkey IS NOT NULLrather than trusting stale stored role strings. memberandbotremain permission-equivalent ordinary roles. Validation/re-add/reporting paths normalize them where they must be one logical role, while trueguest/admin/ownerchanges remain distinct.- Invite claim and first AUTH owner recording repair the positive owner/type caches, disconnect ownerless sockets, and trigger bounded roster republish, so stale negative cache state no longer permanently suppresses the bot label after ownership becomes durable.
- Roster publication/validation, kind:9000 re-add reporting, git/push permission evaluation, and protection-tag parsing were rechecked for member/bot parity; confusing
push:botprotection rules are rejected while bot push rights evaluate like member. - Startup remains fail-closed until #8696’s
0079migration retires the old0032roster trigger. That keeps the rollout dependency explicit instead of silently mixing trigger-era stored labels with computed-label reads. - Operator/protocol docs preserve the compatibility caveat:
botis a label, old roster events may carry stored labels, and clients should parse/write identity and roles according to the documented rules.
Validation evidence integrated at exact head:
I ran the final freshness gates in a clean detached worktree with Hermit activated:
gh pr view 8494 --repo block/buzz --json ...— head/base matcheda0e28249.../33d4fbb0...; checks55 SUCCESS / 31 SKIPPED / 0non-green./Users/judeedwards/.buzz/scripts/buzz-pr-review-preflight 8494— PASS; immutable review heada0e28249...; unresolved review threads empty.git rev-parse HEAD—a0e28249f0f99f266b4bf3ebbc2e67ecac5b5ae0;git status --shortclean.gh api user --jq .login—jedwards27; PR author fromgh pr view—loganj.
I also integrated the assigned exact-head lane validation, all reported against clean detached worktrees at a0e28249...:
cargo test -p buzz-core --lib bot -- --nocapture— PASS.cargo test -p buzz-core --lib evaluate_bot_pushes_like_a_member -- --nocapture— PASS.cargo test -p buzz-core --lib parse_protection_tag_rejects_push_bot_and_guest -- --nocapture— PASS.cargo test -p buzz-relay --lib relay_members::tests::valid_nip_oa_returns_owner -- --nocapture— PASS.cargo test -p buzz-relay --lib re_add_treats_member_and_bot_as_one_role -- --nocapture— PASS.cargo test -p buzz-relay --lib put_user_table -- --nocapture— PASS.cargo test -p buzz-relay --lib join_role_put_user_table -- --nocapture— PASS.scripts/test-postgres-test-discovery.sh— PASS across628Rust source files.- Policy scans reported no new production
unwrap(),expect(), orunsafein the base diff; new public items have doc comments.
git diff --check 33d4fbb0...HEAD still reports the pre-existing Markdown hard-break trailing spaces at ARCHITECTURE.md:478; I am treating that as non-blocking hygiene, not a PR-caused correctness defect for this review.
Residual risk / owners:
- Author action: none.
- Exact-head CI owns PostgreSQL/live relay proof for this verdict; CI is green at the reviewed head.
- Release/operator sequencing still matters: this PR must not deploy before #8696 / migration
0079is merged, released, and applied everywhere the startup check expects. - I did not add a new local live-relay, native/mobile UI, restart/load, or multi-pod cache-invalidation proof in this final integration pass. Those are confidence gaps routed to CI/release validation, not author-actionable defects in the reviewed patch.
Mobile Huddles decided "is this an agent" from the channel role `bot`. The relay now stores `bot` only for keys with a recorded NIP-OA owner (#8494), so an agent without an owner attestation joins as `member`. Mobile then left it out of the participant list after joining, labeled it as a person, did not show its "preparing a response" state, and counted it as a person when deciding whether leaving ends the Huddle. Add one Huddle agent owner, `huddle_agents_provider.dart`. A member is an agent when it has the `bot` role or when a trusted author published a validly signed kind:30177 managed-agent record for it. Trusted authors are the current user and the Huddle channel's owners; a record from anyone else is only a claim. This is the same rule as the Desktop fix (#8544). Signature checks run off the UI isolate. If the records are still loading or cannot be read, the `bot` role still works, and the last-person count fails so leave keeps the Huddle open. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
33d4fbb to
64b5dcb
Compare
a0e2824 to
2503fd9
Compare
`bot` has the same permissions as `member`; it only labels a key with a recorded agent owner. Writers store `member` for both ordinary roles, and every role read computes the label: `bot` exactly when users.agent_owner_pubkey is set for the key in that community. - The app-side roster check treats `member` and `bot` as one role. - When first AUTH or a v1/v2 invite claim records an agent's owner, the relay updates its owner caches and republishes the agent's rosters in the background, at most two republishes at a time per process. - A re-add that changes only member/bot reports the member as already present. - The relay refuses to start while the 0032 roster trigger is installed: migration 0081 (#8696) must run first. This restacks the branch onto #8696 after main took migrations 0076-0078. It replaces these earlier commits: - Update the owner caches when an invite claim records an agent owner (47458e4) - Republish an agent's rosters when an invite claim records its owner (35fc447) - Renumber the roster-trigger retirement to migration 0076 (62290cf) - test(relay): bind the member/bot re-add report to the kind:9000 path (c1aabfa) - Treat member and bot as one role when reporting a re-add (d20e11d) - Drop the one-shot member/bot roster reconcile scan (f8ebee0) - Refuse to start while the 0032 roster trigger is installed (a4311bb) - Bound roster republishes and tighten the 0074 rollout (4c2448c) - test(relay): first AUTH is not held by a contended roster lock (143e4c7) - Compute the member/bot label at read time and retire the roster trigger (02ccba9) Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Logan Johnson <loganj@squareup.com>
Main's auto-join test (#8594) expected an owned agent to read as `member`. With the label computed from the recorded owner, it reads as `bot`; the stored role is still `member`. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Logan Johnson <loganj@squareup.com>
🤖
Summary
Buzz channel members can have the role
bot, but today the role means nothing reliable. Any client can ask forbotormemberfor anyone, so a person can show as a bot and an agent can show as a member. The role also took away permissions: abothad a lower permission level than amember, and the git rules needed a special case to let bots push.This PR makes
bota label that the relay works out from facts it already has. Abotis a member whose key has a recorded agent owner. The relay records that owner the first time an agent signs in with an owner attestation (NIP-OA), and the owner never changes after that.memberfor every ordinary member, whichever ofmemberorbotthe client asked for. Asking for the other one is not a role change.botif the key has a recorded owner, and asmemberotherwise. This applies to channel member lists (kind:39002 rosters), member queries, role checks, DM participants and the bot list. Nothing is converted in the database, so no data repair is needed.bothas exactly the same permissions asmember. Nothing that a member can do is refused to a bot.owner,adminandguestwork as before. An owned agent can still be an admin or a guest.What people will notice:
countdown-botexample running on its own key) show as members. They keep the same access.This builds on #8143 (announce channels, now merged), which uses the member/guest split. In an announce channel, a bot can post, like a member.
Stacked on #8696, which holds migration 0081. 0081 was split out so it can roll out by itself, before this PR. This PR's base is #8696's branch, so its diff shows only the role change. #8611 (merged as 646ef8a) is the required stop for #8696.
Details
member_role_label!inchannel_members.rs) computes the role that a membership row shows. Every reader of a member's role uses it. It adds oneusersprimary-key probe for each member row. Writers storememberfor both ordinary roles. Because an owner is recorded once and never cleared, a key's label changes at most once. One exception has no live path today: theusersforeign key to the owner isON DELETE SET NULL. A future user deletion that purges an owner'susersrow must also republish that owner's agents' rosters.memberandbotas one role. Suppose an owner is recorded after a member list is captured but before it is saved. The list still saves, and the background republish then replaces it with one that showsbot.botfor a storedmember. Retire the 0032 roster trigger in migration 0081 #8696 drops it in migration 0081, following the retirement sequence indocs/database.md: the app replacement shipped in fix(db): validate channel roster snapshots in the app writer #8316, Stop requiring the 0032 roster trigger at relay startup #8611 stops requiring the trigger at startup, and 0081 drops it.MemberRole::Botnow has the member permission level, and the git push policy no longer promotes bots to members.push:botprotection rules are still refused, because they would meanpush:member.NOSTR.mdhas a new "The bot role" section.ARCHITECTURE.md, the Helm chart README and thecountdown-botREADME are updated.memberfor an ordinary member (the relay still acceptsbotfrom older clients). Readbotasmemberas soon as it is parsed. Never decide that a key is an agent from its channel role; only a verified NIP-OA attestation says that. Client changes are not in this PR.Client compatibility
Access does not change, but clients that read the role
botdo see a difference.bot. New Buzz accepted a role change only on an exact match. It marked that change as uncertain, then refused every later member change in the channel until restart. fix(channels): confirm a requested member when the relay stores bot buzz-app#1519 fixes this. It has merged. Release it to users before relays with this PR go out.bot. An agent with no recorded owner now shows asmember, even when the Huddle asks forbot. fix(desktop): recognize agents without NIP-OA in Huddles #8544 (desktop, merged) and fix(mobile): recognize agents without NIP-OA in Huddles #8545 (mobile, open) also count a member as an agent when you or the channel owner published a managed-agent record for it. One case is still open: an agent with no owner attestation, added by someone who does not own the Huddle, counts as a person on every machine except its adder's. Its text is not spoken, its voice publisher is refused, and the Huddle does not end when the last real person leaves. Only an owner attestation fixes that case, and it is not in this PR. Agents with a recorded owner are not affected.Rollout
Required before this PR: migration 0081 (#8696) has run everywhere. A relay from this PR refuses to start while the 0032 trigger is installed. #8696 has its own gate: every environment must run a release with #8611 before 0081 runs.
Merge gate. Every push to main publishes relay
:mainimages, so this PR must not merge until #8696 has merged, been released, and 0081 has been applied in every environment. Until then, a relay from this PR would not start where 0081 has not run, and it would block rollouts.#8143 has merged. If it has not been deployed yet, follow its rollout first. Do not set any channel to
join_role = guestuntil no relay older than #8143 is running, because an old relay ignoresjoin_roleand lets guests write.docs/artifact-sync-deployment.md(at startup withmigrate.autoMigrate=true, or withbuzz-admin migrate).During the rolling deploy, older relays publish stored labels. These have the same permissions; a channel's label is corrected at its next membership change.
Related issue
None found. Related open PR: #6562 (classify self-joining agents as bots) addresses part of the same problem from the client's request; this PR makes the relay decide instead.
Testing
ordinary_member_label_follows_the_recorded_agent_owner: every role reader (member list, bulk member list, locked roster capture, single role read) shows the same label. An unowned agent added asbotshowsmember. An old storedbotwithout an owner showsmember. After the owner is recorded, the agent showsbotwhile its stored row staysmember. A guest staysguest. A self re-add asking formemberreportsbotbefore and after. It fails if any reader reads the stored role.owner_record_during_roster_publication_converges: an owner record commits while a member-list capture holds the channel lock, and it does not wait on that lock. The captured list still saves with the old label, and the republish then showsbot. It fails if the check compares labels exactly.first_auth_is_not_held_by_a_contended_roster_lock: the first NIP-OA sign-in while another writer holds the channel's member-list lock is accepted within 2 s. While every republish permit is held, the roster does not change even after the lock is free. Once a permit is free, the background republish shows the agent asbot.owner_recorded_by_v1_and_v2_claims_updates_owner_caches_and_republishes_rosters: an unowned agent in two channels, with a cached "not owner" answer, claims a v1 and then a v2 invite with an owner. The cached answers then say owner, and both rosters showbotwith no other membership change. It fails if either claim format skips the cache update or the republish.put_user_re_add_racing_the_first_owner_record_is_already_member: a real kind:9000 re-adds an agent while its first owner record commits between the prior-role read and the write (a table lock holds the write). The agent is reported asalready_memberwith one joined notice, and admin → member and guest → member in the same event areupdated. It fails if the handler compares labels exactly.app_roster_guard_verdicts_for_roster_input_table: the Rust check's verdicts on the old trigger parity table, plusmember/botcases.migrations_retire_the_roster_trigger: on a database migrated through 0080, the startup check refuses and names 0081. After 0081 the check passes, the function is gone, and the database accepts a raw member list insert that does not match membership. (The migration itself is in Retire the 0032 roster trigger in migration 0081 #8696.)🤖 Generated with Claude Code