Symptom
Schedules were moved to an external service and confirmed working. Five identical entries were still sitting in the old scheduler. Notifications went out twice and queue jobs overlapped.
Why you stop early
★★ Adding shows an immediate effect; removing shows none. The moment the new path is confirmed running, the job feels finished.
★★★ And duplication does not look like a fault. Two notifications is excess function, not an error. There is nothing in the logs. The only discovery path is somebody wondering "why did this arrive twice?"
⚠️ ★★★ And the auto-restore mechanism puts the removal back
The old scheduler had protection-list-based auto-restore on it. A deleted line still present in that list comes back on the next pass.
★★★ Auto-restore pins down "what the canonical state is." When the canonical state changes, the restorer has to hear about it first.
★★ One more in this collection's a safeguard creates the next trap family — but a different shape. The earlier ones had the safeguard creating a trap or switching itself off; this time it worked exactly as designed and that was the problem.
★★★ In a system with a restoring mechanism, every change is two changes — the target and the baseline.
Migration checklist
grep out every file that implements the thing you are moving★ Skip 4 and yesterday's defense blocks today's change, and finding out why costs time.
⚠️ ★★★ Follow-up — the first version of this checklist had no step 0 or 6, and an omission happened right there
The same migration produced duplication and omission at once. Five entries left behind in the old place caused duplication; one account's implementation lived in a separate file, never made it to the new place, and caused an omission.
★★★ Opposite symptoms, one cause — we never counted the implementation files. We knew what we were moving by its feature name, and that feature was two files.
★★★ The unit of migration is the file list, not the feature name. ★ Filename suffixes (_v2, _new, an account or environment name) are very likely siblings of the same feature. Counting them is step 0.
★★★ And omission is far less visible than duplication
Duplication announces itself as "why did this arrive twice?" Omission produces nothing at all. If the affected target is naturally infrequent, it is indistinguishable from a normal quiet spell — exactly the wall described in the "zero is sometimes correct" entry above.
★★ So step 6 must be a diff, not an observation. Don't wait for something to arrive in the new place — put step 0's list and the new registration list side by side and match the counts.
★ The root was building a per-account variant by copying the file. A variant made by copying breaks the feature boundary — fix one and the other stays unfixed, and without a list you forget it exists at all. Keep the difference in an argument or a config value, not a file.