From 636cb633acfdab12c8ea5c67e59593f9301c5b9e Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 10:21:27 +0100 Subject: [PATCH] C3: the manager's two logs in the logs section the runtime issues (issue 1409) Live after #659 the manager, in its own runtime, was still issued none of its logs: the runtime on main issues template states but logs only from the `logs` section. usage (35 days, 256 MiB) and audit (64 MiB) move there; the states stay templates. The whole tests' runtime now issues logs only from `logs`, as main does, and failed on the old manifest. Builds: design 39 bus-and-switch C3 --- modules/claude-licence-manager/README.md | 3 +- .../claude-licence-manager/manifest_test.go | 54 ++++++++++++++++--- .../cmd/claude-licence-manager/tools_test.go | 6 +++ modules/claude-licence-manager/module.json | 28 ++++------ 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/modules/claude-licence-manager/README.md b/modules/claude-licence-manager/README.md index afad6f7d2..1148eef35 100644 --- a/modules/claude-licence-manager/README.md +++ b/modules/claude-licence-manager/README.md @@ -32,7 +32,8 @@ from a file on this module's node or sealed by a node's `claude_code_add_api_key ## What it keeps on the bus -Each piece asked for in `requires` by its template (ADR 0595), read by `licences_kept`: +Each state asked for in `requires` by its template (ADR 0595), and the two logs declared in the `logs` section, the +form the runtime issues today (design 39 bus-and-switch C3); each read by `licences_kept`: | name | template | what | |---|---|---| diff --git a/modules/claude-licence-manager/cmd/claude-licence-manager/manifest_test.go b/modules/claude-licence-manager/cmd/claude-licence-manager/manifest_test.go index 8916b3680..3a63d0a65 100644 --- a/modules/claude-licence-manager/cmd/claude-licence-manager/manifest_test.go +++ b/modules/claude-licence-manager/cmd/claude-licence-manager/manifest_test.go @@ -10,7 +10,8 @@ import ( ) // design 39 bus-and-switch ยง1: the module moves to the new form whole โ€” no `reads`, no old `state`, no `prepare` -// step โ€” and asks for each piece of its kept data by template. +// step โ€” and asks for each state it keeps by template; its two logs are in the `logs` section with their bounds, the +// form the runtime on main issues (C3). func TestTheManifestKeepsNoOldForm(t *testing.T) { raw, err := os.ReadFile(filepath.Join(moduleDir, "module.json")) if err != nil { @@ -44,15 +45,32 @@ func TestTheManifestKeepsNoOldForm(t *testing.T) { } } for name, template := range map[string]string{"licences": "state", "bindings": "state", "leases": "state", - "offered": "state", "usage": "log", "audit": "log", "carried": "state", "conditions": "state"} { + "offered": "state", "usage": "", "audit": "", "carried": "state", "conditions": "state"} { if kept[name] != template { - t.Errorf("bus-state entry %s is %q, want template %s", name, kept[name], template) + t.Errorf("bus-state entry %s is %q in requires, want %q", name, kept[name], template) + } + } + var logs []json.RawMessage + _ = json.Unmarshal(m["logs"], &logs) + declared := logsSection(logs) + for name, bounds := range map[string]map[string]float64{"usage": {"max-age-days": 35, "max-mib": 256}, + "audit": {"max-mib": 64}} { + l, ok := declared[name] + if !ok { + t.Errorf("the log %s is not in the logs section", name) + continue + } + for k, v := range bounds { + if l[k] != v { + t.Errorf("the log %s has %s %v, want %v", name, k, l[k], v) + } } } } // issued is what the manager's own runtime is issued from its manifest (design 39 bus-and-switch C1): its account, the -// states and logs it keeps (`requires` by template), what it may ask (`invokes`) and say (`emits`). +// states it keeps (`requires` by template), the logs it keeps (the `logs` section, the form the runtime on main issues, +// C3; a log asked for by template in `requires` is issued nothing), what it may ask (`invokes`) and say (`emits`). type issued struct { RunsAs, SecretsOwner string OwnSecrets map[string]string @@ -72,6 +90,7 @@ func manifestIssued(t testing.TB) issued { SecretsOwner string `json:"secrets-owner"` OwnSecrets map[string]string `json:"own-secrets"` Requires []json.RawMessage `json:"requires"` + Logs []json.RawMessage `json:"logs"` Resources []map[string]any `json:"resources"` Invokes []string `json:"invokes"` Emits []string `json:"emits"` @@ -86,13 +105,13 @@ func manifestIssued(t testing.TB) issued { if json.Unmarshal(r, &e) != nil { continue } - switch e.Storage { - case "key-value": + if e.Storage == "key-value" { out.States[e.Name] = true - case "stream": - out.Logs[e.Name] = true } } + for name := range logsSection(m.Logs) { + out.Logs[name] = true + } for _, k := range m.Invokes { out.Invokes[k] = true } @@ -102,6 +121,25 @@ func manifestIssued(t testing.TB) issued { return out } +// logsSection is the manifest's `logs` section by name: each a name alone or {name, max-age-days, max-mib}. +func logsSection(logs []json.RawMessage) map[string]map[string]any { + out := map[string]map[string]any{} + for _, l := range logs { + var name string + if json.Unmarshal(l, &name) == nil { + out[name] = map[string]any{} + continue + } + var e map[string]any + if json.Unmarshal(l, &e) == nil { + if n, _ := e["name"].(string); n != "" { + out[n] = e + } + } + } + return out +} + // mayAsk is whether the manager's own account may ask key: a module tool asked on one node (`.@`) // is invoked as `.`. func (i issued) mayAsk(key string) bool { diff --git a/modules/claude-licence-manager/cmd/claude-licence-manager/tools_test.go b/modules/claude-licence-manager/cmd/claude-licence-manager/tools_test.go index b54e8e969..01af59786 100644 --- a/modules/claude-licence-manager/cmd/claude-licence-manager/tools_test.go +++ b/modules/claude-licence-manager/cmd/claude-licence-manager/tools_test.go @@ -203,8 +203,14 @@ func TestKeptReadsEveryEntryOfTheManifest(t *testing.T) { } var m struct { Requires []json.RawMessage `json:"requires"` + Logs []json.RawMessage `json:"logs"` } _ = json.Unmarshal(raw, &m) + // The logs are declared in the logs section (C3), each read as a log. + for name := range logsSection(m.Logs) { + r, _ := json.Marshal(map[string]string{"name": name, "template": "log"}) + m.Requires = append(m.Requires, r) + } mm := newMesh(t) ctx := context.Background() _, _ = saying(mm) diff --git a/modules/claude-licence-manager/module.json b/modules/claude-licence-manager/module.json index ce212f0a8..4a1a86e16 100644 --- a/modules/claude-licence-manager/module.json +++ b/modules/claude-licence-manager/module.json @@ -40,23 +40,6 @@ "key-ttl": true } }, - { - "name": "usage", - "storage": "stream", - "template": "log", - "options": { - "max-age-days": 35, - "max-mib": 256 - } - }, - { - "name": "audit", - "storage": "stream", - "template": "log", - "options": { - "max-mib": 64 - } - }, { "name": "carried", "storage": "key-value", @@ -74,6 +57,17 @@ } } ], + "logs": [ + { + "name": "usage", + "max-age-days": 35, + "max-mib": 256 + }, + { + "name": "audit", + "max-mib": 64 + } + ], "contributes": { "postgres-database": { "name": "claude_licences",