The check's cycle refusal meets no verb edge on a cycle and no package edge, and still refuses provisions and bases (ADR 0614 §3, §6; issue 774)

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
This commit is contained in:
2026-11-03 23:26:08 +01:00
parent 1d9f9cfa2c
commit 0279c5c288
2 changed files with 33 additions and 10 deletions
@@ -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
@@ -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))
}