RealmPath
untyped stringRealmPath is this realm's own path, used to build Render links.
Value
"/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements"
BooksPath
untyped stringBooksPath is the ledger realm every figure here is derived from. It is taken BY VALUE from that realm's own exported constant rather than typed out again, so this is the compile-time pin itself and not a second copy of it that could drift. A reader checking which ledger a statement describes is reading the import, not a string in a doc comment.
Value
"/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books"
PublicationsAreUncapped
untyped boolPublicationsAreUncapped records a decision by its absence, because every other realm in this portfolio caps its per-owner state and a reader will look for the cap here. There is none, and the three reasons are: - \*\*No shared resource is consumed.\*\* Keys are per owner, like r/books' books. An owner can fill their own publication history and nobody else's, and the cost of doing so is gas plus a storage deposit each time. That is the same griefing argument r/books makes for its per-owner caps, and here it is sufficient on its own. - \*\*Nothing iterates them.\*\* Render reads the newest [RenderPublicationLimit](#RenderPublicationLimit) by sequence number, by direct key lookup, so page cost does not grow with the count. No accessor walks a publisher's history. - \*\*There is no engine cap to mirror.\*\* r/books caps because p/journal caps and the interaction of its ceiling with RederiveFromJournal is unmeasured. No such ceiling exists above this realm. A cap added only so a test could reach it would be worse than none.
Value
true
RenderPublicationLimit
untyped bigintRenderPublicationLimit caps how many of a publisher's statements one Render call lists. Publications are unbounded (see [PublicationsAreUncapped](#PublicationsAreUncapped)), so this is what keeps a publisher's page cost flat.
Value
(20 <untyped> bigint)
RenderAbnormalLimit
untyped bigintRenderAbnormalLimit caps how many census rows one Render call emits. A chart may hold books.MaxAccountsPerBook accounts and in a pathological book every one of them is abnormal.
Value
(25 <untyped> bigint)
record
typerecord is one frozen statement. It stores the [stmt.Statement](/gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements#Statement) whole rather than copying its ten fields into a parallel struct: a second field set is a second place for the two to disagree, and every field here is a value or a slice this realm allocated. There is deliberately no seq field. One was written here and read nowhere — found by the sabotage harness, which could not make dropping the assignment fail any test. A stored copy of the sequence would be a second place for the key and the record to disagree, and the key already carries it: every read path reaches a record by constructing pubKey(owner, seq) from a seq it already holds, so a field would only ever be checked against its own source.
Value
statements.record
published
*v0.Treepublished maps "\<owner>|\<padded seq>" to \*record. The pointer never leaves this file. Every accessor copies scalars out, so no caller receives a handle onto stored state — the same discipline r/books applies to its \*journal.Book.
- OID
- 03d65d…b668:4
published details
counts
*v0.Treecounts maps an owner's address string to how many statements they have published. It is the sequence allocator as well as the count.
- OID
- 03d65d…b668:7
counts details
pubCount
intpubCount is the global total, maintained alongside the tree so the index page never iterates. It is a count and nothing more; see the package doc on why there is no way to enumerate publishers.
Value
2
refuseCoins
func()refuseCoins panics if a user call arrives carrying coins. Adopted verbatim from r/books, which faces the identical slot: no withdrawal path, so accepted coins are stranded permanently. The guard is on IsUserCall rather than IsUser deliberately, and the reason is NOT the one the canonical payment-guard advice gives. That advice is about a realm that ACCEPTS payment, where IsUser is dangerously broad because the MsgRun ephemeral can consume the envelope before forwarding control and the receipt invariant no longer holds. This guard REFUSES, so the broader predicate would refuse MORE, and "more conservative" is the wrong frame. The real reason is that OriginSend is a fact about the TRANSACTION, not about this realm. It is a faithful receipt only on the direct MsgCall path, where the envelope's destination is this realm. On a MsgRun path the envelope can be non-empty while nothing arrives here — coins sent to the ephemeral flow back to the user — so pairing it with IsUser buys no protection and adds a FALSE REFUSAL mode: a panic on a call that brought this realm nothing. IsUserCall is the narrowest predicate covering the only path on which a non-empty envelope means coins actually credited to this address. The residual is stated rather than guarded: coins can still reach this realm's address through an intermediary's explicit banker send, which no predicate here can see. What makes that survivable is not this guard but the absence of any consumption path — nothing in this realm can spend what arrives, so a stranded balance is inert rather than exploitable. An earlier version of this comment said "OriginSend is only meaningful for a direct MsgCall", which is false as written — it is readable on every path and merely stops being a receipt. An independent audit caught it. The predicate was right and the stated reason was not, which is the worse of the two failures: the predicate is re-derivable from the references and a confident wrong sentence is what stops a maintainer re-deriving it. Onyx's VM tightens IsUserCall, so this is exactly the comment that gets consulted after the semantics have moved underneath it.
- OID
- 03d65d…b668:10
refuseCoins details
parseKind
func(s string) journal.KindparseKind turns one of [r/books](/r/books)' five kind words back into a [journal.Kind](/gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal#Kind), and PANICS on anything else. This is the ugly seam and it is named rather than hidden. r/books returns an account's kind as \`journal.Kind.String()\` — a presentation format that has become an API across a realm boundary — and no Kind parser exists anywhere in this portfolio (measured in DISCOVERY.md § 7, step 2c: zero hits for ParseKind, parseKind and kindFromString; r/books' own kindFromCode maps a code PREFIX to a kind and is unexported). It panics rather than returning a zero Kind, and the reason is specific: the zero Kind is reported CREDIT-NORMAL by [journal.Kind.IsDebitNormal](/gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal#Kind.IsDebitNormal), so a silently-unparsed word would not drop the row — it would file it under claims and produce a statement that balances and misstates. p/statements' Derive would catch it as ErrInvalidKind, and relying on that would make this realm's error message point at the wrong layer. The round trip is pinned in the suite over all five kinds rather than assumed, because the contract is another realm's String method.
- OID
- 03d65d…b668:12
parseKind details
chartOf
func(owner .uverse.address) []journal.AccountchartOf reads an owner's whole chart out of [r/books](/r/books) as journal.Account rows, ready for [stmt.Derive](/gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements#Derive). ### One walk, not a paging loop {#hdr-One_walk__not_a_paging_loop} books.Codes takes a \`from\` and a \`limit\`, so the obvious shape is a paging loop — and DISCOVERY.md § 6.4 planned one, costing ceil(n/limit) full chart walks because that iterator has no seek and honours \`from\` by skipping. This asks for the whole chart in ONE call instead, with limit at r/books' own account cap, which is exactly one walk and is strictly cheaper than any paging of it. The cap is read from that realm's exported constant, so the request can never be short of what a book may hold. Dropping the loop also drops its hazard. books.Codes' \`from\` is INCLUSIVE — paging re-reads the last code received — so every paging caller owes itself a boundary dedup, and a missed one double-counts one account per page boundary. A double-counted balance is the defect this product is least able to detect: the statement would still balance. ### The agreement check is not decoration {#hdr-The_agreement_check_is_not_decoration} books.Accounts and books.Codes are two reads, taken at one instant but through different code paths. If they disagree this refuses rather than publishing over a truncated chart, because a chart missing rows yields a statement that is wrong in a way no identity here can see. ### Taint {#hdr-Taint} The \[]string books.Codes returns was allocated under that realm and reaches here readonly-tainted. Nothing below mutates it or stores it: the codes are read out by value, and the rows are allocated here.
- OID
- 03d65d…b668:13
chartOf details
deriveLive
func(owner .uverse.address) statements.StatementderiveLive reads the owner's chart now and classifies it. Every live figure in this realm goes through here, so there is one path from the ledger to a statement and no accessor can derive differently from Publish. MustDerive, not Derive: on the mutating path an error return does not revert a Gno transaction and only a panic does.
- OID
- 03d65d…b668:14
deriveLive details
Publish
func() int64Publish freezes the caller's own statement at the current height and returns its sequence number, which starts at 1 for each publisher. The owner is cur.Previous().Address() and there is no owner parameter, so authorization is structural rather than a check: there is no address for a caller to forge and no way to publish a statement about somebody else's book. A realm that calls in publishes over its own book for free, which arrives as a property of that rule rather than as a feature — the same way it does in r/books. An EMPTY CHART IS REFUSED, and this is a deliberate divergence from p/statements, whose doc calls zero rows "a legitimate statement of nothing". It is legitimate for a pure function and not worth a storage deposit here: a frozen row of zeros at a permanent path reads as a statement rather than as an absence, and the publisher has paid to say nothing. Everything that can refuse does so before this realm writes anything.
- OID
- 03d65d…b668:15
Publish details
Count
func(owner .uverse.address) intCount is how many statements an owner has published. Zero for an address that has never published, which is not an error.
- OID
- 03d65d…b668:16
Count details
Latest
func(owner .uverse.address) (int64, bool)Latest is an owner's highest sequence number, and whether they have published at all. The second result matters: sequence numbers start at 1, so a zero first result means "never published" and never "statement 0".
- OID
- 03d65d…b668:17
Latest details
Total
func() intTotal is how many statements exist across every publisher. It is a count and nothing more; see the package doc on why there is no enumeration and no cross-owner figure.
- OID
- 03d65d…b668:18
Total details
Has
func(owner .uverse.address, seq int64) boolHas reports whether a publication exists, so a reader can distinguish it from the panics every accessor below raises for an unknown one.
- OID
- 03d65d…b668:19
Has details
PublishedAt
func(owner .uverse.address, seq int64) int64PublishedAt is the chain height a statement was frozen at.
- OID
- 03d65d…b668:20
PublishedAt details
Rows
func(owner .uverse.address, seq int64) intRows is how many accounts the statement classified.
- OID
- 03d65d…b668:21
Rows details
BalanceSheet
func(owner .uverse.address, seq int64) (assets int64, liabilities int64, equity int64, cumulativeNet int64, claims int64)BalanceSheet returns a frozen statement's balance sheet. cumulativeNet appears here as an EXPLICIT RECONCILING LINE because nothing in this stack closes a nominal account into equity. claims is liabilities + equity + cumulativeNet, so a reader who checks assets against liabilities + equity alone and finds it short by exactly cumulativeNet is not looking at a bug. assets and claims are both returned and their equality is deliberately not offered as a verdict; see the package doc.
- OID
- 03d65d…b668:22
BalanceSheet details
IncomeStatement
func(owner .uverse.address, seq int64) (cumulativeRevenue int64, cumulativeExpenses int64, cumulativeNet int64)IncomeStatement returns a frozen statement's income statement. Every figure is CUMULATIVE SINCE THE BOOK'S FIRST POSTING, not a period figure; see the package doc.
- OID
- 03d65d…b668:23
IncomeStatement details
SectionTotal
func(owner .uverse.address, seq int64, section string) int64SectionTotal returns the figure presented for one section: "assets", "claims" or "performance". The performance total is the income statement's bottom line, not the sum of its two components.
- OID
- 03d65d…b668:24
SectionTotal details
AbnormalCount
func(owner .uverse.address, seq int64) intAbnormalCount is how many accounts in a frozen statement carry a balance running against their kind. ### What the census catches, and what it cannot {#hdr-What_the_census_catches__and_what_it_cannot} This is the only partial answer in this portfolio to p/journal's consumer obligation 7 — a kind set wrongly when an account was opened is undetectable in that package and undetectable anywhere else in it. The answer is sound and incomplete, and the incompleteness is exact: - It CATCHES a misclassification that crosses the debit/credit-normal boundary and whose account has a non-zero balance. An account named "Accounts payable" but coded 1200 is an asset in r/books' scheme, accumulates credits, and reads negative here. - It CANNOT CATCH a misclassification that stays on the same side: liability to equity to revenue, or asset to expense. Both subtotals move, no balance goes negative, and nothing in this portfolio detects it. Obligation 7 is NARROWED, NOT CLOSED. - It has a FALSE-POSITIVE MODE that is not a defect. A genuine contra account — accumulated depreciation under assets, a sales return under revenue — is legitimately abnormal-signed. r/books' code-prefix scheme has no contra range, so such an account is indistinguishable here from a miscoded one. So a non-zero count is A FINDING TO READ, never a verdict, and a zero count is not a clean bill of health.
- OID
- 03d65d…b668:25
AbnormalCount details
AbnormalIn
func(owner .uverse.address, seq int64, section string) intAbnormalIn counts the abnormal accounts presented in one section: "assets", "claims" or "performance".
- OID
- 03d65d…b668:26
AbnormalIn details
AbnormalAt
func(owner .uverse.address, seq int64, i int) (code string, name string, kind string, section string, balance int64)AbnormalAt returns one census row by its zero-based index. Rows are in ascending account-code order, which p/statements enforces as a precondition on its input rather than relying on this realm to sort. section is the row's statement section, derived from its kind through the same mapping the totals were built from.
- OID
- 03d65d…b668:27
AbnormalAt details
CurrentBalanceSheet
func(owner .uverse.address) (assets int64, liabilities int64, equity int64, cumulativeNet int64, claims int64)CurrentBalanceSheet derives the owner's balance sheet from their chart NOW, publishing nothing. It exists so that a false [Verify](#Verify) is diagnosable: a reader can see which figure moved instead of only that one did. Each of the three Current accessors re-reads the chart, so asking for all of them is three walks. [Verify](#Verify) is the single-call form.
- OID
- 03d65d…b668:28
CurrentBalanceSheet details
CurrentIncomeStatement
func(owner .uverse.address) (cumulativeRevenue int64, cumulativeExpenses int64, cumulativeNet int64)CurrentIncomeStatement derives the owner's income statement from their chart now. Cumulative since inception; see the package doc.
- OID
- 03d65d…b668:29
CurrentIncomeStatement details
CurrentAbnormalCount
func(owner .uverse.address) intCurrentAbnormalCount runs the census over the owner's chart now. Its reach is exactly [AbnormalCount](#AbnormalCount)'s.
- OID
- 03d65d…b668:30
CurrentAbnormalCount details
CurrentRows
func(owner .uverse.address) intCurrentRows is how many accounts the owner's chart holds right now, as this realm reads it. It agrees with books.Accounts by construction — chartOf refuses when they disagree — and is exposed so a reader comparing a frozen statement's Rows against the live chart does not have to call two realms.
- OID
- 03d65d…b668:31
CurrentRows details
Verify
func(owner .uverse.address, seq int64) boolVerify re-derives the owner's chart now and reports whether every figure in a frozen statement still holds, including the census rows. ### Read the ceiling before quoting this {#hdr-Read_the_ceiling_before_quoting_this} A true result means THE FROZEN FIGURES STILL HOLD. It does NOT mean the chart is untouched, and the gap is demonstrable rather than theoretical: a posting that moves an amount between two asset accounts leaves Assets, Claims, the net, the row count and the census all identical, and this still reads true. The suite pins that case. A false result is a real difference and nothing more: the owner has posted since, which is the normal life of a book. It is not evidence of misconduct, and this realm offers no mechanism that treats it as such. What proves the chart's own history is r/books' append-only journal, where a wrong entry is corrected by a visible reversal rather than an edit.
- OID
- 03d65d…b668:32
Verify details
Height
func() int64Height is the chain height this realm stamps publications with. Exposed so a caller can predict what Publish will record.
- OID
- 03d65d…b668:33
Height details
Ledger
func() stringLedger is the path of the realm every figure here is derived from. The constant is [BooksPath](#BooksPath); this is the accessor for a caller that cannot read a constant off a package.
- OID
- 03d65d…b668:34
Ledger details
pubKey
func(owner string, seq int64) stringpubKey is owner|paddedSeq. The sequence is left-padded to 19 digits — the width of MaxInt64 — so that lexicographic order over an owner's prefix is numeric order over their statements. An unpadded decimal would sort "10" before "9". The convention is r/invoices' invKey, adopted rather than re-derived.
- OID
- 03d65d…b668:35
pubKey details
ownerCount
func(owner string) intownerCount reads a publisher's count, treating an absent key as zero. avl.Tree.Get returns ONE value and cannot distinguish an absent key from a nil-valued one, so the two-value type assertion carries the existence answer in one traversal. Only this file ever Sets, and it only ever Sets an int.
- OID
- 03d65d…b668:36
ownerCount details
mustRecord
func(owner .uverse.address, seq int64) *statements.record- OID
- 03d65d…b668:37
mustRecord details
parseSection
func(section string) statements.SectionparseSection turns a section word into a [stmt.Section](/gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements#Section) and panics on anything else. The three words are that type's own String output, so the round trip is exact; the suite pins it rather than trusting this to stay true of another package's method.
- OID
- 03d65d…b668:38
parseSection details
Render
func(path string) stringRender serves the index at "", a publisher's page at "\<address>", and one statement at "\<address>/\<seq>". Every caller-supplied string — an account's code and name, both of them an owner's free text in r/books — goes through \[cell]. Free text reaching Render unescaped is the one injection-shaped surface a realm with no custody still has, and here the bytes come from a THIRD realm's state, which does not make them safer.
- OID
- 03d65d…b668:39
Render details
renderIndex
func() string- OID
- 03d65d…b668:40
renderIndex details
cell
func(s string) stringcell prepares caller-supplied text for a GFM table cell, truncating it for display first. It is r/books' helper of the same name, adopted verbatim, because the slot is identical and the text is literally the same bytes one realm further on. TableCell rather than InlineText: these bytes land between two \`|\` delimiters and InlineText does not escape the pipe — deliberately, per the sanitizer's own doc, so that TableCell can escape it without double-escaping. r/books shipped InlineText in this slot first and an adversarial audit found it. The account CODE needs this as much as the name does. A GFM row is split before inline parsing runs, so a \`|\` inside a backtick span is not protected by the backticks, and r/books' kindFromCode constrains only the code's first character. TRUNCATE BEFORE ESCAPING, and on a RUNE boundary. Escaping first and cutting afterwards can slice a \`\\|\` in half and leave a cell ending in a bare backslash; cutting on a byte boundary can split a multi-byte rune and emit invalid UTF-8. By the time the sanitizer sees the string the damage is upstream of it.
- OID
- 03d65d…b668:41
cell details
renderPublisher
func(owner .uverse.address) string- OID
- 03d65d…b668:42
renderPublisher details
renderStatement
func(owner .uverse.address, seq int64) string- OID
- 03d65d…b668:43