From 1443adc4f0f644c1102a97f67ed58276476c61dd Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 17:09:58 +0100 Subject: [PATCH] mesh-conditions: observe node-tools' and claude-code's first views and the builder's publishing kinds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Their views findings were written and raised by nobody (ADR 0615 §6). The test finds every catalogue module writing a views finding in the tree, so a new writer cannot go unobserved; homes for publish-refused, publish-unanswered, seat-outcome-unread and family-not-heard. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-conditions/homes.go | 8 +++ .../cmd/mesh-conditions/keeper.go | 4 +- .../cmd/mesh-conditions/keeper_test.go | 57 +++++++++++++++++-- modules/mesh-conditions/module.json | 2 + 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/modules/mesh-conditions/cmd/mesh-conditions/homes.go b/modules/mesh-conditions/cmd/mesh-conditions/homes.go index 1bac682cb..4a0a8da7b 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/homes.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/homes.go @@ -111,6 +111,14 @@ var Homes = map[string]Home{ "floor-silent": {Source: "builder", Observer: builder}, "old-form-held": {Source: "builder", Observer: builder}, "tag-removal-not-done": {Source: "builder", Observer: builder}, + // The builder's publishing (design 51): a holder refused the publish, or said nothing past its bound (about the name); + // a holder's outcome the builder cannot read (about the seat). + "publish-refused": {Source: "builder", Observer: builder}, + "publish-unanswered": {Source: "builder", Observer: builder}, + "seat-outcome-unread": {Source: "builder", Observer: builder}, + // A consumer of a delivery part that stopped hearing an event family (ADR 0615 §5), cleared when it binds again: written + // by each of the indexer, the assigner, the builder and the watchman in its own findings, so the kind has no one observer. + "family-not-heard": {Source: "views", Why: "every delivery part whose consumer stopped hearing writes its own"}, // The indexer's own (design 26, ADR 0625): a seat definition that does not parse, generated types out of step with // their definition, one seat defined by two repositories' trunks, a manifest accepted and not said. "seat-definition-unparsed": {Source: "indexer", Observer: indexer}, diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go index 4ea21c90d..a982fe5c7 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go @@ -46,7 +46,9 @@ var Observers = []string{health.WatchmanModule, health.SelfCheckModule, health.H "mesh-watcher", "minio", "nfs-server", gitea, "mesh-build-go", // The builder's, the indexer's (asked of its read tool and followed by its views events, ADR 0615 §1) and the other // readers' of the indexer's and the assigner's state (the registry milestone's join). - builder, indexer, "mesh-delivery-checker", "mesh-delivery-teller", "mesh-delivery-view", enrolment} + builder, indexer, "mesh-delivery-checker", "mesh-delivery-teller", "mesh-delivery-view", enrolment, + // The tool runner's and claude-code's own first views (views.UnansweredFinding in their findings, ADR 0615 §6). + "node-tools", "claude-code"} // The other states it reads, as `.`. const ( diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go index 3c52b87a4..133a492cc 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go @@ -3,6 +3,10 @@ package main import ( "encoding/json" "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" "slices" "sort" "strings" @@ -11,6 +15,7 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/health" ) @@ -279,7 +284,8 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { "data-shrank": selfCheck, "retire-waiting": provisioner, "asks-undelivered": messenger, "image-missing": artifactStore, "build-failed": builder, "floor-silent": builder, "old-form-held": builder, "tag-removal-not-done": builder, "seat-definition-unparsed": indexer, "generated-types-out-of-step": indexer, - "seat-defined-twice": indexer, "accepted-unsaid": indexer, "packages-token-refused": gitea} { + "seat-defined-twice": indexer, "accepted-unsaid": indexer, "packages-token-refused": gitea, + "publish-refused": builder, "publish-unanswered": builder, "seat-outcome-unread": builder} { if h, ok := Homes[kind]; !ok || h.Observer != observer || h.Carried { t.Errorf("%s's home is %+v", kind, h) } @@ -302,21 +308,60 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { } } // The readers' unanswered first view and the owners' views too large to say are every reader's and owner's own. - for _, kind := range []string{"first-view-unanswered", "view-too-large"} { + for _, kind := range []string{"first-view-unanswered", "view-too-large", "family-not-heard"} { if h := Homes[kind]; h.Observer != "" || h.Carried || h.Controller { t.Errorf("%s's home is %+v", kind, h) } } - // Each module that writes a reader's unanswered first view (ADR 0615 §6) is read. - for _, reader := range []string{watchman, "mesh-delivery-teller", enrolment, builder, "mesh-delivery-checker", - "mesh-delivery-view", indexer, configModule, vaultModule, gitea} { + // Each module that writes a reader's unanswered first view (ADR 0615 §6), or that a delivery part's consumer stopped + // hearing, is read: the delivery parts and the tool runner by name (other repositories), and every catalogue module + // whose code writes a views finding, found in the catalogue's tree, so a new writer cannot go unobserved. + readers := []string{watchman, "mesh-delivery-teller", enrolment, builder, "mesh-delivery-checker", "mesh-delivery-view", + indexer, delivery.AssignerModule, configModule, vaultModule, gitea, "node-tools"} + readers = append(readers, viewFindingWriters(t, "../../..")...) + for _, reader := range readers { if !slices.Contains(Observers, reader) { t.Errorf("the reader %s's findings are not read by the keeper", reader) } } // 19 of ADR 0564, the 63 of the table, plan-failed and stalled, mesh-config's four (design 32), the readers' unanswered // first view, the owners' view too large, the builder's four, the indexer's four and the package seats' token refused. - if len(Homes) != 99 || carried != 37 { + // And the builder's three publishing kinds and the delivery parts' family not heard: 103. + if len(Homes) != 103 || carried != 37 { t.Errorf("the table holds %d kinds, %d carried", len(Homes), carried) } } + +// viewFindingWriters are the catalogue modules (under modules) whose non-test Go code writes a views finding into its own +// findings: views.UnansweredFinding, views.TooLargeFinding or views.OwnFindings. +func viewFindingWriters(t *testing.T, modules string) []string { + t.Helper() + writes := regexp.MustCompile(`views\.(UnansweredFinding|TooLargeFinding|OwnFindings)\b`) + var out []string + entries, err := os.ReadDir(modules) + if err != nil { + t.Fatal(err) + } + for _, e := range entries { + if !e.IsDir() || e.Name() == Module { + continue + } + found := false + _ = filepath.WalkDir(filepath.Join(modules, e.Name()), func(path string, d fs.DirEntry, err error) error { + if err != nil || found || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { + return nil + } + if src, err := os.ReadFile(path); err == nil && writes.Match(src) { + found = true + } + return nil + }) + if found { + out = append(out, e.Name()) + } + } + if len(out) < 5 { + t.Fatalf("found %d catalogue modules writing views findings in %s: the tree was not read (%v)", len(out), modules, out) + } + return out +} diff --git a/modules/mesh-conditions/module.json b/modules/mesh-conditions/module.json index b40fea473..3ae20c823 100644 --- a/modules/mesh-conditions/module.json +++ b/modules/mesh-conditions/module.json @@ -32,6 +32,8 @@ "mesh-delivery-teller.findings", "mesh-delivery-view.findings", "mesh-enrolment.findings", + "node-tools.findings", + "claude-code.findings", "mesh-healers.attempts", "node-engine.have" ],