From 3f89c96d205773877a8ddf0765bd001703d09493 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 05:00:03 +0100 Subject: [PATCH] E-1: the tool runner's manifest names no waits and keeps its approvals (issue 1966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit module.json drops waits and its five families, uses operator-channel for the asks of marked waits, and keeps the per-machine state approvals (48 hours) in its old state section; the manifest test holds each change. Builds: design 34 every-event §11, §19 (the manifest test); design 46 7-approval-mark §7, §13 (manifest entry) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../internal/console/manifest_waits_test.go | 58 +++++++++++++------ node-tools/internal/provide/provide_test.go | 13 ++++- node-tools/module.json | 15 ++--- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/node-tools/internal/console/manifest_waits_test.go b/node-tools/internal/console/manifest_waits_test.go index f80ec5f..da628ff 100644 --- a/node-tools/internal/console/manifest_waits_test.go +++ b/node-tools/internal/console/manifest_waits_test.go @@ -8,19 +8,28 @@ import ( "testing" ) -// novox/hq ADR 0367 §5: the tool runner's manifest names the families a session waits on today, one by one, -// and the asker's `decided` for the claude-code module alone. Adding one is a reviewed change to this file -// (§6), so this list changes with it. The delivery's transitions are waited on under the gate's name alone (ADR 0450 §8, issues 1350 -// and 1406): today's module is forgotten. -func TestTheManifestNamesTheFirstFamiliesOneByOne(t *testing.T) { +// The tool runner's manifest names no events to wait on (novox/hq ADR 0670 §2): a session waits on any event by its +// name. It holds each change of design 34 every-event §11 (E-1): no `waits`, `operator-channel` in `uses` for the asks of +// marked waits, the per-machine state `approvals` in the old `state` section with its 48 hours and none in `requires` +// (design 46 7-approval-mark §13), and the finding stream (design 47 G §387). +func TestTheManifestNamesNoWaitsAndKeepsItsApprovals(t *testing.T) { raw, err := os.ReadFile("../../module.json") if err != nil { t.Fatal(err) } + var keys map[string]json.RawMessage + if err := json.Unmarshal(raw, &keys); err != nil { + t.Fatal(err) + } + if _, ok := keys["waits"]; ok { + t.Error("the manifest still carries waits (novox/hq ADR 0670 §2)") + } var m struct { - Module string `json:"module"` - Waits []string `json:"waits"` - Consumes []string `json:"consumes"` + Module string `json:"module"` + Uses []string `json:"uses"` + State []json.RawMessage `json:"state"` + Requires []json.RawMessage `json:"requires"` + Consumes []string `json:"consumes"` Streams []struct { Name string `json:"name"` Events []string `json:"events"` @@ -29,20 +38,33 @@ func TestTheManifestNamesTheFirstFamiliesOneByOne(t *testing.T) { if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } + if m.Module != "node-tools" || !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) { + t.Errorf("%s uses %v, want operator-channel (design 46 7-approval-mark §7)", m.Module, m.Uses) + } + type state struct { + Name string `json:"name"` + PerMachine bool `json:"per-machine"` + TTLSeconds int `json:"ttl-seconds"` + } + var approvals []state + for _, r := range m.State { + var s state + if json.Unmarshal(r, &s) == nil && s.Name == "approvals" { + approvals = append(approvals, s) + } + } + if want := []state{{Name: "approvals", PerMachine: true, TTLSeconds: 172800}}; !reflect.DeepEqual(approvals, want) { + t.Errorf("the old state section's approvals is %+v, want %+v (design 46 7-approval-mark §13)", approvals, want) + } + for _, r := range m.Requires { + if strings.Contains(string(r), `"approvals"`) { + t.Errorf("approvals is asked in requires too: %s", r) + } + } if len(m.Streams) != 1 || m.Streams[0].Name != "node-tools-findings" || !reflect.DeepEqual(m.Streams[0].Events, []string{"finding-written.*", "finding-cleared.*", "findings-stated.*"}) { t.Errorf("the finding stream is %+v, want node-tools-findings with the three finding events (design 47 G §387)", m.Streams) } - want := []string{"mesh-knowledge.reviewed", "mesh-delivery-gate.transition", "gitea.forge.status", - "mesh-self-check.checked", "operator-channel.decided.claude-code"} - if m.Module != "node-tools" || !reflect.DeepEqual(m.Waits, want) { - t.Errorf("%s waits on %v, want %v", m.Module, m.Waits, want) - } - for _, w := range m.Waits { - if strings.ContainsAny(w, "*>") { - t.Errorf("a family named by a wildcard: %s", w) - } - } // A wait is no consumer (ADR 0367 §2): what it consumes is the assigner's two views families (ADR 0615 §5, issue 1966 // TLS-5), the events of the catalog half's branch follower (design 47 G §196, §614: `catalog-revision`, // `registration-changed`, and the gate's `source-moved`; never the indexer's `views.modules` or `views.manifests`), the two diff --git a/node-tools/internal/provide/provide_test.go b/node-tools/internal/provide/provide_test.go index eb35808..0d41e7a 100644 --- a/node-tools/internal/provide/provide_test.go +++ b/node-tools/internal/provide/provide_test.go @@ -126,7 +126,7 @@ func TestTheOfferServesThePortTheManifestServes(t *testing.T) { var m struct { Serves map[string]map[string]any `json:"serves"` Moved bool `json:"moved"` - State []string `json:"state"` + State []json.RawMessage `json:"state"` Reads []string `json:"reads"` Provides []struct { Name string `json:"name"` @@ -172,9 +172,18 @@ func TestTheOfferServesThePortTheManifestServes(t *testing.T) { // Every bucket the sdk's helper reaches is a state the manifest keeps or reads, by the sdk's names: the provider's // own and the seat mesh-provisioner's provisions and plans (issue 1954 L6-T1); the controller's assignments no more. declared := map[string]bool{} - for _, n := range append(m.State, m.Reads...) { + for _, n := range m.Reads { declared[n] = true } + for _, r := range m.State { // an old state entry is its name, or an object naming it (approvals, per machine) + var e struct { + Name string `json:"name"` + } + if json.Unmarshal(r, &e.Name) != nil { + _ = json.Unmarshal(r, &e) + } + declared[e.Name] = true + } for bucket, want := range map[string]string{ provider.BucketOf(Module): "bound", provisions.ProvisionsBucketOf(Provisioner): "mesh-provisioner.provisions", diff --git a/node-tools/module.json b/node-tools/module.json index f206a4d..28fae51 100644 --- a/node-tools/module.json +++ b/node-tools/module.json @@ -6,12 +6,8 @@ "invokes": [ "*" ], - "waits": [ - "mesh-knowledge.reviewed", - "mesh-delivery-gate.transition", - "gitea.forge.status", - "mesh-self-check.checked", - "operator-channel.decided.claude-code" + "uses": [ + "operator-channel" ], "provides": [ { @@ -68,7 +64,12 @@ }, "state": [ "bound", - "findings" + "findings", + { + "name": "approvals", + "per-machine": true, + "ttl-seconds": 172800 + } ], "consumes": [ "mesh-catalog.catalog-revision",