Specification · bridge @ 0.3

bridge — The Ledger Bridge · Area Specification

last conformance run: 4/4 scenarios pass · 6 acts mapped 2026-10-07 20:02 UTC

Spec

Status: 0.3. The hand-over to the ledger of record. 0.3 adds preliminary financials: a profit and loss and a balance sheet derived from the same journal, for the person and their accountant to look at before the close.

Saybooks owns operational truth and derives balanced journal lines from it (core_journal). The LEDGER OF RECORD — QuickBooks Online, Xero, an accountant's own system — owns the chart of accounts, manual journals, adjustments, and the close. The bridge is how the first feeds the second. We never compete with the ledger; we make it better fed than it has ever been.

1. Calibration: who this is for

The person keeping the books, and the accountant who receives them. The accountant supplies the chart: codes and names as their system spells them. The person hands over a period and gets a file their ledger imports. Neither has to re-key anything, and both can answer "what did the ledger already get?" from the record.

2. Scope

In: mapping onto their chart — our fixed derivation accounts, and (0.2) the person's own spending categories and the bank or card each statement came from; building the journal for a period in the shape the target imports (Xero manual journal CSV, QuickBooks Online journal entries CSV, plain CSV); recording every hand-over with its control totals and the exact file.

Both sides of the books reach the ledger: money in from invoicing and order to cash, and (0.2) money out from statements the agent read — spending, fees and receipts, each posting against the account it was paid from.

Out (§9): posting into the ledger over an API, pulling anything back, the chart itself, manual journals, adjustments, the close, currency conversion, cost of goods and inventory.

3. What it is

  1. Account mapping — our fixed derivation accounts (Accounts Receivable, Sales Revenue, Service Revenue, Sales Tax Payable, Cash, Customer Deposits, Customer Credits, Sales Returns & Allowances, Bad Debt Expense; later the p2p set: Inventory, GRNI, AP, COGS, price variance) mapped onto the customer's REAL chart. The mapping is workspace config, set once with the accountant, logged like everything else.
  2. Export formats — journal entries in the shapes the targets actually import (Xero manual-journal CSV; QBO journal import; plain CSV for everyone else). Exact field layouts to be pinned against live trials, not documentation.
  3. Hand-over tracking — what has been exported, through when. The journal itself stays a pure derivation (re-derivation is the truth); the bridge records the hand-over so month-end is a diff against last export, not a re-key and not a duplicate import. Late-arriving or voided facts after an export produce a visible correction entry in the next export, never a silent rewrite of the past.

3. What it is not

No chart of accounts of our own. No manual journals. No close. No bank feeds — the CSV import on unapplied cash is the honest boundary, and bank-feed integrations remain the moat we say out loud we have not crossed.

Acceptance test (the plan)

5. Entities and lifecycles

6. The acts

