{"record":{"id":"6125ca6f71e5594a","repo":"thedotmack/claude-mem","slug":"project-id-must-belong-to-team-id","errorCode":null,"errorMessage":"project_id must belong to team_id","messagePattern":"project_id must belong to team_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/storage/postgres/utils.ts","lineNumber":62,"sourceCode":"  text: string,\n  values: unknown[] = []\n): Promise<T | null> {\n  const result = await client.query<T>(text, values);\n  return result.rows[0] ?? null;\n}\n\nexport async function assertProjectOwnership(\n  client: PostgresQueryable,\n  projectId: string,\n  teamId: string\n): Promise<void> {\n  const row = await queryOne<{ id: string }>(\n    client,\n    'SELECT id FROM projects WHERE id = $1 AND team_id = $2',\n    [projectId, teamId]\n  );\n  if (!row) {\n    throw new Error('project_id must belong to team_id');\n  }\n}\n\nexport async function assertSessionOwnership(\n  client: PostgresQueryable,\n  serverSessionId: string,\n  projectId: string,\n  teamId: string\n): Promise<void> {\n  const row = await queryOne<{ id: string }>(\n    client,\n    'SELECT id FROM server_sessions WHERE id = $1 AND project_id = $2 AND team_id = $3',\n    [serverSessionId, projectId, teamId]\n  );\n  if (!row) {\n    throw new Error('server_session_id must belong to project_id and team_id');\n  }\n}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/storage/postgres/utils.ts#L44-L80","documentation":"Thrown by assertProjectOwnership (shared helper in utils.ts). It queries projects by (id=projectId, team_id=teamId); a missing row means the project is not owned by that team. This is the foundational tenancy check called at the top of create() paths across repositories.","triggerScenarios":"Calling any repository create/validate method with a projectId that is not a row in projects for the given teamId. Fires before source-specific checks because assertProjectOwnership runs first.","commonSituations":"Wrong team context, project id from another team, deleted project, id typo, or thread/request-local team id not set correctly.","solutions":["Verify (projectId, teamId) pairing against projects before calling repository methods.","Derive teamId from the authenticated session/request context and ensure projectId belongs to it.","Use the exact project id returned by project creation."],"exampleFix":"// before\nawait observations.create({ projectId, teamId, content });\n// after\nawait assertProjectOwnership(client, projectId, teamId); // or a guard that returns boolean\nif (!await projectExists(projectId, teamId)) throw new Error('project not in team');\nawait observations.create({ projectId, teamId, content });","handlingStrategy":"validation","validationCode":"async function projectExists(client, projectId, teamId){ const r = await client.query('SELECT 1 FROM projects WHERE id=$1 AND team_id=$2', [projectId, teamId]); return r.rowCount>0; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Resolve team from auth context once; never trust client-supplied teamId.","Keep a projects lookup helper and call it at the API boundary."],"tags":["postgres","validation","multi-tenant","ownership","tenancy"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}