Package checkpoint remembers what a number used to be.
A Series is one value's history. It answers what that value held during a past epoch, which is the question governance asks: voting weight has to be read as of the moment a question was put, or it can be borrowed for the length of one transaction and voted twice.
It was written for a token's balances and its total supply, and there is nothing about it that knows what a token is. Anything a realm wants to look back at — a fee, a quorum, a member count — is a Series.
What it costs
One fact about gno shapes the whole thing: storage deposit is charged per OBJECT, on the DELTA of its encoded size (gnovm/pkg/gnolang/store.go, LastObjectSize, at 100ugnot/byte). A field added to a record that was going to be written anyway is paid for once, at creation, and costs nothing on every write after. On the EVM every extra word touched is a fresh SSTORE forever, which is the entire reason OpenZeppelin's ERC20Votes appends to an array per block — and the reason that shape does not have to be inherited.
So: two points inline, the rest paged into an archive. The overwhelmingly common query is answered without touching the archive at all, and a value that changes several times within one epoch coalesces into a single update of a record already being written.
Epochs, not blocks
The caller supplies the epoch and this package never asks what one is. That is deliberate — the clock is policy. Quantising to something coarser than a block is what makes the cost work, because a checkpoint per block maximises the one thing gno charges dearly for, a brand new key.
It also turns the anti-flash-loan property from a convention into an invariant, if the caller refuses to answer for the current epoch: a transaction cannot outlive its block and a block cannot outlive its epoch, so a borrowed balance cannot be voted and returned. This package cannot enforce that, because it does not know what time it is. It is the caller's half of the bargain and worth writing down where the caller will read it.