M-15: the manager's tools read all it keeps and start by hand all it does (issue 1409)

The licences verb answers each licence's organisation, latest usage with its
reset times, its room or why it is not known, whether it is where a full
licence's consumers move next, when and from where it was adopted and its open
conditions. The usage verb takes since, a limit of at most 2000 and raw. The
module's tools: licences_switches from the audit, licences_read_usage and
licences_switch_now with dry, licences_settings with the level mesh-config
answers (the manifest invokes its config verb), licences_kept over every
bus-state entry with its fill, licences_resync, licences_conditions and
licences_carried with its run, refused once carried. An acting tool waits for
the carry-over.

Builds: design 39 bus-and-switch §34, §35, §37, §38, §39, §40, §41, §42, §43, §44, §47, §20, §11, B9, B18, B40

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
This commit is contained in:
2026-11-12 00:19:22 +01:00
parent 9e545658a5
commit baaf2e56dd
8 changed files with 909 additions and 34 deletions
@@ -260,6 +260,7 @@ type keptLog interface {
Read(from uint64, keys string, limit int) (*stdio.LogPage, error)
Last(key string) (*stdio.LogEntry, error)
Info() (*stdio.LogInfo, error)
Newest(keys string) ([]stdio.LogEntry, error)
}
// UsageEntry is one entry of the log `usage` (§6): when, the flattened reading and the vendor's answer whole (ADR 0054).
@@ -279,8 +280,9 @@ func (s *BusStore) RecordUsage(_ context.Context, licence string, at int64, r Us
return err
}
// Usage is the latest readings, newest first, of one licence or every one.
func (s *BusStore) Usage(ctx context.Context, licence string, limit int) ([]UsageRow, error) {
// Usage is the latest readings, newest first, of one licence or every one, none before since, the vendor's answer
// with raw (§35).
func (s *BusStore) Usage(ctx context.Context, licence string, since time.Time, limit int, raw bool) ([]UsageRow, error) {
names := map[string]string{}
all, err := s.Licences(ctx)
if err != nil {
@@ -304,11 +306,18 @@ func (s *BusStore) Usage(ctx context.Context, licence string, limit int) ([]Usag
continue
}
at, _ := time.Parse(time.RFC3339Nano, u.At)
if at.Before(since) {
continue
}
name := names[e.Key]
if name == "" {
name = e.Key
}
out = append(out, UsageRow{Licence: name, At: at.UnixMilli(), Reading: u.Reading})
row := UsageRow{Licence: name, At: at.UnixMilli(), Reading: u.Reading}
if raw {
row.Raw = u.Raw
}
out = append(out, row)
}
return out, nil
}
@@ -278,6 +278,24 @@ func (l *memLog) Info() (*stdio.LogInfo, error) {
return i, nil
}
func (l *memLog) Newest(keys string) ([]stdio.LogEntry, error) {
l.mu.Lock()
defer l.mu.Unlock()
last := map[string]int{}
for i, e := range l.entries {
if keys == "" || e.Key == keys {
last[e.Key] = i
}
}
out := []stdio.LogEntry{}
for i, e := range l.entries {
if j, ok := last[e.Key]; ok && j == i {
out = append(out, e)
}
}
return out, nil
}
// under is every entry of a log's subject, decoded.
func (l *memLog) under(key string) []map[string]any {
l.mu.Lock()
@@ -309,7 +327,7 @@ func TestAReadingAppendsOneUsageEntry(t *testing.T) {
if got[0]["raw"] == nil || got[0]["reading"] == nil || got[0]["at"] == nil {
t.Fatalf("the entry %v", got[0])
}
rows, _ := mm.store.Usage(ctx, licence1, 20)
rows, _ := mm.store.Usage(ctx, licence1, time.Time{}, 20, false)
if len(rows) != 1 || rows[0].Licence != licence1 || rows[0].Reading.SessionPct == nil {
t.Fatalf("usage answers %+v", rows)
}
@@ -100,10 +100,34 @@ func manager() (*Manager, error) {
SettingsFile: os.Getenv("MESH_LICENCE_SETTINGS"),
Conditions: stdio.State("conditions"),
Say: SayCondition,
ConfigLevels: func(context.Context) (map[string]string, error) { return configLevels(node) },
}
return built, nil
}
// configLevels asks mesh-config where each of the manager's own settings was set on its node (§40, B18): the source
// of each resolved value, by its key.
func configLevels(node string) (map[string]string, error) {
raw, err := stdio.Ask("seat:mesh-config.config", map[string]any{"action": "list", "module": Publisher, "node": node})
if err != nil {
return nil, err
}
var answer struct {
Resolved []struct {
Key string `json:"key"`
Source string `json:"source"`
} `json:"resolved"`
}
if err := json.Unmarshal(raw, &answer); err != nil {
return nil, fmt.Errorf("mesh-config's answer: %w", err)
}
levels := map[string]string{}
for _, r := range answer.Resolved {
levels[r.Key] = r.Source
}
return levels, nil
}
// keypair is this module's own, made once in its state directory; the private half never leaves it.
// Written whole, then linked into place, so a second process reads the first's key and never half of it.
func keypair(dir string) (KeyPair, error) {
@@ -274,6 +298,28 @@ func verb(name, description string, input map[string]any, run func(ctx context.C
// reads are the seat's verbs that only read, served while the carry-over has not ended (§11).
var reads = map[string]bool{"licences": true, "bindings": true, "usage": true, "public-key": true, "current": true}
// sinceOf is the argument since, RFC 3339; absent is the zero time.
func sinceOf(a map[string]any) (time.Time, error) {
v, _ := a["since"].(string)
if strings.TrimSpace(v) == "" {
return time.Time{}, nil
}
t, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(v))
if err != nil {
return time.Time{}, fmt.Errorf("since is not a time in RFC 3339: %w", err)
}
return t, nil
}
// limitOf is the argument limit: def when absent or not positive, at most most.
func limitOf(a map[string]any, def, most int) int {
limit := def
if v, ok := a["limit"].(float64); ok && v > 0 {
limit = int(v)
}
return min(limit, most)
}
func str(description string) map[string]any {
return map[string]any{"type": "string", "description": description}
}
@@ -286,8 +332,8 @@ func two(a map[string]any, k1, k2 string) (string, string, error) {
func tools() []stdio.Tool {
consumer := str("the node's name")
return []stdio.Tool{
verb("licences", "Every licence the manager holds — account, kind, when its token and its refresh token expire, failures in a row, which consumers are bound to it. Never a token.",
return append([]stdio.Tool{
verb("licences", "Every licence the manager holds — account, kind, when its token and its refresh token expire, failures in a row, which consumers are bound to it, its latest usage with its reset times, its room or why it is not known, whether it is where a full licence's consumers move next, when and from where it was adopted, and its open conditions. Never a token.",
nil, func(ctx context.Context, m *Manager, _ map[string]any) (any, error) { return m.Licences(ctx) }),
verb("bindings", "Which licence each consumer (a node's agent, by the node's name) is bound to, and the generation it was last given; with consumer, that consumer's alone.",
map[string]any{"consumer": str("a consumer; absent for every one")},
@@ -330,15 +376,18 @@ func tools() []stdio.Tool {
}
return m.RotateDue(ctx)
}),
verb("usage", "Usage readings, the latest first, for every licence or one.",
map[string]any{"licence": str("a licence; absent for all"), "limit": map[string]any{"type": "number", "description": "how many readings (default 20)"}},
verb("usage", "Usage readings, the latest first, for every licence or one: the flattened reading with its three reset times, none before since, at most limit (default 20, at most 2000), and with raw the vendor's answer whole.",
map[string]any{"licence": str("a licence; absent for all"), "since": str("a time, RFC 3339: no reading before it"),
"limit": map[string]any{"type": "number", "description": "how many readings (default 20, at most 2000)"},
"raw": map[string]any{"type": "boolean", "description": "also the vendor's answer whole"}},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
l, _ := a["licence"].(string)
limit := 20
if v, ok := a["limit"].(float64); ok && v > 0 {
limit = int(v)
since, err := sinceOf(a)
if err != nil {
return nil, err
}
return m.Store.Usage(ctx, l, limit)
raw, _ := a["raw"].(bool)
return m.Store.Usage(ctx, l, since, limitOf(a, 20, 2000), raw)
}),
verb("adopt", "Adopt an API key — never as an argument: from a file on the manager's node (`file`), or sealed to the manager's `public-key` by a node's claude-code (`sealed`; its `claude_code_add_api_key` does this). Subscriptions are adopted from the nodes' logins by themselves.",
map[string]any{"name": str("the licence's name"), "file": str("a file on the manager's node holding the key"),
@@ -377,5 +426,5 @@ func tools() []stdio.Tool {
}
return m.Current(ctx, c)
}),
}
}, moduleTools()...)
}
@@ -166,6 +166,9 @@ type Manager struct {
Conditions keptState
Say func(c oc.Condition) error
conditionsMu sync.Mutex
// ConfigLevels is where mesh-config says each setting was set on the manager's node (mesh, node or default), by
// setting (§40, B18); nil answers every one as the default.
ConfigLevels func(ctx context.Context) (map[string]string, error)
}
const leaseFor = 2 * time.Minute
@@ -651,22 +654,40 @@ func (m *Manager) RotateDue(ctx context.Context) ([]map[string]any, error) {
return out, nil
}
// ReadUsage reads and records each subscription's usage (ADR 0054); the event names the licence and numbers.
// ReadUsage reads every subscription's usage, on the manager's cadence (§24): each reading written, the rule applied
// after it (§26), and the conditions said (§9).
func (m *Manager) ReadUsage(ctx context.Context) error {
_, err := m.ReadUsageOf(ctx, "", false)
return err
}
// Read is one reading as licences_read_usage answers it (§38): the licence, the reading or why it failed, and what §26
// did or, with dry, would do.
type Read struct {
Licence string `json:"licence"`
Reading *UsageReading `json:"reading,omitempty"`
Error string `json:"readError,omitempty"`
Switches []Switch `json:"switches"`
}
// ReadUsageOf reads the usage of one subscription, or every one when licence is empty, as §24 does, and applies §26
// after each reading unless dry; dry answers what §26 would do (§32, §38).
func (m *Manager) ReadUsageOf(ctx context.Context, licence string, dry bool) ([]Read, error) {
if err := m.ReadSettings(ctx); err != nil {
return err
return nil, err
}
all, err := m.Store.Licences(ctx)
if err != nil {
return err
return nil, err
}
out := []Read{}
for _, l := range all {
if l.Kind != "subscription" || l.Generation == 0 {
if l.Kind != "subscription" || l.Generation == 0 || (licence != "" && l.Name != licence) {
continue
}
g, err := m.grantOf(ctx, l)
if err != nil {
return err
return out, err
}
raw, err := m.Vendor.Usage(ctx, g.AccessToken)
g = FullGrant{}
@@ -682,33 +703,39 @@ func (m *Manager) ReadUsage(ctx context.Context) error {
kept.ReadError = err.Error()
l.Usage = &kept
if err := m.Store.SaveLicence(ctx, l); err != nil {
return err
return out, err
}
m.Log("reading the usage of %s failed: %v", l.Name, err)
out = append(out, Read{Licence: l.Name, Error: kept.ReadError, Switches: []Switch{}})
continue
}
reading := FlattenUsage(raw)
if err := m.Store.RecordUsage(ctx, l.Name, m.Now().UnixMilli(), reading, raw); err != nil {
return err
return out, err
}
// The latest reading beside its licence, so licences answers from one key (§2).
latest := reading
latest.ReadAt = m.Now().UTC().Format(time.RFC3339Nano)
l.Usage = &latest
if err := m.Store.SaveLicence(ctx, l); err != nil {
return err
return out, err
}
_ = m.Emit("usage.read", map[string]any{"licence": l.Name, "sessionPct": reading.SessionPct,
"sessionResetsAt": reading.SessionResetsAt, "weeklyPct": reading.WeeklyPct,
"weeklyResetsAt": reading.WeeklyResetsAt, "sonnetPct": reading.SonnetPct,
"sonnetResetsAt": reading.SonnetResetsAt})
// On each reading, a licence at its threshold moves every consumer bound to it (§26).
if _, err := m.ApplyRule(ctx, l.Name, false, false); err != nil {
return err
acts, err := m.ApplyRule(ctx, l.Name, false, dry)
if acts == nil {
acts = []Switch{}
}
out = append(out, Read{Licence: l.Name, Reading: &latest, Switches: acts})
if err != nil {
return out, err
}
}
// What the readings and the settings now hold is said, each condition only when what it says changed (§9).
return m.SayConditions(ctx, false)
return out, m.SayConditions(ctx, false)
}
// ---- the seat's verbs --------------------------------------------------------------------------------
@@ -894,7 +921,9 @@ func (m *Manager) adoptKey(ctx context.Context, name, key, from string) (map[str
return map[string]any{"licence": name, "kind": "api-key", "adopted": true, "fingerprint": Fingerprint(key)}, nil
}
// Licences is each licence as a person reads it: health and who is bound, never a token.
// Licences is each licence as a person reads it (design 39 bus-and-switch §34, §47, B19, B40): today's fields, its
// latest usage with its reset times, its room or why it is not known, whether it is the target a full licence's
// consumers move to next, when and from where it was adopted, and the conditions open about it. Never a token.
func (m *Manager) Licences(ctx context.Context) ([]map[string]any, error) {
all, err := m.Store.Licences(ctx)
if err != nil {
@@ -904,27 +933,52 @@ func (m *Manager) Licences(ctx context.Context) ([]map[string]any, error) {
if err != nil {
return nil, err
}
open, err := m.KeptConditions()
if err != nil {
return nil, err
}
s := m.settings()
stamp := func(ms int64) any {
if ms == 0 {
return nil
}
return time.UnixMilli(ms).UTC().Format(time.RFC3339)
}
targets := map[string]bool{}
for _, l := range all {
if len(consumersOf(l.Name, bindings)) > 0 {
if to, _ := m.targetFor(l, all, s); to != "" {
targets[to] = true
}
}
}
out := []map[string]any{}
for _, l := range all {
bound := []string{}
for _, b := range bindings {
if b.Licence == l.Name {
bound = append(bound, b.Consumer)
}
bound := consumersOf(l.Name, bindings)
if bound == nil {
bound = []string{}
}
account := l.Email
if account == "" {
account = l.AccountUUID
}
var room any = m.roomWords(l, s)
if r, known := Room(l, m.Now(), s); known {
room = r
}
conditions := []map[string]any{}
prefix := "licence." + KeyFor(l.Name) + "."
for _, c := range open {
if strings.HasPrefix(c.Body.Key, prefix) {
conditions = append(conditions, map[string]any{"key": c.Body.Key, "severity": c.Body.Severity,
"headline": c.Body.Headline, "since": c.Since})
}
}
out = append(out, map[string]any{"name": l.Name, "kind": l.Kind, "account": account,
"accessExpiresAt": stamp(l.AccessExpiresAt), "refreshExpiresAt": stamp(l.RefreshExpiresAt),
"rotatedAt": stamp(l.RotatedAt), "failures": l.Failures, "bound": bound})
"organisation": l.OrganizationUUID, "accessExpiresAt": stamp(l.AccessExpiresAt),
"refreshExpiresAt": stamp(l.RefreshExpiresAt), "rotatedAt": stamp(l.RotatedAt), "failures": l.Failures,
"bound": bound, "usage": l.Usage, "room": room, "target": targets[l.Name], "adoptedAt": stamp(l.AdoptedAt),
"adoptedFrom": l.AdoptedFrom, "conditions": conditions})
}
return out, nil
}
@@ -76,6 +76,8 @@ type UsageRow struct {
Licence string `json:"licence"`
At int64 `json:"at"`
Reading UsageReading `json:"reading"`
// Raw is the vendor's answer whole, answered only when asked (§35).
Raw map[string]any `json:"raw,omitempty"`
}
// Store is what the manager keeps.
@@ -97,7 +99,9 @@ type Store interface {
Outcome(ctx context.Context, fingerprint string) (Outcome, error)
RecordOutcome(ctx context.Context, fingerprint, node, account string, o Outcome, why string) error
RecordUsage(ctx context.Context, licence string, at int64, r UsageReading, raw map[string]any) error
Usage(ctx context.Context, licence string, limit int) ([]UsageRow, error)
// Usage is the latest readings, newest first, of one licence or every one: at most limit, none before since (zero
// is none), the vendor's answer with raw (§35).
Usage(ctx context.Context, licence string, since time.Time, limit int, raw bool) ([]UsageRow, error)
// Audit appends one entry for an act; an error is the act failing (B20).
Audit(ctx context.Context, what string, detail map[string]any) error
}
@@ -0,0 +1,413 @@
package main
// The module's own tools (design 39 bus-and-switch §37 to §44, B40): beside the seat's verbs, they read everything the
// manager keeps and start by hand everything it does by itself. A tool that acts answers "the carry-over has not ended"
// until it has (§11); licences_carried's run is the carry-over itself.
import (
"context"
"encoding/json"
"errors"
"fmt"
"slices"
"sort"
"strings"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// tool is one of the module's own tools; acts says whether it acts, so the carry-over's gate holds it (§11).
func tool(name, description string, acts bool, input map[string]any, run func(ctx context.Context, m *Manager, a map[string]any) (any, error)) stdio.Tool {
return stdio.Tool{Name: name, Description: description, Input: input,
Run: func(a map[string]any) (any, error) {
m, err := manager()
if err != nil {
return nil, err
}
if acts {
if err := m.Carry.Acts(); err != nil {
return nil, err
}
}
ctx := WithCause(context.Background(), Cause{Why: "the " + name + " tool", By: stdio.CallerOf(a)})
return run(ctx, m, a)
}}
}
func boolean(description string) map[string]any {
return map[string]any{"type": "boolean", "description": description}
}
func number(description string) map[string]any {
return map[string]any{"type": "number", "description": description}
}
func moduleTools() []stdio.Tool {
licence := str("a licence, as `licences` names it; absent for all")
dry := boolean("answer what the rule would do, and do nothing")
return []stdio.Tool{
tool("licences_switches", "Every switch of a consumer and why, newest first, from the audit: when, from and to which licence, the consumers, by whom and why, and for a switch by usage the reading that moved them.",
false, map[string]any{"licence": licence, "consumer": str("a consumer; absent for all"),
"since": str("a time, RFC 3339: no switch before it"), "limit": number("how many (default 20)")},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
l, _ := a["licence"].(string)
c, _ := a["consumer"].(string)
since, err := sinceOf(a)
if err != nil {
return nil, err
}
return m.Switches(strings.TrimSpace(l), strings.TrimSpace(c), since, limitOf(a, 20, 2000))
}),
tool("licences_read_usage", "Read usage now, for one subscription or every one, as the manager does on its cadence; then apply the switch rule unless dry. Answers each reading and what the rule did or would do.",
true, map[string]any{"licence": licence, "dry": dry},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
l, _ := a["licence"].(string)
d, _ := a["dry"].(bool)
return m.ReadUsageOf(ctx, strings.TrimSpace(l), d)
}),
tool("licences_switch_now", "Apply the switch rule now from the latest readings, for one licence or all, even with switch_auto off since a person asked; with dry, answer the moves and make none.",
true, map[string]any{"licence": licence, "dry": dry},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
l, _ := a["licence"].(string)
d, _ := a["dry"].(bool)
acts, err := m.ApplyRule(ctx, strings.TrimSpace(l), true, d)
if acts == nil {
acts = []Switch{}
}
return acts, err
}),
tool("licences_settings", "The settings in force: each one's value, its default, where it was set (mesh, node or the default, as mesh-config answers it) and why it is invalid when it is.",
false, nil, func(ctx context.Context, m *Manager, _ map[string]any) (any, error) {
return m.SettingsInForce(ctx)
}),
tool("licences_kept", "Every entry the manager keeps on the bus: a state's keys, or a log's subjects with their last entry; with key, that key's value or that subject's last entry; and how full each is against its cap.",
false, map[string]any{"entry": str("one of licences, bindings, leases, offered, usage, audit, carried, conditions; absent for all"),
"key": str("a key of a state, or a subject of a log")},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
e, _ := a["entry"].(string)
k, _ := a["key"].(string)
return m.Kept(strings.TrimSpace(e), strings.TrimSpace(k))
}),
tool("licences_resync", "Recover: say binding.changed again for every binding, ask every node what it holds, say again each condition held, and hand each licence's access token to the vault again. Answers what it said.",
true, nil, func(ctx context.Context, m *Manager, _ map[string]any) (any, error) {
return m.Resync(ctx)
}),
tool("licences_conditions", "The conditions the manager said and holds: each key of its state conditions with its body and since.",
false, nil, func(_ context.Context, m *Manager, _ map[string]any) (any, error) {
return m.KeptConditions()
}),
tool("licences_carried", "The carry-over's record (when, and per table the rows read and entries written), or, while it has not ended, the failure it names; with run and no record, carry over again. Refused once carried.",
false, map[string]any{"run": boolean("carry over again: only while it has not ended")},
func(ctx context.Context, m *Manager, a map[string]any) (any, error) {
run, _ := a["run"].(bool)
return m.Carried(ctx, run)
}),
}
}
// 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 (m *Manager) Switches(licence, consumer string, since time.Time, limit int) ([]map[string]any, error) {
bus, ok := m.Store.(*BusStore)
if !ok {
return nil, errors.New("the audit is not kept on the bus")
}
info, err := bus.AuditLog.Info()
if err != nil || info.Last == 0 {
return []map[string]any{}, err
}
var all []stdio.LogEntry
for next := uint64(1); next != 0; {
page, err := bus.AuditLog.Read(next, "switched", 0)
if err != nil {
return nil, err
}
all = append(all, page.Entries...)
next = page.Next
}
out := []map[string]any{}
for i := len(all) - 1; i >= 0 && len(out) < limit; i-- {
var e map[string]any
if json.Unmarshal(all[i].Value, &e) != nil {
continue
}
at, _ := time.Parse(time.RFC3339Nano, fmt.Sprint(e["at"]))
if at.Before(since) {
continue
}
if licence != "" && e["from"] != licence && e["to"] != licence {
continue
}
consumers, _ := e["consumers"].([]any)
if consumer != "" && !slices.Contains(consumers, any(consumer)) {
continue
}
s := map[string]any{"at": e["at"], "from": e["from"], "to": e["to"], "consumers": e["consumers"],
"by": e["by"], "why": e["why"]}
if _, ok := e["window"]; ok {
s["reading"] = map[string]any{"window": e["window"], "pct": e["pct"], "resetsAt": e["resetsAt"],
"room": e["room"]}
}
out = append(out, s)
}
return out, nil
}
// SettingAnswer is one setting as licences_settings answers it (§40).
type SettingAnswer struct {
Setting string `json:"setting"`
Value any `json:"value"`
Default any `json:"default"`
Level string `json:"level"`
Invalid string `json:"invalid,omitempty"`
}
// SettingsInForce is each setting in force, its default, where mesh-config says it was set, and why it is invalid
// when it is (§40, B18).
func (m *Manager) SettingsInForce(ctx context.Context) ([]SettingAnswer, error) {
levels := map[string]string{}
if m.ConfigLevels != nil {
got, err := m.ConfigLevels(ctx)
if err != nil {
return nil, fmt.Errorf("mesh-config did not say where each setting was set: %w", err)
}
levels = got
}
s := m.settings()
held, err := m.Judge(ctx)
if err != nil {
return nil, err
}
values, defaults := s.Values(), Defaults.Values()
var out []SettingAnswer
for k, v := range values {
a := SettingAnswer{Setting: k, Value: v, Default: defaults[k], Level: levels[k]}
if a.Level == "" {
a.Level = "default"
}
if c, ok := held["settings."+k+".invalid"]; ok {
a.Invalid = c.Summary
}
out = append(out, a)
}
sort.Slice(out, func(i, j int) bool { return out[i].Setting < out[j].Setting })
return out, nil
}
// kept are the entries the manager keeps on the bus, by their local names in the manifest (§2 to §9).
func (m *Manager) kept() (map[string]keptState, map[string]keptLog) {
states, logs := map[string]keptState{}, map[string]keptLog{}
if bus, ok := m.Store.(*BusStore); ok {
states["licences"], states["bindings"] = bus.LicencesState, bus.BindingsState
states["leases"], states["offered"] = bus.LeasesState, bus.OfferedState
logs["usage"], logs["audit"] = bus.UsageLog, bus.AuditLog
}
if m.Carry != nil {
states["carried"] = m.Carry.State
}
if m.Conditions != nil {
states["conditions"] = m.Conditions
}
return states, logs
}
// Kept answers one entry the manager keeps, or every one (§41): a state's keys and their count, a log's subjects with
// their last entry and its fill against its cap; with key, one key's value or one subject's last entry.
func (m *Manager) Kept(entry, key string) (map[string]any, error) {
states, logs := m.kept()
out := map[string]any{}
names := []string{}
for n := range states {
names = append(names, n)
}
for n := range logs {
names = append(names, n)
}
sort.Strings(names)
if entry != "" && !slices.Contains(names, entry) {
return nil, fmt.Errorf("the manager keeps no entry %q: it keeps %s", entry, strings.Join(names, ", "))
}
for _, n := range names {
if entry != "" && n != entry {
continue
}
if st, ok := states[n]; ok {
a, err := keptStateAnswer(st, key)
if err != nil {
return nil, fmt.Errorf("%s: %w", n, err)
}
out[n] = a
continue
}
a, err := keptLogAnswer(logs[n], key)
if err != nil {
return nil, fmt.Errorf("%s: %w", n, err)
}
out[n] = a
}
return out, nil
}
func keptStateAnswer(st keptState, key string) (map[string]any, error) {
keys, err := st.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
a := map[string]any{"kind": "state", "keys": keys, "fill": map[string]any{"keys": len(keys), "cap": "none declared"}}
if key != "" {
e, err := st.Get(key)
if err != nil {
return nil, err
}
if e == nil {
a["value"] = nil
} else {
a["value"] = e.Value
}
}
return a, nil
}
func keptLogAnswer(l keptLog, key string) (map[string]any, error) {
info, err := l.Info()
if err != nil {
return nil, err
}
a := map[string]any{"kind": "log", "fill": map[string]any{"entries": info.Entries, "bytes": info.Bytes,
"maxBytes": info.MaxBytes}}
if key != "" {
e, err := l.Last(key)
if err != nil {
return nil, err
}
a["last"] = e
return a, nil
}
newest, err := l.Newest("")
if err != nil {
return nil, err
}
subjects := map[string]any{}
for _, e := range newest {
subjects[e.Key] = e
}
a["subjects"] = subjects
return a, nil
}
// Resync says again what a consumer, the vault or a node may have missed (§42): binding.changed for every binding,
// holdings.asked, each condition held, and licence-put of each licence's access token. Answers what it said.
func (m *Manager) Resync(ctx context.Context) (map[string]any, error) {
bindings, err := m.Store.Bindings(ctx)
if err != nil {
return nil, err
}
said := map[string]any{}
consumers := []string{}
for _, b := range bindings {
m.changed(b, b.Licence)
consumers = append(consumers, b.Consumer)
}
said["binding.changed"] = consumers
if err := m.AskHoldings(CauseOf(ctx).By); err != nil {
return said, fmt.Errorf("holdings.asked could not be said: %w", err)
}
said["holdings.asked"] = true
if err := m.SayConditions(ctx, true); err != nil {
return said, err
}
conditions, err := m.KeptConditions()
if err != nil {
return said, err
}
keys := []string{}
for _, c := range conditions {
keys = append(keys, c.Body.Key)
}
said["conditions"] = keys
licences, err := m.Store.Licences(ctx)
if err != nil {
return said, err
}
put := []string{}
var errs []error
for _, l := range licences {
if l.Generation == 0 {
continue
}
key := KeyFor(l.Name)
access, err := m.Grants.Get(ctx, key, PartAccess)
if err == nil {
err = m.Grants.Put(ctx, key, PartAccess, l.Generation, access)
}
access = ""
if err != nil {
errs = append(errs, fmt.Errorf("the access token of %s: %w", l.Name, err))
continue
}
put = append(put, l.Name)
}
said["licence-put"] = put
return said, errors.Join(errs...)
}
// KeptConditions is each key of the state conditions with its body and since (§43), by key.
func (m *Manager) KeptConditions() ([]KeptCondition, error) {
out := []KeptCondition{}
if m.Conditions == nil {
return out, nil
}
keys, err := m.Conditions.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
for _, k := range keys {
e, err := m.Conditions.Get(k)
if err != nil {
return nil, err
}
var c KeptCondition
if e == nil || json.Unmarshal(e.Value, &c) != nil {
continue
}
out = append(out, c)
}
return out, nil
}
// Carried is the carry-over's record, or the failure its last run names while it has not ended; with run and no
// record it carries over again, and is refused once carried (§44).
func (m *Manager) Carried(ctx context.Context, run bool) (map[string]any, error) {
if m.Carry == nil {
return nil, errors.New("this manager has nothing to carry over")
}
if m.Carry.Ended() {
if run {
return nil, errors.New("the carry-over has ended: carried.postgres is written, and it is never done again")
}
e, err := m.Carry.State.Get(CarriedKey)
if err != nil || e == nil {
return nil, err
}
var rec CarriedRecord
if err := json.Unmarshal(e.Value, &rec); err != nil {
return nil, err
}
return map[string]any{"carried": rec}, nil
}
if !run {
return map[string]any{"carried": nil, "failure": m.Carry.Failure()}, nil
}
bus, _ := m.Store.(*BusStore)
rec, err := m.Carry.Run(ctx, m, bus)
if serr := m.SayConditions(ctx, false); serr != nil {
m.Log("saying the conditions after the carry-over: %v", serr)
}
if err != nil {
return map[string]any{"carried": nil, "failure": m.Carry.Failure()}, err
}
return map[string]any{"carried": rec}, nil
}
@@ -0,0 +1,327 @@
package main
import (
"context"
"encoding/json"
"errors"
"os"
"strings"
"testing"
"time"
)
// served is every verb and tool the binary serves, by name, run on mm's manager.
func served(t *testing.T, mm *miniMesh) map[string]stdTool {
t.Helper()
built = mm.m
t.Cleanup(func() { built = nil })
out := map[string]stdTool{}
for _, tool := range tools() {
out[strings.TrimPrefix(tool.Name, Seat+".")] = stdTool(tool.Run)
}
return out
}
// answer is a tool's answer as JSON, decoded into a generic form.
func answer(t *testing.T, run stdTool, args map[string]any) any {
t.Helper()
got, err := run(args)
if err != nil {
t.Fatalf("%v answered %v", args, err)
}
raw, _ := json.Marshal(got)
var v any
_ = json.Unmarshal(raw, &v)
return v
}
// design 39 bus-and-switch §34/AC1, §47, B40: an answer per licence carries today's fields, its organisation, usage,
// room or why it is not known, whether it is the next target, when and from where it was adopted and its open
// conditions, and no token.
func TestLicencesAnswersEachFieldAndNoToken(t *testing.T) {
mm := fourOnA(t)
ctx := context.Background()
_ = mm.store.SaveLicence(ctx, Licence{Name: "k", Kind: "api-key", Generation: 1, AdoptedAt: t0.UnixMilli(),
AdoptedFrom: "a file"})
_, _ = saying(mm)
_ = mm.store.SaveLicence(ctx, sub("a", read(40, 95)))
_ = mm.m.SayConditions(ctx, false)
got := answer(t, served(t, mm)["licences"], nil).([]any)
by := map[string]map[string]any{}
for _, l := range got {
f := l.(map[string]any)
by[f["name"].(string)] = f
for _, k := range []string{"name", "kind", "account", "organisation", "accessExpiresAt", "refreshExpiresAt",
"failures", "bound", "usage", "room", "target", "adoptedAt", "adoptedFrom", "conditions"} {
if _, ok := f[k]; !ok {
t.Errorf("%s has no %s: %v", f["name"], k, f)
}
}
}
if by["b"]["target"] != true || by["c"]["target"] != false || by["a"]["room"] != 5.0 ||
by["k"]["room"] != "is an API key, whose room is never known" || by["k"]["adoptedFrom"] != "a file" ||
len(by["a"]["bound"].([]any)) != 4 || by["a"]["usage"].(map[string]any)["weeklyResetsAt"] != "2026-11-14T09:00:00Z" {
t.Fatalf("licences %v", by)
}
if cs := by["a"]["conditions"].([]any); len(cs) != 1 || cs[0].(map[string]any)["key"] != "licence.a.near-limit" {
t.Fatalf("a's conditions %v", cs)
}
raw, _ := json.Marshal(got)
for _, token := range []string{"rt-", "at-", "local-"} {
if strings.Contains(string(raw), token) {
t.Fatalf("licences holds %s: %s", token, raw)
}
}
}
// design 39 bus-and-switch §35/AC1: since and limit bound the answer, newest first; raw adds the vendor's answer.
func TestUsageIsBoundedBySinceAndLimit(t *testing.T) {
mm := newMesh(t)
ctx := context.Background()
_ = mm.store.SaveLicence(ctx, sub("a", nil))
for i := range 30 {
_ = mm.store.RecordUsage(ctx, "a", t0.Add(time.Duration(i)*time.Minute).UnixMilli(), UsageReading{},
map[string]any{"i": float64(i)})
}
run := served(t, mm)["usage"]
rows := answer(t, run, map[string]any{"licence": "a"}).([]any)
first := rows[0].(map[string]any)
if len(rows) != 20 || first["at"] != float64(t0.Add(29*time.Minute).UnixMilli()) || first["raw"] != nil {
t.Fatalf("by default %d rows, the first %v", len(rows), first)
}
since := t0.Add(25 * time.Minute).UTC().Format(time.RFC3339)
rows = answer(t, run, map[string]any{"since": since, "limit": 3.0, "raw": true}).([]any)
if len(rows) != 3 || rows[0].(map[string]any)["raw"].(map[string]any)["i"] != 29.0 {
t.Fatalf("since and limit 3: %v", rows)
}
if rows = answer(t, run, map[string]any{"since": since, "limit": 5000.0}).([]any); len(rows) != 5 {
t.Fatalf("since: %d rows", len(rows))
}
if _, err := run(map[string]any{"since": "yesterday"}); err == nil {
t.Fatal("a since that is not a time is answered")
}
}
// design 39 bus-and-switch §37/AC1: a switch by usage and one by a person are answered, newest first, with their
// causes, a switch by usage with the reading that moved them; licence and consumer narrow the answer.
func TestSwitchesAnswersBothCauses(t *testing.T) {
mm := fourOnA(t)
ctx := context.Background()
_, _ = mm.m.ApplyRule(ctx, "", false, false)
mm.now = t0.Add(time.Minute)
_, _ = mm.m.Bind(WithCause(ctx, Cause{Why: "the switch verb", By: "a person"}), "ace", "c")
run := served(t, mm)["licences_switches"]
got := answer(t, run, nil).([]any)
if len(got) != 2 {
t.Fatalf("switches %v", got)
}
person, usage := got[0].(map[string]any), got[1].(map[string]any)
if person["by"] != "a person" || person["why"] != "the switch verb" || person["to"] != "c" || person["reading"] != nil ||
usage["by"] != ByUsage || usage["reading"].(map[string]any)["window"] != "weekly" || usage["from"] != "a" {
t.Fatalf("switches %v", got)
}
if got := answer(t, run, map[string]any{"licence": "c"}).([]any); len(got) != 1 {
t.Fatalf("of c %v", got)
}
if got := answer(t, run, map[string]any{"consumer": "g14"}).([]any); len(got) != 1 || got[0].(map[string]any)["by"] != ByUsage {
t.Fatalf("of g14 %v", got)
}
}
// design 39 bus-and-switch §38/AC1, §24, §32: reading usage now writes the reading and applies the rule, and with dry
// reads, answers the moves and moves nothing.
func TestReadUsageNowAppliesTheRuleUnlessDry(t *testing.T) {
ctx := context.Background()
for _, dry := range []bool{true, false} {
mm := newMesh(t)
_, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-a", true, t0)})
_ = mm.store.SaveLicence(ctx, Licence{Name: "b@example.org", Kind: "subscription", Usage: read(10, 10)})
mm.vendor.usage = map[string]any{"five_hour": map[string]any{"utilization": 30.0},
"seven_day": map[string]any{"utilization": 99.0, "resets_at": "2026-11-14T09:00:00Z"}}
got := answer(t, served(t, mm)["licences_read_usage"], map[string]any{"dry": dry}).([]any)
r := got[0].(map[string]any)
moved := mm.bound("laptop").Licence == "b@example.org"
if len(got) != 1 || r["reading"].(map[string]any)["weeklyPct"] != 99.0 ||
len(r["switches"].([]any)) != 1 || moved == dry || len(mm.usage.under("")) != 1 {
t.Fatalf("dry %v: answered %v, moved %v", dry, got, moved)
}
}
}
// design 39 bus-and-switch §39/AC1, §32: switching now runs the rule with switch_auto off, and dry moves nothing; an
// acting tool waits for the carry-over (§11).
func TestSwitchNowRunsWithSwitchAutoOff(t *testing.T) {
mm := fourOnA(t)
mm.m.Settings.SwitchAuto = false
run := served(t, mm)["licences_switch_now"]
if got := answer(t, run, map[string]any{"dry": true}).([]any); len(got) != 1 || mm.bound("ace").Licence != "a" {
t.Fatalf("dry %v", got)
}
if got := answer(t, run, map[string]any{"licence": "a"}).([]any); len(got) != 1 || mm.bound("ace").Licence != "b" {
t.Fatalf("switched %v, ace on %s", got, mm.bound("ace").Licence)
}
mm.m.Carry = &Carry{State: newMemState(func() time.Time { return mm.now })}
if _, err := run(nil); err == nil || err.Error() != NotCarried {
t.Fatalf("before the carry-over answered %v", err)
}
}
// design 39 bus-and-switch §40/AC1, B18: 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.
func TestSettingsAnswersTheLevel(t *testing.T) {
mm := newMesh(t)
mm.m.Settings.SwitchAt, mm.m.Settings.WarnAt = 80, 85
mm.m.ConfigLevels = func(context.Context) (map[string]string, error) {
return map[string]string{"switch_at_pct": "node", "warn_at_pct": "mesh", "cadence_minutes": "default"}, nil
}
got := answer(t, served(t, mm)["licences_settings"], nil).([]any)
by := map[string]map[string]any{}
for _, s := range got {
f := s.(map[string]any)
by[f["setting"].(string)] = f
}
if len(by) != len(Defaults.Values()) || by["switch_at_pct"]["level"] != "node" || by["switch_at_pct"]["value"] != 80.0 ||
by["switch_at_pct"]["default"] != 99.0 || by["usage_minutes"]["level"] != "default" ||
!strings.Contains(fmtAny(by["room_below_pct"]["invalid"]), "above switch_at_pct 80") ||
!strings.Contains(fmtAny(by["warn_at_pct"]["invalid"]), "85") || by["switch_auto"]["invalid"] != nil {
t.Fatalf("settings %v", by)
}
mm.m.ConfigLevels = func(context.Context) (map[string]string, error) { return nil, errors.New("no responders") }
if _, err := served(t, mm)["licences_settings"](nil); err == nil {
t.Fatal("mesh-config not answering is answered as the default")
}
}
func fmtAny(v any) string { s, _ := v.(string); return s }
// design 39 bus-and-switch §41/AC1, §20: every bus-state entry the manifest asks for is read by licences_kept, with its
// keys or subjects, one key's value and its fill; and no entry holds a token.
func TestKeptReadsEveryEntryOfTheManifest(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Requires []json.RawMessage `json:"requires"`
}
_ = json.Unmarshal(raw, &m)
mm := newMesh(t)
ctx := context.Background()
_, _ = saying(mm)
mm.m.Carry = &Carry{State: newMemState(func() time.Time { return mm.now })}
_, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-secret-1", true, t0)})
_ = mm.m.ReadUsage(ctx)
run := served(t, mm)["licences_kept"]
all := answer(t, run, nil).(map[string]any)
entries := 0
for _, r := range m.Requires {
var e struct {
Name string `json:"name"`
Template string `json:"template"`
}
if json.Unmarshal(r, &e) != nil || e.Template == "" {
continue
}
entries++
got, ok := all[e.Name].(map[string]any)
if !ok || got["fill"] == nil || got["kind"] != map[string]string{"state": "state", "log": "log"}[e.Template] {
t.Errorf("licences_kept does not read %s as a %s: %v", e.Name, e.Template, all[e.Name])
}
}
if entries != 8 || len(all) != entries {
t.Fatalf("the manifest asks for %d entries, licences_kept reads %d", entries, len(all))
}
one := answer(t, run, map[string]any{"entry": "licences", "key": KeyFor(licence1)}).(map[string]any)
if one["licences"].(map[string]any)["value"] == nil {
t.Fatalf("one key %v", one)
}
if subjects := all["usage"].(map[string]any)["subjects"].(map[string]any); subjects[KeyFor(licence1)] == nil {
t.Fatalf("usage subjects %v", subjects)
}
if _, err := run(map[string]any{"entry": "tables"}); err == nil {
t.Fatal("an entry not kept is answered")
}
everything, _ := json.Marshal(all)
for _, token := range []string{"rt-secret", "rt-1", "at-1", "local-"} {
if strings.Contains(string(everything), token) {
t.Fatalf("a kept entry holds %s", token)
}
}
}
// design 39 bus-and-switch §42/AC1: resync says binding.changed once per binding, holdings.asked once, each condition
// once, and licence-put of each licence's access once.
func TestResyncSaysEachOnce(t *testing.T) {
mm := newMesh(t)
ctx := context.Background()
_, said := saying(mm)
_, _ = mm.m.Consider(ctx, []Holdings{mm.login("laptop", "rt-a", true, t0), mm.login("server", "rt-b", true, t0)})
mm.m.Settings.WarnAt = 100
_ = mm.m.SayConditions(ctx, false)
mm.events, *said = nil, nil
got := answer(t, served(t, mm)["licences_resync"], nil).(map[string]any)
count := map[string]int{}
for _, e := range mm.events {
name, _, _ := strings.Cut(e, " ")
count[name]++
}
puts, _ := got["licence-put"].([]any)
if count["binding.changed"] != 2 || count[HoldingsAsked] != 1 || len(*said) != 1 || len(puts) != 1 || puts[0] != licence1 {
t.Fatalf("said %v, conditions %d, answered %v", mm.events, len(*said), got)
}
}
// design 39 bus-and-switch §43/AC1: each key of conditions is answered with its body and since.
func TestConditionsAnswersEachKey(t *testing.T) {
mm := fourOnA(t)
ctx := context.Background()
_, _ = saying(mm)
_ = mm.store.SaveLicence(ctx, sub("a", read(40, 95)))
mm.m.Settings.RoomBelow = 100
_ = mm.m.SayConditions(ctx, false)
got := answer(t, served(t, mm)["licences_conditions"], nil).([]any)
var keys []string
for _, c := range got {
f := c.(map[string]any)
if f["since"] == "" || f["body"].(map[string]any)["headline"] == "" {
t.Fatalf("condition %v", f)
}
keys = append(keys, f["body"].(map[string]any)["key"].(string))
}
if strings.Join(keys, ",") != "licence.a.near-limit,settings.room_below_pct.invalid" {
t.Fatalf("keys %v", keys)
}
}
// design 39 bus-and-switch §44/AC1: licences_carried answers the failure while not carried, carries over with run, then
// answers the record and refuses run.
func TestCarriedRunIsRefusedOnceCarried(t *testing.T) {
mm := newMesh(t)
_, said := saying(mm)
down := true
mm.m.Carry = &Carry{State: newMemState(func() time.Time { return mm.now }),
Source: func(context.Context) (CarrySource, error) {
if down {
return nil, errors.New("connection refused")
}
return &memSource{}, nil
}}
run := served(t, mm)["licences_carried"]
if _, err := run(map[string]any{"run": true}); err == nil || len(of(*said, CarryOverFailed)) != 1 {
t.Fatalf("a failed run answered %v, said %v", err, *said)
}
if got := answer(t, run, nil).(map[string]any); !strings.Contains(fmtAny(got["failure"]), "connection refused") {
t.Fatalf("not carried %v", got)
}
down = false
if got := answer(t, run, map[string]any{"run": true}).(map[string]any); got["carried"] == nil ||
strings.Join(of(*said, CarryOverFailed), ",") != "raised,cleared" {
t.Fatalf("carried %v, said %v", got, *said)
}
if got := answer(t, run, nil).(map[string]any); got["carried"].(map[string]any)["tables"] == nil {
t.Fatalf("the record %v", got)
}
if _, err := run(map[string]any{"run": true}); err == nil {
t.Fatal("run is answered once carried")
}
}
+2 -1
View File
@@ -97,7 +97,8 @@
"invokes": [
"seat:operator-channel.tell",
"seat:mesh-vault.licence-put",
"seat:mesh-vault.get"
"seat:mesh-vault.get",
"seat:mesh-config.config"
],
"seats": [
{