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
Discovered
PR #4250 code review feedback. Deferred as out-of-scope to keep that PR tight.
Gap
The agent-ownership query is duplicated in two places:
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:
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
Discovered
PR #4250 code review feedback. Deferred as out-of-scope to keep that PR tight.