Typewriter to posted journal entry
The Typewriter golden path is compose → save → sign → hand off → intake approval → draft journal proposal → posting approval → post.
It shares the intake and ledger tail with uploaded documents, but it does not upload a PDF through the Clerk extraction graph.
1. Compose a payload
Artifact, location, job. The artifact is the schema-shaped document payload in the browser and preview request; its location is transient application state; its job is to render a document before anything is stored.
POST /typewriter/preview persists nothing and deliberately accepts a partial payload so the preview can show blanks as a person types.
Preview is not validation, approval or evidence.
The server refuses an unattributable request, but it does not turn preview HTML into the stored document.
2. Save exact PDF bytes and an immutable version
Artifact, location, job. The evidence artifact is the rendered PDF at document-pdfs/<doc_type>/<uuid>.pdf in the private documents object-storage bucket; its job is the exact document a signer will attest to.
The service hashes those exact bytes with SHA-256 before it records the version.
Artifact, location, job. The version artifact is a typewriter_documents row in Postgres; its job is to bind the schema payload, pdf_path, pdf_hash, document group, version, author and PENDING status.
The author identity and UUID come from the verified session; the request body cannot choose them.
Save refuses undeclared schema fields, unreadable numeric values, unsupported currency values and a service caller that cannot supply the session UUID required by the row-level policy.
A correction creates a new version with supersedes_id; it never edits the prior PDF or row.
If the database insert fails after storage, the service attempts to delete the object, but a process death in that interval can leave an unreferenced object; the source does not claim a cross-service atomic transaction.
3. Sign the stored hash
Artifact, location, job. The artifact is an append-only document_approvals trail in Postgres; its job is to record which designated human approved which step and when, against the saved version’s exact PDF hash.
Before recording a step, the service reads the stored PDF and recomputes its SHA-256.
The database approval function rechecks the next step, designation and hash under lock, then records the step and changes the document to APPROVED only when the chain is complete.
The approval gate refuses the document author, a viewer without the required designation, an incomplete or missing chain, a superseded version and stored bytes that no longer match pdf_hash.
Signing does not create an intake row and does not approve accounting.
4. Hand the approved payload directly to intake
Artifact, location, job. The artifact is an intake_queue row in Postgres with payload={fields, lines} and drafted_by='typewriter'; its job is to place the approved composition in the same human review state machine used by Clerk output.
submit_to_clerk derives the idempotency key as typewriter:<pdf_hash> and preserves the document author’s created_by_identity as the queue submitter even when another person clicks Submit.
Despite the route name, this handoff does not run agents/clerk/graph.py.
There is no second classification, extraction, Cognee text publication or Teable master-resolution pass here; the schema payload is projected directly into a pending queue row.
That is why “no Clerk flags” cannot be read as “the Clerk verified it.”
The handoff refuses a document that is not APPROVED and a version that is no longer the group’s head.
A retry returns the existing intake id with outcome='duplicate' instead of inserting a second row.
A different live version in the same group produces a visible duplicate link and can move the new row to flagged; it is not auto-selected or auto-merged.
Every created, duplicate or failed attempt receives a document_handoffs audit row.
This service flow is traceable in backend source, including the direct queue insert and handoff audit.
The known Typewriter handoff fix is merged but was not deployed at the time of this review, and the authenticated deployed flow could not be exercised.
The deployed Compose-to-Submit handoff is therefore unverified, not claimed operational from source alone.
5. Pass the intake gate
Artifact, location, job. The artifact is the same queue row changed to active, an intake_events decision and an intake_launch_outbox job in Postgres; their job is to record human acceptance and durably request a Bookkeeper launch.
The reviewer judges whether the composed business document belongs in the books; the prior PDF signature is not a substitute for that accounting decision.
The intake gate refuses the document author because that identity is the row’s submitter, refuses the Typewriter drafting identity as a maker, refuses a person who resubmitted edited fields and refuses a duplicate group with another accepted or posted member.
Reject writes no journal entry.
Approve can return with the outbox deferred or failed, which means accepted but not yet proposed; it must not be reported as posted.
6. Draft a journal proposal and stop
Artifact, location, job. The artifact is a balanced Bookkeeper proposal in the durable LangGraph checkpoint; its job is to translate the active Typewriter payload through deterministic posting rules for a posting checker to inspect.
The Bookkeeper reads intake_queue.payload, existing Teable master data and the chart of accounts; it does not write any of those sources.
It refuses a non-active row, missing base currency, unmapped accounts or amounts, an unsupported accounting effect and imbalance.
It does not use a suspense fallback.
The graph stops before post, so a drafted proposal is not a draft row in the ledger and cannot become one without the second accounting gate.
7. Approve posting with a different person
Artifact, location, job. The artifact is the server-signed posting mandate; its job is to bind the exact organization, intake record, posting checker and verdict that may resume the Bookkeeper.
The checker must be distinct from the Typewriter author, any resubmitting editor and the intake approver; an agent cannot approve posting.
Together with the Typewriter author-versus-signer rule, the ordinary golden path has four distinct people.
The Bookkeeper refuses a missing or forged mandate, a checkpoint that disagrees with it, substituted lines, a changed source row and a forged success status.
It re-reads the active queue row and re-derives the proposal at post time rather than trusting the paused checkpoint.
8. Post one immutable accounting fact
Artifact, location, job. The artifact is one journal_entries row with its lines and event_stream event in the canonical Postgres ledger, linked back by intake_queue.result_entry_id; its job is the posted accounting fact and its trace to the approved document.
ledger_bridge.post_and_mark creates the ledger and applicable subledger facts, changes the queue row from active to posted and records the result id in one transaction.
LedgerService.post_entry is the only writer and enforces all 18 GL invariants before the write.
It refuses imbalance, invalid accounts or currencies, missing dimensions, a closed period, a conflicting replay and any attempt to mutate a previously posted entry.
If the intake transition cannot complete, the whole transaction rolls back.
Verified at source
Composition, persistence, approval and handoff are in langgraph_chat/devcenter/typewriter_api.py and langgraph_chat/devcenter/typewriter_service.py; the PDF renderer is in langgraph_chat/renderer/; and the approval-chain boot assertion is in typewriter_api.py.
Queue and gate behavior are in agents/clerk/queue.py, devcenter/intake_api.py, devcenter/intake_service.py and devcenter/intake_validation.py.
Proposal, mandate and posting behavior are in agents/bookkeeper/intake_source.py, agents/bookkeeper/graph.py, agents/bookkeeper/post_auth.py, agents/bookkeeper/ledger_bridge.py and backend/app/services/ledger_service.py.