> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vinmake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload to posted journal entry

> The source-traced path from original bytes through Clerk extraction, master-data mapping, intake review and the ledger writer.

# Upload to posted journal entry

The upload path preserves one evidence identity, derives one reviewable structured result and, after two human decisions, posts one accounting fact.
The stages below name the artifact, its location and its job, then state what that stage refuses.

## 1. Land the original bytes

**Artifact, location, job.** The artifact is the original byte sequence; it lives in the private `documents` object-storage bucket at `raw-intake/sha256/<first two hex>/<sha256>`; its job is byte-exact evidence.
`RawIntakeLane.land` hashes the bytes, writes the content-addressed object, reads it back and requires both byte equality and SHA-256 equality before extraction can start.
The source states the boundary plainly: “Original customer bytes live in the existing private `documents` object-storage bucket and are addressed by SHA-256.”

The landing stage refuses an empty document, an invalid MIME type, an unexpected object reference and any round-trip byte or digest mismatch.
Equal bytes have one storage identity even when their filenames differ; the filename remains metadata.

## 2. Record the evidence identity

**Artifact, location, job.** The artifact is a `file_uploads` row in Postgres; its location is the verified organization's upload store; its job is to record the exact `bucket`, `object_path`, `source_storage_reference`, `source_sha256`, original filename, MIME type, size and uploader for the stored evidence.
The upload endpoint derives the organization and actor from its verified `DiscoveryPrincipal`, inserts the row, then returns the database id, storage linkage and a server-minted `upload_authority`.

The authority token binds the organization, verified principal, server session, file id, SHA-256 and storage reference.
The Clerk adoption path rechecks that binding, row ownership, storage reference, digest and the attachment bytes; it refuses a missing, expired, cross-user, cross-session, cross-organization or altered capability.
The browser cannot establish identity by sending an actor name of its own.

<Warning>
  At the web and backend `origin/main` revisions named on [Technical flows and authority](/greatbook/technical/index), this browser hop is not fully connected.
  The upload API returns `upload_authority`, but `worthstate-ui/src/lib/multimodal-utils.ts` keeps only `file_upload_id`, `bucket` and `object_path` in attachment metadata.
  `agents/clerk/files.py` requires the missing authority to adopt that persisted row, so the browser upload-to-Clerk adoption chain is untraced at this boundary rather than claimed as working.
  The Clerk's direct attachment fallback can create and authorize its own row, but that is a different source path.
</Warning>

## 3. Extract without replacing the evidence

**Artifact, location, job.** The artifact is the Clerk graph state containing extracted `fields`, `lines` and source provenance; its location is the durable Clerk run until the terminal queue write; its job is to turn the document into typed candidates while retaining the original storage reference and digest.
The graph runs `ingest → classify → load_registry → sop_retrieve → extract` before mapping.
The verified submitter is stamped by the server-side web proxy on the Clerk run; `source_file_id` links the run and eventual queue row back to `file_uploads`.

The Clerk refuses to guess an unknown or low-confidence document type.
It can attempt at most two shape-only repairs, and it does not silently repair semantic doubts such as an unknown master, an impossible date or a suspected duplicate.

No raw Markdown artifact is written here.
If the optional Clerk retrieval store is configured, `CogneeExtractedTextStore` publishes a `.txt` document containing extracted text and the original storage lineage to a separate Clerk-only Cognee dataset.
That is retrieval material, not evidence and not the structured result awaiting approval.

## 4. Resolve against Teable masters

**Artifact, location, job.** The artifact is a set of projected resolution results inside the Clerk state and later `intake_queue.payload.resolutions`; its mapping source is existing Teable master rows; its job is to attach known master record ids and display values to extracted names or codes.
Teable is the mapping target, not the document store: the PDF, parsed fields and queue row do not land there.

`KnowledgeBase.masters.resolve_all` is read-only.
The Clerk refuses to create a counterparty or account to make a document fit, flags ambiguous or missing applicable masters, and treats a master store it cannot read as unverified rather than as a clean match.
Only document types with an applicable bound master table receive this check; “not applicable” is not reported as a successful resolution.

## 5. Stage the structured result

**Artifact, location, job.** The artifact is `intake_queue.payload`; its location is the `intake_queue` table's JSONB column in Postgres; its job is the structured result awaiting human acceptance.
The terminal projection contains `fields`, `lines`, projected `resolutions` and `source_provenance`, while `source_file_id` links the row to the evidence record and `submitter` and `drafted_by` preserve who filed and who staged it.
The queue store first opens the row as `received`, then commits the complete terminal projection once as `pending` or `flagged`.

