> ## 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.

# 5 and 6 · The Bookkeeper drafts, then gate 2

> The Bookkeeper proposes a balanced journal entry and stops. A third person approves the posting, and only that writes to the book.

# 5 and 6 · The Bookkeeper drafts, then gate 2

**What you are doing.** Answering the second question: *is this journal entry right?*

Not the document.
The entry.

## Part 5 · The row moves to Awaiting posting

Within seconds of the intake approval, the row leaves **Needs review** and appears under **Awaiting posting**.
The audit trail now shows two events: the `approve`, then `bookkeeper_launched`.

<Warning>
  **Awaiting posting is not posted.**
  This is the tab people misread.
  A journal entry has been drafted and is waiting for a second person.
  Nothing is in the ledger.
</Warning>

You never talk to the Bookkeeper, and that is deliberate.
It is not a conversational agent: it runs only when a document has cleared the intake gate, and it is absent from the chat picker so nobody can start it by accident.

<CardGroup cols={3}>
  <Card title="It is launched, not invoked" icon="zap">
    The moment you approve at the intake gate, the audit trail records `bookkeeper_launched`.
    There is no button for it.
  </Card>

  <Card title="It drafts and stops" icon="pause">
    It proposes the accounts, the debits and the credits, plus the posting rule that produced them,
    then pauses at the gate. It cannot post.
  </Card>

  <Card title="You see it only as a proposal" icon="eye">
    Its entire visible output is the JOURNAL ENTRY AWAITING APPROVAL block.
    Approving that block is what writes the book.
  </Card>
</CardGroup>

<Note>
  The Bookkeeper card on the landing reads **Live · in pipeline**, and links to the review inbox
  rather than opening a conversation.
  That label is exact: it is running, it produced every entry in your ledger, and there is no chat
  surface to open.
</Note>

## Part 6 · Check the entry it proposes

Scroll the detail pane and you get **JOURNAL ENTRY AWAITING APPROVAL**: the account codes, their names, the debit and credit columns, and the posting rule that produced them.

```http theme={null}
GET /intake/{intake_id}/posting
X-WorthState-Org-Id: <org>
```

```json theme={null}
200 OK

{
  "intake_id": "<intake row id>",
  "intake_status": "active",
  "result_entry_id": null,
  "makers": ["<submitter>", "<intake approver>", "typewriter"],
  "approved_by": "<intake approver>",
  "posted_by": null,
  "entry_number": null,
  "gate": {
    "available": true,
    "exists": true,
    "at_gate": true,
    "currency": "VND",
    "proposed_lines": [
      { "account_code": "1200", "account_name": "Inventory - raw materials", "debit": "1100000", "credit": "0" },
      { "account_code": "2000", "account_name": "Accounts payable (control)", "debit": "0", "credit": "1100000" }
    ],
    "result_entry_id": null,
    "entry_number": null,
    "checker": null
  }
}
```

<Note>
  That read **degrades rather than fails** when the agent server is unreachable: `gate.available`
  becomes false, and the row's own `intake_status` and `result_entry_id` still tell you the truth about
  whether anything posted.
  A gate you cannot reach must never look like a gate that has been passed.

  The `makers` array is what the UI renders as the rule line under the buttons: it is the list of people
  who may **not** release this gate, sent up front so the interface can explain a 409 before it happens
  rather than after.
</Note>

### Read the draft, not just the total

<Warning>
  The entry balances either way, so **nothing here catches a wrong account for you**.

  On the walked example the rule for this document type is `creates_ap: Dr 1200 / Cr 2000`, and on this
  build it debits the gross 1,100,000 to inventory rather than splitting the 100,000 of input VAT to
  its own account, even though the document type's own header says "creates AP + 133".

  This is precisely why the second gate exists.
  If the accounts are wrong, **Reject posting**.
  That leaves the document intact and the ledger untouched.
</Warning>

## Separation of duties, for real

