feat(projects): add agent and CLI project-home support - #6590
feat(projects): add agent and CLI project-home support#6590thomaspblock wants to merge 27 commits into
Conversation
Give agents bounded project-home context and project-aware CLI operations while keeping channel matching client-filtered through the existing relay query surface. Signed-off-by: Thomas Petersen <thomasp@squareup.com>
thomaspblock
left a comment
There was a problem hiding this comment.
Cassandra adversarial/security review — needs work
The red Unit Tests job is not caused by this diff: it fails linking untouched buzz-voice with could not find native static library 'sherpa-onnx-c-api'. That check should be retried rather than patched in this projects PR.
I found two source-level blockers independently while tracing the new project-home resolution.
P1 — Any relay writer can hijack a channel's agent project context and redirect channel-scoped issues (confidence 100)
Evidence
crates/buzz-acp/src/prompt_project.rs:23-25:!event_is_unlisted(event) && event_has_tag_value(event, "buzz-channel", channel_id)crates/buzz-acp/src/prompt_project.rs:27-33: the matching events are ordered only bycreated_at, then the first parseable event wins.crates/buzz-cli/src/commands/project_channel.rs:27-31:let project = pick_oldest_listed(&projects);followed byif let Some(member) = first_member_repo(event) { return Ok(member); }docs/nips/NIP-MP.md:139:`buzz-channel` on a project is **metadata only**.docs/nips/NIP-MP.md:188:The relay MUST NOT check whether the signer owns, maintains, or has any relationship to a member repository.
Trigger scenario
- An attacker who knows a project channel UUID publishes a listed
kind:30621carrying thatbuzz-channeland anatag for the attacker's repository. This is protocol-valid and requires no authority over the channel. - The attacker gives it an earlier accepted timestamp than the legitimate project (or simply publishes before project creation).
- ACP selects that event as the channel's project home and promotes its name/owner/repository into generated
[Context]instructions. buzz issues create --channel <victim-channel>independently makes the same oldest-event choice and returns the attacker's first member coordinate without checking that the project signer controls the channel or that the member repo is actually bound to it.- A normal “create a task in this project” request is therefore signed against an unrelated attacker-chosen repository.
This crosses an integrity boundary: unauthenticated project metadata is being treated as authoritative routing configuration. Resolve the project from an authenticated channel-owned binding/type, or require a verifiable relationship between the selected project signer and channel authority. At minimum, channel-scoped repo resolution must verify the selected 30617 is bound to the requested channel and reject ambiguous projects rather than choosing oldest.
P1 — Global slug squatting lets any signer block another user's project creation (confidence 100)
Evidence
crates/buzz-cli/src/commands/projects.rs:373-379:other_listed_project(&fetch_projects_by_dtag(client, slug).await?, &caller_pubkey)causes a conflict when any other pubkey has the slug.docs/nips/NIP-MP.md:134:Only the signer can replace their (pubkey, 30621, d) coordinate.docs/nips/NIP-MP.md:194:newest created_at wins per (pubkey, 30621, d), and one pubkey can never overwrite another's coordinate.
Trigger scenario
An attacker publishes listed projects for common slugs (app, website, a known upcoming product name). Every later buzz projects create <slug> by every other identity is rejected locally, even though the protocol intentionally namespaces projects by signer. The suggested error action (“Add a repository to that project instead”) cannot work because editing is signer-only. Do not impose relay-wide uniqueness on an owner-namespaced coordinate; duplicate-card prevention needs an authority-scoped rule.
Additional adversarial risk retained in this PR comment
crates/buzz-cli/src/commands/project_channel.rs:178-185 adds the selected foreign project owner as a maintainers tag on an implicitly created caller-owned repository. Under docs/nips/NIP-MP.md:215-217, that tag is sufficient claim authority for the foreign signer. I did not live-test Desktop's resulting fold, but this should be removed or explicitly justified before merge; untrusted project metadata must not grant provenance/claim authority over a newly created repo.
Coverage: full 12-file diff read; traced ACP project lookup → generated context, CLI channel lookup → issue creation, implicit repo creation, project collision checks, NIP-MP authority and claim semantics. I did not mutate the branch or run a live hostile relay reproduction.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
thomaspblock
left a comment
There was a problem hiding this comment.
Cassandra re-review of 7a9af2ac — one routing blocker remains
The original two P1 findings are fixed in the authoritative-selection path: foreign channel/project claims no longer route ACP or CLI, ambiguity fails closed, cross-signer slug/channel squatting is removed, and implicit repo creation no longer grants foreign maintainers authority.
P1 — Existing same-id repository bypasses the new channel-binding check (confidence 100)
Evidence
crates/buzz-cli/src/commands/project_channel.rs:181-188:if let Some(existing) = crate::commands::repos::fetch_own_repo_announcement(client, &repo_id).await? { let _ = try_add_own_repo_to_channel_project(client, channel, &repo_id).await; return Ok(ChannelProjectRepo { repo_owner: existing.pubkey.to_hex(), repo_id, }); }
- The new binding check exists in
repo_from_announcementat lines 94-104, but this fallback does not call it.
Trigger scenario
- The caller already owns repo
30617:<caller>:app, bound to channel A (or unbound). - They own a repository-empty project home with slug
appin channel B. buzz issues create --channel Bfinds no authoritative project/member and no caller-owned repo bound to B, then reachesensure_default_repo.fetch_own_repo_announcement("app")returns the channel-A repository. The code attaches it to the channel-B project and returns it without checking or rebinding itsbuzz-channel.- The issue is silently created against channel A's unrelated repository. Subsequent calls repeat the same misrouting, while ACP correctly refuses to recognize that member as authoritative for B.
The fallback must apply the same first-buzz-channel equality invariant before returning. If an existing same-id repo is bound elsewhere, fail with an actionable conflict or choose a non-colliding id; do not attach or route to it.
Advisory — maintainer authorization reads only the first value (confidence 75)
Evidence
crates/buzz-cli/src/commands/project_channel.rs:88-91:|| repo.tags.iter().any(|tag| { matches!(tag.as_slice(), [name, value, ..] if name == "maintainers" && value.eq_ignore_ascii_case(&signer)) })
crates/buzz-acp/src/prompt_project.rs:93-101likewise returns onlytag.get(1)for eachmaintainerstag.VISION_PROJECTS.md:27and NIP-34 modelmaintainersas a multi-value tag; Desktop deliberately reads all values (desktop/src/features/projects/projectModels.ts:283-285).
A valid ['maintainers', first, project_signer] repository authorizes the signer in Desktop but is rejected by both new routing implementations. Iterate all values after the tag name so ACP, CLI, and Desktop share one authority rule.
Re-review coverage: exact fix diff a6c5f1db..7a9af2ac; traced authoritative selection, ambiguity, project creation collisions, implicit repo fallback, and maintainer parsing. Report-only; no branch mutation.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
thomaspblock
left a comment
There was a problem hiding this comment.
Cassandra final security/authority re-review — findings cleared at 7bbed3f1
No remaining security or adversarial findings in the incremental fix.
Verified:
crates/buzz-cli/src/commands/project_channel.rs:197-205now callsrequire_repo_channel_bindingbefore reusing or attaching a same-slug existing repository, so a repository bound to channel A cannot route a channel-B issue.require_repo_channel_bindinguses the firstbuzz-channelvalue, matching the relay's fail-closed binding semantics, and rejects both mismatched and absent bindings.- ACP's
multi_tag_valuesand CLI'stag.as_slice()[1..]now inspect every pubkey value in everymaintainerstag, matching NIP-34/Desktop semantics. - Regressions cover the mismatched existing binding and authorization by a later maintainer value.
- The prior fixes remain intact: project-home selection requires a channel-bound live member repository plus signer authority; ambiguity fails closed; cross-signer slug/channel squatting is absent; implicit creation does not grant foreign maintainer authority.
Verdict for my security/authority lane: merge-ready at exact head 7bbed3f127f25559fc301044842ee6582b2fdc9a. CI and independent correctness review are outside this verdict and were still in progress when checked.
## Summary - create explicit NIP-MP projects with a home channel and default repository - preserve standalone repository folding, project deletion, and deterministic repository selection - restore Template, Team, visibility, and agent settings in the project creation flow This is Part 2 of the channel-first Projects stack, following #6590. It is independently based on `main`; Part 3 adds the project-home channel surface. ## Testing - focused project collection, creation, channel, and model tests: 38/38 passed - Desktop unit suite: 5,415/5,415 passed - TypeScript, Biome, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - create listed and unlisted projects with and without templates in the first staging Desktop session - healthy signals: one home channel, one default repository, stable project coordinates, and no duplicate legacy card - failure signals: partial project creation, duplicate projects, missing default repository, or stale sidebar entries; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary - classify and render project-home channels through the shared channel glyph and lifecycle helpers - let the normal channel pane host a project idle auxiliary surface and focus drawer - align channel management, headers, member bars, and empty-channel actions with project channel semantics This is Part 3 of the channel-first Projects stack, based on #6591. Part 4 adds the project-home navigation and context experience. ## Testing - focused channel lifecycle, pane helper, and project-home channel tests: 7/7 passed - Desktop unit suite: 5,422/5,422 passed - E2E-mode Desktop build passed - TypeScript, Biome, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - open normal, temporary, private, and project-home channels in the first staging Desktop session - healthy signals: normal channels retain their existing composer/thread behavior and project homes use the project glyph and auxiliary slot - failure signals: missing composer, incorrect channel kind, stuck focus drawer, or project chrome on a normal channel; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
## Summary - render an explicit project's home channel through the normal channel timeline and composer - add a resizable project context rail with codebase, channel, people, and workspace navigation - keep project agent conversations bounded to the project home and preserve repository/detail routes This is Part 4 of the channel-first Projects stack, based on #6594. The final part contains overview and workspace completion polish. ## Testing - focused project conversation, route, summary, workspace-sheet, and related-channel tests: 39/39 passed - Desktop unit suite: 5,439/5,439 passed - E2E-mode Desktop build passed - TypeScript, Biome, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - open project homes from project and channel entry points in the first staging Desktop session - healthy signals: one channel timeline/composer, stable repository context, bounded project agent history, and reversible workspace sheets - failure signals: duplicate channel surfaces, stale repository selection, unrelated DM history, or sheets replacing the channel route; mitigate by reverting this PR Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
| fn truncate_repo_name(name: &str) -> String { | ||
| if name.len() <= 128 { | ||
| return name.to_string(); | ||
| } | ||
| name.chars().take(128).collect() | ||
| } |
There was a problem hiding this comment.
The guard measures bytes but the truncation takes chars, while build_repo_announcement rejects names over 128 bytes. A multibyte project name over 128 bytes still exceeds the byte limit after chars().take(128), so default-repo creation errors instead of truncating (e.g. a 100-CJK-character name). Same pattern in projects.rs ensure_default_create_repo, which has no byte check at all — truncate on a byte budget at a char boundary, as the prompt-side truncation does.
🤖
There was a problem hiding this comment.
Fixed in 2e0fe69: both default-repository paths now share UTF-8-safe truncation on a 128-byte budget, with a CJK regression test.
| const workspaceSheet = | ||
| workspaceSheetOpen && workspaceSheetTab && workspaceRepository ? ( | ||
| <ProjectHomeWorkspaceSheet | ||
| key={`${workspaceSheetTab}:${workspaceRepository.id}`} | ||
| identityPubkey={identityQuery.data?.pubkey} | ||
| onOpenCommit={handleOpenCommit} | ||
| onRepositoryAdded={handleFilesAdded} | ||
| onSelectRepository={setWorkspaceRepositoryId} | ||
| project={project} | ||
| projects={projects} | ||
| repository={workspaceRepository} | ||
| tab={workspaceSheetTab} | ||
| /> | ||
| ) : null; |
There was a problem hiding this comment.
workspaceSheet is a fresh JSX element every render and flows into the memoized ChannelPane as idleAuxiliaryPanel, so while the sheet is open any parent render (query cache updates, local state) defeats React.memo(ChannelPane) and re-renders the whole message timeline behind the drawer — the exact unstable-prop gotcha the repo docs call out. Its inputs are all stable callbacks/ids, so wrapping the construction in React.useMemo restores the memo boundary.
🤖
There was a problem hiding this comment.
Fixed in 2e0fe69: the conditional workspace sheet element is memoized with its complete dependency set, preserving the downstream ChannelPane memo boundary.
| if (homeChannel) { | ||
| const alreadyMember = homeChannel.memberPubkeys.some( | ||
| (pubkey) => | ||
| normalizePubkey(pubkey) === normalizePubkey(selectedAgent.pubkey), | ||
| ); | ||
| if (!alreadyMember) { | ||
| await addChannelMembers({ | ||
| channelId: homeChannel.id, | ||
| pubkeys: [selectedAgent.pubkey], | ||
| role: "bot", | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
The bot member-add is gated on homeChannel being set, not on the message actually targeting it. restoreProjectsAgentConversation can restore a 1:1 DM while homeChannelId is set, and submitProjectAgentMessage then sends to the DM — in that case this block silently adds the agent as a bot member of the project home channel as a side effect of a DM follow-up. Guard on the resolved target, e.g. only add when !conversation || conversation.channel.id === homeChannel.id.
🤖
There was a problem hiding this comment.
Fixed in 2e0fe69: bot membership is now added only when the resolved existing conversation is the project home channel (or no conversation exists yet).
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
thomaspblock
left a comment
There was a problem hiding this comment.
Cassandra security re-review of 2e0fe6999 — one tenant-scope blocker remains
Matt's three reported defects are correctly fixed: UTF-8 names now truncate to a 128-byte prefix at a character boundary in both callers, the CJK regression passes, the workspace-sheet element has a complete useMemo dependency set, and an existing DM no longer triggers project-home membership.
P1 — Project-home membership is not bound to the captured relay/signer scope (confidence 75)
Evidence
desktop/src/features/projects/ui/ProjectAgentChatPanel.tsx:167-171:await addChannelMembers({ channelId: homeChannel.id, pubkeys: [selectedAgent.pubkey], role: "bot", });
- The immediately following agent start/open/send path passes the captured
relayScopeand signer atProjectAgentChatPanel.tsx:183-201, but this membership mutation passes neither. desktop/src/shared/api/types.ts:88-92exposes no expected relay/signer fields onAddChannelMembersInput.desktop/src-tauri/src/commands/channels.rs:533-559accepts only channel/pubkeys/role and calls unscopedsubmit_event(builder, &state).desktop/src-tauri/src/relay/submit.rs:71-77resolves the currently active relay and signing keys when called.
Trigger scenario
- The panel captures project home channel A, relay A, and signer A.
- The user submits while a community or identity switch races the Tauri membership command (or the switch occurs after this unscoped await starts).
add_channel_membersresolves the then-active workspace and signs/publishes the captured channel UUID there; if that UUID exists in relay B, the bot membership is mutated in the wrong tenant. Even without a collision, the wrong-relay failure occurs outside the later fail-closed path.submitProjectAgentMessagethen checksexpectedRelayUrl/expectedSignerPubkeyand fails closed, leaving membership as a partial side effect even though no project message was sent.
This contradicts the nearby invariant that “every relay side effect” is scope-bound. Extend the membership command/API with expected relay and signer parameters and perform the same assert/captured-target submission used by the message path, or move the membership operation into a scoped orchestration boundary. The channel-target guard fixes Matt's DM case but not this tenant race.
Verification: reviewed exact incremental diff 7d6c4abce..2e0fe6999; git diff --check passed; independently ran the new CJK test at exact head (1 passed). Report-only; no branch mutation.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Cassandra security/adversarial re-review —
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — request changes
Reviewed base e23632941331502c0330e51d407e667bea26ef57 through exact head adff0acff7f5fbe61795aad6e3a5670e1300b45d against VISION.md, VISION_PROJECTS.md, TESTING.md, NIP-MP authority semantics, the ACP resolver/cache, CLI project/repository routing, relay query execution, and the changed Desktop project journeys.
P1 — reject a same-slug repository bound outside the new project home
crates/buzz-cli/src/commands/projects.rs:372-380 adds the coordinate returned by ensure_default_create_repo to the project and publishes it. But ensure_default_create_repo at projects.rs:665-670 returns any caller-owned same-ID repository without checking its buzz-channel. This omits the binding invariant already enforced for issue-time reuse at crates/buzz-cli/src/commands/project_channel.rs:171-182,197-205.
If the caller already owns repo app bound to channel A (or with no binding), buzz projects create app --channel B reports success and publishes a channel-B project containing the channel-A repo. ACP correctly refuses to treat that member as authoritative for B, and later channel-scoped issue routing conflicts rather than targeting the advertised project. Apply the same binding check before reuse (or fail before project publication), with a regression for mismatched and absent bindings.
P1 — do not permanently cache project absence or mutable project metadata
crates/buzz-acp/src/pool.rs:598-611 caches Option<PromptProjectInfo> indefinitely, including None; fetch_project_home_for_channel explicitly treats empty as final at pool.rs:2986-2989. There is no TTL, relevant-event invalidation, or session-boundary refresh.
If ACP resolves channel C before its project/repository publication completes, it caches None. Creating the project later cannot add the Project block to any later turn/session in that process until restart. Positive entries likewise retain obsolete project names/default repositories. Use bounded freshness or invalidate on relevant project/repository events, and regress None → project resolution without restarting ACP.
P1 — do not treat a truncated global query page as authoritative absence
The project-home paths issue one-shot 1,000-row queries: ACP at crates/buzz-acp/src/pool.rs:2993-3003, CLI projects at crates/buzz-cli/src/commands/projects.rs:74-86, and CLI repositories at crates/buzz-cli/src/commands/project_channel.rs:160-168. The relay clamps the SQL query to 1,000 at crates/buzz-relay/src/handlers/req.rs:957-960, while non-single-letter custom-tag matching occurs only after that limited read at crates/buzz-relay/src/api/bridge.rs:1308-1315; the SQL tag pushdown at req.rs:1001-1044 covers #p/#d, not #buzz-channel.
Once more than 1,000 newer visible heads exist, an older authoritative project or repository can be excluded by unrelated global rows. ACP then resolves (and permanently caches) no project; CLI channel routing can say the channel is not a project home or take fallback behavior. BuzzClient already exposes composite-cursor pagination at crates/buzz-cli/src/client.rs:683-729. Page to a defined exhaustive/bounded result with explicit truncation failure, or add indexed relay-side support; a full page cannot prove absence. Add coverage that places the authoritative head beyond page one.
Validation and residual risk
- Clean exact-head
cargo test -p buzz-cli -p buzz-acppassed (809 + 9 + 374 tests; one doc test ignored); clippy for both packages passed with-D warnings. - Desktop unit suite passed 5,451/5,451; Desktop check/typecheck passed; five targeted create/open/retry/lost-ack/sidebar project journeys passed.
- Keyboard Enter/Space and
aria-pressedbehavior, a 900×720 viewport at 24px root text, control visibility, and horizontal overflow were probed successfully in the browser artifact. No additional source-level product/accessibility blocker was found. - All applicable exact-head GitHub checks are green. Those checks do not exercise the three failure shapes above.
- Residual product risk: no exact-head native Tauri/WebView journey or native receipt was available for the materially changed navigation/layout, so native focus, OS input, and shell resizing remain unproven.
- The 1,001-head starvation case was established from the client/relay control flow, not reproduced against a seeded live relay.
Gauge correctness/testing verification of Jude's three P1s — all confirmed at exact head
|
Cassandra security/adversarial response to Jude's review — exact head
|
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Cassandra re-review —
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES
Reviewed: f6e6617a9dcc2308d5039f8afaab974b49fb9577..b4e1d8c13c795dc799f6d8dfaee1be7fb532114b (exact head b4e1d8c13c795dc799f6d8dfaee1be7fb532114b)
Risk: high — project discovery decides ACP authority/context and CLI routing under community-wide relay cardinality. The new-head delta repairs one Desktop E2E navigation step but leaves both prior production defects unchanged.
P1 — expired cached absence becomes ordinary-channel context after refresh failure
ChannelInfoResolver::resolve treats Ok(None) as proven non-project context (crates/buzz-acp/src/pool.rs:604-617). After the 30-second TTL expires, lookup_project returns every stale cache value when refresh fails (pool.rs:624-644). If the stale value is None, an earlier legitimate miss followed by project creation and a relay timeout/error therefore emits ordinary-channel context precisely when project authority is indeterminate.
The checked-in failure regression covers only an empty cache (pool.rs:8565-8614); the expired-negative test at pool.rs:8500-8563 covers successful refresh. A review-only causal mutation seeded expired CachedProjectInfo { value: None } before the malformed-response refresh. cargo test -p buzz-acp failed_project_lookup_through_resolve_cannot_become_ordinary_context -- --nocapture then failed at the intended fail-closed assertion (exit 101), establishing the uncovered branch. Source was restored and the checkout was clean.
Author action: on refresh error, never return stale None as authoritative absence. Propagate ProjectLookupError so resolve() fails closed. If stale Some(project) is intentionally retained for availability, test that policy separately. Add the causal expired-negative → failed-refresh regression through resolve(), and mutation-prove removing the guard fails it.
Verification owner: author for fix/regression; :bot: Jude’s code review agent for exact-new-head mutation and full buzz-acp rerun.
P1 — CLI applies its 10,000-event bound before channel scoping
fetch_projects_for_channel queries community-global kind 30621, calls query_all_bounded(..., 10_000), and only afterward filters buzz-channel client-side (crates/buzz-cli/src/commands/projects.rs:82-96). More than 10,000 unrelated project heads can therefore exhaust or hide valid target-channel discovery. Relay-side #buzz-channel filtering already exists, and ACP uses it; this CLI path omits it.
A review-only production-call-site test captured the exact request from fetch_projects_for_channel and required #buzz-channel. It failed with body [{"kinds":[30621],"limit":500}] at this head. The test was removed and the tree restored clean. Existing project_channel_matching_ignores_unrelated_claims coverage proves only post-fetch matching, not relay query scope.
Author action: add "#buzz-channel": [channel] to the relay filter before query_all_bounded, retaining client filtering as defense in depth. Add a production-call-site regression proving more than 10,000 unrelated events cannot consume the bound, and mutation-prove deleting the filter fails it.
Verification owner: author for fix/regression; :bot: Jude’s code review agent for exact-new-head mutation and full buzz-cli rerun.
Exact-head evidence and residual risk
- Both independent review lanes reproduced their assigned defect against exact head; neither carried prior clearance forward.
- Full
cargo test -p buzz-cli: 377/377 passed. Fullcargo test -p buzz-acp: 812 library + 9 lifecycle tests passed.cargo check -p buzz-cli -p buzz-acpand base-to-headgit diff --checkpassed. These green suites omit the two causal failure shapes above. 73d4e2aa..b4e1d8c1changes onlydesktop/tests/e2e/project-conversation-load-failure.spec.ts:151, selecting the repository before Channels. Exact-head Desktop Smoke E2E (3) is now green, along with Unit Tests, Desktop Core, all other smoke shards, Desktop integrations, relay E2E, Rust Lint, security, and macOS build.- Windows Rust remained in progress at submission. Author action: none unless it fails PR-causally. Verification owner: CI/gate owner for terminal classification.
- No exact-head native Tauri/WebView project-switching artifact or live >10,000-project relay reproduction was produced. Those are confidence gaps, not additional author defects; deterministic production-call mutations establish the blockers.
Gauge round-6 review — head
|
## Summary - create explicit NIP-MP projects with a home channel and default repository - preserve standalone repository folding, project deletion, and deterministic repository selection - restore Template, Team, visibility, and agent settings in the project creation flow This is Part 2 of the channel-first Projects stack, following #6590. It is independently based on `main`; Part 3 adds the project-home channel surface. ## Testing - focused project collection, creation, channel, and model tests: 38/38 passed - Desktop unit suite: 5,415/5,415 passed - TypeScript, Biome, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - create listed and unlisted projects with and without templates in the first staging Desktop session - healthy signals: one home channel, one default repository, stable project coordinates, and no duplicate legacy card - failure signals: partial project creation, duplicate projects, missing default repository, or stale sidebar entries; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> # Conflicts: # desktop/src/features/channels/ui/ChannelPane.tsx # desktop/src/features/channels/ui/ChannelScreen.tsx
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Reviewed: 113a33b7e49b7173ee1767c49ef2f49c63803034..e7220100e45a14d90e637da810af20adec916de4 (exact head e7220100e45a14d90e637da810af20adec916de4)
Risk: high — this changes agent project-home authority, degraded-relay behavior, and CLI relay query/bounding contracts.
Behavior/contracts traced: ACP project-home cache expiry and refresh failure into generated agent context; CLI project lookup through creation collision checks and channel-driven issue/repository routing; relay #buzz-channel parsing into database filtering before ordering/limit.
Blocking findings
-
P1 — CLI project lookup bounds the global project set before channel matching (
crates/buzz-cli/src/commands/projects.rs:70-97).fetch_projects_for_channelsends only{"kinds":[30621]}, invokesquery_all_bounded(..., 10_000), and filtersbuzz-channellocally. The relay pushdown is activated only when the request contains singleton#buzz-channel(crates/buzz-relay/src/api/bridge.rs:1363-1378), after which the database applies containment before ordering/limit (crates/buzz-db/src/event.rs:528-532). The sibling repository lookup already uses the correct request shape (crates/buzz-cli/src/commands/project_channel.rs:163-170). With more than 10,000 accessible unrelated project heads, project creation and channel-driven issue/repository routing can fail at the global bound before finding the channel's project (crates/buzz-cli/src/client.rs:731-749,projects.rs:361-380,project_channel.rs:24-32).Author action: add singleton
"#buzz-channel":[channel]to the production project query before bounded collection. Check in a production-call regression that captures the actual/querybody, plus a causal >10k-unrelated-head or bounded relay-backed regression proving unrelated heads do not consume the channel-match budget. The test must fail when the tag is removed.Verification owner: author for patch/tests; reviewer for mutation check and full
buzz-cligate at the new exact head. -
P1 — expired cached project absence fails open when refresh fails (
crates/buzz-acp/src/pool.rs:604-617,635-645). On refresh error,lookup_projectreturns any expiredstale.value; an expiredNoneis therefore accepted byresolve()as authoritative “not a project,” producing ordinary-channel context. The checked-in tests separately cover expiredNonewith a successful refresh (pool.rs:8500-8563) and a failed lookup with no cache (pool.rs:8565-8615), but not their causal composition. A temporary exact-head regression seeded an expired cachedNone, forced refresh failure, and expectedresolve()to fail closed; production code failed it. Restricting fallback to staleSome(project)made that regression pass. In production, a channel observed as ordinary, then bound as a project home, can be downgraded back to ordinary context during relay degradation, omitting the Project block and allowing duplicate or incorrectly routed project work contrary tobase_prompt.md:39.Author action: on refresh failure, reuse only stale
Some(project); expiredNonemust remain an error soresolve()suppresses ordinary context. Check in causal tests for both policies: expiredNone+ failed refresh fails closed, while expiredSome(project)+ failed refresh retains the last authoritative project.Verification owner: author for patch/tests; reviewer for mutation proof of both branches and full
buzz-acpgate at the new exact head.
Exact-head validation
cargo test -p buzz-cli— PASS: 377 passed, 0 failed; one doc test ignored; cleane7220100e.cargo clippy -p buzz-cli --all-targets -- -D warnings— PASS; cleane7220100e.cargo test -p buzz-acp— PASS: 812 library + 9 lifecycle tests, 0 failed; cleane7220100e.git diff --check 113a33b7e...e7220100e— PASS in both independent lanes.- Causal ACP mutation: expired cached
None+ failed refresh — FAIL on production code; PASS when fallback was limited to staleSome(project); tree restored clean afterward. - Live head/base remain
e7220100e/113a33b7e; PR is mergeable but blocked. At submission, Rust Lint, Unit Tests, Security, both server cross-compiles, DCO, Desktop Release Candidate, macOS Desktop Build, and both main Docker image builds are green. Desktop Core/E2E, Windows Rust, and one push-gateway image job remain in progress.
Manual/native evidence: none. No user-visible native journey was required to establish either source-level blocker.
Residual risk: no live relay was seeded with >10k unrelated project heads; the CLI failure follows deterministically from the literal request, bounded collector, and production callers, but the missing checked-in causal regression is part of the requested fix. In-progress CI remains a confidence gap, not an additional author defect. Any new head invalidates this review.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
## Summary - resolve project homes consistently from both channel and project routes - preserve repository, file, commit, task, and review context when expanding workspace sheets - add encrypted owner-reviewed project-channel requests for managed agents This is Part 5 of the channel-first Projects stack, based on #6595. The final part contains overview, aggregation, and visual polish. ## Testing - focused request parsing, project route, home-channel, workspace-sheet, and sidebar tests: 20/20 passed - ACP tests: 816 passed; CLI tests: 370 passed - Desktop unit suite: 5,444/5,444 passed - E2E-mode Desktop build passed - Rust clippy, TypeScript, Biome, and differential file-size checks passed - full pre-push gate passed ## Post-Deploy Monitoring & Validation - open the same project home from Channels and Projects, then exercise file/work-item deep links and an agent add-channel request - healthy signals: identical home surface, preserved repository context, one approval dialog, and no channel before approval - failure signals: normal channel fallback, wrong repository detail, duplicate requests, or unreviewed channel creation; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Cassandra exact-head adversarial/security review —
|
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
|
Gauge (correctness/testing) — exact-head review at
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES at b539ee9dc432499523d60f1a4c1b90101d97e8da (base f24971033178926153b49d320bd876d15d9cb2bf).
P1 — Indeterminate project lookup still reaches the agent as ordinary-channel context
The cache branch is improved: ChannelInfoResolver::lookup_project now returns an error for an expired cached absence when refresh fails, while retaining a stale known project (crates/buzz-acp/src/pool.rs:620-658). However, that fail-closed outcome is discarded before the actual agent boundary:
ChannelInfoResolver::resolve()collapses project-refresh failure toNone(pool.rs:604-615).run_prompt_taskaccepts thatNoneand continues toformat_prompt(pool.rs:2381-2452).format_context_hintsrendersScope: channelwith the bare channel UUID and no Project block whenchannel_infoisNone(crates/buzz-acp/src/queue.rs:1347-1359,1425-1439).- The checked-in unresolved-metadata test explicitly pins this ordinary-channel rendering (
queue.rs:5523-5544).
Consequently, if an ordinary-channel negative was cached, the channel later became a project home, the cache expired, and relay refresh failed, the agent would still receive actionable ordinary-channel context. It can create a duplicate project or route tasks/repositories/files to the wrong target. The regression at pool.rs:8565-8653 proves the resolver result only; it does not protect the production prompt boundary.
Author action: preserve a distinct indeterminate-project outcome through run_prompt_task and abort/requeue the turn, or otherwise guarantee that no actionable ordinary-channel prompt is delivered while project identity is indeterminate. Add a production-boundary regression proving expired None plus failed refresh never reaches ACP as Scope: channel without Project context. Mutation-prove it by restoring the current None → ordinary prompt path.
Verification owner: author for the patch and causal regression; reviewer for mutation proof and a full cargo test -p buzz-acp rerun on the next exact head.
Re-review disposition and evidence
Two independent traces confirmed the same production-boundary defect. The prior CLI query issue is cleared: the production request now includes singleton #buzz-channel before bounded pagination (crates/buzz-cli/src/commands/projects.rs:122-150), and removing the tag causes both the request-body and bound=1 decoy/target regressions to fail (projects.rs:1008-1089). The stale-negative/stale-positive resolver branches are also mutation-sensitive at exact head. Review of the owner-review queue found no separate concrete spoofing, wrong-project, duplicate-request, boundedness, or accessibility defect in the searched observer/buffer/hook/queue/dialog paths.
Exact-head evidence:
cargo test -p buzz-cli: 381 passed, 0 failed.- Full Desktop unit suite: 5,579 passed, 0 failed.
cargo clippy -p buzz-acp -p buzz-cli --all-targets -- -D warnings: passed.git diff --check f2497103...HEAD: passed.- Exact-head GitHub Unit Tests, Rust Lint, Security, relay/backend/Desktop E2E, cross-platform builds, DCO, and release-candidate checks are green.
Confidence gap, not author action: reviewer-local full buzz-acp runs intermittently failed unchanged idle/keepalive timing tests; correctly filtered reruns passed and exact-head CI Unit Tests is green. Reviewer/tooling owns rerunning the full package after the required patch. Native Desktop/AX interaction was not required to establish this source-level blocker and remains reviewer-owned verification rather than author rework.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
|
Addressed the indeterminate-project prompt-boundary finding at exact head
Verification at exact committed SHA
|
|
Gauge re-review at exact head Verified (confidence 100, all at the exact SHA in a detached worktree):
Residual (non-blocking, confidence 75 — traced, not executed): |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES at 710bcb4c8d94469db1e4e76d57d6345a28d0e015 (base 52621c09bea503f4d5860030dfabfaf9ade71bfa).
P1 — indeterminate project authority still crosses the new-session agent boundary
The new guard stops the triggering event prompt, but project authority is resolved earlier for a new channel session and every error is collapsed to unresolved/DM context (crates/buzz-acp/src/pool.rs:1048-1057,1980-1987). Execution then creates the ACP session (pool.rs:2021-2043) and, when configured, sends BUZZ_ACP_INITIAL_MESSAGE as an actionable user turn (pool.rs:2191-2239) before the guarded lookup aborts and requeues the event batch (pool.rs:2376-2398). session/new also receives system/origin context derived from that collapsed result.
The checked-in boundary regression pre-seeds "live-session" (pool.rs:8720-8723), so it skips this path. Strengthening it to start without a session and with initial_message caused the exact-head production code to fail the no-ACP-wire assertion (rc=101): the capture existed after ACP received session creation and the initial turn. The later requeue cannot recall agent work already performed.
Author action: resolve channel/project authority once before session creation or initial-message delivery and thread the typed result through session setup and event formatting. When identity is indeterminate, send neither session/new nor session/prompt; requeue the batch. Extend the production-boundary regression to cover no pre-existing session plus configured initial message, and mutation-prove that collapsing the early error makes it fail.
P1 — local relay lookup failure is misclassified as ACP protocol corruption
The guarded path wraps project-resolution failure as AcpError::Protocol (crates/buzz-acp/src/pool.rs:2379-2397). The main loop defines all Protocol errors as corrupted ACP transport and shuts down/respawns the healthy agent (crates/buzz-acp/src/lib.rs:4178-4187,4203-4239). Three relay lookup failures in 60 seconds can therefore open the slot circuit for five minutes (lib.rs:1419-1428,1486-1495) while the batch is separately requeued (lib.rs:3919-3924,4002-4016). A degraded relay can churn healthy processes, discard sessions, and drain the pool for a failure that occurred before ACP delivery.
Author action: represent indeterminate project resolution as a local/application outcome that requeues without invalidating or respawning ACP. Add a handle_prompt_result-level regression proving the same agent/session is retained, SlotCircuit is not incremented/opened, and the batch remains retryable. Keep the wire-boundary regression.
Integrated re-review evidence
- Both independent lanes pinned the clean exact head; live GitHub head/base were rechecked immediately before submission.
- Full
cargo test -p buzz-acp: 813 library + 9 lifecycle passed, 0 failed. - Full
cargo test -p buzz-cli: 381 passed, 0 failed; one doc test ignored. - Existing boundary mutation (
Err -> None -> ordinary prompt) produced the intended failure; sources were restored and trees clean. - New-session + initial-message strengthening failed at the no-ACP-wire assertion as described; source restored and tree clean.
git diff --check 52621c09...HEAD: passed.- Exact-head required GitHub checks are green, including Unit Tests, Rust Lint, Security, relay/backend/Desktop E2E, macOS/Windows/cross-compiles, Desktop Release Candidate, and DCO.
- Reviewed CLI bounded query, relay tenant pushdown, project/repository identity and authority, stale cache policy, owner-review request/approval flow, dedupe/cap, and dialog semantics; no additional author-actionable defect was found in those searched paths.
Verification owner: author for both fixes and causal regressions; reviewer for both mutation proofs and full buzz-acp package validation on the next exact head.
Confidence gap, not author action: no native Desktop/AX run was performed. The exact-head fix delta is Rust-only, both blockers are established at ACP process/wire boundaries, and exact-head Desktop CI is green. Any new head invalidates this verdict.
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
|
Addressed Jude round 9 at exact head
Causal mutation evidence:
Verification:
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES
Reviewed: 4f529a45f219f55bc6038f304cd69e3f51ec0533..8428b3238cc5cecea8a6b82451362e0b1d57947f (exact head 8428b3238cc5cecea8a6b82451362e0b1d57947f)
Risk: high — this changes agent project authority and retry/session lifecycle, CLI project creation/routing, and an owner-approved channel-creation flow.
P1 — owner review hides the requested temporary-channel lifetime
The owner-review dialog says to “Review the details before creating it,” but renders only the requested name, description, visibility, and optional template (desktop/src/features/projects/ui/ProjectChannelRequestDialog.tsx:27-68). The agent-facing command accepts --ttl (crates/buzz-cli/src/lib.rs:1337-1357), Desktop preserves the value in the reviewed request (desktop/src/features/projects/projectChannelRequest.ts:41-64,68-79), and approval forwards it into channel creation (desktop/src/features/projects/useProjectChannelRequests.ts:128-159; useAddProjectChannel.ts:81-87). The product’s existing lifecycle copy defines this setting as automatic cleanup after inactivity (desktop/src/features/channels/lib/ephemeralChannel.ts:143-168).
An owner can therefore approve what appears to be an ordinary persistent project channel while the unseen request makes it ephemeral. The channel and its project context can later disappear on a lifecycle the owner was never asked to review. This defeats the stated owner-review boundary; the TTL is a material persistence property, not incidental metadata.
Author action: show a friendly temporary-channel lifetime and its automatic-cleanup consequence in ProjectChannelRequestDialog whenever ttlSeconds is present. Add a dialog-level regression proving an agent-requested TTL is visible before approval (and omitted or clearly persistent when absent).
Verification owner: author for the UI and regression; :bot: Jude’s code review agent for exact-new-head product review and affected Desktop gate.
Resolved prior blockers
Both prior ACP blockers are fixed at this head. run_prompt_task resolves project authority once before core/canvas work, session/new, or initial_message, and threads that result into session metadata and prompt formatting (crates/buzz-acp/src/pool.rs:1900-1925,2012-2016,2405-2466). Indeterminate relay state is now a typed local outcome (pool.rs:513-520) whose Queue-mode batch enters bounded retry, while the healthy agent/session is returned without respawn or slot-circuit mutation (crates/buzz-acp/src/lib.rs:3919-4029,4205-4214). The production-boundary regression starts without a session, configures an initial message, preserves the exact event, and proves no ACP wire capture was created (pool.rs:8683-8801); the handler regression preserves the session, batch, agent slot, and circuit state (lib.rs:8259-8344). Independent causal mutations collapsing the error or restoring AcpError::Protocol failed the boundary regression as intended.
Exact-head validation
cargo test -p buzz-acp— PASS locally: 814 unit + 9 lifecycle tests; doc tests passed. I independently repeated the clean full package run at matching HEAD; a second review lane mutation-proved both repaired boundaries.cargo test -p buzz-cli— PASS locally: 381 tests; one doc test ignored.- Desktop full unit suite — PASS in the product lane: 5,638 tests; TypeScript typecheck passed.
git diff --check 4f529a45f219f55bc6038f304cd69e3f51ec0533..HEAD— PASS.- All exact-head required GitHub checks are terminal green, including Unit Tests, Rust Lint, Security, Desktop core/smoke/integration/relay, relay/backend E2E, macOS/Windows/cross-compiles, release candidate, and DCO.
Confidence gaps — no additional author action: no native Tauri/AX focus, keyboard, or screen-reader journey was observed, and no separate live degraded-relay/ACP process journey was run. Static AlertDialog semantics and pending-state disabling appear sound; exact production-seam wire/handler tests plus causal mutations cover the repaired ACP failure shape. These remain reviewer/tooling residual risks, not extra blockers.
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Reviewed: 4f529a45f219f55bc6038f304cd69e3f51ec0533..8428b3238cc5cecea8a6b82451362e0b1d57947f (exact head 8428b3238cc5cecea8a6b82451362e0b1d57947f)
Risk: high — this crosses managed-agent session/prompt ordering, relay-derived project authority, retry/session lifecycle, CLI creation, and an owner-approved channel-persistence boundary.
Behavior/contracts traced: channel/project authority resolution → ACP session/new / configured initial_message / prompt formatting; typed indeterminate outcomes → bounded batch requeue → agent/session retention and slot-circuit state; CLI request schema → Desktop parsing → owner-review dialog → project-channel creation; relay/CLI ownership and project-home matching.
Blocking finding
desktop/src/features/projects/ui/ProjectChannelRequestDialog.tsx:34-68 promises the owner a review of the requested channel details but renders only name, description, visibility, and optional template. The accepted request also carries ttlSeconds (desktop/src/features/projects/projectChannelRequest.ts:45-77), and approval forwards that value into channel creation (desktop/src/features/projects/useProjectChannelRequests.ts:150-158). The owner can therefore approve a temporary channel without being shown either its lifetime or that inactivity will automatically remove it. That is a material persistence property, not decorative metadata; approval is currently uninformed and the resulting project context can disappear on an undisclosed lifecycle.
Author action: display a friendly temporary-channel lifetime/automatic-cleanup description in the owner-review dialog whenever ttlSeconds is present, and add coverage proving that value is visible before approval.
Verification owner: author for the UI and regression coverage; reviewer for exact-head verification.
Prior blockers cleared
The prior ACP findings are resolved. Authority now resolves once before core/canvas work, session/new, configured initial_message, or prompt delivery; one resolved value feeds session metadata and final formatting. Indeterminate local relay/project state is represented as PromptOutcome::ProjectContextIndeterminate, preserves the retryable batch, and returns the same healthy agent/session without entering transport respawn or slot-circuit mutation. The strengthened regressions cover a no-session/configured-initial-message boundary and handler state retention.
Validation
At this exact clean head:
cargo test -p buzz-acp: PASS — 814 unit + 9 lifecycle tests; doc tests passed.cargo test -p buzz-cli: PASS — 381 tests; one doctest ignored.- Desktop tests: PASS — 5,638 unit tests; TypeScript typecheck passed.
- Causal mutation checks: collapsing the early authority error and remapping it to
AcpError::Protocoleach made the boundary regression fail as intended; source was restored and the tree was clean. - Live GitHub required checks were refreshed immediately before this review: all terminal checks were
SUCCESSor expectedSKIPPED; PR wasMERGEABLE.
Manual/native evidence: no native focus, keyboard, or screen-reader journey was observed. Static AlertDialog semantics and pending-state disabling appear reasonable.
Residual risk: native accessibility/interaction remains a reviewer/tooling confidence gap, not additional author action. No other unresolved author-actionable defect was found in the named systems, relay/CLI, retry, owner-review, and project-channel creation surfaces searched.
— :bot: Jude’s code review agent
Summary
This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations.
Testing
cargo fmt --all -- --checkcargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warningscargo test -p buzz-cli -p buzz-acp— 1,184 tests passed, 1 doc test ignoredPost-Deploy Monitoring & Validation