Compare commits

...
5 Commits
Author SHA1 Message Date
jschoubben 09d6070ebd RR2-5: the reader reads a seat-named view in consumes and a seat's views
mesh/delivery ready: it is merged on the Approve
mesh/delivery-group group fix/47-G-reader-release-2 ready: every member ready, and composed together they pass
mesh/check pass 09d6070ebd86 on 45e143fba672 | unit 796 run, 1280 kept, 1 skipped | integration 802 run, 1377 kept, 1 skipped | over bound: unit (slow…
G's builder and checker follow the assigner's views by the seat,
seat:mesh-module-assigner.views.assignments; SeatViewConsume splits that
form, and a seat compiled from its definition names the states it offers
as views, which the validator checks the form against.

Builds: design 47 G §621, §616, §699

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:09:33 +01:00
jschoubben 98f22ffe72 RR2-4: the manifest reader reads the long form of an invokes entry
The tool runner's invokes hold * beside {verb, fields} entries; Invokes
keeps every verb, so none of its readers changes, and InvokeFields maps a
verb to the fields it names, written back in the long form.

Builds: design 47 G §621, §617

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:08:25 +01:00
jschoubben 3747b5f78c RR2-3: the manifest reader reads an artifact's calls and states
mesh-sdk's own manifest names the capability words its packages call, and
the tool runner's artifact the words it states; the running reader refused
both manifests on the unknown word.

Builds: design 47 G §621, §605, §545

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:06:59 +01:00
jschoubben c06482f0d4 RR2-2: the manifest reader reads max-mib on an old state section
G's indexer, builder and runner cap their buckets; the running reader
called the word an unknown field and refused the whole manifest. A value
out of the objects section's bounds is refused naming them.

Builds: design 47 G §494, §469, §621

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:05:33 +01:00
jschoubben 028f93575f RR2-1: the seat-definition reader reads by-caller and chunks on a verb, view on a state
A definition G writes marks a verb by-caller (mesh-recovery, mesh-broker, the
gated verbs) or chunks, and a state as a view; the running reader refused
each as a field the format does not know, so every such definition.

Builds: design 47 G §621, §697, §681, §699

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:04:05 +01:00
7 changed files with 224 additions and 5 deletions
+93 -1
View File
@@ -587,6 +587,11 @@ type Manifest struct {
// person's account (design 25 §7) already had the same shape. // person's account (design 25 §7) already had the same shape.
Invokes []string `json:"invokes,omitempty"` Invokes []string `json:"invokes,omitempty"`
// InvokeFields maps a verb of Invokes to the fields it names, read from an entry of `invokes` in its long form
// {verb, fields}, which may stand beside `*` (design 47 G §617): Invokes keeps the verb, so none of its readers
// changes. A long-form entry naming no field is a verb of Invokes alone.
InvokeFields map[string][]string `json:"-"`
// Waits are the event families a session may wait on through the mesh MCP server's `mesh_wait`, each // Waits are the event families a session may wait on through the mesh MCP server's `mesh_wait`, each
// `<emitter>.<event>` as `consumes` names one, a seat's or a module's (novox/hq ADR 0367 §2). Only the tool // `<emitter>.<event>` as `consumes` names one, a seat's or a module's (novox/hq ADR 0367 §2). Only the tool
// runner's module declares them, one by one and never by a wildcard. **A grant apart from `consumes`**: the // runner's module declares them, one by one and never by a wildcard. **A grant apart from `consumes`**: the
@@ -1246,6 +1251,16 @@ type Artifact struct {
// release keeps a manifest that names them whole (design 47 G §340, §409); the manifest validator checks their form. // release keeps a manifest that names them whole (design 47 G §340, §409); the manifest validator checks their form.
Needs []string `json:"needs,omitempty"` Needs []string `json:"needs,omitempty"`
// Calls are the capability words (design 47 G §488, §605) a package's code calls of the tool runner: the delivery plan
// derives, for each module whose build resolved a version of such a package, an ordering edge to the module whose
// artifact States the word. Read here so the second reader release keeps a manifest that names them (§621).
Calls []string `json:"calls,omitempty"`
// States are the capability words (design 47 G §488) the tool runner's artifact states, the edge of a package's Calls
// running to the module that states each (§545). Read here so the second reader release keeps a manifest that names
// them (§621).
States []string `json:"states,omitempty"`
// GeneratedFrom marks a package of a contracts module (novox/hq ADR 0608 §5, design 26 *Contracts modules*): the // GeneratedFrom marks a package of a contracts module (novox/hq ADR 0608 §5, design 26 *Contracts modules*): the
// directory, relative to the module's, of the seat definitions every file of the package is generated from. A // directory, relative to the module's, of the seat definitions every file of the package is generated from. A
// module carrying it holds contracts only and claims no seat, which the judge refuses naming the seat; the build // module carrying it holds contracts only and claims no seat, which the judge refuses naming the seat; the build
@@ -1572,6 +1587,10 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
if err := consumesEntryProblem(keys["consumes"]); err != nil { if err := consumesEntryProblem(keys["consumes"]); err != nil {
return err return err
} }
invokeFields, err := readInvokes(keys)
if err != nil {
return err
}
plain := map[string]string{} plain := map[string]string{}
many := map[string]map[string]string{} many := map[string]map[string]string{}
if secrets, ok := keys["secrets"]; ok && string(secrets) != "null" { if secrets, ok := keys["secrets"]; ok && string(secrets) != "null" {
@@ -1726,6 +1745,7 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
m.forms = forms m.forms = forms
m.OptionalRequires = optionalRequires m.OptionalRequires = optionalRequires
m.RequireShares = requireShares m.RequireShares = requireShares
m.InvokeFields = invokeFields
if len(plain) > 0 { if len(plain) > 0 {
m.Secrets = plain m.Secrets = plain
} }
@@ -1744,6 +1764,61 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
return nil return nil
} }
// invokeLongForm is an entry of `invokes` in its long form: the verb, and the fields of its answer the caller relies on
// (design 47 G §617).
type invokeLongForm struct {
Verb string `json:"verb"`
Fields []string `json:"fields,omitempty"`
}
// readInvokes reads `invokes` whose entries are verbs or, in their long form, {verb, fields}: it puts the verbs back in
// keys as plain names and answers the fields each long-form entry names.
func readInvokes(keys map[string]json.RawMessage) (map[string][]string, error) {
raw, ok := keys["invokes"]
if !ok || string(raw) == "null" {
return nil, nil
}
var entries []json.RawMessage
if err := json.Unmarshal(raw, &entries); err != nil {
return nil, nil // not a list: the field's own decoder says so
}
var verbs []string
var fields map[string][]string
for i, e := range entries {
trimmed := bytes.TrimSpace(e)
if len(trimmed) == 0 || trimmed[0] != '{' {
var verb string
if err := json.Unmarshal(trimmed, &verb); err != nil {
return nil, fmt.Errorf("invokes[%d]: a verb, or {verb, fields}: %w", i, err)
}
verbs = append(verbs, verb)
continue
}
var long invokeLongForm
dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields()
if err := dec.Decode(&long); err != nil {
return nil, fmt.Errorf("invokes[%d]: a verb, or {verb, fields}: %w", i, err)
}
if long.Verb == "" {
return nil, fmt.Errorf("invokes[%d]: a long-form entry names its verb", i)
}
verbs = append(verbs, long.Verb)
if len(long.Fields) > 0 {
if fields == nil {
fields = map[string][]string{}
}
fields[long.Verb] = long.Fields
}
}
plain, err := json.Marshal(verbs)
if err != nil {
return nil, err
}
keys["invokes"] = plain
return fields, nil
}
// checkSharesNotContributed refuses a module whose own `contributes` to s3-bucket names `shares` (ADR 0468 §8). // checkSharesNotContributed refuses a module whose own `contributes` to s3-bucket names `shares` (ADR 0468 §8).
func (m Manifest) checkSharesNotContributed() error { func (m Manifest) checkSharesNotContributed() error {
if len(m.RequireShares[BucketRequirement]) == 0 { if len(m.RequireShares[BucketRequirement]) == 0 {
@@ -1797,13 +1872,30 @@ func (m Manifest) marshalFields() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(m.SecretsMany) == 0 && len(m.ContributesMany) == 0 && len(m.OptionalRequires) == 0 && len(m.RequireShares) == 0 { if len(m.SecretsMany) == 0 && len(m.ContributesMany) == 0 && len(m.OptionalRequires) == 0 && len(m.RequireShares) == 0 &&
len(m.InvokeFields) == 0 {
return raw, nil return raw, nil
} }
var keys map[string]json.RawMessage var keys map[string]json.RawMessage
if err := json.Unmarshal(raw, &keys); err != nil { if err := json.Unmarshal(raw, &keys); err != nil {
return nil, err return nil, err
} }
if len(m.InvokeFields) > 0 {
// Each verb that names fields is written back in its long form (design 47 G §617).
entries := make([]any, 0, len(m.Invokes))
for _, verb := range m.Invokes {
if fields, named := m.InvokeFields[verb]; named {
entries = append(entries, invokeLongForm{Verb: verb, Fields: fields})
continue
}
entries = append(entries, verb)
}
listed, err := json.Marshal(entries)
if err != nil {
return nil, err
}
keys["invokes"] = listed
}
if optional := optionalRequiresList(m); len(optional) > 0 { if optional := optionalRequiresList(m); len(optional) > 0 {
// Beside `requires`, never inside it: an older controller reads `requires` as plain names, and one // Beside `requires`, never inside it: an older controller reads `requires` as plain names, and one
// manifest it cannot read stops the whole catalogue (OptionalRequiresKey). // manifest it cannot read stops the whole catalogue (OptionalRequiresKey).
+79 -1
View File
@@ -1,6 +1,13 @@
package catalogue package catalogue
import "testing" import (
"encoding/json"
"slices"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
// A manifest carrying the words design 47 G writes is read whole by the reader release (design 47 G §340, §409): // A manifest carrying the words design 47 G writes is read whole by the reader release (design 47 G §340, §409):
// `version` as MAJOR.MINOR (§368), a `*` emitter in `consumes` (§386), an observer's finding stream (§387) and an // `version` as MAJOR.MINOR (§368), a `*` emitter in `consumes` (§386), an observer's finding stream (§387) and an
@@ -21,3 +28,74 @@ func TestTheReaderReleaseReadsTheManifestWordsGWrites(t *testing.T) {
t.Fatalf("read as %+v", m) t.Fatalf("read as %+v", m)
} }
} }
// The second reader release reads the words G's manifests write beyond the first's (design 47 G §494, §621): each row a
// manifest read whole, or refused naming what is wrong, and what the reader gives of it.
func TestTheSecondReaderReleaseReadsGsManifestWords(t *testing.T) {
for _, c := range []struct {
name, raw, refused string
check func(Manifest) bool
}{
{name: "a state's cap (§469)", raw: `{"module":"m","version":"1.0","state":["plain",{"name":"pipelines","max-mib":256}]}`,
check: func(m Manifest) bool { return m.State[0].MaxMiB == 0 && m.State[1].MaxMiB == 256 }},
{name: "a state's cap out of the objects section's bounds (§469)",
raw: `{"module":"m","version":"1.0","state":[{"name":"pipelines","max-mib":0}]}`, refused: "max-mib is between 1 and 8192"},
{name: "a package's calls and the tool runner's states (§605, §545)",
raw: `{"module":"m","version":"1.0","build":{"artifacts":[{"name":"go","kind":"package","language":"go","calls":["put-sessions"]},` +
`{"name":"runtime","kind":"bundle","language":"go","states":["put-sessions","finding-calls"]}]}}`,
check: func(m Manifest) bool {
a := m.Build.Artifacts
return len(a) == 2 && len(a[0].Calls) == 1 && a[0].Calls[0] == "put-sessions" && len(a[1].States) == 2
}},
{name: "* with two long-form invokes (§617)",
raw: `{"module":"m","version":"1.0","invokes":["*",{"verb":"seat:mesh-catalog.revision","fields":["none-yet"]},` +
`{"verb":"seat:mesh-catalog.entry"}]}`,
check: func(m Manifest) bool {
out, _ := json.Marshal(m)
return slices.Equal(m.Invokes, []string{"*", "seat:mesh-catalog.revision", "seat:mesh-catalog.entry"}) &&
len(m.InvokeFields) == 1 && slices.Equal(m.InvokeFields["seat:mesh-catalog.revision"], []string{"none-yet"}) &&
strings.Contains(string(out), `"invokes":["*",{"verb":"seat:mesh-catalog.revision","fields":["none-yet"]},"seat:mesh-catalog.entry"]`)
}},
{name: "a long-form invoke with a word it does not know (§617)",
raw: `{"module":"m","version":"1.0","invokes":[{"verb":"seat:mesh-catalog.revision","field":["none-yet"]}]}`,
refused: `invokes[0]: a verb, or {verb, fields}`},
} {
m, err := decodeForTest([]byte(c.raw))
switch {
case c.refused != "" && (err == nil || !strings.Contains(err.Error(), c.refused)):
t.Errorf("%s: read as %v, want refused naming %q", c.name, err, c.refused)
case c.refused == "" && err != nil:
t.Errorf("%s: refused: %v", c.name, err)
case c.refused == "" && (len(m.UnknownField()) != 0 || !c.check(m)):
t.Errorf("%s: read as %+v (set aside %v)", c.name, m, m.UnknownField())
}
}
}
// A consumed view of a seat, `seat:<seat>.views.<state>`, is read apart from a consumed event, and a seat's compiled form
// names the states its definition offers as views (design 47 G §616, §699, §621).
func TestTheSecondReaderReleaseReadsASeatNamedView(t *testing.T) {
for _, row := range []struct {
consume, seat, state string
ok, bad bool
}{
{"seat:mesh-module-assigner.views.assignments", "mesh-module-assigner", "assignments", true, false},
{"mesh-delivery-assigner.views.assignments", "", "", false, false},
{"seat:mesh-module-assigner.assignments", "", "", true, true},
{"seat:.views.assignments", "", "", true, true},
} {
seat, state, ok, err := SeatViewConsume(row.consume)
if seat != row.seat || state != row.state || ok != row.ok || (err != nil) != row.bad {
t.Errorf("SeatViewConsume(%q) = %q, %q, %v, %v", row.consume, seat, state, ok, err)
}
}
m, err := decodeForTest([]byte(`{"module":"m","version":"1.0","consumes":["seat:mesh-module-assigner.views.assignments"]}`))
if err != nil || len(m.UnknownField()) != 0 || m.Consumes[0] != "seat:mesh-module-assigner.views.assignments" {
t.Errorf("a seat-named view in consumes read as %v, %v", m.Consumes, err)
}
s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-module-assigner", Scope: "mesh",
States: []seatdef.State{{Name: "assignments", View: true}, {Name: "placement"}}})
if !slices.Equal(s.Views, []string{"assignments"}) {
t.Errorf("the seat's views = %v", s.Views)
}
}
+5
View File
@@ -25,6 +25,11 @@ func SeatFromDefinition(d seatdef.Definition) Seat {
Input: seatdef.ResolveRefs(v.Input, d.Defs), Output: seatdef.ResolveRefs(v.Answer, d.Defs), Input: seatdef.ResolveRefs(v.Input, d.Defs), Output: seatdef.ResolveRefs(v.Answer, d.Defs),
Optional: v.Optional, Replaces: v.Replaces}) Optional: v.Optional, Replaces: v.Replaces})
} }
for _, st := range d.States {
if st.View {
s.Views = append(s.Views, st.Name)
}
}
for _, r := range d.Receives { for _, r := range d.Receives {
rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered} rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered}
if r.Shape != nil { if r.Shape != nil {
+3
View File
@@ -45,6 +45,9 @@ type Seat struct {
// Serves carries each verb in full — name, description, schema — because a role's tools are the // Serves carries each verb in full — name, description, schema — because a role's tools are the
// mesh's to define and an agent's to call (novox/hq ADR 0132, design 33 §2). // mesh's to define and an agent's to call (novox/hq ADR 0132, design 33 §2).
Serves []Verb Serves []Verb
// Views are the names of the states the seat offers as views, which a module follows by `consumes`
// `seat:<seat>.views.<state>` (design 47 G §616, §699). Compiled from the definition's states marked `view`.
Views []string
// Receives is what other modules may contribute to the seat's holder, by kind (novox/hq ADR // Receives is what other modules may contribute to the seat's holder, by kind (novox/hq ADR
// 0212): each kind is text in the tool's own grammar, placed by the holder with // 0212): each kind is text in the tool's own grammar, placed by the holder with
// ${contribution:<seat>:<kind>}. Compiled, never stored: like the protocol, it is the mesh's // ${contribution:<seat>:<kind>}. Compiled, never stored: like the protocol, it is the mesh's
+32 -3
View File
@@ -35,11 +35,14 @@ type StateDeclaration struct {
// the tool runner's account, read it and never write it (the lead's ruling, 2026-11-04: nats' warrant spend record // 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. // `spent`). The judge's rule and the grants follow from it.
ReadOnly bool `json:"read-only,omitempty"` ReadOnly bool `json:"read-only,omitempty"`
// MaxMiB is the bucket's cap in MiB, between ObjectsLeastMiB and ObjectsMostMiB, as an old objects section's
// is (design 47 G §469, §494); zero is no cap, as before.
MaxMiB int `json:"max-mib,omitempty"`
// KeptLayout is the layout its holder says it writes: its form and its catch-up tool (design 47). // KeptLayout is the layout its holder says it writes: its form and its catch-up tool (design 47).
KeptLayout KeptLayout
} }
// UnmarshalJSON reads a bucket as its bare name, or as {name, history, ttl-seconds, per-machine, key-ttl, read-only, form, catch-up}. // UnmarshalJSON reads a bucket as its bare name, or as {name, history, ttl-seconds, per-machine, key-ttl, read-only, max-mib, form, catch-up}.
func (s *StateDeclaration) UnmarshalJSON(raw []byte) error { func (s *StateDeclaration) UnmarshalJSON(raw []byte) error {
trimmed := bytes.TrimSpace(raw) trimmed := bytes.TrimSpace(raw)
if len(trimmed) > 0 && trimmed[0] == '"' { if len(trimmed) > 0 && trimmed[0] == '"' {
@@ -47,27 +50,53 @@ func (s *StateDeclaration) UnmarshalJSON(raw []byte) error {
} }
type plain StateDeclaration type plain StateDeclaration
var full plain var full plain
var said struct {
MaxMiB *int `json:"max-mib"`
}
_ = json.Unmarshal(trimmed, &said)
dec := json.NewDecoder(bytes.NewReader(trimmed)) dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields() dec.DisallowUnknownFields()
if err := dec.Decode(&full); err != nil { 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)) return fmt.Errorf("a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl, read-only, max-mib, form, catch-up}: %w", typedUnknown(err))
} }
if err := full.KeptLayout.refuse(fmt.Sprintf("state %q", full.Name)); err != nil { if err := full.KeptLayout.refuse(fmt.Sprintf("state %q", full.Name)); err != nil {
return err return err
} }
if said.MaxMiB != nil && (*said.MaxMiB < ObjectsLeastMiB || *said.MaxMiB > ObjectsMostMiB) {
return fmt.Errorf("state %q: max-mib is between %d and %d, not %d", full.Name, ObjectsLeastMiB, ObjectsMostMiB, *said.MaxMiB)
}
*s = StateDeclaration(full) *s = StateDeclaration(full)
return nil return nil
} }
// MarshalJSON writes back the short form when there is nothing else to say. // MarshalJSON writes back the short form when there is nothing else to say.
func (s StateDeclaration) MarshalJSON() ([]byte, error) { func (s StateDeclaration) MarshalJSON() ([]byte, error) {
if s.History == 0 && s.TTLSeconds == 0 && !s.PerMachine && !s.KeyTTL && !s.ReadOnly && s.KeptLayout == (KeptLayout{}) { if s.History == 0 && s.TTLSeconds == 0 && !s.PerMachine && !s.KeyTTL && !s.ReadOnly && s.MaxMiB == 0 && s.KeptLayout == (KeptLayout{}) {
return json.Marshal(s.Name) return json.Marshal(s.Name)
} }
type plain StateDeclaration type plain StateDeclaration
return json.Marshal(plain(s)) return json.Marshal(plain(s))
} }
// SeatViewPrefix opens a consumed view of a seat, `seat:<seat>.views.<state>` (design 47 G §616).
const SeatViewPrefix = "seat:"
// SeatViewConsume splits a consumed view of a seat, `seat:<seat>.views.<state>`, into the seat and the state; ok is false for a
// consumed entry that is not of this kind (an event, `<emitter>.<event>`), and err says why a `seat:` entry is malformed. The
// holder is not in it: it is resolved where the view is followed, so a new holder is followed with no change of the
// manifest (design 47 G §616, §621).
func SeatViewConsume(consume string) (seat, state string, ok bool, err error) {
rest, isSeat := strings.CutPrefix(consume, SeatViewPrefix)
if !isSeat {
return "", "", false, nil
}
seat, view, named := strings.Cut(rest, ".views.")
if !named || !stateName.MatchString(seat) || !stateName.MatchString(view) {
return "", "", true, fmt.Errorf("%q does not name a seat and its view: a consumed view of a seat is seat:<seat>.views.<state>", consume)
}
return seat, view, true, nil
}
// ReadState splits a read into the owning module and the local name, or says why it is not one. // 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) { func ReadState(read string) (module, local string, err error) {
at := strings.LastIndex(read, ".") at := strings.LastIndex(read, ".")
+8
View File
@@ -221,6 +221,12 @@ type Verb struct {
// NeedsApproval marks the verb for every holder: the operator approves a call of it first (design 46 *The approval // NeedsApproval marks the verb for every holder: the operator approves a call of it first (design 46 *The approval
// mark* §2). // mark* §2).
NeedsApproval bool `json:"needs-approval,omitempty"` NeedsApproval bool `json:"needs-approval,omitempty"`
// ByCaller says the holder's process answers the verb itself and tells callers apart by the account the bus proved
// (design 47 G §554, §621, §697).
ByCaller bool `json:"by-caller,omitempty"`
// Chunks says the holder's process sends the answer's file in chunks, each acknowledged by the caller, on the
// subjects mesh.seat.<seat>.chunk.<verb>.<call>.<n> and mesh.seat.<seat>.ack.<verb>.<call>.<n> (design 47 G §681).
Chunks bool `json:"chunks,omitempty"`
} }
// The streams an accepted kind may ride (design 47 G §415). // The streams an accepted kind may ride (design 47 G §415).
@@ -402,6 +408,8 @@ type State struct {
History int `json:"history,omitempty"` History int `json:"history,omitempty"`
MaxAgeDays int `json:"max-age-days,omitempty"` MaxAgeDays int `json:"max-age-days,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
// View is true when the seat offers the state as a view for a module to follow by `consumes` (design 47 G §699).
View bool `json:"view,omitempty"`
} }
// KeyForm is one form a state's key takes: a pattern of named parts (`<asker>.<id>`, a part between angle brackets, // KeyForm is one form a state's key takes: a pattern of named parts (`<asker>.<id>`, a part between angle brackets,
+4
View File
@@ -84,6 +84,8 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
`"domain":"Core","decision":"ADR 1","records":[{"name":"asks"}],`, 1), "records: a field the format does not know"}, `"domain":"Core","decision":"ADR 1","records":[{"name":"asks"}],`, 1), "records: a field the format does not know"},
{"a state with a key form and a value", withState(whole, `{"name":"asks","template":"state","writer":"caller", {"a state with a key form and a value", withState(whole, `{"name":"asks","template":"state","writer":"caller",
"history":3,"key-forms":[{"key":"<asker>.<id>","value":{"type":"object"}}]}`), ""}, "history":3,"key-forms":[{"key":"<asker>.<id>","value":{"type":"object"}}]}`), ""},
{"a state offered as a view", withState(whole, `{"name":"asks","template":"state","view":true,
"key-forms":[{"key":"<id>","value":{"type":"object"}}]}`), ""},
{"a stream state with an age", withState(whole, `{"name":"placement","template":"events","max-age-days":7, {"a stream state with an age", withState(whole, `{"name":"placement","template":"events","max-age-days":7,
"key-forms":[{"key":"changed.<node>","value":{"type":"object"}}]}`), ""}, "key-forms":[{"key":"changed.<node>","value":{"type":"object"}}]}`), ""},
{"a state of an unknown template", withState(whole, `{"name":"asks","template":"bucket", {"a state of an unknown template", withState(whole, `{"name":"asks","template":"bucket",
@@ -142,6 +144,8 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
{"an acting rule on another kind", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","on":"kind:Image"}],"verbs":`, 1), "acts[0].on"}, {"an acting rule on another kind", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","on":"kind:Image"}],"verbs":`, 1), "acts[0].on"},
{"a verb marked for approval", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":true,`, 1), ""}, {"a verb marked for approval", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":true,`, 1), ""},
{"a verb marked with a word that is no boolean", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":"yes",`, 1), "not a seat definition's JSON"}, {"a verb marked with a word that is no boolean", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":"yes",`, 1), "not a seat definition's JSON"},
{"a verb answered by its caller's account, in chunks", strings.Replace(whole, `"name":"list",`, `"name":"list","by-caller":true,"chunks":true,`, 1), ""},
{"a verb's by-caller that is no boolean", strings.Replace(whole, `"name":"list",`, `"name":"list","by-caller":"yes",`, 1), "not a seat definition's JSON"},
{"a bodies section", strings.Replace(whole, `"family":`, `"bodies":{"Finding":{"type":"object"}},"family":`, 1), ""}, {"a bodies section", strings.Replace(whole, `"family":`, `"bodies":{"Finding":{"type":"object"}},"family":`, 1), ""},
{"one kind on two streams", strings.Replace(whole, `"events":`, {"one kind on two streams", strings.Replace(whole, `"events":`,
`"accepts":[{"name":"step","body":{},"stream":"check"},{"name":"step","body":{},"stream":"delivery"}],"events":`, 1), ""}, `"accepts":[{"name":"step","body":{},"stream":"check"},{"name":"step","body":{},"stream":"delivery"}],"events":`, 1), ""},