Name the storage types, templates, options and ledger effects, and give a ledger its types (issue 1966 S2)

The words of design 25 § 12.1 and ADR 0597 §1-2 as constants, and a
ledger's types (schema, operations each with its effect, indexes) on the
bus-state entry, so the judge, the bus module and the tool runner read one
contract. No bound and no rule: those are the judge's (ADR 0578 §7).
This commit is contained in:
2026-11-03 13:20:35 +01:00
parent 779ad9ac10
commit 0bec849c40
3 changed files with 132 additions and 1 deletions
+111
View File
@@ -13,6 +13,115 @@ import (
// leniently. Every rule that judges an entry (a template known, an option within its bound, a fixed setting not given)
// is the indexer's judge's.
// The storage types (design 25 § 12.1): the stream is the base; the key-value bucket, the object store and the ledger
// are built on it, each with calls of its own (ADR 0595 §2).
const (
StorageStream = "stream"
StorageKeyValue = "key-value"
StorageObjectStore = "object-store"
StorageLedger = "ledger"
)
// The six templates, each a storage type with a predefined configuration (ADR 0595 §2, design 25 § 12.1): state on
// key-value, events, work queue and log on stream, objects on object store, ledger on ledger. A bare stream names
// none and gives its own Configuration.
const (
TemplateState = "state"
TemplateEvents = "events"
TemplateWorkQueue = "work-queue"
TemplateLog = "log"
TemplateObjects = "objects"
TemplateLedger = "ledger"
)
// The option names, a template's settings a module may change (design 25 § 12.1's table), written as the old sections
// wrote them, so a module that moves carries its old settings as options unchanged (ADR 0595 §11). Which option a
// template takes, and its bound, is the judge's.
const (
// OptionPastValues is how many past values a state's key keeps (the old `state`'s `history`).
OptionPastValues = "history"
// OptionTimeToLive is how long a state's value lives, in seconds.
OptionTimeToLive = "ttl-seconds"
// OptionLifetimePerKey is whether a state's key may be given a lifetime of its own.
OptionLifetimePerKey = "key-ttl"
// OptionPerMachine is whether a state is kept one bucket per machine.
OptionPerMachine = "per-machine"
// OptionAgeDays is how long a stream keeps a message, in days.
OptionAgeDays = "max-age-days"
// OptionCapMiB is the cap of a stream, a bucket or an object store, in MiB.
OptionCapMiB = "max-mib"
// OptionGrant names the trusted config value granting another module a part of it (ADR 0363 §4, ADR 0592 §9).
OptionGrant = "grant"
// OptionDeliveries is how many times a work queue's message is delivered before it is a dead letter.
OptionDeliveries = "max-deliveries"
// OptionDuplicates is how long, in seconds, a work queue detects a message sent twice.
OptionDuplicates = "duplicates"
)
// ConfigWhenFull is what a configuration of its own says a bare stream does when full: WhenFullRefuse or
// WhenFullDropOldest. In a template it is fixed, never an option (ADR 0595 §3).
const (
ConfigWhenFull = "when-full"
WhenFullRefuse = "refuse"
WhenFullDropOldest = "drop-oldest"
)
// The ledger effects, what an operation does to an instance's state (ADR 0597 §2): one of this fixed set.
const (
// EffectSetFields sets the state's fields to the operation's Fields.
EffectSetFields = "set-fields"
// EffectSetEntry sets one entry of the state's map Into, under the key the operation's field Key holds, to its Fields.
EffectSetEntry = "set-entry"
// EffectAppend appends the operation's Fields to the state's list Into.
EffectAppend = "append"
// EffectRemove removes the entry of the map Into under the operation's field Key, or, naming no Into, the instance's
// state.
EffectRemove = "remove"
)
// LedgerType is one type of a ledger (ADR 0597 §1): a schema and the operations allowed on it, with its indexes. An
// instance of it is named by a path whose first part is the type's name (`decision/0290`).
type LedgerType struct {
Name string `json:"name"`
// Schema is the type's fields, by name.
Schema map[string]LedgerField `json:"schema"`
// Operations are the operations allowed on an instance, each with its effect.
Operations []LedgerOperation `json:"operations"`
// Indexes are its declared indexes, each a second key-value bucket kept by a watcher (ADR 0597 §4).
Indexes []LedgerIndex `json:"indexes,omitempty"`
}
// LedgerField is one field of a ledger type's schema.
type LedgerField struct {
// Type is the field's value type, as the schema names it.
Type string `json:"type"`
// DigestOf names the text field this field is the digest of (ADR 0597 §2): `apply` refuses an operation whose
// digest does not match its text.
DigestOf string `json:"digest-of,omitempty"`
}
// LedgerOperation is one operation a ledger type allows, and its effect on the state.
type LedgerOperation struct {
Name string `json:"name"`
// Effect is one of the ledger effects (EffectSetFields, EffectSetEntry, EffectAppend, EffectRemove).
Effect string `json:"effect"`
// Fields are the operation's fields its effect copies into the state; it may carry more (ADR 0597 §2).
Fields []string `json:"fields,omitempty"`
// Into is the state's map (EffectSetEntry, EffectRemove) or list (EffectAppend) the effect writes.
Into string `json:"into,omitempty"`
// Key is the operation's field holding the entry's key (EffectSetEntry, EffectRemove).
Key string `json:"key,omitempty"`
// Sequence and Author are whether the effect also writes the operation's sequence and its proven author.
Sequence bool `json:"sequence,omitempty"`
Author bool `json:"author,omitempty"`
}
// LedgerIndex is one declared index of a ledger type: its name, and the fields it is kept on.
type LedgerIndex struct {
Name string `json:"name"`
On []string `json:"on"`
}
// BusStateField is where a stored manifest keeps its bus-state entries: beside `requires`, which stays a plain list of
// names, as OptionalRequiresKey keeps the optional ones.
const BusStateField = "bus-state"
@@ -31,6 +140,8 @@ type BusStateEntry struct {
// Configuration is a configuration of the module's own, where no template fits: a bare stream's, or a ledger's
// whose history expires (ADR 0595 §2).
Configuration map[string]json.RawMessage `json:"configuration,omitempty"`
// Types are a ledger's types (ADR 0597 §1), for the storage type ledger only.
Types []LedgerType `json:"types,omitempty"`
}
// isBusStateEntry is whether a `requires` item in its long form asks for bus state: it names a storage type.
+1 -1
View File
@@ -53,7 +53,7 @@ func readRequires(raw json.RawMessage) (requiresRead, error) {
if isBusStateEntry(e) {
entry, unknown, err := readBusStateEntry(e)
if err != nil {
return requiresRead{}, fmt.Errorf("requires[%d]: a bus-state entry is {name, storage, template, options, configuration} (novox/hq ADR 0595 §1): %w", i, err)
return requiresRead{}, fmt.Errorf("requires[%d]: a bus-state entry is {name, storage, template, options, configuration, types} (novox/hq ADR 0595 §1): %w", i, err)
}
names = append(names, entry.Name)
busState = append(busState, entry)
@@ -41,6 +41,23 @@ func TestARequirementsLongForm(t *testing.T) {
records := BusStateEntry{Name: "records", Storage: "ledger", Template: "ledger",
Options: map[string]json.RawMessage{"max-mib": json.RawMessage(`1024`)}}
// A ledger type with each of the four effects.
listing := LedgerType{Name: "listing",
Schema: map[string]LedgerField{"price": {Type: "number"}, "seen": {Type: "list"}, "notes": {Type: "map"},
"text": {Type: "text"}, "digest": {Type: "text", DigestOf: "text"}},
Operations: []LedgerOperation{
{Name: "priced", Effect: EffectSetFields, Fields: []string{"price"}, Sequence: true, Author: true},
{Name: "noted", Effect: EffectSetEntry, Fields: []string{"text", "digest"}, Into: "notes", Key: "title"},
{Name: "seen", Effect: EffectAppend, Fields: []string{"at"}, Into: "seen"},
{Name: "unnoted", Effect: EffectRemove, Into: "notes", Key: "title"},
},
Indexes: []LedgerIndex{{Name: "cheapest", On: []string{"price"}}}}
typeJSON, err := json.Marshal(listing)
if err != nil {
t.Fatal(err)
}
ledger := records
ledger.Types = []LedgerType{listing}
for _, c := range []struct {
name string
requires string
@@ -56,6 +73,9 @@ func TestARequirementsLongForm(t *testing.T) {
{"a bus-state entry with a key it does not know", `[
{"name":"records","storage":"ledger","template":"ledger","colour":"red","options":{"max-mib":1024}}]`,
"records", []BusStateEntry{records}, []string{"requires[0].colour"}},
// 1966/AC3: a ledger type with each of the four effects round-trips through JSON unchanged.
{"a ledger with its type", `[{"name":"records","storage":"ledger","template":"ledger","options":{"max-mib":1024},
"types":[` + string(typeJSON) + `]}]`, "records", []BusStateEntry{ledger}, nil},
} {
got, err := decodeForTest([]byte(`{"module":"x","version":"1","requires":` + c.requires + `}`))
if err != nil {