Skip to content
Ritesh FirodiyaGet in touch

Work / Chitragupt / Wiki / Concepts

user-identity-entry

Conceptcanonicalverified 2026-07-17

CONCEPT.USER-IDENTITY-ENTRY

User → Identity → Document → Entry

Summary

Every parsed fact in chitragupt lives on one chain — a Firebase user owns one "self" identity plus many entity identities (banks, brokers, employers, insurers, loans, properties, retirement accounts, businesses, conditions, ID docs); every uploaded document points to one identity; every parsed entry inherits that identity plus a bucket + a strict-enum entry type.

Why it matters

Before this model, three collides masqueraded as one: the login owner's PAN lived on users/{uid} root; entities lived in accounts/; the matcher's per-doc identity_extracted snapshot lived on every documents/{docId} but never rolled up. Every callable that read PAN had two code paths, mappers emitted free-form entry_type strings that shipped silent-orphan bugs before a contract test caught them, and the Inbox folder tree grouped by a fourth ad-hoc taxonomy. Collapsing to one chain removes all four sources of drift.

Chitragupt is upload-only (upload-only · no-manual-entry) — the parser is the only writer of facts. Every fact therefore needs a single stable home: which user, which identity, which document produced it, which bucket it belongs to. That is what this concept enforces.

Implications

  • Exactly one identity per user has type: "self" (deterministic id "self"). It holds the login owner's PAN, DOB, full name, and residential status. users/{uid} never stores those fields again. Self is the only hand-editable identity — the user edits it on Settings › People & accounts (backed by upsertIdentity with id = "self").
  • Every entity identity lives under one of the 12 top-level buckets documented in identity-buckets. No new bucket without an ADR.
  • Entity identities are upload-only — a non-self identity is minted only as a side-effect of the parser calling ensureAcceptedIdentity during document ingestion, per 2026-07-17-identity-creation-is-upload-only. There is no "+ Add bank / Add employer / …" affordance in the UI. Settings › People & accounts renders non-self identities read-only, with a Delete affordance guarded by a document-count precondition (delete blocked if any doc still references the identity — otherwise the FK dangles and every rollup loses those docs).
  • Every document has identity_id (the entity it came from — or "self" for docs like 26AS/AIS/ITR-V), bucket (denormalised for fast Inbox queries), and about_self (default true — false only when the doc is about an entity, not the taxpayer).
  • Every ledger entry has identity_id, bucket, and an entry_type from the strict enum in ledger-entry-types. Free-form entry_type is forbidden.
  • Multi-person households use Pro Family (multiple Firebase logins with independent DPDP consent — family-workspace). No "dependent-without-login" model exists.
  • The matcher (parser/identity-matcher.ts) matches extracted attributes (PAN, TAN, IFSC, account_last4, folio, client_id, …) against all users/{uid}/identities/* in one query. On mismatch the doc goes to pending_identity_confirmation — a single status that replaced the prior pending_owner_confirmation | pending_family_transfer | pending_entity_confirmation triad. User accepts (grows the identities), transfers to a family member (existing two-sided flow), or rejects.
  • Every recompute — tax review, expense review, portfolio review, capital gains — reads from ledger entries filtered by identity_id + AY / FY. Tax review sources login-owner PAN from identities/self, never from users/{uid}.
  • CA share scope becomes identity_ids[] (not account_ids[]). Rules enforce the scope at the read.

Related

Sources

  • .context/wiki/concepts/* § "User → Identity → Document → Entry"
  • packages/shared/src/schemas/identity.ts
  • apps/functions/src/parser/identity-matcher.ts

Every project of mine is written down like this.

Read the résumé