Skip to content

feat(projects): make the home channel the project surface - #6595

Merged
thomaspblock merged 1 commit into
projects-channel-first-pt1-agent-clifrom
projects-channel-first-pt4-project-home
Aug 23, 2026
Merged

feat(projects): make the home channel the project surface#6595
thomaspblock merged 1 commit into
projects-channel-first-pt1-agent-clifrom
projects-channel-first-pt4-project-home

Conversation

@thomaspblock

Copy link
Copy Markdown
Contributor

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

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra security/adversarial review — original head c6fb3d67762a23b30cc20604ec64d7f1ebe5b66c

Verdict: needs work (P1). This report is against the published PR head above. The branch is also stale/dirty against #6594; the implementer is updating the stack, so this finding must be re-verified at the new head.

P1 — Direct project routing bypasses authoritative home classification

Confidence: 100

Evidence:

  • desktop/src/features/projects/ui/ProjectDetailScreen.tsx:678-680
    const showChannelHome =
      Boolean(project.projectChannelId) &&
      !wantsProjectRepositorySurface({
  • desktop/src/features/projects/ui/ProjectChannelHome.tsx:106-109
    const homeChannel =
      channelsQuery.data?.find(
        (channel) => channel.id === project.projectChannelId,
      ) ?? null;
  • desktop/src/features/projects/ui/ProjectAgentChatPanel.tsx:158-168
    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",
        });

Trigger: a hostile signer publishes an otherwise valid zero-member project event whose buzz-channel names a real channel the victim can see. The project read model retains that asserted ID. Opening /projects/<hostile-project> enters ProjectChannelHome solely because the ID is non-null, resolves the victim's channel from the ordinary channel cache, and renders its timeline/composer under the hostile project. If an administrator then invokes the project-agent flow, the page may add the selected bot to that unrelated channel before sending.

This reintroduces the authority bypass fixed in #6594: project-home status must require a live member repository bound to the same channel and authorization of the project signer as repository owner/maintainer. Part 4 applies that predicate to channel chrome but not to the direct project route.

Suggested fix: gate showChannelHome (or ProjectChannelHome) with the shared authoritative-home predicate for this exact project/channel, not Boolean(project.projectChannelId). If proof is absent, fail closed to the repository/unavailable surface and do not pass a homeChannel into project-agent actions. Add hostile bare-assertion and mismatched/unauthorized binding route tests, including that no member-add action is reachable.

Residuals / incomplete verification

  • Current PR is stale against feat(channels): support project-home channel surfaces #6594 (merge-base 34d4b7917; base head b98580790) and GitHub reports DIRTY; final review must run on the conflict-resolved head.
  • Focused tests could not run in the fresh review worktree because dependencies were not installed (ERR_MODULE_NOT_FOUND: typescript). This is an environment limitation, not evidence of a product test failure. Existing CI at the original head is red and stale.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Gauge review — PR #6595, head c6fb3d67762a23b30cc20604ec64d7f1ebe5b66c

Verdict from correctness/testing lenses: needs-work. Findings (confidence anchors per buzz-review-panel):

Actionable

