package catalogue import "fmt" // The delivery's owner (novox/hq ADR 0239, to-be 47): one holder for the mesh, the module mesh-delivery, // which owns a delivery — one commit in one repository, from its pull request's head to every machine — // and a delivery group, and asks the controller for every act. A seat of the mesh's own, so the role has // one name whoever holds it, and the controller can tell whether anything holds it: while nothing does, // the controller starts every walk itself, as it did before there was a delivery to own. // DeliverySeat is the seat mesh-delivery holds. const DeliverySeat = "mesh-delivery" // RemovedDeliveryVerbs are the delivery seat's verbs release 6 removed (novox/hq ADR 0539 clause 17): with no plan the // gate keeps the merge's states and derives nothing after the merge. Each stays in deliveryVerbs as an optional row // with no input, so the holder of before and the holder of release 6 both hold the seat and a caller is answered by // name with its replacement, given here. var RemovedDeliveryVerbs = map[string]string{ "times": "the delivery view (nox-delivery): each machine's want and have, and since when", "what-if": "the merge check's composition, which composes every machine a change touches", "release": "the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view", "stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)", "retire-history": "nothing to retire: the gate keeps no delivery after its merge", } // deliveryRemovedInR6 is the row of a verb release 6 removed from the delivery seat. func deliveryRemovedInR6(name string) Verb { return Verb{Name: name, Description: "Removed in R6 (ADR 0539): refused by name, naming its replacement: " + RemovedDeliveryVerbs[name], Input: schema(nil, nil), Optional: true} } // deliveryVerbs are the delivery seat's tools: what reads the merges and checks, and the acts of a person and healer H2 // on a merge (ADR 0539 clause 17). func deliveryVerbs() []Verb { return []Verb{ {Name: "deliveries", Description: "Every delivery not final, and those that ended in the last day, one line " + "each: its id (owner/repository@commit), its state, its group, what it waits for and since when. Narrowed " + "by state, repository or group.", Input: schema(map[string]string{"state": "one state, e.g. delivering or held", "repository": "owner/repository", "group": "a group's id (its branch name)", "all": "\"true\": the final ones of the last thirty days too"}, nil, "all")}, deliveryRemovedInR6("times"), {Name: "show", Description: "One delivery or group whole: its delivery plan (what it builds, what each " + "machine receives, what is not an ordinary send), every transition with when and why, the machine " + "steps of its walk, its group and its order.", Input: schema(map[string]string{"id": "a delivery's id, or a group's"}, []string{"id"})}, {Name: "groups", Description: "Every delivery group: its members in order, why each pair is ordered " + "(declared, built by, version skew, engine before controller, by name), its composed check and its " + "state, derived from its members.", Input: schema(map[string]string{"all": "\"true\": the groups that ended too"}, nil, "all")}, deliveryRemovedInR6("what-if"), {Name: "checks", Description: "What the mesh's checks said of a pull request's head or of one commit: each " + "of the commit's mesh statuses (mesh/check, the one a main requires from R4e, hq's mesh/repo-check, " + "mesh/delivery, …) with its state, " + "description and when it was set; the merge check's full verdict as the controller said it — each " + "layer's summary, the machine that ran it, when, its build id and its report; and whether the branch's " + "protection would let it merge, every required status being success.", Input: schema(map[string]string{"repository": "owner/repository", "number": "a pull request's number: its head is read", "commit": "a commit's sha, or the start of one, instead of a pull request"}, []string{"repository"}), // Added after the seat's first holder shipped: optional until mesh-delivery serves it everywhere. Optional: true}, {Name: "table", Description: "The state table every delivery runs by: each transition with its guard, " + "each state's bound and what healer H2 may do once it has passed; and the machine steps' table.", Input: schema(map[string]string{}, nil)}, {Name: "stalled", Description: "Every delivery held past its state's bound, with the transition the table " + "lets healer H2 take for it, or none.", Input: schema(map[string]string{}, nil)}, {Name: "recheck", Description: "Check a rejected or ready delivery again: it goes back to proposed and the " + "controller is asked for its check. With why.", Input: schema(map[string]string{"id": "the delivery's id", "why": "why, kept with the transition"}, []string{"id", "why"})}, deliveryRemovedInR6("release"), deliveryRemovedInR6("stop"), {Name: "close", Description: "Healer H2's verb: take the one transition the table names for a delivery held " + "past its bound. Refused for any other.", Input: schema(map[string]string{"id": "the delivery's id", "why": "the condition it answers"}, []string{"id", "why"})}, deliveryRemovedInR6("retire-history"), {Name: "settle", Description: "Ask the operator to settle deliveries held because their merge reached the " + "trunk without a passing check: one ask on the operator's channel for all of them, as an Approve is asked. " + "The verb moves nothing; on the operator's answer each goes to merged, and whether it is on the machines " + "is derived as for any merge (novox/hq ADR 0556). Refused for a delivery not held, or held for another " + "reason, naming why.", Input: map[string]any{"type": "object", "additionalProperties": false, "required": []string{"ids", "why"}, "properties": map[string]any{ "ids": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "minItems": 1, "description": "the held deliveries' ids (owner/repository@commit)"}, "why": map[string]any{"type": "string", "description": "why they may be settled, shown to the operator"}, }}, // Added after the seat's release 6 holder shipped: optional until the gate serves it everywhere. Optional: true}, } } // SeatSays is whether a consumed `.` names one of the seat's own events: the seat emits it, // exactly or by one of its patterns (`log.*`). A consumed name whose emitter is a seat and whose event the // seat does not emit is the event of the module of that name (novox/hq ADR 0239): mesh-delivery is a seat // and the module holding it, and what the module says it says as itself, through the runtime that launches // it — read as the seat's, a consumer would subscribe a subject nothing may publish. func SeatSays(emits []string, event string) bool { for _, e := range emits { if e == event || topicMatches(e, event) { return true } } return false } // seatEventProblems refuses a module that lists among its own `emits` an event a seat it claims says (the lead, // 2026-10-29, issue 1952, the second of a kind: the self-check's `checked` and mesh-hand-acts' `act-recorded`). A // consumer of `.` hears the seat's subject (SeatSays), so the holder says it there, with stdio.SeatPublish // on `mesh.seat..event.`; said through stdio.Emit as the module's own, it lands where nobody listens. Caught // here, when the manifest is read at the build, not when nothing is heard on the machines. func seatEventProblems(m Manifest) []string { var problems []string for _, c := range m.Claims { seat, known := SeatNamed(c.Name) if !known || len(seat.Emits) == 0 { continue } for _, e := range m.Emits { if SeatSays(seat.Emits, e) { problems = append(problems, fmt.Sprintf("%s lists %q among its own emits, and the seat %s it claims says it: "+ "say it on the seat with stdio.SeatPublish(\"mesh.seat.%s.event.%s\", ...) and leave it out of emits, or "+ "no consumer of %s.%s hears it", m.Module, e, seat.Name, seat.Name, e, seat.Name, e)) } } } return problems } // topicMatches is whether an event's name matches a declared pattern, `*` one dot-separated segment and // `>` or `**` the rest. func topicMatches(pattern, event string) bool { p, e := splitDots(pattern), splitDots(event) for i, part := range p { if part == ">" || part == "**" { return len(e) > i } if i >= len(e) || (part != "*" && part != e[i]) { return false } } return len(p) == len(e) } func splitDots(s string) []string { var out []string start := 0 for i := 0; i < len(s); i++ { if s[i] == '.' { out = append(out, s[start:i]) start = i + 1 } } return append(out, s[start:]) }