RR-2, RR-3: the reader reads a verb's and a manifest's needs-approval and a definition's bodies
A verb's needs-approval is a boolean, a manifest's a list of tools, seat verbs and events; bodies are named schemas the grants part makes no seat event or stream from, and a reference #/bodies/<name> resolves beside #/defs/<name>. All 40 definitions of mesh-contracts' feat/47-G-build (5f7610f) read. Builds: design 47 G §409, §439, §448; design 46 M7 §1, §2, §23 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -465,6 +465,11 @@ type Manifest struct {
|
||||
// choosing `kc` for keycloak keeps the identity legible where a hash would not.
|
||||
Slug string `json:"slug,omitempty"`
|
||||
|
||||
// NeedsApproval marks tools the module serves (`<tool>`), seat verbs it serves (`<seat>.<verb>`) and events it
|
||||
// emits that need the operator's approval first (design 46 *The approval mark* §1; design 34 every-event §32). Absent
|
||||
// is no mark.
|
||||
NeedsApproval []string `json:"needs-approval,omitempty"`
|
||||
|
||||
// Provides are the names other modules may require. A module always provides its own name;
|
||||
// this is for the rest — `zsh` provides `shell`, `xorg` provides `display-server`.
|
||||
Provides []Offer `json:"provides,omitempty"`
|
||||
|
||||
@@ -6,13 +6,16 @@ import "testing"
|
||||
// `version` as MAJOR.MINOR (§368), a `*` emitter in `consumes` (§386), an observer's finding stream (§387) and an
|
||||
// artifact's `needs` (§47, §432).
|
||||
func TestTheReaderReleaseReadsTheManifestWordsGWrites(t *testing.T) {
|
||||
m, err := decodeForTest([]byte(`{"module":"mesh-watcher","version":"1.4",` +
|
||||
m, err := decodeForTest([]byte(`{"module":"mesh-watcher","version":"1.4","needs-approval":["restart","node-engine.composition","finding-written"],` +
|
||||
`"consumes":["*.finding-written","mesh-conditions.findings-asked"],` +
|
||||
`"streams":[{"name":"mesh-watcher-findings","events":["finding-written","finding-cleared","findings-stated"],"max-age-days":7,"max-mib":16}],` +
|
||||
`"build":{"artifacts":[{"name":"program","kind":"bundle","language":"go","needs":["mesh-grants:program","node-tools:runtime"]}]}}`))
|
||||
if err != nil {
|
||||
t.Fatalf("a manifest with G's words was refused: %v", err)
|
||||
}
|
||||
if len(m.UnknownField()) != 0 || len(m.NeedsApproval) != 3 {
|
||||
t.Fatalf("a word was set aside: %v (needs-approval %v)", m.UnknownField(), m.NeedsApproval)
|
||||
}
|
||||
if m.Version != "1.4" || m.Consumes[0] != "*.finding-written" || m.Streams[0].Name != "mesh-watcher-findings" ||
|
||||
len(m.Build.Artifacts[0].Needs) != 2 || m.Build.Artifacts[0].Needs[0] != "mesh-grants:program" {
|
||||
t.Fatalf("read as %+v", m)
|
||||
|
||||
+15
-5
@@ -1,18 +1,28 @@
|
||||
package seatdef
|
||||
|
||||
import "strings"
|
||||
|
||||
// ResolveRefs is a schema (its all-string lists as []string, StringLists) with every `{"$ref": "#/defs/<name>"}` replaced by the definition's named type, as the seat
|
||||
// list's readers need it (a verb's input and answer are one schema each, not a schema and its types). A reference to a
|
||||
// name the definition does not define stays as it is; nesting deeper than 32 levels stops there.
|
||||
func ResolveRefs(schema map[string]any, defs map[string]map[string]any) map[string]any {
|
||||
//
|
||||
// A reference `#/bodies/<name>` resolves against the definition's bodies, given after defs (design 47 G §439).
|
||||
func ResolveRefs(schema map[string]any, defs map[string]map[string]any, bodies ...map[string]map[string]any) map[string]any {
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
m, _ := StringLists(resolveValue(schema, defs, 0)).(map[string]any)
|
||||
all := make(map[string]map[string]any, len(defs))
|
||||
for n, d := range defs {
|
||||
all["#/defs/"+n] = d
|
||||
}
|
||||
for _, b := range bodies {
|
||||
for n, d := range b {
|
||||
all["#/bodies/"+n] = d
|
||||
}
|
||||
}
|
||||
m, _ := StringLists(resolveValue(schema, all, 0)).(map[string]any)
|
||||
return m
|
||||
}
|
||||
|
||||
// resolveValue resolves against refs, each reference as written (`#/defs/<name>`, `#/bodies/<name>`).
|
||||
func resolveValue(v any, defs map[string]map[string]any, depth int) any {
|
||||
if depth > 32 {
|
||||
return v
|
||||
@@ -20,7 +30,7 @@ func resolveValue(v any, defs map[string]map[string]any, depth int) any {
|
||||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
if r, ok := x["$ref"].(string); ok {
|
||||
if d, ok := defs[strings.TrimPrefix(r, "#/defs/")]; ok {
|
||||
if d, ok := defs[r]; ok {
|
||||
return resolveValue(d, defs, depth+1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ func TestResolveRefsPutsNamedTypesInPlace(t *testing.T) {
|
||||
"id": {"type": "string"},
|
||||
"row": {"type": "object", "properties": map[string]any{"id": map[string]any{"$ref": "#/defs/id"}}},
|
||||
}
|
||||
bodies := map[string]map[string]any{"Finding": {"type": "object", "properties": map[string]any{"id": map[string]any{"$ref": "#/defs/id"}}}}
|
||||
cases := []struct {
|
||||
name string
|
||||
schema, want map[string]any
|
||||
@@ -20,11 +21,15 @@ func TestResolveRefsPutsNamedTypesInPlace(t *testing.T) {
|
||||
map[string]any{"type": "array", "items": map[string]any{"type": "object",
|
||||
"properties": map[string]any{"id": map[string]any{"type": "string"}}}}},
|
||||
{"a name it does not define stays", map[string]any{"$ref": "#/defs/gone"}, map[string]any{"$ref": "#/defs/gone"}},
|
||||
{"a body", map[string]any{"type": "array", "items": map[string]any{"$ref": "#/bodies/Finding"}},
|
||||
map[string]any{"type": "array", "items": map[string]any{"type": "object",
|
||||
"properties": map[string]any{"id": map[string]any{"type": "string"}}}}},
|
||||
{"a body it does not define stays", map[string]any{"$ref": "#/bodies/Gone"}, map[string]any{"$ref": "#/bodies/Gone"}},
|
||||
{"a list of schemas", map[string]any{"oneOf": []any{map[string]any{"$ref": "#/defs/id"}}},
|
||||
map[string]any{"oneOf": []any{map[string]any{"type": "string"}}}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := ResolveRefs(c.schema, defs); !reflect.DeepEqual(got, c.want) {
|
||||
if got := ResolveRefs(c.schema, defs, bodies); !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("%s: %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ 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"`
|
||||
// Bodies are the bodies other modules say on their own streams, named: each a JSON Schema, referred to as
|
||||
// `{"$ref": "#/bodies/<name>"}`. The grants part makes no seat event or stream from them (design 47 G §439, §409).
|
||||
Bodies map[string]map[string]any `json:"bodies,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.
|
||||
@@ -215,6 +218,9 @@ type Verb struct {
|
||||
Callers *Callers `json:"callers,omitempty"`
|
||||
// Class is the verb's class, ClassRead, ClassAct or ClassWarrant (design 47 G §391).
|
||||
Class string `json:"class,omitempty"`
|
||||
// NeedsApproval marks the verb for every holder: the operator approves a call of it first (design 46 *The approval
|
||||
// mark* §2).
|
||||
NeedsApproval bool `json:"needs-approval,omitempty"`
|
||||
}
|
||||
|
||||
// Message is one accepted message or one event of the seat, its body a JSON Schema.
|
||||
|
||||
@@ -140,6 +140,9 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
|
||||
{"the step pipeline and an image rule", strings.Replace(listGrain, `"verbs":`,
|
||||
`"acts":[{"step":"pipeline"},{"step":"build","on":"kind:image"},{"step":"check"}],"verbs":`, 1), ""},
|
||||
{"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 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 bodies section", strings.Replace(whole, `"family":`, `"bodies":{"Finding":{"type":"object"}},"family":`, 1), ""},
|
||||
{"over 256 KiB", listGrain[:len(listGrain)-1] + `,"description":"` + strings.Repeat("x", MaxSize) + `"}`,
|
||||
ErrTooLarge.Error()},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user