W1.1: the seat-definition reader reads a seat's acting rules
mesh/delivery merged: source.main moved to novox/mesh-sdk@fb2c601303d2
mesh/check checking 9e61f75d3b46 on fb2c601303d2
mesh/delivery merged: source.main moved to novox/mesh-sdk@fb2c601303d2
mesh/check checking 9e61f75d3b46 on fb2c601303d2
A definition carrying acts was refused as an unknown field by every reader (design 47, What ADR 0656 leaves to this design). seatdef reads acts at either grain: each rule's step one of the seven, on every or a language, a bound as a duration, one step on one language once; ActsOn and Wait give a rule's match and its bound, the step's default when it names none. W1.2: the node-engine copy follows mesh-contracts a6c5eaa6 (its compose rule). Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxSize is the largest definition, or part, the mesh keeps (design 26: at most 256 KiB a definition).
|
||||
@@ -92,6 +93,105 @@ type Definition struct {
|
||||
Paths []Path `json:"paths,omitempty"`
|
||||
// Defs are named types a schema refers to as `{"$ref": "#/defs/<name>"}`.
|
||||
Defs map[string]map[string]any `json:"defs,omitempty"`
|
||||
// Acts are the seat's acting rules (novox/hq ADR 0656 §1; design 47 *What ADR 0656 leaves to this design*): each step
|
||||
// of a delivery the seat's holder acts in, on which modules, and how long an entry waits there for the holder's new
|
||||
// version. At either grain: a seat-list definition names its holder's step too.
|
||||
Acts []ActingRule `json:"acts,omitempty"`
|
||||
}
|
||||
|
||||
// ActingRule is one acting rule of a seat: the step its holder acts in, the modules it acts on (every module, or those
|
||||
// whose manifest says this language), and how long an entry waits in that step for the holder's new version, a
|
||||
// duration such as "30m"; none is the step's default (DefaultBound).
|
||||
type ActingRule struct {
|
||||
Step string `json:"step"`
|
||||
On string `json:"on,omitempty"`
|
||||
Bound string `json:"bound,omitempty"`
|
||||
}
|
||||
|
||||
// The steps of a delivery a seat's holder acts in (design 47): what the indexer, the checker, the build modules, the
|
||||
// node-engine, the grants part and the assigner each do for a change.
|
||||
const (
|
||||
StepTakeIn = "take-in"
|
||||
StepValidate = "validate"
|
||||
StepCheck = "check"
|
||||
StepBuild = "build"
|
||||
StepCompose = "compose"
|
||||
StepGrant = "grant"
|
||||
StepPlace = "place"
|
||||
)
|
||||
|
||||
// Steps are the steps an acting rule names, in a delivery's order.
|
||||
var Steps = []string{StepTakeIn, StepValidate, StepCheck, StepBuild, StepCompose, StepGrant, StepPlace}
|
||||
|
||||
// OnEvery is an acting rule's `on` for every module; an absent `on` reads as it.
|
||||
const OnEvery = "every"
|
||||
|
||||
// DefaultBound is how long an entry waits in step for its acting part's new version when the rule names no bound
|
||||
// (design 47 *An acting part's new version acts first*): 60 minutes to build, 30 to compose, 20 to check (the first
|
||||
// tier's), 10 to grant and to place; the indexer's take-in and validate have no wait, so zero.
|
||||
func DefaultBound(step string) time.Duration {
|
||||
switch step {
|
||||
case StepBuild:
|
||||
return 60 * time.Minute
|
||||
case StepCompose:
|
||||
return 30 * time.Minute
|
||||
case StepCheck:
|
||||
return 20 * time.Minute
|
||||
case StepGrant, StepPlace:
|
||||
return 10 * time.Minute
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Acting is the modules the rule acts on: OnEvery, or a language.
|
||||
func (r ActingRule) Acting() string {
|
||||
if r.On == "" {
|
||||
return OnEvery
|
||||
}
|
||||
return r.On
|
||||
}
|
||||
|
||||
// ActsOn says whether the rule acts on a module whose manifest says language.
|
||||
func (r ActingRule) ActsOn(language string) bool {
|
||||
on := r.Acting()
|
||||
return on == OnEvery || strings.EqualFold(on, strings.TrimSpace(language))
|
||||
}
|
||||
|
||||
// Wait is how long an entry waits in the rule's step for its acting part's new version: its bound, or the step's
|
||||
// default. A bound the reader refused never reaches here; one that does not parse reads as the default.
|
||||
func (r ActingRule) Wait() time.Duration {
|
||||
if d, err := time.ParseDuration(r.Bound); err == nil && d > 0 {
|
||||
return d
|
||||
}
|
||||
return DefaultBound(r.Step)
|
||||
}
|
||||
|
||||
func checkActs(rs []ActingRule) Problems {
|
||||
var ps Problems
|
||||
seen := map[string]bool{}
|
||||
for i, r := range rs {
|
||||
where := fmt.Sprintf("acts[%d]", i)
|
||||
switch {
|
||||
case r.Step == "":
|
||||
ps = append(ps, Problem{where + ".step", "missing"})
|
||||
case !slices.Contains(Steps, r.Step):
|
||||
ps = append(ps, Problem{where + ".step", fmt.Sprintf("%q is not a step (%s)", r.Step, strings.Join(Steps, ", "))})
|
||||
}
|
||||
if r.On != "" && r.On != OnEvery && !wordName.MatchString(r.On) {
|
||||
ps = append(ps, Problem{where + ".on", fmt.Sprintf("%q is neither every nor a language's lower-case name", r.On)})
|
||||
}
|
||||
if r.Bound != "" {
|
||||
if d, err := time.ParseDuration(r.Bound); err != nil || d <= 0 {
|
||||
ps = append(ps, Problem{where + ".bound", fmt.Sprintf("%q is not a duration such as 30m", r.Bound)})
|
||||
}
|
||||
}
|
||||
if k := r.Step + " " + r.Acting(); r.Step != "" && seen[k] {
|
||||
ps = append(ps, Problem{where, fmt.Sprintf("the step %s on %s twice", r.Step, r.Acting())})
|
||||
} else {
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// Verb is one verb the seat serves: its input and, written whole, its answer, each a JSON Schema.
|
||||
@@ -554,6 +654,7 @@ func (d Definition) check() Problems {
|
||||
}
|
||||
}
|
||||
ps = append(ps, checkStates(d.States)...)
|
||||
ps = append(ps, checkActs(d.Acts)...)
|
||||
for i, p := range d.Provisions {
|
||||
where := fmt.Sprintf("provisions[%d]", i)
|
||||
if p.Name == "" {
|
||||
|
||||
@@ -99,6 +99,21 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
|
||||
"key-forms":[{"key":"<id>","value":{}}]}`), "states[0].history"},
|
||||
{"a state twice", withState(withState(whole, `{"name":"asks","template":"state","key-forms":[{"key":"<id>","value":{}}]}`),
|
||||
`{"name":"asks","template":"log","key-forms":[{"key":"<id>","value":{}}]}`), `"asks" twice`},
|
||||
// W1.1: a seat's acting rules (design 47 *What ADR 0656 leaves to this design*), at either grain.
|
||||
{"acting rules", strings.Replace(whole, `"family":`, `"acts":[{"step":"take-in"},{"step":"validate","on":"every"},`+
|
||||
`{"step":"build","on":"go","bound":"60m"}],"family":`, 1), ""},
|
||||
{"an acting rule at the seat list's grain", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"compose","bound":"30m"}],"verbs":`, 1), ""},
|
||||
{"an acting rule with no step", strings.Replace(listGrain, `"verbs":`, `"acts":[{"on":"go"}],"verbs":`, 1), "acts[0].step: missing"},
|
||||
{"an acting rule of an unknown step", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"deploy"}],"verbs":`, 1),
|
||||
`acts[0].step: "deploy" is not a step (take-in, validate, check, build, compose, grant, place)`},
|
||||
{"an acting rule on no language", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","on":"Go Lang"}],"verbs":`, 1),
|
||||
"acts[0].on"},
|
||||
{"an acting rule's bound that is no duration", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","bound":"an hour"}],"verbs":`, 1),
|
||||
`acts[0].bound: "an hour" is not a duration`},
|
||||
{"an acting rule's field the format does not know", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","when":"now"}],"verbs":`, 1),
|
||||
"when: a field the format does not know"},
|
||||
{"one step on one language twice", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"grant"},{"step":"grant","on":"every"}],"verbs":`, 1),
|
||||
"acts[1]: the step grant on every twice"},
|
||||
{"over 256 KiB", listGrain[:len(listGrain)-1] + `,"description":"` + strings.Repeat("x", MaxSize) + `"}`,
|
||||
ErrTooLarge.Error()},
|
||||
}
|
||||
@@ -199,3 +214,28 @@ func TestAnEventsSubjectNamesItsParts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W1.1: what an acting rule reads as where it says less (design 47 *An acting part's new version acts first*).
|
||||
func TestAnActingRuleReadsItsStepsDefaults(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
rule ActingRule
|
||||
language string
|
||||
acts bool
|
||||
wait string
|
||||
}{
|
||||
{ActingRule{Step: StepBuild, On: "go"}, "go", true, "1h0m0s"},
|
||||
{ActingRule{Step: StepBuild, On: "go"}, "typescript", false, "1h0m0s"},
|
||||
{ActingRule{Step: StepCompose}, "anything", true, "30m0s"},
|
||||
{ActingRule{Step: StepCheck, On: OnEvery}, "", true, "20m0s"},
|
||||
{ActingRule{Step: StepGrant, Bound: "5m"}, "go", true, "5m0s"},
|
||||
{ActingRule{Step: StepPlace}, "go", true, "10m0s"},
|
||||
{ActingRule{Step: StepTakeIn}, "go", true, "0s"},
|
||||
} {
|
||||
if got := c.rule.ActsOn(c.language); got != c.acts {
|
||||
t.Errorf("%+v acts on %q: %v, want %v", c.rule, c.language, got, c.acts)
|
||||
}
|
||||
if got := c.rule.Wait().String(); got != c.wait {
|
||||
t.Errorf("%+v waits %s, want %s", c.rule, got, c.wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user