The people named under the buttons are locked out.

On the reference walkthrough, four distinct identities touched one document: one composed it, one signed it, one approved the intake, and a fourth approved the posting.
Every shortcut was attempted and every one was refused with a 409.

| Attempt                                       | Result                                                                                                |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| the submitter approves their own intake row   | refused: "… submitted this document and cannot also approve it"                                       |
| the intake approver also approves the posting | refused: "… submitted, drafted, edited or approved this document and cannot also approve its posting" |
| a third distinct person approves the posting  | accepted                                                                                              |

That middle row is not a claim.
It was executed on the running system, and this is the screen it produced.

<Warning>
  Note that the confirmation box stays open behind the error.
  Retrying will fail the same way.
  Hand the row to a colleague instead.

  And note that the button was **not** disabled.
  The rule is printed under the buttons and names you if you are one of the makers, but the control
  looks clickable ([rough edge 4](/greatbook/guides/troubleshooting)).
  Nothing can go wrong, because the server refuses it. You just get an error rather than a disabled
  control.
</Warning>

## Posted, and only now is it in the book

Once a second person approves the posting, the row moves to **Posted**, an entry number is assigned, and the detail pane tells the whole story: who approved the intake, who approved the posting, and a **View journal entry** link into the ledger.

```http theme={null}
POST /intake/{intake_id}/post-approve
X-WorthState-Org-Id: <org>
```

```json theme={null}
200 OK

{
  "id": "<intake row id>",
  "status": "posted",
  "result_entry_id": "<journal entry id>",
  "entry_number": "JE-000020",
  "posted_by": "<the server-derived checker>",
  "thread_id": "<the posting run>"
}
```

| Status | Meaning                                                                 |
| ------ | ----------------------------------------------------------------------- |
| `200`  | one balanced, immutable entry written, attributed to the checker        |
| `409`  | separation of duties, an illegal transition, or the posting was refused |
| `404`  | no such intake row in this org                                          |
| `503`  | the agent server is unreachable. **Nothing was written**                |

<Warning>
  **The answer you get is the database's, not the run's report.**

  `status` and `entry_number` are re-read from the intake row and the journal entry *after* the posting
  run returns, rather than taken from what the run said it did.
  A document that cannot name its journal entry did not post, and the endpoint refuses to report it as
  posted.

  That is not defensive coding for its own sake.
  An earlier version reported the paused run's own claim, and a planted `{"status": "posted"}` on the
  run's state made a real reviewer's approval return HTTP 200, with a green tick and the item gone from
  the inbox, for a document with an empty ledger.
  The generalisation the fix produced: **only the database may establish an outcome, and the layer that
  answers a human is the layer that must ask it.**
</Warning>

<Note>
  The checker recorded on the entry is **derived from the verified session**, never from a
  client-supplied header.
  That matters most here: this string becomes the ledger's author and is the value the
  maker-is-not-checker comparison is made against.
  A client-chosen actor would defeat the whole posting gate rather than just the intake one.
</Note>

## What you cannot do here, and why

**You cannot post twice.**
The writer applies the [exact replay contract](/greatbook/capabilities/idempotency-and-durability), so an identical retry returns the existing entry while changed immutable facts conflict.

**You cannot post an entry that does not balance**, or one that lands in a closed period, or one missing a required dimension.
The writer validates [18 GL invariants](/greatbook/guarantees) before any write and refuses with a typed error, writing nothing.

**You cannot undo this.**
Once an entry is posted the only correction is a reversing entry.
That is why both gates are worth the extra minute.

## Next

<Card title="The entry in the ledger" icon="landmark" href="/greatbook/guides/see-it-in-the-ledger" horizontal>
  Where it lands, and how to read what you are looking at.
</Card>

## Related

* [The seven guarantees](/greatbook/guarantees) - the 18 GL invariants in full
* [Who does what](/greatbook/roles) - why three distinct people, not two
