Merge pull request 'The provisioner says why a key stays asked when its provider reads no plans of the seat (issue 774)' (#575) from fix/holder-says-unread-plans into main
This commit was merged in pull request #575.
This commit is contained in:
@@ -568,7 +568,7 @@ func (h *Holder) outcome(key string, p *provisions.Provision) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if e == nil {
|
if e == nil {
|
||||||
p.State, p.Why = provisions.Asked, ""
|
p.State, p.Why = provisions.Asked, h.unread(p.Provider.Module)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var en entry
|
var en entry
|
||||||
@@ -596,6 +596,26 @@ func (h *Holder) outcome(key string, p *provisions.Provision) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unread is why a provider whose helper keeps its outcome in its entries has written none, when its manifest says it:
|
||||||
|
// it reads no plans of the seat (novox/hq ADR 0570 clause 2), so its helper never hears a bind and the key stays asked
|
||||||
|
// for good (issue 774, 2026-10-31: route-proxy's helper read only the controller's assignments, and 38 routes the seat
|
||||||
|
// keyed for modules not handed stood asked with no why while the assigner withheld their consumers' builds). "" when
|
||||||
|
// it reads them, or its manifest cannot say: then it has not answered yet.
|
||||||
|
func (h *Holder) unread(module string) string {
|
||||||
|
manifest, err := h.providerManifest(module)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var decl struct {
|
||||||
|
Reads []string `json:"reads"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(manifest), &decl) != nil || slices.Contains(decl.Reads, provisions.PlansRead) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return module + " reads no plans of the seat (its manifest's reads do not name " + provisions.PlansRead +
|
||||||
|
"), so its helper never answers this key"
|
||||||
|
}
|
||||||
|
|
||||||
// haveOutcome is the outcome of a provider that makes no credential per consumer: its own have on its node (the lead's
|
// haveOutcome is the outcome of a provider that makes no credential per consumer: its own have on its node (the lead's
|
||||||
// reading of ADR 0570 clause 3, recorded as an insight). Running with health ok at the build its want names is
|
// reading of ADR 0570 clause 3, recorded as an insight). Running with health ok at the build its want names is
|
||||||
// provided; failed is failed with the have's words; anything else is asked, waiting for the provider to run.
|
// provided; failed is failed with the have's words; anything else is asked, waiting for the provider to run.
|
||||||
|
|||||||
@@ -443,31 +443,39 @@ func TestAProvidersOutcomeIsReadPerOffer(t *testing.T) {
|
|||||||
entry string
|
entry string
|
||||||
have string // the provider's have key, written running
|
have string // the provider's have key, written running
|
||||||
state string
|
state string
|
||||||
|
why string // a part of the provision's why, when it says one
|
||||||
}{
|
}{
|
||||||
{"gitea-read: the helper's refusal is not its outcome; gitea runs, so provided", "ace",
|
{"gitea-read: the helper's refusal is not its outcome; gitea runs, so provided", "ace",
|
||||||
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "gitea-read", Provider: "novox", ProviderModule: "gitea"})},
|
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "gitea-read", Provider: "novox", ProviderModule: "gitea"})},
|
||||||
"ace.claude-code.gitea-read", "gitea.bound", refused, "novox.gitea", provisions.Provided},
|
"ace.claude-code.gitea-read", "gitea.bound", refused, "novox.gitea", provisions.Provided, ""},
|
||||||
{"forge-account: still the helper's entry", "ace",
|
{"forge-account: still the helper's entry", "ace",
|
||||||
map[string]catalogue.Plan{"mesh-build-go": planOf(catalogue.PlanProvision{Provision: "forge-account", Provider: "novox", ProviderModule: "gitea"})},
|
map[string]catalogue.Plan{"mesh-build-go": planOf(catalogue.PlanProvision{Provision: "forge-account", Provider: "novox", ProviderModule: "gitea"})},
|
||||||
"ace.mesh-build-go.forge-account", "gitea.bound", `{"state":"provided"}`, "", provisions.Provided},
|
"ace.mesh-build-go.forge-account", "gitea.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
|
||||||
{"route: route-proxy's entry is read", "novox",
|
{"route: route-proxy's entry is read", "novox",
|
||||||
map[string]catalogue.Plan{"gitea": planOf(catalogue.PlanProvision{Provision: "route", Provider: "novox", ProviderModule: "route-proxy"})},
|
map[string]catalogue.Plan{"gitea": planOf(catalogue.PlanProvision{Provision: "route", Provider: "novox", ProviderModule: "route-proxy"})},
|
||||||
"novox.gitea.route", "route-proxy.bound", `{"state":"provided"}`, "", provisions.Provided},
|
"novox.gitea.route", "route-proxy.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
|
||||||
{"mcp-endpoint: node-tools' entry is read", "ace",
|
{"mcp-endpoint: node-tools' entry is read", "ace",
|
||||||
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "mcp-endpoint", Provider: "ace", ProviderModule: "node-tools"})},
|
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "mcp-endpoint", Provider: "ace", ProviderModule: "node-tools"})},
|
||||||
"ace.claude-code.mcp-endpoint", "node-tools.bound", `{"state":"provided"}`, "", provisions.Provided},
|
"ace.claude-code.mcp-endpoint", "node-tools.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
|
||||||
|
// Issue 774, 2026-10-31: route-proxy's helper read the controller's assignments, never the seat's plans, so 38
|
||||||
|
// routes the seat keys stood asked with no why while the assigner withheld their consumers' builds.
|
||||||
|
{"route: no entry from a provider that reads no plans of the seat is said so", "ace",
|
||||||
|
map[string]catalogue.Plan{"grafana": planOf(catalogue.PlanProvision{Provision: "route", Provider: "ace", ProviderModule: "route-proxy"})},
|
||||||
|
"ace.grafana.route", "", "", "", provisions.Asked, "reads no plans of the seat"},
|
||||||
} {
|
} {
|
||||||
m := newMesh()
|
m := newMesh()
|
||||||
m.manifestOf("gitea", gitea)
|
m.manifestOf("gitea", gitea)
|
||||||
m.manifestOf("route-proxy", routes)
|
m.manifestOf("route-proxy", routes)
|
||||||
m.manifestOf("node-tools", nodeTools)
|
m.manifestOf("node-tools", nodeTools)
|
||||||
m.atWant[row.node], m.atNewest[row.node] = row.plan, row.plan
|
m.atWant[row.node], m.atNewest[row.node] = row.plan, row.plan
|
||||||
m.s.write(t, row.bucket, row.key, row.entry)
|
if row.bucket != "" {
|
||||||
|
m.s.write(t, row.bucket, row.key, row.entry)
|
||||||
|
}
|
||||||
if row.have != "" {
|
if row.have != "" {
|
||||||
m.s.write(t, HaveRead, row.have, running)
|
m.s.write(t, HaveRead, row.have, running)
|
||||||
}
|
}
|
||||||
m.compose(t, row.node)
|
m.compose(t, row.node)
|
||||||
if p := m.state(t, row.key); p.State != row.state {
|
if p := m.state(t, row.key); p.State != row.state || !strings.Contains(p.Why, row.why) {
|
||||||
t.Errorf("%s: %s is %s (%s)", row.name, row.key, p.State, p.Why)
|
t.Errorf("%s: %s is %s (%s)", row.name, row.key, p.State, p.Why)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user