From cc8a354f754b72c5cc8a0fe97bec7f6d2a565e32 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 01:02:14 +0100 Subject: [PATCH] M-3 fix: an adopted licence says where it came from, and a newer login keeps its reading (issue 1409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 1, finding 3: adoptedFrom is the node a login came from, or where an API key came from; a newer login of the same account keeps the latest reading and generation the key holds. Builds: design 39 bus-and-switch §2, §34, §46 Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ --- .../cmd/claude-licence-manager/manager.go | 9 +++++---- .../cmd/claude-licence-manager/manager_test.go | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/claude-licence-manager/cmd/claude-licence-manager/manager.go b/modules/claude-licence-manager/cmd/claude-licence-manager/manager.go index 5c0ca7f31..12e7d2f2a 100644 --- a/modules/claude-licence-manager/cmd/claude-licence-manager/manager.go +++ b/modules/claude-licence-manager/cmd/claude-licence-manager/manager.go @@ -384,9 +384,10 @@ func (m *Manager) adoptOne(ctx context.Context, c Candidate, reports []Holdings, } now := m.Now().UnixMilli() l := Licence{Name: NameFor(c.Identity), Kind: "subscription", AccountUUID: c.Identity.AccountUUID, - Email: c.Identity.EmailAddress, OrganizationUUID: c.Identity.OrganizationUUID} + Email: c.Identity.EmailAddress, OrganizationUUID: c.Identity.OrganizationUUID, AdoptedFrom: c.Node} if held != nil { - l.Name, l.NotifiedAt, l.Generation = held.Name, held.NotifiedAt, held.Generation + // A newer login keeps what the key holds beside its grant: its latest reading (§2, §24). + l.Name, l.NotifiedAt, l.Generation, l.Usage = held.Name, held.NotifiedAt, held.Generation, held.Usage if l.Email == "" { l.Email = held.Email } @@ -898,9 +899,9 @@ func (m *Manager) adoptKey(ctx context.Context, name, key, from string) (map[str _ = m.Emit("licence.refused", map[string]any{"licence": name, "from": from, "reason": why}) return nil, fmt.Errorf("%s is refused: %s", name, why) } - l := Licence{Name: name, Kind: "api-key", AdoptedAt: m.Now().UnixMilli()} + l := Licence{Name: name, Kind: "api-key", AdoptedAt: m.Now().UnixMilli(), AdoptedFrom: from} if held != nil { - l.Generation = held.Generation + l.Generation, l.Usage = held.Generation, held.Usage } if err := m.handOver(ctx, &l, key, key); err != nil { return nil, err diff --git a/modules/claude-licence-manager/cmd/claude-licence-manager/manager_test.go b/modules/claude-licence-manager/cmd/claude-licence-manager/manager_test.go index c0d645616..09c91fbe7 100644 --- a/modules/claude-licence-manager/cmd/claude-licence-manager/manager_test.go +++ b/modules/claude-licence-manager/cmd/claude-licence-manager/manager_test.go @@ -231,6 +231,12 @@ func TestANewerLoginReplacesTheGrantHeldAndAnOlderOneDoesNot(t *testing.T) { ctx := context.Background() _, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-a", true, t0.Add(-time.Minute))}) before, _ := mm.store.Licence(ctx, licence1) + // §2: a licence says where it was adopted from, and a newer login keeps the latest reading the key holds. + pct := 42.0 + before.Usage = &UsageReading{SessionPct: &pct, ReadAt: t0.Format(time.RFC3339Nano)} + if before.AdoptedFrom != "laptop" || mm.store.SaveLicence(ctx, *before) != nil { + t.Fatalf("the adopted licence is %+v", before) + } _, _ = mm.m.Consider(ctx, []Holdings{mm.login("server", "rt-old", true, t0.Add(-24*time.Hour))}) if strings.Join(mm.vendor.exchanged, ",") != "rt-a" { t.Fatalf("an older login was exchanged: %v", mm.vendor.exchanged) @@ -241,6 +247,9 @@ func TestANewerLoginReplacesTheGrantHeldAndAnOlderOneDoesNot(t *testing.T) { if after.RefreshFingerprint == before.RefreshFingerprint || mm.vendor.exchanged[len(mm.vendor.exchanged)-1] != "rt-new" { t.Fatalf("a newer login did not win: %v", mm.vendor.exchanged) } + if after.AdoptedFrom != "desktop" || after.Usage == nil || after.Usage.SessionPct == nil || *after.Usage.SessionPct != 42 { + t.Fatalf("after a newer login the licence is %+v", after) + } if ls, _ := mm.store.Licences(ctx); len(ls) != 1 { t.Fatalf("one account became %d licences", len(ls)) }