A restore point is taken before a module moves across a change of how it writes a kept data, so each declaration (the old sections and every entry in requires by template) names its form, 0 where none is said, and the catch-up tool a restore is followed by. KeptDeclarations reads them in the one walk KeptStates now uses; FormChanges is what differs between the running manifest and the one applied (design 47). Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
83 lines
3.5 KiB
Go
83 lines
3.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// stateName is one local name of a module's state: lower-case, no dot, no underscore.
|
|
var stateName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
|
|
|
// The mesh's caps on what a module may ask of a bucket's history.
|
|
const (
|
|
// StateMostHistory is the most past values a key may keep. The server's own limit.
|
|
StateMostHistory = 64
|
|
)
|
|
|
|
// StateDeclaration is one bucket a module owns: its local name, and the options that are the
|
|
// owner's to choose, as a seat chooses how long its backlog survives (design 32 §3).
|
|
type StateDeclaration struct {
|
|
Name string `json:"name"`
|
|
// History is how many values a key keeps, the current one included; zero is one.
|
|
History int `json:"history,omitempty"`
|
|
// TTLSeconds is how long a value lives once written; zero is until it is replaced or deleted.
|
|
TTLSeconds int `json:"ttl-seconds,omitempty"`
|
|
// PerMachine says the state is keyed by machine: each machine's instance writes the key named for
|
|
// its machine and no other, and the bus grants it that key alone (novox/hq ADR 0260).
|
|
PerMachine bool `json:"per-machine,omitempty"`
|
|
// KeyTTL says each key of the bucket lives the time its writer gives it, and the server writes a delete marker
|
|
// when it ends (novox/hq ADR 0433 §1): the bucket is made with the server's time to live per message.
|
|
KeyTTL bool `json:"key-ttl,omitempty"`
|
|
// ReadOnly says only the module's processes whose own bus.state names the state write it; the module's tools,
|
|
// the tool runner's account, read it and never write it (the lead's ruling, 2026-11-04: nats' warrant spend record
|
|
// `spent`). The judge's rule and the grants follow from it.
|
|
ReadOnly bool `json:"read-only,omitempty"`
|
|
// KeptLayout is the layout its holder says it writes: its form and its catch-up tool (design 47).
|
|
KeptLayout
|
|
}
|
|
|
|
// UnmarshalJSON reads a bucket as its bare name, or as {name, history, ttl-seconds, per-machine, key-ttl, read-only, form, catch-up}.
|
|
func (s *StateDeclaration) UnmarshalJSON(raw []byte) error {
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) > 0 && trimmed[0] == '"' {
|
|
return json.Unmarshal(trimmed, &s.Name)
|
|
}
|
|
type plain StateDeclaration
|
|
var full plain
|
|
dec := json.NewDecoder(bytes.NewReader(trimmed))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&full); err != nil {
|
|
return fmt.Errorf("a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl, read-only, form, catch-up}: %w", typedUnknown(err))
|
|
}
|
|
*s = StateDeclaration(full)
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON writes back the short form when there is nothing else to say.
|
|
func (s StateDeclaration) MarshalJSON() ([]byte, error) {
|
|
if s.History == 0 && s.TTLSeconds == 0 && !s.PerMachine && !s.KeyTTL && !s.ReadOnly && s.KeptLayout == (KeptLayout{}) {
|
|
return json.Marshal(s.Name)
|
|
}
|
|
type plain StateDeclaration
|
|
return json.Marshal(plain(s))
|
|
}
|
|
|
|
// ReadState splits a read into the owning module and the local name, or says why it is not one.
|
|
func ReadState(read string) (module, local string, err error) {
|
|
at := strings.LastIndex(read, ".")
|
|
if at <= 0 || at == len(read)-1 {
|
|
return "", "", fmt.Errorf("%q does not name a module and its state: a read is <module>.<name>", read)
|
|
}
|
|
module, local = read[:at], read[at+1:]
|
|
if !stateName.MatchString(module) {
|
|
return "", "", fmt.Errorf("%q cannot own state: a module whose state is read is one plain name", module)
|
|
}
|
|
if !stateName.MatchString(local) {
|
|
return "", "", fmt.Errorf("%q is not a state name: lower-case letters, digits and hyphens", local)
|
|
}
|
|
return module, local, nil
|
|
}
|