Conversation
When indexing v1_meeting and v1_past_meeting objects, the committee ID
was being stored as the v1 Salesforce ID instead of the v2 UID. This
fixes all affected code paths (meeting update, mapping update, mapping
delete, past meeting update, past meeting mapping update, past meeting
mapping delete) to look up the v2 UID via the committee.sfid.{sfid}
mapping before sending to the indexer and access service.
Also fixes meeting.Committees not being populated on the meeting struct
before sending the indexer message, and adds a CommitteeUID field to
meetingInput for the resolved v2 UID of the primary committee field.
Generated with [Claude Code](https://claude.com/claude-code)
Signed-off-by: Andres Tobon <andrest2455@gmail.com>
There was a problem hiding this comment.
Pull request overview
Fixes committee identifier propagation when syncing/indexing v1 meetings by resolving v1 committee SFIDs to v2 committee UIDs before publishing indexer/access messages, and by adding an explicit v2 committee_uid field for the primary committee.
Changes:
- Add
CommitteeUID(committee_uid) tomeetingInputand wire it through JSON unmarshalling. - Resolve committee SFIDs to v2 UIDs via
committee.sfid.<sfid>mappings across meeting and past-meeting update/mapping update/delete flows. - Populate
meeting.Committees/pastMeeting.Committeesso committee UID tags can be emitted during indexing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| cmd/lfx-v1-sync-helper/models_meetings.go | Adds committee_uid to the meeting payload model and unmarshalling so primary committee can be represented as a v2 UID. |
| cmd/lfx-v1-sync-helper/handlers_meetings.go | Resolves committee SFIDs to v2 UIDs when building indexer/access messages and populates committee lists for tagging. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Committee is the ID of the committee | ||
| // It is a Global Secondary Index on the meeting table. | ||
| Committee string `json:"committee"` | ||
|
|
||
| // CommitteeUID is the v2 UID of the committee. | ||
| CommitteeUID string `json:"committee_uid,omitempty"` |
There was a problem hiding this comment.
The struct comment for Committee is now misleading: the code treats meeting.Committee as a v1 committee SFID (it’s used to look up committee.sfid.<sfid>), while CommitteeUID is the v2 UID. Update the Committee field comment to explicitly state it stores the v1 SFID (GSI key) to avoid future misuse.
| // Resolve the primary committee SFID to its v2 UID. | ||
| if meeting.Committee != "" { | ||
| committeeMappingKey := fmt.Sprintf("committee.sfid.%s", meeting.Committee) | ||
| if entry, err := mappingsKV.Get(ctx, committeeMappingKey); err == nil { |
There was a problem hiding this comment.
CommitteeUID resolution silently ignores mappingsKV.Get failures. If the mapping is missing or there’s a transient KV error, CommitteeUID stays empty with no signal, which can cause downstream indexing/access updates to lose committee association. Consider distinguishing not-found vs other errors and at least logging the error (or returning an error on non-notfound) so this doesn’t fail silently.
| if entry, err := mappingsKV.Get(ctx, committeeMappingKey); err == nil { | |
| entry, err := mappingsKV.Get(ctx, committeeMappingKey) | |
| if err != nil { | |
| slog.Error("failed to resolve committee UID from SFID", | |
| "committee_sfid", meeting.Committee, | |
| "mapping_key", committeeMappingKey, | |
| "error", err, | |
| ) | |
| } else { |
| // Resolve each committee's v1 SFID to its v2 UID via the committee.sfid mapping. | ||
| for _, cm := range committeeMappings { | ||
| committeeMappingKey := fmt.Sprintf("committee.sfid.%s", cm.CommitteeID) | ||
| if entry, err := mappingsKV.Get(ctx, committeeMappingKey); err == nil { | ||
| committeeUID := string(entry.Value()) | ||
| committees = append(committees, committeeUID) | ||
| meeting.Committees = append(meeting.Committees, Committee{ | ||
| UID: committeeUID, | ||
| AllowedVotingStatuses: cm.CommitteeFilters, | ||
| }) | ||
| } else { | ||
| funcLogger.With("committee_sfid", cm.CommitteeID).WarnContext(ctx, "committee SFID not found in mappings, skipping") | ||
| } |
There was a problem hiding this comment.
Committee SFID->UID lookups treat any mappingsKV.Get error as “not found” and continue. That means a transient KV issue can result in sending an indexer/access update with an incomplete committees list (effectively dropping committee tags/permissions). Consider handling non-notfound errors separately (log the error and abort this update), and include the underlying error in the log to aid debugging.
| // Try to get committee mappings from the index first | ||
| var committees []string | ||
| pastMeeting.Committees = []Committee{} | ||
| committeeMappings := make(map[string]mappingCommittee) | ||
| indexKey := fmt.Sprintf("v1-mappings.past-meeting-mappings.%s", uid) | ||
| indexEntry, err := mappingsKV.Get(ctx, indexKey) | ||
| if err == nil && indexEntry != nil { | ||
| if err := json.Unmarshal(indexEntry.Value(), &committeeMappings); err != nil { |
There was a problem hiding this comment.
In handleZoomPastMeetingUpdate, committee resolution/population happens after the indexer message + tags are already sent earlier in the function. That means the past-meeting document is still indexed with the original (likely v1 SFID) committee IDs or with no committee tags, and only the access message benefits from the resolved v2 UIDs. Move committee resolution (and getPastMeetingTags) before sendIndexerMessage so indexing and access updates stay consistent.
| // Build the committee list from the now-complete index, resolving each v1 SFID to its v2 UID. | ||
| committees := []string{} | ||
| meeting.Committees = []Committee{} | ||
| for _, committee := range committeeMappings { | ||
| committees = append(committees, committee.CommitteeID) | ||
| meeting.Committees = append(meeting.Committees, Committee{ | ||
| UID: committee.CommitteeID, | ||
| AllowedVotingStatuses: committee.CommitteeFilters, | ||
| }) | ||
| committeeMappingKey := fmt.Sprintf("committee.sfid.%s", committee.CommitteeID) | ||
| if entry, err := mappingsKV.Get(ctx, committeeMappingKey); err == nil { | ||
| committeeUID := string(entry.Value()) | ||
| committees = append(committees, committeeUID) | ||
| meeting.Committees = append(meeting.Committees, Committee{ | ||
| UID: committeeUID, | ||
| AllowedVotingStatuses: committee.CommitteeFilters, | ||
| }) | ||
| } else { | ||
| funcLogger.With("committee_sfid", committee.CommitteeID).WarnContext(ctx, "committee SFID not found in mappings, skipping") | ||
| } |
There was a problem hiding this comment.
This committee-resolution block performs one KV lookup per committee and duplicates the same SFID->UID resolution logic in several handlers (meeting update/mapping update/delete, past-meeting equivalents). Consider extracting a small helper that resolves SFIDs with a per-handler cache and shared error handling; this will reduce JetStream KV round-trips and keep behavior consistent across code paths.
Summary
v1_meetingandv1_past_meetingobjects, the committee ID was being stored as the v1 Salesforce ID instead of the v2 UIDcommittee.sfid.{sfid}mapping before sending to the indexer and access servicemeeting.Committeesnot being populated on the meeting struct before sending the indexer message, and adds aCommitteeUIDfield tomeetingInputfor the resolved v2 UID of the primary committee fieldTicket
LFXV2-1311
🤖 Generated with Claude Code