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

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

receivables source realm

Package receivables publishes a receivables aging report and the resulting allowance for credit losses over invoices ...

Overview

Package receivables publishes a receivables aging report and the resulting allowance for credit losses over invoices held in r/invoices.

An issuer registers an aging schedule — age bands in BLOCKS and a loss rate per band — and then names the invoice ids to age. This realm reads every FACT from the invoice realm (amount, amount paid, due height, status) and prices the result with p/aging. The published report is frozen: the per-bucket unpaid totals, the per-bucket provision, the grand provision, and a snapshot of the schedule that priced it.

The caller supplies the selection, the chain supplies the facts

This is the central design constraint and it is a measured one, not a preference. r/invoices exports no per-issuer id enumerator — its internal `Iterate` is unexported and nothing above it can walk an issuer's records — so a realm here CANNOT derive "every open invoice" on its own. It therefore takes the id list from the caller.

The consequence is stated rather than hidden: **a report can under-report**. An issuer who omits an overdue invoice from the list gets a provision that is too small, and no check here can see the omission. What this realm does instead is publish a COVERAGE LINE with every report — how many ids were listed, how many were aged, how many were filtered and why, and how many open invoices and stored records the issuer held at that height — so a reader can compute the shortfall themselves. See Coverage.

Under-reporting is made VISIBLE here. It is not prevented, and a reader who treats a published provision as complete without reading the coverage line is reading it wrongly.

Ages are in BLOCKS

r/invoices stores `dueHeight` as a block height, so an age is a count of blocks and every schedule edge is a count of blocks. A "30/60/90 day" schedule is a conversion the ISSUER owns and this realm never performs: nothing here knows the block time, and compiling a guess in would make every published rate wrong by whatever the guess was off by.

This realm writes no ledger entry

A provision is an accounting entry — debit a credit-loss expense, credit an allowance — and this realm deliberately does not post it. r/books derives the owner of a book structurally from its caller, so a realm that called `PostMulti` would post into ITS OWN book and not the issuer's. There is no owner parameter to pass and no delegation path to use.

So ProposedEntry returns the entry as a string in the form r/books' `PostMulti` accepts, and the issuer posts it themselves from their own account. The realm stays keyless with respect to every ledger, which is the stronger position: it cannot write a wrong entry because it cannot write.

Custody

None. This realm holds no coins, has no withdrawal path, and refuses a user call that arrives carrying any. Nothing here moves value; a provision is a statement about value, which is not the same thing.

Limitations

  • **The report is a self-report of a self-report.** r/invoices' amounts are whatever an issuer typed; an aging of them inherits that entirely and then looks like an audited artifact. No audit happens here.
  • **It can under-report, by construction.** See above and Coverage.
  • **It cannot over-report an unlisted invoice**, because every fact comes from the chain — but it CAN over-report a *listed* one whose amount the issuer inflated in r/invoices in the first place.
  • **No write-off.** p/aging prices an expected loss and never removes an obligation; a receivable this report provisions at 100% is still a live invoice in r/invoices. Voiding is that realm's operation, not this one's.
  • **A frozen report does not track the invoices.** A payment landing after publication leaves the published figures untouched, which is what "frozen" means. There is no `Verify` here: re-deriving would need the id list, which is not stored (see Publish).
  • **Buckets are floored per bucket**, so the per-bucket column sums to the grand provision exactly and both are at or below the unrounded figure. p/aging's package doc carries the counterexample.
  • **No cross-issuer aggregate.** There is no enumerator and no total provision across issuers; such a figure would sum strangers' assertions.

r/invoices: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/invoices r/books: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books p/aging: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging

Constants 6

const InvoicesPath

1const InvoicesPath = invoices.RealmPath
source

InvoicesPath is the receivables source every figure here is read from.

It is taken BY VALUE from that realm's own exported constant rather than typed out again, so this is the compile-time pin itself and not a second copy that could drift. The convention is r/statements' BooksPath.

const MaxIDsPerCall

1const MaxIDsPerCall = invoices.MaxRecordsPerIssuer
source

MaxIDsPerCall caps how many invoice ids one Publish may name, and it is r/invoices' own per-issuer storage cap taken by value.

