Skip to content
Ritesh FirodiyaGet in touch

Work / Chitragupt / Wiki / Synthesis

document-status-graph

Synthesiscanonicalverified 2026-07-19

SYNTHESIS.DOCUMENT-STATUS-GRAPH

Document status — full state graph

Summary

The persisted DocumentStatus enum has 15 values (packages/shared/src/schemas/document.ts), guarded by a legal-transitions table in apps/functions/src/_lib/document-status.ts. Every docRef.update({ status: X }) site in the functions codebase routes through assertTransition(from, to) before writing. This page is the human-readable version of the same graph — refer to the code file as the authoritative source when they diverge.

Built from

  • inbox-document-status — the 15-value enum
  • upload-only — the concept that makes the graph load-bearing
  • apps/functions/src/_lib/document-status.ts — the enforced table
  • apps/functions/src/__tests__/document-status.test.ts — 72 tests

Status classes

Class Members Notes
Seed _new (sentinel, not persisted) Only ingest handlers legally write from _new
Midstream parse_pending, view_only, unreviewed, needs_classification, needs_ocr, pending_password, pending_identity_confirmation The doc is being processed or awaiting user input
Terminal — happy confirmed Ledger fan-out ran; the row is live in the pillars
Terminal — rejection rejected_format, rejected_quota, rejected_duplicate, rejected_oversized, rejected_owner_denied, rejected_transfer_denied, rejected_parser_error No outbound edges except the two documented undos
Terminal — supersede superseded The row was replaced by a newer version; ledger entries reconciled

Legal transitions (target ← sources)

Target Legal sources
parse_pending _new, all midstream, confirmed (rescan resets to parse)
view_only _new
unreviewed parse_pending, view_only, pending_password, pending_identity_confirmation, needs_classification, needs_ocr, unreviewed, _new (family-transfer accept only)
needs_classification parse_pending, view_only, needs_classification
needs_ocr parse_pending, view_only, needs_ocr
pending_password parse_pending, view_only, pending_password
pending_identity_confirmation parse_pending, view_only, unreviewed, rejected_owner_denied (restore), pending_identity_confirmation
confirmed unreviewed, pending_identity_confirmation
rejected_quota _new
rejected_oversized _new, view_only (backfill oversize)
rejected_format parse_pending, view_only, pending_password, needs_classification, needs_ocr
rejected_duplicate all midstream
rejected_owner_denied pending_identity_confirmation, rejected_transfer_denied
rejected_transfer_denied pending_identity_confirmation
rejected_parser_error parse_pending
superseded all midstream, confirmed

Undo paths

  • rejected_owner_denied → pending_identity_confirmation — restoreOwnership callable. Undoes an accidental deny before the 7-day sweep deletes the row.
  • rejected_transfer_denied → rejected_owner_denied — denyOwnership callable. Uploader re-denies a bounced family-transfer.

These are the ONLY documented outbound edges from terminal-rejection states. Everything else is a bug — the test suite Graph invariants — no orphans, no dead ends asserts this at CI.

Canonical documented chains

  • Happy path: _new → parse_pending → unreviewed → confirmed
  • Free tier: _new → view_only → (backfill) → unreviewed → confirmed
  • Identity mismatch → restore: _new → parse_pending → pending_identity_confirmation → rejected_owner_denied → pending_identity_confirmation → unreviewed → confirmed
  • Bounced transfer → owner-denied: _new → parse_pending → pending_identity_confirmation → rejected_transfer_denied → rejected_owner_denied
  • Encrypted PDF: _new → parse_pending → pending_password → unreviewed → confirmed
  • Parse rot: _new → parse_pending × 5 → rejected_parser_error
  • Rescan on confirmed: confirmed → parse_pending → unreviewed → confirmed
  • Dedup exact_duplicate: _new → parse_pending → unreviewed → rejected_duplicate
  • Supersede on confirmed: confirmed → superseded

Enforcement layers

  1. Zod: DocumentStatusSchema.parse(status) at every schema boundary rejects unknown values.
  2. assertTransition: called before every persisted status write; throws HttpsError("failed-precondition", "Illegal document_status transition: {from} → {to}") on mismatch.
  3. Firestore rules: every user-visible collection is allow write: if false — the client cannot forge a status.
  4. Tests: 72 unit + integration tests including graph invariants, documented chains, terminal invariants.

Related

Last refresh

2026-07-19 — created after the state-machine work in commits dcb2b893, 4a933f28, 4e25a2d3.

Every project of mine is written down like this.

Read the résumé