{"record":{"id":"543b9e80d66fb088","repo":"thedotmack/claude-mem","slug":"agent-event-source-id-must-belong-to-project-id-an","errorCode":null,"errorMessage":"agent_event source_id must belong to project_id and team_id","messagePattern":"agent_event source_id must belong to project_id and team_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":261,"sourceCode":"\n  private async validateSource(input: {\n    projectId: string;\n    teamId: string;\n    sourceType: ObservationGenerationJobSourceType;\n    sourceId: string;\n    agentEventId?: string | null;\n    serverSessionId?: string | null;\n  }): Promise<void> {\n    await assertProjectOwnership(this.client, input.projectId, input.teamId);\n    if (input.sourceType === 'agent_event') {\n      const eventId = input.agentEventId ?? input.sourceId;\n      const row = await queryOne<{ id: string; server_session_id: string | null }>(\n        this.client,\n        'SELECT id, server_session_id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',\n        [eventId, input.projectId, input.teamId]\n      );\n      if (!row || input.sourceId !== eventId) {\n        throw new Error('agent_event source_id must belong to project_id and team_id');\n      }\n      if (input.serverSessionId) {\n        await assertSessionOwnership(this.client, input.serverSessionId, input.projectId, input.teamId);\n        if (row.server_session_id && row.server_session_id !== input.serverSessionId) {\n          throw new Error('server_session_id must match the agent_event server_session_id');\n        }\n      }\n      return;\n    }\n\n    if (input.sourceType === 'session_summary') {\n      const sessionId = input.serverSessionId ?? input.sourceId;\n      await assertSessionOwnership(this.client, sessionId, input.projectId, input.teamId);\n      if (input.sourceId !== sessionId) {\n        throw new Error('session_summary source_id must equal server_session_id');\n      }\n      return;\n    }","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/storage/postgres/generation-jobs.ts#L243-L279","documentation":"Thrown by validateSource when creating/scoping an observation_generation_job with sourceType 'agent_event' but the referenced agent_event row doesn't exist for the given eventId+project_id+team_id, or when sourceId was provided alongside a differing agentEventId. This enforces tenancy: the source event must belong to the same project and team as the job.","triggerScenarios":"Job creation/input with sourceType 'agent_event' where (eventId = agentEventId ?? sourceId) has no row in agent_events for that project/team, or input.sourceId !== eventId (the two identifiers disagree).","commonSituations":"Caller passed an agentEventId from a different project/team (cross-tenant); typo in sourceId/agentEventId; the agent_event was deleted before job creation; mixing sourceId and agentEventId inconsistently.","solutions":["Verify the agent_event exists for the same project_id and team_id before creating the job (SELECT id FROM agent_events WHERE id=? AND project_id=? AND team_id=?).","Ensure sourceId and agentEventId refer to the same event (or omit agentEventId when it equals sourceId).","Confirm project_id/team_id on the request match the event's ownership; reject cross-tenant references early.","If the event was deleted, either re-create it or choose a valid source."],"exampleFix":"// before\nawait repo.create({ sourceType: 'agent_event', sourceId, agentEventId: otherId, projectId, teamId });\n\n// after\nconst eventId = agentEventId ?? sourceId;\nif (sourceId !== eventId) throw new Error('sourceId must match agentEventId');\nconst owned = await queryOne('SELECT id FROM agent_events WHERE id=$1 AND project_id=$2 AND team_id=$3', [eventId, projectId, teamId]);\nif (!owned) throw new Error('agent_event not owned by project/team');\nawait repo.create({ sourceType: 'agent_event', sourceId, projectId, teamId });","handlingStrategy":"validation","validationCode":"// Pre-validate ownership and id consistency\nconst eventId = input.agentEventId ?? input.sourceId;\nif (input.sourceId !== eventId) throw new Error('sourceId must equal agentEventId');\nconst row = await queryOne(\n  'SELECT id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',\n  [eventId, input.projectId, input.teamId]\n);\nif (!row) throw new Error('agent_event not owned by project/team');","typeGuard":null,"tryCatchPattern":"try {\n  await repo.create(input);\n} catch (err) {\n  if (err instanceof Error && /source_id must belong to project_id and team_id/.test(err.message)) {\n    return res.status(400).json({ error: 'Agent event not found for this project/team' });\n  }\n  throw err;\n}","preventionTips":["Always scope source lookups by project_id and team_id to enforce tenancy.","Keep sourceId and agentEventId consistent (or omit agentEventId when equal).","Validate the agent_event exists before creating the job to give a clear 400."],"tags":["postgres","jobs","tenancy","validation","ownership","database"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}