Skip to content
Ritesh FirodiyaGet in touch

Work / Chitragupt / Wiki / Decisions

2026-07-11-user-identity-entry-model

Decisioncanonicalverified 2026-09-12

DECISION.2026-07-11.USER-IDENTITY-ENTRY-MODEL

User → Identity → Document → Entry (12-bucket model)

Decision

Every parsed fact in chitragupt lives on a single chain:

User          = Firebase auth account (one PAN via self identity, subscription, DPDP consent, regime pref)
  └─ Identity = "self" (exactly one per user, holds this user's PAN + DOB + name)
                OR an entity relevant to the user
                  ~31 typed variants across 12 top-level buckets
       └─ Document = uploaded file + parsed fields + identity_id + about_self
             └─ Entry = one parsed fact (income line, TDS row, holding, txn, prescription)

Rename users/{uid}/accounts/ → users/{uid}/identities/. The typed discriminator becomes identity_type (~31 values). One reserved identity per user has type: "self" and holds the login-owner's PAN / DOB / full name (removed from users/{uid} root). Every document carries identity_id (the source entity) + bucket (denormalised for fast Inbox queries) + about_self. Every ledger entry carries identity_id + bucket + a strict-enum entry_type (no more free-form string).

The Inbox left rail is exactly 12 top-level buckets — Tax · Banks · Employer · Investments · Loans · Insurance · Health · Assets · Retirement · Business · Identity documents · Other. Every identity belongs to exactly one bucket. The Tax bucket is a virtual view (docs cross-listed by AY relevance).

Multi-Firebase-user family (Pro Family, up to 6 PANs) stays unchanged as the only multi-person model — dependents = their own login. No in-workspace person-identities.

Why

Today three ideas collide:

  1. Login owner carries their own PAN inline on users/{uid} — one truth for "self".
  2. Entities the user deals with live in the typed accounts/ table — another truth for "who is this doc from".
  3. identity_extracted snapshot on every document — a third truth, never rolled up, used only for matcher scratchpad.

Every callable that reads PAN has two code paths (users/{uid}.pan vs accounts/{id}.employer_pan); the parser has one identity discipline; the Inbox folder tree has a third. This drift caused named bugs (FormType enumerated in 4 places, category never re-derived after parse, mapper entry_type free-form).

Prior ADR 2026-05-31-accounts-master-table established the typed-discriminator primitive — this decision keeps that primitive and extends it: one uniform identities/ table where self is just another row. Prior ADR 2026-05-31-owner-validation established the accept-grows-identity matcher — this decision keeps the flow and renames the module (owner-validation → identity-matcher), collapsing three "pending" statuses into one pending_identity_confirmation.

Chitragupt is a zero-customer MVP; there is no cost to a clean rewrite. The no-legacy rule mandates a single-PR cutover with every account_id / users/{uid}.pan / three-status-set reference deleted, not left behind under a banner.

Impact

  • Schema (packages/shared/src/schemas/): account.ts → identity.ts; new buckets.ts, entry-types.ts; user-profile.ts shrinks (drops pan, dob_iso, display_name, residential_status); document.ts gains identity_id + bucket + about_self, collapses three "pending" statuses; ledger-entry.ts gains identity_id + bucket and enum-checks entry_type.
  • Storage (firebase/firestore.{rules,indexes.json}): new users/{uid}/identities/* rules; delete users/{uid}/accounts/**; CA share scope becomes identity_ids; new composite indexes on (identity_id, uploaded_at), (bucket, uploaded_at), (identity_id, ay, entry_class), (bucket, ay).
  • Backend (apps/functions/src/): parser/owner-validation.ts → parser/identity-matcher.ts; on-document-confirmed.ts + makeEntry extend signatures; all 34 mappers stamp identity_id + bucket; profile/save-profile.ts shrinks + new upsert-identity.ts; tax + CA-handoff PDF source PAN from identities/self; seed personas regenerated.
  • Website (apps/website/src/): store/accounts.ts → store/identities.ts; Inbox FolderTree + DocumentList rewritten around 12 buckets + 4-level tree; new settings/identities/ surface; onboarding step-1 writes to identities/self; settings/accounts/* deleted.
  • Wiki: this ADR + new pages user-identity-entry, identity-buckets, identity-types, ledger-entry-types; inbox-folder-taxonomy superseded by identity-buckets; family-workspace clarified to state dependents = own Firebase login only.
  • Wireframes: 12-bucket Inbox tree + new Settings/Identities page + updated onboarding step-1.

Locked-in scope decisions this ADR carries:

  • Dependents ≠ in-workspace identities. Multi-user family (Pro Family) is the only multi-person model.
  • No household_rollups. Household aggregates for Expense/Portfolio computed at read time.
  • Regime preference stays per-user (dependents have their own Firebase login → their own regime).
  • Health-pillar schema deferred — health_condition identity type exists in the schema so docs land in the Health bucket, but a dedicated Health page ships later.

Affects inbox-document-types, pillar-inbox, pillar-tax, pillar-expense, pillar-portfolio, inbox-pillar, upload-only, no-manual-entry, family-workspace, inbox-folder-taxonomy, 2026-05-31-accounts-master-table, 2026-05-31-owner-validation.

Cleanup progress (2026-09-12)

The account_id line of the aggressive-cleanup checklist is now zero across apps/, packages/, firebase/ and scripts/ (the only survivors are Razorpay's own account_id on its webhook payload and linked_account_id, which points at linked_broker_accounts — a real broker account, not an identity). The rename also closed a live bug: firestore.rules scoped the CA read on documents/{docId} using resource.data.account_id, a field DocumentSchema had already dropped, so an identity-scoped share matched no document at all. See ROADMAP.md Phase 8 for the itemised list and for what is still outstanding — chiefly sub-phase 3, which still leaves users/{uid}.pan / dob_iso / display_name / residential_status writable alongside identities/self.

Status

Active. Supersedes 2026-05-31-accounts-master-table and 2026-05-31-owner-validation.

Sources

  • .context/wiki/decisions/* § "User → Identity → Document → Entry (12-bucket model)"
  • packages/shared/src/schemas/identity.ts (post-rewrite)
  • apps/functions/src/parser/identity-matcher.ts (post-rewrite)

Every project of mine is written down like this.

Read the résumé