From 0279c5c28842ff94330bd2a04613b6154995cf02 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 23:22:51 +0100 Subject: [PATCH] =?UTF-8?q?The=20check's=20cycle=20refusal=20meets=20no=20?= =?UTF-8?q?verb=20edge=20on=20a=20cycle=20and=20no=20package=20edge,=20and?= =?UTF-8?q?=20still=20refuses=20provisions=20and=20bases=20(ADR=200614=20?= =?UTF-8?q?=C2=A73,=20=C2=A76;=20issue=20774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rows in the judge's table: two modules invoking each other, or a verb closing a cycle with a provision, are not refused; a cycle of provisions and one of bases are. The refusal's words name what a part waits on now. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- .../internal/check/examiner_test.go | 37 +++++++++++++++---- .../internal/check/graph_cycle.go | 6 ++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/modules/mesh-delivery-checker/internal/check/examiner_test.go b/modules/mesh-delivery-checker/internal/check/examiner_test.go index 7da5b258..b237c86f 100644 --- a/modules/mesh-delivery-checker/internal/check/examiner_test.go +++ b/modules/mesh-delivery-checker/internal/check/examiner_test.go @@ -1029,32 +1029,53 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { // untouched is the judge's refusal of mail, which the change leaves as it is, and trunkMail its refusal on the trunk: // a problem the trunk had is a warning, one the change causes fails (ADR 0579 §2) untouched, trunkMail string - // cycle reads the manifests, so the change's module graph is worked out (ADR 0603 §2): the change's web awaits - // mail's site while mail awaits web's database; trunkCycle has the trunk's web await it too. - cycle, trunkCycle bool + // cycle reads the manifests, so the change's module graph is worked out (ADR 0603 §2): with "provisions" the change's + // web awaits mail's site while mail awaits web's database, and trunkCycle has the trunk's web await it too; with "bases" + // each is built on the other; with "verbs" each invokes the other's tool, and with "a verb and a provision" web invokes + // mail's tool while mail awaits web's database: both left out, so neither is refused (ADR 0614 §3, §6). + cycle string + trunkCycle bool }{ {name: "accepted"}, {name: "an untouched manifest's problem the trunk had", untouched: "mail says no credential form", trunkMail: "mail says no credential form"}, {name: "a problem the change causes in an untouched manifest", untouched: "mail reads a state web no longer keeps", fails: "refuses the manifest of mail: mail reads a state web no longer keeps"}, - {name: "a cycle the change makes", cycle: true, fails: "the change's module graph has the cycle mail -> web -> mail"}, - {name: "a cycle the trunk has", cycle: true, trunkCycle: true}, + {name: "a cycle the change makes", cycle: "provisions", fails: "the change's module graph has the cycle mail -> web -> mail"}, + {name: "a cycle the trunk has", cycle: "provisions", trunkCycle: true}, + {name: "a cycle of bases", cycle: "bases", fails: "the change's module graph has the cycle mail -> web -> mail"}, + {name: "a cycle of verbs", cycle: "verbs"}, + {name: "a cycle of a verb and a provision", cycle: "a verb and a provision"}, {name: "refused", refused: "web requires \"amqp\"\nweb has no id", fails: "the indexer's judge at mesh-delivery's trunk " + strings.Repeat("d", 40) + ` refuses the manifest of web: web requires "amqp"; web has no id`}, } { b := newBench(t) b.ex.Manifests = fixedDigests{"mail": strings.Repeat("a", 64), "web": strings.Repeat("b", 64)} b.ex.Trunks = fixedTrunks{heads: map[string]string{DeliveryRepository: trunk}} b.ex.Predefined = keptList(`{"font-size": {"type": "number", "description": "the size"}}`) - if row.cycle { + if row.cycle != "" { offer := func(name string) []catalogue.Offer { return []catalogue.Offer{{Name: name, Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}} } + on := func(module string) *catalogue.Build { + return &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "BASE", Module: module, Artifact: "img"}}} + } trunkWeb := catalogue.Manifest{Module: "web", Provides: offer("database")} if row.trunkCycle { trunkWeb.Requires = []string{"site"} } - b.ex.Read = fixedManifests{strings.Repeat("a", 64): {Module: "mail", Provides: offer("site"), Requires: []string{"database"}}, - strings.Repeat("b", 64): trunkWeb, web: {Module: "web", Provides: offer("database"), Requires: []string{"site"}}} + mail := catalogue.Manifest{Module: "mail", Provides: offer("site"), Requires: []string{"database"}} + changed := catalogue.Manifest{Module: "web", Provides: offer("database"), Requires: []string{"site"}} + switch row.cycle { + case "bases": + mail = catalogue.Manifest{Module: "mail", Build: on("web")} + changed = catalogue.Manifest{Module: "web", Build: on("mail")} + case "verbs": + mail = catalogue.Manifest{Module: "mail", Tools: []string{"mail_send"}, Invokes: []string{"web.web_page"}} + changed = catalogue.Manifest{Module: "web", Tools: []string{"web_page"}, Invokes: []string{"mail.mail_send"}} + case "a verb and a provision": + mail = catalogue.Manifest{Module: "mail", Tools: []string{"mail_send"}, Requires: []string{"database"}} + changed = catalogue.Manifest{Module: "web", Provides: offer("database"), Invokes: []string{"mail.mail_send"}} + } + b.ex.Read = fixedManifests{strings.Repeat("a", 64): mail, strings.Repeat("b", 64): trunkWeb, web: changed} } var judged []delivery.ComposeJob answer := b.asks.Answer diff --git a/modules/mesh-delivery-checker/internal/check/graph_cycle.go b/modules/mesh-delivery-checker/internal/check/graph_cycle.go index 673ff548..56766476 100644 --- a/modules/mesh-delivery-checker/internal/check/graph_cycle.go +++ b/modules/mesh-delivery-checker/internal/check/graph_cycle.go @@ -15,7 +15,9 @@ import ( ) // The check refuses a change whose module graph has a cycle and names it (novox/hq ADR 0603 §2): modules that would each -// wait for the other for good, through their bases or the provisions they await. The graph is worked out from the +// wait for the other for good, through their bases, the provisions and bus state they await, or the verb edges the graph +// keeps (ADR 0614 §6). A verb edge on a cycle is left out of the graph, so it is never refused (§3), and a package or a +// build module's edge is a trigger, never a wait, so a cycle through one is not refused either (§6). The graph is worked out from the // catalogue as the change leaves it (every trunk manifest, each one a pull request touches replaced by its candidate's), // with the contract's ManifestGraph, the way the indexer works out its graph's edges. A cycle the trunk already has among // the same modules is the trunk's: said in the verdict's notes, not the change's failure. @@ -70,5 +72,5 @@ func (st *state) refuseCycle(ctx context.Context) *delivery.Verdict { } } return fail(goModule, fmt.Sprintf("the change's module graph has the cycle %s: these modules would each wait for the other for "+ - "good, through their bases or the provisions they await (ADR 0603 §2)", words)) + "good, through their bases, the provisions and bus state they await, or the verbs they invoke (ADR 0603 §2, ADR 0614 §6)", words)) }