Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

statements.gno

48.53 Kb · 1046 lines
   1// Package statements publishes classified financial statements over a chart
   2// of accounts this realm does not own, cannot write, and did not supply.
   3//
   4// # A PUBLISHED STATEMENT IS A CLAIM ABOUT A CLAIM
   5//
   6// Read this before trusting any figure this realm returns, and read it before
   7// publishing anything here.
   8//
   9// [r/books] says of itself, correctly: *"a book is a set of claims by its
  10// owner"*. It holds no asset, so nothing ties an account reading `Cash 5000`
  11// to 5000 real ugnot anywhere. A statement derived from such a book inherits
  12// that completely — and then **adds a layer of apparent authority**, because
  13// a balance sheet at a stable on-chain path, stamped with a height and a
  14// sequence number, looks like an audited artifact. It is a self-report of a
  15// self-report.
  16//
  17// This realm performs no audit, verifies no event, and holds no custody. What
  18// it guarantees is narrow and exact: the figures below are **derived** from
  19// the owner's chart at a height, by code nobody can parameterise, and frozen.
  20// That the chart describes anything real is not claimed here and is not
  21// checkable from here.
  22//
  23// # Derived, not supplied — which is the whole reason this realm exists
  24//
  25// Every figure comes from [r/books] through its own read surface. A publisher
  26// says *when to freeze*; they do not say what the statement says. The
  27// alternative design — the publisher passes the rows in — was considered in
  28// DISCOVERY.md § 4 and is fatal to the thesis: a statement whose inputs its
  29// publisher typed is an assertion about nothing, and this realm would be a
  30// pure function with a storage bill.
  31//
  32// The consequence is that **this is the first first-party realm-to-realm
  33// import in this portfolio, and it is permanent.** Bytes are immutable after
  34// addpkg, so these statements read the [r/books] at [BooksPath] forever. If
  35// that realm ever gets a v2, published statements keep reading v1 — arguably
  36// correct for an auditable artifact, and still a coupling nothing here has
  37// carried before.
  38//
  39// # What is published and what is not
  40//
  41// [Publish] freezes the three figures a balance sheet needs, the three an
  42// income statement needs, and the abnormality census. It does NOT freeze the
  43// chart: account names, codes and the postings behind them stay in [r/books],
  44// which keeps writing them.
  45//
  46// So [Verify] answers **"do the frozen figures still hold?"** and NOT **"is
  47// the chart untouched?"**. The two differ and the difference is demonstrable:
  48// a posting that moves 100 from one asset account to another leaves Assets,
  49// Claims, the net and the row count all identical, and [Verify] still reads
  50// true. It is a reconciliation, not a seal. The seal on the chart's own
  51// history is [r/books]' append-only journal, and that is where to look.
  52//
  53// # The income statement is cumulative since inception
  54//
  55// [p/journal]'s Close is a backdating seal: it refuses entries dated at or
  56// before a cutoff and sweeps no nominal account into equity. There is no
  57// retained-earnings account and no closing entry anywhere in the stack, so
  58// revenue and expense accumulate from the book's first posting forever.
  59//
  60// Every accessor and every rendered line therefore carries the word
  61// cumulative. A reader who takes them for a period figure misreads every
  62// statement this realm publishes. A period statement needs entry-level data;
  63// see DISCOVERY.md § 6.8.
  64//
  65// # The balance sheet identity is NOT offered as a check
  66//
  67// Assets and Claims are both reported. Their equality is not exposed as a
  68// verdict anywhere, because over a chart drawn from a [p/journal] Book it is
  69// algebraically identical to `totalDebits == totalCredits`, which [r/books]
  70// enforces on every post — a constant true, and true of a chart whose every
  71// account is classified backwards. [p/statements]' package doc carries the
  72// derivation. A `Balances()` here would be the fifth instance of the
  73// vacuous-true class this project keeps finding in its own work.
  74//
  75// What ships instead is the census: [AbnormalCount] and [AbnormalIn] report
  76// every account whose balance runs against its kind. That survives a balanced
  77// book. Its incompleteness is exact and is stated at [AbnormalCount].
  78//
  79// # NO CUSTODY — and refusing coins is not the same as not holding them
  80//
  81// There is no coinio import, no payable path, no fee, no withdrawal, no
  82// admin. So [Publish] REFUSES a transaction carrying coins: a realm with no
  83// withdrawal path that silently accepts ugnot strands it permanently, and
  84// "we hold no funds" would then be true of the design and false of the
  85// balance.
  86//
  87// # No admin, and no cross-owner aggregate beyond a count
  88//
  89// The deploying namespace has no privilege here: no setter, no pause, no
  90// ability to publish or to read anything a stranger cannot read. [Total] is a
  91// count of publications and nothing more. [r/books] refuses to enumerate
  92// books because such a total would sum every stranger's self-reported
  93// assertion; the same reasoning applies one level up, so there is no way to
  94// enumerate publishers and no cross-owner figure of any kind.
  95//
  96// # What this realm does NOT do
  97//
  98//   - It does not hold, move, or price any asset.
  99//   - It does not verify that an entry describes a real event.
 100//   - It does not detect a same-side misclassification. See [AbnormalCount].
 101//   - It publishes two statements and a census — no cash-flow statement, no
 102//     statement of changes in equity, no notes, no comparatives.
 103//
 104// [r/books]: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books
 105// [p/journal]: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal
 106// [p/statements]: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements
 107package statements
 108
 109import (
 110	"chain/runtime"
 111	"chain/runtime/unsafe"
 112	"strconv"
 113	"strings"
 114
 115	"gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal"
 116	stmt "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements"
 117	"gno.land/p/nt/avl/v0"
 118	"gno.land/p/nt/markdown/sanitize/v0"
 119	"gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books"
 120)
 121
 122// RealmPath is this realm's own path, used to build Render links.
 123const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements"
 124
 125// BooksPath is the ledger realm every figure here is derived from.
 126//
 127// It is taken BY VALUE from that realm's own exported constant rather than
 128// typed out again, so this is the compile-time pin itself and not a second
 129// copy of it that could drift. A reader checking which ledger a statement
 130// describes is reading the import, not a string in a doc comment.
 131const BooksPath = books.RealmPath
 132
 133// PublicationsAreUncapped records a decision by its absence, because every
 134// other realm in this portfolio caps its per-owner state and a reader will
 135// look for the cap here.
 136//
 137// There is none, and the three reasons are:
 138//
 139//   - **No shared resource is consumed.** Keys are per owner, like r/books'
 140//     books. An owner can fill their own publication history and nobody
 141//     else's, and the cost of doing so is gas plus a storage deposit each
 142//     time. That is the same griefing argument r/books makes for its
 143//     per-owner caps, and here it is sufficient on its own.
 144//   - **Nothing iterates them.** Render reads the newest
 145//     [RenderPublicationLimit] by sequence number, by direct key lookup, so
 146//     page cost does not grow with the count. No accessor walks a publisher's
 147//     history.
 148//   - **There is no engine cap to mirror.** r/books caps because p/journal
 149//     caps and the interaction of its ceiling with RederiveFromJournal is
 150//     unmeasured. No such ceiling exists above this realm.
 151//
 152// A cap added only so a test could reach it would be worse than none.
 153const PublicationsAreUncapped = true
 154
 155// RenderPublicationLimit caps how many of a publisher's statements one Render
 156// call lists. Publications are unbounded (see [PublicationsAreUncapped]), so
 157// this is what keeps a publisher's page cost flat.
 158const RenderPublicationLimit = 20
 159
 160// RenderAbnormalLimit caps how many census rows one Render call emits. A
 161// chart may hold books.MaxAccountsPerBook accounts and in a pathological book
 162// every one of them is abnormal.
 163const RenderAbnormalLimit = 25
 164
 165// ---------------------------------------------------------------------------
 166// State
 167// ---------------------------------------------------------------------------
 168
 169// record is one frozen statement. It stores the [stmt.Statement] whole rather
 170// than copying its ten fields into a parallel struct: a second field set is a
 171// second place for the two to disagree, and every field here is a value or a
 172// slice this realm allocated.
 173//
 174// There is deliberately no seq field. One was written here and read nowhere —
 175// found by the sabotage harness, which could not make dropping the assignment
 176// fail any test. A stored copy of the sequence would be a second place for the
 177// key and the record to disagree, and the key already carries it: every read
 178// path reaches a record by constructing pubKey(owner, seq) from a seq it
 179// already holds, so a field would only ever be checked against its own source.
 180type record struct {
 181	at int64
 182	st stmt.Statement
 183}
 184
 185// published maps "<owner>|<padded seq>" to *record.
 186//
 187// The pointer never leaves this file. Every accessor copies scalars out, so no
 188// caller receives a handle onto stored state — the same discipline r/books
 189// applies to its *journal.Book.
 190var published = avl.NewTree()
 191
 192// counts maps an owner's address string to how many statements they have
 193// published. It is the sequence allocator as well as the count.
 194var counts = avl.NewTree()
 195
 196// pubCount is the global total, maintained alongside the tree so the index
 197// page never iterates. It is a count and nothing more; see the package doc on
 198// why there is no way to enumerate publishers.
 199var pubCount int
 200
 201// ---------------------------------------------------------------------------
 202// Guards
 203// ---------------------------------------------------------------------------
 204
 205// refuseCoins panics if a user call arrives carrying coins. Adopted verbatim
 206// from r/books, which faces the identical slot: no withdrawal path, so
 207// accepted coins are stranded permanently.
 208//
 209// The guard is on IsUserCall rather than IsUser deliberately, and the reason
 210// is NOT the one the canonical payment-guard advice gives. That advice is about
 211// a realm that ACCEPTS payment, where IsUser is dangerously broad because the
 212// MsgRun ephemeral can consume the envelope before forwarding control and the
 213// receipt invariant no longer holds. This guard REFUSES, so the broader
 214// predicate would refuse MORE, and "more conservative" is the wrong frame.
 215//
 216// The real reason is that OriginSend is a fact about the TRANSACTION, not about
 217// this realm. It is a faithful receipt only on the direct MsgCall path, where
 218// the envelope's destination is this realm. On a MsgRun path the envelope can
 219// be non-empty while nothing arrives here — coins sent to the ephemeral flow
 220// back to the user — so pairing it with IsUser buys no protection and adds a
 221// FALSE REFUSAL mode: a panic on a call that brought this realm nothing.
 222// IsUserCall is the narrowest predicate covering the only path on which a
 223// non-empty envelope means coins actually credited to this address.
 224//
 225// The residual is stated rather than guarded: coins can still reach this
 226// realm's address through an intermediary's explicit banker send, which no
 227// predicate here can see. What makes that survivable is not this guard but the
 228// absence of any consumption path — nothing in this realm can spend what
 229// arrives, so a stranded balance is inert rather than exploitable.
 230//
 231// An earlier version of this comment said "OriginSend is only meaningful for a
 232// direct MsgCall", which is false as written — it is readable on every path and
 233// merely stops being a receipt. An independent audit caught it. The predicate
 234// was right and the stated reason was not, which is the worse of the two
 235// failures: the predicate is re-derivable from the references and a confident
 236// wrong sentence is what stops a maintainer re-deriving it. Onyx's VM tightens
 237// IsUserCall, so this is exactly the comment that gets consulted after the
 238// semantics have moved underneath it.
 239func refuseCoins(cur realm) {
 240	// A no-op at today's single call site: Publish's first cur is
 241	// runtime-current by construction and stays current when forwarded through
 242	// a non-crossing call. It is here because this realm is IMMUTABLE after
 243	// addpkg, so a future second caller cannot be made safe later — and a
 244	// helper that reads Previous() off an unchecked realm parameter is a
 245	// designation-forgery shape whether or not it is reachable today.
 246	if !cur.IsCurrent() {
 247		panic("statements: refuseCoins called with a realm that is not current")
 248	}
 249	if cur.Previous().IsUserCall() && len(unsafe.OriginSend()) > 0 {
 250		panic("statements: this realm holds no coins and has no withdrawal path; send none")
 251	}
 252}
 253
 254// ---------------------------------------------------------------------------
 255// The cross-realm reads
 256// ---------------------------------------------------------------------------
 257
 258// parseKind turns one of [r/books]' five kind words back into a
 259// [journal.Kind], and PANICS on anything else.
 260//
 261// This is the ugly seam and it is named rather than hidden. r/books returns an
 262// account's kind as `journal.Kind.String()` — a presentation format that has
 263// become an API across a realm boundary — and no Kind parser exists anywhere
 264// in this portfolio (measured in DISCOVERY.md § 7, step 2c: zero hits for
 265// ParseKind, parseKind and kindFromString; r/books' own kindFromCode maps a
 266// code PREFIX to a kind and is unexported).
 267//
 268// It panics rather than returning a zero Kind, and the reason is specific: the
 269// zero Kind is reported CREDIT-NORMAL by [journal.Kind.IsDebitNormal], so a
 270// silently-unparsed word would not drop the row — it would file it under
 271// claims and produce a statement that balances and misstates. p/statements'
 272// Derive would catch it as ErrInvalidKind, and relying on that would make this
 273// realm's error message point at the wrong layer.
 274//
 275// The round trip is pinned in the suite over all five kinds rather than
 276// assumed, because the contract is another realm's String method.
 277func parseKind(s string) journal.Kind {
 278	switch s {
 279	case "asset":
 280		return journal.Asset
 281	case "liability":
 282		return journal.Liability
 283	case "equity":
 284		return journal.Equity
 285	case "revenue":
 286		return journal.Revenue
 287	case "expense":
 288		return journal.Expense
 289	}
 290	panic("statements: unreadable account kind " + strconv.Quote(s) +
 291		" from " + BooksPath)
 292}
 293
 294// chartOf reads an owner's whole chart out of [r/books] as journal.Account
 295// rows, ready for [stmt.Derive].
 296//
 297// # One walk, not a paging loop
 298//
 299// books.Codes takes a `from` and a `limit`, so the obvious shape is a paging
 300// loop — and DISCOVERY.md § 6.4 planned one, costing ceil(n/limit) full chart
 301// walks because that iterator has no seek and honours `from` by skipping.
 302// This asks for the whole chart in ONE call instead, with limit at r/books'
 303// own account cap, which is exactly one walk and is strictly cheaper than any
 304// paging of it. The cap is read from that realm's exported constant, so the
 305// request can never be short of what a book may hold.
 306//
 307// Dropping the loop also drops its hazard. books.Codes' `from` is INCLUSIVE —
 308// paging re-reads the last code received — so every paging caller owes itself
 309// a boundary dedup, and a missed one double-counts one account per page
 310// boundary. A double-counted balance is the defect this product is least able
 311// to detect: the statement would still balance.
 312//
 313// # The agreement check is not decoration
 314//
 315// books.Accounts and books.Codes are two reads, taken at one instant but
 316// through different code paths. If they disagree this refuses rather than
 317// publishing over a truncated chart, because a chart missing rows yields a
 318// statement that is wrong in a way no identity here can see.
 319//
 320// # Taint
 321//
 322// The []string books.Codes returns was allocated under that realm and reaches
 323// here readonly-tainted. Nothing below mutates it or stores it: the codes are
 324// read out by value, and the rows are allocated here.
 325func chartOf(owner address) []journal.Account {
 326	n := books.Accounts(owner) // panics if the owner has no book
 327	codes := books.Codes(owner, "", books.MaxAccountsPerBook)
 328	if len(codes) != n {
 329		panic("statements: " + BooksPath + " reports " + strconv.Itoa(n) +
 330			" accounts but enumerated " + strconv.Itoa(len(codes)) +
 331			"; refusing to report on a partial chart")
 332	}
 333
 334	rows := make([]journal.Account, 0, n)
 335	for _, code := range codes {
 336		name, kind, debits, credits, balance := books.AccountInfo(owner, code)
 337		a := journal.Account{
 338			Code:    code,
 339			Name:    name,
 340			Kind:    parseKind(kind),
 341			Debits:  debits,
 342			Credits: credits,
 343		}
 344		// The balance r/books reported, against the one this row computes.
 345		//
 346		// State its reach exactly, because the flattering reading is
 347		// available: BOTH sides are journal.Account.Balance over the same two
 348		// columns, so this is a CONSTANT TRUE against any disagreement
 349		// between the two realms' arithmetic and must never be quoted as
 350		// cross-checking it. What it does catch is a transcription error in
 351		// the four assignments above — swapping Debits and Credits, or
 352		// reading a column off the wrong code — which is this realm's own
 353		// code and the only place in this path where that could happen.
 354		if a.Balance() != balance {
 355			panic("statements: balance mismatch for account " + code +
 356				"; this realm transcribed the chart wrongly")
 357		}
 358		rows = append(rows, a)
 359	}
 360	return rows
 361}
 362
 363// deriveLive reads the owner's chart now and classifies it. Every live figure
 364// in this realm goes through here, so there is one path from the ledger to a
 365// statement and no accessor can derive differently from Publish.
 366//
 367// MustDerive, not Derive: on the mutating path an error return does not revert
 368// a Gno transaction and only a panic does.
 369func deriveLive(owner address) stmt.Statement {
 370	return stmt.MustDerive(chartOf(owner))
 371}
 372
 373// ---------------------------------------------------------------------------
 374// Write
 375// ---------------------------------------------------------------------------
 376
 377// Publish freezes the caller's own statement at the current height and returns
 378// its sequence number, which starts at 1 for each publisher.
 379//
 380// The owner is cur.Previous().Address() and there is no owner parameter, so
 381// authorization is structural rather than a check: there is no address for a
 382// caller to forge and no way to publish a statement about somebody else's
 383// book. A realm that calls in publishes over its own book for free, which
 384// arrives as a property of that rule rather than as a feature — the same way
 385// it does in r/books.
 386//
 387// An EMPTY CHART IS REFUSED, and this is a deliberate divergence from
 388// p/statements, whose doc calls zero rows "a legitimate statement of nothing".
 389// It is legitimate for a pure function and not worth a storage deposit here: a
 390// frozen row of zeros at a permanent path reads as a statement rather than as
 391// an absence, and the publisher has paid to say nothing.
 392//
 393// Everything that can refuse does so before this realm writes anything.
 394func Publish(cur realm) int64 {
 395	refuseCoins(cur)
 396	owner := cur.Previous().Address()
 397
 398	rows := chartOf(owner)
 399	if len(rows) == 0 {
 400		panic("statements: this book has no accounts; there is nothing to report on")
 401	}
 402	st := stmt.MustDerive(rows)
 403
 404	key := owner.String()
 405	seq := int64(ownerCount(key)) + 1
 406	published.Set(pubKey(key, seq), &record{
 407		at: runtime.ChainHeight(),
 408		st: st,
 409	})
 410	counts.Set(key, int(seq))
 411	pubCount++
 412	return seq
 413}
 414
 415// ---------------------------------------------------------------------------
 416// Reads — the frozen statements
 417// ---------------------------------------------------------------------------
 418
 419// Count is how many statements an owner has published. Zero for an address
 420// that has never published, which is not an error.
 421func Count(owner address) int { return ownerCount(owner.String()) }
 422
 423// Latest is an owner's highest sequence number, and whether they have
 424// published at all. The second result matters: sequence numbers start at 1, so
 425// a zero first result means "never published" and never "statement 0".
 426func Latest(owner address) (int64, bool) {
 427	n := ownerCount(owner.String())
 428	if n == 0 {
 429		return 0, false
 430	}
 431	return int64(n), true
 432}
 433
 434// Total is how many statements exist across every publisher. It is a count and
 435// nothing more; see the package doc on why there is no enumeration and no
 436// cross-owner figure.
 437func Total() int { return pubCount }
 438
 439// Has reports whether a publication exists, so a reader can distinguish it
 440// from the panics every accessor below raises for an unknown one.
 441func Has(owner address, seq int64) bool {
 442	return published.Has(pubKey(owner.String(), seq))
 443}
 444
 445// PublishedAt is the chain height a statement was frozen at.
 446func PublishedAt(owner address, seq int64) int64 {
 447	return mustRecord(owner, seq).at
 448}
 449
 450// Rows is how many accounts the statement classified.
 451func Rows(owner address, seq int64) int {
 452	return mustRecord(owner, seq).st.Rows
 453}
 454
 455// BalanceSheet returns a frozen statement's balance sheet.
 456//
 457// cumulativeNet appears here as an EXPLICIT RECONCILING LINE because nothing
 458// in this stack closes a nominal account into equity. claims is
 459// liabilities + equity + cumulativeNet, so a reader who checks assets against
 460// liabilities + equity alone and finds it short by exactly cumulativeNet is
 461// not looking at a bug.
 462//
 463// assets and claims are both returned and their equality is deliberately not
 464// offered as a verdict; see the package doc.
 465func BalanceSheet(owner address, seq int64) (assets, liabilities, equity, cumulativeNet, claims int64) {
 466	s := mustRecord(owner, seq).st
 467	return s.Assets, s.Liabilities, s.Equity, s.CumulativeNet, s.Claims
 468}
 469
 470// IncomeStatement returns a frozen statement's income statement. Every figure
 471// is CUMULATIVE SINCE THE BOOK'S FIRST POSTING, not a period figure; see the
 472// package doc.
 473func IncomeStatement(owner address, seq int64) (cumulativeRevenue, cumulativeExpenses, cumulativeNet int64) {
 474	s := mustRecord(owner, seq).st
 475	return s.CumulativeRevenue, s.CumulativeExpenses, s.CumulativeNet
 476}
 477
 478// SectionTotal returns the figure presented for one section: "assets",
 479// "claims" or "performance". The performance total is the income statement's
 480// bottom line, not the sum of its two components.
 481func SectionTotal(owner address, seq int64, section string) int64 {
 482	v, err := mustRecord(owner, seq).st.SectionTotal(parseSection(section))
 483	if err != nil {
 484		// Unreachable: parseSection panics on anything outside the three.
 485		panic("statements: " + err.Error())
 486	}
 487	return v
 488}
 489
 490// AbnormalCount is how many accounts in a frozen statement carry a balance
 491// running against their kind.
 492//
 493// # What the census catches, and what it cannot
 494//
 495// This is the only partial answer in this portfolio to p/journal's consumer
 496// obligation 7 — a kind set wrongly when an account was opened is undetectable
 497// in that package and undetectable anywhere else in it. The answer is sound and
 498// incomplete, and the incompleteness is exact:
 499//
 500//   - It CATCHES a misclassification that crosses the debit/credit-normal
 501//     boundary and whose account has a non-zero balance. An account named
 502//     "Accounts payable" but coded 1200 is an asset in r/books' scheme,
 503//     accumulates credits, and reads negative here.
 504//   - It CANNOT CATCH a misclassification that stays on the same side:
 505//     liability to equity to revenue, or asset to expense. Both subtotals
 506//     move, no balance goes negative, and nothing in this portfolio detects
 507//     it. Obligation 7 is NARROWED, NOT CLOSED.
 508//   - It has a FALSE-POSITIVE MODE that is not a defect. A genuine contra
 509//     account — accumulated depreciation under assets, a sales return under
 510//     revenue — is legitimately abnormal-signed. r/books' code-prefix scheme
 511//     has no contra range, so such an account is indistinguishable here from a
 512//     miscoded one.
 513//
 514// So a non-zero count is A FINDING TO READ, never a verdict, and a zero count
 515// is not a clean bill of health.
 516func AbnormalCount(owner address, seq int64) int {
 517	return len(mustRecord(owner, seq).st.Abnormal)
 518}
 519
 520// AbnormalIn counts the abnormal accounts presented in one section:
 521// "assets", "claims" or "performance".
 522func AbnormalIn(owner address, seq int64, section string) int {
 523	return mustRecord(owner, seq).st.AbnormalIn(parseSection(section))
 524}
 525
 526// AbnormalAt returns one census row by its zero-based index. Rows are in
 527// ascending account-code order, which p/statements enforces as a precondition
 528// on its input rather than relying on this realm to sort.
 529//
 530// section is the row's statement section, derived from its kind through the
 531// same mapping the totals were built from.
 532func AbnormalAt(owner address, seq int64, i int) (code, name, kind, section string, balance int64) {
 533	rows := mustRecord(owner, seq).st.Abnormal
 534	if i < 0 || i >= len(rows) {
 535		panic("statements: no census row " + strconv.Itoa(i) +
 536			"; this statement has " + strconv.Itoa(len(rows)))
 537	}
 538	a := rows[i]
 539	sec, err := stmt.SectionOf(a.Kind)
 540	if err != nil {
 541		// Unreachable: Derive refuses a row whose kind is outside the five.
 542		panic("statements: " + err.Error())
 543	}
 544	return a.Code, a.Name, a.Kind.String(), sec.String(), a.Balance
 545}
 546
 547// ---------------------------------------------------------------------------
 548// Reads — the live chart
 549// ---------------------------------------------------------------------------
 550
 551// THE COST DECISION FOR EVERY ACCESSOR BELOW, RECORDED BECAUSE IT WAS NOT.
 552//
 553// Each of these takes an `owner address` the CALLER names, and each one walks
 554// that stranger's whole chart: one books.Codes call plus up to
 555// books.MaxAccountsPerBook separate cross-realm books.AccountInfo calls. None
 556// of them is a crossing function, so anyone can trigger that against any
 557// address for free. An audit raised it; this block is the answer, and the
 558// realm's other cost decisions are documented so this one has to be too.
 559//
 560// WHAT IS AND IS NOT AT STAKE. The querying caller pays the gas. Nothing is
 561// charged to the owner, nothing accumulates in chain state, and no figure here
 562// can be moved by the query. The residual is that a vm/qrender or vm/qeval
 563// against a chart at the account cap is that many cross-realm calls of
 564// NODE-SIDE work — a node-operator consideration, not a hazard to this realm
 565// or to the owner. [Verify] being reached from [Render] is the one part that is
 566// involuntary for a viewer and repeatable by anyone: a public page pays it per
 567// view.
 568//
 569// THE DECISION IS TO KEEP THE INLINE CALL. The cheap alternative is for
 570// renderStatement to print the frozen figures and link to [Verify] instead of
 571// calling it. That is refused because a page showing frozen figures with no
 572// live comparison is the exact artifact this realm exists to improve on — the
 573// whole reason [Verify] and these accessors exist is that a stale statement be
 574// visibly stale, and deferring that to a second click makes the common reading
 575// the uninformed one. The bound is the realm's own account cap, it is
 576// double-clamped by books.Codes, and the cost is flat per view rather than
 577// growing.
 578//
 579// THE AXIS THAT DOES NOT PRICE THIS. Entries, not accounts, are what a book
 580// accumulates without bound, so the question that decides the ceiling is
 581// whether this walk is priced by entries as well as by accounts. It is not,
 582// and the reason is STRUCTURAL rather than measured: books.AccountInfo returns
 583// the account's STORED Debits and Credits and a Balance() over them, so it
 584// READS NO ENTRIES AT ALL and performs no journal replay. chartOf does one
 585// books.Codes walk plus one AccountInfo per account, bounded by the account
 586// cap. So the ceiling is the MAX of the account and entry axes and never their
 587// product.
 588//
 589// Say it as "O(1) in entries EXAMINED", never as "O(1) in gas" -- the second is
 590// the overclaim available here and the measurement below refutes it. A bigger
 591// book is a bigger store and every lookup into it traverses a deeper avl tree,
 592// so cost does grow with entries: sub-linearly, in the store's depth rather
 593// than in the entry count. The ceiling argument needs only that the entry term
 594// is absent from the PRODUCT, which this gives it.
 595//
 596// A gas measurement checks that and does not establish it, which is the right
 597// way round and was not how this comment first read. Measured on the pinned
 598// onyx toolchain, two accounts held constant, as (101 reads minus 1 read) / 100
 599// so the setup cancels: about 476k gas per read against a 3-entry book and
 600// about 563k against one at books.MaxEntriesPerBook -- +18% across the whole
 601// entry range, or roughly twenty gas per entry, IN THAT FIXTURE. Read the next
 602// paragraph before quoting either figure; the qualifier is not a hedge and the
 603// percentage is not a property of this realm. What the figures do establish is
 604// the shape: entry count adds a realm-size overhead and not a replay, which is
 605// what the structural argument predicts.
 606//
 607// THOSE TWO FIGURES ARE EXACTLY REPRODUCIBLE AND ARE STILL NOT A PRICE, and
 608// the distinction is the whole point of this paragraph. Two separate
 609// invocations of the same four-case fixture returned 476,470 and 562,874 gas
 610// per read, identical to the unit -- so this is not run-to-run noise and the
 611// toolchain is deterministic. Add NINE UNRELATED test cases ahead of the same
 612// four and the cap figure becomes 828,602, up 47%, while the 3-entry figure
 613// moves 3%. What the number is sensitive to is therefore the total avl state
 614// resident when the read happens, not the entry count under test. A figure
 615// measured in a fixture is a figure about that fixture; on chain the resident
 616// state is the whole realm's history and nothing here bounds it.
 617//
 618// That also explains the two designs that failed, which are kept because the
 619// failure is the evidence. Differencing setup-only against setup-plus-one-read
 620// at the cap puts a ~5e5 signal against a ~3.5e9 setup. Differencing by TEN
 621// reads returned a NEGATIVE marginal: each case carries its own setup, and the
 622// later case's setup runs against more accumulated state than the earlier
 623// one's, so the order effect exceeded a 10-read signal and reversed its sign.
 624// A hundred reads clears that; ten does not. The confound was found by the sign
 625// rather than argued.
 626//
 627// An earlier version of this comment quoted 1,060,407 and 1,085,495 gas,
 628// "+2.4%", "about six gas per entry". Those came from one of the confounded
 629// designs and did not reproduce. They are replaced rather than defended. The
 630// structural claim above is the one that survives, and it is checkable from
 631// source forever while a gas figure is not.
 632//
 633// WHAT THIS IS NOT. It is NOT the cost decision books.Rederive makes by being
 634// a crossing function. That realm's doc says a free read there "would let
 635// anyone make an owner's book expensive to query", and the first reading of
 636// this finding was that this realm undoes that decision. It does not, and the
 637// analogy was withdrawn on evidence: Rederive replays the APPEND-ONLY JOURNAL,
 638// bounded by entry count and growing without bound, while chartOf is bounded
 639// by ACCOUNTS at a fixed cap. The two differ by orders of magnitude, which is
 640// why one is gated and these are not.
 641
 642// CurrentBalanceSheet derives the owner's balance sheet from their chart NOW,
 643// publishing nothing. It exists so that a false [Verify] is diagnosable: a
 644// reader can see which figure moved instead of only that one did.
 645//
 646// Each of the three Current accessors re-reads the chart, so asking for all of
 647// them is three walks. [Verify] is the single-call form.
 648func CurrentBalanceSheet(owner address) (assets, liabilities, equity, cumulativeNet, claims int64) {
 649	s := deriveLive(owner)
 650	return s.Assets, s.Liabilities, s.Equity, s.CumulativeNet, s.Claims
 651}
 652
 653// CurrentIncomeStatement derives the owner's income statement from their chart
 654// now. Cumulative since inception; see the package doc.
 655func CurrentIncomeStatement(owner address) (cumulativeRevenue, cumulativeExpenses, cumulativeNet int64) {
 656	s := deriveLive(owner)
 657	return s.CumulativeRevenue, s.CumulativeExpenses, s.CumulativeNet
 658}
 659
 660// CurrentAbnormalCount runs the census over the owner's chart now. Its reach
 661// is exactly [AbnormalCount]'s.
 662func CurrentAbnormalCount(owner address) int {
 663	return len(deriveLive(owner).Abnormal)
 664}
 665
 666// CurrentRows is how many accounts the owner's chart holds right now, as this
 667// realm reads it. It agrees with books.Accounts by construction — chartOf
 668// refuses when they disagree — and is exposed so a reader comparing a frozen
 669// statement's Rows against the live chart does not have to call two realms.
 670func CurrentRows(owner address) int { return len(chartOf(owner)) }
 671
 672// Verify re-derives the owner's chart now and reports whether every figure in
 673// a frozen statement still holds, including the census rows.
 674//
 675// # Read the ceiling before quoting this
 676//
 677// A true result means THE FROZEN FIGURES STILL HOLD. It does NOT mean the
 678// chart is untouched, and the gap is demonstrable rather than theoretical: a
 679// posting that moves an amount between two asset accounts leaves Assets,
 680// Claims, the net, the row count and the census all identical, and this still
 681// reads true. The suite pins that case.
 682//
 683// A false result is a real difference and nothing more: the owner has posted
 684// since, which is the normal life of a book. It is not evidence of
 685// misconduct, and this realm offers no mechanism that treats it as such.
 686//
 687// What proves the chart's own history is r/books' append-only journal, where
 688// a wrong entry is corrected by a visible reversal rather than an edit.
 689func Verify(owner address, seq int64) bool {
 690	want := mustRecord(owner, seq).st
 691	got := deriveLive(owner)
 692
 693	if want.Rows != got.Rows ||
 694		want.Assets != got.Assets ||
 695		want.Liabilities != got.Liabilities ||
 696		want.Equity != got.Equity ||
 697		want.CumulativeRevenue != got.CumulativeRevenue ||
 698		want.CumulativeExpenses != got.CumulativeExpenses ||
 699		want.CumulativeNet != got.CumulativeNet ||
 700		want.Claims != got.Claims {
 701		return false
 702	}
 703	if len(want.Abnormal) != len(got.Abnormal) {
 704		return false
 705	}
 706	for i, a := range want.Abnormal {
 707		b := got.Abnormal[i]
 708		if a.Code != b.Code || a.Kind != b.Kind || a.Balance != b.Balance {
 709			return false
 710		}
 711		// Name is deliberately NOT compared. r/books has no rename path, so
 712		// it cannot change; comparing it would assert a property of that
 713		// realm's API surface from here, where a reader would mistake it for
 714		// a check on the chart.
 715	}
 716	return true
 717}
 718
 719// Height is the chain height this realm stamps publications with. Exposed so a
 720// caller can predict what Publish will record.
 721func Height() int64 { return runtime.ChainHeight() }
 722
 723// Ledger is the path of the realm every figure here is derived from. The
 724// constant is [BooksPath]; this is the accessor for a caller that cannot read
 725// a constant off a package.
 726func Ledger() string { return BooksPath }
 727
 728// ---------------------------------------------------------------------------
 729// Internals
 730// ---------------------------------------------------------------------------
 731
 732// pubKey is owner|paddedSeq. The sequence is left-padded to 19 digits — the
 733// width of MaxInt64 — so that lexicographic order over an owner's prefix is
 734// numeric order over their statements. An unpadded decimal would sort "10"
 735// before "9". The convention is r/invoices' invKey, adopted rather than
 736// re-derived.
 737func pubKey(owner string, seq int64) string {
 738	s := strconv.FormatInt(seq, 10)
 739	for len(s) < 19 {
 740		s = "0" + s
 741	}
 742	return owner + "|" + s
 743}
 744
 745// ownerCount reads a publisher's count, treating an absent key as zero.
 746//
 747// avl.Tree.Get returns ONE value and cannot distinguish an absent key from a
 748// nil-valued one, so the two-value type assertion carries the existence answer
 749// in one traversal. Only this file ever Sets, and it only ever Sets an int.
 750func ownerCount(owner string) int {
 751	n, ok := counts.Get(owner).(int)
 752	if !ok {
 753		return 0
 754	}
 755	return n
 756}
 757
 758func mustRecord(owner address, seq int64) *record {
 759	r, ok := published.Get(pubKey(owner.String(), seq)).(*record)
 760	if !ok {
 761		panic("statements: no statement " + strconv.FormatInt(seq, 10) +
 762			" for " + owner.String())
 763	}
 764	return r
 765}
 766
 767// parseSection turns a section word into a [stmt.Section] and panics on
 768// anything else. The three words are that type's own String output, so the
 769// round trip is exact; the suite pins it rather than trusting this to stay
 770// true of another package's method.
 771func parseSection(section string) stmt.Section {
 772	switch strings.ToLower(strings.TrimSpace(section)) {
 773	case "assets":
 774		return stmt.SectionAssets
 775	case "claims":
 776		return stmt.SectionClaims
 777	case "performance":
 778		return stmt.SectionPerformance
 779	}
 780	panic("statements: unknown section " + strconv.Quote(section) +
 781		"; want assets, claims or performance")
 782}
 783
 784// ---------------------------------------------------------------------------
 785// Render
 786// ---------------------------------------------------------------------------
 787
 788// Render serves the index at "", a publisher's page at "<address>", and one
 789// statement at "<address>/<seq>".
 790//
 791// Every caller-supplied string — an account's code and name, both of them an
 792// owner's free text in r/books — goes through [cell]. Free text reaching
 793// Render unescaped is the one injection-shaped surface a realm with no custody
 794// still has, and here the bytes come from a THIRD realm's state, which does
 795// not make them safer.
 796func Render(path string) string {
 797	p := strings.TrimSpace(strings.Trim(path, "/"))
 798	if p == "" {
 799		return renderIndex()
 800	}
 801	if i := strings.Index(p, "/"); i >= 0 {
 802		owner := address(strings.TrimSpace(p[:i]))
 803		rest := strings.TrimSpace(strings.Trim(p[i+1:], "/"))
 804		seq, err := strconv.ParseInt(rest, 10, 64)
 805		if err != nil {
 806			return "# statements\n\n`" + sanitize.InlineText(rest) +
 807				"` is not a statement number.\n"
 808		}
 809		return renderStatement(owner, seq)
 810	}
 811	return renderPublisher(address(p))
 812}
 813
 814func renderIndex() string {
 815	var sb strings.Builder
 816	sb.WriteString("# statements — classified financial statements\n\n")
 817	sb.WriteString("A balance sheet, a cumulative income statement and an ")
 818	sb.WriteString("abnormality census, **derived** from a chart of accounts ")
 819	sb.WriteString("in [`" + BooksPath + "`](" + BooksPath + ") — never ")
 820	sb.WriteString("supplied by the publisher.\n\n")
 821
 822	sb.WriteString("> **A published statement is a claim about a claim.** ")
 823	sb.WriteString("The ledger it reads holds no asset, so nothing ties an ")
 824	sb.WriteString("account reading `Cash 5000` to 5000 real ugnot anywhere. ")
 825	sb.WriteString("A statement derived from it inherits that entirely and ")
 826	sb.WriteString("then adds a layer of apparent authority: it looks like an ")
 827	sb.WriteString("audited artifact and is a self-report of a self-report. ")
 828	sb.WriteString("No audit is performed here and no custody exists.\n\n")
 829
 830	sb.WriteString("## State\n\n")
 831	sb.WriteString("- statements published: **" + strconv.Itoa(pubCount) + "**\n")
 832	sb.WriteString("- ledger: `" + BooksPath + "`\n")
 833	sb.WriteString("- chain height: **" + strconv.FormatInt(runtime.ChainHeight(), 10) + "**\n\n")
 834	sb.WriteString("There is deliberately no way to enumerate publishers or ")
 835	sb.WriteString("to aggregate across them: such a total would sum every ")
 836	sb.WriteString("stranger's self-reported assertion.\n\n")
 837
 838	sb.WriteString("## What is frozen, and what is not\n\n")
 839	sb.WriteString("`Publish` freezes the **figures**. It does not freeze the ")
 840	sb.WriteString("chart — account codes, names and the postings behind them ")
 841	sb.WriteString("stay in the ledger, which keeps writing them. So ")
 842	sb.WriteString("`Verify` answers *\"do the frozen figures still hold?\"* ")
 843	sb.WriteString("and **not** *\"is the chart untouched?\"*: a posting that ")
 844	sb.WriteString("moves an amount between two asset accounts leaves every ")
 845	sb.WriteString("figure identical and `Verify` still reads true.\n\n")
 846
 847	sb.WriteString("## The three sections\n\n")
 848	sb.WriteString("| section | account kinds | presented as |\n|---|---|---|\n")
 849	sb.WriteString("| `assets` | asset | balance sheet, debit side |\n")
 850	sb.WriteString("| `claims` | liability, equity | balance sheet, credit side |\n")
 851	sb.WriteString("| `performance` | revenue, expense | income statement |\n\n")
 852	sb.WriteString("Revenue and expenses are **cumulative since the book's ")
 853	sb.WriteString("first posting**, not period figures: nothing in this ")
 854	sb.WriteString("stack closes a nominal account into equity, so the net ")
 855	sb.WriteString("appears on the balance sheet as an explicit reconciling ")
 856	sb.WriteString("line.\n\n")
 857
 858	sb.WriteString("## Usage\n\n")
 859	sb.WriteString("```\nseq := Publish()            // over the caller's own book\n")
 860	sb.WriteString("BalanceSheet(owner, seq)\nIncomeStatement(owner, seq)\n")
 861	sb.WriteString("AbnormalCount(owner, seq)   // a finding to read, not a verdict\n")
 862	sb.WriteString("Verify(owner, seq)          // do the figures still hold?\n```\n\n")
 863	sb.WriteString("A publisher's statements are at `" + RealmPath +
 864		":<address>`, and one statement at `" + RealmPath +
 865		":<address>/<seq>`.\n")
 866	return sb.String()
 867}
 868
 869// cell prepares caller-supplied text for a GFM table cell, truncating it for
 870// display first.
 871//
 872// It is r/books' helper of the same name, adopted verbatim, because the slot is
 873// identical and the text is literally the same bytes one realm further on.
 874// TableCell rather than InlineText: these bytes land between two `|`
 875// delimiters and InlineText does not escape the pipe — deliberately, per the
 876// sanitizer's own doc, so that TableCell can escape it without
 877// double-escaping. r/books shipped InlineText in this slot first and an
 878// adversarial audit found it.
 879//
 880// The account CODE needs this as much as the name does. A GFM row is split
 881// before inline parsing runs, so a `|` inside a backtick span is not protected
 882// by the backticks, and r/books' kindFromCode constrains only the code's first
 883// character.
 884//
 885// TRUNCATE BEFORE ESCAPING, and on a RUNE boundary. Escaping first and cutting
 886// afterwards can slice a `\|` in half and leave a cell ending in a bare
 887// backslash; cutting on a byte boundary can split a multi-byte rune and emit
 888// invalid UTF-8. By the time the sanitizer sees the string the damage is
 889// upstream of it.
 890func cell(s string) string {
 891	const maxRunes = 48
 892	if r := []rune(s); len(r) > maxRunes {
 893		s = string(r[:maxRunes]) + "…"
 894	}
 895	return sanitize.TableCell(s)
 896}
 897
 898func renderPublisher(owner address) string {
 899	n := ownerCount(owner.String())
 900	if n == 0 {
 901		return "# statements\n\nNo statement published by `" +
 902			sanitize.InlineText(owner.String()) + "`.\n"
 903	}
 904
 905	var sb strings.Builder
 906	sb.WriteString("# Statements published by " +
 907		sanitize.InlineText(owner.String()) + "\n\n")
 908	sb.WriteString("Derived from that address's book in [`" + BooksPath +
 909		"`](" + BooksPath + ":" + owner.String() + "). ")
 910	sb.WriteString("**A statement is a claim about a claim** — see the ")
 911	sb.WriteString("[index](" + RealmPath + ").\n\n")
 912
 913	shown := n
 914	if shown > RenderPublicationLimit {
 915		shown = RenderPublicationLimit
 916	}
 917	sb.WriteString("| seq | height | rows | assets | claims | cumulative net | census |\n")
 918	sb.WriteString("|---|---|---|---|---|---|---|\n")
 919	for seq := int64(n); seq > int64(n-shown); seq-- {
 920		r, ok := published.Get(pubKey(owner.String(), seq)).(*record)
 921		if !ok {
 922			// Unreachable: counts and published are written together.
 923			continue
 924		}
 925		sb.WriteString("| [" + strconv.FormatInt(seq, 10) + "](" + RealmPath +
 926			":" + owner.String() + "/" + strconv.FormatInt(seq, 10) + ") | " +
 927			strconv.FormatInt(r.at, 10) + " | " +
 928			strconv.Itoa(r.st.Rows) + " | " +
 929			strconv.FormatInt(r.st.Assets, 10) + " | " +
 930			strconv.FormatInt(r.st.Claims, 10) + " | " +
 931			strconv.FormatInt(r.st.CumulativeNet, 10) + " | " +
 932			strconv.Itoa(len(r.st.Abnormal)) + " |\n")
 933	}
 934	if n > shown {
 935		sb.WriteString("\n_Showing the " + strconv.Itoa(shown) +
 936			" most recent of " + strconv.Itoa(n) + " statements._\n")
 937	}
 938	return sb.String()
 939}
 940
 941func renderStatement(owner address, seq int64) string {
 942	r, ok := published.Get(pubKey(owner.String(), seq)).(*record)
 943	if !ok {
 944		return "# statements\n\nNo statement " +
 945			strconv.FormatInt(seq, 10) + " for `" +
 946			sanitize.InlineText(owner.String()) + "`.\n"
 947	}
 948	s := r.st
 949
 950	var sb strings.Builder
 951	sb.WriteString("# Statement " + strconv.FormatInt(seq, 10) + " of " +
 952		sanitize.InlineText(owner.String()) + "\n\n")
 953	sb.WriteString("Frozen at height **" + strconv.FormatInt(r.at, 10) +
 954		"** over **" + strconv.Itoa(s.Rows) + "** accounts, derived from [`" +
 955		BooksPath + "`](" + BooksPath + ":" + owner.String() + ").\n\n")
 956
 957	sb.WriteString("> **This is a claim about a claim.** The ledger holds no ")
 958	sb.WriteString("asset and no audit was performed. Every figure below is ")
 959	sb.WriteString("derived faithfully from what this address wrote about ")
 960	sb.WriteString("itself.\n\n")
 961
 962	sb.WriteString("## Balance sheet\n\n")
 963	sb.WriteString("| | |\n|---|---|\n")
 964	sb.WriteString("| assets | " + strconv.FormatInt(s.Assets, 10) + " |\n")
 965	sb.WriteString("| liabilities | " + strconv.FormatInt(s.Liabilities, 10) + " |\n")
 966	sb.WriteString("| equity | " + strconv.FormatInt(s.Equity, 10) + " |\n")
 967	sb.WriteString("| cumulative net | " + strconv.FormatInt(s.CumulativeNet, 10) + " |\n")
 968	sb.WriteString("| **claims** | **" + strconv.FormatInt(s.Claims, 10) + "** |\n\n")
 969	sb.WriteString("`claims` is liabilities + equity + cumulative net. The ")
 970	sb.WriteString("net is an **explicit reconciling line**, because nothing ")
 971	sb.WriteString("in this stack closes a nominal account into equity — a ")
 972	sb.WriteString("reader who checks assets against liabilities + equity ")
 973	sb.WriteString("alone and finds it short by exactly the net is not ")
 974	sb.WriteString("looking at a bug.\n\n")
 975	sb.WriteString("That `assets` and `claims` agree is **not offered as a ")
 976	sb.WriteString("check**: over any chart the ledger can produce that ")
 977	sb.WriteString("equality is algebraically identical to *debits equal ")
 978	sb.WriteString("credits*, which it enforces on every post. It is true of ")
 979	sb.WriteString("a chart whose every account is classified backwards.\n\n")
 980
 981	sb.WriteString("## Income statement — cumulative since inception\n\n")
 982	sb.WriteString("| | |\n|---|---|\n")
 983	sb.WriteString("| cumulative revenue | " + strconv.FormatInt(s.CumulativeRevenue, 10) + " |\n")
 984	sb.WriteString("| cumulative expenses | " + strconv.FormatInt(s.CumulativeExpenses, 10) + " |\n")
 985	sb.WriteString("| **cumulative net** | **" + strconv.FormatInt(s.CumulativeNet, 10) + "** |\n\n")
 986	sb.WriteString("**Not a period figure.** Revenue and expenses accumulate ")
 987	sb.WriteString("from the book's first posting forever: the ledger's close ")
 988	sb.WriteString("is a backdating seal and sweeps no nominal account into ")
 989	sb.WriteString("equity.\n\n")
 990
 991	sb.WriteString("## Abnormality census\n\n")
 992	if len(s.Abnormal) == 0 {
 993		sb.WriteString("_No account carried a balance against its kind._\n\n")
 994	} else {
 995		sb.WriteString("**" + strconv.Itoa(len(s.Abnormal)) + "** account(s) ")
 996		sb.WriteString("carried a balance running against their kind.\n\n")
 997		sb.WriteString("| code | name | kind | section | balance |\n")
 998		sb.WriteString("|---|---|---|---|---|\n")
 999		for i, a := range s.Abnormal {
1000			if i >= RenderAbnormalLimit {
1001				break
1002			}
1003			secName := "invalid"
1004			if sec, err := stmt.SectionOf(a.Kind); err == nil {
1005				secName = sec.String()
1006			}
1007			sb.WriteString("| `" + cell(a.Code) + "` | " + cell(a.Name) +
1008				" | " + a.Kind.String() + " | " + secName + " | " +
1009				strconv.FormatInt(a.Balance, 10) + " |\n")
1010		}
1011		if len(s.Abnormal) > RenderAbnormalLimit {
1012			sb.WriteString("\n_Showing " + strconv.Itoa(RenderAbnormalLimit) +
1013				" of " + strconv.Itoa(len(s.Abnormal)) + " rows._\n")
1014		}
1015		sb.WriteString("\n")
1016	}
1017	sb.WriteString("A census row is **a finding to read, never a verdict**. ")
1018	sb.WriteString("It catches a misclassification that crosses the ")
1019	sb.WriteString("debit/credit-normal boundary; it **cannot** catch one ")
1020	sb.WriteString("that stays on the same side (liability↔equity↔revenue, ")
1021	sb.WriteString("asset↔expense), and a genuine contra account — ")
1022	sb.WriteString("accumulated depreciation, a sales return — is ")
1023	sb.WriteString("legitimately abnormal-signed. An empty census is not a ")
1024	sb.WriteString("clean bill of health.\n\n")
1025
1026	sb.WriteString("## Does it still hold?\n\n")
1027	if !books.HasBook(owner) {
1028		sb.WriteString("_The ledger reports no book for this address._\n")
1029		return sb.String()
1030	}
1031	if Verify(owner, seq) {
1032		sb.WriteString("**Verify: true** — re-deriving the chart now ")
1033		sb.WriteString("reproduces every figure above. This means the ")
1034		sb.WriteString("**figures** still hold; it does **not** mean the ")
1035		sb.WriteString("chart is untouched. A posting that moves an amount ")
1036		sb.WriteString("between two asset accounts leaves every figure here ")
1037		sb.WriteString("identical.\n")
1038	} else {
1039		sb.WriteString("**Verify: false** — the chart has moved since this ")
1040		sb.WriteString("statement was frozen. That is the normal life of a ")
1041		sb.WriteString("book and is not evidence of anything else. The ")
1042		sb.WriteString("current figures are derivable with ")
1043		sb.WriteString("`CurrentBalanceSheet`.\n")
1044	}
1045	return sb.String()
1046}