// Command mesh-delivery-checker-tools serves the checker's read tools (novox/hq ADR 0484 clause 13, issue 1686): the runtime // launches it for a call, it reads the checker's verdicts and kept states and the indexer's candidates through the checker's // state and reads, and writes nothing. The checker is the other program: it serves no tool (ADR 0437 §4). package main import ( "encoding/json" "fmt" "os" "time" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-checker/internal/check" ) func read(s stdio.KeptState) check.StateRead { return check.StateRead{Keys: s.Keys, Get: func(key string) (json.RawMessage, error) { e, err := s.Get(key) if err != nil || e == nil { return nil, err } return e.Value, nil }} } // stateStore is a state the tools write: the tries. type stateStore struct { check.StateRead create func(key string, value []byte) error } func (s stateStore) Keys() ([]string, error) { return s.StateRead.Keys() } func (s stateStore) Get(key string) (json.RawMessage, error) { return s.StateRead.Get(key) } func (s stateStore) Create(key string, value []byte) error { return s.create(key, value) } // Trying is what the try's tools work on: the tries, and the gate's trunk heads. type Trying struct { Tries check.TryStore Trunks func() (map[string]string, error) Now func() time.Time } // TryTools are the try's two tools (ADR 0501 §5): ask one, read one. mesh-controller's try calls them. func TryTools(tr Trying) []stdio.Tool { str := func(args map[string]any, k string) string { s, _ := args[k].(string); return s } return []stdio.Tool{ {Name: check.TryTool, Description: "Try a pushed head before its pull request is opened: the checker judges it as it judges a pull request's head, merged onto its trunk's head, " + "its unit and integration levels through the build modules, and owes the forge nothing. Answers the try's id at once; " + check.TryReadTool + " reads " + "where it stands. A try of the same head that waits or runs is the answer to a second ask; at most 4 wait or run at once, mesh-wide. A repository " + "the checker checks no level of (hq, one whose trunk the gate does not name) is refused.", Input: map[string]any{"type": "object", "required": []string{"repository", "head"}, "properties": map[string]any{ "repository": map[string]any{"type": "string", "description": "/"}, "head": map[string]any{"type": "string", "description": "the full commit to try, 40 or 64 hex digits"}, "trunk": map[string]any{"type": "string", "description": "the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent"}, "tests": map[string]any{"type": "string", "description": "a go test -run pattern: only those tests run, and nothing is kept"}, "level": map[string]any{"type": "string", "enum": []string{"unit", "integration"}, "description": "only this level; both when absent"}}}, Run: func(args map[string]any) (any, error) { if tr.Tries == nil || tr.Trunks == nil { return nil, fmt.Errorf("the tries state cannot be written here") } trunks, err := tr.Trunks() if err != nil { return nil, fmt.Errorf("the trunk heads cannot be read: %w", err) } now := time.Now if tr.Now != nil { now = tr.Now } id, err := check.AskTry(tr.Tries, trunks, check.TryAsk{Repository: str(args, "repository"), Head: str(args, "head"), Trunk: str(args, "trunk"), Tests: str(args, "tests"), Level: str(args, "level")}, now()) if err != nil { return nil, err } return map[string]any{"id": id}, nil }}, {Name: check.TryReadTool, Description: "Where a try stands, as stored: queued, running, or its end (pass, fail, or error when it could not run), with what was said of it, " + "the head and the trunk commit it was merged onto, the time it took and what failed first. Read-only.", Input: map[string]any{"type": "object", "required": []string{"id"}, "properties": map[string]any{ "id": map[string]any{"type": "string", "description": "the try's id, try-"}}}, Run: func(args map[string]any) (any, error) { if tr.Tries == nil { return nil, fmt.Errorf("the tries state cannot be read here") } return check.ReadTry(tr.Tries, str(args, "id")) }}, } } // Tools are the read tools over the checker's states. func Tools(r check.Reader) []stdio.Tool { return []stdio.Tool{ {Name: check.VerdictsReadTool, Description: "The verdicts the checker holds, each as stored under verdicts..: its state, what failed first, the tests run, kept and skipped per level, the time of each part, the trunk heads it judged against and the composition, with the full " + "report of the status each pull request carries (the line the forge shows is cut at 140 characters, this is not). With a group, that group's only. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{ "group": map[string]any{"type": "string", "description": "the delivery group's branch name"}}}, Run: func(args map[string]any) (any, error) { group, _ := args["group"].(string) vs, err := r.Verdicts(group) return map[string]any{"verdicts": vs}, err }}, {Name: check.PullReadTool, Description: "The verdict that judges one pull request and the full report of its status mesh/check; for an hq pull request, that hq is judged by " + "merge-check.sh until issue 1296. Read-only.", Input: map[string]any{"type": "object", "required": []string{"repository", "number"}, "properties": map[string]any{ "repository": map[string]any{"type": "string", "description": "/"}, "number": map[string]any{"type": "integer", "description": "the pull request's number"}}}, Run: func(args map[string]any) (any, error) { repository, _ := args["repository"].(string) n, _ := args["number"].(float64) return r.Pull(repository, int(n)) }}, {Name: check.KeptReadTool, Description: "The kept verdicts of passing packages, each as stored under kept..: the package, its tests, the time they took, the toolchain " + "and when it was kept. With a level, that level's; with a level and a digest, that one. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{ "level": map[string]any{"type": "string", "enum": []string{"unit", "integration"}, "description": "the level"}, "digest": map[string]any{"type": "string", "description": "the package's digest, with a level"}}}, Run: func(args map[string]any) (any, error) { level, _ := args["level"].(string) digest, _ := args["digest"].(string) kept, err := r.Kept(level, digest) return map[string]any{"kept": kept}, err }}, {Name: check.PreviewsReadTool, Description: "The merge previews the checker holds, each as stored under previews...: the merge commit, per module directory " + "its tree, manifest digest and lock hashes, or the files in conflict. With a repository, that repository's; with a head (7 or more characters) " + "only that head's. Keys that are no preview key are listed as unreadable. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{ "repository": map[string]any{"type": "string", "description": "/"}, "head": map[string]any{"type": "string", "description": "a pull request head commit, or a prefix of at least 7 characters"}}}, Run: func(args map[string]any) (any, error) { repository, _ := args["repository"].(string) head, _ := args["head"].(string) if head != "" && len(head) < 7 { return nil, fmt.Errorf("the head %q is too short: give at least 7 characters", head) } ps, bad, err := r.Previews(repository, head) return map[string]any{"previews": ps, "unreadable": bad}, err }}, {Name: check.StatusReadTool, Description: "What holds the checker back and what runs: the capacity per node (the setting, the default one step per node), whether the controller's " + "delivery plans in their build phase hold a key (no new check starts while one does), the steps in flight on the build modules by node, and the " + "candidates without a verdict for their heads. The steps the checker holds waiting for a place are in its process, not in a bucket, and the " + "answer says so. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{}}, Run: func(map[string]any) (any, error) { return r.Status() }}, } } func capacity() (string, error) { path := os.Getenv("MESH_CHECKER_SETTINGS") if path == "" { return os.Getenv("MESH_CHECKER_CAPACITY"), nil } raw, err := os.ReadFile(path) if err != nil { return "", err } var s struct { Capacity string `json:"capacity"` } return s.Capacity, json.Unmarshal(raw, &s) } func main() { r := check.Reader{VerdictState: read(stdio.State("verdicts")), KeptState: read(stdio.State("kept")), CandidateState: read(stdio.State("mesh-delivery-indexer.candidates")), PreviewState: read(stdio.State("previews")), PlansState: read(stdio.State("mesh-controller.plans-building")), StepStates: map[string]check.StateRead{"mesh-build-go": read(stdio.State("mesh-build-go.steps")), "mesh-build-node": read(stdio.State("mesh-build-node.steps"))}, Capacity: capacity} tries := stdio.State(check.TriesState) source := stdio.State("mesh-delivery-gate.source") trying := Trying{ Tries: stateStore{StateRead: read(tries), create: func(key string, value []byte) error { _, err := tries.Create(key, json.RawMessage(value)) return err }}, Trunks: func() (map[string]string, error) { e, err := source.Get(delivery.SourceMainKey) if err != nil || e == nil { return nil, err } s, err := delivery.DecodeSource(e.Value) return s.Heads, err }, } if err := stdio.Serve("", append(Tools(r), TryTools(trying)...)); err != nil { fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err) os.Exit(1) } }