Skip to content

fix(analyze): clamp community/synthesizer kinds to the session workspace#210

Merged
zzet merged 1 commit into
mainfrom
fix/analyze-community-workspace-clamp
Jul 1, 2026
Merged

fix(analyze): clamp community/synthesizer kinds to the session workspace#210
zzet merged 1 commit into
mainfrom
fix/analyze-community-workspace-clamp

Conversation

@zzet

@zzet zzet commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to the intent-based scoping in #202. While validating that change, the community / synthesizer analyze kinds were found to cross the workspace isolation boundary: in a multi-repo workspace, analyze kind=clusters (and synthesizers) returned members / edges from repos in other workspaces. The kinds are documented as "workspace-bound but not repo-narrowed in v1", but the implementation applied no workspace filter at all.

Concretely, from a session bound to the gortex workspace, analyze kind=clusters repo:gortex returned cluster members living in vioportals/ and rate_checkers_detector/ — repos that an explicit repo: arg is otherwise hard-rejected for as cross-workspace.

Root cause

  • handleAnalyzeClusters / handleAnalyzeConcepts / handleSuggestBoundaries run community detection over the whole s.graph and filtered results only by min_size / path_prefix.
  • handleAnalyzeSynthesizers swept g.AllEdges() with no workspace filter.
  • The only disclosure (scope_note) rode in MCP _meta, which several clients (Claude Code among them) do not surface — so the leak was silent to an agent.

Change

  • Workspace clamp. New graph.RepoPrefixOfID + Server.communitiesInSessionScope return a workspace-filtered copy of the cached community partition (the shared cache is never mutated); applied to clusters / concepts / suggest_boundaries. AnalyzeSynthesizers gains WithSynthesizerRepoScope, which drops edges whose source is outside the workspace and re-tallies counts / by-kind / samples.
  • Visible disclosure. A scope block now rides in the response body ({applied, note}) instead of the invisible _meta note, so an agent sees the applied workspace and — when a narrowing arg is passed — that the kind is workspace-bound but not repo/project-narrowed in v1.
  • The now-stale _meta scope_note is suppressed for these self-disclosing kinds so the two signals don't contradict.

Single-repo / unbound sessions are unaffected (no clamp, no scope block).

Testing

  • New TestAnalyzeSynthesizers_RepoScope (analyzer) and TestAnalyzeScope_Clusters_WorkspaceClamp (mcp, bidirectional non-vacuous cross-workspace probe + body-disclosure assertion).
  • Updated TestAnalyzeScope_ScopeNote_BypassKind to the new body-disclosure behavior.
  • go build ./..., go vet, go test ./internal/analyzer/ ./internal/graph/ ./internal/mcp/ all pass, including -race on the touched packages.

Community detection (clusters, concepts, suggest_boundaries) and
synthesizer edge enumeration run a global graph algorithm over the whole
index and filtered only by size / path prefix, so a workspace-bound
caller saw clusters and synthesized edges whose members live in sibling
workspaces — a breach of the workspace isolation boundary these kinds are
documented to honour.

Clamp each to the session workspace: communitiesInSessionScope returns a
workspace-filtered copy of the cached partition (never mutating the
cache), and AnalyzeSynthesizers gains a repo-scope option that drops
edges whose source is outside the workspace and re-tallies its counts.

Surface the scope disclosure in the response body (a `scope` block)
rather than the _meta scope_note that several clients do not surface, and
suppress the now-stale _meta note for these self-disclosing kinds.
@zzet zzet merged commit ed1f1cc into main Jul 1, 2026
10 checks passed
@zzet zzet deleted the fix/analyze-community-workspace-clamp branch July 3, 2026 06:30
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