From 4ce2d82645238624d373be1e068a8096da985ebc Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:42:08 +0100 Subject: [PATCH 1/7] D1 judge-queue-invokes: an asker's queue and a seat's accepted kind are judged against the seat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The third reader release reads G's invokes `seat:.accept.` and `seat:..` with G's check of them: a queue or kind the seat's definition does not accept is refused, naming it. Builds: design 47 G §601, A-231, A-102, A-343, A-344 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/rules.go | 32 +++++++++++++++++-- .../mesh-delivery-indexer/judge/rules_test.go | 19 ++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index 1b69c235..e0afd1d2 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -4067,12 +4067,24 @@ func filepathDir(p string) string { } // invokeNameValid says a module or process's invoked entry names a tool: ., a role's -// seat:., or * for every tool on the mesh (novox/hq ADR 0152, ADR 0436 §1). +// seat:. or a kind the seat accepts (seat:., seat:.., design 47 G A-102), +// or * for every tool on the mesh (novox/hq ADR 0152, ADR 0436 §1). func invokeNameValid(t string) bool { if t == "*" { return true } - t = strings.TrimPrefix(t, "seat:") + if rest, isSeat := strings.CutPrefix(t, "seat:"); isSeat { + // A seat's verb, or a kind the seat accepts: ``, or `.` for one that rides a stream (design 47 G + // A-102), an asker's queue `accept.` among them (§601, A-231); seatInvokeProblems judges it against the seat. + seat, after, named := strings.Cut(rest, ".") + if !named || !name.MatchString(seat) { + return false + } + if stream, kind, onStream := strings.Cut(after, "."); onStream { + return toolName.MatchString(stream) && toolName.MatchString(kind) + } + return toolName.MatchString(after) + } first, second, named := strings.Cut(t, ".") return named && name.MatchString(first) && toolName.MatchString(second) } @@ -4586,7 +4598,12 @@ func seatInvokeProblems(shelf catalogue.Shelf, declared map[string]catalogue.Sea problems = append(problems, fmt.Sprintf("%s invokes %s, a verb of the seat %q, "+catalogue.UndeclaredSeat, module, invoke, seat)) continue } - if !slices.ContainsFunc(verbs, func(v catalogue.Verb) bool { return v.Name == verb }) { + // An asker's queue of a build seat, `seat:.accept.` (design 47 G §544, §601, A-231), read by mesh-sdk's + // one reader: judged against the kinds the seat's definition accepts. + if _, queue, isQueue := catalogue.QueueInvoke(invoke); isQueue { + verb = queue + } + if !slices.ContainsFunc(verbs, func(v catalogue.Verb) bool { return v.Name == verb }) && !acceptsKind(seat, verb, declared) { problems = append(problems, fmt.Sprintf("%s invokes %s, and the seat %s serves no verb %q", module, invoke, seat, verb)) continue } @@ -4598,6 +4615,15 @@ func seatInvokeProblems(shelf catalogue.Shelf, declared map[string]catalogue.Sea return problems } +// acceptsKind says the seat accepts the kind an invoke names after its seat (design 47 G A-102): ``, or +// `.` for a kind that rides a stream. A module's own seat names its accepts by kind alone. +func acceptsKind(seat, kind string, declared map[string]catalogue.SeatDeclaration) bool { + if s, isMesh := catalogue.SeatNamed(seat); isMesh { + return slices.Contains(s.Accepts, kind) || slices.Contains(s.AcceptedOn, kind) + } + return slices.Contains(declared[seat].Accepts, kind) +} + // SeatPathProblems is what is wrong with the paths a set of the mesh's own seats names (novox/hq ADR // 0287): only a node seat names one, each absolute and literal; every path has exactly one seat naming it // without stepping back; and a seat steps back only for another node seat naming the same path without diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index abc533d4..b7497f0a 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -5240,6 +5240,11 @@ func caseTwoModulesCannotDeclareTheSameSeat(t *testing.T) { func caseUsingASeatNobodyDeclaresIsRefused(t *testing.T) { unclaimed := telegram() unclaimed.Claims = nil + // A mesh seat that accepts a kind riding a stream (design 47 G A-102), beside the seat list's own. + was := catalogue.Seats() + t.Cleanup(func() { catalogue.UseSeats(was) }) + catalogue.UseSeats(append(catalogue.Seats(), catalogue.Seat{Name: "mesh-steps", Scope: catalogue.ScopeMesh, Decision: "test", + Accepts: []string{"step"}, AcceptedOn: []string{"check.step"}})) for _, row := range []struct { name, want string shop catalogue.Manifest @@ -5268,8 +5273,20 @@ func caseUsingASeatNobodyDeclaresIsRefused(t *testing.T) { {"what the engine reads of a manifest, and its event (ADR 0618 §3, §4)", "", catalogue.Manifest{Module: "shop", Invokes: []string{"seat:node-engine.manifest-reads"}, Consumes: []string{"node-engine.manifest-reads-changed"}}, telegram(), false}, + // An asker names the queue it asks as `seat:.accept.`, checked against the kinds the seat's definition + // accepts (design 47 G §601, A-231); a kind the seat accepts, alone or on its stream, as `seat:.` or + // `seat:..` (A-102). + {"a queue a build seat accepts", "", catalogue.Manifest{Module: "shop", Invokes: []string{"seat:node-build-go.accept.check"}}, telegram(), false}, + {"a queue a build seat does not accept", `the seat node-build-go serves no verb "other"`, + catalogue.Manifest{Module: "shop", Invokes: []string{"seat:node-build-go.accept.other"}}, telegram(), false}, + {"a queue a declared seat accepts", "", catalogue.Manifest{Module: "shop", Invokes: []string{"seat:telegram-sender.accept.send"}}, telegram(), false}, + {"a kind a seat accepts", "", catalogue.Manifest{Module: "shop", Invokes: []string{"seat:mesh-steps.step"}}, telegram(), false}, + {"a kind a seat accepts on its stream", "", catalogue.Manifest{Module: "shop", Invokes: []string{"seat:mesh-steps.check.step"}}, telegram(), false}, + {"a kind on a stream the seat does not take it on", `the seat mesh-steps serves no verb "delivery.step"`, + catalogue.Manifest{Module: "shop", Invokes: []string{"seat:mesh-steps.delivery.step"}}, telegram(), false}, } { - got := CatalogueProblems(catalogue.Shelf{"telegram": row.seat, "shop": row.shop}) + // The manifest's own invokes rule (at parse) takes the dotted kind as well; the seat's rule then judges it. + got := append(invokeProblems(row.shop), CatalogueProblems(catalogue.Shelf{"telegram": row.seat, "shop": row.shop})...) joined := strings.Join(got, "; ") if row.want == "" && joined != "" || row.want != "" && (len(got) != 1 || !strings.Contains(joined, row.want) || catalogue.IsUndeclaredSeat(got[0]) != row.note) { From 3543e7818bc05a342c7d6445a3e3b5302e084af3 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:42:54 +0100 Subject: [PATCH 2/7] D5 judge-process-bus-consumes: a process hears only what its module consumes A process's `bus.consumes` (read by mesh-sdk since S2) is judged as V5 judges what a process names of its module's `requires`: an event its module consumes is accepted, another is refused, naming it. Builds: design 47 G A-340, A-343 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/rules.go | 8 ++++++++ modules/mesh-delivery-indexer/judge/rules_test.go | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index e0afd1d2..05fa7edc 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -4034,6 +4034,14 @@ func ProcessBusProblems(m catalogue.Manifest) []string { m.Module, id, e, m.Module)) } } + // Design 47 G A-340: a process hears only what its module consumes, as V5 lets it name only what its module asks for. + for _, e := range b.Consumes { + if !slices.Contains(m.Consumes, e) { + problems = append(problems, fmt.Sprintf( + "%s's process %s's bus consumes %q, which %s does not consume (design 47 G A-340)", + m.Module, id, e, m.Module)) + } + } for _, q := range b.Queues { if !streams[q] { problems = append(problems, fmt.Sprintf( diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index b7497f0a..fb8bec60 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -2459,7 +2459,7 @@ func caseAProcessBusNamesOnlyTheModulesOwn(t *testing.T) { // its events stream and a state of its key-value state, and a name it does not ask for there is refused as before. asked := func(bus string) string { raw := mailSenderProcessManifest(t, bus) - return strings.Replace(raw, `"resources"`, `"requires":[{"name":"mail-events","storage":"stream","template":"events",`+ + return strings.Replace(raw, `"resources"`, `"consumes":["mesh-conditions.findings-asked"],"requires":[{"name":"mail-events","storage":"stream","template":"events",`+ `"options":{"events":["mail.send.queued"]}},{"name":"mail-kept","storage":"key-value","template":"state"}],"resources"`, 1) } for name, c := range map[string]struct{ bus, says string }{ @@ -2467,6 +2467,9 @@ func caseAProcessBusNamesOnlyTheModulesOwn(t *testing.T) { "a state asked for by template": {`{"state":["mail-kept"]}`, ""}, "an event the events stream does not hold": {`{"emits":["mail.send.lost"]}`, "mail.send.lost"}, "a state the module does not ask for": {`{"state":["mail-other"]}`, "mail-other"}, + // Design 47 G A-340: a process's bus may name, under consumes, an event its module consumes, judged as V5 judges. + "an event the module consumes": {`{"consumes":["mesh-conditions.findings-asked"]}`, ""}, + "an event the module does not consume": {`{"consumes":["mesh-conditions.other"]}`, `consumes "mesh-conditions.other"`}, } { _, err := ParseManifest([]byte(asked(c.bus))) if (c.says == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.says)) { From cee3102568aa15363397e9adaaf2b90904863898 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:43:47 +0100 Subject: [PATCH 3/7] D2 judge-seat-reads: a read of a seat holder's state is judged against the seat's states `reads` `seat:.` (split by mesh-sdk's SeatRead since S4) is accepted where the seat declares the state and refused, naming it, where it does not or the seat is unknown; a module's read is judged as today. Builds: design 47 G A-116, A-128, A-343, A-344 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/rules.go | 13 +++++++++++++ modules/mesh-delivery-indexer/judge/rules_test.go | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index 05fa7edc..01dfe290 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -5652,6 +5652,19 @@ func StateProblems(m catalogue.Manifest) []string { } } for _, r := range m.Reads { + if seat, state, isSeat, err := catalogue.SeatRead(r); isSeat { + // A read of a seat holder's state (design 47 G A-116, A-128): the seat declares the state; the holder is resolved + // where it is read. + switch s, known := catalogue.SeatNamed(seat); { + case err != nil: + problems = append(problems, fmt.Sprintf("%s reads %v", m.Module, err)) + case !known: + problems = append(problems, fmt.Sprintf("%s reads %q, a state of the seat %q, "+catalogue.UndeclaredSeat, m.Module, r, seat)) + case !slices.Contains(s.States, state): + problems = append(problems, fmt.Sprintf("%s reads %q, and the seat %s declares no state %q (design 47 G A-128)", m.Module, r, seat, state)) + } + continue + } module, _, err := catalogue.ReadState(r) if err != nil { problems = append(problems, fmt.Sprintf("%s reads %v", m.Module, err)) diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index fb8bec60..6be77af8 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -2950,6 +2950,11 @@ func caseAStateNameIsLocalAndOneToken(t *testing.T) { {`{"module":"a.b","version":"1","state":["s"]}`, `no dot`}, {`{"module":"a","version":"1","reads":["bindings"]}`, `a read is .`}, {`{"module":"a","version":"1","reads":["a.s"]}`, `which is its own state`}, + // A read of a seat holder's state names the seat, which declares the state (design 47 G A-116, A-128). + {`{"module":"a","version":"1","reads":["seat:mesh-conditions.findings"]}`, ``}, + {`{"module":"a","version":"1","reads":["seat:mesh-conditions.nothing"]}`, `the seat mesh-conditions declares no state "nothing"`}, + {`{"module":"a","version":"1","reads":["seat:mesh-unknown.findings"]}`, `a state of the seat "mesh-unknown"`}, + {`{"module":"a","version":"1","reads":["seat:mesh-conditions"]}`, `does not name a seat and its state`}, {`{"module":"a","version":"1","state":[{"name":"s","shared":true}]}`, `{name, history, ttl-seconds, per-machine, key-ttl, read-only, max-mib, form, catch-up}`}, } { _, err := ParseManifest([]byte(c.manifest)) From 771471c83b32ddcb00a0d61db1e67c84e7eae044 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:44:20 +0100 Subject: [PATCH 4/7] D3 judge-objects-cap: a state may name objects-max-mib for its objects store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The state template takes the option `objects-max-mib` (mesh-sdk S5's name and bounds): within the objects' bounds it is accepted, outside them refused; every other option of today keeps its verdict. Builds: design 47 G §513, §547, A-343 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/busstate.go | 3 +++ modules/mesh-delivery-indexer/judge/busstate_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/modules/mesh-delivery-indexer/judge/busstate.go b/modules/mesh-delivery-indexer/judge/busstate.go index bd7c0cf6..5db56a78 100644 --- a/modules/mesh-delivery-indexer/judge/busstate.go +++ b/modules/mesh-delivery-indexer/judge/busstate.go @@ -49,6 +49,9 @@ var busTemplates = map[string]busTemplate{ catalogue.OptionTimeToLive: {kind: optionInt, least: 1, most: 315360000}, catalogue.OptionLifetimePerKey: {kind: optionFlag}, catalogue.OptionPerMachine: {kind: optionFlag}, + // The cap of the state's own objects store `-objects`, for a state needing more than the cap planned for it + // (design 47 G §513, §547). + catalogue.OptionObjectsCapMiB: {kind: optionInt, least: catalogue.ObjectsLeastMiB, most: catalogue.ObjectsMostMiB}, }, }, catalogue.TemplateEvents: { diff --git a/modules/mesh-delivery-indexer/judge/busstate_test.go b/modules/mesh-delivery-indexer/judge/busstate_test.go index 8f68a4b7..8c97ec71 100644 --- a/modules/mesh-delivery-indexer/judge/busstate_test.go +++ b/modules/mesh-delivery-indexer/judge/busstate_test.go @@ -52,6 +52,10 @@ func TestBusStateIsJudgedOnce(t *testing.T) { {"template on another storage type", manifest(`{"name":"x","storage":"stream","template":"state"}`), []string{"\"x\"", "template state is on the storage type key-value"}}, {"past values 0", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"history":0}}`), []string{"\"x\"", "options \"history\" is 0", "0 is refused"}}, {"past values 65", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"history":65}}`), []string{"\"x\"", "\"history\" is 65", "1 to 64"}}, + // Design 47 G §513, §547: a state needing more for its objects store names objects-max-mib, within the objects' bounds. + {"an objects cap on a state", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"objects-max-mib":64}}`), nil}, + {"an objects cap of 0", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"objects-max-mib":0}}`), []string{"\"x\"", "\"objects-max-mib\" is 0"}}, + {"an objects cap over 8192", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"objects-max-mib":8193}}`), []string{"\"x\"", "\"objects-max-mib\" is 8193"}}, {"a lifetime that is no number", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"ttl-seconds":"soon"}}`), []string{"\"x\"", "\"ttl-seconds\""}}, {"events age over 30 days", manifest(`{"name":"x","storage":"stream","template":"events","options":{"events":["seen"],"max-age-days":31}}`), []string{"\"x\"", "\"max-age-days\" is 31", "1 to 30"}}, {"events cap over 8192", manifest(`{"name":"x","storage":"stream","template":"events","options":{"events":["seen"],"max-mib":8193}}`), []string{"\"x\"", "\"max-mib\" is 8193"}}, From b30232298c93942ae99fba3098e81f0257086e19 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:45:26 +0100 Subject: [PATCH 5/7] D4 judge-foundation-list: the parts G puts on the foundation list keep their old sections before it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before G's merge the foundation list stays as it is; the forge module, the image registry module and the node-engine's module are accepted with their old `objects` section, their stream `-findings` and their bus state in `requires`, as G's judge accepts them, and are counted as today, so no other module's verdict moves. Builds: design 47 G §584, §627, §529, A-343, A-345 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/judge_test.go | 13 +++++++++++-- modules/mesh-delivery-indexer/judge/oldsections.go | 8 +++++++- modules/mesh-delivery-indexer/judge/rules.go | 5 +++-- modules/mesh-delivery-indexer/judge/rules_test.go | 5 +++++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/judge_test.go b/modules/mesh-delivery-indexer/judge/judge_test.go index 0eaf20e8..5cecb643 100644 --- a/modules/mesh-delivery-indexer/judge/judge_test.go +++ b/modules/mesh-delivery-indexer/judge/judge_test.go @@ -116,7 +116,15 @@ func TestTheJudge(t *testing.T) { "user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"]}`)}, "late": {Raw: json.RawMessage(`{"module":"late","version":"1","logs":[{"name":"l"}]}`)}, "newer": {Raw: json.RawMessage(`{"module":"newer","version":"1","state":["y"]}`)}, - "mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)}}}, + "mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)}, + // Design 47 G §584, §627, A-345: the parts G puts on the foundation list keep their old sections before it, the + // old objects section, the stream `-findings` and bus state in `requires` among them; another module's + // old objects section, which no trunk module uses, is refused as before. + "gitea": {Raw: json.RawMessage(`{"module":"gitea","version":"1","objects":[{"name":"incoming","max-mib":64}],` + + `"requires":[{"name":"idle","storage":"key-value","template":"state"}]}`)}, + "mesh-host": {Raw: json.RawMessage(`{"module":"mesh-host","version":"1","streams":[` + + `{"name":"mesh-host-findings","events":["finding-written.*"],"max-age-days":7,"max-mib":16}]}`)}, + "keeper": {Raw: json.RawMessage(`{"module":"keeper","version":"1","objects":[{"name":"incoming","max-mib":64}]}`)}}}, {Name: "trunk", Manifests: map[string]composer.Manifest{ "user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"]}`)}, "mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)}}}}} @@ -126,7 +134,8 @@ func TestTheJudge(t *testing.T) { } got := ov.Cases[0].Manifests if got["late"].Accepted || !strings.Contains(got["late"].Refused, "late uses the old section logs") || - !got["newer"].Accepted || !got["user"].Accepted || !got["mesh-grants"].Accepted { + !got["newer"].Accepted || !got["user"].Accepted || !got["mesh-grants"].Accepted || !got["gitea"].Accepted || + !got["mesh-host"].Accepted || got["keeper"].Accepted || !strings.Contains(got["keeper"].Refused, "keeper uses the old section objects") { t.Errorf("the change's old sections: %+v", got) } r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)} diff --git a/modules/mesh-delivery-indexer/judge/oldsections.go b/modules/mesh-delivery-indexer/judge/oldsections.go index 91a2446d..d4e28eb8 100644 --- a/modules/mesh-delivery-indexer/judge/oldsections.go +++ b/modules/mesh-delivery-indexer/judge/oldsections.go @@ -23,6 +23,12 @@ var OldSections = []string{"state", "logs", "streams", "objects"} var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools", "mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault"} +// foundationAtG are the parts design 47 G puts on the foundation list in its own change: the forge module and the image +// registry module (G §584) and the node-engine's module (§627). Until then their old sections are accepted as a foundation +// part's, its old `objects` section and its stream `-findings` among them, and their bus state in `requires` as +// today (A-343, A-345); they are counted as today, so no other module's verdict moves. +var foundationAtG = []string{"gitea", "distribution", "mesh-host"} + // 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. var ViewOwners = []string{"mesh-delivery-indexer", "mesh-delivery-assigner"} @@ -90,7 +96,7 @@ func OldSectionProblems(m catalogue.Manifest, counts OldSectionCounts) []string } return nil } - if counts == nil { + if counts == nil || slices.Contains(foundationAtG, m.Module) { return nil } var problems []string diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index 01dfe290..6af40f0a 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -5701,8 +5701,9 @@ func PredefinedFormProblems(m catalogue.Manifest) []string { for _, s := range m.Streams { // A stream that keeps its history is one of the bus's predefined forms (novox/hq ADR 0592 §6). // A foundation part keeps its old sections for good, its streams among them: its findings stream - // `-findings` (design 47 G §529, A-30, A-146). - if !s.KeepsHistory() && !slices.Contains(Foundation, m.Module) && !slices.Contains(streamsKeptUntilMoved[m.Module], s.Name) { + // `-findings` (design 47 G §529, A-30, A-146); so do the parts G puts on the list (A-345). + foundation := slices.Contains(Foundation, m.Module) || slices.Contains(foundationAtG, m.Module) + if !s.KeepsHistory() && !foundation && !slices.Contains(streamsKeptUntilMoved[m.Module], s.Name) { out = append(out, fmt.Sprintf("%s keeps the stream %q, outside the bus's predefined forms: keep it as a state, "+ "a log or an object store, which the runtime reads for the module's tools and others read by `reads`", m.Module, s.Name)) } diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index 6be77af8..5da4519e 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -5226,6 +5226,11 @@ func caseTwoModulesCannotDeclareOneStream(t *testing.T) { if got := PredefinedFormProblems(catalogue.Manifest{Module: "mesh-delivery-indexer", Streams: findings}); len(got) != 0 { t.Fatalf("a foundation part's findings stream was refused: %v", got) } + // So does a part G puts on the list, before it (design 47 G §627, A-345). + hostFindings := []catalogue.StreamDeclaration{{Name: "mesh-host-findings", Events: []string{"finding-written.*"}}} + if got := PredefinedFormProblems(catalogue.Manifest{Module: "mesh-host", Streams: hostFindings}); len(got) != 0 { + t.Fatalf("the node-engine's findings stream was refused: %v", got) + } } // A seat name meaning two protocols is the failure nobody could diagnose afterwards. From 1a16fb55da82508525776e7c7abf5445540ee0d6 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:47:21 +0100 Subject: [PATCH 6/7] D4 judge-foundation-list: the pipeline runner keeps its old sections before G too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lead's A-349 names the pipeline runner beside the forge module, the image registry module and the node-engine's module: its old `objects` section and its findings stream are accepted before G puts it on the foundation list. It is new in G, so no trunk verdict moves. Builds: design 47 G §529, A-345, A-349 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/judge_test.go | 4 +++- modules/mesh-delivery-indexer/judge/oldsections.go | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/judge_test.go b/modules/mesh-delivery-indexer/judge/judge_test.go index 5cecb643..93457894 100644 --- a/modules/mesh-delivery-indexer/judge/judge_test.go +++ b/modules/mesh-delivery-indexer/judge/judge_test.go @@ -124,6 +124,8 @@ func TestTheJudge(t *testing.T) { `"requires":[{"name":"idle","storage":"key-value","template":"state"}]}`)}, "mesh-host": {Raw: json.RawMessage(`{"module":"mesh-host","version":"1","streams":[` + `{"name":"mesh-host-findings","events":["finding-written.*"],"max-age-days":7,"max-mib":16}]}`)}, + "mesh-delivery-pipeline": {Raw: json.RawMessage(`{"module":"mesh-delivery-pipeline","version":"1",` + + `"objects":[{"name":"step-outputs","max-mib":64}]}`)}, // and the pipeline runner (§529, A-349) "keeper": {Raw: json.RawMessage(`{"module":"keeper","version":"1","objects":[{"name":"incoming","max-mib":64}]}`)}}}, {Name: "trunk", Manifests: map[string]composer.Manifest{ "user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"]}`)}, @@ -135,7 +137,7 @@ func TestTheJudge(t *testing.T) { got := ov.Cases[0].Manifests if got["late"].Accepted || !strings.Contains(got["late"].Refused, "late uses the old section logs") || !got["newer"].Accepted || !got["user"].Accepted || !got["mesh-grants"].Accepted || !got["gitea"].Accepted || - !got["mesh-host"].Accepted || got["keeper"].Accepted || !strings.Contains(got["keeper"].Refused, "keeper uses the old section objects") { + !got["mesh-host"].Accepted || !got["mesh-delivery-pipeline"].Accepted || got["keeper"].Accepted || !strings.Contains(got["keeper"].Refused, "keeper uses the old section objects") { t.Errorf("the change's old sections: %+v", got) } r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)} diff --git a/modules/mesh-delivery-indexer/judge/oldsections.go b/modules/mesh-delivery-indexer/judge/oldsections.go index d4e28eb8..a7beaa35 100644 --- a/modules/mesh-delivery-indexer/judge/oldsections.go +++ b/modules/mesh-delivery-indexer/judge/oldsections.go @@ -24,10 +24,10 @@ var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh- "mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault"} // foundationAtG are the parts design 47 G puts on the foundation list in its own change: the forge module and the image -// registry module (G §584) and the node-engine's module (§627). Until then their old sections are accepted as a foundation -// part's, its old `objects` section and its stream `-findings` among them, and their bus state in `requires` as -// today (A-343, A-345); they are counted as today, so no other module's verdict moves. -var foundationAtG = []string{"gitea", "distribution", "mesh-host"} +// registry module (G §584), the node-engine's module (§627) and the pipeline runner (§529, A-349). Until then their old +// sections are accepted as a foundation part's, its old `objects` section and its stream `-findings` among them, +// and their bus state in `requires` as today (A-343, A-345); they are counted as today, so no other module's verdict moves. +var foundationAtG = []string{"gitea", "distribution", "mesh-host", "mesh-delivery-pipeline"} // 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. From 4b29e43ca1f49c21dd758f418e94060036631054 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:47:46 +0100 Subject: [PATCH 7/7] D6 judge-release-whole: the release's judge accepts all of G and keeps today's verdicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A run, no code: the prepare's probe (reader-release runs/rrprobe.go.txt) built in the indexer module at this head against mesh-sdk 9e5bd7e. G's 193 manifests with G's 65 seat definitions: 193 accepted the checker's way, 0 refused and 0 waiting the indexer's way (today: 15 refused). Today's 191 manifests with today's 60 seats: 189 accepted, f1-circuits and link2pay refused, output identical to today's; today's manifests with G's seats: identical to today's. Builds: design 47 G §50, §340, A-343, A-347 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h