EE-D4: the validator has no rule about the waits list

The manifest no longer carries what a session waits on (mesh-sdk EE-S2), so
the rule judging it and its token form go; a manifest still carrying waits
is a field the validator does not know, refused by Shelf and set aside by
ShelfWaiting, and the tool runner's manifest without it is accepted.

Builds: design 34 every-event §10

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:38:52 +01:00
parent f913d85e66
commit 7ce80ce590
2 changed files with 15 additions and 64 deletions
@@ -2938,7 +2938,6 @@ func parseManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.M
}
}
problems = append(problems, invokeProblems(m)...)
problems = append(problems, waitProblems(m)...)
problems = append(problems, replacesProblems(m)...)
problems = append(problems, UnitSettingProblems(m)...)
problems = append(problems, OwnerSettingProblems(m)...)
@@ -3390,43 +3389,6 @@ func replacesProblems(m catalogue.Manifest) []string {
return problems
}
// waitProblems judges the event families a module says a session may wait on (novox/hq ADR 0367 §2, §6):
// the tool runner's alone, each named whole — emitter and event, with no wildcard — and each once. Refused
// here, in the manifest's words, rather than at the composition of the bus's users.
func waitProblems(m catalogue.Manifest) []string {
if len(m.Waits) == 0 {
return nil
}
if !catalogue.IsToolRunner(m) {
return []string{fmt.Sprintf("%s says what a session waits on; only the tool runner's module (it says "+
"\"tool-runner\": true) declares waits (novox/hq ADR 0367 §2)", m.Module)}
}
var problems []string
seen := map[string]bool{}
for _, w := range m.Waits {
if strings.ContainsAny(w, "*>") {
problems = append(problems, fmt.Sprintf("%s waits on %q: a family is named one by one, never a "+
"wildcard (novox/hq ADR 0367 §2)", m.Module, w))
continue
}
tokens := strings.Split(w, ".")
whole := len(tokens) >= 2 && name.MatchString(tokens[0])
for _, t := range tokens[1:] {
whole = whole && waitToken.MatchString(t)
}
if !whole {
problems = append(problems, fmt.Sprintf("%s waits on %q, which does not name an emitter and an "+
"event: a family is <emitter>.<event>, as consumes names one", m.Module, w))
continue
}
if seen[w] {
problems = append(problems, fmt.Sprintf("%s waits on %q twice", m.Module, w))
}
seen[w] = true
}
return problems
}
// whileStoppedProblems judges one container's maintenance window against its own definition
// (novox/hq ADR 0189).
//
@@ -3646,8 +3608,6 @@ var renamed = map[string]string{
var toolName = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]*$`)
var waitToken = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]*$`)
var engineGeneric = map[string]string{
"database": "postgres-database, mssql-database, mariadb-database",
"db": "postgres-database, mssql-database, mariadb-database",
@@ -3156,29 +3156,20 @@ func caseAVerbWithAnUnknownKeyIsRefused(t *testing.T) {
}
}
// A family is named one by one, never by a wildcard, and only by the tool runner's module (ADR 0367 §2, §6):
// each refusal is said in the manifest's words, at parse, rather than at the composition of the bus's users.
func caseAWaitIsRefusedUnlessItNamesOneFamilyOfTheToolRunner(t *testing.T) {
for manifest, want := range map[string]string{
`{"module":"shop","version":"1.0","waits":["mesh-knowledge.reviewed"]}`: "only the tool runner's module",
`{"module":"node-tools","version":"1.0","waits":["mesh-knowledge.reviewed"]}`: "only the tool runner's module", // its name says nothing (issue 774)
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["mesh-knowledge.*"]}`: "never a wildcard",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["**"]}`: "never a wildcard",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["mesh-knowledge.>"]}`: "never a wildcard",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["*.reviewed"]}`: "never a wildcard",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["reviewed"]}`: "does not name an emitter and an event",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["mesh-knowledge..reviewed"]}`: "does not name an emitter and an event",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["Mesh-Knowledge.reviewed x"]}`: "does not name an emitter and an event",
`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["a.b","a.b"]}`: "twice",
} {
_, err := ParseManifest([]byte(manifest))
if err == nil {
t.Errorf("accepted: %s", manifest)
continue
}
if !strings.Contains(err.Error(), want) {
t.Errorf("%s refused for the wrong reason (want %q): %v", manifest, want, err)
}
// EE-D4 (design 34 every-event §10; ADR 0670 §2, ADR 0578 §1): the manifest no longer names what a session waits on, so
// `waits` is a field the validator does not know: refused by the checker's Shelf naming it, set aside to wait by the
// indexer's ShelfWaiting; the tool runner's manifest without it is accepted.
func caseAManifestCarryingWaitsIsAFieldNotKnown(t *testing.T) {
raws := map[string][]byte{
"node-tools": []byte(`{"module":"node-tools","version":"1.0","tool-runner":true,"waits":["mesh-knowledge.reviewed"]}`),
"runner": []byte(`{"module":"runner","version":"1.0","tool-runner":true}`),
}
if strict, _ := Shelf(raws, nil, 0); len(strict) != 1 || !strings.Contains(strings.Join(strict["node-tools"], " "), "waits") {
t.Errorf("Shelf on a manifest carrying waits: %v", strict)
}
if refused, waits, _ := ShelfWaiting(raws, nil, 0, nil, nil); len(refused) != 0 ||
!reflect.DeepEqual(waits, map[string][]string{"node-tools": {"waits"}}) {
t.Errorf("ShelfWaiting on a manifest carrying waits: refused %v, waits %v", refused, waits)
}
}
@@ -6355,7 +6346,7 @@ func TestRulesBus(t *testing.T) {
t.Run("AServedValueNamingSomethingTheMeshDoesNotHaveIsRefused", caseAServedValueNamingSomethingTheMeshDoesNotHaveIsRefused)
t.Run("ASiteScopedProvisionIsRefused", caseASiteScopedProvisionIsRefused)
t.Run("AStateNameIsLocalAndOneToken", caseAStateNameIsLocalAndOneToken)
t.Run("AWaitIsRefusedUnlessItNamesOneFamilyOfTheToolRunner", caseAWaitIsRefusedUnlessItNamesOneFamilyOfTheToolRunner)
t.Run("AManifestCarryingWaitsIsAFieldNotKnown", caseAManifestCarryingWaitsIsAFieldNotKnown)
t.Run("AWorkQueueStreamIsReadByExactlyOneProcessOfItsModule", caseAWorkQueueStreamIsReadByExactlyOneProcessOfItsModule)
t.Run("AWorkQueueStreamsValuesAreBounded", caseAWorkQueueStreamsValuesAreBounded)
t.Run("AZoneMustNameOneOfTheModulesListens", caseAZoneMustNameOneOfTheModulesListens)