Skip to content

Contacts re-key — random-UID doc-ids + remove the abbreviations collection

Why

Follow-up revision to T-048 (contacts unification — done). The live model uses slug doc-ids and a separate abbreviations lookup collection; the owner wants opaque random-UID doc-ids and the abbreviations collection removed. Contract: docs/contacts-unification-design.md → "OWNER REVISIONS 2026-06-21".

Scope

  1. Re-key individuals (28 live docs) — and organizations (19), pending owner confirm — from slug ids to random UIDs; remap every reference: organizations.representativeIds[], individuals.system.repOfCompanyIds[] / affiliatedCompanyId, operational individualId links (payees/Students), the users bridge, and the boot-critical director registry.
  2. Remove the abbreviations collection (41 docs). Keep the editable abbreviation field on the individual; match / auto-populate by querying individuals by that field (+ an aliases[] array for superseded codes). Delete the lookup write-path (in lib/individuals/abbreviations.server.ts + the swap logic in crud.server.ts).
  3. Execute as an idempotent script: JSON backup → dry-run → confirm-before-delete → verify (counts, references intact, director registry resolves to JC+JN, abbreviation matching works, app serves).

Decision log

Retrofit note (added 2026-06-21 by Accounting (Diagnostics)): every entry below was authored by the EOP Local Assistance agent, per its Attestation (EOP Local Assistance): byline. The **Source:** <name> · <session URL> convention was added to AGENTS.md mid-2026-06-21 — after EOP Local Assistance had already closed this task — so its session URL is not recorded here. If EOP Local Assistance re-opens this task in a future session it can append its own Source line. Source (edit): Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea

