Design: Identity Mapping Schema and Token Convention
Date: 2026-04-02 Author: Pranav Narahari Ticket: EDE3-5 Status: Draft — pending engineer review before implementation (EDE3-7)
1. Purpose
Before any Slack message, Gmail thread, or Drive activity reaches the LLM or the COO’s UI, every real identity must be replaced with a stable, opaque token. This document defines:
- The Firestore schema for the mapping table
- The token naming convention (grounded in Eden’s actual org structure)
- Edge case rules for non-person accounts
- Storage location decision and rationale
- Update policy for new hires, departures, and role changes
2. Background: Eden’s Org Structure
Sourced from Admin Directory API (2026-04-02, 181 total accounts). Eden uses two domains as one org:
| Domain | Users | Primary use |
|---|---|---|
@tryeden.com | 78 | Legacy / original domain; Executives, Operations, US-based staff |
@eden.health | 103 | Newer domain; Pharmacy, BPO, Data/Eng, Marketing |
Both domains must be treated as a single identity namespace. A person on @tryeden.com and a person on @eden.health are in the same org but are distinct identities unless confirmed otherwise.
Org Units (source of truth for token prefix)
| Org Unit Path | Short label | Token prefix |
|---|---|---|
/Executives | Exec | Exec |
/Internal Operations/Data and Development | Engineering | Eng |
/Internal Operations/Accounting and Finance | Finance | Finance |
/Internal Operations/HR and Recruiting | HR | HR |
/Internal Operations (catch-all) | Operations | Ops |
/Marketing | Marketing | Mktg |
/Member Experience (non-BPO) | Member Experience | MX |
/Member Experience/BPO | BPO | BPO |
/PharmMedOps | Pharmacy/Med Ops | PharmOps |
/Pharmacy | Pharmacy | Pharmacy |
/IT and Security | IT | IT |
/Team India | Engineering (India) | Eng |
/Legacy Operations | Legacy Ops | Legacy |
| No dept / unassigned | Unknown | Unknown |
Special account types (non-person):
| Type | Token prefix |
|---|---|
| Shared / functional mailboxes | SharedMailbox |
| Service accounts / integrations | ServiceAcct |
| Distribution lists | DL |
| External contacts (not in directory) | External |
3. Mapping Schema
Firestore Collection: identity_map
One document per identity. Document ID = URL-safe base64 of the primary email (e.g. ZGFuaWVsQHRyeWVkZW4uY29t for daniel@tryeden.com). Using a derived ID prevents duplicate documents for the same person and enables O(1) lookup without a query.
identity_map/{doc_id}
Document Fields
| Field | Type | Description |
|---|---|---|
token | string | Stable anonymized token, e.g. Exec_1. Never changes. |
primary_email | string | Canonical email address used as the primary key |
alt_emails | string[] | Other known emails for the same person (e.g. both domains) |
slack_user_id | string | null | Slack user ID (e.g. U05TX3GBC1F). Null if no Slack account. |
slack_display_name | string | null | Slack display name at time of seeding (e.g. Daniel Dietz) |
full_name | string | null | Display name from Google directory |
org_unit | string | Google Workspace OU path at time of seeding (e.g. /Executives) |
token_prefix | string | The prefix component of the token (e.g. Exec) |
token_counter | number | The numeric component of the token (e.g. 1) |
account_type | string | person | shared_mailbox | service_account | distribution_list | external |
status | string | active | suspended | departed |
created_at | timestamp | When this mapping was first created |
updated_at | timestamp | Last metadata update (status changes, name changes). Token never changes. |
notes | string | null | Optional: reason for edge case handling, e.g. "functional inbox, not a person" |
Example Documents
// daniel@tryeden.com → Exec_1
{
"token": "Exec_1",
"primary_email": "daniel@tryeden.com",
"alt_emails": [],
"slack_user_id": "U05TX3GBC1F",
"slack_display_name": "Daniel Dietz",
"full_name": "Daniel Dietz",
"org_unit": "/Executives",
"token_prefix": "Exec",
"token_counter": 1,
"account_type": "person",
"status": "active",
"created_at": "2026-04-02T00:00:00Z",
"updated_at": "2026-04-02T00:00:00Z",
"notes": null
}
// care@tryeden.com → SharedMailbox_1
{
"token": "SharedMailbox_1",
"primary_email": "care@tryeden.com",
"alt_emails": [],
"slack_user_id": null,
"slack_display_name": null,
"full_name": null,
"org_unit": "/Member Experience/Operational Functions",
"token_prefix": "SharedMailbox",
"token_counter": 1,
"account_type": "shared_mailbox",
"status": "active",
"created_at": "2026-04-02T00:00:00Z",
"updated_at": "2026-04-02T00:00:00Z",
"notes": "Functional member experience inbox"
}Lookup Indexes (Firestore composite indexes required)
| Index | Use case |
|---|---|
primary_email | Redact emails in Gmail/Drive/Calendar output |
alt_emails (array-contains) | Handle secondary domain emails |
slack_user_id | Redact Slack <@USERID> mentions |
slack_display_name | Redact hydrated names in MCP search results |
token | Reverse lookup for debugging (admin only, never exposed to COO) |
4. Token Naming Convention
Format
{Prefix}_{Counter}
- Prefix — short, role-based label derived from the Google Workspace OU (see table in §2)
- Counter — monotonically increasing integer starting at 1, assigned in alphabetical order of
primary_emailwithin each prefix group at seeding time. Never reassigned, never reused.
Rules
- Tokens are permanent. Same person → same token forever, even if they change departments, change names, or leave.
- Counter is based on seeding order, not seniority or any meaningful attribute. This prevents inferring rank from the token.
- Prefix is based on OU at seeding time. If someone moves departments after seeding, their token prefix stays the same. Only new hires after that date get the new-department prefix.
- No collisions are possible because each prefix has its own counter namespace (
Exec_1andEng_1are different people).
Token assignments for known Executives (seeded from directory)
| Name | Token | |
|---|---|---|
adam@tryeden.com | Adam McBride (CEO) | Exec_1 |
brad@eden.health | Brad | Exec_2 |
daniel@tryeden.com | Daniel Dietz (COO) | Exec_3 |
jared@eden.health | Jared | Exec_4 |
jonah@tryeden.com | Jonah | Exec_5 |
josh@tryeden.com | Josh | Exec_6 |
jowenne.duymayan@eden.health | Jowenne Duymayan | Exec_7 |
mitesh@eden.health | Mitesh | Exec_8 |
rebecca@tryeden.com | Rebecca | Exec_9 |
Note: The COO (
daniel@tryeden.com) will seeExec_3in all redacted outputs. The full mapping is stored only in Firestore within Eden’s GCP project; the COO’s UI never surfaces it.
5. Edge Case Rules
| Scenario | Rule | Token prefix |
|---|---|---|
| Functional/shared mailboxes (care@, eden@, data@, disputes@, growth@, team@, etc.) | Detected by: no fullName in directory, OU = /Member Experience/Operational Functions or no dept, or email is a common-noun word. Assign SharedMailbox_N. | SharedMailbox |
| Service accounts / integrations (zapier.integration@, superadmin@, sebastian_villa@, emergency.admin@, user.archives@, devops1@) | Detected by: OU = /IT and Security/2FA Bypass or /Legacy Operations with no person-name email, or contains integration/admin/bot in local part. | ServiceAcct |
| Distribution lists (marketing_internal@, training.cohort@, collabs@, partners@, etc.) | Detected by: plural-noun or compound-noun email with no directory person record. | DL |
| Suspended users (cutter@, logan@, megan@, vanessa@, murali.prakash@) | Seed at mapping time with status: "departed". Token is still assigned and stable — it may appear in historical data. | Same prefix as their OU |
| Users with no OU / no dept (crislyn@, daelene@, denmark@, etc.) | Assign Unknown_N. Revisit on next identity map refresh if OU is populated. | Unknown |
| External contacts (people who appear in email threads / Slack DMs but are not in the directory) | Detected by: email domain not in {tryeden.com, eden.health}. Assign External_N at runtime when first encountered by the PII redaction layer. | External |
| Dual-domain same-person (e.g. adam.poma@eden.health and a hypothetical adam@tryeden.com for same person) | Cross-reference by first name + last name match across domains. If confirmed same person, link via alt_emails. Use the @eden.health address as primary_email for newer accounts; @tryeden.com for legacy. | Single token, two lookup entries |
| Bot / test accounts (test.user@eden.health, pharmacyintern@eden.health) | OU = /IT and Security/Testing or obviously named. Assign ServiceAcct_N. | ServiceAcct |
6. Storage Decision: Firestore
Chosen: Google Cloud Firestore (in Eden’s GCP project, same project as the Command Center)
Rationale
| Requirement | Firestore | Secret Manager | Encrypted JSON in GCS |
|---|---|---|---|
| Indexed lookup by email in <10ms | ✓ | ✗ (not a DB) | ✗ (full file load) |
| Indexed lookup by Slack user ID | ✓ | ✗ | ✗ |
| Array-contains query for alt_emails | ✓ | ✗ | ✗ |
| BAA compliance (stays in Eden GCP) | ✓ | ✓ | ✓ |
| No extra GCP service needed | ✗ (Firestore is separate) | ✓ | ✓ |
| Append new entries at runtime | ✓ | Awkward | Full file rewrite |
| Audit log of changes | ✓ (Firestore audit logs) | ✓ | Manual |
Every Slack message, email, or Drive item that passes through the PII redaction layer triggers at least one identity lookup. At M1 scale (~50 messages/session), even GCS would be fast enough with caching — but Firestore’s indexing and atomic writes make the external-contact append pattern (§5) safe without locking.
Bootstrap: Seed Firestore from the Admin Directory API on first deploy. Script lives at src/scripts/seed-identity-map.ts.
Backup: Export the Firestore collection to GCS weekly. The export is also the source of truth for the alt_emails cross-domain mapping (maintained manually by the Brainforge eng team, not auto-derived).
7. Update Policy
New hires
When a new user appears in the Admin Directory API (detected by the weekly refresh script), create a new document with the next available counter for their OU prefix. The counter always increments — never fills gaps from departed users.
Departures
Set status: "departed" and updated_at to departure date. Do not delete the document. Historical data (emails, Slack messages) may reference their token. The token remains valid for redaction of past data.
Role / department changes
Update org_unit and updated_at. Do not change the token. The prefix reflects the original OU at seeding time. This is intentional: it prevents token instability and avoids re-processing historical data.
Name changes
Update full_name, slack_display_name, and updated_at. Add the old name to a former_names: string[] field (add this field when first needed). The PII redaction layer must check both current and former names.
New external contacts
When the PII redaction layer encounters an email domain not in {tryeden.com, eden.health}, it calls a createExternalIdentity() helper that:
- Checks if the email already exists in
identity_map - If not, creates a new document with
token: "External_N"(N = next available external counter, read from acounters/externaldocument in Firestore) - Returns the token immediately for use in the current redaction pass
Slack display name drift
Slack display names change independently of Google directory names. The weekly refresh script re-fetches Slack user profiles and updates slack_display_name in Firestore. The old name is kept in former_names so historical MCP search results can still be redacted.
8. Counter Management
A separate Firestore document tracks the next available counter per prefix:
counters/{prefix} → { next: number }
Example: counters/Exec → { next: 10 } means the next Exec token will be Exec_10.
Increments are done as Firestore atomic transactions to prevent race conditions during concurrent seeding.
9. What This Design Does NOT Cover
- Implementing the lookup function — EDE3-7
- Building the PII redaction middleware — EDE3-8
- The seeding script — part of EDE3-7 implementation
- Key rotation or token re-mapping — explicitly out of scope; tokens are permanent by design
- Exposing the reverse mapping to the COO — never; the mapping is admin-only
10. Review Checklist
- Schema reviewed by AI tech lead (Sam)
- Token prefix list confirmed against Eden org chart with CSO
- Dual-domain cross-reference rules reviewed (edge case §5)
- Firestore project location confirmed (Eden GCP project, not Brainforge)
- BAA coverage for Firestore confirmed with Eden legal / Adam