Writes (2): map_account, export. Reads (4): accounts (our accounts, which the books use, how each is mapped, which formats are ready), preview (the period in the target's shape with its totals and anything unmapped; records nothing), exports (what has been handed over, through when), financials (a preliminary profit and loss for a period and a balance sheet at its end, with what they leave out). Environment: core's journal derivation, and whatever module produced the facts.

7. Invariants

8. Conformance (scenarios)

01 the first hand-over: an invoice, a payment and its application; the accounts read shows what is in use and unmapped; the export is refused naming them; the accountant's codes are written; the preview is ready; the export records the hand-over and returns a link · 02 the month after: a second period continues from the last hand-over without a start date, a re-export of a period already sent is allowed and says so, and the plain CSV needs no chart at all · 03 the spending side: a card statement imported and reviewed; categories and the card account are mapped; a transfer posts nothing and an unreviewed row is reported as left out; the file carries the expense against the card · 04 preliminary financials: refused without a period and for a period that is not one; an issued invoice partly paid and a checking statement with software, a fee, a transfer to savings, an owner draw and the client's payment; the profit and loss, a balance sheet that balances and a checking account that ties to its statement; a later row left unreviewed is named and the account no longer ties, while the statements still balance.

9. Deferred — with reasons

ItemWhy deferred, not rejected
Posting over an API (QBO/Xero apps)A file a person reviews and imports is the honest boundary for a system that is not the ledger. An API posting is a standing write permission into someone's books; it needs their app review and their consent, not ours.
Automatic correction entriesA fact that changes after a period went over shows as a visible re-export today. Deriving a reversing entry pair instead needs the accountant's convention, not ours.
Their chart, pulledReading the chart from the ledger would remove the typing, and it needs the same API connection as posting.
Cost of goods, inventoryItems carry no cost; the ledger of record owns margin.
Grouping entriesOne entry per fact, so every posting traces to an invoice or a statement line. Summarising a month into one entry per account is a policy the accountant should choose.
A bank-transaction shapeSome ledgers would rather import statement rows than journal entries. Journal entries are correct double entry and one shape; the other is a format, addable when someone's import asks for it.
Currency conversionSums are per currency and never cross. A rate and a policy belong to the ledger.

---

Change log: 0.3 (2026-10-07) — preliminary financials, from Aryeh Munk's phase 1 list ("Please generate preliminary Q1 financials"). 0.0-sketch (2026-08) — a goal on the record. 0.2 (2026-09-10) — the spending side: categories and statement accounts are mapped the same way, purchases contributes its own postings, transfers post nothing and unreviewed rows are reported rather than dropped in silence. 0.1 (2026-09-10) — built: the account map, three export shapes, hand-over tracking with control totals and the exact file. Prompted by an accounting firm asking whether it syncs with QuickBooks or Xero; the honest answer was "it derives the lines but cannot hand them over yet", so that was the thing to build. Layouts follow the published import templates and want one live trial each; the formats are a table in src/modules/bridge/views.js, so a correction is a data change.

Acts

ActKindRequired
map_accountwriteaccount
exportwriteformat to
accountsreadnone
previewreadformat to
exportsreadnone
financialsreadnone

Invariants

IdInvariant
B-1The journal is a derivation, never a posting: an export reads it and changes nothing.
B-2Money is integer minor units; every export balances, per entry and in total.
B-3One currency per hand-over; a period holding more than one is refused until a currency is named.
B-4An export is refused while an account the period uses lacks the mapping the format needs, naming it.
B-5Every hand-over records format, period, counts, control totals, a hash and the actor, and keeps the exact file.
B-6Omitting the period start continues from the last hand-over in that format.
B-7Re-exporting a period already sent is allowed and always visible, naming the earlier hand-over.
B-8The chart is theirs: codes and names come from the accountant, never invented.
B-9Every write is a logged act with an actor; refusals are logged too.
B-10Spending posts against the account it was paid from; a transfer posts nothing, so money already recorded elsewhere is never counted twice.
B-11An unreviewed row posts nothing and is reported as left out, in the preview and on the hand-over.
B-12Preliminary statements are derived on request and never stored, balance by construction from the statements' printed openings, say whether each statement account ties, and name on their face what they leave out.

Scenarios

Each scenario is a file of acts with expected outcomes. ok means the act must succeed with the listed fields; refused means the act must be refused with a sentence containing the listed text. Refusals are contract.

the first hand-over: map their chart, then send the period pass

B-4 refused while unmapped, B-8 the chart is theirs, B-5 the hand-over is recorded with its totals, B-2 it balances.
Environment: set_company_profile, create_customer
#ActExpectWhy
1draft_invoiceok total=300000
2issue_invoiceok status="issued"
3record_paymentok
4apply_paymentok
5accountsok in_use=4receivable, service revenue, cash and deposits are in use; nothing is mapped yet
6exportrefused account codeB-4: Xero matches on the code and there is none
7map_accountok
8map_accountok
9map_accountok
10map_accountok account="Customer Deposits"
11previewok ready=true entry_count=3 line_count=6three entries: the invoice, the receipt, the application
12exportok entry_count=3 line_count=6B-5: recorded with its control totals and the file kept
13exportsok count=1

the month after: continue from the last hand-over, and re-send visibly pass

B-6 the period continues by itself, B-7 a re-export says so, and the plain CSV needs no chart.
Environment: set_company_profile, create_customer
#ActExpectWhy
1draft_invoiceok
2issue_invoiceok
3map_accountok
4map_accountok
5exportok entry_count=1
6exportrefused already exported throughB-6: nothing new after the last hand-over
7exportok entry_count=1B-7: allowed, and the result names the earlier hand-over
8exportokthe plain CSV needs no chart at all
9exportsok count=3

the spending side: a card statement reaches the ledger pass

B-10 spending posts against the card it was paid from and a transfer posts nothing; B-11 an unreviewed row is reported, not dropped in silence; B-4 the categories and the card need their chart accounts first.
Environment: set_company_profile
#ActExpectWhy
1import_statementok rows_in=5the agent read the statement; it reconciles to the printed balances
2review_batchok reviewed=4 still_unreviewed=1the Adobe row is left unreviewed on purpose; the deposit is a transfer that says where it went
3previewok entry_count=4B-10 the transfer names no other side so it posts nothing and is listed; B-11 the unreviewed row is listed too
4exportrefused account nameB-4: the categories and the card have no chart account yet
5map_accountok kind="category"
6map_accountok
7map_accountok
8map_accountok kind="source"
9map_accountok kind="derivation"
10map_accountrefused no categoryB-8: only words the books actually carry
11previewok ready=true entry_count=4 line_count=8
12exportok entry_count=4B-11: the hand-over names what it left out, with its value
13exportsok count=1

preliminary financials, said on their face pass

B-12 a profit and loss and a balance sheet derived from the journal: balanced by construction, the checking account tied to its statement, and what is left out named.
Environment: set_company_profile, create_customer
#ActExpectWhy
1financialsrefused need a periodB-12 a statement is about a period
2financialsrefused is not a period
3draft_invoiceok
4issue_invoiceok
5record_paymentok
6apply_paymentok
7import_statementok rows_in=5
8review_batchok reviewed=5
9financialsok balanced=true income_total=300000 expense_total=7499 net_income=292501 total_assets=1242501 total_liabilities=0 unreviewed_in_period=0B-12 revenue 3,000.00 less software and the fee; checking 10,425.01 ties to its statement with the invoice payment counted where it landed; savings an asset, the owner draw against equity
10import_statementok
11financialsok balanced=true net_income=292501 unreviewed_in_period=1B-12 an unreviewed row is named, not counted; the checking account no longer ties and says so, and the statements still balance

These pages are generated from the files in specs/ in the repository; scenarios are executed by src/conformance.js on every build. Written and maintained by Peter Varga, who also runs Portland AI Works. github.com/alviso/saybooks, AGPL-3.0.