books
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books on onyx-1 testnet
realmfactory-applicationaccountinglive on onyx-1
Keep a general ledger on chain, one chart of accounts per owner.
Gives any address a set of books it alone can post to, with every entry balanced before it is accepted.
In detail curated
A general ledger, one book per address, and the FIRST CONSUMER of p/journal on any chain. It holds no coins -- no coinio import, no payable path, no withdrawal, no admin -- so a book is a set of CLAIMS BY ITS OWNER and nothing here ties 'Cash 5000' to 5000 real ugnot anywhere; the realm deliberately exposes no cross-owner aggregate, because such a total would be the sum of every stranger's self-reported assertion. What it adds over the primitive is the two decisions journal refuses to make for a consumer: the CLOCK is runtime.ChainHeight() and never an argument, so Close() has real period discipline; and the KIND IS DERIVED FROM THE ACCOUNT CODE under a mandatory 1xxx-5xxx convention (1 Asset, 2 Liability, 3 Equity, 4 Revenue, 5 Expense) rather than chosen by the caller. That derivation MOVES journal's unauditable-kind hazard rather than removing it -- after it the kind cannot disagree with the code, so 'is this kind right?' becomes the visible 'is this code right?', checkable by any reader of the chart against the published convention -- and nothing validates that the right transaction went into the right account. Authorization is per-owner and total: every mutating path resolves its book from cur.Previous().Address() and there is no cross-owner write, so one owner can never post into another's book. Posting is Post-only with a reversing entry as the sole correction, inherited wholesale from journal; Equation's third result `offsets` is a VACUOUS TRUE on every book this realm can produce and is pinned as such by the suite rather than asserted true and read green.
Use it
A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.
Read it — no key, no account, no GNOT
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books.Books()' \
--remote https://rpc.onyx.testnets.gno.land:443Books is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.
Change its state
Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 7 crossing functions, with the argument names filled in.
Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.
Import it from another realm
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books"The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.
Called from real code
Not an example written for this page. These lines are quoted from cashflow, a deployed package in this portfolio that imports this one as books, out of the committed file whose sha256 onyx-1 testnet attested. They compile because they are compiled.
n := books.Accounts(owner) // panics if the owner has no bookcodes := books.Codes(owner, "", books.MaxAccountsPerBook)name, kind, debits, credits, balance := books.AccountInfo(owner, code)Deployments
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 328,091 |
| Deploy transaction | 17b30107c640d95418020af6012e4349526f387b999c7bdc2a97110cda3b8933 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 54,632,443 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | books.gno gnomod.toml |
| Deployed bytes | books.gno — 40,799 bytes |
| sha256 | 08e3abf635d26a26e7ae2b61f6a4a9c08bb5ee874d825ddb4a5c6be22b99a235 |
| Realm address | g1wn7qumhu55va6449qkxp5tjw6akx3jdcnny559 derived, never confirmed against the realm |
Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books$download&file=books.gno' | shasum -a 256Expected: 08e3abf635d26a26e7ae2b61f6a4a9c08bb5ee874d825ddb4a5c6be22b99a235 — 40,799 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 28 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AccountInfoAccountsBalanceBooksCapacityCloseThroughClosedThroughCodesEntryCountEntryInfoEntryLegsEquationHasAccountHasBookHeightIsBalancedIsReversedKindOfOpenAccountOpenBookOpenedAtPostMultiPostSimpleRawBalanceRederiveRenderReverseTotals
Overview
Package books is a per-owner general ledger: an address opens a chart of accounts and posts balanced double-entry entries against it.
The one idea
Every other ledger-like realm in this portfolio is SINGLE-PURPOSE and its state shape IS its question. invoices tracks receivables. payroll tracks accrued wages. budgets tracks allocations. fee_split tracks shares. Each is a correct answer to one question and structurally cannot answer a second, because there is nowhere to put a transaction that is not already one of the modelled kinds.
This realm has no opinion about what the accounts mean. The owner defines the chart; the realm enforces that every entry balances, that it cannot be altered after the fact, and that only the owner may write it.
NO CUSTODY — and refusing coins is not the same as not holding them
This realm holds nothing. There is no coinio import, no payable path, no fee, no withdrawal, no admin.
So every mutating entrypoint REFUSES a transaction that arrives carrying coins. A realm with no withdrawal path that silently accepts ugnot strands it permanently, and "we don't hold funds" would then be true of the design and false of the balance. Having no custody is not the same as being safe to send coins to, and the difference is one guard.
A BOOK IS A SET OF CLAIMS BY ITS OWNER
Read this before trusting any number this realm returns.
With no custody there is no solvency invariant tying "Cash 5000" to 5000 real ugnot anywhere. A book can be internally perfect and describe nothing. IsBalanced proves arithmetic, never truth.
Consequently this realm exposes NO CROSS-OWNER AGGREGATE. Summing every book's debits would be summing every stranger's self-reported assertion, and the getter's existence would invite reading it as a signal. What it exposes instead is owner-scoped, which is the only question a consumer can act on — and a consumer acting on its OWN book is trusting only itself.
The account code DETERMINES the account kind
An account's code's leading digit fixes its kind, by the standard chart-of-accounts convention:
1xxx -> Asset 2xxx -> Liability 3xxx -> Equity 4xxx -> Revenue 5xxx -> Expense
There is no kind parameter. This is deliberate and it is the realm's answer to p/journal consumer obligation 7, which exists because of that package's audit finding Y1: NO CHECK IN p/journal VALIDATES AN ACCOUNT'S KIND. An account opened Asset that should have been Liability reports a sign-flipped Balance() forever while IsBalanced, RederiveFromJournal and AccountingEquation all three stay healthy — every one of them is built on Debits and Credits, which the kind does not touch.
Deriving the kind does not eliminate that hazard. It MOVES it: after the derivation the kind cannot disagree with the code, so the unauditable question "is this kind right?" becomes the visible question "is this code right?", which any reader of the chart can check against the convention above. That is the whole claim and it is deliberately not stated as more.
The cost is a mandatory numbering convention: a code whose first character is not 1-5 is refused. For a realm whose purpose is auditable books, that is the feature.
Entries are append-only and are corrected by REVERSAL
There is no edit and no delete. A wrong entry is corrected by posting its mirror image, which leaves both the error and the correction readable. Each entry may be reversed at most once; a reversal is itself an entry and is itself reversible once.
Periods
An owner may seal everything up to a height with CloseThrough, after which no entry may be dated at or before it. The seal only moves forward. This is the owner's own discipline, not a protocol rule — nothing forces an owner to close anything.
The clock is the chain's
Entries are dated with runtime.ChainHeight() and never with a caller- supplied value, satisfying p/journal obligations 2 and 3. Heights throughout, never seconds. A realm that let a caller choose the date would have no period discipline at all.
Caps, and why they are far below the engine's
p/journal permits 10 000 accounts and 1 000 000 entries. Its AUDIT.md names the gas ceiling on RederiveFromJournal at full cap — a worst case of 64M posting visits in one call — as UNMEASURED and does not claim it. So this realm sets its own caps far below, and exposes Rederive as an explicit owner-callable rather than running it inside a getter where another party would pay for it.
The caps are per-owner, never global. A global cap on books would be a griefing vector: one address could fill it and lock everyone else out. Per-owner, an owner can exhaust their own capacity and nobody else's, and the cost of doing so is gas plus a storage deposit per record.
Realms may keep books too, for free
The owner is cur.Previous().Address(), so a realm that calls in keeps books under its own address with no extra authorization code. That is the realm-to-realm integration surface, and it arrived as a property of the owner-only rule rather than as a feature.
What this realm does NOT do
- It does not hold, move, or price any asset.
- It does not verify that an entry describes a real event.
- It does not validate that the right transaction went in the right account. The derived kind makes the CODE auditable, not the judgement.
- It has no admin. The deploying namespace has no privilege here at all: no setter, no pause, no ability to post, reverse, or read anything a stranger cannot read.
Imports
chain/runtimechain/runtime/unsafegno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journalgno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconvstrings
Constants and variables
MaxCodeLen and MaxNameLen bound an account's identifiers. Both are at or below journal's own limits, so the engine's validation is a backstop here rather than the only check.
const (
MaxCodeLen = 16
MaxNameLen = 128
)
MaxAccountsPerBook caps one owner's chart. Far below journal's MaxAccountsLimit of 10000: IsBalanced sums the whole chart, and this bound is what keeps that read cheap for everyone.
const MaxAccountsPerBook = 128
MaxEntriesPerBook caps one owner's journal. Far below journal's MaxEntriesLimit of 1000000, because that limit's interaction with RederiveFromJournal is unmeasured — see the package doc.
A book that fills up refuses further entries rather than dropping history. The remedy is a fresh book with opening balances carried forward; there is deliberately no Archive, because it would destroy history and needs more design than it has had.
const MaxEntriesPerBook = 4096
MaxLegsPerEntry caps one entry's postings. journal permits 64; this is lower because an entry is parsed from a single string argument and rendered in full, and both of those costs are linear in the leg count.
const MaxLegsPerEntry = 16
MaxMemoLen bounds an entry's free text. Below journal's MaxMemoLen of 1024 because every memo is a candidate for Render output.
const MaxMemoLen = 256
RealmPath is this realm's own path, used to build Render links.
const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books"
RenderEntryLimit caps how many entries one Render call emits. A book may hold MaxEntriesPerBook of them and rendering all of them would make the page cost grow without bound.
const RenderEntryLimit = 25
Functions
AccountInfo
func AccountInfo(owner address, code string) (name, kind string, debits, credits, balance int64)
AccountInfo returns an account's full state. journal returns Account BY VALUE, so this hands out a copy and no pointer into stored state.
Accounts
func Accounts(owner address) int
Accounts is the number of accounts in an owner's chart.
Balance
func Balance(owner address, code string) (int64, bool)
Balance returns an account's balance in its NORMAL direction: positive means a debit balance for an asset or expense, and a credit balance for a liability, equity or revenue account.
The second result distinguishes "no such account" from "zero".
Books
func Books() int
Books is the number of books that exist. It is a count and nothing more: there is deliberately no way to enumerate them or aggregate across them.
Capacity
func Capacity(owner address) (accountsLeft, entriesLeft int)
Capacity returns an owner's remaining headroom, so a consumer can see a full book coming rather than discovering it on a refused entry.
CloseThrough
func CloseThrough(cur realm, through int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCloseThrough(cross(cur), ...).
CloseThrough seals the caller's book up to and including a height: no entry may afterwards be dated at or before it. The seal only ever moves forward.
Passing a height at or beyond the current one seals the present and makes the book immediately unpostable until the chain advances past it. That is permitted — it is the owner's own book — and it is called out because it is the one way to brick a book without filling it.
ClosedThrough
func ClosedThrough(owner address) (int64, bool)
ClosedThrough returns the height an owner's book is sealed to, and whether it has ever been closed. The second result matters: a book closed through height 0 is not the same as a book that has never been closed.
Codes
func Codes(owner address, from string, limit int) []string
Codes lists an owner's account codes in ascending code order, beginning at from INCLUSIVE and returning at most limit of them. A limit <= 0 returns nothing; a limit above the chart cap is clamped to it. Page by passing the last code received back as from, which re-reads that one code.
Why this exists, and why a counter was not enough
Accounts above returns HOW MANY accounts a chart holds, and AccountInfo answers for a code the caller already knows. Neither lets a caller DISCOVER a code. Without this, a trial balance, a balance sheet and an income statement are all unbuildable from outside this realm however many times the chart is interrogated — a consumer could only report on accounts it had independently been told about, which for a chart the OWNER defines is no guarantee at all.
Why not simply export journal's iterator
journal.Book has IterateAccounts(fn func(Account) bool), and exporting a realm entrypoint that takes fn from the caller would be the shortest code here and a security defect. A caller-supplied callback is the "callback params" class, and journal's own doc records the sharp edge: fn runs under the CALLING REALM'S STORAGE AUTHORITY, because the receiver is stamped with this realm's PkgID and a top-level fn has no declaring realm to anchor it elsewhere. A hostile fn would execute holding r/books' own authority.
So the iterator stays internal and this returns VALUES. The closure below is written by this realm, appends to a local slice, and re-enters no mutator — which is exactly the condition journal's doc attaches to it.
Codes is a free read and NOT a crossing function, so an owner does not pay for a consumer's paging. The cost ceiling is the chart cap: the underlying iterator has no seek, so `from` is honoured by skipping, and one call walks the whole chart however small limit is. A caller paging a 128-account chart 8 codes at a time therefore does 16 full walks, not one. That is a deliberate trade for not touching p/journal, whose Codes-with-seek would have re-opened Gate A and the sabotage sweeps for all three of its importers.
EntryCount
func EntryCount(owner address) int
EntryCount is the number of entries in an owner's journal.
EntryInfo
func EntryInfo(owner address, id uint64) (at int64, memo string, legs int, reversalOf uint64, total int64)
EntryInfo returns one entry's header. Postings are returned separately by EntryLegs, because an entry's leg count is variable and a crossing-function return cannot carry a slice usefully.
EntryLegs
func EntryLegs(owner address, id uint64) string
EntryLegs returns one entry's postings in the "code:side:amount,..." form PostMulti accepts, so an entry can be read and re-posted without a separate encoding.
Equation
func Equation(owner address) (debitNormal, creditNormal int64, offsets bool)
Equation partitions an owner's chart BY ACCOUNT KIND: the summed raw delta of every debit-normal account (Asset, Expense), the same for every credit-normal one (Liability, Equity, Revenue), and whether the two offset.
The two FIGURES are why this exists. They are the balance-sheet and income-statement partition of the chart, and no other accessor on this realm exposes it — Totals sums debits and credits without regard to kind, and AccountInfo answers one code at a time.
`offsets` IS A VACUOUS TRUE ON THIS REALM — do not quote it as a check
Read this before using the third result for anything.
The kind partition is TOTAL: journal's Kind is 1..5, IsDebitNormal is `k == Asset || k == Expense`, so every account lands in exactly one of the two sums and neither can omit one. Therefore
debitNormal + creditNormal == sum of RawDelta over ALL accounts == totalDebits - totalCredits
and this realm refuses an entry that does not balance, so that difference is always zero. `offsets` is consequently TRUE for every book this realm can produce, and it is true for a chart whose accounts are classified nonsensically just as readily as for a correct one.
This is the `HasValidSignature` shape §7b already names: a predicate that is a constant under current conditions, and so is evidence of nothing. The suite pins the vacuity deliberately rather than asserting true and reading green — a test that cannot fail is the thing that makes this dangerous.
What `offsets` DOES still distinguish is arithmetic overflow, where journal returns (0, 0, false). So the honest reading is **"the two sums are trustworthy"**, never "the classification is right". A consumer wanting a genuine classification check must compare the kinds against its own expectation, which is the statement consumer's job and not this realm's.
Both figures are RAW deltas (debits - credits), not normalised balances, so a debit-normal total is normally positive and a credit-normal total normally negative. A credit-normal account carrying a positive raw delta is a contra account, which is legitimate, and is why neither figure is forced non-negative.
HasAccount
func HasAccount(owner address, code string) bool
HasAccount reports whether an owner's chart contains a code.
HasBook
func HasBook(owner address) bool
HasBook reports whether an address has opened a book.
Height
func Height() int64
Height is the chain height this realm dates entries with. Exposed so a caller can predict what CloseThrough will seal.
IsBalanced
func IsBalanced(owner address) bool
IsBalanced is the CHEAP invariant and this realm's live-verification probe.
It compares two independently-maintained accumulators: the per-account totals summed across the chart, against the book's own running totals. They are written by different code paths, so a drift between them is a real defect and this finds it.
What it does NOT do is re-read the journal, so a defect that corrupted both accumulators identically would survive it. That is Rederive's job, and Rederive is a crossing function because the owner should pay for it.
And the ceiling on both: this proves the book is self-consistent, never that its entries describe real events. See the package doc.
IsReversed
func IsReversed(owner address, id uint64) bool
IsReversed reports whether an entry has already been reversed. An entry may be reversed at most once, so this is also "may this still be reversed".
KindOf
func KindOf(code string) string
KindOf reports the kind a code would be given, without opening anything. A consumer can call this before OpenAccount to check its own numbering.
OpenAccount
func OpenAccount(cur realm, code, name string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asOpenAccount(cross(cur), ...).
OpenAccount adds an account to the caller's book. The KIND IS DERIVED from the code's leading digit; there is no kind parameter. See the package doc.
OpenBook
func OpenBook(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asOpenBook(cross(cur), ...).
OpenBook creates the caller's book. It panics if the caller already has one.
Separating this from OpenAccount is deliberate: creating a book is the call that pays the storage deposit for the owner's record, and a caller should see that cost on its own rather than hidden inside their first account.
OpenedAt
func OpenedAt(owner address) int64
OpenedAt is the height an owner's book was created.
PostMulti
func PostMulti(cur realm, legs, memo string) uint64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPostMulti(cross(cur), ...).
PostMulti posts an entry of up to MaxLegsPerEntry legs, specified as a comma-separated list of colon-separated triples:
"1000:D:500,4000:C:300,4100:C:200"
Side is D or C, case-insensitive. Amounts are positive integers in the owner's chosen unit. The string form is used because a crossing function cannot usefully take a slice over MsgCall; treasury_board encodes a list the same way.
Debits must equal credits, and journal names which side is short.
A leg may name an account another leg already named — journal folds them, and "1000:D:500,1000:C:200,4000:C:300" is a legitimate net +300 to 1000. What is refused is an entry in which EVERY leg names the same account: it balances only by netting to exactly zero, so it is the PostSimple mistake wearing the multi-leg syntax. The narrow rule is deliberate — a blanket "no repeated account" would reject the legitimate fold above.
PostSimple
func PostSimple(cur realm, debitCode, creditCode string, amount int64, memo string) uint64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPostSimple(cross(cur), ...).
PostSimple posts the two-leg entry that is the overwhelmingly common case: debit one account, credit another, for the same amount.
It exists beside PostMulti because the two-leg form cannot be mis-encoded. A caller who wants one debit and one credit should never have to get a leg specification's syntax right.
RawBalance
func RawBalance(owner address, code string) (int64, bool)
RawBalance returns an account's debits minus credits, without the normal- direction sign convention. Exposed because a consumer reconciling against an external system usually wants the raw figure.
Rederive
func Rederive(cur realm) (debits, credits int64, agrees bool)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRederive(cross(cur), ...).
Rederive runs journal's STRONG invariant check over the caller's own book: it replays every posting of every entry and compares the result against both accumulators.
It is a crossing function rather than a getter on purpose. The check is O(all postings ever) and the owner is the party who should pay for it — exposing it as a free read would let anyone make an owner's book expensive to query. IsBalanced is the cheap check and is a free read.
Returns the replayed totals and whether they agree.
Render
func Render(path string) string
Render serves the index at "" and one owner's book at "<address>".
Every caller-supplied string — account names and entry memos — goes through the ecosystem sanitizer. Free text reaching Render unescaped is the one injection-shaped surface a realm with no custody still has.
Reverse
func Reverse(cur realm, id uint64, memo string) uint64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asReverse(cross(cur), ...).
Reverse posts the mirror image of an existing entry in the caller's book, dated now. The original is left exactly as it was: this is a correction, not an edit.
An entry may be reversed at most once. The reversal is itself an entry and may itself be reversed once.
Totals
func Totals(owner address) (debits, credits int64)
Totals returns an owner's running debit and credit totals.
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv, strings |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- NO BOOK HAS BEEN OPENED ON CHAIN. Every live probe is a free read or a refusal: not one OpenBook, OpenAccount, PostSimple, PostMulti, Reverse, CloseThrough or Rederive has been broadcast, so no entry has been posted, nothing has been reversed and no period has been closed. IsBalanced, Equation, Rederive, Codes, EntryLegs, Totals, ClosedThrough and the whole of renderBook are COVERED BY THE COMMITTED SUITE AND UNEXERCISED ON CHAIN. What the chain has shown is that the deployed bytes are the audited bytes (vm/qfile byte-match at 40,799 B) and that the p/journal link executes (KindOf('1000') returns the lowercase 'asset' that only journal.Kind.String() produces). The economically meaningful validation is a separate, later step, as it was for budgets.
- THE PER-OWNER ISOLATION THAT CARRIES THE REALM'S CENTRAL SAFETY ARGUMENT WAS NEVER EXERCISED ON CHAIN -- there is no second owner for it to be wrong about, because there is no FIRST owner. 'One owner can never post into another's book' rests entirely on the committed suite and the 127-case sabotage sweep (0 uncaught, 0 did not apply, FULLY COVERED). This is budgets' first limitation one step worse: there, one owner and one grant existed.
- Equation's third result `offsets` IS A VACUOUS TRUE ON THIS REALM and must not be quoted as a check. The kind partition is total and this realm refuses an unbalanced entry, so debitNormal + creditNormal is always zero and `offsets` is true for a chart whose accounts are classified nonsensically just as readily as for a correct one. It distinguishes only journal's arithmetic-overflow return (0, 0, false). The honest reading is 'the two sums are trustworthy', never 'the classification is right'.
- A BOOK IS A SET OF CLAIMS BY ITS OWNER; there is no custody, so there is no solvency invariant and a book can be internally perfect and describe nothing. IsBalanced proves arithmetic, never truth. This is journal's fourth limitation inherited without modification -- deriving the kind does not touch it.
- refuseCoins and mustOwnBook both open with an IsCurrent() check that is a NO-OP AT EVERY ONE OF TODAY'S CALL SITES: each is a crossing entrypoint forwarding its own first cur, runtime-current by construction. They are there because the realm is immutable after addpkg, so a call site added later cannot be made safe later, and a helper reading Previous() off an unchecked realm parameter is a designation-forgery shape (security.md Class 2) whether or not it is reachable today. Added at 412635c; AUDIT.md section O records the finding.
- CloseThrough CAN BRICK A BOOK AND THAT IS PERMITTED. Passing a height at or beyond the current one seals the present and makes the book unpostable until the chain advances past it. It is the owner's own book, and it is the one way to brick a book without filling it. The caps -- 128 accounts, 4,096 entries, far below journal's 10,000 and 1,000,000 -- are deliberate, because journal's AUDIT.md names the gas ceiling on RederiveFromJournal at full cap (a worst case of 64M posting visits in one call) as UNMEASURED. A full book refuses further entries rather than dropping history; there is no Archive.
- Codes WALKS THE WHOLE CHART PER CALL. journal's iterator has no seek, so `from` is honoured by skipping: a caller paging a 128-account chart 8 codes at a time does 16 full walks, not one. It is a free read so the cost falls on the caller, and the alternative -- adding a seek to p/journal -- would have re-opened Gate A and the sabotage sweeps for all three of its importers.
- THE STORAGE DEPOSIT IS BALANCE-DERIVED, NOT CHAIN-ATTESTED. onyx-1 emits no StorageDepositEvent (storage_deposit_events: false in this stanza), so storage_bytes and storage_deposit are null here as on every onyx-1 entry. A pre- and a post-broadcast gno_account read bracket it -- 18,453,370 then 14,377,572 ugnot, both chain_id_bound: true -- which with the exactly-reproduced 163,898 fee leaves a 3,911,900 ugnot residual, charged at actual against the 10000000ugnot max_deposit the chain recorded in its own [addpkg] stanza. That residual is an inference from two balances and a single-step sequence move 47->48, not a figure the chain attested.
- GATE A AND GATE B DISAGREE ON BOTH INPUTS AND DIGEST, unlike journal's pair: 7 compared dependencies under --include-tests (digest 6d47229f...fc96c9d, 21-stdlib residual) against 3 without (digest ca00ffc2...55d4861d, 16-stdlib residual). The four test-only dependencies -- p/nt/testutils/v0, p/nt/uassert/v0, p/nt/ufmt/v0, p/onbloc/diff/v0 -- are inside 'what the test result means' and outside 'what the chain executes'. Both carry checker digest 53871a57...350d962d. The stdlib residuals execute from the node binary and are not comparable, so equivalence for them rests on the SEALED onyx GNOROOT matching the node's build, which the node does not expose: sealed, never attested (CLAUDE.md 7a).
- simulate=true ON onyx-1 DOES NOT TYPE-CHECK (the chain's inert code-submission policy), so the dry run before this broadcast was a gas/fee/authorization probe only. The correctness substitute is `gno lint .` against the onyx release, exit 0 with zero output -- and unlike journal's deploy it ran BEFORE the broadcast, so it is a gate this deploy cleared rather than a property of the deployed bytes discovered afterwards.
- BECAUSE gno_addpkg HAS NO FILE-PATH PARAMETER the 40,799-byte source was re-emitted by hand as a tool argument, which is one structurally unverifiable step guarding an immutable, exact-path-guarded realm path. The control was a pre-broadcast self-transcription digest check (40,799 bytes, 999 lines, LF only, sha256 equal) and the post-broadcast vm/qfile read confirms it held -- but that control proves the transcription, not that anything independent checked the act of trusting it.
- books_test.gno (84,401 bytes) is committed and DELIBERATELY NOT DEPLOYED, with vm/qfile's package listing of exactly `books.gno gnomod.toml` as the machine proof. AUDIT.md, DISCOVERY.md, DEPLOY_READINESS.md and the two closure records are likewise not deployed: addpkg takes the file list it is given, not the directory.
- THE 7 ECOSYSTEM-ABSENCE PHRASING IS USED AND IT IS NOT A UNIQUENESS CLAIM. [an unpublished record] records 'No relevant existing implementation was found in the searched sources' over finite sources.
Source
| Source file | onyx/r/books/books.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |