Read how a module says each resource is ready, and send it to engines that read it (hq ADR 0240, to-be 48 Phase B)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-controller@4291fee68e95 (merged as a5a132ac into main, walk plan-17913907…
mesh/delivery-group group feat/health-the-field delivered: every member is delivered
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-controller@4291fee68e95 (merged as a5a132ac into main, walk plan-17913907…
mesh/delivery-group group feat/health-the-field delivered: every member is delivered
A module could say nothing about what ready means for what it runs, so a web application with its port open and its requests hanging passed everything for eleven hours (issue 145). A long-running resource now carries `health` — the image's own check adopted by name, http, tcp, exec, unit or a module's own tool, with its timing — refused near its author when it names a port or an address, an endpoint the module does not declare, a tool it does not serve, a tool check alone, or a timing outside the record's bounds. It is composed with the endpoint as the port this machine published it on, and sent only to a node-engine whose statement says it reads it: an older one would refuse the whole declaration. The engine is granted its own machine's instance of each health tool. `module check` warns of every long-running resource without `health`, counts them for the catalogue, and refuses them from 2026-11-18. A check's findings stay out of a condition's summary. The node-engine's validator is vendored at its Phase B commit, so what is composed is judged by the words the engine takes.
This commit is contained in:
+41
-3
@@ -590,6 +590,23 @@ type Process struct {
|
||||
// For a process that stays up; a step or a scheduled run is not running a moment later by
|
||||
// design, so there is nothing to hand over to.
|
||||
Replaces []string `json:"replaces,omitempty"`
|
||||
|
||||
// Witness is how the node-engine judges a new build of this process, and restores the build
|
||||
// before it when the new one is not healthy in bound (novox/hq to-be 45 §8): "lease" — the
|
||||
// controller this machine started holds the controller's lease; "ping" — this machine's runtime
|
||||
// answers the services protocol's PING; "none". Absent is the default for the process's name:
|
||||
// the mesh's two core processes are judged, nothing else is. For a process that stays up.
|
||||
Witness string `json:"witness,omitempty"`
|
||||
|
||||
// NotReversible says why this build may not be rolled back, when it may not: the build before it
|
||||
// would run against what this one changes — a migration it runs that the older build cannot read
|
||||
// (to-be 45 §8, rule 8). A build so declared that is not healthy in bound is left running and said
|
||||
// as urgent; the build before it is never started against the newer data.
|
||||
NotReversible string `json:"not-reversible,omitempty"`
|
||||
|
||||
// Health is how this resource is ready (novox/hq ADR 0240 rule 2, Phase B): one kind and its
|
||||
// timing, judged by the node-engine beside liveness. Absent: judged alive or not, and nothing more.
|
||||
Health *Health `json:"health,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Process) Identity() string { return d.ID }
|
||||
@@ -621,7 +638,7 @@ func ProcessNameProblem(name string) string {
|
||||
}
|
||||
|
||||
func (d *Process) validate(where string, _ bool) []string {
|
||||
var problems []string
|
||||
problems := d.Health.problems(where, false, !d.RunOnce && d.Schedule == "")
|
||||
if problem := ProcessNameProblem(d.Name); problem != "" {
|
||||
problems = append(problems, where+": "+problem)
|
||||
}
|
||||
@@ -637,6 +654,19 @@ func (d *Process) validate(where string, _ bool) []string {
|
||||
if len(d.Run) == 0 {
|
||||
problems = append(problems, where+": a process needs to say what to run")
|
||||
}
|
||||
switch d.Witness {
|
||||
case "", "lease", "ping", "none":
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf("%s: witness %q is not one this host keeps: lease, ping or none",
|
||||
where, d.Witness))
|
||||
}
|
||||
if d.Witness != "" && d.Witness != "none" && (d.RunOnce || d.Schedule != "") {
|
||||
problems = append(problems, where+": a witness judges a process that stays up; a step or a "+
|
||||
"scheduled run is not running between its runs")
|
||||
}
|
||||
if strings.ContainsAny(d.NotReversible, "\n\r") {
|
||||
problems = append(problems, where+": not-reversible is one line")
|
||||
}
|
||||
for _, part := range d.Run {
|
||||
if part == "" {
|
||||
problems = append(problems, where+": a process command has an empty element")
|
||||
@@ -756,6 +786,10 @@ type Service struct {
|
||||
// said by the controller, which knows the found tunnel's key is this node's own: without that,
|
||||
// starting this unit on the found one's port would drop every peer's packets.
|
||||
TakesOver *TakeOver `json:"takes-over,omitempty"`
|
||||
|
||||
// Health is how this resource is ready (novox/hq ADR 0240 rule 2, Phase B): one kind and its
|
||||
// timing, judged by the node-engine beside liveness. Absent: judged alive or not, and nothing more.
|
||||
Health *Health `json:"health,omitempty"`
|
||||
}
|
||||
|
||||
// TakeOver is a found tunnel a service replaces: its interface, the unit that raised it, and its
|
||||
@@ -784,7 +818,7 @@ const (
|
||||
func (s *Service) UserScoped() bool { return s.Scope == ScopeUser }
|
||||
|
||||
func (s *Service) validate(where string, _ bool) []string {
|
||||
var problems []string
|
||||
problems := s.Health.problems(where, false, s.State == "running")
|
||||
if s.Unit == "" {
|
||||
problems = append(problems, where+": a service needs a unit")
|
||||
}
|
||||
@@ -1096,6 +1130,10 @@ type Container struct {
|
||||
// offline job says *before*, not *instead of*; a recurring window is the case order cannot
|
||||
// express, and the only one this serves.
|
||||
WhileStopped []string `json:"while-stopped,omitempty"`
|
||||
|
||||
// Health is how this resource is ready (novox/hq ADR 0240 rule 2, Phase B): one kind and its
|
||||
// timing, judged by the node-engine beside liveness. Absent: judged alive or not, and nothing more.
|
||||
Health *Health `json:"health,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Container) Identity() string { return c.ID }
|
||||
@@ -1103,7 +1141,7 @@ func (c *Container) Kind() Type { return TypeContainer }
|
||||
func (c *Container) Target() string { return c.Name }
|
||||
|
||||
func (c *Container) validate(where string, _ bool) []string {
|
||||
var problems []string
|
||||
problems := c.Health.problems(where, true, !c.RunOnce && c.Schedule == "")
|
||||
if c.Name == "" {
|
||||
problems = append(problems, where+": a container needs a name")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user