That cap is the right bound and it is worth saying why, because the two candidate bounds differ by a factor of sixteen. p/aging accepts up to `aging.MaxObligations` (1024) obligations and offers a chunking protocol for more; r/invoices stores at most `MaxRecordsPerIssuer` (64) records of any status per issuer. An issuer therefore cannot have more than 64 ageable invoices in existence at one height, so **this product never chunks**, and p/aging's documented chunk-flooring error — where the sum of per-chunk provisions is not the provision of the whole — cannot arise here. The two caps were chosen independently and it is their inequality, not any code below, that makes the composition exact.

const RateScale

1const RateScale = aging.RateScale
source

RateScale is the unit of a loss rate, re-exported by value from p/aging: a rate of 2500 is 25%. Re-exported because every rate in this realm's API is in these units and a caller should not have to find the other package to learn the denominator.

const RealmPath

1const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables"
source

RealmPath is this realm's own path, used to build Render links.

const RenderReportLimit

1const RenderReportLimit = 20
source

RenderReportLimit caps how many of an issuer's reports one Render call lists, which is what keeps an issuer's page cost flat while histories are unbounded.

const ReportsAreUncapped

1const ReportsAreUncapped = true
source

ReportsAreUncapped records a decision by its absence, because every other realm in this portfolio caps its per-owner state and a reader will look for the cap here.

There is none, for r/statements' reasons, which transfer unchanged: keys are per issuer so an issuer can fill only their own history; nothing iterates a history except Render, which reads the newest RenderReportLimit by direct key lookup; and there is no engine cap above this realm to mirror. Each publication costs its own gas and storage deposit.

There is also no retraction, and that is the same decision from the other side. A `Retract` would split the sequence allocator from the live count — two numbers that can disagree — in exchange for letting an issuer delete a report they regret. A frozen record that can be deleted is not frozen, and the realm has no cap an issuer could be bricked against.

Functions 22

func BucketAt

Action
1func BucketAt(issuer address, seq int64, i int) (lo, hi int64, unbounded bool, rate, unpaid, provision int64)
source

BucketAt returns one line item of a frozen report, youngest band first.

`lo` and `hi` are the band's age range in BLOCKS, half-open [lo, hi); when `unbounded` is true the band has no upper edge and `hi` is meaningless. An age equal to an edge belongs to the OLDER band. `rate` is in RateScale units, `unpaid` is the outstanding total that landed in the band, and `provision` is `floor(unpaid × rate / RateScale)`.

func BucketCount

Action
1func BucketCount(issuer address, seq int64) int
source

BucketCount is how many age bands a frozen report has, which is the bucket count of the schedule that priced it and may differ from the issuer's current schedule.

func Buckets

Action
1func Buckets(issuer address) int
source

Buckets is how many age bands an issuer's current schedule has.

func Count

Action
1func Count(issuer address) int
source

Count is how many reports an issuer has published. Zero for an address that never has, which is not an error.

func Coverage

Action
1func Coverage(issuer address, seq int64) (listed, aged, notOpen, zeroOut, notDue, dupes, openAt, recordsAt int)
source

Coverage is the part of a report a reader must not skip.

This realm cannot enumerate an issuer's invoices (see the package doc), so a report covers exactly the ids the issuer named. These eight numbers are what let a reader judge whether that was everything:

  • `listed` — distinct ids in the call, after deduplication.
  • `aged` — ids that became obligations in the report. The provision covers these and nothing else.
  • `notOpen` — settled or voided, so nothing owed.
  • `zeroOut` — open with nothing outstanding.
  • `notDue` — open and owed, but not yet due at `PublishedAt`.
  • `dupes` — repeated ids dropped before aging.
  • `openAt` — `invoices.OpenOf(issuer)` at publication: how many open invoices the issuer actually held.
  • `recordsAt` — `invoices.RecordsOf(issuer)` at publication: records of any status, which is the figure r/invoices' per-issuer cap applies to.

The arithmetic to do: `aged + zeroOut + notDue` is how many OPEN invoices the report accounted for, so `openAt` minus that is **how many open invoices were left out**. A non-zero remainder means the provision is understated by an unknown amount, and no part of this realm can say by how much.

`listed = aged + notOpen + zeroOut + notDue` always holds; `dupes` is outside it, having been dropped before the filter.

ACCOUNTED FOR IS NOT THE SAME AS AT RISK

