{"id":"69f26ba9bd28ba8b2142b8e9","title":"2026 04 29 Issue 251","path":"how-to/committee/release-notes/2026-04-29-issue-251","contentMarkdown":"# 29-Apr-2026 — rebuild draft leader on copy and backfill stale walk_leader [closes #251](https://github.com/nbarrett/ngx-ramblers/issues/251)\n\n## [build 629](https://github.com/nbarrett/ngx-ramblers/actions/runs/25131677331) — [commit 59d78a4](https://github.com/nbarrett/ngx-ramblers/commit/59d78a43fa43e3c78e56d4d85477115e73dc0ecc)\n\n_____\n\nTwo related fixes for one root cause: the legacy walks schema's flat\ncontactId field was fanned out at migration time into four new-schema\nfields (groupEvent.walk_leader.id/name, fields.contactDetails.contactId,\nfields.publishing.ramblers.contactName), and the current edit UI only\nkeeps three of those four in sync when a leader is selected. Any stale\noverride on the legacy contactId therefore stayed embedded in\ngroupEvent.walk_leader indefinitely, and the copy-from-previous-walk\nflow then propagated it onto every walk copied from such a record.\n\nReported by Kerry O'Grady after Jenny Brown's Bearsted Circular walk\ncopied on 2026-04-19 came through with Nick Barrett as the Ramblers\ncontact. Tracing the affected 2026 walk and its 2024 source confirmed\nthe chain: a 2024 admin override of contactId, faithfully migrated, then\nfaithfully spread-merged forward in 2026.\n\nCode fix - projects/ngx-ramblers/src/app/pages/walks/walk-edit/walk-edit-copy-from.ts\n---\ncopyDetailsFromPreviousWalk() previously did\n{ ...targetFields, ...sourceFields }\n{ ...targetGroupEvent, ...sourceGroupEvent }\nwhich let the source walk's leader-shaped fields win the merge.\n\nNow, after the merge, rebuildLeaderIdentity re-derives every leader\nfield from the draft's own pre-merge contactDetails.memberId via\neventDefaultsService.memberToContact:\n\n- fields.contactDetails (memberId, contactId, displayName, email, phone)\n- fields.publishing.ramblers.contactName (member.contactId)\n- fields.publishing.meetup.contactName (null; meetup has its own flow)\n- groupEvent.walk_leader (Contact built from the current Member)\n\nThe publish booleans on both channels are preserved so the copier's\non/off choices survive. groupEvent.status is also deleted on the clone\nso the source walk's \"confirmed\" status does not silently promote the\nnew draft past its approval gate.\n\nThe previous deletes of fields.contactDetails, fields.publishing, and\ngroupEvent.walk_leader on the clone are no longer needed - the rebuild\nis now authoritative for all four leader fields, anchored on the\ndraft's pre-merge memberId.\n\nData backfill - server/lib/mongo/migrations/database/20260424000000-backfill-walk-leader-from-member.ts\n---\nThe code fix prevents new leakage but does not heal walks already in\nthe database with stale groupEvent.walk_leader from the migration-time\nfan-out. Idempotent backfill:\n\n- Scope: fields.inputSource = \"manually-created\" with a non-null\nfields.contactDetails.memberId. Ramblers-cached and file-import walks\nare deliberately out of scope - their walk_leader.id is a Ramblers\nWalks Manager contactId, not a Member _id, and rewriting it would\nbreak the Ramblers submission path.\n- For each walk, look up the Member by memberId and compute the\nexpected walk_leader using the same shape memberToContact produces\non new walks (is_overridden, id=member._id.toString(), name,\ntelephone, has_email).\n- Compare current walk_leader to expected field-by-field. Equal: skip.\nMissing or different: replace.\n- Walks whose memberId no longer resolves to a Member are logged and\nleft inert rather than silently cleared.\n\nRe-runs find every doc already matching and perform no writes, so the\nmigration is safe to replay during rollout or after partial runs.\nfields.contactDetails.contactId and fields.publishing.ramblers.contactName\nare intentionally not touched: the leader-toggle UI in walk-edit-leader.ts\nlets users diverge them from the member record on purpose, and the\nbackfill should not undo that.\n\nRuns automatically on deploy alongside the rest of the schema migrations\n- not registered under manualMigrations in migrations-config.ts. The\nbackfill is idempotent and the scope filter (manually-created walks\nwith a memberId) keeps it cheap on environments that have no affected\ndocuments, so there is no benefit to gating it behind an explicit\nadmin trigger.","contentHtml":"<h1>29-Apr-2026 — rebuild draft leader on copy and backfill stale walk_leader <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/251\">closes #251</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/25131677331\">build 629</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/59d78a43fa43e3c78e56d4d85477115e73dc0ecc\">commit 59d78a4</a></h2>\n<hr>\n<p>Two related fixes for one root cause: the legacy walks schema&#39;s flat\ncontactId field was fanned out at migration time into four new-schema\nfields (groupEvent.walk_leader.id/name, fields.contactDetails.contactId,\nfields.publishing.ramblers.contactName), and the current edit UI only\nkeeps three of those four in sync when a leader is selected. Any stale\noverride on the legacy contactId therefore stayed embedded in\ngroupEvent.walk_leader indefinitely, and the copy-from-previous-walk\nflow then propagated it onto every walk copied from such a record.</p>\n<p>Reported by Kerry O&#39;Grady after Jenny Brown&#39;s Bearsted Circular walk\ncopied on 2026-04-19 came through with Nick Barrett as the Ramblers\ncontact. Tracing the affected 2026 walk and its 2024 source confirmed\nthe chain: a 2024 admin override of contactId, faithfully migrated, then\nfaithfully spread-merged forward in 2026.</p>\n<h2>Code fix - projects/ngx-ramblers/src/app/pages/walks/walk-edit/walk-edit-copy-from.ts</h2>\n<p>copyDetailsFromPreviousWalk() previously did\n{ ...targetFields, ...sourceFields }\n{ ...targetGroupEvent, ...sourceGroupEvent }\nwhich let the source walk&#39;s leader-shaped fields win the merge.</p>\n<p>Now, after the merge, rebuildLeaderIdentity re-derives every leader\nfield from the draft&#39;s own pre-merge contactDetails.memberId via\neventDefaultsService.memberToContact:</p>\n<ul>\n<li>fields.contactDetails (memberId, contactId, displayName, email, phone)</li>\n<li>fields.publishing.ramblers.contactName (member.contactId)</li>\n<li>fields.publishing.meetup.contactName (null; meetup has its own flow)</li>\n<li>groupEvent.walk_leader (Contact built from the current Member)</li>\n</ul>\n<p>The publish booleans on both channels are preserved so the copier&#39;s\non/off choices survive. groupEvent.status is also deleted on the clone\nso the source walk&#39;s &quot;confirmed&quot; status does not silently promote the\nnew draft past its approval gate.</p>\n<p>The previous deletes of fields.contactDetails, fields.publishing, and\ngroupEvent.walk_leader on the clone are no longer needed - the rebuild\nis now authoritative for all four leader fields, anchored on the\ndraft&#39;s pre-merge memberId.</p>\n<h2>Data backfill - server/lib/mongo/migrations/database/20260424000000-backfill-walk-leader-from-member.ts</h2>\n<p>The code fix prevents new leakage but does not heal walks already in\nthe database with stale groupEvent.walk_leader from the migration-time\nfan-out. Idempotent backfill:</p>\n<ul>\n<li>Scope: fields.inputSource = &quot;manually-created&quot; with a non-null\nfields.contactDetails.memberId. Ramblers-cached and file-import walks\nare deliberately out of scope - their walk_leader.id is a Ramblers\nWalks Manager contactId, not a Member _id, and rewriting it would\nbreak the Ramblers submission path.</li>\n<li>For each walk, look up the Member by memberId and compute the\nexpected walk_leader using the same shape memberToContact produces\non new walks (is_overridden, id=member._id.toString(), name,\ntelephone, has_email).</li>\n<li>Compare current walk_leader to expected field-by-field. Equal: skip.\nMissing or different: replace.</li>\n<li>Walks whose memberId no longer resolves to a Member are logged and\nleft inert rather than silently cleared.</li>\n</ul>\n<p>Re-runs find every doc already matching and perform no writes, so the\nmigration is safe to replay during rollout or after partial runs.\nfields.contactDetails.contactId and fields.publishing.ramblers.contactName\nare intentionally not touched: the leader-toggle UI in walk-edit-leader.ts\nlets users diverge them from the member record on purpose, and the\nbackfill should not undo that.</p>\n<p>Runs automatically on deploy alongside the rest of the schema migrations</p>\n<ul>\n<li>not registered under manualMigrations in migrations-config.ts. The\nbackfill is idempotent and the scope filter (manually-created walks\nwith a memberId) keeps it cheap on environments that have no affected\ndocuments, so there is no benefit to gating it behind an explicit\nadmin trigger.</li>\n</ul>\n"}