Cosmic Bull

Build with Cosmic Bull

The reusable packages are live, verified and importable by anyone. This page walks the whole journey: an objective, the packages that fit it, when not to use them, the exact imports, real minimal examples, and how to verify every claim against the chain before you depend on it.

1. Start from an objective

The worked example on this page: build a Gno application that accepts payments, tracks bounded fees, and holds a scheduled obligation. Swap in your own objective; the journey is the same.

2. Discover the packages

14 reusable /p/ primitives are live across gnoland-1 and onyx-1 — not every one on every chain, and not all from one digest; each card names its own. For this objective: coinio (accepting and paying out coins, safely), feeledger (balances and bounded fees, with a conservation contract), and duebook (scheduled obligations with exactly-once claims).

aging

Track overdue balances and outstanding obligations.

live on onyx-1 · used by 1 on onyx-1

cashflow

Understand where money comes from and where it goes.

live on onyx-1 · used by 1 on onyx-1

coinio

Receive, pay out and sweep coins without rewriting the checks each time.

live on mainnet + onyx-1 · used by 7 on mainnet, 12 on onyx-1

depreciation

Know what a capital asset is worth after a given number of periods.

live on onyx-1 · used by 1 on onyx-1

duebook

Authorize an action now and carry it out later.

live on mainnet + onyx-1 · used by 2 on mainnet, 2 on onyx-1

feeledger

Keep per-account balances and protocol fees in separate books.

live on mainnet + onyx-1 · used by 7 on mainnet, 12 on onyx-1

journal

Keep double-entry books that always balance.

live on mainnet + onyx-1 · used by 8 on onyx-1

lots

Work out the cost of the goods you have used.

live on onyx-1 · used by 1 on onyx-1

periods

Turn a running ledger into a report for one period.

live on onyx-1 · used by 2 on onyx-1

permbook

Define named permissions and check them in one call.

live on mainnet · no consuming realm on any chain yet

spendbook

Let one account spend another's funds, up to a limit.

live on mainnet + onyx-1 · used by 1 on mainnet, 2 on onyx-1

spendbook/v2

Let one account spend another's funds, with a changed expiry honoured.

live on mainnet + onyx-1 · no consuming realm on any chain yet

statements

Turn a chart of accounts into finished financial statements.

live on onyx-1 · used by 2 on onyx-1

tiers

Price an amount against a graduated schedule.

live on onyx-1 · used by 3 on onyx-1

Machine-readable versions of everything on this site, for tooling: data/portfolio.json (the verified manifest: paths, provenance, dependencies, consumers, limitations, every chain) and api/ (the chain-derived API reference, one Markdown file per package, each generated from one chain's own vm/qdoc and naming it — gnoland-1 and onyx-1 across the set). The API files are verbatim copies of the artifacts this site is generated from; the manifest is the committed one with a published_subset field added, which names the chains published here and any that were withheld — so a consumer can tell a complete portfolio from a cut-down one instead of having to assume.

3. Decide when — and when not — to use them

curated Each package page carries a curated summary and its known limitations; read both before composing. All 115 cautions recorded against these 14 packages are here in full — nothing is summarised away. They are grouped by package, each group carries its own count, and the three this page's objective uses are already open. A caution recorded against more than one package appears under each and names the others.

aging 9 cautions

THE BYTE-MATCH WAS EXACT

unlike r/inventory one deploy earlier, this transcription was byte-perfect (on-chain aging.gno == committed 77510a46), so no reconciliation was needed. The post-broadcast vm/qfile byte-match remains the authoritative fidelity check for the sent bytes; it passed clean.

AMENDED 2026-10-15 (AL29) -- THE ROOT CAUSE IN THIS ENTRY IS FALSE

see the AL29 entry immediately following. The original text is preserved verbatim here because section 9 forbids rewriting a record and because the wording is what the error was: >>> 'A BARE /p/ CANNOT BE PROBED ON CHAIN. vm/qeval needs a realm context; `ProvisionFor(...)` over a struct literal answered name ProvisionFor not declared. The live evidence is the EXACT vm/qfile byte-match (unstamped, bound by the URL dialled -- the which-bytes-run proof) plus a stamped gno_read (chain_id_bound: true -- attests the chain served it). Every behavioural claim is carried by the committed 33-test suite and the 101-case sabotage sweep under the pinned onyx toolchain, and by nothing on chain.' <<< What remains TRUE of it: the byte-match and the stamped gno_read are the evidence this record was actually built on, and the suite plus sabotage sweep are what carry coverage. What is FALSE: the generalisation in the first sentence, and the diagnosis that a realm context or a struct literal is the obstacle.

AL29, 2026-10-15: A BARE /p/ METHOD *CAN* BE PROBED

and the entry above is corrected here rather than edited. MEASURED on onyx-1, stamped chain_id_bound: true: `Schedule{}.NumBuckets()` on this very package answers (0 int) -- a struct literal with UNEXPORTED fields, plus a method call, on a bare /p/, with no realm context. The real limit is narrower -- and the CHAINING limit first written here was itself FALSE, corrected the same day before this entry was committed. It read: 'vm/qeval takes an EXPRESSION, not statements, so a multi-valued constructor's first return cannot be bound in order to call a method on it, which is why New(...).ProvisionFor(...) answered as it did.' A function literal invoked immediately is a single EXPRESSION whose body is statements, so the pair binds and the methods run -- measured on onyx-1 against p/journal and p/lots, the two packages this correction had just declared permanently unprobeable. There is no chaining limit. The one real requirement is that the whole probe be ONE EXPRESSION, which a closure satisfies. Nothing about /p/, struct literals, unexported fields or multi-value returns is an obstacle. Consequence for THIS record: the 'by nothing on chain' clause above overstates the gap -- a behavioural citation was available and was not taken. It is not retrofitted into the historical deployment record; see onyx/AL29-behavioral-probes.json for the thirteen probes taken across four sibling /p/ packages once the diagnosis was corrected, and [an unpublished record] Group 18 Amendment A.

GATE A/B COMPARED ZERO DEPENDENCIES

e3b0c442-class empty closure means 'no chain dependencies at all' (imports errors only), never 'a closure was verified'. The errors stdlib residual executes from the node binary and rests on the GNOROOT pin matching the node build, which the node does not expose. Permanent for a stdlib-only package.

THE INTEGER PROVISION IS NOT MONOTONE, BY DESIGN

It can fall by up to min(NumBuckets,len(obs))-1 as receivables age with no payment, because the floor is taken per bucket. A realm gating on provision>=lastProvision will eventually be wrong by one. Only the exact rational provision is monotone. The doc states the counterexample in full and TestFloorIsPerBucketNotAtTheEnd pins it.

COST FIGURES ARE SELF-REPORTED AND THE PACKAGE PRICES NOTHING

The rates are an input validated for shape and ordering, never for plausibility; picking loss rates is the whole of credit policy and none of it is here. A provision is an estimate in a report and the package never sees a coin.

THE WORK IS BOUNDED, NOT PRICED

MaxBuckets 16, MaxObligations 1024 bound the loops; the package doc gives the comparison/multiplication counts but explicitly does not assert they are affordable inside a Render -- a consumer realm must measure its own gas.

THE AUDIT WAS SELF-ADMINISTERED (Task unavailable in-subagent)

Verdict PROCEED WITH CAUTION downgraded to 0 RED / 2 YELLOW both fixed (Y1 the ReportAt pricing-precondition gap, Y2 the first-edge-0 off-by-one); recorded rather than smoothed.

A1: every provision figure is a function of the caller-supplied `now` and the consumer contract carries no provenance clause -- RED in any realm exposing `now` to public input (none does today, verified across all live consumers). Doc-parity gap vs permbook's reference standard; consumer obligation recorded here since the live bytes cannot change. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

cashflow 12 cautions

THE BYTE-MATCH WAS EXACT

On-chain cashflow.gno == committed 77dfe592 at 33331 bytes; fourth consecutive byte-perfect /p/ deploy. The committed bytes were pre-measured before transcription (33295 chars, 872 newlines, 835 tabs, 0 CR, trailing newline, 0 trailing-whitespace lines, 18 non-ASCII all U+2014). THE PRE-BROADCAST TRANSCRIPTION HAD NO INDEPENDENT CONTROL and that is recorded rather than dressed up: a /tmp rehearsal hashes a DIFFERENT transcription from the one sent to gno_addpkg, so it cannot cover the payload that ships, and quoting it as a control would be the check-that-proves-nothing shape. The real controls were the post-broadcast vm/qfile byte-match (authoritative, EXACT) and the weaker signal that simulate and broadcast returned identical gas_used.

GATE B RAN AFTER THE BROADCAST -- A SECTION-7a VIOLATION

REPORTED NOT WAIVED. 7a requires Gate B 'at TESTNET VALIDATION, immediately before deploy' and says to treat both gates as required. Gate A ran; Gate B did not, and the omission was caught by listing the artifact directory during record-writing, AFTER the irreversible write. When run it read EQUIVALENT with a digest identical to Gate A's (006eb57d), and the tree was provably quiescent across the interval -- payload digests matched HEAD immediately pre-broadcast and the only working-tree changes were two gate JSONs. So no wrong outcome followed, AND FAILING TO PRODUCE A WRONG OUTCOME IS NOT THE SAME AS HAVING RUN THE GATE. This is task #107 recurring AFTER it was filed, which is the finding worth more than the miss: A TASK FILED IS NOT A CONTROL INSTALLED.

GATE A/B COMPARED 4 DEPENDENCIES, NOT A FULL CLOSURE

Residual stdlibs (6 at Gate B, 17 at Gate A with --include-tests) execute from the node binary and rest on the GNOROOT pin, which is SEALED, not attested.

A BARE /p/ HOLDS NO STATE

so nothing on chain was mutated. The behavioural citations evaluate pure functions; vm/qeval discards writes and there are none to discard. A consumer realm persisting a Report would discharge this; none is deployed.

also recorded against periods

DERIVE WAS NOT PROBED LIVE

It returns (Report, error) and qeval takes an expression, so a multi-valued return cannot be bound. Per-entry attribution and the three-reason refusal rest on the committed 55-test suite and the 93-case sabotage sweep under the pinned onyx toolchain. Statement support in qeval (upstream) or a consumer realm calling Derive would discharge it.

THE POLICY IS THE TRUST BOUNDARY AND NOTHING CAN VERIFY IT

Which codes hold cash, and what each other code's movement means, are caller declarations; a chart declaring a receivable as cash produces a plausible, WRONG statement with every guard satisfied. This is a design property stated in the package doc, not a gap -- it is inherent to the direct method. Likewise the opening balance is echoed back unverified (p/periods' limitation inherited), so Closing is only as good as the Opening supplied.

