Use the glossary's words, say which modules raise a condition, and judge left-out modules in D1 (review of #223)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without …
mesh/repo-check pass: its merge-check.sh passed

The operator reads manifest, declaration and send, not definition,
composition and update. D1 already resolves every machine, so the
left-out judgement rides on it instead of resolving each machine again.
A test holds that a wait's own needs-operator still clears beside it.
This commit is contained in:
2026-10-11 11:10:22 +02:00
parent a330c564ba
commit 419d662ad8
5 changed files with 115 additions and 67 deletions
+6 -7
View File
@@ -74,8 +74,12 @@ type probe struct {
// probeRegistry is the registry, in to-be 45's order. **The registry is the design's live form**: a // probeRegistry is the registry, in to-be 45's order. **The registry is the design's live form**: a
// probe added to a design is a row added here. // probe added to a design is a row added here.
var probeRegistry = []probe{ var probeRegistry = []probe{
{ID: "D1", Asserts: "every machine's declaration composes, and passes the node-engine's validation", // D1 also says every module assigned and left out of a machine's declaration (novox/hq issue 380), from the
From: "issues 236, 263, 275", Kind: "declaration-refused", Raises: []string{kindAwaitingPush}, Phase: 1, // resolution it already makes: needs-operator for a setting nobody gave, left-out for any other cause.
{ID: "D1", Asserts: "every machine's declaration composes, and passes the node-engine's validation; no module " +
"assigned to a machine is left out of its declaration unsaid",
From: "issues 236, 263, 275, 380", Kind: "declaration-refused",
Raises: []string{kindAwaitingPush, kindLeftOut, kindNeedsOperator}, Phase: 1,
run: probeDeclarations}, run: probeDeclarations},
{ID: "D2", Asserts: "every holder of the mesh's resolver answers a machine name for IPv4, and NODATA for IPv6", {ID: "D2", Asserts: "every holder of the mesh's resolver answers a machine name for IPv4, and NODATA for IPv6",
From: "issue 262", Kind: "resolver-wrong", Phase: 1, run: probeResolvers}, From: "issue 262", Kind: "resolver-wrong", Phase: 1, run: probeResolvers},
@@ -136,11 +140,6 @@ var probeRegistry = []probe{
{ID: "D-root", Asserts: "no agent can become root without a person on a machine where the router or a channel " + {ID: "D-root", Asserts: "no agent can become root without a person on a machine where the router or a channel " +
"proving its sender runs: not by its own account, and not through a tool that runs its command as an account " + "proving its sender runs: not by its own account, and not through a tool that runs its command as an account " +
"that can", From: "ADR 0259 §8", Kind: kindRootNotFree, Phase: 2, run: probeAgentRoot}, "that can", From: "ADR 0259 §8", Kind: kindRootNotFree, Phase: 2, run: probeAgentRoot},
// A module assigned and left out of its machine's composition (novox/hq issue 380): said on that machine, the
// operator's, never urgent; cleared once it composes again or is unassigned.
{ID: probeLeftOutID, Asserts: "no module assigned to a machine is left out of its composition unsaid: each " +
"raises needs-operator for a setting nobody gave, left-out for any other cause", From: "issue 380",
Kind: kindLeftOut, Raises: []string{kindNeedsOperator}, Phase: 1, run: probeLeftOut},
{ID: "DW", Asserts: "the watchdogs of the signals table ran within three of their intervals", {ID: "DW", Asserts: "the watchdogs of the signals table ran within three of their intervals",
From: "ADR 0227 rule 6: the watchers are watched", Kind: "watchdogs-silent", Phase: 1, run: probeWatchdogs}, From: "ADR 0227 rule 6: the watchers are watched", Kind: "watchdogs-silent", Phase: 1, run: probeWatchdogs},
// The core's health definitions (novox/hq to-be 45 §8, ADR 0236): what a core component's new build is // The core's health definitions (novox/hq to-be 45 §8, ADR 0236): what a core component's new build is
+48 -52
View File
@@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
@@ -11,12 +10,12 @@ import (
"github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/conditions"
) )
// A module assigned to a machine and left out of its composition is said (novox/hq issue 380). // A module assigned to a machine and left out of its declaration is said (novox/hq issue 380).
// //
// **An omission is a finding, never a refusal of the push** (ADR 0163, rule 6): the machine is sent everything // **An omission is a finding, never a refusal of the push** (ADR 0163, rule 6): the machine is sent everything
// else, and the module's held things are kept. Until issue 380 the only place it showed was `plan`: nfs-server was // else, and the module's held things are kept. Until issue 380 the only place it showed was `plan`: nfs-server was
// assigned to the home server for days, every push left it out for a setting nobody gave, and the operator believed // assigned to the home server for days, every send left it out for a setting nobody gave, and the operator believed
// it ran. So the self-check composes every machine as the next push would (Resolution.LeftOutBecause, the push's // it ran. So the self-check (D1) judges every machine as the next send would (Resolution.LeftOutBecause, the send's
// own judgement) and raises a condition on that machine for each module it leaves out: // own judgement) and raises a condition on that machine for each module it leaves out:
// //
// - a setting nobody gave (catalogue.UnsetSettingError) is the operator's to give: kind needs-operator, naming // - a setting nobody gave (catalogue.UnsetSettingError) is the operator's to give: kind needs-operator, naming
@@ -31,19 +30,20 @@ import (
// catalogue's own unknown-field condition says it once for the whole mesh (ADR 0262); it is still listed. // catalogue's own unknown-field condition says it once for the whole mesh (ADR 0262); it is still listed.
const ( const (
// probeLeftOutID is the self-check's probe that raises and clears these conditions. // probeLeftOutID is the self-check's probe that raises and clears these conditions: D1, which already
probeLeftOutID = "D-left-out" // resolves every machine (probeDeclarations), so the judgement costs no resolution of its own.
probeLeftOutID = "D1"
// kindLeftOut is a module left out for any cause but a setting nobody gave; it is also every such condition's // kindLeftOut is a module left out for any cause but a setting nobody gave; it is also every such condition's
// token, whichever its kind, so a cause that changes is the same condition said anew. // token, whichever its kind, so a cause that changes is the same condition said anew.
kindLeftOut = "left-out" kindLeftOut = "left-out"
) )
// leftOutModule is one module of a machine's set that its composition leaves out, and why. // leftOutModule is one module of a machine's set that its declaration leaves out, and why.
type leftOutModule struct { type leftOutModule struct {
Module string Module string
// Setting is the setting nobody gave, when that is the cause. // Setting is the setting nobody gave, when that is the cause.
Setting string Setting string
// Why is the composition's own reason, whole. // Why is the declaration's own reason, whole.
Why string Why string
// Unread is a stored manifest this controller cannot read whole (said by the catalogue's condition). // Unread is a stored manifest this controller cannot read whole (said by the catalogue's condition).
Unread bool Unread bool
@@ -76,13 +76,13 @@ func settingCommand(module, setting, node string) string {
} }
// reason is why a module is left out, as `status`, `node show` and the condition's summary say it: for a setting // reason is why a module is left out, as `status`, `node show` and the condition's summary say it: for a setting
// nobody gave, the setting and the command that gives it; otherwise the composition's own words. // nobody gave, the setting and the command that gives it; otherwise the declaration's own words.
func (l leftOutModule) reason(node string) string { func (l leftOutModule) reason(node string) string {
if l.Setting != "" { if l.Setting != "" {
return fmt.Sprintf("nothing sets its setting %q, so every push leaves it out — %s sets it", l.Setting, return fmt.Sprintf("nothing sets its setting %q, so every send leaves it out of its declaration — %s sets it", l.Setting,
settingCommand(l.Module, l.Setting, node)) settingCommand(l.Module, l.Setting, node))
} }
return "every push leaves it out: " + l.Why return "every send leaves it out of its declaration: " + l.Why
} }
// leftOutObservations are the conditions a machine's left-out modules raise, one each. // leftOutObservations are the conditions a machine's left-out modules raise, one each.
@@ -97,7 +97,7 @@ func leftOutObservations(node string, left []leftOutModule) []conditions.Observa
return out return out
} }
// leftOutObservation is one module left out of one machine's composition: needs-operator for a setting nobody // leftOutObservation is one module left out of one machine's declaration: needs-operator for a setting nobody
// gave, left-out otherwise; a warning either way, the operator's to resolve. // gave, left-out otherwise; a warning either way, the operator's to resolve.
func leftOutObservation(node string, l leftOutModule) conditions.Observation { func leftOutObservation(node string, l leftOutModule) conditions.Observation {
o := conditions.Observation{Scope: conditions.ScopeModule, ID: l.Module + "." + node, Token: kindLeftOut, o := conditions.Observation{Scope: conditions.ScopeModule, ID: l.Module + "." + node, Token: kindLeftOut,
@@ -108,10 +108,10 @@ func leftOutObservation(node string, l leftOutModule) conditions.Observation {
if l.Setting != "" { if l.Setting != "" {
o.Kind = kindNeedsOperator o.Kind = kindNeedsOperator
} else { } else {
// The composition's words may name a path or an address, which the operator's channel withholds: the // The words of why may name a path or an address, which the operator's channel withholds: the
// summary sends them to the evidence, and `plan` says them in full. // summary sends them to the evidence, and `plan` says them in full.
o.Summary = fmt.Sprintf("%s is assigned to %s and not applied: every push leaves it out, because what is "+ o.Summary = fmt.Sprintf("%s is assigned to %s and not applied: every send leaves it out of its declaration, "+
"set for it does not compose with its definition — the evidence and `plan %s` say why", l.Module, node, node) "because what is set for it does not fit its manifest — the evidence and `plan %s` say why", l.Module, node, node)
} }
o.Headline, o.Explanation, o.Needs, o.Resolved = w.Headline, w.Explanation, w.Needs, w.Resolved o.Headline, o.Explanation, o.Needs, o.Resolved = w.Headline, w.Explanation, w.Needs, w.Resolved
return o return o
@@ -121,20 +121,20 @@ func leftOutObservation(node string, l leftOutModule) conditions.Observation {
func leftOutWords(module, node, setting string) words { func leftOutWords(module, node, setting string) words {
w := words{ w := words{
Headline: fmt.Sprintf("%s is not applied on %s", module, node), Headline: fmt.Sprintf("%s is not applied on %s", module, node),
Explanation: fmt.Sprintf("%s is assigned to %s, and every update of %s leaves it out because what is set "+ Explanation: fmt.Sprintf("%s is assigned to %s, and every send to %s leaves it out of the declaration because what "+
"for it does not fit its definition. Nothing of it changes there; the rest of %s is updated as usual.", "is set for it does not fit its manifest. Nothing of it changes there; the rest of %s is sent as usual.",
module, node, node, node), module, node, node, node),
Needs: fmt.Sprintf("read why in the details, then change what is set for %s or unassign it.", module), Needs: fmt.Sprintf("read why in the details, then change what is set for %s or unassign it.", module),
Resolved: fmt.Sprintf("%s on %s is no longer left out", module, node), Resolved: fmt.Sprintf("%s on %s is no longer left out", module, node),
} }
if setting != "" { if setting != "" {
w.Explanation = fmt.Sprintf("%s is assigned to %s, and every update of %s leaves it out because nothing "+ w.Explanation = fmt.Sprintf("%s is assigned to %s, and every send to %s leaves it out of the declaration because "+
"sets its setting %s. Nothing of it runs there until it is set; the rest of %s is updated as usual.", "nothing sets its setting %s. Nothing of it runs there until it is set; the rest of %s is sent as usual.",
module, node, node, setting, node) module, node, node, setting, node)
w.Needs = fmt.Sprintf("set %s for %s on %s, or approve it when it is proposed to you.", setting, module, node) w.Needs = fmt.Sprintf("set %s for %s on %s, or approve it when it is proposed to you.", setting, module, node)
// A setting's name that is not plain (a dotted key) is in the summary instead. // A setting's name that is not plain (a dotted key) is in the summary instead.
if _, ok := conditions.PlainWords(w, node); !ok { if _, ok := conditions.PlainWords(w, node); !ok {
w.Explanation = fmt.Sprintf("%s is assigned to %s, and every update of %s leaves it out because a "+ w.Explanation = fmt.Sprintf("%s is assigned to %s, and every send to %s leaves it out of the declaration because a "+
"setting it needs is not set. Nothing of it runs there until it is set; the details name it.", "setting it needs is not set. Nothing of it runs there until it is set; the details name it.",
module, node, node) module, node, node)
w.Needs = fmt.Sprintf("set what %s needs on %s; the details name the setting.", module, node) w.Needs = fmt.Sprintf("set what %s needs on %s; the details name the setting.", module, node)
@@ -143,31 +143,6 @@ func leftOutWords(module, node, setting string) words {
return w return w
} }
// probeLeftOut is the self-check's probe of issue 380: every machine's set is judged as its next push would
// judge it, and each module left out raises its condition. A machine that does not resolve is passed over — D1
// says it — and a store that cannot be read is an error, never "nothing left out" (ADR 0227 rule 4).
func probeLeftOut(ctx context.Context, d *doctor) ([]conditions.Observation, error) {
nodes, err := d.open.inventory.Nodes(ctx)
if err != nil {
return nil, err
}
var out []conditions.Observation
for _, n := range nodes {
plan, settings, err := planFor(ctx, d.open, n.Name)
if err != nil {
if unresolvable(err) {
continue
}
return nil, fmt.Errorf("%s cannot be worked out: %w", n.Name, err)
}
out = append(out, leftOutObservations(n.Name, leftOutOf(plan, settings, n.Adopted))...)
if ctx.Err() != nil {
return nil, ctx.Err()
}
}
return out, nil
}
// notAppliedLines is a machine's "assigned, not applied" as `node show` prints it: one line a module, with the // notAppliedLines is a machine's "assigned, not applied" as `node show` prints it: one line a module, with the
// reason its condition says. // reason its condition says.
func notAppliedLines(node string, left []leftOutModule) []string { func notAppliedLines(node string, left []leftOutModule) []string {
@@ -181,12 +156,15 @@ func notAppliedLines(node string, left []leftOutModule) []string {
return lines return lines
} }
// machineNotApplied is one module assigned to a machine and left out of its composition, in `status --json`. // machineNotApplied is one module assigned to a machine and left out of its declaration, in `status --json`.
type machineNotApplied struct { type machineNotApplied struct {
Node string `json:"node"` Node string `json:"node"`
Module string `json:"module"` Module string `json:"module"`
Setting string `json:"setting,omitempty"` Setting string `json:"setting,omitempty"`
Reason string `json:"reason"` Reason string `json:"reason"`
// UnreadManifest is a module left out because this controller cannot read its manifest whole: it raises no
// condition of its own on the machine, since the catalogue's unknown-field condition says it once (ADR 0262).
UnreadManifest bool `json:"unread-manifest,omitempty"`
} }
// notApplied is every machine's left-out modules, in a stated order, as `status --json` carries them. // notApplied is every machine's left-out modules, in a stated order, as `status --json` carries them.
@@ -199,7 +177,8 @@ func notApplied(left map[string][]leftOutModule) []machineNotApplied {
var out []machineNotApplied var out []machineNotApplied
for _, name := range names { for _, name := range names {
for _, l := range left[name] { for _, l := range left[name] {
out = append(out, machineNotApplied{Node: name, Module: l.Module, Setting: l.Setting, Reason: l.reason(name)}) out = append(out, machineNotApplied{Node: name, Module: l.Module, Setting: l.Setting, Reason: l.reason(name),
UnreadManifest: l.Unread})
} }
} }
return out return out
@@ -211,16 +190,33 @@ func printNotApplied(left map[string][]leftOutModule) {
if len(rows) == 0 { if len(rows) == 0 {
return return
} }
fmt.Printf("%d module(s) assigned, not applied — every push leaves them out, and each raises a condition:\n", unread := 0
len(rows)) for _, r := range rows {
if r.UnreadManifest {
unread++
}
}
// Which raise a condition is said, not implied (review of #223): one whose manifest this controller cannot
// read is listed here and raises none of its own on the machine — the catalogue's condition says it.
raises := "each raises a condition on its machine"
switch {
case unread == len(rows):
raises = "none raises a condition on its machine: this controller cannot read their manifests, which the " +
"catalogue's own condition says"
case unread > 0:
raises += fmt.Sprintf(", except the %d whose manifest this controller cannot read, which the catalogue's own "+
"condition says", unread)
}
fmt.Printf("%d module(s) assigned, not applied — every send leaves them out of their declaration; %s:\n",
len(rows), raises)
for _, r := range rows { for _, r := range rows {
fmt.Printf(" %-12s %-22s %s\n", r.Node, r.Module, r.Reason) fmt.Printf(" %-12s %-22s %s\n", r.Node, r.Module, r.Reason)
} }
fmt.Println() fmt.Println()
} }
// isLeftOutCondition is whether a condition is this probe's, which the machine's health statements neither // isLeftOutCondition is whether a condition is a module left out of its declaration, which the machine's health
// raise nor clear. // statements neither raise nor clear: by its token, which every one carries whatever its kind.
func isLeftOutCondition(c conditions.Condition) bool { func isLeftOutCondition(c conditions.Condition) bool {
return c.Source == probeLeftOutID || strings.HasSuffix(c.Key, "."+kindLeftOut) return c.Subject.Scope == conditions.ScopeModule && strings.HasSuffix(c.Key, "."+kindLeftOut)
} }
+56 -8
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"os" "os"
"slices"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -11,9 +12,10 @@ import (
"github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/catalogue"
"github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/conditions"
"github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
) )
// novox/hq issue 380: nfs-server was assigned to the home server for days and every push left it out — "nfs-server // novox/hq issue 380: nfs-server was assigned to the home server for days and every send left it out — "nfs-server
// has a file that says ${setting:shares}, and nothing sets shares for it" — and nothing but `plan` said so. The // has a file that says ${setting:shares}, and nothing sets shares for it" — and nothing but `plan` said so. The
// manifest is the catalogue's own at the commit that added it (mesh-catalog 48fba44), which still says // manifest is the catalogue's own at the commit that added it (mesh-catalog 48fba44), which still says
// ${setting:shares}; the reason below is the one the live push printed. // ${setting:shares}; the reason below is the one the live push printed.
@@ -60,16 +62,16 @@ func TestAModuleLeftOutForASettingNobodyGaveNeedsTheOperatorNamingTheSettingAndT
} }
} }
if o.Said != left[0].Why { if o.Said != left[0].Why {
t.Errorf("the evidence is not the composition's reason: %s", o.Said) t.Errorf("the evidence is not the reason the send gives: %s", o.Said)
} }
plainExample(t, o, "nfs-server is not applied on anchor", plainExample(t, o, "nfs-server is not applied on anchor",
"Needs you: set shares for nfs-server on anchor, or approve it when it is proposed to you. nfs-server is assigned to "+ "Needs you: set shares for nfs-server on anchor, or approve it when it is proposed to you. nfs-server is assigned to "+
"anchor, and every update of anchor leaves it out because nothing sets its setting shares. Nothing of it "+ "anchor, and every send to anchor leaves it out of the declaration because nothing sets its setting shares. "+
"runs there until it is set; the rest of anchor is updated as usual.") "Nothing of it runs there until it is set; the rest of anchor is sent as usual.")
} }
func TestAModuleLeftOutForAnotherCauseIsAWarningWithTheReasonAsEvidence(t *testing.T) { func TestAModuleLeftOutForAnotherCauseIsAWarningWithTheReasonAsEvidence(t *testing.T) {
// A setting stored that its definition can no longer compose: one with a line break (issue 339). // A setting stored that its manifest can no longer take: one with a line break (issue 339).
left := leftOutOf(leftOutNFS(t), sharesGiven("library=/srv/library\nmedia=/srv/media"), false) left := leftOutOf(leftOutNFS(t), sharesGiven("library=/srv/library\nmedia=/srv/media"), false)
if len(left) != 1 || left[0].Setting != "" { if len(left) != 1 || left[0].Setting != "" {
t.Fatalf("left out: %+v", left) t.Fatalf("left out: %+v", left)
@@ -87,8 +89,8 @@ func TestAModuleLeftOutForAnotherCauseIsAWarningWithTheReasonAsEvidence(t *testi
} }
plainExample(t, o, "nfs-server is not applied on anchor", plainExample(t, o, "nfs-server is not applied on anchor",
"Needs you: read why in the details, then change what is set for nfs-server or unassign it. nfs-server is assigned to "+ "Needs you: read why in the details, then change what is set for nfs-server or unassign it. nfs-server is assigned to "+
"anchor, and every update of anchor leaves it out because what is set for it does not fit its definition. "+ "anchor, and every send to anchor leaves it out of the declaration because what is set for it does not fit "+
"Nothing of it changes there; the rest of anchor is updated as usual.") "its manifest. Nothing of it changes there; the rest of anchor is sent as usual.")
} }
// The self-check raises it, keeps it a warning however long it stands, and clears it when the module composes // The self-check raises it, keeps it a warning however long it stands, and clears it when the module composes
@@ -158,7 +160,7 @@ func TestALeftOutModulesConditionClearsWhenItComposesAgainOrIsUnassigned(t *test
func TestTheLeftOutProbeIsInTheRegistry(t *testing.T) { func TestTheLeftOutProbeIsInTheRegistry(t *testing.T) {
for _, p := range probeRegistry { for _, p := range probeRegistry {
if p.ID == probeLeftOutID { if p.ID == probeLeftOutID {
if p.run == nil || p.Kind != kindLeftOut { if p.run == nil || !slices.Contains(p.Raises, kindLeftOut) || !slices.Contains(p.Raises, kindNeedsOperator) {
t.Fatalf("%+v", p) t.Fatalf("%+v", p)
} }
return return
@@ -201,3 +203,49 @@ func TestStatusAndNodeListAModuleAssignedAndNotApplied(t *testing.T) {
t.Fatalf("node show says:\n%s", lines) t.Fatalf("node show says:\n%s", lines)
} }
} }
// The skip is this probe's alone (review of #223): a part waiting for the operator (ADR 0283) keeps its own
// needs-operator condition, `module.<m>.<node>.needs-operator`, which still clears on the first statement that no
// longer names it — beside a left-out condition on the same machine, which stays.
func TestAWaitsNeedsOperatorStillClearsWhenItsStatementStopsNamingItBesideALeftOutOne(t *testing.T) {
store := conditions.NewInMemory()
k := conditions.NewKeeper(t.Context(), conditions.Options{Store: store, History: store})
defer k.Close(context.Background())
left := leftOutObservations("anchor", leftOutOf(leftOutNFS(t), nil, false))
if err := k.Reconcile(t.Context(), probeLeftOutID, left); err != nil {
t.Fatal(err)
}
waiting := map[string][]inventory.ResourceHealth{"notes": {{Module: "notes", Resource: "server", State: link.StateWaiting,
Waits: []inventory.Wait{{Setting: "domain", What: "the domain it serves"}}}}}
if err := judgeModuleHealth(t.Context(), nil, k, "anchor", waiting, map[string]int{"notes": 2}, time.Now()); err != nil {
t.Fatal(err)
}
waitKey, leftKey := needsOperatorKey("notes", "anchor"), "module.nfs-server.anchor.left-out"
open := func() map[string]conditions.Condition {
t.Helper()
list, err := k.Open(t.Context())
if err != nil {
t.Fatal(err)
}
out := map[string]conditions.Condition{}
for _, c := range list {
out[c.Key] = c
}
return out
}
if c, raised := open()[waitKey]; !raised || c.Kind != kindNeedsOperator {
t.Fatalf("the wait raised %+v", open())
}
if c := open()[leftKey]; c.Kind != kindNeedsOperator {
t.Fatalf("the left-out condition is not open as needs-operator: %+v", open())
}
if err := judgeModuleHealth(t.Context(), nil, k, "anchor", map[string][]inventory.ResourceHealth{}, nil, time.Now()); err != nil {
t.Fatal(err)
}
if _, still := open()[waitKey]; still {
t.Fatalf("the statement no longer names the wait, and its needs-operator is still open: %+v", open())
}
if _, still := open()[leftKey]; !still {
t.Fatalf("the left-out condition was cleared by a health statement: %+v", open())
}
}
+5
View File
@@ -78,6 +78,11 @@ func probeDeclarations(ctx context.Context, d *doctor) ([]conditions.Observation
return nil, fmt.Errorf("%s cannot be worked out: %w", n.Name, err) return nil, fmt.Errorf("%s cannot be worked out: %w", n.Name, err)
} }
var problems, foreseen []string var problems, foreseen []string
// Each module the next send leaves out of this machine's declaration (novox/hq issue 380), judged from this
// resolution as the send judges it: a finding, never a refusal.
if err == nil {
out = append(out, leftOutObservations(n.Name, leftOutOf(plan, settings, n.Adopted))...)
}
if err == nil && gensErr == nil { if err == nil && gensErr == nil {
var declared sendable var declared sendable
if declared, err = declarationWith(ctx, open, n.Name, plan, settings, gens, Foreseeing); err == nil { if declared, err = declarationWith(ctx, open, n.Name, plan, settings, gens, Foreseeing); err == nil {
BIN
View File
Binary file not shown.