From b5c5594ea7892c63e88095248c5e4ae2f16a95c1 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 15:31:47 +0100 Subject: [PATCH] licences_switches answers the old field names of carried entries (issue 1409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 39 bus-and-switch §37, §12 and the lead's answer C5 (hq 45ee2475). --- .../claude-licence-manager/cmd/tools/tools.go | 16 +++++++++++++++ .../cmd/tools/tools_test.go | 20 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/modules/claude-licence-manager/cmd/tools/tools.go b/modules/claude-licence-manager/cmd/tools/tools.go index 6f4e647fb..ed4f9e2d4 100644 --- a/modules/claude-licence-manager/cmd/tools/tools.go +++ b/modules/claude-licence-manager/cmd/tools/tools.go @@ -92,6 +92,21 @@ func (r *Reader) Tools() []stdio.Tool { } } +// carriedForm gives an entry carried over from the old table `audit` (§12) the fields of §37: the old manager wrote a +// switch as `consumer` (one), `licence` (the one moved to), `from` and `by`, and no `consumers` or `to`. +func carriedForm(e map[string]any) { + if _, ok := e["to"]; !ok { + if l, ok := e["licence"]; ok { + e["to"] = l + } + } + if _, ok := e["consumers"]; !ok { + if c, ok := e["consumer"]; ok { + e["consumers"] = []any{c} + } + } +} + // Switches are the audit's switches, newest first: of a licence (from or to it) and a consumer when named, none before // since, at most limit (§37). func (r *Reader) Switches(licence, consumer string, since time.Time, limit int) ([]map[string]any, error) { @@ -118,6 +133,7 @@ func (r *Reader) Switches(licence, consumer string, since time.Time, limit int) if json.Unmarshal(all[i].Value, &e) != nil { continue } + carriedForm(e) at, _ := time.Parse(time.RFC3339Nano, fmt.Sprint(e["at"])) if at.Before(since) { continue diff --git a/modules/claude-licence-manager/cmd/tools/tools_test.go b/modules/claude-licence-manager/cmd/tools/tools_test.go index 6df0bf48b..15c03e577 100644 --- a/modules/claude-licence-manager/cmd/tools/tools_test.go +++ b/modules/claude-licence-manager/cmd/tools/tools_test.go @@ -229,6 +229,26 @@ func TestSwitchesAnswersBothCauses(t *testing.T) { } } +// design 39 bus-and-switch §37, §12: a switch carried over from the old table keeps the old field names (consumer, +// licence) and is answered with `to` and `consumers`, and found by them. +func TestSwitchesAnswersCarriedEntries(t *testing.T) { + rt := newRuntime(t) + rt.seedLog(licences.EntryAudit, "switched", map[string]any{"at": "2026-11-11T21:10:33Z", "consumer": "novox", + "licence": "d", "from": "c", "by": "jschoubben@novox.be"}) + r := rt.reader() + for _, args := range []map[string]any{nil, {"consumer": "novox"}, {"licence": "d"}} { + got := run(t, r, "licences_switches", args).([]any) + if len(got) != 1 { + t.Fatalf("%v: %v", args, got) + } + e := got[0].(map[string]any) + if e["to"] != "d" || e["from"] != "c" || e["by"] != "jschoubben@novox.be" || + len(e["consumers"].([]any)) != 1 || e["consumers"].([]any)[0] != "novox" { + t.Fatalf("%v: %v", args, e) + } + } +} + // design 39 bus-and-switch §40/AC1, B18, design 39 parts §3: a value set at node level is answered with its level, the // rest with the default, each with its value and default, and an invalid one saying why, read from the settings file; // mesh-config not answering is an error, never the default.