Skip to content

refactor: extract resolveAgentOwnerOrg to shared helper (drift surface) #4377

@bokelley

Description

@bokelley

Gap

The agent-ownership query is duplicated in two places:

  • `server/src/routes/registry-api.ts:4820` — `resolveAgentOwnerOrg(userId, agentUrl)` returns the org id of the org that owns this agent for the calling user, or null.
  • `server/src/addie/mcp/member-tools.ts:3576-3585` — inline copy of the same JOIN, with an additional `WHERE mp.workos_organization_id = $1` clause that tightens the predicate to "is THIS resolved org the one that owns the agent" instead of "is there ANY org you're a member of that owns it."

Two writers of the same predicate → drift surface. The two queries differ in semantics (the second is strictly tighter) and a future security fix that updates one will silently leave the other behind. Per the FK-less-pointer playbook this is exactly the kind of duplication that bites.

Fix shape

Create `server/src/services/agent-ownership.ts` with two helpers:

  • `findOwnerOrgForUser(userId, agentUrl): Promise<string | null>` — registry-api.ts's "any owning org" semantic
  • `isOrgOwnerOfAgent(orgId, userId, agentUrl): Promise` — member-tools.ts's "this specific org" semantic (used for the owner_test canonical-write gate)

Both call the same base CTE; the second adds the org id constraint. Importers replace inline implementations. Add unit tests covering: (a) anonymous user → false, (b) user in org without agent in member_profiles → false, (c) cross-org viewer → false, (d) actual owner → true with correct org id.

Acceptance criteria

  • No inline copies of the JOIN remain
  • Both call sites use the helpers, semantics preserved
  • Unit tests cover the four ownership states above
  • No behavior change at the API surface (existing integration tests pass)

Discovered

PR #4250 code review feedback. Deferred as out-of-scope to keep that PR tight.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions