README.md
gno.land/p/moul/pausable
The switch a realm checks before it acts: a parsed pause state, the rule for combining two of them, and the asserts that stop a call.
It holds no state and knows nothing about where a pause setting is stored.
r/moul/config is the realm that stores one and
wires this to it; this package is the part worth getting right once.
1st := pausable.MustParse(raw) // a stored value, failing closed
2st.AllowsRead() // false only when fully paused
3st.AllowsWrite() // false unless running
4st.AssertWritable() // aborts, with the reason if there is one
5st.Notice() // the markdown banner, or "" while running
6pausable.Strictest(a, b) // combine a global and a per-realm state
Three levels, because "paused" is usually too blunt
Taking a realm fully offline hides the thing people came to read. Most incidents only need the writes stopped: a board keeps rendering, an exchange keeps quoting, nothing new lands while the fix is prepared.
| level | value | reads | writes |
|---|---|---|---|
| Running | "" or running |
yes | yes |
| ReadOnly | readonly |
yes | no |
| Paused | paused |
no | no |
Each optionally followed by : <reason>, which shows up in the banner and in
the abort message: paused: migrating storage, back in an hour.
So a realm guards its mutating functions and leaves Render alone:
1func Post(cur realm, body string) {
2 config.AssertWritable() // aborts while ReadOnly or Paused
3 ...
4}
5
6func Render(path string) string {
7 return config.TopBlock() + body // the banner explains itself
8}
AssertReadable exists but most realms should not put it in Render. A page
that aborts tells a reader nothing; the banner tells them what happened and
when to come back. Reach for it only where serving stale data is itself the
harm.
It fails closed, and that is why the writer must validate
MustParse turns a value it does not recognise into Paused, not Running. A
pause switch that a typo silently disables is not a pause switch: the failure
would be invisible until the incident it was meant to cover.
The cost is that a typo takes a realm offline, so Parse reports ok=false
and the storing realm is expected to refuse the write. Validate on the way
in, fail closed on the way out. r/moul/config does exactly that, so in
practice the closed path is never reached.
The values this refuses are the ones someone would plausibly type: yes,
true, 1, on, stop, read-only. None of them may read as Running.
The stricter of two states wins
A global pause and a per-realm pause combine with Strictest, so a per-realm
entry left behind from last month can never re-open a realm during a global
halt.
The cost is that exempting one realm from a global pause is not expressible. Clear the global and set the others instead. That is the right trade for an emergency brake: the failure mode of the alternative is a stale exemption nobody remembers, discovered during the incident.
The reason travels with the level that won, and on a tie the first argument
does, so calling Strictest(global, scoped) keeps the global explanation when
both say the same thing.
Notice is one markdown block
The reason is text a manager typed, and it lands inside a blockquote where a
newline would end the quote and let the rest render as page content. Notice
folds it to a single line.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.