{"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":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":270,"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":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/generation-jobs.ts#L252-L288","documentation":"validateSource enforces that the agent_event a generation job references actually exists and is scoped to the same project_id and team_id as the job being created. If the SELECT returns no row, or input.sourceId differs from the validated eventId, creation is aborted. This prevents cross-project/cross-team data leakage through generation-job sources.","triggerScenarios":"Calling create() with sourceType 'agent_event' where the source_id does not exist in agent_events, or exists under a different project_id/team_id, or the supplied sourceId string does not match the event id used for the ownership query.","commonSituations":"Client passes an event id from another tenant; stale event id after a data deletion; mixing up sourceId with another field in the request payload; multi-team setup where the caller cached ids from a previous team context.","solutions":["Confirm the agent_event exists in the same project_id and team_id as the generation job before creating it","Query the event scoped to your project/team first and use its id as sourceId verbatim","Check you are not mixing tenant contexts (team_id/project_id from different requests)","If the event was deleted, re-create or re-emit the event before creating the generation job"],"exampleFix":"// before\nawait jobs.create({ sourceType: 'agent_event', sourceId: eventId, projectId: 'p1', teamId: 't1' });\n// after\nconst event = await eventsRepository.getByIdForScope(eventId, 'p1', 't1');\nif (!event) throw new Error('event not found for scope');\nawait jobs.create({ sourceType: 'agent_event', sourceId: event.id, projectId: 'p1', teamId: 't1' });","handlingStrategy":"validation","validationCode":"const event = await client.query('SELECT id FROM agent_events WHERE id=$1 AND project_id=$2 AND team_id=$3', [eventId, projectId, teamId]);\nif (event.rowCount === 0) throw new Error('agent_event not found in scope');","typeGuard":null,"tryCatchPattern":"try {\n  await jobs.create(input);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('agent_event source_id must belong')) {\n    throw new ScopeError('Referenced agent_event does not exist in this project/team');\n  }\n  throw err;\n}","preventionTips":["Always resolve source ids through scoped (project/team-filtered) queries","Never cache event ids across team/project switches","Validate ids belong to the current tenant in your API layer before hitting the repository"],"tags":["postgres","multi-tenancy","validation"],"backgroundTag":"resource-not-found","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}