periods
gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods on onyx-1 testnet
pure packageprimitiveaccountinglive on onyx-1
Turn a running ledger into a report for one period.
Shows what moved between two dates, what the period earned, and the closing entry that carries the result into equity.
In detail curated
A pure period-close primitive over a HALF-OPEN Window{From, Until): it derives a period income statement from a p/journal entry set and produces the CLOSING ENTRY that p/journal.Close deliberately does not make. Imports stdlib errors plus p/journal and p/statements; MaxAccounts is reached THROUGH statements.MaxRows (10000) rather than restated, and a sabotage case asserts that -- a restated constant would silently desynchronise from the statement layer. MaxEntries 4096. The design centrepiece is the THREE-BUCKET REVERSAL CENSUS: Reversals (resolved inside the window), CrossWindowReversals (the pair straddles the boundary) and UnresolvedReversals (no pair found), which is the distinction a single reversal count cannot express and the reason a naive period report silently double-counts or silently drops a straddling reversal. ErrNegativeColumns is the Y1 audit remediation and its guard MUST sit BEFORE the nominal partition -- a delete-only sweep scores both placements identically, so the harness carries a MOVE case rather than a deletion. Fourteen sentinel errors; checkedAdd/checkedSub with an explicit minInt64 for the negation asymmetry. Window carries Valid/Contains/Tiles, where Tiles encodes half-open adjacency (10,20) tiles (20,30) and does NOT tile (25,30).
Use it
A pure package has no state and no transactions: you use it by importing it.
Import it
import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods"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.
Deployments
onyx-1 testnet onyx-1
chain-attested| Import path | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 374,238 |
| Deploy transaction | 9f02467f68a38356660ce0c7ddfad74fb0e9b21fd75d999fd23ecb1f79eff617 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 43,407,775 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | periods.gno gnomod.toml |
| Deployed bytes | periods.gno — 32,007 bytes |
| sha256 | 11215de3fab731525a9b2d524613669ee5b0457e4f4e0b0dd00d2eb381e388ae |
| Realm address | none — A /p/ package is not a realm: it holds no state, custodies no coins and is never a transaction sender. The pkgPath derivation would still produce a value; recording one would name nothing. |
| Used by | p/cashflow, r/cashflow |
|---|
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/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods$download&file=periods.gno' | shasum -a 256Expected: 11215de3fab731525a9b2d524613669ee5b0457e4f4e0b0dd00d2eb381e388ae — 32,007 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 2 exported functions, 4 types, 7 methods.
This is a /p/ package: you import it, you do not call it in a transaction. gnoweb's $help shows only exported top-level functions, so the types and methods below do not appear there at all — which is why this reference exists.
Overview
Package periods turns a cumulative double-entry book into a period report: per-account movement over a date window, the period income statement, and the closing entry that sweeps nominal accounts into equity.
p/journal is cumulative by construction and says so. Its Close is a backdating seal — it refuses entries dated at or before a cutoff and does not sweep anything — so revenue and expense accumulate from the book's first posting forever, and p/statements names its three figures CumulativeRevenue, CumulativeExpenses and CumulativeNet for that reason. Its package doc states the gap in one sentence: "A period income statement needs entry-level data and is out of scope." This package is that entry-level consumer. It reads no chain, holds no state, and writes nothing.
w := Window{From: q1Start, Until: q2Start} rep, err := Derive(chart, entries, w) // rep.Activity, rep.PeriodNet, rep.CrossWindowReversals post, err := rep.ClosingEntry("3100") // a PROPOSAL, not a write
Why the chart is a parameter
journal.Posting is {Account string, Side, Amount int64}: it carries the account's CODE and never its Kind. So revenue cannot be told from expense from postings alone, and no income statement is derivable without the chart. That single fact fixes this package's signature. Only Code, Name and Kind are read; Debits and Credits are the book's CUMULATIVE columns and are deliberately neither read nor validated here, because period movement comes from the entries.
The window is closed-open
Window is [From, Until): From is included, Until is not. Consecutive periods therefore TILE — Window.Tiles is the predicate — with no overlap and no gap, which is what makes a sequence of period reports add up to the cumulative figures. A closed-closed window double-counts exactly one timestamp per boundary, and that error is invisible in any single report.
Two inputs, two ordering decisions, opposite answers
This package imposes an ordering precondition on one input and refuses one on the other, and the rule separating them is what journal GUARANTEES:
- chart must be strictly ascending by Code (ErrChartNotOrdered). journal.Book.IterateAccounts is "ascending code order", so every producer meets it. It buys duplicate-code detection, chart-order output with no sort, and a binary-search code lookup instead of a map.
- entries must be strictly ascending by ID (ErrEntriesNotOrdered). journal.Book.IterateEntries and journal.Book.Entries both guarantee it. It buys duplicate-ID detection, the binary search that resolves ReversalOf, and ID >= 1 for free, 0 being ReversalOf's sentinel.
- entries must NOT be required in ascending At order, and this package must never require it. At is caller-supplied and backdating is legal until Close seals it, so journal guarantees nothing about it. Such a precondition would be one no producer in this portfolio can meet — a gate that can never pass, in an API signature rather than in a checker.
The reversal census has three buckets, and the third is the point
A reversal dated inside the window whose original is dated outside it changes what the period means: the window carries a correction to activity it never contained. The census reports three things and never two:
- [Report.Reversals] — in-window reversal, original also in-window: ordinary, classified.
- [Report.CrossWindowReversals] — in-window reversal, original dated outside: classified, and material.
- [Report.UnresolvedReversals] — in-window reversal whose original is ABSENT from the entries slice, so the classification COULD NOT BE MADE.
Collapsing the third into either of the first two would read "could not evaluate" as assent. It is a census, not a verdict — the same posture as statements.Statement.Abnormal — and it is a function of what the caller passed, not of the book: pass a window-filtered slice and every reversal reaching back before it is unresolved by construction.
What this package does not do
- **It never writes.** Report.ClosingEntry returns postings for the caller to Post. It sweeps PERIOD MOVEMENT, not cumulative balance, so running it twice over one window and posting both results double-sweeps — and this package never sees the Book, so it cannot detect that. The caller owns idempotence.
- **No paging, no Merge.** MaxEntries is the hard bound. Merging reports across pages would make UnresolvedReversals an artifact of the paging rather than of the data; fixing that needs a two-pass design over the whole entry set and is a different package.
- **No cash-flow statement.** The indirect method needs a cash-vs-accrual classification journal.Posting does not carry. [Report.Activity] is the balance-sheet delta half it would need.
- **No audit.** Every figure is derived from what the caller supplied. A chart from one book and entries from another produce a plausible report and nothing here detects it.
p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal p/statements: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements
Imports
errorsgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journalgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements
Constants and variables
MaxAccounts bounds the chart Derive will accept. It is statements.MaxRows — journal's own account cap, reached through the package that already made this decision rather than restated — because a row set larger than the largest chart this ecosystem's bookkeeping primitive can hold did not come from a chart.
const MaxAccounts = statements.MaxRows
MaxEntries bounds the entry slice Derive will walk, and it is set far below journal.MaxEntriesLimit on purpose: the journal holds a million entries and a million-entry loop is not a transaction.
What the bound costs, stated rather than waved at. Derive makes one pass over the entries and, per entry, one pass over its postings, each posting costing a binary search over the chart. journal.MaxPostingsPerEntry is 64 and is re-checked here (ErrTooManyPostings) so the product is a PROVEN ceiling and not an assumed one:
4096 entries x 64 postings = 262 144 posting visits, each a log2(10 000) ~ 14-step search
which is the same order as the 10 000-row walk statements.Derive documents. A realm wanting period reports caps its own journal at or below this; r/books allows 128 accounts and should be read the same way — this is the ceiling a caller may ask for, not a size worth reaching.
const MaxEntries = 4096
Errors returned by this package.
var (
// ErrInvalidWindow is a window failing [Window.Valid]: From negative, or
// Until not strictly after From. An empty window is refused rather than
// reported as a period with no activity, because the two are different
// claims and only one of them is a report.
ErrInvalidWindow = errors.New("periods: window must satisfy 0 <= From < Until")
// ErrTooManyAccounts is a chart longer than [MaxAccounts].
ErrTooManyAccounts = errors.New("periods: chart length exceeds MaxAccounts")
// ErrTooManyEntries is an entry slice longer than [MaxEntries].
ErrTooManyEntries = errors.New("periods: entry count exceeds MaxEntries")
// ErrChartNotOrdered is a chart not in strictly ascending Code order.
// journal's own enumerator is, so this also catches a duplicated code.
ErrChartNotOrdered = errors.New("periods: chart must be in strictly ascending code order")
// ErrEntriesNotOrdered is an entry slice not in strictly ascending ID
// order, which includes an ID of zero. Both of journal's producers are
// ordered; see the package doc on why At carries no such requirement.
ErrEntriesNotOrdered = errors.New("periods: entries must be in strictly ascending ID order")
// ErrInvalidKind is an account whose Kind is outside journal's five. The
// zero Kind is the case that matters: journal reports it as
// credit-normal, so a silent fallthrough would file it rather than refuse
// it.
//
// The two producers are not symmetrical, and saying so is the point.
// [Derive] raises it from the CHART PRE-PASS, which proves every Kind
// [journal.Kind.Valid] before anything is summed; its later
// [statements.SectionOf] branch is therefore UNREACHABLE, because
// SectionOf's switch covers exactly the five kinds Valid admits. That
// branch ships anyway, as fail-closed defence against the two definitions
// drifting apart in a later version of either package — but it is not a
// path this package can reach today and must not be cited as one.
// [Report.ClosingEntry] is the reachable SectionOf case: it is handed a
// Report rather than a chart, runs no pre-pass, and so a hand-built row
// with an invalid Kind reaches SectionOf directly.
ErrInvalidKind = errors.New("periods: unknown account kind")
// ErrInvalidPosting is a posting with an invalid Side or a non-positive
// Amount. journal cannot produce one; a hand-built entry can.
ErrInvalidPosting = errors.New("periods: posting needs a valid side and a positive amount")
// ErrTooManyPostings is an entry with more than
// [journal.MaxPostingsPerEntry] postings. journal cannot produce one, and
// re-checking is what makes [MaxEntries]'s cost bound a proven ceiling.
ErrTooManyPostings = errors.New("periods: entry exceeds journal.MaxPostingsPerEntry")
// ErrUnknownAccount is a posting naming a code absent from the chart.
// Dropping it silently would UNDERSTATE the period, which is worse than
// refusing to report it.
ErrUnknownAccount = errors.New("periods: posting names a code absent from the chart")
// ErrNegativeColumns is an [Activity] row whose Debits or Credits is
// negative. [Derive] cannot produce one — it accumulates positive posting
// amounts from zero — so this is only reachable through a hand-built
// Report, and it is checked for the reason [statements.ErrNegativeTotals]
// is checked over the identical hazard on the identical type.
//
// [Activity.Movement] delegates to [journal.Account.Balance], which
// subtracts one column from the other with no check of its own. A row
// carrying Credits near max and a NEGATIVE Debits therefore yields a
// WRAPPED movement before any arithmetic here runs, and the wrap survives
// every downstream guard: it is nonzero, so the m == 0 skip does not
// fire, and it is not minInt64, so magnitude reports success. The result
// was a closing posting with an INVERTED SIGN, balanced, both sides
// present, within the posting cap, and acceptable to [journal.Book.Post]
// — a wrong figure with no error anywhere, which is strictly worse than
// [ErrOverflow] because nothing downstream can tell it from a real one.
// With both columns non-negative the subtraction lands in
// [-maxInt64, maxInt64] and cannot wrap, which is the precondition the
// checked arithmetic in [Report.ClosingEntry] is sufficient under.
ErrNegativeColumns = errors.New("periods: activity column totals must not be negative")
// ErrInvalidEquityCode is an empty equity code, or one longer than
// [journal.MaxCodeLen].
ErrInvalidEquityCode = errors.New("periods: equity code must be non-empty and within journal.MaxCodeLen")
// ErrEquityCodeIsNominal is an equity code naming one of the nominal
// accounts being swept. The resulting entry would balance and mean
// nothing. See [Report.ClosingEntry] on what this check cannot cover.
ErrEquityCodeIsNominal = errors.New("periods: equity code names a nominal account being closed")
// ErrNothingToClose is a window with no nominal movement at all. The
// alternative is returning an empty posting slice that journal would
// refuse with ErrTooFewPostings, which moves the failure one call later
// where it is harder to read.
ErrNothingToClose = errors.New("periods: no nominal movement in the window")
// ErrTooManyNominalAccounts is a sweep that would exceed
// [journal.MaxPostingsPerEntry]: at most 63 nominal accounts plus the
// equity posting.
ErrTooManyNominalAccounts = errors.New("periods: closing entry exceeds journal.MaxPostingsPerEntry")
// ErrOverflow is an int64 overflow in any accumulation.
ErrOverflow = errors.New("periods: int64 overflow")
)
Types
type Activity
type Activity struct {
Code string
Name string
Kind journal.Kind
Debits int64
Credits int64
}
Activity is one account's movement WITHIN the window. Debits and Credits are period sums, not the book's cumulative columns, and both are non-negative AS PRODUCED BY Derive — postings carry positive amounts and Derive refuses anything else.
A Report assembled by hand is under no such constraint, so Report.ClosingEntry checks it: a negative column there is ErrNegativeColumns. The check is not tidiness. [Movement] delegates to journal.Account.Balance, an unchecked subtraction, so a negative column wraps the movement BEFORE any guard here runs and the wrap passes all of them — the earlier version of this comment claimed a hand-built Report's only consequence was ErrOverflow, and an audit disproved it by producing a sign-inverted closing entry that journal.Book.Post accepts with no error at all. See ErrNegativeColumns for the mechanism.
Derive emits a row only for a code the window actually touched: an account with no movement is ABSENT, not a zero row. Over a chart of 10 000 with three active accounts that is three rows, and the alternative is paying statements' full MaxRows cost to report nothing.
Activity.Movement
func (a Activity) Movement() int64
Movement returns the period movement in the account's NORMAL direction, so a revenue account that earned reads positive and a contra movement reads negative.
It is journal's own definition, reached by handing journal the two columns rather than re-deriving the normal-balance rule here. One definition of which kinds are debit-normal, in the package that owns it.
Inheriting that definition means inheriting its arithmetic: Balance subtracts without a check, so on a row with a NEGATIVE column — which only a hand-built Report can hold — this wraps and returns a plausible wrong number. It is left unchecked rather than given an error return, because a two-value Movement would have to be handled at four call sites to defend one input class that Report.ClosingEntry refuses outright. The guard is at the boundary; see ErrNegativeColumns.
Activity.RawDelta
func (a Activity) RawDelta() int64
RawDelta returns Debits-Credits regardless of kind. Summed over every row of a report derived from balanced entries it is exactly zero, which is what makes it useful for a consistency check and wrong for a report.
type CrossReversal
type CrossReversal struct {
ID uint64
ReversalOf uint64
Total int64
}
CrossReversal is an in-window reversal whose original entry is dated outside the window: the period carries a correction to activity it never contained.
Total is the reversal's debit total (journal.Entry.Total), which is its magnitude on both sides because journal only accepts balanced entries.
type Report
type Report struct {
// Window is the window the figures are for, echoed back so a stored
// report cannot be read without it.
Window Window
// Activity is per-account period movement, in CHART order — which the
// chart precondition makes ascending by Code — for codes the window
// touched.
Activity []Activity
// Entries is how many entries fell in the window. Zero is a legitimate
// report of a quiet period, not an error.
Entries int
// FirstID and LastID are the lowest and highest IDs IN THE WINDOW, or
// both zero when Entries is zero. They are min and max over the
// in-window SUBSET and not the ends of the supplied slice: over entries
// 1, 2, 3 with only 2 in the window, both read 2.
FirstID uint64
LastID uint64
// PeriodRevenue and PeriodExpenses are the natural-sign period totals of
// the two nominal kinds. The names carry the word PERIOD for the same
// reason statements' carry CUMULATIVE: a reader who takes one for the
// other misreads every figure.
PeriodRevenue int64
PeriodExpenses int64
// PeriodNet is PeriodRevenue - PeriodExpenses: the period income
// statement's bottom line, which no cumulative report can produce.
PeriodNet int64
// Reversals counts in-window reversals whose original is also in-window.
Reversals int
// CrossWindowReversals lists in-window reversals whose original is dated
// outside the window, in the order the entries were walked.
CrossWindowReversals []CrossReversal
// UnresolvedReversals lists the IDs of in-window reversals whose original
// is absent from the supplied entries, so they could be classified
// neither way. See the package doc: this is the bucket that must not be
// collapsed.
UnresolvedReversals []uint64
}
Report is one window's activity. Every slice is freshly allocated by Derive on every call and is never nil, so the caller owns it and an empty result is an empty slice rather than a nil to be distinguished from one.
| Exported field | Type | Doc |
|---|---|---|
Window | Window | Window is the window the figures are for, echoed back so a stored report cannot be read without it. |
Activity | []Activity | Activity is per-account period movement, in CHART order — which the chart precondition makes ascending by Code — for codes the window touched. |
Entries | int | Entries is how many entries fell in the window. Zero is a legitimate report of a quiet period, not an error. |
FirstID | uint64 | FirstID and LastID are the lowest and highest IDs IN THE WINDOW, or both zero when Entries is zero. They are min and max over the in-window SUBSET and not the ends of the supplied slice: over entries 1, 2, 3 with only 2 in the window, both read 2. |
LastID | uint64 | |
PeriodRevenue | int64 | PeriodRevenue and PeriodExpenses are the natural-sign period totals of the two nominal kinds. The names carry the word PERIOD for the same reason statements' carry CUMULATIVE: a reader who takes one for the other misreads every figure. |
PeriodExpenses | int64 | |
PeriodNet | int64 | PeriodNet is PeriodRevenue - PeriodExpenses: the period income statement's bottom line, which no cumulative report can produce. |
Reversals | int | Reversals counts in-window reversals whose original is also in-window. |
CrossWindowReversals | []CrossReversal | CrossWindowReversals lists in-window reversals whose original is dated outside the window, in the order the entries were walked. |
UnresolvedReversals | []uint64 | UnresolvedReversals lists the IDs of in-window reversals whose original is absent from the supplied entries, so they could be classified neither way. See the package doc: this is the bucket that must not be collapsed. |
Report.ActivityIn
func (r Report) ActivityIn(sec statements.Section) int
ActivityIn counts the rows presented in sec.
The section is recomputed from each row's Kind through statements.SectionOf rather than stored, so there is one definition of the three-bucket partition and no second field that could disagree with it. A Report built by hand can hold a row with an invalid Kind; such a row is counted in no section, and Derive cannot produce one.
It deliberately does NOT check ErrNegativeColumns, unlike Report.ClosingEntry, and has no error return to do it with. The reason is that it reads no column: the count depends on Kind alone, so a negative column cannot make this answer wrong. A guard added here for symmetry would be a check that can never change an outcome — the vacuous-true shape this portfolio refuses elsewhere — and the honest version is this sentence.
Report.ClosingEntry
func (r Report) ClosingEntry(equityCode string) ([]journal.Posting, error)
ClosingEntry returns the postings that zero every nominal account's PERIOD MOVEMENT and carry the net to equityCode — the entry journal.Book.Close deliberately does not make, Close being a backdating seal and nothing else.
Nominal means statements.SectionPerformance: the partition is read through statements.SectionOf rather than restated as a second list of kinds.
The result is a PROPOSAL. This package holds no Book and posts nothing, and three consequences follow that a caller must own:
- It sweeps period movement, not cumulative balance. Posting it twice for one window double-sweeps the period, and nothing here can detect that.
- A window the book has already sealed is still reportable — Close refuses writes, not reads — so these postings may come back journal.ErrPeriodClosed. That is journal's call; this package cannot see the seal and does not pre-empt it.
- equityCode is checked for being empty, over-long, or one of the nominal codes being swept (ErrEquityCodeIsNominal). It CANNOT be checked for naming an Equity-kind account, because Report does not retain the chart: a code naming a known liability yields an entry journal will accept and that files net income in the wrong place. journal rejects an unknown code on its own; the wrong-kind case is the caller's.
The postings always balance and always carry both a debit and a credit when there are any at all, which is why a non-error return is always postable on those grounds. Net income is credited to equity and a net loss is debited, equity being credit-normal. A window with no nominal movement is ErrNothingToClose rather than an empty slice.
That postability claim rests on a precondition Derive supplies for free and a hand-built Report does not, so this function establishes it itself: every row's columns must be non-negative, or ErrNegativeColumns. Without the check the claim is false in the worst available way — not an error, but a balanced, postable, SIGN-INVERTED entry. See ErrNegativeColumns.
type Window
type Window struct {
From int64
Until int64
}
Window is a half-open date range [From, Until): From is included, Until is not. The unit is whatever the book's At values are — journal treats At as an opaque non-negative int64, so a caller holding seconds passes seconds and a caller holding block heights passes heights. This package imposes no calendar, which is why it needs none.
Window.Contains
func (w Window) Contains(at int64) bool
Contains reports whether at falls in w. Half-open: From is in, Until is out.
Window.Tiles
func (w Window) Tiles(next Window) bool
Tiles reports whether next begins exactly where w ends, so that the two cover [w.From, next.Until) with no overlap and no gap.
It exists as API rather than as a comment because the tiling property is what makes a sequence of period reports sum to the cumulative figures, and a caller generating a sequence should be able to assert it.
Window.Valid
func (w Window) Valid() bool
Valid reports whether w is a usable window: From non-negative and Until strictly after it. An empty or inverted window is not a period.
Functions
Derive
func Derive(chart []journal.Account, entries []journal.Entry, w Window) (Report, error)
Derive computes the period report.
It is a pure function of its three arguments: the same inputs always give the same Report, and nothing about the caller, the chain or the time enters it. On any error the returned Report is the zero value, so a caller that ignores the error reads zeros rather than a half-summed period.
Preconditions, each for a reason given in the package doc: w must be valid; chart must be strictly ascending by Code and no longer than MaxAccounts, with every Kind one of journal's five; entries must be strictly ascending by ID and no longer than MaxEntries, with at most journal.MaxPostingsPerEntry postings each.
Entries outside w are skipped, not refused — a caller passing the whole book is the expected case — and their postings are therefore NOT content-checked, because no figure in the report depends on them. ErrInvalidPosting and ErrUnknownAccount are scoped to the window for that reason; the postings-per-entry cap is not, since it is what makes MaxEntries' cost ceiling a bound rather than a hope.
MustDerive
func MustDerive(chart []journal.Account, entries []journal.Entry, w Window) Report
MustDerive is Derive and panics instead of returning an error. It is for a realm's mutating path, where an error return does not revert the transaction and only a panic does.
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 | errors, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- 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.
- 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.
Source
| Source file | onyx/p/periods/periods.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 |