2026-06-21 — task opened, contract revised

  • Attestation (EOP Local Assistance): read AGENTS.md and scanned the board — this is a new-scope follow-up to T-048 (re-key + drop abbreviations), not a duplicate of its original unify scope.
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • What changed: opened this task; recorded the doc-id + abbreviations revision in the design contract.
  • Proposed by: the owner. Approved by: the owner (same person — the human directing the session).
  • Rationale: slug ids encode the person's name/style (and Firestore docs can't be renamed, so the id must be opaque + permanent); the separate abbreviations lookup is redundant overhead when an abbreviation can be resolved by querying the individuals collection directly.
  • Evidence — owner, 2026-06-21 (verbatim; conversation links aren't available, so quoted):

    "the individual's document name should be a random UID instead of something like alfred-hui or, alison-tang as these naming are of character (style) … while documents cannot be renamed, so they should always be a Unique ID instead of the way they are now."

"for places that would allow user to type in their abbreviations for auto-populated information, the web app should still allow the matching and fetching using the individuals abbreviation. Therefore, the abbreviation collections should be removed as the existence of it makes things HIGHLY inefficient." - Open question (owner): re-key organizations too (same slug pattern, e.g. org-16thproductionslimited), or individuals only? - Live state verified 2026-06-21 (Admin SDK via the NAS channel): aote-system/individuals=28 (slug ids), abbreviations=41, organizations=19 (slug ids), users=4 (auth uids); tebs-epl/payees=2, clients=20; tebs-mel/clients=1, Students=11.

2026-06-21 — scope confirmed (include directors + orgs); dry-run verified; ready to cut over

  • Attestation (EOP Local Assistance): read AGENTS.md; checked the board; tracking T-081 (kept current).
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • Scope (owner-confirmed): re-key all 28 individuals (incl. both directors) + all 19 organizations slug→random-UID; delete the 41-doc abbreviations collection and resolve abbreviations by querying the individuals field instead.
  • Dry-run (scripts/rekey-contacts.ts, read-only): integrity clean; blast radius = org.representativeIds(16) ↔ individual.repOfCompanyIds(16), abbreviations.individualId(41, deleted), and 32/32 signed WOPC docs whose closingDirectorId = jake-ngai (the cross-issuing director). Reverse refs in payees/Students/users: none. Backup → scripts/migration-backups/.
  • Director coupling (the risk): re-keying directors also requires patching lib/directors/registry.ts constants + the 32 WOPC closingDirectorId + the boot consistency check. Those consumers (assertDirectorsConsistent, getDirectorById) are hit only during WOPC signing (lib/wopc/signingRequests/operations.server.ts), not on boot — so a mid-cutover window degrades WOPC-signing + abbreviation-autofill only, not general use.
  • Proposed by: EOP Local Assistance — recommended exempting directors (their slug is a stable key in 32 signed WOPCs; the "rename" problem doesn't apply to code-seeded directors). Approved by: the owner, who chose to include them after seeing the coupling.
  • Evidence — owner, 2026-06-21 (verbatim):

    "include and keep name"

…answering "exempt the directors … or still include them (and I carefully migrate the 32 signed WOPCs + registry too)?" - Cutover runbook: backup → npx tsx scripts/rekey-contacts.ts --execute (re-key + WOPC + delete abbreviations) → patch registry.ts to the new director UIDs + switch abbreviation resolution to query individuals + remove abbreviation write paths → tsc → redeploy NAS → verify (consistency, typeahead, app). Rollback = restore from the JSON backup.

2026-06-21 — FULL INCLUDE confirmed; coupled code done (tsc-clean); image building

  • Attestation (EOP Local Assistance): read AGENTS.md; checked the board; tracking T-081 (kept current).
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • Director scope escalated to FULL INCLUDE. A deeper live scan found the director slug is a cross-subsystem stable key far beyond the 32 WOPCs: 32 WOPC closingDirectorId + 44 wopcSigningRequests.assignedDirectorId + 129 signing audit-log events (directorId) = ~205 signed/audit records. EOP Local Assistance recommended exempting the directors (re-keying them mutates the financial audit trail for no goal-relevant gain); the owner chose full include.
  • Evidence — owner, 2026-06-21 (verbatim): answered "Full include" to

    "Re-keying directors actually touches ~205 signed/audit records (incl. 129 immutable audit-log events), not just 32 WOPCs. How should I handle the 2 directors?"

…after earlier "include and keep name". - Coupled code (committed ff5cbf52, tsc-clean): registry.ts director ids → UIDs (oS1b0stJGPc0aCoFEb5N, E6g30MzNOkhzlCJwKvoe); abbreviation resolution + uniqueness now query individuals (abbreviations.server, payeeSync, merge/gather); crud mints random UIDs for new individuals/orgs; seedDirectors drops the abbr claim. Migration script extended to migrate the 44 signing requests + 129 events. Persisted mapping pins the director UIDs so code + data agree. - Cutover (in progress): NAS image building (tag nas-build-t081-160755eop-app:latest). Sequence = redeploy the new code FIRST (keeps typeahead working, avoids new orphans), then --execute the data migration back-to-back, then verify (director consistency / WOPC signing, typeahead, contacts load, no orphans).

2026-06-21 — DONE ✅ — cutover executed + verified (verdict + blast-radius handoff)

  • Attestation (EOP Local Assistance): read AGENTS.md; closing T-081 with the required done verdict + blast-radius note (per the rulebook's "Close the loop on done").
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)

Outcome / verdict (verified): - Re-keyed 28 individuals + 19 organizations slug → opaque Firestore UID; deleted the abbreviations collection (41 docs); remapped director slug → UID across all 205 cross-subsystem sites — 32 WOPC closingDirectorId, 44 wopcSigningRequests.assignedDirectorId, 129 signing audit-event directorId. Directors now oS1b0stJGPc0aCoFEb5N (JC) / E6g30MzNOkhzlCJwKvoe (JN). - Deploy-first (coupled code live, then data executed) → typeahead never broke, no new orphans. - Verification PASS: all individuals/orgs UID-keyed, 0 old slugs anywhere, abbreviations=0, director docs at the registry UIDs, query-based resolver works (JC/JN/AH resolve), referential integrity clean (representativeIdsrepOfCompanyIds), 0 orphaned slug refs in WOPC/signing/events. App healthy: both NAS containers up, clean logs ("Ready in 2.9s"), eop + p-eop → 200. LIVE IN PRODUCTION (eop.theestablishers.com via the NAS).

Commits / artifacts: code ff5cbf52, board 1f361dc9; data applied via scripts/rekey-contacts.ts --execute (Firestore writes — not a commit). Rollback JSON in scripts/migration-backups/. (Diff is in git; not re-narrated here.)

Blast-radius note for other agents: - Contact doc-ids are opaque UIDs now, not slugs. Anything that built or assumed a contact id from a name (jeffero-chan, org-…) breaks — always resolve via a query or a stored reference id. - The abbreviations collection is gone. Resolve abbreviations by querying individuals (abbreviation + basic.aliases[]) via resolveIndividualIdByAbbreviation — don't read the collection. - Director ids changed and propagated into signed WOPCs + signing requests + audit events. Accounting / WOPC / IR56M work: the director key is now a UID; lib/directors/registry.ts is the source of truth. The frozen payees/{JC|JN} abbreviations are UNCHANGED. - Rollback: scripts/migration-backups/rekey-contacts-2026-06-21T09-01-15-827Z.json.