`notDue` counts toward "accounted for" and carries NO provision. That is right on its own terms — the invoice was examined, and p/aging deliberately refuses to bucket something that is not yet due — but it makes one reading wrong in a way the arithmetic above will not catch. A reader who takes `aged` (or the provision) as "the receivables at risk" while `notDue` is non-zero has measured a STRICT SUBSET of the open book: the omitted part is not omitted by mistake, it is merely not due yet, and it becomes due later with no further call to this realm.

So `OmittedOpen == 0` means "every open invoice was examined", never "every open invoice is provisioned". The second question is answered by reading `notDue` beside the provision, and the render pages print both for exactly this reason.

func Has

Action
1func Has(issuer address, seq int64) bool
source

Has reports whether a report exists, so a reader can distinguish it from the panics the accessors below raise for an unknown one.

func HasSchedule

Action
1func HasSchedule(issuer address) bool
source

HasSchedule reports whether an issuer has registered a schedule, so a reader can distinguish that from the panic ScheduleOf raises.

func Height

Action
1func Height() int64
source

Height is the current chain height, which is the `now` a report published in this transaction would be aged against.

func Latest

Action
1func Latest(issuer address) (int64, bool)
source

Latest is an issuer's highest sequence number, and whether they have published at all. The second result matters: sequence numbers start at 1, so a zero first result means "never published" and never "report 0".

func OmittedOpen

Action
1func OmittedOpen(issuer address, seq int64) int
source

OmittedOpen is the coverage arithmetic as one number: how many of the issuer's open invoices the report did not account for, at the height it was published.

Zero is the answer a complete report gives. A positive answer means the provision is understated. A NEGATIVE answer is possible and is not a bug here: `openAt` is read after the loop, so an invoice settled or voided by a transaction between — impossible within one transaction, but the figure is a snapshot either way — would make the accounted set larger than the open count. It is returned signed rather than clamped, because a clamp would turn an inconsistency into a clean zero.

func ProposedEntry

Action
1func ProposedEntry(issuer address, seq int64, expenseCode, allowanceCode string) string
source

ProposedEntry returns the double entry that records a report's provision, in the `code:side:amount` form r/books' PostMulti accepts — and this realm does NOT post it.

It cannot. r/books derives a book's owner structurally from its caller, so a posting made from here would land in this realm's own book rather than the issuer's, and there is no owner parameter and no delegation path. The issuer posts this string themselves, from their own account, into their own book. That keeps this realm keyless with respect to every ledger: it cannot write a wrong entry because it cannot write.

The entry debits `expenseCode` and credits `allowanceCode` for the whole provision:

Example
1ProposedEntry(issuer, seq, "E-BADDEBT", "A-ALLOW")
2  → "E-BADDEBT:D:1250,A-ALLOW:C:1250"

Both codes go through [validCode], an ASCII whitelist, and the reason is the format itself: a code containing `:` or `,` would inject extra legs into a string this realm presents as its own arithmetic. Whether a code EXISTS, or is the right kind, in any ledger is not checked and cannot be — this realm reads no ledger.

A zero provision is refused rather than returned as a balanced pair of zeros, which would be a storage deposit spent to post nothing.

It is a PROPOSAL in the full sense: nothing here records that it was posted, posting it twice double-counts, and the allowance it credits is a contra asset whose previous balance this realm cannot see. An issuer provisioning monthly posts the DELTA, not this figure, and working that out is theirs.

func Provision

Action
1func Provision(issuer address, seq int64) int64
source

Provision is the allowance for credit losses a report concluded: the sum of the per-bucket column, each bucket floored.

func Publish

crossing Action
1func Publish(cur realm, ids string) int64
source

Publish freezes an aging report over the named invoices at the current height and returns its sequence number, which starts at 1 for each issuer.

`ids` is a comma-separated list of the caller's own invoice ids in r/invoices, at most MaxIDsPerCall of them. The caller must have registered a schedule first.

What each id is checked for

An id with no record for this issuer PANICS and fails the whole call. That is deliberate: a silently-dropped id is an under-report, and under-reporting is the one error this product cannot detect after the fact. The existence probe is a separate `HasInvoice` call rather than letting the next read panic, because r/invoices' own message is `"unknown invoice"` and does not say WHICH — unhelpful in a list of sixty-four.

An id that exists but is not ageable is filtered, counted, and reported:

  • not `open` — settled or voided, so nothing is owed. Counted `notOpen`.
  • open with nothing outstanding. Counted `zeroOut`.
  • open, owed, but `dueHeight > now`. Counted `notDue`.