RECONCILING AGAINST p/periods PROVES NOTHING ABOUT THE SPLIT. Comparing Report.NetChange with the cash accounts' movement in a periods report is an arithmetic self-check that both packages added the same columns the same way. A statement that files every flow under the WRONG CATEGORY reconciles perfectly. Report.Complete() is the same shape: completeness is a property of the POLICY's coverage, and a policy classifying every account wrongly is complete.

THE SIMULATE DID NOT TYPE-CHECK

onyx-1 runs the inert code-submission policy, so the dry run reports gas without compiling. Type-correctness rests on the local gno test under the onyx pin (55 passed, 0 failed), which is the control actually relied on.

THE STAGED DISCOVERY.md ONYX-1 CORPUS WAS 10 FIRST-PARTY PACKAGES SHORT (AL31, task #115)

The step-8 re-run returned REVIEW REQUIRED against staging's '10/10 terms zero'; cause measured, not assumed -- the staging corpus held 7 of the then-live 17 first-party onyx-1 packages, omitting exactly everything at height >= 327179, i.e. the whole general-ledger family, which is cashflow's own. Staging's '0 unreadable' line was TRUE AND USELESS: REACH IS NOT CURRENCY, and every control passed because a control proves the predicate ran over the corpus, never that the corpus is the population. THE VERDICT IS UNCHANGED -- the 5 subject hits in the fresh 684-path corpus are all first-party siblings' own negative declarations, already classified in DISCOVERY.md section 3 -- so what is wrong is two COVERAGE ROWS in the record, not the conclusion.

AL29, 2026-10-15: LIMITATION 4's STATED REASON IS FALSE

and it is corrected here rather than edited because this record is committed (section 9). Limitation 4 reads 'DERIVE WAS NOT PROBED LIVE. It returns (Report, error) and qeval takes an expression, so a multi-valued return cannot be bound.' The first sentence is true; the second is an INFERENCE from a real mechanism and not a measurement of it, and it is wrong. func() T { ...statements...; return x }() is a SINGLE EXPRESSION whose body is statements, so a (*T, error) constructor binds and its methods run -- measured on onyx-1 the same day, stamped chain_id_bound: true, against p/journal (construct, open a chart, post a balanced entry, then REPLAY every posting -> (700 int64) (700 int64) (true bool), with the 700->600 fixture returning 'journal: entry does not balance, credits are short' as its non-vacuity control) and p/lots (FIFO 200 against LIFO 250 over identical layers, each value the other's control). CONSEQUENCE FOR THIS RECORD: Derive is UNPROBED, not unreachable, and the honest word matters because a ceiling declared closed suppresses the work that would reopen it. The obstacle that IS measured is a different one -- Derive names []journal.Account and []journal.Entry, and the imported identifier `journal` is not bound in a qeval expression context ('name journal not declared', measured twice on p/statements). Derive(nil, nil, periods.Window{}, Policy{}, 0) names no foreign value and has never been attempted; it is owed, not impossible. THE GENERAL RULE THIS ENTRY EXISTS TO CARRY: a record declaring a ceiling must CITE THE ATTEMPT that established it, so that 'unreachable' and 'never tried' stop reading identically. Nothing mechanical checks a declared limitation. See [an unpublished record] and onyx/AL29-behavioral-probes.json.

AL29b, 2026-10-15: DERIVE IS NOW PROBED LIVE, AND THE ROUTE LIMITATION 9 NAMED AS THE THING TO TRY WAS ITSELF WRONG

Limitation 9 records `Derive(nil, nil, periods.Window{}, Policy{}, 0)` as un-attempted-not-impossible. That expression does NOT work: `periods.Window{}` names the unbound `periods` identifier exactly as `journal` is unbound, so the route recorded as the discharge reproduces the obstacle it was written to route around. Corrected here rather than edited, per section 9. THE WORKING ROUTE IS A FIELD SELECTOR: Report.Window is a periods.Window (cashflow.gno:435) and field names never name a package. Three probes, all predicted before the read, all stamped chain_id_bound: true, all agreeing (probes 25-27 in onyx/AL29-behavioral-probes.json): func() string { _, err := Derive(nil, nil, Report{}.Window, Policy{}, 0); return err.Error() }() -> 'cashflow: invalid window'; then with `var r Report; r.Window.From = 0; r.Window.Until = 10` giving a VALID window, Derive(nil, nil, r.Window, Policy{}, 0) -> 'cashflow: policy declares no cash accounts' (which proves guard 1 passed); then Policy{Cash: []string{"1000"}} -> 'cashflow: policy declares a cash account absent from the chart'. GENERALISED: when an imported identifier is unbound in a qeval expression, reach the foreign named type through any first-party struct field that already has that type. THE TRIO DISCRIMINATES GUARD ORDER in the shipped bytes -- window, then empty-cash, then chart-lookup -- with three distinct verbatim audited sentinels, each the others' non-vacuity control, which is the top of the citation-strength ordering. WHAT IS STILL OUT OF REACH, AND THIS CEILING CITES ITS ATTEMPT: no type in p/cashflow or p/statements has a field of type journal.Account or journal.Entry -- measured by grep, not assumed -- so a non-nil chart or entry slice cannot be built without binding `journal`. cashflow's ATTRIBUTED HAPPY PATH therefore remains unreached, since validatePolicy requires a cash code present in the chart; per-entry attribution and the three-reason refusal still rest on the committed 55-test suite and the 93-case sweep. A consumer realm is the instrument for those. NOTE FOR A FUTURE READER: limitation 4's first sentence ('DERIVE WAS NOT PROBED LIVE') and the identical sentence in [an unpublished record] Group 21 were TRUE WHEN WRITTEN and are superseded by this entry, not refuted as of their date -- only the stated reason was false, which limitation 9 already corrected. Do not re-file either as a false-ceiling instance.

C1: Derive's outflow negation at the minInt64 edge wraps unchecked (-net before checkedAdd can see it), so the documented non-negative-column invariant is false for HAND-BUILT entry slices (a journal.Book cannot produce the shape; Reconciles() catches the result downstream). Importer-conditional; fix staged for the mainnet twin pre-deploy decision / successor: periods' magnitude() shape. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

coinio 2 cautions

Consumed on this chain by revshare, service_market, subscriptions as of 2026-09-25 (Group 1). Before that this entry read "no consumer on this chain"; the claim was true when written and the reusability argument it qualified was carried over from pearl-1.

gnoland-1 only · also recorded against feeledger

storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposits actually charged were measured from the signer's balance and are recorded in [an unpublished record], Cost; they are not chain-attested per package by this manifest.

gnoland-1 only · also recorded against duebook, feeledger and permbook

depreciation 13 cautions

NOTHING CONSUMES THIS PACKAGE AND NO PROBE OF IT CAN CHANGE THAT

phrased deliberately narrowly, because p/journal's record carried the structural-sounding version of this sentence ('and no probe can change that') and it went FALSE IN ONE DAY. consumed_by is empty and that is the honest state. A /p/ package declares no crossing functions, so MsgCall has nothing to dispatch to, and vm/qeval needs an exported realm function a /p/ does not have; so there is no transaction and no evaluation that executes this code without a consuming realm. What would change it is DEPLOYING A CONSUMER: onyx/r/assets is written against these exact bytes and staged (task #72), and this package's deploy is what unblocked it. Until then every behavioural claim rests on the 30 test functions (36 subtests, 66 --- PASS) and the 72 sabotage cases run locally under the pinned onyx toolchain, with the vm/qfile byte-match transferring that local evidence to this chain.

THE LIVE-VERIFICATION ARGUMENT FOR THIS ENTRY IS A BYTE-MATCH AND A SOURCE READ, NOT A BEHAVIOURAL PROBE

and the two halves carry different provenance. The mechanical byte-match -- chain depreciation.gno 19,983 bytes, sha256 6f0b9c40... == the committed bytes, with the 88-byte committed gnomod.toml an exact prefix of the chain's 203 after the VM appended its [addpkg] stanza -- was taken by RAW-RPC vm/qfile and is therefore UNSTAMPED: it is bound only by the URL dialled and is not a 9a citation. The 9a-stamped reads are three, all chain_id_bound: true: the gnomod.toml read showing [addpkg] height 332075 and creator, the two gno_account reads bracketing the cost, and a gno_read symbols=[Periods] whose returned source is the POST-FIX implementation (uint64 span, no placedAt<0 && now>0 guard). That last one is the closest available substitute for a live-call probe: it proves which bytes onyx-1 holds at this path, specifically that they are the audit-fixed ones, and it is still a read of source rather than an execution of it.

THE MUTATION SWEEP IS A FLOOR AND THIS PACKAGE IS THE PROOF OF THAT, MEASURED ON ITSELF

The harness read FULLY COVERED over 70 sabotages at the moment Periods was returning a NEGATIVE PERIOD COUNT WITH A NIL ERROR, and it was not malfunctioning: it measures whether the suite notices a changed term, and this defect needed an INPUT THE SUITE NEVER PASSED. The sweep is now 72 cases, 0 uncaught, and three of them put the defect back three ways (quotient taken in int64, bound compared after a signed narrowing, old guard literally reinstated) -- the third is caught ONLY by the fixtures added with the fix, which is the proof those fixtures are load-bearing. None of that makes the sweep a correctness proof.

THE AUDIT FOUND A REAL DEFECT AND WAS ALSO WRONG ABOUT IT, IN BOTH DIRECTIONS

It reported Periods' wrap guard YELLOW with two named witnesses at 99% confidence; the second was REFUTED by computing it (-1 - minInt64 fits in an int64 with nothing to spare, so the old code's answer was correct). And the defect it did find had a second class it never reported, FIVE TIMES LARGER: 52 rows where the guard REFUSED A REPRESENTABLE ANSWER against 10 where it returned a wrong one, four of those returning a negative count with a nil error. None of that was visible from the audit text -- all of it came out of running the arithmetic. The lesson this entry carries forward is that an arithmetic claim gets COMPUTED, never reasoned about.

THE DIFFERENTIAL SWEEP IS AN EMULATION, NOT EVIDENCE ABOUT THE VM

periods_sweep.py models int64 wrapping in Python and reports 0 disagreements for the fixed implementation over 1,960 stated triples and 2,268,109 wider ones, against 62 for the old one. It LOCATED the rows; depreciation_test.gno asserts them under the pinned onyx toolchain, and that is where the evidence is. A model cannot be evidence about the VM, which is why the rows are in the suite too.

THE CLOSURE VERDICT IS 'NO CHAIN DEPENDENCIES AT ALL', WHICH IS NOT 'A CLOSURE WAS VERIFIED'

Gates A and B are both EQUIVALENT over ZERO compared dependencies, both digests e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -- the sha256 of an empty input. Per CLAUDE.md 7a that must be stated the first way and never the second. The package imports only `errors`, so this is a permanent property and not a gap. The stdlib residual is outside the comparison by construction: stdlibs execute from the node binary, so equivalence for them rests on the sealed onyx GNOROOT matching the node's build, which the node does not expose, and the seal proves only that nothing has changed since the pin was written -- not that it was right when written.

GATE B WAS RUN BEFORE THE BROADCAST, WHICH IS A CORRECTION OF THIS PROJECT'S OWN PRECEDENT RATHER THAN A RESTATEMENT OF THE RULE

Group 12 recorded against itself that its Gate B ran AFTER the broadcast, a deviation from CLAUDE.md 7a. Here the record is onyx/p/depreciation/depclosure-gateB.json, exit 0, timestamped before tx 5148670f... was composed. The same ordering defect remains possible on every future deploy until task #107 makes the Gate A/Gate B sequence mechanical rather than remembered.

simulate=true ON onyx-1 DOES NOT TYPE-CHECK (the chain's inert code-submission policy), and the tool said so in its own next_steps, so the dry run before this broadcast was a gas/fee/authorization probe ONLY. The correctness substitute is `gno lint .` against the onyx release at rc=0 on exactly these bytes, which was established as part of the readiness pass and confirmed for this deploy by digest -- all three recorded sha256s matched and `git status` on the package was empty, which is why the readiness table's figures describe these bytes rather than some earlier ones. Digest confirmation in place of re-running is a deliberate and stated choice, not an omission.

NO GAS FIGURE FOR A CALL, ONLY FOR THE DEPLOY

MaxLife's doc states the loop's ITERATION bound -- at most 1,199 per CarryingAt, 2,398 per Charge, and the degenerate schedule whose per-period charge truncates to zero (Cost*RateNum < RateDen) runs the full bound on every call for the asset's whole life, defeating the early exit -- and explicitly refuses to price it. `gno test` reports one GAS line per test FUNCTION, which cannot be read as a per-call cost. Discharged by: a realm that renders many assets measuring its own. onyx/r/assets is that realm and is not deployed.

NO PERIOD CONVENTION BEYOND WHOLE PERIODS, AND NO TAX OR ACCOUNTING STANDARD HAS BEEN CHECKED AGAINST

An asset placed in service mid-period depreciates from its placed-in-service date, not from the start of the calendar period it fell in. Half-year and mid-month conventions are a caller's job -- shift placedAt, which is sound because every function is pure in the resulting count. Daily/Monthly/Yearly are conveniences and not a calendar: Monthly is 30 days and Yearly is 365, so neither tracks month lengths or leap years. AUDIT.md section 4 states the mechanism and states plainly that it is a mechanism and not a standard.

Basis() IS THE ONE EXPORTED METHOD THAT DOES NOT VALIDATE, AND THAT IS FORCED RATHER THAN CHOSEN

check() calls Basis to establish one of its own overflow preconditions, so Basis cannot call check(). On an ill-formed schedule it returns an arbitrary number -- Schedule{Cost: maxInt64, Salvage: -1} wraps to minInt64 -- and a zero result means 'invalid, or zero cost', never 'fully depreciated'. A consumer must guard a caller-supplied Schedule with Valid() before quoting Basis from it. Separately, a DecliningBalance schedule's LAST charge can be much larger than the one before it, because the clamp at Life absorbs whatever the asymptotic approach left; textbooks usually avoid that by crossing over to straight line, and this package deliberately does not, because the crossover rule is the caller's policy.

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', names the two candidates the gate KILLED, and explicitly claims no novelty for the straight-line arithmetic -- a quantity moving at a fixed rate per period toward a bound is shipped elsewhere in this ecosystem more than once. What was not found is the domain model: a salvage floor, a carrying amount, a declining-balance schedule over integers. That document also predicted its own expiry and was right: it enumerated 639 onyx paths, and the step-8 re-enumeration immediately before this deploy found 669.

DP1: same temporal-authority class on Periods(placedAt, now, period), weaker -- telescoping bounds total damage at Basis (mis-timing, not fabrication). Same consumer obligation as aging A1. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

duebook 5 cautions

No consumer on this chain. gnoland-1 now carries realms as well as the primitives, but none of them imports this package, so every reusability claim here remains a design argument carried over from pearl-1 rather than a demonstrated fact on gnoland-1.

gnoland-1 only · also recorded against permbook

storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposits actually charged were measured from the signer's balance and are recorded in [an unpublished record], Cost; they are not chain-attested per package by this manifest.

gnoland-1 only · also recorded against coinio, feeledger and permbook

The deployed file set includes the _test.gno file. That is deliberate for a /p/ library and costs gas rather than storage, but it is a real difference from the pearl-1 deployment of the same source, where gnomcp stripped test files -- so the two chains' file sets are not expected to match.

gnoland-1 only · also recorded against feeledger and permbook

D1/D2 (same as the onyx entry): unauthenticated-principal Cancel and ttl==0 maxOpen pinning are consumer obligations; all live consumers verified safe; bytes immutable. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

gnoland-1 only

D1: Cancel(id, owner) authorizes over an unauthenticated principal string and Claim/Expire take caller-supplied `now` -- documented contracts, graded RED in any realm forwarding caller input (none does, verified). D2: a ttl==0 deferral is removable only by owner Cancel or Claim -- Expire refuses it forever -- so degenerate input can pin the shared maxOpen cap; consumers must impose ttl or per-owner bounds (all live consumers verified bounded). Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

onyx-1 only

feeledger 6 cautions

The unexported-*Ledger-pointer consumer rule is documented in the catalog but not in-package; the deployed copy is frozen.

gnoland-1 only

Consumed on this chain by revshare, service_market, subscriptions as of 2026-09-25 (Group 1). Before that this entry read "no consumer on this chain"; the claim was true when written and the reusability argument it qualified was carried over from pearl-1.

gnoland-1 only · also recorded against coinio

storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposits actually charged were measured from the signer's balance and are recorded in [an unpublished record], Cost; they are not chain-attested per package by this manifest.

gnoland-1 only · also recorded against coinio, duebook and permbook

The deployed file set includes the _test.gno file. That is deliberate for a /p/ library and costs gas rather than storage, but it is a real difference from the pearl-1 deployment of the same source, where gnomcp stripped test files -- so the two chains' file sets are not expected to match.

gnoland-1 only · also recorded against duebook and permbook

F1 (same as the onyx entry): unauthenticated account/feeBps parameters, thinnest consumer contract of the trio; all live consumers verified safe; obligation recorded, bytes immutable. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

gnoland-1 only

F1: Deposit/Withdraw/WithdrawAll take an unauthenticated account string and feeBps with the thinnest consumer contract of the data-layer trio -- no 'authorize the caller' warning, unlike journal/duebook. Every live consumer verified to derive the account from cur.Previous().Address(); the obligation is recorded here since the live bytes cannot change. RED in any realm passing caller-chosen account/feeBps. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].

onyx-1 only

journal 14 cautions

No transaction enters a /p/: the gnoland-1 commit-proof arrives with the first consumer (the accounting-suite realms are staged candidates, each its own section-8 selection). The onyx twin's coverage transfers at byte-exact strength (zero substitutions).

gnoland-1 only

J1 (audit 2026-11-04): Post/Reverse/Close take a caller-supplied clock -- a realm forwarding caller input as `at` guts the period discipline (RED in any realm that does; none does, verified across all five live accounting realms). Documented consumer obligation 2; recorded here because the live bytes cannot change.

gnoland-1 only

NOTHING CONSUMES THIS PACKAGE AND NO PROBE CAN CHANGE THAT

consumed_by is empty and that is the honest state. A /p/ package declares no crossing functions, so MsgCall has nothing to dispatch to and there is no transaction and no vm/qeval that executes this code without a consuming realm. onyx/r/books is written, audited and sabotage-covered against these exact bytes but is NOT deployed, so every behavioural claim here rests on the 56 unit tests (82 including subtests) and the 99 sabotage cases run locally under the pinned onyx toolchain, with the vm/qfile byte-match transferring that local evidence to this chain. Group 8's spendbook record and its 2026-10-05 amendment are the precedent for how this changes once a consumer lands -- and for the fact that a limitation written as permanent can expire in a day. Group 6's and Group 7's live-lifecycle evidence does NOT read across; those are different packages. CORRECTION, 2026-10-11, ONE DAY LATER AND KEPT VERBATIM ABOVE RATHER THAN REPLACED: the first sentence is now FALSE. onyx/r/books is live at height 328091 and consumed_by reads ['books'], written by tools/sync_consumed_by.py from the CHAIN's import graph and not by hand. The second clause -- 'and no probe can change that' -- was the part that was wrong, and it was wrong for a precise reason worth keeping: no probe OF THIS PACKAGE can change it, which is true and is not what the sentence said. What changed it was deploying a consumer, and the consumer's KindOf('1000') probe returns the lowercase 'asset' that only journal.Kind.String() produces, so this package's bytes are now PROVEN to execute on onyx-1 rather than merely proven to be stored there. The behavioural claims that still rest on the 56 unit tests and 99 sabotage cases are the ones r/books does not reach: nothing has posted an entry on chain, because no book has been opened. This bullet predicted its own expiry 'within a day' and that is exactly how long it took -- the lesson is not that the prediction was good but that a limitation phrased as structural ('no probe can') when it is actually contingent ('nothing consumes it yet') outlives its truth and gets quoted after it stops being true.

onyx-1 only

NO CHECK IN THIS PACKAGE VALIDATES AN ACCOUNT'S KIND

and this is structural rather than an omission to be fixed later. A Kind is accepted if it is one of the five and is never revisited, so an account opened Asset that should have been Liability reports a sign-flipped Balance() forever -- while IsBalanced, RederiveFromJournal and AccountingEquation all three stay healthy, because every one of them is built on Debits and Credits, which the Kind does not touch. It is the invariant tiers' blind spot, stated in-source as consumer-contract item 7, and a realm that lets a caller choose the Kind has delegated a decision no later check can audit. TestAccountingEquationIsBlindToAKindMisclassification pins the blindness as observed behaviour rather than leaving it as a claim.

onyx-1 only

AccountingEquation IS WEAKER THAN IT LOOKS and its own doc said something FALSE until 2026-10-07. What `offsets` proves is sum(RawDelta) == 0 over the chart, which is IsBalanced's first conjunct and nothing more -- so it is implied by IsBalanced, is NOT an independent accounting fact, and citing it as a second guarantee alongside IsBalanced is counting one check twice. It proves nothing whatsoever about normal-balance handling despite the Kind partition sitting in the code: dn and cn are a partition of the same addends, so reclassifying every account changes both subtotals while offsets stays true. The two SUBTOTALS are the product; the flag is a duplicate. Likewise IsBalanced does not re-read the journal, so a defect corrupting both accumulators identically would survive it -- RederiveFromJournal is the strong check and costs O(all postings ever).

onyx-1 only

THE BOOKS ARE CLAIMS BY WHOEVER POSTED THEM

this package proves arithmetic, never truth. With no custody there is no solvency invariant tying 'Cash 5000' to 5000 real ugnot anywhere, so a realm that presents a journal balance as a holding is asserting something this package did not check. Stated in-source as consumer-contract item 1 and flagged there as the single most important obligation and the one most commonly got wrong.

onyx-1 only

THE CLOCK IS CALLER-SUPPLIED AND UNVERIFIABLE HERE

journal cannot tell whether `at` came from runtime.ChainHeight() or time.Now().Unix(), so a realm that lets a caller choose `at` has no period discipline at all -- the caller dates entries wherever convenient, which empties Close() of meaning. Heights and seconds must also not be mixed within one Book. Consumer-contract items 2 and 3. Authorization is likewise absent: journal authenticates nobody, and who may open, post or close is the realm's policy, derived from cur.Previous().Address() and never from an argument (item 5).

onyx-1 only

A *Book IS A MUTABLE HANDLE and must not cross a realm boundary. Returning one hands out the right to open accounts, post and reverse. The package itself returns Account and Entry VALUES and never pointers into its state, and Entry's Postings slice is a defensive copy on every exported path -- but it cannot stop a consumer from exporting the handle. Consumer-contract item 4.

onyx-1 only

THE TWO ITERATORS RUN fn UNDER THE CALLING REALM'S STORAGE AUTHORITY

The receiver is stamped with the importing realm's PkgID, so borrow rule #2 leaves the realm context set to that realm for the whole callback, and a top-level fn has no receiver to anchor it elsewhere. A callback that re-enters the calling realm's own mutators does so with that realm's authority, so a caller-supplied function must never be passed here from inside a permission-gated path. Documented on IterateAccounts in-source, together with the separate rule that fn must not mutate the tree it is iterating.

onyx-1 only

STORAGE GROWS MONOTONICALLY AND maxEntries IS A LIFETIME CAP, NOT A CONCURRENT ONE

Entries are never removed, because an append-only journal whose entries could vanish is not a journal, so a Book that reaches its cap is finished -- it refuses further entries rather than dropping history, and the plan a long-lived consumer needs is a new Book with opening balances carried forward, not a pruned one. Amounts are int64 in whatever minor unit the consumer picks; journal does not know about denominations and will happily balance an entry that mixes two of them (item 6).

onyx-1 only

THE CLOSURE VERDICT IS THIN AND THE TOOLCHAIN IS SEALED, NOT ATTESTED

Gates A and B are both EQUIVALENT over exactly ONE compared chain dependency, gno.land/p/nt/avl/v0, digest efa7dbbf...c02391b5 on both sides. That is not zero -- a zero-dep EQUIVALENT means 'no chain dependencies at all' and this is not that -- but it is one package and nothing first-party. The residual differs between the gates and the verdict does not: 17 stdlib packages under --include-tests against 6 without. Stdlibs 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. Per CLAUDE.md 7a the pin proves only that nothing has changed since it was written, not that it was right when written.

onyx-1 only

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, rc=0 on exactly these bytes -- but it was run AFTER the broadcast, not before it, and the pre-broadcast gate set did not include it. The lint result is therefore a property of the deployed bytes rather than a gate the deploy cleared.

onyx-1 only

THE STORAGE DEPOSIT IS BALANCE-DERIVED, NOT CHAIN-ATTESTED

onyx-1 emits no StorageDepositEvent (storage_deposit_events: false in this stanza; re-confirmed on this tx, whose DeliverTx log reads events:[]), so storage_bytes and storage_deposit are null here as on every onyx-1 entry. A pre-broadcast and a post-broadcast gno_account read bracket the cost -- 22,165,152 then 18,453,370 ugnot, both chain_id_bound: true -- which with the exactly-reproduced fee leaves a 3,569,000 ugnot residual. That residual is an inference from two balances, not a figure the chain attested, and it is recorded in [an unpublished record] Group 10 as such.

onyx-1 only

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' and says explicitly that the sources are finite so no uniqueness claim is made. It also records the honest counter-argument against the package's own central framing rather than leaving it for a reviewer.

onyx-1 only

lots 7 cautions

A BARE /p/ CAN NEVER BE PROBED ON CHAIN, SO THE BYTE-MATCH IS THE WHOLE OF THE LIVE VERIFICATION

vm/qeval needs a realm to evaluate in, and this package declares no realm, so there is no live call to design and claiming one would be fabrication. What the chain attests is that 33,062 bytes with sha256 5ab3f19a... are at this path, read back through vm/qfile and matched EXACTLY against the committed blob. Every correctness claim about those bytes comes from the committed suite under the pinned onyx toolchain, from the mutation sweep, and from the audit -- none of it from this chain. DEPLOY_READINESS.md said so before the broadcast and the broadcast did not change it.

NOTHING ON CHAIN CALLS THESE BYTES YET

r/inventory is the consumer and is composed, tested and staged but NOT DEPLOYED (task #76), so the live state of this package is bytes at a path with zero callers. The Gate A dependency ordering this product preserved -- p/journal, then p/lots, then r/inventory -- is now two thirds complete, and the third step is what will produce the first execution evidence for any of this arithmetic on any chain.

THE PATH-INDEPENDENCE FIGURES ARE LOCAL MEASUREMENTS AND ARE NOT CHAIN-ATTESTED

The differential sweep against exact arithmetic over quantities 1-12 and costs 1-50, evaluating every ordered composition of the quantity, is in the package doc and in the committed suite. It ran under the pinned onyx toolchain on this machine. The chain executed none of it, and a reader who wants it re-established runs the suite rather than querying the node.

THE MaxLotsLimit COST CEILING IS DOCUMENTED AND WAS NEVER MEASURED, AT THE CAP OR ANYWHERE

MaxLotsLimit = 1024 bounds Consume's scan and both cross-foots at O(live lots), and the package doc states what the bound costs rather than waving at it. No gas figure was taken: gno test reports one GAS line per test FUNCTION, which cannot be read as a per-call cost. A realm rendering many pools in one query must carry its own cap and measure its own gas. Named open in AUDIT.md section 7, discharged by r/inventory measuring a Render over many pools.

THE DEPOSIT RATIO BROKE THE BAND A SECOND CONSECUTIVE TIME, DOWNWARD

AND THE MECHANISM IS UNKNOWN. 0.5344x here against r/assets' 1.237x two deploys ago, either side of a five-deploy 0.7064x-1.009x band. The comment-fraction hypothesis was measured and refuted (7,131 non-comment bytes, not 17,711). See the storage_measurement_note. This is recorded as a limitation and not as a finding because the deposit is sized from the simulate dry run and the deploy succeeded; what is lost is any ability to PREDICT a deposit from source bytes, which the band was the last remaining pretence of.

THE AUDIT'S FALSE-POSITIVE FILTER WAS SELF-ADMINISTERED, NOT A FRESH DISPATCH

Both first-pass candidates were downgraded by the same context that raised them, which is the weaker of the two available procedures. It is mitigated rather than discharged by both downgrades resting on G6's enumerable write surface, and it is named open in AUDIT.md section 7 with the harness limitation that causes it.

A MALFORMED Lot CAN ONLY ARRIVE FROM A REALM, AND THAT AUDIT IS THE CONSUMER'S

Lot's fields are exported SPECIFICALLY so a realm can persist them, which makes realm-side rehydration the one route to a malformed Lot outside this package. G6 shows Pool is fail-closed against it and G8 shows the hazard is documented, so it is not a p/lots finding -- it is an audit question carried to r/inventory, on the budgets/spendbook precedent.

periods 6 cautions

THE BYTE-MATCH WAS EXACT

On-chain periods.gno == committed 11215de3 at 32007 bytes; third consecutive byte-perfect /p/ deploy. The committed bytes were pre-measured before transcription (31939 chars, 668 tabs, 0 CR, trailing newline, 34 non-ASCII all U+2014) because r/inventory's pre-broadcast fidelity proof hashed a /tmp transcription rather than the bytes actually sent and diverged at one doc-comment token -- caught only by the post-broadcast vm/qfile.

GATE A WAS PREVIOUSLY DIVERGENT AND THE DISCHARGE WAS VERIFIED, NOT ASSUMED

It reads EQUIVALENT over 3 compared deps here because p/journal (h327179) and p/statements (h329571) are now live. The other DEPLOY_READINESS blocker -- the chain_probe preflight permission, task #16 -- was confirmed by RUNNING the command.

GATE A/B COMPARED 3 DEPENDENCIES, NOT A FULL CLOSURE

Residual stdlibs (6 at Gate B, 17 at Gate A with --include-tests) execute from the node binary and rest on the GNOROOT pin, which is SEALED, not attested.

A BARE /p/ HOLDS NO STATE

so nothing on chain was mutated. The behavioural citations evaluate pure functions; vm/qeval discards writes and there are none to discard. A consumer realm persisting a Report would discharge this; none is deployed.

also recorded against cashflow

THE THREE-BUCKET REVERSAL CENSUS IS UNTESTED ON CHAIN

Reversals / CrossWindowReversals / UnresolvedReversals -- the design centrepiece -- rest on the committed 44-test suite and the 46-case sabotage sweep under the pinned onyx toolchain. A consumer exercising a cross-window reversal live would discharge it.

AUDIT.md SECTION 3 RECORDS TWO SHIPPED DOC SENTENCES THAT WERE FALSE

and section 5 names findings left open deliberately. Y1 (a hand-built Report yields a balanced, journal-accepted, SIGN-INVERTED closing entry with err == nil) was remediated by ErrNegativeColumns, whose presence in the shipped bytes is proven by the stamped eval above. Section 4 states what the sweep could not have found.

permbook 3 cautions

No consumer on this chain. gnoland-1 now carries realms as well as the primitives, but none of them imports this package, so every reusability claim here remains a design argument carried over from pearl-1 rather than a demonstrated fact on gnoland-1.

also recorded against duebook

storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposits actually charged were measured from the signer's balance and are recorded in [an unpublished record], Cost; they are not chain-attested per package by this manifest.

also recorded against coinio, duebook and feeledger

The deployed file set includes the _test.gno file. That is deliberate for a /p/ library and costs gas rather than storage, but it is a real difference from the pearl-1 deployment of the same source, where gnomcp stripped test files -- so the two chains' file sets are not expected to match.

also recorded against duebook and feeledger

spendbook 9 cautions

No transaction enters a /p/: nothing on gnoland-1 has yet executed these bytes inside a committing transaction. The onyx-1 proof (byte-identical source, budgets consumer, ten transactions) transfers at the strength of byte identity. The gnoland-1 budgets deploy, when selected and cleared, becomes this chain's commit-proof and must add 'r/budgets' to consumed_by here.

gnoland-1 only

Disclosed material YELLOWs, retained by operator decision 2026-10-31 (grounds_status in catalog/release_decisions.json): Y2, a queued expiry extension can be silently lost (owner-recoverable, Revoke + re-Open); Y3, an amendment raising PerPeriod while lowering ExpiresAt admits ONE draw at the raised cap past the amended expiry, self-correcting after the draw, with Reap refusing to clear the window (Y3-REAP). Probe: tools/probe_spendbook_expiry.sh, PINNED, 13 assertions. Fix: .../spendbook/v2, a separate registered application.

gnoland-1 only

The payload is testless by operator decision (SEC1 route 2, AL45): spendbook_test.gno is pruned from this tree. Coverage lives at onyx/p/spendbook/spendbook_test.gno and transfers by byte identity of the executable file; register gate ENG1 re-derives that identity per run.

gnoland-1 only

This package now HAS a live, exercised consumer: gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets, deployed 2026-10-05 at height 186167, which drove ten real transactions through Open/Spend/Amend/Revoke against these deployed bytes. CORRECTED 2026-10-05 (second correction to this field): it opened 'NOTHING IS DEPLOYED THAT CONSUMES THIS PACKAGE. consumed_by is empty and that is the honest state', and went on to say that CROSS-TRANSACTION PERSISTENCE IS STILL UNDEMONSTRATED and still needs a consuming realm. Both were true when written and both are now FALSE, with transaction hashes behind the refutation: the queued loosening at height 186289 (tx 13b10861...) took effect at a window boundary and was honored by a draw 3,147 blocks later at height 189436 (tx f58bc910...), which no single transaction and no vm/qeval could have shown. The duebook precedent named in the superseded text is what happened here. The behavioural argument still rests mostly on the committed suite -- now 64 unit tests and 41 sabotage cases, up from the 61 and 40 recorded at deploy time, the three new tests and one new case being what exercising the package found -- but it is no longer the ONLY evidence. The earlier correction stands and is unaffected: vm/qeval does not dispatch through MsgCall and needs no consumer, 20 of 20 differential probes agree between the committed source and the deployed bytes (tools/probe_spendbook_live.py; onyx/p/spendbook/behavioral-probe-onyx1.json), six of them re-taken with chain_id_bound: true. See [an unpublished record], the 2026-10-05 Amendment and Group 9.

onyx-1 only

THE EXPORTED Allowance FIELDS ARE THE LAST-WRITTEN RECORD, NOT THE LIVE VIEW

and the only live-resolved view reachable from outside this package is Remaining. Between a window boundary and the next write there is no write to refresh the record, so Terms, Pending, HasPending, WindowIndex and SpentWindow all describe a window the clock has already left. Found by its own first consumer: measured on chain, budgets' grant #1 held a stored cap of 200,000 while the enforcer correctly honored a draw of 800,000 in the same window -- a factor of four, in the dangerous direction, since the owner is shown LESS authority than the delegate holds. THIS IS NOT AN ENFORCEMENT BUG: Spend and Remaining both route through the one at() function and were correct in all ten live transactions. It is the SHAPE of the API, the hazard is bounded (any write heals the record), and the remedy a future version wants is an exported live view such as Allowance.At(now). Three regression tests now pin the observed behaviour. Only HALF of it is sabotageable and the harness says which: 'Remaining resolves live' is one expression and has a case, while 'the fields are stale' follows from Get taking no `now` at all, so there is no term to invert and a case for it could never fail. Consumers must derive the window live, as budgets does.

onyx-1 only

The two largest safety properties are CONSUMER OBLIGATIONS the package cannot enforce, and both are stated in-source as items 1 and 2 of the consumer contract. (1) spendbook cannot verify that `now` came from a real clock. A realm that lets a caller choose `now` has handed the spender the power to roll the window at will, which defeats every cap here -- and it would also let a caller Reap a LIVE allowance by naming a future instant. (2) spendbook compares the spender key string it is given against the one recorded at Open; it cannot tell whether that string is the real caller. Derive it from cur.Previous().Address(), never from a caller-supplied argument.

onyx-1 only

maxLive is a single GLOBAL figure, not a per-owner quota, so any owner permitted to Open can exhaust a shared Book and lock out every other owner. Left unfixed on purpose -- the remedy (per-owner cap? whitelist? first-come?) is a policy a primitive cannot pick -- but it means 'bounded storage' must not be read as 'safe to share between mutually untrusting owners'. Named in-source under State growth.

onyx-1 only

An EXPIRED allowance is not self-removing: Spend refuses it forever while it still holds a maxLive slot. Reap closes that leak and is permissionless (an expired allowance can authorize nothing, so removing it takes nothing from anyone), but it still has to be CALLED by somebody, and nothing in this package calls it.

onyx-1 only

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, which was rc=0 on exactly these bytes.

onyx-1 only

spendbook/v2 10 cautions

No transaction enters a /p/: nothing on gnoland-1 has executed these bytes inside a committing transaction. The onyx-1 v2 twin's live probe and byte identity are the transfer; a future consumer on this chain becomes the commit-proof.

gnoland-1 only

Operator-accepted residual (SEC1, 2026-11-04, non-material): exported value-receiver IsExpired answers not-expired on the caller-built never-stored shape 0 != ExpiresAt <= now < Anchor; valid() prevents any stored allowance from having it and Spend/Reap resolve the stored allowance only. [an unpublished record] carries the full trail.

gnoland-1 only

NOTHING IS DEPLOYED THAT CONSUMES THIS PACKAGE

consumed_by is empty and that is the honest state. v1's own record shows what this costs: cross-transaction persistence of these bytes is UNDEMONSTRATED on chain, because vm/qeval discards writes and no single read can show a queued amendment taking effect at a later boundary. The Y2 fix is proven by the committed suite and by y2_probe.py, which runs this package's test file against BOTH v2 and the exact v1 bytes live at .../spendbook and refuses unless every test's declared `v1:` marker matches the measured run (AGREE, 7 of 14 discriminating). That is a LOCAL comparison of two source trees under one pin and is not chain evidence about either path.

onyx-1 only

v1's DEPLOYED CONSUMERS CAN NEVER BE MIGRATED

gno.land/r/.../budgets (height 186167) and gno.land/r/.../reimbursements (height 551862) are live and immutable with v1's bytes compiled into every path they run, which is what the dependency-closure gate is about: publishing a corrected /p/ repairs nothing already deployed. Those two realms still carry Y2. v2 is purely additive, for consumers written after it.

onyx-1 only

THE WindowIndex / SpentWindow STALENESS IS UNCHANGED AND WAS DELIBERATELY NOT FIXED. The exported Allowance fields are still the LAST-WRITTEN record rather than a live view, and Remaining is still the only live-resolved view reachable from outside. Adding an exported Allowance.At(now) was considered and declined here: it is an API addition rather than a defect repair, and the selected scope was Y2. See [an unpublished record] section 5 for the candidate the discovery gate killed and why.

onyx-1 only

THE TWO LARGEST SAFETY PROPERTIES REMAIN CONSUMER OBLIGATIONS this package cannot enforce, unchanged from v1 and stated in-source as items 1 and 2 of the consumer contract. (1) spendbook cannot verify that `now` came from a real clock; a realm letting a caller choose `now` has handed the spender the power to roll the window at will. (2) spendbook compares the spender key string it is given against the one recorded at Open and cannot tell whether that string is the real caller -- derive it from cur.Previous().Address(), never from a caller-supplied argument. The v2 fix narrows neither: a forged-clock Reap of a LIVE allowance is now harder, since IsExpired resolves pending terms, but naming a sufficiently distant future `now` still reaches it.

onyx-1 only

maxLive is still a single GLOBAL figure, not a per-owner quota, so any owner permitted to Open can exhaust a shared Book and lock out every other owner. Left unfixed on purpose, as in v1 -- the remedy is a policy a primitive cannot pick -- but 'bounded storage' must not be read as 'safe to share between mutually untrusting owners'.

onyx-1 only

An EXPIRED allowance is still not self-removing, and Reap still has to be CALLED by somebody; nothing in this package calls it. What changed is WHICH allowances Reap will accept: under v1 it accepted some whose owner had already extended them.

onyx-1 only

simulate=true on onyx-1 does NOT type-check (the chain's inert code-submission policy), and the server said so in its own next_steps. The dry run before this broadcast was a gas/fee/authorization probe only. The correctness substitute is `gno lint .` against the onyx release, which was exit 0 on exactly these bytes, and under the gnoland-1 pin as well.

onyx-1 only

THE DEPLOY DEPOSIT IS BALANCE-DERIVED, NOT EVENT-ATTESTED

onyx-1 emits no event for addpkg -- declared in this manifest's storage_deposit_events_note and re-confirmed on this tx at 0 events -- so storage_bytes and storage_deposit are null here, as C2 requires. The figure recorded in [an unpublished record] (2,739,500 ugnot over a 2,860,298 ugnot balance delta less the 120,798 ugnot gas fee) is derived from two chain_id_bound balance reads bracketing a sequence move of exactly 1, not from a chain attestation.

onyx-1 only

statements 12 cautions

NO CONSUMER IS ON CHAIN, SO Derive OVER REAL ROWS IS NOT LIVE-PROBED

BUT THE BOUNDARY IS NARROWER THAN THE FIRST DRAFT OF THIS BULLET CLAIMED, AND THE CORRECTION WAS MEASURED, NOT REASONED. onyx/r/statements is written, audited and sabotage-covered against these exact bytes but is NOT deployed. A bare /p/ declares no crossing functions, so there is no MsgCall path into it, and the imported `journal` identifier is NOT bound in a vm/qeval expression context (`MustDerive([]journal.Account{})` -> `name journal not declared`, measured twice), so Derive cannot be handed real rows from outside a consuming realm. What that does NOT block is anything reachable through the package's OWN types: Statement and Abnormal are in-package, so their composite literals ARE constructible in an expression, and a field whose type is a FOREIGN named type takes an untyped constant -- Abnormal{Code: "1000", Kind: 1, Balance: -5} type-checks on chain. The first draft of this bullet therefore listed 'the abnormality census over a miscoded chart' and 'every non-zero total' among the unreached, AND BOTH WERE FALSE WHEN WRITTEN. They were falsified by issuing the probes, not by re-reading the prose -- which is exactly how the vm/qeval binding limit itself was found, one notch earlier. PROVEN LIVE, every read chain_id_bound: true: MaxRows == 10000, which is journal.MaxAccountsLimit and so proves the cross-package import RESOLVED on chain rather than merely that these bytes are stored; SectionOf(2) == SectionClaims with a nil error; SectionOf(9) == (0, 'statements: unknown account kind'), the fail-closed branch, with error text specific to the audited source; SectionPerformance.String() == 'performance' and Section(42).String() == 'invalid', the switch default; MustDerive(nil) runs Derive and MustDerive end-to-end over the empty chart; Statement{Assets: 7, Claims: 7, CumulativeNet: -3}.SectionTotal(SectionPerformance) == (-3, nil), a NON-ZERO total through the branch that distinguishes the bottom line from the sum of its components; Statement{Abnormal: []Abnormal{{Code: "1000", Kind: 1, Balance: -5}}}.AbnormalIn(SectionAssets) == 1, which routes a journal.Kind through SectionOf and counts the match; and MustDerive(nil).SectionTotal(0) == (0, 'statements: unknown section'), the second fail-closed guard. Note the type is reported as the fully-qualified gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements.Section, not a bare Section. WHAT REMAINS GENUINELY UNREACHED, and this distinction is the whole correction: the census READER is probed and the census PRODUCER is not. Nothing on chain has run the branch inside Derive that DECIDES a row is abnormal -- AbnormalIn was handed its slice by the expression. Likewise unreached: every checked-add overflow path, ErrRowsNotOrdered, ErrNegativeTotals, ErrTooManyRows, and every figure Derive computes rather than a caller supplies. Those rest on 27 local unit tests (27 declared / 27 ran / 27 PASS / 0 FAIL, name sets identical, no subtests) and 58 sabotage cases (0 uncaught, 0 did not apply, 0 timed out, FULLY COVERED) under the pinned onyx toolchain, with the vm/qfile byte-match transferring that local evidence to this chain. DO NOT READ THIS BULLET AS PERMANENT: the equivalent sentence on p/journal was written as structural ('no probe can change that') and went FALSE in one day when its consumer landed, and this bullet's own first draft went false in under an hour. The contingent half here is 'nothing consumes it yet'; the structural half is the foreign-package binding limit, which a consumer deploy does not remove but does route around.

GATE B WAS RUN AFTER THE BROADCAST, NOT IMMEDIATELY BEFORE IT

AND THAT IS A DEVIATION FROM CLAUDE.md 7a RATHER THAN A RESTATEMENT OF IT. The rule puts Gate B at TESTNET VALIDATION immediately before deploy precisely because the working tree may drift between Gate A and the broadcast and because first-party dependencies may land in between. Neither happened -- Gate A ran minutes earlier on a tree verified identical to HEAD, every action between the two was a read, and Gate A used --include-tests, a strict SUPERSET of the chain-executed closure -- so the substance Gate B checks was established before the broadcast. What was actually skipped is the separately-timed run whose JSON record the deployment record is supposed to carry. Both gates now read EQUIVALENT with the SAME tested-closure digest 66d146fe20d9f0eb112580d080319e69bb6235976e863be5218c2c7b77b09b20 appearing as the chain-closure digest too, over 2 compared dependencies. The ordering defect produced no wrong verdict here; recording it as 'Gate B: EQUIVALENT' without the timing would have been the citation that cannot be reproduced.

THE COMMITTED GATE A RECORD READ DIVERGENT UNTIL THIS DEPLOY, FOR A NAMED AND CONTROLLED REASON

The superseded run recorded equivalent: false with exactly two failures -- 'gno.land/p/nt/avl/v0 is in the tested closure but NOT in the chain closure' and 'journal is a tested dependency but absent from the target chain' -- both of which were the SAME fact: journal was not yet deployed, so the chain-side closure walk could not reach avl through it. The flip to EQUIVALENT is a single-variable change and is mechanically demonstrable: the tested-closure digest is BYTE-IDENTICAL across the two runs, and checker_sha256, include_tests, gnoroot, gnohome, rpc and stdlib_residual all read SAME, so only the chain side moved. That is the dependency ordering p/journal -> p/statements being proven by measurement rather than asserted.

simulate=true ON onyx-1 DOES NOT TYPE-CHECK, because of the chain's inert code-submission policy, so the mandatory dry run before this broadcast was a gas/fee/authorization probe only -- it returned gas_used 25,987,184 against the broadcast's 25,987,174, a 10-unit difference. UNLIKE p/journal's deploy, the correctness substitute WAS run before the broadcast here: `gno lint .` against the onyx release, exit 0 with zero output, on bytes proven identical to HEAD. That is the gate p/journal's record had to admit it cleared only afterwards.

THE STORAGE DEPOSIT IS BALANCE-DERIVED, NOT CHAIN-ATTESTED

onyx-1 emits no StorageDepositEvent, so storage_bytes and storage_deposit are null here as on every onyx-1 entry. A pre-broadcast and a post-broadcast gno_account read bracket the cost -- 14,377,572 then 12,868,410 ugnot, both chain_id_bound: true, with the sequence moving 48 -> 49 and so by exactly ONE step, which is what makes the delta attributable to this single transaction. Delta 1,509,162 ugnot less the exactly-reproduced fee of 77,962 leaves a 1,431,200 ugnot residual. That residual is an INFERENCE FROM TWO BALANCES, not a figure the chain attested.

THE BALANCE-SHEET IDENTITY THIS PACKAGE REPORTS IS A CONSTANT TRUE OVER ITS PRIMARY INPUT

and the package says so in its own doc rather than leaving a reader to find out. Over a chart drawn from a journal.Book, Assets == Claims is algebraically identical to totalDebits == totalCredits, which the Book already enforces on every Post, so it holds even of a chart whose every account is classified backwards. No Balances() verdict is offered anywhere in the API -- only the two figures. The identity is NOT vacuous over the package's own wider input domain, since rows may be hand-built and need not sum to zero, and THAT SCOPE IS THE WHOLE CONTENT OF THE CLAIM: dropping it is the documentation defect this project has recorded most often.

THE ABNORMALITY CENSUS IS SOUND BUT INCOMPLETE, AND THE INCOMPLETENESS IS EXACTLY CHARACTERISABLE

It CATCHES a misclassification that crosses the debit/credit-normal boundary on an account with a non-zero balance. It CANNOT CATCH one that stays on the same side -- Liability <-> Equity <-> Revenue, or Asset <-> Expense -- because both subtotals move, no balance goes negative, and nothing anywhere detects it; journal's consumer-contract obligation 7 is NARROWED, NOT CLOSED. It also has a FALSE-POSITIVE MODE that is not a defect: a genuine contra account, such as accumulated depreciation under assets or a sales return under revenue, is legitimately abnormal-signed. The census is a finding to read, never a verdict.

A STATEMENT IS A REPORT ABOUT A BOOK, AND A BOOK IS A SET OF CLAIMS BY ITS OWNER WITH NO CUSTODY BEHIND IT

This package never sees a coin and performs no audit: every figure is derived from rows the caller supplied, so deriving them faithfully is the guarantee and that they describe anything real is not. A realm publishing this output must say so loudly, because a published statement ADDS APPARENT AUTHORITY TO A SELF-REPORT, and that is the one way this product can mislead someone.

THE CLOSURE VERDICT IS THIN AND THE TOOLCHAIN IS SEALED, NOT ATTESTED

Both gates are EQUIVALENT over 2 compared chain dependencies -- journal (first-party) and gno.land/p/nt/avl/v0 -- which is more than p/journal's one and is still two packages. The residual differs between the gates while the verdict does not: 17 stdlib packages under --include-tests against 6 without. Stdlibs 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. Per CLAUDE.md 7a a pin proves only that nothing has changed since it was written, never that it was right when written.

TWO STATEMENTS AND A CENSUS, NOT A STATEMENT SET

No statement of changes in equity, no notes, no comparatives. A PERIOD income statement is out of scope because it needs entry-level data this package never receives, and a cash-flow statement is out of scope because it needs a cash-vs-accrual classification journal.Posting does not carry. MaxRows is journal's own 10,000-account cap and is a CEILING A CALLER MAY ASK FOR, NOT A SIZE WORTH REACHING: Derive is one pass with a checked add and a comparison per row, so a chart at the limit is 10,000 iterations in a single call, and the wrapping realm should and does cap far lower (r/books allows 128).

ErrRowsNotOrdered IS A PRECONDITION, NOT AN INTERNAL SORT, and that is load-bearing in three ways at once: it rules out a duplicate code silently double-counting a balance, it keeps Abnormal in a deterministic order a Render can rely on, and it costs one string comparison per row instead of an allocation and a sort. A row with an EMPTY Code fails it, since no string sorts before the empty string -- journal refuses an empty code at Open, so a Book-derived chart cannot contain one. ErrNegativeTotals is likewise not tidiness: journal.Account.Balance subtracts one column from the other with no check of its own, so a row with Debits near max and a NEGATIVE Credits yields a WRAPPED balance before any arithmetic here runs, and no amount of checked adding downstream recovers a figure that was already wrong on arrival. Refusing negative columns is the precondition under which the checked adds in Derive are sufficient.

AL29b, 2026-10-15: Derive(nil) IS NOW PROBED DIRECTLY, AND LIMITATION 0's QUALIFIER IS CONFIRMED EXACT RATHER THAN CORRECTED

New probe (28 in onyx/AL29-behavioral-probes.json), predicted before the read, stamped chain_id_bound: true, agreeing: func() (int, int64, int64, bool, bool) { st, err := Derive(nil); return st.Rows, st.Assets, st.Claims, st.Abnormal != nil, err == nil }() -> (0 int) (0 int64) (0 int64) (true bool) (true bool). IT IS A WEAK PROBE AND IS LABELLED SO: an all-zero tuple whose single non-vacuous bit is st.Abnormal != nil, true only because Derive explicitly initialises []Abnormal{} -- a zero Statement{} returns false there. Weaker than p/cashflow's three verbatim sentinels. LIMITATION 0 WAS AND REMAINS CORRECT, and that is the finding. It says 'Derive OVER REAL ROWS is not live-probed' and lists 'MustDerive(nil) runs Derive and MustDerive end-to-end over the empty chart' under 'PROVEN LIVE' -- consistent and precise, because MustDerive(nil) passes an EMPTY chart and the unbound-`journal` limit is what keeps real rows out. Both catalog claims were re-issued today (probes 29-30) and BOTH REPRODUCE EXACTLY, including MustDerive(nil).SectionTotal(0) -> (0 int64) and the *errors.errorString carrying 'statements: unknown section'. WHAT WAS FALSE WAS AN EXTERNAL RECORD: [an unpublished record]'s first AL29 amendment listed 'statements.Derive(nil)' in 'the un-attempted set', while this entry had recorded the attempt since commit bc01165. MustDerive is a one-line wrapper calling Derive and panicking on its error, so MustDerive(nil) returning a nine-field Statement IS Derive(nil) succeeding; the residual list dropped limitation 0's 'over real rows' qualifier, which is the whole content of the bullet. TWO MECHANISM NOTES. (1) MustDerive(nil).SectionTotal(0) chains a method onto a constructor result with NO CLOSURE, so the bare New(...).Method(...) shape works whenever the constructor is SINGLE-VALUED; a closure is needed only for a multi-valued return. (2) WIRE FORM, new: a struct renders inline as (struct{(v t),(v t),...} <fully-qualified>.Type) -- fields in DECLARATION ORDER, comma-separated, NO FIELD NAMES -- and an empty-but-non-nil slice renders (slice[] []<path>.Elem). A struct reply is therefore POSITIONAL, so renaming or reordering a field silently reshuffles it; prefer a closure returning named scalars when a reply will be parsed. STILL UNREACHED, unchanged and citing its attempt: no type in this package or p/cashflow has a field of type journal.Account or journal.Entry (measured by grep), so all seven of Derive's error paths, every checked-add overflow, ErrRowsNotOrdered, ErrNegativeTotals, ErrTooManyRows and the abnormality PRODUCER remain local-test evidence only.

tiers 7 cautions

THE BYTE-MATCH WAS EXACT

On-chain tiers.gno == committed 2adf734f at 36347 bytes; no reconciliation needed. Second consecutive byte-perfect /p/ deploy after p/aging.

A STAMPED EVAL CITATION EXISTS

UNLIKE p/aging. gno_eval RateScale -> (10000 int64) with chain_id_bound: true is a section-9a value citation, because a package-level const evaluates on a bare /p/ where a method on a struct literal does not. The byte-match (unstamped, bound by URL) remains the which-bytes-run proof; the eval adds chain attestation of one source-derived value. Behaviour rests on the 41-test suite and 105-case sweep under the pinned onyx toolchain.

GATE A/B COMPARED ZERO DEPENDENCIES

errors + math/bits are both stdlib; EQUIVALENT means 'no chain deps', not 'a closure was verified'. Both residual stdlibs execute from the node binary and rest on the GNOROOT pin.

THE 0.53x /p/ CLUSTER WAS PREMATURE

tiers measured 0.3214x, so three bare /p/ now span 0.32x-0.53x. The ratio is not a predictor and was never treated as one for sizing; a deposit comes from the simulate dry run.

COST FIGURES ARE SELF-REPORTED AND THE PACKAGE PRICES NOTHING

Edges and rates are an input validated for shape and range, never plausibility, and -- unlike p/aging -- not for ordering either, so a transposed rate array is accepted and undetectable. Picking a fee policy is none of this package.

THE WORK IS BOUNDED, NOT PRICED

MaxTiers 16 bounds the loops; the doc gives the 128-bit mul/div and comparison counts but declines to assert they are affordable inside a Render.

THE AUDIT WAS SELF-ADMINISTERED (Task unavailable in-subagent)

Verdict PROCEED, 0 RED / 3 YELLOW; the rate-sign guard (chargeSlice(1,-1) returned a positive charge with nil error) was added after the adversarial pass and is recorded rather than smoothed.

When not to use them: if you need upgradeable logic (deployed bytes are immutable), non-ugnot assets beyond what coinio's denom parameter covers, or push-based payouts (everything here is pull/claim by design).

4. Import them

On gno.land the import path is the deployed address; there is no package manager and nothing to pin — the bytes at these paths can never change.

The paths are per chain, because the namespace is part of the path. Use the block for the chain you are deploying to; the other one will not resolve there. The code behind both is byte-identical.

Deploying to gno.land mainnet gnoland-1

import "gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio"
import "gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebook"
import "gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger"

Deploying to onyx-1 testnet onyx-1

import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio"
import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook"
import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger"

Declare each in your gnomod.toml module as usual.

5. Read the APIs

gnoweb's $help shows only top-level functions of a /p/ package — for these packages that hides most of the surface (types and methods). The full chain-derived API is on each package page here, aging, cashflow, coinio, depreciation, duebook, feeledger, journal, lots, periods, permbook, spendbook, spendbook/v2, statements, tiers, and as raw Markdown under api/.

6. Start from a real minimal example

These three realms are the canonical minimal consumers — each is the smallest real composition of one or two primitives this portfolio has, and each was deployed, byte-verified and attacked on chain. They are on onyx-1 testnet:

booksthe smallest real consumer of a single primitive: one p/journal double-entry book per owner, holding no coins, with the clock taken from the chain and the account kind derived from its codesource
invoicescoinio + feeledger composed over real ugnot custody: issuing moves nothing and reserves nothing, the named payer settles in coins, and the issuer pulls the proceedssource
latefeesa p/tiers schedule priced over overdue duration, and the first realm here to import another realm through the deploy path — it publishes a penalty it has no power to collectsource

For a full-scale composition, read subscriptions — plans, windowed renewals and fee handling over the same two value primitives.

7. Call a live realm

Realms are driven from the command line, and you do not need to hand-write the transaction: gnoweb generates the exact command for any exported function, with the argument names filled in — the call builder for service_registry.AcceptOwnership. It looks like this:

gnokey maketx call \
  -pkgpath gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/service_registry \
  -func AcceptOwnership \
  -gas-fee 1000000ugnot -gas-wanted 2000000 \
  -remote https://rpc.gno.land:443 \
  -chainid gnoland-1 \
  <your-key>

That command targets gno.land mainnet and spends real GNOT. It is not the testnet one, because there is no live testnet deployment of service_registry to point at. Read it before you run it, start with a read-only function, and size -gas-fee from a simulation rather than from this example.

Read-only views cost nothing to inspect; every package page links the rendered page and the state explorer. If you are integrating against a realm that moves value, read its known limitations and the linked deployment record first — those record what was not verified on chain, which is usually the part that matters.

8. Verify before you depend

Every package page carries the digest of the deployed bytes and the command that fetches them from the chain — proof that needs no trust in this site. For coinio on gnoland-1:

curl -sS 'https://gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio$download&file=coinio.gno' | shasum -a 256

Expect ea9d467d380ff2971695d452f96fe64ea46d847d094023f755edf5f79995b09d (5,685 bytes). The same check works for every package on every chain it is deployed to, with its own command on its page — and because 6 of these 14 are deployed from identical bytes, each one's digest is the same on every chain that holds it while the URL is not.

9. Compose your application

The pattern every live consumer follows: guard the payment at the door, account for it in the ledger, record the obligation, and pay out only by pull. In outline:

// A sketch of the composition -- the demo realms above are the
// full, deployed, attacked pattern. Receive guards the payment,
// the ledger holds balances and the bounded fee, the book holds
// the scheduled obligation.
var (
    ledger = feeledger.MustNew(maxFeeBps) // hard fee cap, forever
    book   = duebook.MustNew(minDelay, maxDelay, maxOpen)
)

func Pay(cur realm) {
    payer, amount := coinio.Receive(0, cur, "ugnot")
    ledger.MustDeposit(payer.String(), amount, feeBps)
    book.MustSchedule(payer.String(), payload,
        runtime.ChainHeight(), delay, ttl)
}

func Claim(cur realm, amount int64) {
    caller := cur.Previous().Address()
    if err := ledger.Withdraw(caller.String(), amount); err != nil {
        panic(err)
    }
    coinio.Payout(0, cur, caller, "ugnot", amount)
}

From here, the Verification page explains exactly what has been proved about everything you just imported — and what has not.