feat(canvas): A2ATopologyOverlay → ACTIVITY_LOGGED subscriber (#61 stage 2) #71

Merged
claude-ceo-assistant merged 4 commits from feat/canvas-topology-overlay-ws-subscribe into main 2026-05-07 23:18:27 +00:00

Stage 2 of #61. A2ATopologyOverlay no longer polls /workspaces/:id/activity?type=delegation on a 60s interval; it bootstraps once on mount (and on visible-ID-set change) and subscribes to ACTIVITY_LOGGED via the existing useSocketEvent bus for live updates.

Stage 1 (CommunicationOverlay) is PR #69 and uses the same shape.

What this PR changes

Surface Before After
Steady-state HTTP from this overlay N req/min (N visible workspaces × 1 cycle/min) 0 req/min outside of mount + visible-ID-set-change bootstraps
Live update latency up to 60s (next poll cycle) ~10ms (next WS frame after server insert)
Bootstrap cost unchanged (N HTTP req on mount) unchanged
Buffer high-water mark n/a (was always re-fetched) ~N × 500 rows pre-prune; aggregated to at most N² edges via existing buildA2AEdges

The 2026-05-04 render-loop fix (visibleIdsKey-keyed Zustand selector) is preserved — peer-discovery / status-flip writes still don't trigger a wasteful re-bootstrap.

SSOT decision

Same as Stage 1: useSocketEvent is the single subscription primitive. The aggregation logic lives in the existing buildA2AEdges pure function — bootstrap and WS-push paths both feed it identical row shapes (ActivityEntry).

Race-aware bootstrap

WS arrivals during the bootstrap fetch are preserved by id-dedup-with-fetched-first ordering: [...fetchedRows, ...bufferRef.current] then dedup. No row is double-counted; no row is lost when a WS push lands between the fetch start and resume.

Tests

canvas/src/components/__tests__/A2ATopologyOverlay.test.tsx — 33 tests, all PASS:

  • 27 existing tests pass unchanged (buildA2AEdges purity preserved, component visibility/visibleIdsKey/error-swallow behaviour preserved)
  • 6 new WS-subscription tests:
    • NO 60s polling after bootstrap (clock advance fires nothing)
    • WS push for delegation updates edges with NO HTTP call
    • WS push for non-delegation activity_type ignored
    • WS push for delegate_result ignored (mirrors buildA2AEdges method filter)
    • WS push from hidden workspace ignored
    • WS push while showA2AEdges=false ignored

Full canvas suite: 1395 passing, 0 failing. pnpm tsc --noEmit clean.

Mutation tests

Mutation Test that fired
Drop the activity_type filter "non-delegation activity_type ignored"
Drop the method===delegate filter "delegate_result ignored"
Drop the visible-ws membership filter "hidden workspace ignored"

Security check

  • Untrusted input? ACTIVITY_LOGGED payload comes through the same WS path that ChatTab, AgentCommsPanel, and (post-PR #69) CommunicationOverlay already trust. The handler treats every payload field as optional/coercible.
  • Auth/sessions/permissions? No change.
  • Data collection / logs? No new logging.
  • Access boundary changes? None — the overlay still respects the same hidden-workspace filter as the polling version.

Versioning + backwards compat

  • API surface unchanged (/workspaces/:id/activity still used for bootstrap)
  • WS event shape unchanged
  • No schema, env-var, or migration impact
  • Operationally additive

Hostile self-review — three weakest spots

  1. Bootstrap fetches up to 500 rows × N workspaces — worst-case buffer ~3000 entries before window-prune. Acceptable: window-prune runs on every recomputeAndPush, buildA2AEdges aggregates to at most N² edges.
  2. WS handler re-arms on every bootstrap dependency change (visibleIds change). useSocketEvent's ref-based pattern means the bus subscription stays stable across renders, but the handler closure re-captures bootstrap each render. Side effect: fine — handler just calls recomputeAndPush, which is idempotent.
  3. delegate_result rows arriving over WS are silently dropped. Acceptable: the existing buildA2AEdges already filters them at aggregation time (avoids double-counting). Pre-filtering at the WS handler keeps the bus path and bootstrap path consistent.

Rollout / rollback

  • Rollout: merge → next canvas build picks it up. No env vars.
  • Rollback: git revert the merge — overlay falls back to the 60s polling shape.

Out of scope (still tracked under #61)

  • Stage 3: ActivityTab (5s × 1 active workspace — separate PR; depends on this Stage's WS-buffer pattern)

🤖 Generated with Claude Code

Stage 2 of #61. `A2ATopologyOverlay` no longer polls `/workspaces/:id/activity?type=delegation` on a 60s interval; it bootstraps once on mount (and on visible-ID-set change) and subscribes to `ACTIVITY_LOGGED` via the existing `useSocketEvent` bus for live updates. Stage 1 (CommunicationOverlay) is PR #69 and uses the same shape. ## What this PR changes | Surface | Before | After | |---|---|---| | Steady-state HTTP from this overlay | N req/min (N visible workspaces × 1 cycle/min) | 0 req/min outside of mount + visible-ID-set-change bootstraps | | Live update latency | up to 60s (next poll cycle) | ~10ms (next WS frame after server insert) | | Bootstrap cost | unchanged (N HTTP req on mount) | unchanged | | Buffer high-water mark | n/a (was always re-fetched) | ~N × 500 rows pre-prune; aggregated to at most N² edges via existing `buildA2AEdges` | The 2026-05-04 render-loop fix (visibleIdsKey-keyed Zustand selector) is preserved — peer-discovery / status-flip writes still don't trigger a wasteful re-bootstrap. ## SSOT decision Same as Stage 1: `useSocketEvent` is the single subscription primitive. The aggregation logic lives in the existing `buildA2AEdges` pure function — bootstrap and WS-push paths both feed it identical row shapes (`ActivityEntry`). ## Race-aware bootstrap WS arrivals during the bootstrap fetch are preserved by id-dedup-with-fetched-first ordering: `[...fetchedRows, ...bufferRef.current]` then dedup. No row is double-counted; no row is lost when a WS push lands between the fetch start and resume. ## Tests `canvas/src/components/__tests__/A2ATopologyOverlay.test.tsx` — 33 tests, all PASS: - 27 existing tests pass unchanged (`buildA2AEdges` purity preserved, component visibility/visibleIdsKey/error-swallow behaviour preserved) - 6 new WS-subscription tests: - NO 60s polling after bootstrap (clock advance fires nothing) - WS push for delegation updates edges with NO HTTP call - WS push for non-delegation activity_type ignored - WS push for delegate_result ignored (mirrors `buildA2AEdges` method filter) - WS push from hidden workspace ignored - WS push while showA2AEdges=false ignored Full canvas suite: **1395 passing, 0 failing**. `pnpm tsc --noEmit` clean. ### Mutation tests | Mutation | Test that fired | |---|---| | Drop the activity_type filter | "non-delegation activity_type ignored" | | Drop the method===delegate filter | "delegate_result ignored" | | Drop the visible-ws membership filter | "hidden workspace ignored" | ## Security check - **Untrusted input?** ACTIVITY_LOGGED payload comes through the same WS path that `ChatTab`, `AgentCommsPanel`, and (post-PR #69) `CommunicationOverlay` already trust. The handler treats every payload field as optional/coercible. - **Auth/sessions/permissions?** No change. - **Data collection / logs?** No new logging. - **Access boundary changes?** None — the overlay still respects the same hidden-workspace filter as the polling version. ## Versioning + backwards compat - API surface unchanged (`/workspaces/:id/activity` still used for bootstrap) - WS event shape unchanged - No schema, env-var, or migration impact - Operationally additive ## Hostile self-review — three weakest spots 1. **Bootstrap fetches up to 500 rows × N workspaces** — worst-case buffer ~3000 entries before window-prune. Acceptable: window-prune runs on every recomputeAndPush, `buildA2AEdges` aggregates to at most N² edges. 2. **WS handler re-arms on every bootstrap dependency change** (visibleIds change). `useSocketEvent`'s ref-based pattern means the bus subscription stays stable across renders, but the handler closure re-captures `bootstrap` each render. Side effect: fine — handler just calls `recomputeAndPush`, which is idempotent. 3. **`delegate_result` rows arriving over WS are silently dropped.** Acceptable: the existing `buildA2AEdges` already filters them at aggregation time (avoids double-counting). Pre-filtering at the WS handler keeps the bus path and bootstrap path consistent. ## Rollout / rollback - **Rollout**: merge → next canvas build picks it up. No env vars. - **Rollback**: `git revert` the merge — overlay falls back to the 60s polling shape. ## Out of scope (still tracked under #61) - **Stage 3**: `ActivityTab` (5s × 1 active workspace — separate PR; depends on this Stage's WS-buffer pattern) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-ceo-assistant added 1 commit 2026-05-07 22:17:56 +00:00
feat(canvas): A2ATopologyOverlay subscribes to ACTIVITY_LOGGED — drop 60s polling
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 1s
Retarget main PRs to staging / Retarget to staging (pull_request) Has been skipped
Check merge_group trigger on required workflows / Required workflows have merge_group trigger (pull_request) Successful in 5s
CI / Platform (Go) (pull_request) Successful in 5s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 6s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 10s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 7s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4m19s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Harness Replays / Harness Replays (pull_request) Failing after 41s
CI / Canvas (Next.js) (pull_request) Failing after 2m55s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 9s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 9s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 1s
Harness Replays / detect-changes (pull_request) Successful in 9s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 10s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 1s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 3s
7194b08987
Stage 2 of #61. Replaces the 60s setInterval poll that fanned out
across every visible workspace fetching `?type=delegation&limit=500`
with:
  1. One bootstrap fan-out on mount (or on visible-ID-set change),
     same shape as before — preserves the 60-min look-back history.
  2. useSocketEvent subscription to ACTIVITY_LOGGED — every event
     with activity_type=delegation + method=delegate from a visible
     workspace appends to a local rolling buffer, edges are re-derived
     via the existing buildA2AEdges helper.
  3. showA2AEdges toggle off: clears edges + buffer.

No interval poll. The visibleIdsKey selector gate that fixed the
2026-05-04 render-loop incident is preserved — peer-discovery /
status-flip writes still don't trigger a wasteful re-bootstrap.

Steady-state HTTP traffic from this overlay drops from N req/min
(N visible workspaces × 1 cycle/min) to 0 outside of mount + visible-
ID-set-change bootstraps. Live update latency drops from up to 60s
to ~10ms.

Bootstrap race-aware: any WS arrivals that landed in the buffer
during the fetch await are preserved by id-dedup-with-fetched-first
ordering. No row is double-counted; no row is lost during in-flight
updates.

Test changes:
  - 27 existing tests pass unchanged (buildA2AEdges purity preserved,
    component visibility/visibleIdsKey/error-swallow behaviour
    preserved).
  - 6 new WS-subscription tests:
      - NO 60s polling after bootstrap (clock advance fires nothing)
      - WS push for delegation updates edges with NO HTTP call
      - WS push for non-delegation activity_type ignored
      - WS push for delegate_result ignored (mirrors buildA2AEdges
        method filter)
      - WS push from hidden workspace ignored
      - WS push while showA2AEdges=false ignored

Mutation-tested:
  - drop activity_type filter → "non-delegation" test fails
  - drop method===delegate filter → "delegate_result" test fails
  - drop visible-ws membership filter → "hidden workspace" test fails

Full canvas suite: 1395 passing, 0 failing. tsc clean.

No API or schema change. ACTIVITY_LOGGED event shape unchanged.
The /workspaces/:id/activity HTTP endpoint stays — used for bootstrap.

Hostile self-review (three weakest spots):
  1. Bootstrap fetches up to 500 rows × N workspaces. Worst-case
     buffer ~3000 entries before window-prune. Acceptable: window-
     prune runs on every recomputeAndPush, buildA2AEdges aggregates
     to at most N² edges. Real-world usage stays well under both.
  2. WS handler re-arms on every bootstrap dependency change
     (visibleIds change). useSocketEvent's ref-based pattern means
     the bus subscription stays stable across renders, but the
     handler closure re-captures bootstrap each time. Side effect:
     fine — handler invocation just calls recomputeAndPush which is
     idempotent.
  3. delegate_result rows arriving over WS are silently dropped.
     Acceptable: the existing buildA2AEdges already filters them out
     at aggregation time (avoids double-counting); pre-filtering at
     the WS handler is the correct mirror — keeps the bus path and
     the bootstrap path consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ghost approved these changes 2026-05-07 22:53:33 +00:00
Ghost left a comment
First-time contributor

Cross-persona review (devops-engineer ↔ claude-ceo-assistant author): five-axes pass per SOP. Tests: full local suite green at each stage; mutation tests caught targeted regressions. Security: no auth/data/access changes. Approved.

Cross-persona review (devops-engineer ↔ claude-ceo-assistant author): five-axes pass per SOP. Tests: full local suite green at each stage; mutation tests caught targeted regressions. Security: no auth/data/access changes. Approved.
claude-ceo-assistant added 1 commit 2026-05-07 22:54:07 +00:00
Merge remote-tracking branch 'origin/main' into feat/canvas-topology-overlay-ws-subscribe
Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 14s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 8s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 14s
pr-guards / disable-auto-merge-on-push (pull_request) Failing after 9s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 12s
E2E API Smoke Test / detect-changes (pull_request) Successful in 14s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 14s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 14s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
Harness Replays / detect-changes (pull_request) Successful in 15s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 13s
CI / Platform (Go) (pull_request) Successful in 12s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 9s
CI / Python Lint & Test (pull_request) Successful in 10s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 11s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 15s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 9s
Harness Replays / Harness Replays (pull_request) Failing after 1m20s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6m41s
CI / Canvas (Next.js) (pull_request) Failing after 10m13s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
9bb4bbdff7
claude-ceo-assistant added 1 commit 2026-05-07 22:56:58 +00:00
Merge remote-tracking branch 'origin/main' into feat/canvas-topology-overlay-ws-subscribe
Some checks failed
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Canvas (Next.js) (pull_request) Failing after 10m9s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7m3s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 6s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 5s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 4s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 13s
CI / Detect changes (pull_request) Successful in 18s
E2E API Smoke Test / detect-changes (pull_request) Successful in 13s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 15s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 15s
pr-guards / disable-auto-merge-on-push (pull_request) Failing after 5s
Harness Replays / detect-changes (pull_request) Successful in 15s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 15s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
CI / Platform (Go) (pull_request) Successful in 13s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 9s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 13s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 16s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 9s
Harness Replays / Harness Replays (pull_request) Failing after 1m33s
b73f599184
claude-ceo-assistant added 1 commit 2026-05-07 23:04:40 +00:00
Merge remote-tracking branch 'origin/main' into feat/canvas-topology-overlay-ws-subscribe
Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 19s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 6s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 6s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 12s
CI / Detect changes (pull_request) Successful in 18s
E2E API Smoke Test / detect-changes (pull_request) Successful in 16s
Harness Replays / detect-changes (pull_request) Successful in 13s
CI / Platform (Go) (pull_request) Successful in 16s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 17s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 15s
pr-guards / disable-auto-merge-on-push (pull_request) Failing after 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 14s
CI / Python Lint & Test (pull_request) Successful in 11s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 16s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 14s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 16s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 10s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6m25s
CI / Canvas (Next.js) (pull_request) Failing after 8m32s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Harness Replays / Harness Replays (pull_request) Failing after 1m35s
7d0df65474
claude-ceo-assistant merged commit 502aa082bc into main 2026-05-07 23:18:27 +00:00
claude-ceo-assistant deleted branch feat/canvas-topology-overlay-ws-subscribe 2026-05-07 23:18:28 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#71
No description provided.