CedarCopilot wants to merge 1 commit into staging from fix/meeting-transcript-cross-user-connection
Live on prod, 1 of 3 behaviors degraded
Post-deploy production telemetry shows 1,457 meeting S3 hydration attempts (worker <id>; API 346) and 1,157 successful direct-key hydrations (worker <id>; API 250), including repeated FULL CONTEXT RESTORED logs. However, the required legacy fallback has no positive evidence: 0.
Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.
Libra has verdicts on 1 of 3 tracked behaviors on prod; 2 are still being checked. Libra checks hourly for 3 days after each deploy.
getEventRawContentByIds and hydrateExecutionEvents (apps/server/src/services/crm/events.ts) resolved a meeting's S3 object by enumerating the querying user's own connection IDs and guessing ${connectionId}/${meetingId}.json for each , a guess that only works when the meeting was ingested under a connection the querying user actually owns.crm_meeting_events.transcription_key already holds the exact key set at ingestion time (storeMeetingInS3 writes ${ingestingConnectionId}/${meetingId}.json), but the SELECT in getEventRawContentByIds never read it, so the code had no choice but to guess.getMeetingFromS3ByKey (apps/server/src/pipelines.ts) and a shared resolveMeetingS3Object helper (apps/server/src/services/crm/events.ts) that try the recorded key directly first, falling back to the connection-ID-guessing loop (findMeetingInS3) only when no key is on record , this preserves correctness for older rows from before the column was reliably populated.getEventRawContentByIds's meeting branch and hydrateExecutionEvents's meeting branch) now use the shared resolver and log which path resolved the object (direct-key vs guessed-connection) via createStructuredLog, so a regression of this exact bug shows up in structured logs instead of requiring raw SQL forensics to catch.findMeetingInS3/getMeetingFromS3ByKey/resolveMeetingS3Object to a new MeetingsBucketEnv type (Pick<CedarRuntimeEnv, 'MEETINGS_BUCKET' | 'MEETINGS_BUCKET_FALLBACK'>) instead of the full runtime env, since that's all they touch , this also makes them cleanly unit-testable with an in-memory bucket instead of a full env fake.No real customer data was touched , the Warp/Kendall/Manveer case is diagnostic only; the code fix alone makes future reads of that row resolve correctly.
apps/server/src/services/crm/__tests__/meeting-s3-cross-user-resolution.test.ts pins the exact cross-user scenario with synthetic ids: a transcriptionKey set to otherUsersConnectionId/meetingId.json, the querying user's own connection IDs excluding that connection, and an S3 mock populated only at the transcriptionKey path , asserts the transcript is still retrieved via direct-key. Also covers: guess-only would have failed (sanity check on the fixture), fallback-to-guessing still works when no key is recorded, and a clean null when neither path resolves.pnpm --filter @zero/server exec vitest run src/services/crm/__tests__/meeting-s3-cross-user-resolution.test.ts , 4/4 passedfindMeetingInS3/conversation hydration , all passedpnpm --filter @zero/server run types , cleaneslint on touched files , cleanš¤ Generated with Claude Code
https://claude.ai/code/session_01YZB5RgfxHLmuKtWof2wP4g
This PR adds authoritative meeting-transcript lookup by the recorded S3 key, retains connection-ID guessing for legacy rows, wires the resolver into raw-content and execution hydration, and adds focused resolver tests and structured resolution logging. The recorded-key approach fixes legitimate cross-user conversation reads, but using a client-writable key directly introduces an object-level authorization gap.
getMeetingFromS3ByKey with primary/fallback bucket support.Libra has not measured any production surfaces for this change yet.