The third one is not an optimisation. `aging.ReportAt` requires `DueAt <= now` for EVERY obligation and fails the whole call on the first that is not, so a report over a list containing one current invoice would produce nothing at all. Pre-filtering here is what makes "age everything I have" a usable call, and the count is what stops the filter being invisible.

The outstanding cross-check

`amount - paid` is computed here and compared against r/invoices' `Outstanding`. State its reach exactly, because the flattering reading is available: both sides are that realm's own two numbers, so this is a CONSTANT TRUE against any disagreement in ITS arithmetic and must never be quoted as cross-checking it. What it catches is a transcription error in THIS realm — reading `paid` where `amount` belongs in a six-value return, or subtracting the wrong way round — which is the one place in this path such an error could happen, and an inflated outstanding is an over-provision that still cross-foots.

The id list is not stored

A report keeps the figures and the coverage counts, not the ids. Storing 64 ids per report would roughly double the record for the sake of a `Verify` that could not mean much anyway: the invoices keep moving, so re-deriving would answer "do the figures still hold at today's height" and not "was this report right when made". r/statements documents the same distinction about its own Verify. The honest artifact is the frozen figure plus the coverage line.

func PublishedAt

Action
1func PublishedAt(issuer address, seq int64) int64
source

PublishedAt is the chain height a report was frozen at. It is also the `now` every age in it was measured against.

func Render

1func Render(path string) string
source

Render serves the index at "", an issuer's page at "<address>", and one report at "<address>/<seq>".

Every caller-supplied string reaching the output goes through the sanitizer. The surface is narrower here than in the sibling realms — this realm stores no free text, so the only untrusted bytes are the path itself — and it is still the one injection-shaped surface a realm with no custody has.

func ScheduleOf

Action
1func ScheduleOf(issuer address) (edges, rates string)
source

ScheduleOf returns an issuer's CURRENT schedule in the comma-separated form SetSchedule accepts, so the round trip is exact.

This is the schedule that would price the issuer's NEXT report, not necessarily the one that priced their last. ScheduleUsed answers that.

func ScheduleUsed

Action
1func ScheduleUsed(issuer address, seq int64) (edges, rates string)
source

ScheduleUsed returns the schedule that PRICED a frozen report, in the comma-separated form SetSchedule accepts.

This exists because p/aging warns that a report carries no reference to its schedule and two schedules of the same bucket count will silently accept each other's reports. Here the reference is stored, so a reader can see the rates that produced the figure rather than inferring them from whatever the issuer has registered today.

func SetSchedule

crossing Action
1func SetSchedule(cur realm, edges, rates string) int
source

SetSchedule registers or replaces the caller's own aging schedule and returns its bucket count.

`edges` is a comma-separated list of block ages, positive and strictly increasing; `rates` is one more number than there are edges, each in [0, RateScale] and non-decreasing. So

Example
1SetSchedule("216000,432000,648000", "0,1000,2500,5000")

reads as: nothing provisioned under 216,000 blocks past due, 10% from there to 432,000, 25% to 648,000, 50% beyond. Every constraint is p/aging's and is validated by `aging.New` rather than re-checked here; a second copy of somebody else's validation is a second place for it to drift.

The owner is `cur.Previous().Address()` and there is no owner parameter, so authorization is structural rather than a check: there is no address to forge and no way to set a schedule for somebody else.

REPLACEMENT IS SAFE and that is a property of [report], not of this function: every published report snapshots the schedule that priced it, so a new schedule applies only to reports published after it. Nothing already frozen is re-priced.

func Source

Action
1func Source() string
source

Source is the realm every figure here is read from, by value from its own exported constant.

func Total

Action
1func Total() int
source

Total is how many reports exist across every issuer. It is a count and nothing more; see the package doc on why there is no cross-issuer provision.

func Totals

Action
1func Totals(issuer address, seq int64) (unpaid, provision int64, aged int)
source

Totals returns a report's headline figures together, which is the shape a reader wants and the shape that cannot be assembled from two reads at different heights.

func Unpaid

Action
1func Unpaid(issuer address, seq int64) int64
source

Unpaid is the total outstanding the report aged. It is the denominator the provision is a fraction of, and it covers ONLY the aged invoices — not the issuer's whole receivable book. See Coverage.

Dependencies

Source Files 2