The queue refuses to auto-merge a suspected duplicate.
A duplicate becomes a visible flag, an unclassifiable document stays reviewable as uncertainty, and a natural-reference collision cannot quietly create a second active claim.
This row is not a draft journal entry and it cannot post itself.

## 6. Pass the intake gate

**Artifact, location, job.** The artifact is the accepted queue row with `status='active'` plus an `intake_events` audit event; its location is Postgres; its job is to record that a human accepted this structured document for the Bookkeeper to propose accounting.
The acting identity comes from the verified DevCenter request, not a browser-supplied actor header.

The intake service locks the row and its duplicate group.
It refuses a row outside the reviewable states, a duplicate group that already has an accepted or posted member, and an approver who is the submitter, drafter or person who last resubmitted edited fields.
Rejecting ends this path without writing money.

Approval and launch intent commit together: an `intake_launch_outbox` row is written in the same transaction as `status='active'`.
That outbox artifact lives in Postgres and its job is to make the Bookkeeper handoff retryable and observable as launched, deferred, failed or abandoned.
An approved row can therefore honestly be “approved, but posting has not started”; intake approval is not posting.

## 7. Build a proposal and stop

**Artifact, location, job.** The artifact is the Bookkeeper's balanced proposed journal lines; its location is the durable LangGraph checkpoint for the deterministic intake thread; its job is to show a human exactly what the posting rules derived from the active queue row.
The Bookkeeper re-reads `intake_queue.payload`, reads master and chart-of-accounts data without writing them, and applies deterministic posting rules.

The proposal stage refuses a non-active row, a missing organization base currency, an unmapped account or amount, an unavailable posting rule and an unbalanced result.
There is no suspense-account fallback.
The graph compiles with `interrupt_before=['post']`, so reaching this checkpoint cannot create a journal entry.

## 8. Pass the posting gate

**Artifact, location, job.** The artifact is a server-signed posting mandate; it is carried to the resumed Bookkeeper run and verified against server authority; its job is to bind the organization, intake record, checker and approve-or-reject verdict that may release the writer.
The posting checker must be a named human who is not the submitter, drafter, resubmitter or intake approver.
An agent identity cannot be the checker.

The source warning is exact: “THE CHECKPOINT DECIDES NOTHING.”
At post time the Bookkeeper verifies the mandate, re-reads the active queue row and re-derives the lines from that trusted record.
It refuses substituted proposal or committed lines, a changed record, a forged outcome, a checkpoint that contradicts the mandate and a row that has already left `active`.
A rejection records no ledger write.

## 9. Post once through the single writer

**Artifact, location, job.** The artifact is one posted `journal_entries` row, its `journal_entry_lines`, its hash-chained `event_stream` event and the queue's `result_entry_id`; their location is the canonical Postgres ledger; their job is the immutable accounting fact and its source lineage.
`ledger_bridge.post_and_mark` calls `LedgerService.post_entry`, performs applicable subledger writes, changes the intake row from `active` to `posted` and stamps `result_entry_id` in one database transaction.

The `LedgerService` source calls itself “The single writer” and says it will “Validate the 18 invariants, then post an immutable, hash-chained entry.”
It refuses fewer than two lines, two-sided or negative lines, imbalance, unknown or inactive accounts, unresolved currency or FX, missing required dimensions, a closed period, a conflicting idempotency replay and any other GL invariant failure.
If the queue row cannot be marked from `active` to `posted`, the transaction rolls back instead of leaving a journal entry without its intake link.

## Verified at source

The upload and evidence stages are in `langgraph_chat/devcenter/uploads_api.py`, `agents/clerk/raw_intake.py`, `agents/clerk/upload_authority.py` and `agents/clerk/files.py`.
The browser identity and known attachment-metadata boundary are in `worthstate-ui/src/app/api/[..._path]/route.ts`, `src/lib/langgraph-passthrough-guard.ts`, `src/lib/session-identity.ts` and `src/lib/multimodal-utils.ts`.
Extraction, mapping and queue staging are in `agents/clerk/graph.py`, `agents/clerk/queue.py`, `agents/common/kb.py` and `data_entry/resolver.py`.
The gates and durable launch are in `devcenter/intake_api.py`, `devcenter/intake_service.py`, `devcenter/intake_validation.py`, `agents/bookkeeper/graph.py` and `agents/bookkeeper/post_auth.py`.
The final write is in `agents/bookkeeper/intake_source.py`, `agents/bookkeeper/ledger_bridge.py` and `backend/app/services/ledger_service.py`.
