Package cashflow publishes a direct-method statement of cash flows over a book this realm does not own, cannot write, and did not supply.
What it is, in one sentence
An owner of a book in r/books declares which of their account codes hold cash and what every other code's movement is for; this realm then reads the entries in a window straight out of that ledger, hands them to p/cashflow, and freezes the resulting statement at a sequence number.
A PUBLISHED STATEMENT IS A CLAIM ABOUT A CLAIM — and here, about TWO
r/statements already says this of itself and it is true here with one extra layer. A book is a set of claims by its owner; nothing ties an account reading `Cash 5000` to 5000 real ugnot anywhere. A cash-flow statement derived from such a book inherits that completely, and then adds a SECOND caller declaration on top: which codes are cash at all.
p/cashflow states the consequence exactly and it is repeated here rather than linked, because it is the single most important sentence about this realm: *"A realm that declares a receivable as cash produces a plausible, wrong statement. The declaration is the trust boundary."*
So what this realm guarantees is narrow:
- the ENTRIES are read from r/books, not supplied by the publisher;
- the CLASSIFICATION is the publisher's own declared policy, frozen with the statement so a reader sees what it was;
- the ARITHMETIC is p/cashflow's, which nobody here can parameterise.
That the chart describes anything real, and that the codes called cash hold cash, are not claimed here and are not checkable from here.
Every figure comes from r/books through its own read surface, except one. p/cashflow cannot derive an OPENING cash position from a window-filtered entry slice — a window carries period movement, not a cumulative position — so `opening` is an argument to Publish and is echoed into the report unchanged. [Closing] is `Opening + NetChange` and is therefore only as good as a number the publisher typed.
This realm does not leave it at that. See OpeningReconciled: when the window extends past every entry the book holds, the sum of the declared cash accounts' balances is the cumulative cash position, and that is comparable to [Closing]. Where the comparison is available, Publish REFUSES to publish a statement that fails it. Where it is not available, the report says so and the figure stands as a declaration.
The window is REFUSED when it is too wide, never chunked
p/cashflow is explicit that it does no paging and no merging, because merging reports across pages would make the unresolved set an artifact of the paging. Its entry cap is 4096, exactly books.MaxEntriesPerBook, so the primitive can always accept a whole book and the binding constraint is not its cap — it is the cross-realm call budget, two reads per entry.
So this realm declares MaxWindowEntries and refuses a window holding more, telling the caller to narrow it. A chunk-and-merge would be cheaper to use and would violate the primitive's own stated rule.
Window selection rests on a premise OWNED BY ANOTHER REALM
Entries are located by binary search over entry ID, which is correct only because `At` is non-decreasing in ID. That is not a property of p/journal, which refuses only a negative `at` and one at or before the close cutoff. It is a property of r/books, which dates every entry with `runtime.ChainHeight()` at all three of its posting call sites and exposes no `at`-taking entrypoint. r/books declares the obligation in its own package doc.
The failure mode if that premise ever breaks is the dangerous kind: a statement over FEWER entries than the window holds, which still balances and still reconciles. So the premise is checked rather than assumed — [scan] verifies `At` is non-decreasing across every entry it reads (free, since each `At` is already in hand) and probes the two entries immediately outside the located range to prove the range is maximal. A violation panics instead of publishing.
RESIDUAL, stated with its boundary: that makes a violation at or adjacent to the window edges loud. A violation deep outside the scanned range is invisible without a full scan, which is the cost this design exists to avoid. OpeningReconciled closes it for the one window shape where the cumulative cross-check applies, and nowhere else.
Entry IDs are relied upon to be dense, and the argument is structural
The binary search indexes entries 1..books.EntryCount. That is sound because p/journal allocates `id := nextID; nextID++` and writes the entry immediately, in one function with no error return between the two, into a tree that has no delete; a post that fails panics and reverts the whole transaction. So there are no gaps and no reuse. It is stated here because it is a premise, not an observation: [EntryInfo] panics on an absent ID, so a gap would abort the scan loudly rather than skew a figure.
A reversal lands in the period it was MADE
r/books dates a reversing entry at the current height, not at the reversed entry's date, and p/cashflow deliberately does not special-case reversals: a reversing entry that touches cash moved cash. So reversing a prior-period entry shows as movement in the CURRENT period and the corrected period's published statement does not change. That is right for a cash-flow statement and surprising to a reader expecting otherwise, which is why it is here.
NO CUSTODY, and no re-derivation seal
There is no coinio import, no payable path, no fee, no withdrawal, no admin. Publish and DeclarePolicy refuse a transaction carrying coins.
There is deliberately NO `Verify` accessor re-deriving a stored report and comparing. For a window that closed before the publication height such a check is a CONSTANT TRUE by construction: entries are dated at the chain height and the journal is append-only, so no entry can later fall into a closed window, and the chart cannot lose an account. Shipping it would be the fifth instance of the vacuous-true class this project keeps finding in its own work. What ships instead is Rescan, whose non-vacuity is exact and narrow: it re-locates the window NOW and so tests the selection premise above, not the ledger's contents.
No cross-owner aggregate beyond a count
r/books scopes every read to one owner, and a cash-flow statement mixing owners would assert a claim no single owner can make. Total is a count and there is no way to enumerate publishers.
Limits, stated plainly
- `opening` is caller-supplied, and verified only where OpeningReconciled says it was.
- The policy is caller-declared and is the trust boundary. A wrong cash declaration yields a plausible, wrong statement and no on-chain check here can detect it.
- The cost bound is reasoned against a live precedent (r/statements performs roughly 130 cross-realm reads per report), not measured in gas.
- Memos and account codes and names are an owner's free text in r/books and are unsanitized there. Every one of them goes through [cell] before it reaches Render.
- The chart reconstruction here is a second copy of r/statements' unexported `chartOf`. Promoting it would modify a live realm.
r/books: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books r/statements: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements p/cashflow: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal p/periods: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods