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

v0 source pure

Package pausable is the switch a realm checks before it acts: a parsed pause state, the rule for combining two of the...

Readme View source

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.

Overview

Package pausable is 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.

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, and nothing new lands while the fix is prepared. So the levels are Running, ReadOnly and Paused, and a realm normally guards its mutating functions with AssertWritable and leaves Render alone.

Example
1func Post(cur realm, body string) {
2	config.AssertWritable()  // aborts while the realm is ReadOnly or Paused
34}
5
6func Render(path string) string {
7	return config.TopBlock() + body  // the banner explains itself
8}

It fails closed

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, and the failure is loud and one transaction from fixed, where the opposite failure is silent and discovered during the incident it was meant to cover.

The cost of that choice is a typo taking a realm offline, so the writer is expected to validate: Parse reports ok=false and the storing realm refuses the write. Validate on the way in, fail closed on the way out.

The stricter of two states wins

A global pause and a per-realm pause combine with Strictest, so a stale per-realm entry 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.

Constants 1

const Running, ReadOnly, Paused

1const (
2	// Running is the normal state, and what an unset value means.
3	Running Level = iota
4	// ReadOnly still renders and still answers queries; it refuses writes.
5	ReadOnly
6	// Paused refuses everything, reads included.
7	Paused
8)
source

Functions 3

func MustParse

1func MustParse(raw string) State
source

MustParse is Parse for a reader, failing closed: a value it cannot read becomes Paused, carrying the raw text as the reason so whoever hits it can see what is wrong.

func Parse

1func Parse(raw string) (State, bool)
source

Parse reads a stored value: "", "running", "readonly", "paused", each optionally followed by ": <reason>".

ok is false for anything else. A caller that is STORING the value should refuse on !ok; a caller that is READING one should use MustParse, which fails closed instead.

func Strictest

1func Strictest(a, b State) State
source

Strictest returns whichever of the two stops more, keeping the reason that belongs to the level it returns. On a tie the first argument wins, so a caller passing (global, scoped) keeps the global explanation when both say the same thing, and the scoped one when it is the stricter.

Types 2

type Level

ident
1type Level int
source

Level is how much of a realm is still allowed to work.

Methods on Level

func AllowsRead

method on Level
1func (l Level) AllowsRead() bool
source

AllowsRead reports whether rendering and querying are still allowed.

func AllowsWrite

method on Level
1func (l Level) AllowsWrite() bool
source

AllowsWrite reports whether state-changing calls are still allowed.

func String

method on Level
1func (l Level) String() string
source

type State

struct
1type State struct {
2	Level  Level
3	Reason string
4}
source

State is a parsed pause setting: a level and an optional reason to show.

The zero State is Running with no reason, which is what an unset setting means, so a realm that never configures anything is never paused.

Methods on State

func AllowsRead

method on State
1func (s State) AllowsRead() bool
source

AllowsRead reports whether rendering and querying are still allowed.

func AllowsWrite

method on State
1func (s State) AllowsWrite() bool
source

AllowsWrite reports whether state-changing calls are still allowed.

func AssertReadable

method on State
1func (s State) AssertReadable()
source

AssertReadable aborts unless reads are allowed. Most realms do not want this in Render: a page that aborts tells a reader nothing, while Notice tells them what happened and when to come back.

func AssertWritable

method on State
1func (s State) AssertWritable()
source

AssertWritable aborts unless writes are allowed. This is the one a realm puts at the top of every state-changing function.

func IsPaused

method on State
1func (s State) IsPaused() bool
source

IsPaused reports whether anything at all is being held back. It is true for ReadOnly as well as Paused, because the question a caller usually means by "is it paused" is "is it behaving normally".

func Notice

method on State
1func (s State) Notice() string
source

Notice is the banner a Render puts above its content, or "" while running.

It is a markdown blockquote, so it reads as set apart from the page without needing any style the renderer might not have.

func String

method on State
1func (s State) String() string
source

String renders a State back into a storable value. It round-trips through Parse, and the zero State renders empty so an unset setting stays unset.

Imports 1

  • strings stdlib

Source Files 4