package main // The module's four reading tools (design 39 parts §3; bus-and-switch §37, §40, §41, §43): they read everything the // manager keeps and answer it, and write nothing. Every act is the manager bundle's (parts §4). import ( "context" "encoding/json" "fmt" "slices" "sort" "strings" "time" stdio "git.novox.be/novox/mesh-sdk/go" "claude-licence-manager/internal/licences" ) // Reader reads the module's kept data and its settings, as the module whose bundle asks. type Reader struct { // States and Logs are the module's kept states and logs, by their names in the manifest. States map[string]licences.State Logs map[string]licences.Log // SettingsFile is the settings file the node-engine writes, read again at every call (bus-and-switch §33). SettingsFile string // ConfigLevels is where mesh-config says each setting was set on the manager's node (mesh, node or default), by // setting (§40, B18). ConfigLevels func(ctx context.Context) (map[string]string, error) } func str(description string) map[string]any { return map[string]any{"type": "string", "description": description} } func number(description string) map[string]any { return map[string]any{"type": "number", "description": description} } // 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) } // Tools are the four reading tools, registered under the module (parts §3). func (r *Reader) Tools() []stdio.Tool { licence := str("a licence, as `licences` names it; absent for all") return []stdio.Tool{ {Name: "licences_switches", Description: "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.", Input: 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)")}, Run: func(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 r.Switches(strings.TrimSpace(l), strings.TrimSpace(c), since, limitOf(a, 20, 2000)) }}, {Name: "licences_settings", Description: "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.", Run: func(map[string]any) (any, error) { return r.SettingsInForce(context.Background()) }}, {Name: "licences_kept", Description: "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.", Input: 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")}, Run: func(a map[string]any) (any, error) { e, _ := a["entry"].(string) k, _ := a["key"].(string) return r.Kept(strings.TrimSpace(e), strings.TrimSpace(k)) }}, {Name: "licences_conditions", Description: "The conditions the manager said and holds: each key of its state conditions with its body and since.", Run: func(map[string]any) (any, error) { return licences.KeptConditions(r.States[licences.EntryConditions]) }}, } } // 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) { audit, ok := r.Logs[licences.EntryAudit] if !ok { return nil, fmt.Errorf("the module keeps no log %s", licences.EntryAudit) } info, err := audit.Info() if err != nil || info.Last == 0 { return []map[string]any{}, err } var all []stdio.LogEntry for next := uint64(1); next != 0; { page, err := audit.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 } carriedForm(e) 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, read from the settings file, its default, where mesh-config says it was // set, and why it is invalid when it is (§40, B18). func (r *Reader) SettingsInForce(ctx context.Context) ([]SettingAnswer, error) { levels := map[string]string{} if r.ConfigLevels != nil { got, err := r.ConfigLevels(ctx) if err != nil { return nil, fmt.Errorf("mesh-config did not say where each setting was set: %w", err) } levels = got } s := licences.SettingsFrom(r.SettingsFile) reasons := s.Reasons() values, defaults := s.Values(), licences.Defaults.Values() var out []SettingAnswer for k, v := range values { a := SettingAnswer{Setting: k, Value: v, Default: defaults[k], Level: levels[k], Invalid: reasons[k]} if a.Level == "" { a.Level = "default" } out = append(out, a) } sort.Slice(out, func(i, j int) bool { return out[i].Setting < out[j].Setting }) return out, nil } // Kept answers one entry the module 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 (r *Reader) Kept(entry, key string) (map[string]any, error) { out := map[string]any{} names := []string{} for n := range r.States { names = append(names, n) } for n := range r.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 := r.States[n]; ok { a, err := stateAnswer(st, key) if err != nil { return nil, fmt.Errorf("%s: %w", n, err) } out[n] = a continue } a, err := logAnswer(r.Logs[n], key) if err != nil { return nil, fmt.Errorf("%s: %w", n, err) } out[n] = a } return out, nil } func stateAnswer(st licences.State, 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 logAnswer(l licences.Log, 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 }