payslips
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips on onyx-1 testnet
realmapplicationpayrolllive on onyx-1
Publish payslips with withholding over a live payroll.
Takes the wage facts payroll has already recorded and shows gross pay, deductions and net pay without restating any of them.
In detail curated
Graduated-withholding payslip publication over the live r/payroll. An employer registers a withholding schedule with SetSchedule -- bracket edges in UGNOT OF GROSS and a rate per bracket in RateScale basis points -- then names employees to Publish; the realm reads every FACT from r/payroll (paid periods, per-period rate, due periods, end height), prices gross with p/tiers, and stores a frozen record: per-employee gross, withholding, the platform-fee interval, net, and a snapshot of the schedule that priced it. The house axis holds -- the caller supplies the SELECTION and the chain supplies the FACTS, so nothing passed to Publish can change a figure, only which employees appear in it. THE DESIGN AXIS IS THAT THE PLATFORM FEE IS AN INTERVAL AND NOT A POINT, AND THE INTERVAL IS THE EXACT REACHABLE SET RATHER THAN SLACK. r/payroll's Accrue settles every due period in ONE MustDeposit(employee, gross, AccrueFeeBps) and floors the fee on that AGGREGATE, so the fee actually realized depends on how the employer BATCHED their accruals -- and r/payroll exposes no view of its own batching history. A flat-rate reconstruction from cumulative gross is therefore derivable, plausible and WRONG for any batched employee, with nothing on chain to contradict it. So this realm publishes two-sided bounds instead: feeHi = floor(gross x bps / RateScale) (one settlement for all) and feeLo = paidPeriods x floor(perPeriod x bps / RateScale) (one period per settlement), tight because floor(x)+floor(y) <= floor(x+y) and floor(n.x) >= n.floor(x). BOTH ENDPOINTS ARE REALIZED ON CHAIN, not merely argued: payslip seq 2 priced a single-period gross of 3,000,050 where feeLo == feeHi == 30,000, a WIDTH-0 CONTROL proving the interval collapses when it should; seq 3 priced two periods of 6,000,100 at [60,000, 60,001], width 1; and the realized cumulative payroll.FeesAccrued() is 60,000 -- the LOW endpoint -- attained because the employer accrued period-by-period rather than batching. Which endpoint is realized is a fact about accrual batching that payslips cannot observe from paidPeriods alone, which is exactly why it reports a range. The fee's own arithmetic is reused rather than rewritten: payroll.AccrueFeeBps is reproduced by a ONE-TIER tiers.Schedule, legal because MaxTiers' minimum is 1, and sound because feeledger.BpsDenominator == tiers.RateScale == 10000 -- an equivalence the suite now ASSERTS two-sidedly over 13 amounts and 8 rates after a fresh-context challenge found the doc comment citing a test that did not exist (AL59). Withholding and the platform fee are reported in SEPARATE fields and never summed into a total deduction: different payees, different floors, and different certainties -- withholding is exact, the fee is an interval -- so a combined figure would inherit the weaker of each. Publish prices SETTLED gross (PaidPeriodsOf x PerPeriodOf), exact rather than approximate because payroll exports no setter for perPeriod and paidPeriods only increases; due-but-unaccrued periods are excluded and disclosed separately as UnsettledGrossOf. THE FAILURE MODE IS SILENT UNDER-REPORTING AND IT IS MADE VISIBLE RATHER THAN PREVENTED: Terminate -> Release -> Hire is legal in r/payroll and resets paidPeriods to 0, so pre-release gross becomes unreadable from any payroll view and a later payslip reports a figure smaller than lifetime gross, correct for the current position, and indistinguishable from a complete answer. So a record never claims lifetime gross, every record declares its own scope and its PublishedAt height, Coverage returns eight counts describing what the call actually did, and OmittedRoster -- roster size at publication minus employees priced -- is the one a reader checks first. The three refusal rules are deliberately asymmetric: an employee with no open position PANICS and fails the whole call (a silently dropped line is the one error this product cannot detect afterwards), a duplicate is dropped and COUNTED, and a zero-gross employee is PRICED NOT FILTERED because filtering would shrink the denominator and make the coverage line agree with the omission. This realm custodies NO coins: no Deposit, no Withdraw, no banker import, no coin balance, and refuseCoins fires on any envelope arriving with a user call. It also CANNOT WRITE to r/payroll, proven by construction rather than review -- a cross-realm write requires cross(...) at the call site and the source carries exactly one 'cross(' hit, inside a doc comment, so there are ZERO executable cross-realm call sites. The threat model is therefore ACCOUNTING, NOT CUSTODY, and the monetary-invariant class that governs every custodial realm in this portfolio does not apply.
Use it
A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.
Read it — no key, no account, no GNOT
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips.Employers()' \
--remote https://rpc.onyx.testnets.gno.land:443Employers is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.
Change its state
Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 2 crossing functions, with the argument names filled in.
Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.
Import it from another realm
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips"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
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 467,309 |
| Deploy transaction | 659d912fab3ea0bbf7d1e78044d778a911f8f7c867d700024b380d81925e2072 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 66,304,466 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml payslips.gno |
| Deployed bytes | payslips.gno — 49,936 bytes |
| sha256 | b711561c87a98cad0b53d8be4da9f406db75559730ac37a78c05377939061abc |
| Realm address | g1p02wgq6ynmc5c8k4pcadne427hje4mp8ykp80k |
Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips$download&file=payslips.gno' | shasum -a 256Expected: b711561c87a98cad0b53d8be4da9f406db75559730ac37a78c05377939061abc — 49,936 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 28 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
ChargeOnCountCoverageEmployersFeeBpsAtGrossOfHasHasScheduleHeightLineEndedLineNetRangeLinesMarginalRateOfNetRangeNumTiersOfOmittedRosterOverWithheldPayslipsPlatformFeeRangePublishPublishedAtRenderScheduleAtScheduleOfSetScheduleTierBreakdownUnsettledGrossOfWithheldOf
Overview
Package payslips publishes graduated withholding statements over the wage facts of a live r/payroll.
An employer sets a withholding schedule — a p/tiers bracket table — and publishes a payslip naming the employees it covers. For each one this realm reads payroll's own views, reconstructs the settled gross, prices it through the schedule, and records the result. Nothing is supplied by the caller except the schedule and the selection: every amount comes from payroll.
WHAT THIS REALM IS, AND WHAT IT CANNOT BE
It is a REPORTER. It holds no coins, has no withdrawal path, and moves nothing. It does not deduct the withholding it computes — payroll settles wages and this realm states what a graduated schedule would have withheld from them. The separation is deliberate and it is also forced: r/payroll is live and immutable, so a graduated rate cannot be charged inside it, and a realm that charged one here would be a second custody surface over coins payroll already holds.
The consequence is the realm's only real safety argument and it is a strong one: THIS REALM CANNOT WRITE A WRONG PAYMENT, BECAUSE IT CANNOT WRITE A PAYMENT. The failure mode it does have is a wrong or incomplete REPORT, and every mitigation below is aimed there.
SETTLED IS NOT EARNED
The gross on a payslip is the gross payroll has SETTLED for the position that is open right now:
gross = PaidPeriodsOf(employer, employee) * PerPeriodOf(employer, employee)
That identity is exact rather than approximate, and the reason is structural: payroll exports no setter for perPeriod, so a position's rate is fixed for its whole life by the Hire that created it, and paidPeriods only ever increases. The product is therefore the position's lifetime settled gross and not an estimate of it.
It is still not the employee's lifetime earnings under this employer, and the gap is not small. payroll's Release removes a settled position and frees its storage, and Hire refuses only while a position is OPEN — so Terminate → Release → Hire is a legal sequence that resets paidPeriods to zero and makes every period before it unreadable from any view. The Released event carries paidPeriods, but an event is not realm-readable. So:
A PAYSLIP IS SCOPED TO THE POSITION OPEN AT ITS OWN HEIGHT, AND MUST NEVER BE READ AS LIFETIME GROSS.
The failure mode is silent under-reporting, which is the one error a reader cannot detect from the number itself. The mitigations are that every payslip records its height, its own scope, and a coverage line; that Coverage and OmittedRoster say how much of the employer's roster the payslip left out; and that an employee named with no open position PANICS rather than being dropped — a silently-dropped line is exactly the under-report this realm cannot detect after the fact.
PERIODS THAT ARE DUE BUT NOT ACCRUED ARE EXCLUDED, AND DISCLOSED
payroll accrues on demand: anyone may call Accrue, and until someone does, completed periods are due and unsettled. A payslip published before an Accrue therefore reports a smaller gross than the employee is owed. Rather than guess, each line records dueGross — DuePeriods times perPeriod at the payslip's height — and UnsettledGrossOf totals it. A payslip whose UnsettledGrossOf is non-zero is complete about what was settled and explicit about what was not.
THE PLATFORM FEE IS AN INTERVAL, AND THAT IS A MEASUREMENT
payroll takes an accrual fee of AccrueFeeBps from each settlement, and the obvious reconstruction — a flat rate on the cumulative gross — is wrong. Its Accrue settles every due period in ONE deposit and floors the fee on that aggregate, so the fee actually taken depends on how the accruals were BATCHED, and payroll exports no view of the batching. What it does export is paidPeriods and perPeriod, and those bound the fee exactly:
feeLo = paidPeriods floor(perPeriod bps / RateScale) one period per call feeHi = floor(gross * bps / RateScale) one call for all
Both endpoints are achievable, so the interval is tight and not a hedge: floor(x) + floor(y) <= floor(x+y) gives the upper bound, and floor(n*x) >= n*floor(x) gives the lower. They coincide whenever perPeriod * bps divides RateScale evenly, which is the common case.
So PlatformFeeRange and NetRange return pairs. A single platform-fee number here would have been plausible, derivable, and wrong for any employee whose accruals were batched — and nothing on the chain would have contradicted it.
THE SCHEDULE PRICES GROSS, AND THE TWO DEDUCTIONS ARE NEVER COMBINED
The withholding schedule is applied to gross. payroll's platform fee is a separate deduction that payroll has already taken. They are reported side by side and never folded into one effective rate, because they have different payees, different arithmetic (graduated versus flat) and different certainty (exact versus an interval).
A schedule in p/tiers carries no identity and no effective date, and a falling rate array is legal there — it is a volume discount — so a transposed table is a silent authoring error that tiers.New cannot detect. Two consequences this realm acts on: every payslip SNAPSHOTS the schedule that priced it, so a later SetSchedule cannot retroactively re-price a published payslip; and OverWithheld names the one arithmetic outcome that is almost certainly an authoring error — a schedule withholding more than the employee actually received — rather than hiding it or refusing to publish it.
AUTHORITY
An employer publishes about their OWN roster: the employer of every payslip is cur.Previous().Address(), never an argument. This realm therefore cannot publish a payslip about a roster the caller does not hold, and a payslip's key is the employer that published it.
Imports
chainchain/runtimechain/runtime/unsafegno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/tiersgno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payrollstrconvstrings
Constants and variables
MaxEmployeesPerCall caps how many employees one Publish may name, and it is r/payroll's own per-employer roster cap taken by value.
That is the right bound and not an arbitrary one: payroll refuses a Hire past MaxRoster open positions, so an employer cannot have more than MaxRoster employees to report on, and a cap above it would admit a call that must contain a duplicate or an absent position. A cap below it would make some rosters unreportable in one call, which is the under-report this realm exists to avoid.
const MaxEmployeesPerCall = payroll.MaxRoster
MaxTiers is the widest schedule p/tiers will build, by value. A schedule has one rate per tier and one edge between each adjacent pair, so an employer may name at most MaxTiers rates and MaxTiers-1 edges.
const MaxTiers = tiers.MaxTiers
PayrollPath is the wage 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/receivables' InvoicesPath.
const PayrollPath = payroll.RealmPath
PayslipsAreUncapped records a decision by its absence: there is no limit on how many payslips one employer may publish. Each one is a fresh record and the storage deposit for it is paid by the caller at publish time, so the chain already prices the only resource at stake. A cap here would cause the one failure this realm cannot tolerate — an employer unable to publish the statement that would have disclosed an omission.
const PayslipsAreUncapped = true
PlatformFeeBps is the accrual fee r/payroll charges, taken by value from that realm's own constant.
Taking it by value is sound here for a reason worth stating rather than assuming: payroll is LIVE and a realm's bytes are immutable after addpkg, so that constant cannot change for the payroll this realm imports. A future payroll version would be a different path and a different import.
const PlatformFeeBps = payroll.AccrueFeeBps
RateScale is the unit of a withholding rate, re-exported by value from p/tiers: 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 RateScale = tiers.RateScale
RealmPath is this realm's own path, used to build Render links.
const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payslips"
RenderPayslipLimit is how many payslips an employer's Render page lists. Not a storage cap — see PayslipsAreUncapped.
const RenderPayslipLimit = 20
Functions
ChargeOn
func ChargeOn(employer address, gross int64) int64
ChargeOn quotes an employer's current schedule against an arbitrary gross without publishing anything. The quote a caller needs before choosing a schedule, and the one view here that is not about a record.
Count
func Count(employer address) int
Count is how many payslips an employer has published. Sequence numbers run 1..Count with no gaps: a payslip is never removed.
Coverage
func Coverage(employer address, seq int64) (listed, priced, zeroGross, withDue, ended, dupes, rosterAt, openAt int)
Coverage is the payslip's coverage line, and it is the number to read before any total.
listed employees the caller named, duplicates included priced lines in the record zeroGross priced lines whose settled gross is zero withDue priced lines with gross due and not accrued ended priced lines whose position's clock had stopped dupes duplicate names dropped rosterAt open positions the employer held at this height openAt open positions across every employer at this height
listed == priced + dupes always, by construction: nothing else is dropped. rosterAt - priced is OmittedRoster.
Employers
func Employers() int
Employers is how many addresses have set a schedule here. Note the tense: a schedule survives, so this is not a count of employers with payslips.
FeeBpsAt
func FeeBpsAt(employer address, seq int64) int64
FeeBpsAt is the platform fee rate this payslip was priced against.
GrossOf
func GrossOf(employer address, seq int64) int64
GrossOf is a payslip's total SETTLED gross. Read it beside UnsettledGrossOf and OmittedRoster: neither is an error, and both change what this number means.
Has
func Has(employer address, seq int64) bool
Has reports whether a payslip exists, so the record accessors can be pre-checked rather than recovered from. Every one of them panics on an absent record, which is deliberate — returning a zero figure for a payslip that was never published is the exact silent under-report this realm exists to refuse — but a panic is only a usable contract when the caller has a way not to hit it.
It is here before the first deploy rather than in a successor because a realm's bytes are frozen by addpkg: an accessor a consumer needs cannot be added afterwards.
HasSchedule
func HasSchedule(employer address) bool
HasSchedule reports whether an employer has set a schedule, without panicking on absence — every other schedule accessor panics, which makes them unusable as an existence probe.
Height
func Height() int64
Height is the current chain height, exposed so a caller can see the clock this realm reads payroll against.
LineEnded
func LineEnded(employer address, seq int64, i int) bool
LineEnded reports whether this line's position had been terminated or had passed its end height when the payslip was published. An ended position can still carry unsettled gross: payroll keeps the obligation alive past Terminate on purpose.
LineNetRange
func LineNetRange(employer address, seq int64, i int) (lo, hi int64)
LineNetRange bounds what this employee kept. Same interval as NetRange, one line.
Lines
func Lines(employer address, seq int64) int
Lines is how many employees a payslip prices.
MarginalRateOf
func MarginalRateOf(employer address, gross int64) int64
MarginalRateOf is the rate an employer's current schedule would charge on the next ugnot of gross past the given amount, of RateScale.
NetRange
func NetRange(employer address, seq int64) (lo, hi int64)
NetRange bounds what the employee kept: gross, less the schedule's withholding, less payroll's fee. The interval is inherited whole from PlatformFeeRange — the withholding itself is exact.
IT CAN BE NEGATIVE, and that is a finding rather than a bug. A schedule may legally charge up to 100%, and payroll's fee is taken on top of whatever this realm withholds, so a schedule at or near the ceiling withholds more than the employee actually received. OverWithheld names that case.
NumTiersOf
func NumTiersOf(employer address) int
NumTiersOf is how many brackets an employer's current schedule has.
OmittedRoster
func OmittedRoster(employer address, seq int64) int
OmittedRoster is how many of the employer's open positions this payslip did not price. It is the one number that says whether a payslip is complete, and a total read without it is being read wrongly.
It cannot be negative: every priced line has an open position under this employer at the payslip's height, duplicates are dropped before pricing, so priced is a count of distinct open positions and rosterAt is their total.
OverWithheld
func OverWithheld(employer address, seq int64) bool
OverWithheld reports whether this payslip's withholding plus payroll's largest possible fee exceeds the gross. True is almost always a transposed or mis-scaled rate array, which p/tiers cannot detect, so this realm surfaces it instead of refusing to publish it — a refusal would lose the evidence.
Payslips
func Payslips() int
Payslips is how many payslips exist across every employer.
PlatformFeeRange
func PlatformFeeRange(employer address, seq int64) (lo, hi int64)
PlatformFeeRange bounds the accrual fee r/payroll already took on this gross. It is an interval because the fee depends on accrual batching, which payroll does not expose — see the package doc. lo == hi is common and means the batching could not have mattered.
Publish
func Publish(cur realm, employees string) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPublish(cross(cur), ...).
Publish prices the named employees under the caller's own roster and records a payslip. It returns the payslip's sequence number, which is per-employer and starts at 1.
employees is a comma-separated list of addresses, at most MaxEmployeesPerCall of them. Order is the caller's and is preserved in the record, so a payslip reads in the order it was requested.
THREE REFUSAL RULES, AND THE ASYMMETRY BETWEEN THEM IS THE POINT:
- An employee with NO OPEN POSITION under the caller PANICS and fails the whole call. A silently-dropped line is an under-report, and under- reporting is the one error this product cannot detect after the fact. A caller that wants to skip an absent position must leave it out, which is a decision in the transaction rather than a silence in the record.
- A DUPLICATE is dropped, counted in the coverage line, and does not fail the call. Dropping it cannot under-report: the employee is still priced once, and double-counting them would be worse.
- An employee with ZERO SETTLED GROSS is PRICED, not filtered. A payslip for a position that has settled nothing yet is a correct payslip with a gross of zero, and filtering it would turn a true statement into a missing line.
An empty list is refused. Nothing here filters a list down to nothing, so after deduplication a call always prices at least one employee.
PublishedAt
func PublishedAt(employer address, seq int64) int64
PublishedAt is the height a payslip was published at, which is the height every figure in it was read at.
Render
func Render(path string) string
Render serves the index at "", an employer's page at "<address>", and one payslip at "<address>/<seq>".
Every caller-supplied string reaching the output goes through the sanitizer. This realm stores no free text, so the untrusted bytes are the path itself and the addresses in a record — and that is still the one injection-shaped surface a realm with no custody has.
ScheduleAt
func ScheduleAt(employer address, seq int64) (edges, rates string)
ScheduleAt is the schedule that priced this payslip, as comma-separated lists that round-trip exactly through SetSchedule. The snapshot, not the employer's current schedule — see ScheduleOf for that.
ScheduleOf
func ScheduleOf(employer address) (edges, rates string)
ScheduleOf is an employer's CURRENT schedule, as lists that round-trip through SetSchedule. It is not what priced any published payslip — see ScheduleAt.
SetSchedule
func SetSchedule(cur realm, edges, rates string) int
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetSchedule(cross(cur), ...).
SetSchedule installs the caller's own withholding schedule and returns its tier count.
edges and rates are comma-separated int64 lists in p/tiers's units: rates are of RateScale, edges are gross amounts in ugnot. There is one more rate than there are edges, because the last tier is unbounded. An EMPTY edges string is legal and means a one-tier schedule — a flat rate — which is the shape to use for a flat withholding and the shape this realm uses internally for the platform fee.
SetSchedule(cross(cur), "1000000,5000000", "500,1500,2500")
is 5% on the first 1,000,000 ugnot of gross, 15% on the next 4,000,000, and 25% above 5,000,000.
REPLACING A SCHEDULE DOES NOT RE-PRICE ANYTHING. Every published payslip carries the schedule that priced it, so this only affects payslips published after it. That is a design requirement rather than a convenience: a published statement whose numbers change under it is not a record.
A FALLING RATE ARRAY IS ACCEPTED, because p/tiers accepts one — it is a volume discount, which is a real schedule. The consequence is that a transposed array is a silent authoring error here, and neither this realm nor tiers can detect it. Read back ScheduleOf after setting one.
TierBreakdown
func TierBreakdown(employer address, seq int64, i int) string
TierBreakdown is the withholding on one line split by bracket, as a comma-separated list in tier order — the column a payslip reader actually wants, because it shows which brackets the gross reached.
It is recomputed from the payslip's own schedule snapshot rather than stored, so it is exact for the schedule that priced the line and costs no storage. It cross-foots the column against the stored total before returning it: p/tiers DOCUMENTS that Charge is defined as the sum of ChargeByTier, and a documented identity across a package boundary is worth one loop to assert.
UnsettledGrossOf
func UnsettledGrossOf(employer address, seq int64) int64
UnsettledGrossOf is gross that had completed and not been accrued at the payslip's height, and is therefore EXCLUDED from GrossOf. Non-zero is normal: payroll accrues on demand.
WithheldOf
func WithheldOf(employer address, seq int64) int64
WithheldOf is the schedule's total charge on the gross.
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/tiers, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payroll, strconv, strings |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/tiers, gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/payroll |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- THE PLATFORM FEE IS AN INTERVAL AND NEVER A POINT unless the endpoints coincide, and the realized endpoint is UNRECOVERABLE from this realm. A caller needing the exact fee must read payroll.FeesAccrued() deltas around their own accruals; r/payroll exposes no batching history, so nothing here can narrow [feeLo, feeHi] after the fact. Measured live on this deployment: the cumulative realized fee is 60,000 = feeLo, attained by period-by-period accrual, while a batched accrual of the same 6,000,100 gross would have realized 60,001 = feeHi. The two are indistinguishable from paidPeriods alone.
- SETTLED ONLY, and a payslip can therefore SILENTLY UNDER-REPORT. Terminate -> Release -> Hire resets payroll's paidPeriods to 0, so pre-release gross is unreadable from any payroll view and no view here reports lifetime gross. The mitigation is disclosure, not prevention: every record carries its PublishedAt height, an eight-count Coverage line and OmittedRoster, from which a reader computes the shortfall. A reader who quotes a gross figure without its coverage line has quoted an unbounded subset.
- NetRange CAN BE NEGATIVE. A schedule whose rates exceed 100% of a bracket is legal in p/tiers; this realm reports the consequence rather than refusing the schedule, so a negative net is a finding about the employer's registered schedule and not a defect here.
- A RECORD IS FROZEN -- no amend, no delete. A wrong selection is corrected by publishing again and both records stay readable, which is deliberate: a record that can be revised is not evidence. The consequence is that an under-reporting payslip cannot be withdrawn, only superseded.
- NO WITHHOLDING IS REMITTED. This realm computes a figure; it does not pay a tax authority and has no path to. The employer carries the number into their own books.
- storage_bytes and storage_deposit are null for the reason in storage_measurement_note -- the figures ARE chain-attested (50,678 B / 5,067,800ugnot at height 467313) and are withheld from these fields only because verify_catalog C2's one-block window cannot see the approver's later event. Do not read the nulls as an unmeasured deploy.
Source
| Source file | onyx/r/payslips/payslips.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 |