{"record":{"id":"8a96bec8ab8a7a2f","repo":"thedotmack/claude-mem","slug":"server-session-id-must-belong-to-project-id-and-te","errorCode":null,"errorMessage":"server_session_id must belong to project_id and team_id","messagePattern":"server_session_id must belong to project_id and team_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/storage/postgres/utils.ts","lineNumber":78,"sourceCode":"  );\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}\n\nexport function canonicalJson(value: unknown): string {\n  return JSON.stringify(sortJson(value));\n}\n\nexport function deterministicKey(parts: readonly unknown[]): string {\n  const fingerprint = createHash('sha256')\n    .update(canonicalJson(parts))\n    .digest('hex');\n  return fingerprint;\n}\n\nfunction sortJson(value: unknown): unknown {\n  if (Array.isArray(value)) {\n    return value.map(sortJson);\n  }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/storage/postgres/utils.ts#L60-L96","documentation":"Thrown by assertSessionOwnership (shared helper in utils.ts). It queries server_sessions by (id, project_id, team_id); a missing row means the server session is not in the project/team scope. Used wherever a serverSessionId must be validated against tenant boundaries.","triggerScenarios":"Passing a serverSessionId that does not exist under the given project/team to create()/validateSource() or any helper that calls assertSessionOwnership. Project ownership is usually validated separately first.","commonSituations":"Session id from another project/tenant, expired/purged session, stale cached id, or a derived session-group id being passed instead of the real server_session id.","solutions":["Resolve the server session within scope before referencing it.","Confirm the session was created under the same project/team.","Use the id stored in server_sessions exactly."],"exampleFix":"// before\nawait observations.create({ projectId, teamId, serverSessionId: staleId, content });\n// after\nconst session = await serverSessions.getByIdForScope({ id: serverSessionId, projectId, teamId });\nif (!session) throw new Error('session not in scope');\nawait observations.create({ projectId, teamId, serverSessionId, content });","handlingStrategy":"validation","validationCode":"const session = await serverSessions.getByIdForScope({ id: serverSessionId, projectId, teamId });\nif (!session) throw new Error('server_session not in scope');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always scope session lookups by project+team.","Discard cached session ids when the project context changes."],"tags":["postgres","validation","multi-tenant","ownership","sessions"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}