Skip to content

Commit 24d8810

Browse files
jordaneclaude
andcommitted
Scope committee_member.sfid forward mapping key by committee
The v2->v1 create path keyed the forward mapping on the v1 API MemberID (contact SFID) alone, which the same contact reuses across every committee it belongs to. Adding that contact to a second committee silently overwrote the first committee's forward mapping, orphaning it so a later v1-WAL delete couldn't find it. Scope the create-path key by committee SFID and update the delete path to look up the scoped key. Add a one-shot backfill to migrate existing flat keys to the scoped format and tombstone stale entries whose committee can no longer be resolved. Issue: LFXV2-2709 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jordan Evans <jevans@linuxfoundation.org>
1 parent 099e6b6 commit 24d8810

6 files changed

Lines changed: 710 additions & 26 deletions

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ The following table shows the supported mapping key patterns and their expected
8484
| v1→v2 | `committee.sfid.{v1_sfid}` | `committee.sfid.123e4567-e89b-12d3-a456-426614174003` | `{v2_uuid}` | Committee SFID to UUID |
8585
| v2→v1 | `committee.uid.{v2_uuid}` | `committee.uid.123e4567-e89b-12d3-a456-426614174001` | `{project_sfid}:{committee_sfid}` | Committee UUID to compound SFID |
8686
| **Committee Members** |
87-
| v1→v2 | `committee_member.sfid.{v1_sfid}` | `committee_member.sfid.123e4567-e89b-12d3-a456-426614174004` | `{committee_uuid}:{member_uuid}` | Member SFID to compound UUID |
87+
| v1→v2 | `committee_member.sfid.{v1_sfid}` | `committee_member.sfid.123e4567-e89b-12d3-a456-426614174004` | `{committee_uuid}:{member_uuid}` | Member record SFID to compound UUID |
88+
| v1→v2 | `committee_member.sfid.{committee_sfid}.{member_sfid}` | `committee_member.sfid.a0941000002wBjEAAU.0034100000abcDEAAY` | `{committee_uuid}:{member_uuid}` | Member SFID to compound UUID, scoped by committee since the v1 API MemberID (contact SFID) is reused across committees for the same contact (LFXV2-2709) |
8889
| v2→v1 | `committee_member.uid.{v2_member_uuid}` | `committee_member.uid.123e4567-e89b-12d3-a456-426614174002` | `{project_sfid}:{committee_sfid}:{member_sfid}` | Member UUID to compound SFID |
8990

9091
### User SFID Lookup API
@@ -194,6 +195,47 @@ Locally (with NATS port-forwarded):
194195
lfx-v1-sync-helper --backfill-committee-member-mappings [--dry-run]
195196
```
196197

198+
**Committee-member forward-mapping migration (`--backfill-committee-member-forward-mappings`):**
199+
200+
Migrates committee-member forward mappings written by the v2→v1 create path
201+
(`committee_member.sfid.{v1_sfid}`, keyed only on the contact SFID) to the committee-scoped
202+
format `committee_member.sfid.{committee_sfid}.{member_sfid}` — the fix for LFXV2-2709, where an
203+
unscoped key let MemberID reuse across committees silently overwrite an earlier committee's
204+
mapping. Skips v1-ingest forward keys (record sfid, a UUID) untouched. Because the collision
205+
already collapsed colliding committees' entries into a single flat key before this fix shipped,
206+
only the surviving (last-write) entry per contact can be migrated — this is cleanup, not data
207+
recovery. Writes are optimistic-concurrency guarded against the live sync-helper, idempotent, and
208+
safe to re-run.
209+
210+
**Rollout note:** with the default single-replica `RollingUpdate` deployment, a new pod (writing
211+
the committee-scoped key) can briefly run alongside an old pod (whose delete handling only
212+
checks the pre-fix flat key), which can orphan a scoped mapping that this backfill's flat-key
213+
scan can't find. Deploy this release with `kubectl rollout restart` preceded by scaling to 0
214+
replicas (or equivalent `maxUnavailable: 0` / `maxSurge: 0` old→new cutover) rather than a
215+
default rolling update, to avoid old and new pods processing events concurrently.
216+
217+
Run once after deploying the fix, to migrate already-affected mappings. A dry-run pass is
218+
recommended first:
219+
220+
```sh
221+
kubectl --context lfx-v2-prod -n v1-sync-helper apply -f manifests/backfill-committee-member-forward-mappings-job.yaml
222+
```
223+
224+
Add `--dry-run` to the manifest args, apply, inspect logs
225+
(`inspected`/`migrated`/`skipped_record_key`/`malformed`/`tombstoned`/`unresolved`/`conflicted`
226+
counts). A Job's pod template is immutable, so delete the dry-run Job before removing
227+
`--dry-run` and re-applying for the live run:
228+
229+
```sh
230+
kubectl --context lfx-v2-prod -n v1-sync-helper delete job backfill-committee-member-forward-mappings
231+
```
232+
233+
Locally (with NATS port-forwarded):
234+
235+
```sh
236+
lfx-v1-sync-helper --backfill-committee-member-forward-mappings [--dry-run]
237+
```
238+
197239
## Architecture Diagrams
198240

199241
Regarding the following sequence diagrams:

0 commit comments

Comments
 (0)