1. [100] Deterministic smoke failure: PR removes the "Overview" tab but entity-link-recipient-cards.spec.ts still expects it.
The diff deletes the tab (ProjectWorkspaceTabList.tsx: removed <TabsTrigger aria-label="Overview" ... value="overview">, replaced with a project-workspace-back button), and updates projects-v3-screenshots.spec.ts accordingly — but not entity-link-recipient-cards.spec.ts:582:
await expect(page.getByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true") → CI: "waiting for getByRole('tab', { name: 'Overview' }) … element(s) not found", failed 3/3 retries (run 32611165753 job 97124476278, Smoke E2E (2)).

2. [75] Deterministic smoke failure: PR-added openBuzzProject step times out.
projects-v3-screenshots.spec.ts:64 (added by this PR): await page.getByTestId("project-home-context-repo-buzz").click();locator.click: Test timeout of 30000ms exceeded, 3/3 retries, in all three tests that call openBuzzProject (lines 187/243/265; job 97124476342, Smoke E2E (4)). The testid renders only inside ProjectHomeContextPanel.tsx:362, which mounts only when ProjectDetailScreen.tsx:678-679 showChannelHome is true (Boolean(project.projectChannelId)). The mock "buzz" project's home surface apparently doesn't reach that state in CI. Needs local repro against the e2e bridge.

3. [75] Reachable inert-timeline bug — the #6594 trip-wire is now live.
ProjectChannelHome.tsx:263 passes idleAuxiliaryPanel={workspaceSheet}, and the sheet state does not close when a thread opens. In ChannelPane.tsx:467-469, useFocusIdleDrawer = useSplitAuxiliaryPane && Boolean(idleAuxiliaryPanel) && Boolean(onCloseIdleAuxiliaryPanel) — no thread check. With the default thread view mode "split" (threadViewModePreference.ts:21), opening a thread while the Files sheet is open gives: useFocusThreadDrawer false, useFocusIdleDrawer true → channelIsCovered true (ChannelPane.tsx:471-476) → the main timeline section gets inert (ChannelPane.tsx:576) with no covering drawer rendered (the aux slot shows the thread side-by-side, ChannelPane.tsx:818). The channel timeline becomes non-interactive. Also: external focused-thread close requests route to onCloseIdleAuxiliaryPanel instead of onCloseThread (ChannelPane.tsx:472-475).

4. [100] Branch is based on a stale Part 3 — Cassandra's P1 authority fix is absent from this tree.
Merge-base with pt3 is 34d4b79, predating b985807. This tree still ships the permissive predicate: projectHomeChannel.ts: return projects.some((project) => project.projectChannelId === channelId); — no repo-authority check, and the 5 authority tests are missing. No textual conflict (pt4 doesn't touch the file), but branch CI and local suites validate the unfixed code. Merge current pt3 into pt4.

Verified clean

  • Full desktop unit suite at c6fb3d6: 5,439 pass / 0 fail (local, git rev-parse HEAD confirmed).
  • Merge commit c6fb3d6 is an exact clean merge (merge-tree of parents reproduces its tree).
  • restoreProjectsAgentConversation home-channel restore path checks channel.isMember before honoring a stored pointer; DM contract untouched for non-home channels.
  • useAddProjectChannel: stale-head guard (liveHead.created_at > input.project.createdAt → refuse) and unsupported-relay error path both present.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra adversarial re-review — needs work

Reviewed exact head 32a8f67e843c4bbcd593f6155863637f77530dcc independently through security/adversarial lenses. The earlier project-home confused-deputy finding is fixed: direct routing now requires hasAuthoritativeHomeBinding(project), and the idle drawer yields to real/skeleton thread surfaces.

P1 — hostile same-channel repository is silently adopted and re-signed by the project owner (confidence 100)

Evidence:

  • desktop/src/features/projects/lib/projectCollection.ts:26-31
    const channelHost = repository.channelId
      ? explicitProjects.find(
          (project) => project.projectChannelId === repository.channelId,
        )
      : undefined;
    if (channelHost) return channelHost;
  • desktop/src/features/projects/lib/projectCollection.ts:59-63
    return project.repositories.filter(
      (repository) =>
        !signed.has(repository.repoAddress) &&
        repositoryBelongsOnProjectHome(project, repository),
    );
  • desktop/src/features/projects/lib/projectCollection.ts:42-46
    return Boolean(
      (repository.channelId &&
        repository.channelId === project.projectChannelId) ||
        (repository.owner === project.owner && repository.dtag === project.dtag),
    );
  • desktop/src/features/projects/useHealProjectHomeRepositories.ts:45-52
    const pending = homeRepositoriesToBind(project, signedAddresses);
    for (const repository of pending) {
      ...
      await mutateAsync({ project, repository });
    }

Trigger: an attacker publishes a repository announcement whose buzz-channel names a victim project's legitimate home channel. absorbStandaloneProjectRepositories folds that attacker-owned repository into the victim project's in-memory repository list solely on channel equality. When the actual project owner opens Project Home, useHealProjectHomeRepositories treats the absorbed repository as repairable and publishes a new owner-signed project head containing the attacker's repository address. No repository-owner equality, maintainer authorization, or explicit owner confirmation is required.

This recreates an authority-propagation confused deputy one layer downstream from the fixed route predicate: hostile metadata cannot route the project anymore, but it can induce the owner to bless unrelated repository metadata. The automatic heal should use the same owner/maintainer authority rule as hasAuthoritativeHomeBinding, or require an explicit user action.

P2 — stale-head rejection leaves a newly created, unlinked channel behind (confidence 100)

Evidence: desktop/src/features/projects/useAddProjectChannel.ts:55-76

const targetOwner = input.project.owner.toLowerCase();
const channel = await createChannel({
  channelType: "stream",
  description: input.description,
  name: input.name,
  ttlSeconds: input.ttlSeconds,
  visibility: input.visibility,
});

const liveHeads = await fetchEvents({ ... });
...
if (liveHead.created_at > input.project.createdAt) {
  throw new Error(
    "This project was updated by another session while you were working. Refresh and try again.",
  );
}

Trigger: another session updates the project after this screen loaded. Clicking Add channel successfully creates the channel first, then the stale-head guard throws before publishing the project binding. The UI reports failure, but the side effect persists as an unrelated/orphan channel; retrying can create additional orphans. Fetch and validate the live head before channel creation, then retain the existing pre-publication recheck or provide compensating cleanup.

Coverage / clean checks

  • Traced project enumeration → standalone-repository absorption → Project Home auto-heal → owner-signed attach mutation.
  • Traced Add Channel authorization UI, live-head patch construction, owner/owned-agent publication, cache updates, and template side effects.
  • Traced Project Home routing and agent-channel action. With authoritative routing in place, homeChannel no longer derives from a bare hostile project assertion; the earlier unrelated-channel bot-add scenario is closed.
  • Checked hostile metadata bounds/validation, repository/channel context-rail routing, no-tab Overview/Back behavior, and the idle-drawer/thread guard.
  • git diff --check clean at the exact head.

Residual verification

Fresh GitHub CI run 32648140542 is still in progress for Desktop Core, build, relay E2E, and all four smoke shards. This review does not treat pending remote CI as passing.

@thomaspblock
thomaspblock marked this pull request as ready for review August 23, 2026 15:31
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra follow-up at 9b72d3e7217ef0a34be23d5e14d9b783cc667ed6needs work

The hostile repository absorption/healing blocker is fixed: channel-based adoption now requires repository-owner equality or a case-normalized maintainer grant to the project owner, and both absorption and healing selection have negative regressions.

The stale-before-click orphan case is also fixed, but moving the only live-head read before channel creation opens a time-of-check/time-of-use lost-update race.

P1 — concurrent project update during channel creation can be overwritten (confidence 100)

Evidence: desktop/src/features/projects/useAddProjectChannel.ts:57-85 at 9b72d3e7:

const liveHeads = await fetchEvents({
  authors: [targetOwner],
  kinds: [KIND_PROJECT_ANNOUNCEMENT],
  "#d": [input.project.dtag],
  limit: 1,
});
const liveHead = liveHeads[0];
...
if (liveHead.created_at > input.project.createdAt) {
  throw new Error(...);
}

const channel = await createChannel({ ... });

const templates = buildProjectRelatedChannelPatchTemplate({
  channelId: channel.id,
  liveHead,
  ownerPubkey: targetOwner,
});

There is no second live-head fetch after the awaited createChannel and before publishing the copied project template.

Trigger: session A passes preflight and waits for channel creation. Session B publishes a project update (for example, adds a repository or another related channel). A resumes, constructs its replacement event from the old liveHead, and publishes it with a fresh timestamp. Because the addressable latest event wins, A can erase B's tags; same-second timestamps can also make the winner depend on event-id ordering. The new tests cover only heads already stale/missing before createChannel, not an update that lands during it.

Preserve the early preflight so known-stale clicks do not create orphans, but re-fetch/compare immediately after channel creation before constructing the replacement event. If that second guard fails, compensate by deleting/archiving the just-created channel (or use a relay-side conditional write/transaction) so the original orphan concern does not return.

Verification

  • Read the full four-file fix diff and traced both mutation publication paths.
  • Ran the complete Desktop unit suite locally at exact head: 5,448 passed, 0 failed.
  • git diff --check clean.
  • Fresh GitHub CI run 32648804451 remains in progress; remote CI is not yet evidence for this head.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Gauge re-review — correctness / testing / reliability, head 9b72d3e7217ef0a34be23d5e14d9b783cc667ed6

Prior findings — all four verified fixed

  1. [fixed] entity-link-recipient-cards.spec.ts:582 no longer asserts the removed Overview tab; it now asserts page.getByTestId("project-workspace-back") is visible — the replacement contract Carto and I asked to be encoded.
  2. [fixed] The impossible project-home-context-repo-buzz click is removed from openBuzzProject (projects-v3-screenshots.spec.ts). Coverage of the real home rail is preserved by project-commit-detail.spec.ts (:687, :621, :654), so removal does not orphan the surface.
  3. [fixed] Idle-drawer/thread collision: ChannelPane.tsx:470-477 now computes hasThreadSurface = Boolean(threadHeadMessage) || shouldShowThreadSkeleton and routes through shouldUseFocusIdleDrawer (ChannelPane.helpers.ts), which returns false whenever a thread surface exists. In split mode the thread takes the aux slot, the sheet unmounts (state retained), channelIsCovered stays false, no inert timeline; in focus mode useFocusDrawerPresence routes close to onCloseThread. Regression test present and passing (ChannelPane.helpers.test.mjs — "focus idle drawers yield to an open thread surface").
  4. [fixed] Merge-base with pt3 is now b985807903eb77a982707e8855318ac9b771c393 — the authority predicate and its 5 tests are in the tree. The hand-resolved conflict in project-commit-detail.spec.ts (merge 93730b76d) kept both base assertions (create-project-listing/create-project-agent); I diffed the resolution against git merge-tree auto-merge — the only delta is conflict-marker resolution, no dropped coverage.

Also verified: Cassandra's P1 route fix (ProjectDetailScreen.tsx:679-680 gates on hasAuthoritativeHomeBinding(project)), and the two newer adoption-path fixes at this head — projectCollection.ts now requires repositoryAuthorizesProjectOwner for channel-equality absorption/healing (hostile-negative + maintainer-positive tests added), and useAddProjectChannel.ts moves the live-head preflight before createChannel, with mutation-level regressions proving neither stale nor missing heads create a channel. That ordering change also closes the orphan-channel partial-write window on stale heads.

New finding

[75] The idle-drawer guard yields to threads but not to the other aux surfaces that outrank it — same inert-timeline mechanism as finding 3, rarer triggers.

shouldUseFocusIdleDrawer checks only {hasIdleAuxiliaryPanel, hasThreadSurface, hasIdlePanelCloseHandler, useSplitAuxiliaryPane}. But the aux render chain (ChannelPane.tsx:809/825/897/911/952/976) mounts channel-management, agent-session, and profile panels ahead of the idle panel, and none of their open paths clear it:

  • useChannelProfilePanel.ts:33-38handleOpenProfilePanel closes thread/agent/management but never calls onCloseIdleAuxiliaryPanel.
  • ChannelScreen.tsx handleManageChannel likewise clears thread/agent/profile only.

So: project home channel, Files sheet open, user clicks an avatar in the timeline → profilePanelPubkey set, chain renders UserProfilePanel via wrapAux (a split pane, not a covering drawer), but useFocusIdleDrawer is still true → channelIsCovered true → the timeline section gets inert (ChannelPane.tsx:583) with nothing covering it. Same class for agent-session and channel-management opens. Fix shape: extend the guard's "yield" input to any higher-priority aux surface (selectedAgent, profilePanelPubkey, channelManagementOpen), or close the sheet when those open. Confidence 75: causal chain fully quoted, not reproduced live.

Verified clean at exact head 9b72d3e72

  • Full desktop unit suite locally: 5,448 passed / 0 failed (node --test, 81 suites; git rev-parse HEAD confirmed in the same shell).
  • Delta 32a8f67e8..9b72d3e72 touches only projectCollection.{ts,test.mjs} and useAddProjectChannel.{ts,test.mjs}, so the finding-1/2/3/4 verifications above carry over untouched.
  • CI at 9b72d3e72 was still in progress at review time (Desktop Build green; Smoke shards running). The two previously-deterministic smoke failures are fixed in-tree; I'll flag if any shard fails all retries.

Scope note: negative claims above are limited to the files and paths named; I did not re-audit the pt3 base beyond confirming b985807 ancestry.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra follow-up at 8857f9e6f5f3e0a83430362e84591cf81e611c6e

The reported Add Channel TOCTOU blocker is fixed.

The mutation now preserves the early no-side-effect preflight, re-fetches after createChannel, compares the exact signed event ID, and deletes the newly created channel before rejecting a missing, changed, or failed second read. The interleaving regression demonstrates all three required properties for the reported scenario: the concurrent head differs, stale replacement publication is never invoked, and the created channel is deleted.

I also traced cleanup to tauriChannels.deleteChannel → the signed delete_channel operation and found no new security/adversarial finding in this two-file delta. git diff --check is clean.

Status: my TOCTOU finding is closed at this exact head. This is not yet my final whole-PR verdict because Wrench has stated another confirmed auxiliary-surface arbitration fix will land before final handoff, and fresh GitHub CI for this head is still starting.

Residual: as with the existing project mutation architecture, a concurrent update can still occur after the final client-side read and before publication; completely eliminating that narrower interval requires relay-side conditional replacement/CAS. The submitted fix correctly closes the material channel-creation interleaving reported here and compensates its partial side effect.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra final adversarial verdict — merge-ready pending CI

Reviewed exact final head 9ba49f83bbc733cb3d983814c772164259ac813f. No open security/adversarial findings remain.

Confirmed closures

  • Project-home confused deputy: direct project routing uses the shared authoritative binding predicate; a bare or mismatched buzz-channel assertion cannot mount an unrelated channel or enable project-agent membership side effects there.
  • Hostile repository adoption: channel-equality absorption and auto-healing require repository owner equality or an explicit maintainer grant to the project owner. Hostile same-channel repositories remain standalone and are not owner-re-signed.
  • Add Channel partial failure / concurrency: preflight happens before channel creation; the exact signed project event ID is revalidated afterward; changed, missing, or failed revalidation triggers signed deletion of the newly created channel before rejection. The interleaving regression proves no stale publication and no orphan for the reported race.
  • Auxiliary-surface arbitration: the idle focus drawer now yields to every higher-priority surface represented by the render priority chain: channel management, thread/thread skeleton, selected agent session, and profile panel. The timeline is no longer marked inert when one of those split surfaces displaces the idle project sheet.
  • Routing/UI state: repository/context-rail destinations, no-tab Overview with visible Back control, and idle project workspace behavior match the stated contract.

Verification at exact head

  • Read the complete PR diff and surrounding route, mutation, project-folding, agent-message, and auxiliary-panel call paths.
  • Focused final-head regressions: 19 passed, 0 failed (ChannelPane.helpers, project collection/healing selection, home authority, Add Channel stale/missing/interleaving).
  • Complete Desktop suite was independently run at preceding fix head 9b72d3e7: 5,448 passed, 0 failed; Wrench reports 5,449/0 at this exact final head plus typecheck and all local push gates.
  • git diff --check clean; Part 3 authority base b98580790 is an ancestor; GitHub reports the PR mergeable.

Residual / external gate

Fresh GitHub CI run 32649777247 is still in progress for Desktop Core, build, relay E2E, and four smoke shards. Merge-readiness is conditional on that exact-head run completing green. Client-side revalidation cannot provide strict CAS against an update in the final read→publish interval; fully eliminating that generic addressable-event limitation would require relay-side conditional replacement, but no practical regression specific to this PR remains after the compensated two-phase guard.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Gauge final re-review — correctness / testing / reliability, head 9ba49f83bbc733cb3d983814c772164259ac813f

Finding 75 (aux-surface idle-drawer arbitration) — verified fixed

ChannelPane.helpers.ts shouldUseFocusIdleDrawer now requires !channelManagementOpen && !hasAgentSession && !hasProfilePanel && !hasThreadSurface, and the call site (ChannelPane.tsx:471-487) feeds it exactly the same predicates the aux render chain uses at :810/:826/:898/:912/:953/:977 — including hasAgentSession: Boolean(activeChannel && selectedAgent), matching the chain's activeChannel && selectedAgent arm (the selectedAgent memo was hoisted above the guard for this). The table-style regression in ChannelPane.helpers.test.mjs asserts each of the four surfaces independently suppresses the idle drawer. This closes the uncovered-but-inert timeline class for every surface that outranks the idle panel in the chain.

TOCTOU fix (8857f9e6f, Cassandra's blocker) — verified from my lanes

useAddProjectChannel.ts now: preflight → createChannel → re-fetch and require confirmedLiveHead.id === liveHead.id → on changed/missing/failed second read, removeCreatedChannelAndThrow deletes the created channel before rejecting (with an AggregateError if cleanup itself fails — good partial-failure surfacing). The interleaving regression injects a concurrent head with a new related-channel tag during creation and asserts deleted == [CREATED_CHANNEL] and zero publish calls. Templates and createdAt are built from confirmedLiveHead, not the stale first read.

One residual, [75], low severity — not merge-blocking

Publish failure after a successful confirm still orphans the created channel. The cleanup helper wraps only the second read; the publish block (useAddProjectChannel.ts:127-154) rethrows on failure — including publication.publicationError — without calling deleteChannel(channel.id). Sequence: preflight ok → channel created → confirm ok → publishOwnerAnnouncement fails ⇒ channel exists on the relay, project never links it, user is told to retry, and retry creates a second channel. This window predates this PR's fixes (the pre-fix code had it too) and is now strictly narrower, but the new removeCreatedChannelAndThrow machinery makes closing it cheap: route the publish catch through the same helper. Confidence 75: code path fully quoted, not reproduced live.

Verified clean at exact head 9ba49f83b

  • Full desktop unit suite locally: 5,449 passed / 0 failed (81 suites; git rev-parse HEAD = 9ba49f83b… confirmed in the same shell before and after the run).
  • Delta 9b72d3e72..9ba49f83b touches only ChannelPane.{helpers.ts,helpers.test.mjs,tsx} and useAddProjectChannel.{ts,test.mjs} — all prior verifications (findings 1–4, P1 route gate, adoption-path authority guards, merge-base b985807, conflict-resolution audit) carry over untouched.
  • deleteChannel exists at shared/api/tauriChannels.ts:239 — the new dep default is real.
  • CI at 9ba49f83b in progress at review time (earlier "failures" at 9b72d3e72/32a8f67e8 were fail-fast cancellations from superseding pushes, not real results). Gate on this run completing green.

Verdict from correctness/testing/reliability lanes: merge-ready pending green CI on 9ba49f83b. The publish-failure orphan residual can ship as-is (pre-existing, narrowed) or take the one-line reroute through the existing cleanup helper.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra final adversarial verdict — merge-ready pending CI

Reviewed exact final head 35c29d9c954adc9d6969e2e926d1cdbbc24b62fa. No open security/adversarial findings or accepted review residuals remain.

The final publication-failure delta correctly routes both direct-owner publicationError/throw paths and owned-agent publication failures through removeCreatedChannelAndThrow. Unsupported-project-kind translation is preserved before compensation, cleanup failure remains explicit through AggregateError, and canvas/agent template side effects occur only after successful project-link publication. The new regression proves two matching live-head reads, failed publication, and deletion of the created channel.

All earlier closures remain intact at this descendant head: authoritative project-home routing; hostile repository absorption/healing authority checks; two-phase exact-event revalidation with cleanup; project-agent channel confinement; context-rail/repository routing; and idle-drawer yielding to every higher-priority auxiliary surface.

Exact-head verification

  • Read the complete final two-file delta and traced both direct-owner and owned-agent publication failure paths.
  • Ran useAddProjectChannel.test.mjs: 4 passed, 0 failed at 35c29d9c.
  • git diff --check clean; GitHub reports the PR mergeable.
  • Wrench reports the complete Desktop suite 5,450/0, typecheck, targeted Biome, and every local push gate passing at this exact head.

External gate

Fresh GitHub CI run 32650380296 has only begun. Merge-readiness is conditional on exact-head Desktop Core, build, relay E2E, and smoke shards completing green.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Gauge — publish-orphan residual verified closed at 35c29d9c954adc9d6969e2e926d1cdbbc24b62fa

Delta 9ba49f83b..35c29d9c9 touches only useAddProjectChannel.{ts,test.mjs}.

Verified: the publish catch (useAddProjectChannel.ts:147-155) now routes every publication failure — including the unsupported-kind translation and non-Error throws — through removeCreatedChannelAndThrow, so the created channel is deleted before rejecting, and cleanup failure still surfaces as the AggregateError with manual-deletion guidance. The new regression asserts the exact sequence I flagged: two matching live-head reads (fetchCalls === 2), failed publish, deleted == [CREATED_CHANNEL], original error preserved (/publication failed/).

Verified clean at exact head: full desktop unit suite 5,450 passed / 0 failed locally (81 suites; git rev-parse HEAD = 35c29d9c9… confirmed in the same shell before and after the run). All prior verifications (findings 1–4 + 75, TOCTOU, P1 route gate, adoption guards, merge-base audit) carry over — no other files changed.

No open findings remain from my lanes. Verdict: merge-ready pending green CI at 35c29d9c9 (run restarted after this push; the earlier "failure" statuses at 9ba49f83b/9b72d3e72/32a8f67e8 were fail-fast cancellations from superseding pushes, not real test results — no head in this sequence has produced a completed red test shard since the two deterministic failures fixed at 71d881ba0).

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra security/adversarial re-review — exact head 82fa4da3bee5da5f4a75f7f271d053f9a99df786

Verdict: merge-ready pending the fresh exact-head CI run.

Actionable findings

None.

Coverage

  • Read the complete 11-file delta from the previously reviewed 35c29d9c954adc9d6969e2e926d1cdbbc24b62fa through this exact head, then traced the affected project-home authority classifier, related-channel enumeration, repository mutation callback, repository source switching, sidebar navigation, and fixture generation paths.
  • Security lens: no new permission decision, publication authority, or trust boundary is weakened. Project-home presentation still requires hasAuthoritativeHomeBinding; nested sidebar rows resolve only to channels returned by the channel query; repository mutation permissions remain in ProjectRepositoryManagement.
  • Adversarial lens: exercised malformed/missing sync metadata reasoning, local/remote branch transitions, project-home vs repository navigation, related-channel duplication, projects with and without child channels, and the earlier #general fixture collision. The dedicated #buzz fixture removes that collision.
  • The navigation and callback split are coherent: project creation and repository membership changes return to project home, while explicit repository-row selection still opens repository detail.
  • git diff --check is clean; local and GitHub head both resolve to the SHA above; GitHub reports the PR mergeable.
  • Implementer evidence at this exact head: E2E build succeeded, the consolidated project smoke batch passed 54/54, and push hooks passed.

Confidence anchors

No findings were emitted, so no finding confidence score applies. The clean verdict is based on quoted-code/path tracing and exact-head test evidence rather than absence of CI failures.

Residual / gate

The prior Smoke (3) run exposed real defects, so the new GitHub Actions run 32664303990 is a binding merge gate. At review time its Desktop/Smoke jobs, including Smoke (3), are still in progress. Do not merge until that exact-head run completes green. This comment is the durable sink for that residual.

@thomaspblock
thomaspblock force-pushed the projects-channel-first-pt4-project-home branch from 82fa4da to b82be0e Compare August 23, 2026 20:42
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra replacement-head security/adversarial re-review — b82be0eac12fa0a702741cb74f20fdbcfdfebaf0

This supersedes my verdict for 82fa4da3bee5da5f4a75f7f271d053f9a99df786.

Verdict: merge-ready pending fresh exact-head CI.

Findings

None.

Replacement delta checked

The only delta from the prior reviewed SHA is desktop/src/features/projects/ui/ProjectDetailScreen.tsx: the unintended automatic switch to local source was removed. The resulting handler preserves an explicit remote choice and demotes local only when resolved localBranches affirmatively excludes the selected branch. Missing metadata keeps the current source, avoiding the external-host false negative that caused the regression.

I also rechecked the surrounding source-selection effect: when a checkout is genuinely unavailable, the existing hasLocalCheckout effect still demotes local to remote. This replacement therefore narrows the branch handler without removing the established fallback.

Exact-head evidence

  • Local checkout and GitHub PR head both resolve to b82be0eac12fa0a702741cb74f20fdbcfdfebaf0; tree is clean.
  • git diff --check is clean for both the replacement delta and the complete correction over 35c29d9c9.
  • GitHub reports mergeable.
  • Implementer exact-tree validation now includes every changed E2E spec: 68/68 passed, plus build and push hooks.

Residual / binding gate

Fresh CI run 32665233884 is in progress at this exact SHA. Because earlier Smoke (3) runs exposed real defects, Smoke (3) on this replacement head remains a binding merge gate. Do not merge until this run is green. This comment is the durable sink for that residual.

@thomaspblock
thomaspblock force-pushed the projects-channel-first-pt4-project-home branch from b82be0e to 1f5dbb9 Compare August 23, 2026 20:56
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra replacement-head security/adversarial re-review — 1f5dbb906beab915bb2a0ec2d98f674bb03595ea

This supersedes my verdict for b82be0eac12fa0a702741cb74f20fdbcfdfebaf0.

Verdict: merge-ready pending fresh exact-head CI.

Findings

None.

Replacement delta checked

The complete delta from the prior reviewed SHA is two test-contract corrections:

  1. channel-browser.spec.ts now includes the deliberately open/joined seeded #buzz project-home channel in alphabetical, member-count, and recent-activity order. This matches the fixture's actual product-visible semantics rather than hiding the channel to satisfy an old list.
  2. entity-link-recipient-cards.spec.ts now asserts project-channel-home after clicking the project breadcrumb. That is the actual destination; project-workspace-back exists only on repository detail and was an invalid assertion.

These changes correct expectations exposed by the superseded CI run; they do not weaken production authority, authorization, or navigation logic.

Exact-head evidence

  • Local checkout, origin, and GitHub PR head resolve to 1f5dbb906beab915bb2a0ec2d98f674bb03595ea; the tree is clean and GitHub reports mergeable.
  • git diff --check is clean.
  • I independently reran the two corrected focused flows at this exact head: channel browser sorting and entity-link workspace restoration both passed (2/2, 4.0s).
  • Implementer evidence: a 193-test batch covering every PR-modified spec plus the full channel-browser spec reached 191 passes, then the corrected member-order case and unrelated transient channels case passed together 2/2; push hooks passed.

Residual / binding gate

Fresh CI run 32665962284 is pending at this exact SHA. Earlier full runs repeatedly found real cross-spec fixture regressions outside narrow project batches, so all Desktop smoke shards—not only Smoke (3)—are binding merge gates now. Do not merge until the exact-head run is green. This comment is the durable sink for that residual.

@thomaspblock
thomaspblock force-pushed the projects-channel-first-pt4-project-home branch from 1f5dbb9 to 2805103 Compare August 23, 2026 21:23
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra adversarial/security re-review — 28051036f902677b074bf164e7fe7b00407c49cb

Verdict: merge-ready pending exact-head CI. No open security or adversarial findings.

Exact-head delta reviewed

The delta from the previously reviewed head 1f5dbb906beab915bb2a0ec2d98f674bb03595ea is exactly one insertion in desktop/tests/e2e/terminal-wheel.spec.ts:

desktop/tests/e2e/terminal-wheel.spec.ts:188await page.getByTestId("project-home-context-repo-buzz").click();

This is the correct contract repair. Clicking the project entry now lands on project channel home; the added step follows the actual codebase navigation control (ProjectHomeContextPanel.tsx:361-362 invokes onOpenRepository(repository.id)), after which the repository-scoped terminal control is present (ProjectRightPanelControls.tsx:84-86). It does not weaken an assertion, introduce alternate selector fallback, or expand Buzz Term onto project home.

Adversarial/security coverage

  • malformed or ambiguous repository selection: selector is fixture-specific and resolves the authoritative buzz repository;
  • race/readiness: Playwright actionability waits on the repository control before click, and the following enabled assertion waits on the destination control;
  • privilege/auth boundary: test-only navigation change; no production auth, authorization, input, persistence, IPC, or secret-handling surface changed;
  • false-positive risk: the test still requires project-terminal-toggle to become enabled and verifies the terminal opens in docked mode;
  • regression scope: remote PR head independently resolves to 28051036f; GitHub reports the PR mergeable. The exact terminal flow independently passed 1/1, and the implementer reports the grep-derived five-spec project-entry batch passed 72/72.

Binding gate / residual risk

Run 32667398195 is on the exact SHA and is still in progress. Because prior project-home fixture fallout appeared serially across smoke shards, do not merge until Desktop Core and Desktop Smoke E2E (1)–(4) are all green on this SHA. This CI gate is the only remaining residual; this comment is its durable sink and supersedes my earlier SHA verdict.

Base automatically changed from projects-channel-first-pt3-channel-shell to projects-channel-first-pt1-agent-cli August 23, 2026 21:48
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Current merge blocker after green CI — base branch advanced

The product/test review remains clean, and exact-head CI at 28051036f902677b074bf164e7fe7b00407c49cb is fully green. The PR is currently unmergeable for a new base-branch conflict, not a CI or product regression.

Confidence: 100. GitHub now reports base 6c0abfdddbf4f75fadb6ff27d5e917acb3b6085d, mergeable: false, mergeable_state: dirty. That base advanced through the merged Part 2/Part 3 work (ac2c45e84, 6c0abfddd) after the reviewed head was built with its own historical Part 2/Part 3 merges. A local read-only git merge-tree --write-tree 28051036f 6c0abfddd reproduces conflicts in eight files:

  • desktop/src/features/channels/ui/ChannelPane.helpers.test.mjs
  • desktop/src/features/channels/ui/ChannelPane.helpers.ts
  • desktop/src/features/channels/ui/ChannelPane.tsx
  • desktop/src/features/projects/lib/projectCollection.test.mjs
  • desktop/src/features/projects/lib/projectCollection.ts
  • desktop/src/features/projects/lib/projectHomeChannel.test.mjs
  • desktop/src/features/projects/lib/projectHomeChannel.ts
  • desktop/tests/e2e/project-commit-detail.spec.ts

These are expected stacked-branch reconciliation conflicts, but several touch prior security fixes (authoritative home binding and hostile repository adoption), so the resolution must preserve the current PR side rather than mechanically choose the newly advanced base. Any replacement head supersedes the exact-head CI/review verdict and needs delta review plus fresh required CI.

Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
@thomaspblock
thomaspblock force-pushed the projects-channel-first-pt4-project-home branch from 2805103 to 553495e Compare August 23, 2026 21:56
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra ancestry-repair delta review — 553495efa425febb3c61aebda2a2ff551ebe0f87

Verdict: merge-ready pending fresh exact-head CI. No new security/adversarial finding.

The conflict resolution is mechanically content-identical to the previously reviewed and fully green head:

  • 28051036f^{tree} = cb4f37a7cdda982a2d01a22041fc3e9a345b91be
  • 553495efa^{tree} = cb4f37a7cdda982a2d01a22041fc3e9a345b91be
  • git diff 28051036f 553495efa has zero files and git diff --check is clean.
  • The new commit's sole parent is the advanced PR base 6c0abfddd, which is an ancestor of the new head.
  • Remote PR head matches 553495efa; GitHub now reports MERGEABLE.

Because the file tree is byte-identical, all prior code-path findings and their closures transfer without semantic re-review: authoritative project-home routing, hostile repository rejection, compensated channel-creation races/failures, auxiliary-panel arbitration, and project-home/repository navigation are unchanged.

Fresh CI run 32669027648 has started on this exact SHA. Formal merge gate remains Desktop Core and all Desktop Smoke E2E shards green on 553495efa; prior green CI proves the tree, but not the new commit status required by branch policy. This supersedes the temporary conflict report.

@thomaspblock
thomaspblock merged commit 7d6c4ab into projects-channel-first-pt1-agent-cli Aug 23, 2026
23 checks passed
@thomaspblock
thomaspblock deleted the projects-channel-first-pt4-project-home branch August 23, 2026 22:32
matt2e pushed a commit that referenced this pull request Aug 25, 2026
## 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>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
thomaspblock added a commit that referenced this pull request Aug 25, 2026
## 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant