Serve a removed verb as its refusal, not as a verb this build is behind on, and fit the tests of the verbs and the stopped checks (issue 1851, A11a; issue 1299, C1; issue 1318, C3)

This commit is contained in:
2026-10-22 19:38:02 +02:00
parent a10ef8995b
commit 7c0e1d223f
8 changed files with 21 additions and 97 deletions
@@ -33,6 +33,12 @@ func TestThePullRequestChecksAreAskedOnlyForARepositoryTheCheckerDoesNotJudge(t
inventory.Source{Repository: "novox/mesh-controller", Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil {
t.Fatal(err)
}
// hq is cloned as a module built from it is, from its URL: no forge is held in this test.
if err := open.inventory.RegisterModule(ctx, catalogue.Manifest{Module: "hq-docs", Version: "1"},
inventory.Source{Repository: "http://forge.invalid/novox/hq.git", Ref: "main", Path: "docs", BuiltFrom: "c0",
Head: "c0"}); err != nil {
t.Fatal(err)
}
required := map[string][]string{"novox/mesh-controller": {"mesh/check"}, "novox/hq": {"mesh/repo-check"}}
was := requiredStatusesOf
requiredStatusesOf = func(_ context.Context, owner, repo, _ string) ([]string, error) {
+7 -1
View File
@@ -495,5 +495,11 @@ func removedByName(command string) error {
"forget-checks": "build-agent makes no check build from R4e (ADR 0503 §3), so there is nothing to let go of",
"delivery-plan": "a delivery's plan is read with `delivery-order` and the checker's verdict (ADR 0503 §3)",
}[command]
return fmt.Errorf("`%s` is removed: %s", command, replacement)
return &removedCommand{msg: fmt.Sprintf("`%s` is removed: %s", command, replacement)}
}
// removedCommand is a command or verb the controller no longer has: a refusal that is the verb's whole answer, not a
// verb of a newer build this one cannot run.
type removedCommand struct{ msg string }
func (r *removedCommand) Error() string { return r.msg }
+1 -1
View File
@@ -241,7 +241,7 @@ func notRegistered(ctx context.Context, open *stores, node, module string, opts
"at the controller's terminal, and the indexer holds no %s", inventory.ErrNoSuchModule, where.said, module)
}
answer := strings.TrimPrefix(where.said, inventory.ErrNoSuchModule.Error()) +
"\n a module in a repository is registered from the builder's done key once a merge adds it, then assigned"
"\n a module in a repository is registered from the builder's done key of it, then assigned"
if opts.Build {
answer += "\n build \"true\" asks for nothing: the controller asks no build (ADR 0501 §5), and the indexer " +
"holds no module of that name"
-41
View File
@@ -323,44 +323,3 @@ exit 1
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("GOTOOLCHAIN", "local")
}
// A recheck runs as a command of its own (`delivery check` with one member), where the controller is not the serving
// one and holds no connection: it dials the bus for the moment and asks mesh-delivery's `groups` as the daemon does.
// This drives the verb on a test bus with a fake mesh-delivery answering `groups`, with no stub of the seam between.
func TestTheRecheckVerbAsksMeshDeliveryOnABusOfItsOwn(t *testing.T) {
realAsk := askDeliveryOwner
r := theGroupOfIssue867(t)
askDeliveryOwner = realAsk
js, _ := aBuildQueue(t)
if servingBus.Load() != nil {
t.Fatal("this test is of a process that is not the serving controller")
}
asked := 0
wrapped, _ := json.Marshal(map[string]any{"content": []map[string]any{{"type": "text", "text": r.groups}}})
sub, err := js.Conn().Subscribe(link.SeatToolSubject(catalogue.DeliverySeat, "groups"), func(m *nats.Msg) {
asked++
body, _ := json.Marshal(link.Answer{Result: wrapped})
_ = m.Respond(body)
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sub.Unsubscribe() })
member := `[{"id":"novox/mesh-host@` + r.hostHead[:12] + `","repository":"novox/mesh-host","base":"main","head":"` +
r.hostHead + `","number":114,"paths":["notes.txt"],"module_dirs_said":true}]`
if err := deliveryCommand(t.Context(), []string{"check", "--members", member}); err != nil {
t.Fatal(err)
}
q, err := link.ReadQueue(t.Context(), js, link.TheBuildMachine)
if err != nil || len(q.Asks) == 0 {
t.Fatalf("nothing was asked of the build seat: %v", err)
}
c := q.Asks[len(q.Asks)-1].Request.Check
if c.SiblingsUnknown != "" || len(c.Siblings) != 1 || c.Siblings[0].Repository != "novox/mesh-controller" ||
c.Siblings[0].Number != 346 || c.Siblings[0].Head != r.controllerHead {
t.Fatalf("the recheck learned siblings %+v, unknown %q", c.Siblings, c.SiblingsUnknown)
}
if asked != 1 {
t.Fatalf("mesh-delivery was asked %d times", asked)
}
}
+2 -1
View File
@@ -1325,7 +1325,8 @@ func seatToolHandlers() (map[string]link.ToolHandler, []string, error) {
continue
}
var policy *heldAtTheTerminal
if _, err := argvFor(verb, sampleArguments(v)); err != nil && !errors.As(err, &policy) {
var removed *removedCommand
if _, err := argvFor(verb, sampleArguments(v)); err != nil && !errors.As(err, &policy) && !errors.As(err, &removed) {
// **A row ahead of this binary is not a reason to go silent.**
//
// The row is the store's and a control plane follows it (novox/hq ADR 0154), so a verb
@@ -38,7 +38,7 @@ func servedSchemas(t *testing.T) map[string]catalogue.Verb {
func TestNoArgumentAVerbIsGivenIsPassedOver(t *testing.T) {
for name, v := range servedSchemas(t) {
if inProcess[name] {
if inProcess[name] || removedVerbs[name] {
continue
}
names, switches := declaredArguments(v)
@@ -78,7 +78,7 @@ func TestAnArgumentAVerbDoesNotDeclareIsRefused(t *testing.T) {
args := sampleArguments(v)
args[stranger] = "x"
_, err := argvFor(name, args)
if inProcess[name] {
if inProcess[name] || removedVerbs[name] {
_, err = readArguments(name, args)
}
if err == nil || !strings.Contains(err.Error(), strconv.Quote(stranger)) {
@@ -105,7 +105,7 @@ func TestEveryFlagOfAVerbsCommandIsAnArgumentOrAccountedFor(t *testing.T) {
reached := map[string]map[string]bool{} // verb → flag sets its command lines reach
served := servedSchemas(t)
for name, v := range served {
if inProcess[name] || name == "command" {
if inProcess[name] || removedVerbs[name] || name == "command" {
continue
}
names, switches := declaredArguments(v)
+2 -5
View File
@@ -177,11 +177,8 @@ var accountedFlags = map[string]map[string]string{
"all": "withheld: the verb naming no machine is --behind, and a push of every machine is said at a " +
"shell (novox/hq ADR 0217); `command` reaches it",
},
"build": {
"wait": "set by the verb to 0: the id follows the build (issue 176)",
"self": "set by the verb from the repository's form: a path on the forge, or a URL",
"dry-run": "withheld: a dry run answers only when the build ends, which a call cannot wait for; `command` reaches it",
},
// Kept only to be refused by name, naming what replaced it (novox/hq ADR 0501 §5, ADR 0429 §18).
"replay": {"register": "withheld: refused by name, a registered replay being removed (ADR 0501 §5)"},
"builds": {"n": "=limit"},
"plans": {"n": "=limit", "what-if": "=repository"},
// The machine's tunnel key, named as the verb's other arguments are (novox/hq ADR 0169).
-45
View File
@@ -230,48 +230,3 @@ func TestASeatsVerbGainsTheArgumentsTheBinaryNames(t *testing.T) {
}
}
}
// **A stored row's build verb takes on and behind** (novox/hq ADR 0246: arguments added to a verb are
// additive): a row seeded before them described build with a repository it required, so the console
// would refuse both and a call naming neither would be refused for want of a repository. Re-seeding
// gives the row this binary's build.
func TestAStoredRowsBuildVerbTakesOnAndBehind(t *testing.T) {
inv := ForTest(t)
ctx := t.Context()
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
old := `[{"name":"build","description":"Have the build machine build a repository.","input":{"type":"object",
"properties":{"repository":{"type":"string"},"path":{"type":"string"},"ref":{"type":"string"}},
"required":["repository"]}}]`
if _, err := inv.store.Pool().Exec(ctx, `update seat set serves = $1 where name = $2`,
[]byte(old), catalogue.ControllerSeatName); err != nil {
t.Fatal(err)
}
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
seats, err := inv.Seats(ctx)
if err != nil {
t.Fatal(err)
}
var build catalogue.Verb
for _, s := range seats {
if s.Name == catalogue.ControllerSeatName {
for _, v := range s.Serves {
if v.Name == "build" {
build = v
}
}
}
}
props, _ := build.Input["properties"].(map[string]any)
for _, arg := range []string{"repository", "path", "ref", "on", "behind"} {
if _, has := props[arg]; !has {
t.Errorf("build in the row does not take %s: %v", arg, props)
}
}
if req, _ := build.Input["required"].([]any); len(req) != 0 {
t.Errorf("build in the row still requires %v, so on and behind alone are refused", req)
}
}