IX30 (part 1): the validator's foundation list gains the forge and image registry modules, and a part's findings are judged in its part's form
Builds: design 47 G §529, §577, §584 (IX30) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -371,8 +371,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if !slices.Equal(judge.Foundation, []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools",
|
||||
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault", "mesh-delivery-pipeline"}) {
|
||||
t.Fatalf("the foundation is %v, want exactly the nine of ADR 0619 and the pipeline runner (ADR 0666 §4)", judge.Foundation)
|
||||
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault", "mesh-delivery-pipeline", "gitea", "distribution"}) {
|
||||
t.Fatalf("the foundation is %v, want exactly the nine of ADR 0619, the pipeline runner (ADR 0666 §4) and the forge and image registry modules (design 47 G §584)", judge.Foundation)
|
||||
}
|
||||
late := hold("late", `{"module":"late","version":"1.0","logs":[{"name":"l"}]}`)
|
||||
grants := hold("mesh-grants", `{"module":"mesh-grants","version":"2.0","logs":[{"name":"l"},{"name":"m"}]}`)
|
||||
|
||||
@@ -2,6 +2,7 @@ package judge
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -16,16 +17,32 @@ func TestTheJudgeReadsMilestoneGsWords(t *testing.T) {
|
||||
{name: "MAJOR.MINOR", raw: `{"module":"a","version":"1.4"}`},
|
||||
{name: "a pre-release suffix", raw: `{"module":"a","version":"1.4-beta"}`, want: "version"},
|
||||
{name: "three numbers", raw: `{"module":"a","version":"1.4.2"}`, want: "version"},
|
||||
{name: "a findings state without its stream", raw: `{"module":"a","version":"1.0","state":["findings"]}`, want: "findings"},
|
||||
{name: "a findings state with its stream and ask", raw: `{"module":"a","version":"1.0","state":["findings"],` +
|
||||
`"streams":[{"name":"a-findings","events":["finding-written"],"retention":"history"}],"consumes":["mesh-conditions.findings-asked"]}`},
|
||||
{name: "a foundation part's findings without its stream", raw: `{"module":"gitea","version":"1.0","state":["findings"]}`, want: "findings"},
|
||||
{name: "a foundation part's findings with its stream and ask (§529, §584)", raw: `{"module":"gitea","version":"1.0","state":["findings"],` +
|
||||
`"streams":[{"name":"gitea-findings","events":["finding-written"],"retention":"history"}],"consumes":["mesh-conditions.findings-asked"]}`},
|
||||
{name: "the image registry is a foundation part too (§584)", raw: `{"module":"distribution","version":"1.0","state":["findings"],` +
|
||||
`"streams":[{"name":"distribution-findings","events":["finding-written"],"retention":"history"}],"consumes":["mesh-conditions.findings-asked"]}`},
|
||||
{name: "another part's old findings and stream (§529, §577)", raw: `{"module":"a","version":"1.0","state":["findings"],` +
|
||||
`"streams":[{"name":"a-findings","events":["finding-written"],"retention":"history"}],"consumes":["mesh-conditions.findings-asked"]}`, want: "old sections"},
|
||||
{name: "another part's findings in its requires (§529)", raw: `{"module":"a","version":"1.0","consumes":["mesh-conditions.findings-asked"],"requires":[` +
|
||||
`{"name":"findings","storage":"key-value","template":"state"},` +
|
||||
`{"name":"finding-events","storage":"stream","template":"events","options":{"events":["finding-written.*","finding-cleared.*","findings-stated.*"],"max-age-days":7,"max-mib":16}}]}`},
|
||||
{name: "another part's findings without the finding events (§529)", raw: `{"module":"a","version":"1.0","consumes":["mesh-conditions.findings-asked"],"requires":[` +
|
||||
`{"name":"findings","storage":"key-value","template":"state"}]}`, want: "finding-events"},
|
||||
{name: "another part's finding events not kept 7 days (§529)", raw: `{"module":"a","version":"1.0","consumes":["mesh-conditions.findings-asked"],"requires":[` +
|
||||
`{"name":"findings","storage":"key-value","template":"state"},` +
|
||||
`{"name":"finding-events","storage":"stream","template":"events","options":{"events":["finding-written.*","finding-cleared.*","findings-stated.*"],"max-age-days":30,"max-mib":16}}]}`, want: "max-age-days"},
|
||||
} {
|
||||
raws := map[string][]byte{"a": []byte(row.raw)}
|
||||
module := "a"
|
||||
if m := regexp.MustCompile(`"module":"([a-z-]+)"`).FindStringSubmatch(row.raw); m != nil {
|
||||
module = m[1]
|
||||
}
|
||||
raws := map[string][]byte{module: []byte(row.raw)}
|
||||
var refused map[string][]string
|
||||
WithSeats([]catalogue.Seat{{Name: "thing", Scope: catalogue.ScopeMesh, Emits: []string{"finding-written"}}}, func() {
|
||||
refused, _ = Shelf(raws, nil, 0)
|
||||
})
|
||||
got := strings.Join(refused["a"], "\n")
|
||||
got := strings.Join(refused[module], "\n")
|
||||
if (row.want == "") != (got == "") || !strings.Contains(got, row.want) {
|
||||
t.Errorf("%s: refused with %q, want %q", row.name, got, row.want)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package judge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
@@ -30,25 +32,79 @@ func versionProblems(m catalogue.Manifest) []string {
|
||||
"and no pre-release or custom suffix exists (design 47 G §368)", m.Module, m.Version)}
|
||||
}
|
||||
|
||||
// gWordsProblems are the problems of a manifest's words milestone G adds: the finding stream and
|
||||
// ask of a module that declares a state `findings` (§387). The `*` emitter of `consumes` (§386) is judged with the
|
||||
// other consumed patterns (consumePattern).
|
||||
// FindingEventsEntry is the local name of the stream a part that is no foundation part asks for its findings, by the template
|
||||
// `events` (design 47 G §529).
|
||||
const FindingEventsEntry = "finding-events"
|
||||
|
||||
// findingEvents are the three events a finding stream lists (design 47 G §383, §529).
|
||||
var findingEvents = []string{"finding-written.*", "finding-cleared.*", "findings-stated.*"}
|
||||
|
||||
// gWordsProblems are the problems of a manifest's words milestone G adds: the finding state and stream and the ask of a
|
||||
// module that declares a state `findings` (§387), in the form of its part (§529, §577): a foundation part (Foundation)
|
||||
// declares the state in its old `state` section and the stream `<module>-findings` in its old `streams` section; every other
|
||||
// part asks both in its `requires`, the state `findings` by the template `state` and the stream `finding-events` by the
|
||||
// template `events`, and the old stream is refused. The `*` emitter of `consumes` (§386) is judged with the other consumed
|
||||
// patterns (consumePattern).
|
||||
func gWordsProblems(m catalogue.Manifest) []string {
|
||||
var problems []string
|
||||
for _, s := range m.State {
|
||||
if s.Name != "findings" {
|
||||
continue
|
||||
oldState := slices.ContainsFunc(m.State, func(s catalogue.StateDeclaration) bool { return s.Name == "findings" })
|
||||
stream := FindingStreamOf(m.Module)
|
||||
oldStream := slices.ContainsFunc(m.Streams, func(d catalogue.StreamDeclaration) bool { return d.Name == stream })
|
||||
var asked, askedStream *catalogue.BusStateEntry
|
||||
for i, e := range m.BusState {
|
||||
switch {
|
||||
case e.Name == "findings" && e.Template == catalogue.TemplateState:
|
||||
asked = &m.BusState[i]
|
||||
case e.Name == FindingEventsEntry && e.Template == catalogue.TemplateEvents:
|
||||
askedStream = &m.BusState[i]
|
||||
}
|
||||
stream := FindingStreamOf(m.Module)
|
||||
if !slices.ContainsFunc(m.Streams, func(d catalogue.StreamDeclaration) bool { return d.Name == stream }) {
|
||||
}
|
||||
if !oldState && asked == nil {
|
||||
return nil
|
||||
}
|
||||
if slices.Contains(Foundation, m.Module) {
|
||||
if !oldStream {
|
||||
problems = append(problems, fmt.Sprintf("%s declares the state findings without the stream %q its findings are said on "+
|
||||
"(design 47 G §387)", m.Module, stream))
|
||||
}
|
||||
if !slices.Contains(m.Consumes, FindingsAsked) {
|
||||
problems = append(problems, fmt.Sprintf("%s declares the state findings without consuming %q, with which the "+
|
||||
"keeper asks its findings again (design 47 G §387)", m.Module, FindingsAsked))
|
||||
} else {
|
||||
if oldState || oldStream {
|
||||
problems = append(problems, fmt.Sprintf("%s declares its findings in its old sections, which only a foundation part does; "+
|
||||
"it asks the state `findings` by the template `state` and the stream %q by the template `events` in `requires` "+
|
||||
"(design 47 G §529, §577)", m.Module, FindingEventsEntry))
|
||||
}
|
||||
break
|
||||
if askedStream == nil {
|
||||
problems = append(problems, fmt.Sprintf("%s declares the state findings without the stream %q its findings are said on, "+
|
||||
"asked by the template `events` in `requires` (design 47 G §529)", m.Module, FindingEventsEntry))
|
||||
} else {
|
||||
problems = append(problems, findingEventsProblems(m.Module, *askedStream)...)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(m.Consumes, FindingsAsked) {
|
||||
problems = append(problems, fmt.Sprintf("%s declares the state findings without consuming %q, with which the "+
|
||||
"keeper asks its findings again (design 47 G §387)", m.Module, FindingsAsked))
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
// findingEventsProblems judges the entry `finding-events` of a part that is no foundation part: it lists the three finding
|
||||
// events in its option `events` and keeps them 7 days and 16 MiB (design 47 G §529).
|
||||
func findingEventsProblems(module string, e catalogue.BusStateEntry) []string {
|
||||
var problems []string
|
||||
var listed []string
|
||||
if raw, ok := e.Options["events"]; ok {
|
||||
_ = json.Unmarshal(raw, &listed)
|
||||
}
|
||||
for _, want := range findingEvents {
|
||||
if !slices.Contains(listed, want) {
|
||||
problems = append(problems, fmt.Sprintf("%s's %s does not list the event %q in its `events` (design 47 G §529)", module, FindingEventsEntry, want))
|
||||
}
|
||||
}
|
||||
for option, want := range map[string]string{"max-age-days": "7", "max-mib": "16"} {
|
||||
if got := strings.TrimSpace(string(e.Options[option])); got != want {
|
||||
problems = append(problems, fmt.Sprintf("%s's %s sets %s to %q, not %s (design 47 G §529)", module, FindingEventsEntry, option, got, want))
|
||||
}
|
||||
}
|
||||
slices.Sort(problems)
|
||||
return problems
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ import (
|
||||
// OldSections are the sections of a manifest that keep bus state in the old forms.
|
||||
var OldSections = []string{"state", "logs", "streams", "objects"}
|
||||
|
||||
// Foundation are the modules that keep using the old sections for good: exactly the ten parts the bus module's own
|
||||
// Foundation are the modules that keep using the old sections for good: exactly the parts the bus module's own
|
||||
// composition, assignments, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3, as ADR 0666 §4 changes
|
||||
// them): the bus module, the grants part, the indexer, the assigner, the tool runner, the builder, the checker, mesh-config,
|
||||
// the vault and the pipeline runner, which runs the bus module's own pipeline. Their new state goes on their old sections
|
||||
// the vault, the pipeline runner, which runs the bus module's own pipeline, and the forge module and the image registry
|
||||
// module, which its build calls (design 47 G §584). Their new state goes on their old sections
|
||||
// too, so a foundation manifest asking bus state in the new form is refused.
|
||||
var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools",
|
||||
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault", "mesh-delivery-pipeline"}
|
||||
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault", "mesh-delivery-pipeline", "gitea", "distribution"}
|
||||
|
||||
// ViewOwners are the two owners whose state every reader reads through their first views and read tools, never by a
|
||||
// `reads` entry (novox/hq ADR 0615 §1): CountOwnerReads counts what is left of those entries.
|
||||
|
||||
Reference in New Issue
Block a user