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

README.md

3.88 Kb · 96 lines

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.