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

# The Accountant

> Period close, adjusting entries and statutory reporting. It proposes; a human approves; the single writer posts. Nothing in the package writes.

# The Accountant

🟡 **Deployed source; agent flow unexercised end to end for this documentation cut.**
It owns the period close and the statutory reports, and it is the only agent that **produces journal-entry proposals** without being able to post them.

## The shape

```
START -> scope_gate -> { decline                              (out of lane)
                         close_check        -> answer          (the checklist)
                         propose_adjustment -> stage_at_gate -> answer
                         statutory          -> answer }        (TT200 / TB / P&L / BS)
```

No interrupt here.
The pause that matters lives on the **Bookkeeper's** thread.

That contrast is the whole design:

| Agent          | Interrupt                   | Write channel                            |
| -------------- | --------------------------- | ---------------------------------------- |
| Bookkeeper     | `interrupt_before=["post"]` | the single one                           |
| Auditor        | none                        | none                                     |
| **Accountant** | **none**                    | **none** - but it produces **proposals** |

## Three capabilities

<AccordionGroup>
  <Accordion title="The close checklist">
    Five steps, in the order a human closes a period in: `period`, `integrity`, `unposted`, `close_list`, `statutory`.

    It **orchestrates existing oracles rather than reinventing them**.
    Step 2 calls the [Auditor's](/greatbook/agents/auditor) own sweep; step 4 calls the backend's [Close checksum](/greatbook/capabilities/close-list) oracle.
    The two checks that genuinely belong here are the ones living *between* the agents: is the period open, and are approved documents still unposted.

    Any step that cannot run is **INCONCLUSIVE**, and an inconclusive report is never `ready_to_close`.

    Full detail: [Fiscal periods and closing](/greatbook/capabilities/periods-and-close).
  </Accordion>

  <Accordion title="Adjusting entries · eight kinds, no guesses">
    Built from a deterministic rule table, the accountant's counterpart to the Bookkeeper's posting rules and held to the same three commitments.

    | Kind              | Entry                                                                                                         |
    | ----------------- | ------------------------------------------------------------------------------------------------------------- |
    | `fx_revaluation`  | gain: `Dr` the revalued account / `Cr 4110` (VAS 515). loss: `Dr 6210` (VAS 635) / `Cr` the account           |
    | `accrual`         | `Dr` the expense account / `Cr 2050` (VAS 335 accrued expenses)                                               |
    | `accrued_revenue` | `Dr 1110` (VAS 1388 unbilled) / `Cr` the revenue account                                                      |
    | `prepaid_release` | `Dr` the expense account / `Cr 1400` (VAS 242 prepaid)                                                        |
    | `depreciation`    | `Dr 6300` (VAS 6274 / 6424) / `Cr 1510` (VAS 214 accumulated depreciation)                                    |
    | `cit_charge`      | `Dr 6500` (VAS 821) / `Cr 2120` (VAS 3334 CIT payable)                                                        |
    | `carry_forward`   | close revenue, cost of goods sold and operating expenses, then carry the after-tax result to `3200` (VAS 911) |
    | `year_end_roll`   | move `3200` (VAS 911) to `3100` (VAS 421 retained earnings) on the first day of the new fiscal year           |

    An unknown kind, or one missing a parameter its rule requires, raises `UnmappedAdjustmentError`.
    There is no fallback anywhere in the table, and **the suspense accounts are refused outright even when a caller names one explicitly**:

    > refusing to post to suspense account 6900 - an adjustment that cannot be coded to a real account is a human's decision, not a suspense dump

    The account codes are duplicated here as constants rather than read from the database, deliberately: the rule must be decidable offline, and the ledger engine independently verifies that every account exists and is active before it posts.
    So a stale constant fails loudly at the writer rather than silently booking somewhere plausible.

    A business reversal is **not** an adjustment kind.
    It goes through the separate typed reversal boundary, and the Accountant cannot invoke that write path.
  </Accordion>

  <Accordion title="Statutory reporting">
    Trial balance, P\&L, balance sheet and the Circular-200 projection, all built from one posted-lines base so they cannot disagree.

    Full detail: [Statutory reporting](/greatbook/capabilities/statutory-reporting).
  </Accordion>
</AccordionGroup>

## Wave D/F: period-end assurance

Wave D completes the explicit `close_gated` assurance family and its server-owned monthly job.
The family reads the phase metadata from the generated Close inventory rather than keeping a second list of codes, so the three `KETCHUYEN-*` checks and `DEPR-COVERAGE` join the sweep through the same authority.

The routine full sweep still excludes `close_gated` because its subject must be a finished period.
A question naming a close-gated control resolves one exact closed period; the monthly job uses a bounded `last_closed` backlog scan so a missed run cannot permanently skip an unverified closed period.
No closed period, a named open/unknown period, or a control that cannot run is **inconclusive**, never a vacuous pass.
Findings are scoped to their fiscal period, so a clean August run cannot resolve a still-real July finding.

Wave F adds `DEPR-COVERAGE` as one of those close-gated controls.
For the selected period, every positive-cost asset already in service and still inside its useful-life month window needs its own depreciation charge posted inside that period.
A future or fully depreciated asset is outside the population; a positive-cost asset with no in-service date makes coverage inconclusive and is named rather than dropped.
FX-revaluation coverage remains deferred until the realized-FX and rate workflow can support it honestly.

## Accounting Desk read surfaces

The Accountant workspace is not only a chat answer.
Its Accounting Desk exposes Close, Analysis, Allocations, Costing and Adjustments as authenticated read surfaces with exact decimal strings, pagination, event/journal links and explicit empty, partial and error states.

An adjustment or close calculation is backed by an immutable `journal_workpapers` source record.
The workpaper links to the exact journal event and entries, and Transactions links back to the workpaper; a descriptive source label alone is not resolved lineage.
These read surfaces do not release or post an adjustment.

## How it reaches the book: it does not

`posting_gate.py` is the **only** seam out, and it hands the proposal to the **Bookkeeper's** graph, where it stops at that graph's existing human gate.

```mermaid theme={null}
flowchart TB
  A["Accountant<br/>propose_adjustment"] --> S["stage_at_gate<br/>signs the payload"]
  S --> B["Bookkeeper graph<br/>entry_kind = adjustment"]
  B --> G{"the SAME human posting gate"}
  G --> W["LedgerService<br/>the single writer"]
```

> **The accountant proposes; a human approves; the single writer posts.**
> No step of that can be skipped from here, because there is no code in this package that writes.

An adjustment has no staging row to re-read, so its trusted record is the payload **signed where it is staged**, inside the server process that built it.
The lines, the amounts, the entry date, the GL source lineage and the proposing agent are all fixed before the run ever pauses.
See [the posting mandate](/greatbook/capabilities/posting-mandate).

Separation of duties still applies: the checker must be **named**, and may not be the proposing agent.

## Idempotency by construction

Every proposal carries a **stable** source id derived from `(kind, period, subject)`:

```
ADJ-<KIND>-<PERIOD>-<SUBJECT>
```

That is half the ledger's idempotency key, so re-proposing the same adjustment for the same period resolves to the same key.
**At most one entry can ever exist for it**, however many times a close is re-run.
An exact proposal replay returns that entry; a proposal that reuses the key with different immutable facts conflicts instead of inheriting the old result.

That property matters more here than anywhere else: a close *is* re-run, repeatedly, as blockers clear.

## Read-only elsewhere

The Accountant shares the Auditor's read-only channel, so the database rejects a write regardless of what any node asks for.

A static import check asserts that **no accountant module can reach a writer**.
That test is the control; without it, the guarantee lasts until the next import.

## The release path, stated plainly

A staged adjustment uses the stable adjustment-keyed approve/reject path.
Both decisions require a named non-agent human, approval refuses the proposing agent as checker, and the decision is bound to the exact adjustment source.
Only a resulting journal entry establishes that an approved proposal posted; rejection is stored as a durable decision.

That same release path is now registered as two typed staged-workspace outcomes.
The approve/reject adapters accept only the stable `ADJ-*` source and its exact workpaper content hash, revalidate the current gate, then call the existing canonical decision service.
Approval cannot substitute journal lines and reports success only from the immutable entry; rejection requires a written reason and retains its close-run decision evidence.
The public OAuth/MCP ingress and release source are current at registry `1.14.1`.
Real ChatGPT and Claude identity, scope, discovery, fallback, confirmation and safety exercises remain open.

## Worked cases

<AccordionGroup>
  <Accordion title="acct-close-checklist-completeness">
    All five steps must appear.
    Completeness matters because a checklist that silently drops a step reads exactly like a checklist that passed it.
  </Accordion>

  <Accordion title="acct-close-checklist-blockers-reported">
    Two independent real conditions are seeded: a document a human approved that is not in the books, and a credit to the AP control with no matching bill in the sub-ledger.

    The verdict is read back from the period row's own status, **not** from what the answer claims.
  </Accordion>

  <Accordion title="acct-adjusting-entry-routes-through-gate">
    The governance case, and the reason the Accountant is riskier than the Auditor: it **proposes journal entries**.

    The proposal is routed through the **real** Bookkeeper graph, proven paused at the gate, with the ledger empty.
    And `MakerIsCheckerError` when the proposing agent tries to be its own checker.
  </Accordion>

  <Accordion title="acct-adjusting-entry-unmapped-raises">
    The same anti-suspense guardrail as the Bookkeeper's.
    No fallback, ever.
  </Accordion>
</AccordionGroup>

The [Proof tab](/greatbook/proof/cases-accountant) carries all five.

## Where it is enforced

| Concern                                | Code                                                    |
| -------------------------------------- | ------------------------------------------------------- |
| the graph and the deterministic router | `langgraph_chat/agents/accountant/graph.py`, `scope.py` |
| the checklist                          | `close.py` - `ALL_STEPS`                                |
| the adjustment rule table              | `adjustments.py` - `ADJUSTMENT_KINDS`                   |
| the statutory reports                  | `statutory.py`                                          |
| the only seam out                      | `posting_gate.py`                                       |
| the read-only channel                  | `gl_read.py`                                            |

## Related

* [Fiscal periods and closing](/greatbook/capabilities/periods-and-close) - the checklist in full
* [The Close checksums](/greatbook/capabilities/close-list) - step 4's oracle
* [The Bookkeeper](/greatbook/agents/bookkeeper) - where an adjustment actually posts
* [Closing a period](/greatbook/guides/close-a-period) - the same flow, from a user's chair
