Merge pull request 'mesh-issues: take every condition key the controller raises and every delivery id mesh-delivery gives (hq issue 475)' (#218) from fix/475-a-delivery-condition-can-be-tracked into main
This commit was merged in pull request #218.
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
@@ -219,13 +220,45 @@ const (
|
||||
var LinkKinds = []string{LinkDuplicates, LinkRelatesTo, LinkDependsOn, LinkPullRequest, LinkDecision, LinkDesign, LinkCondition, LinkDelivery}
|
||||
|
||||
var (
|
||||
pullRequest = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,99}#[1-9][0-9]{0,6}$`)
|
||||
decision = regexp.MustCompile(`^[0-9]{4}$`)
|
||||
conditionKey = regexp.MustCompile(`^[a-z0-9][a-z0-9._:=@-]{0,199}$`)
|
||||
deliveryID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,99}$`)
|
||||
issueNumber = regexp.MustCompile(`^[1-9][0-9]{0,8}$`)
|
||||
pullRequest = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,99}#[1-9][0-9]{0,6}$`)
|
||||
decision = regexp.MustCompile(`^[0-9]{4}$`)
|
||||
// deliveryID mirrors mesh-delivery's `IDOf` (modules/mesh-delivery/cmd/mesh-delivery/model.go): the forge's
|
||||
// repository as owner/name, lower-cased, `@` and at most twelve characters of its commit. The pattern this
|
||||
// replaced took neither the slash nor the `@`, so no delivery could be linked (novox/hq issue 475).
|
||||
deliveryID = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,99}/[a-z0-9][a-z0-9._-]{0,99}@[0-9a-f]{1,12}$`)
|
||||
issueNumber = regexp.MustCompile(`^[1-9][0-9]{0,8}$`)
|
||||
)
|
||||
|
||||
// The controller's rule for a condition's key, mirrored (novox/hq issue 475): mesh-controller's
|
||||
// internal/conditions/condition.go composes every key in `Key` as `<scope>.<id>.<kind>`, the id split on its dots,
|
||||
// each token made of the bus's safe characters `A-Za-z0-9_=/-` (anything else becomes `_`, an empty token `_`),
|
||||
// and `Observation.check` refuses a scope not in its `Scopes`. A delivery's id carries its repository as
|
||||
// owner/name, so its keys hold a slash: the narrower shape this replaced refused every one of them, and no issue
|
||||
// could track a delivery's condition. Mirroring the rule, rather than widening a pattern, keeps the tracker taking
|
||||
// exactly what the controller can raise.
|
||||
var (
|
||||
conditionScopes = []string{"machine", "plan", "call", "build", "merge", "provider", "seat", "bus", "core",
|
||||
"probe", "mesh", "delivery", "module"}
|
||||
conditionKeyToken = regexp.MustCompile(`^[A-Za-z0-9_=/-]+$`)
|
||||
)
|
||||
|
||||
// MostConditionKey is the longest key a link takes: the controller sets no bound, the tracker keeps a link one line.
|
||||
const MostConditionKey = 255
|
||||
|
||||
// isConditionKey says whether k is a key the controller could raise.
|
||||
func isConditionKey(k string) bool {
|
||||
parts := strings.Split(k, ".")
|
||||
if len(k) > MostConditionKey || len(parts) < 3 || !slices.Contains(conditionScopes, parts[0]) {
|
||||
return false
|
||||
}
|
||||
for _, p := range parts {
|
||||
if !conditionKeyToken.MatchString(p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkTarget is a link's target, as its kind takes it, said the one way it is kept.
|
||||
func checkTarget(kind, target string) (string, error) {
|
||||
t := strings.TrimSpace(target)
|
||||
@@ -241,9 +274,11 @@ func checkTarget(kind, target string) (string, error) {
|
||||
case LinkDesign:
|
||||
ok, shape = designPath.MatchString(t), "a design document, such as 03-DESIGN/01-to-be/53-the-issue-tracker.md"
|
||||
case LinkCondition:
|
||||
ok, shape = conditionKey.MatchString(t), "a condition's key as the controller names it"
|
||||
ok, shape = isConditionKey(t), "a condition's key as the controller names it: <scope>.<id>.<kind>, "+
|
||||
"the scope one of "+strings.Join(conditionScopes, ", ")+", each part of letters, digits and _=/-"
|
||||
case LinkDelivery:
|
||||
ok, shape = deliveryID.MatchString(t), "a delivery's id as the controller names it"
|
||||
ok, shape = deliveryID.MatchString(t), "a delivery's id as mesh-delivery names it: the repository as owner/name, @ and twelve "+
|
||||
"characters of its commit, such as novox/mesh-controller@d2e9dc61590f"
|
||||
default:
|
||||
return "", fmt.Errorf("kind %q is not a kind of link: one of %s", kind, strings.Join(LinkKinds, ", "))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A condition link takes every key the controller raises (novox/hq issue 475). The keys are real: the delivery's
|
||||
// as `mesh-controller.status` named it when the link was refused, the others as `mesh-controller.conditions`
|
||||
// answered on 2026-10-11.
|
||||
var realConditionKeys = []string{
|
||||
"delivery.novox/mesh-controller_d2e9dc61590f.stalled", // a delivery's id carries its repository, owner/name
|
||||
"machine.novox.telegram.telegram-token.secret-given",
|
||||
"module.openrazer.shanks.relogin-needed",
|
||||
"mesh.hand-acts.plans_go.healer-wanted", // a space in the id, made `_` by the controller
|
||||
"delivery.leaf.over-budget",
|
||||
}
|
||||
|
||||
func TestAConditionLinkTakesEveryKeyTheControllerRaises(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "a delivery stalls", "mesh-catalog")
|
||||
for _, k := range realConditionKeys {
|
||||
if _, err := h.Link(LinkAsk{Number: n, Kind: LinkCondition, Target: k}); err != nil {
|
||||
t.Errorf("%s: %v", k, err)
|
||||
}
|
||||
}
|
||||
got, err := h.Tracking([]string{realConditionKeys[0]})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if is := got.Conditions[realConditionKeys[0]]; len(is) != 1 || is[0].Number != n || got.Said != "Issues track 1 condition." {
|
||||
t.Fatalf("the delivery's condition is not said tracked: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// What the controller never raises is refused, so a link never names a condition that cannot exist.
|
||||
func TestAConditionLinkRefusesWhatTheControllerNeverRaises(t *testing.T) {
|
||||
for _, k := range []string{
|
||||
"",
|
||||
"delivery.stalled", // no id: a key is scope, id and kind
|
||||
"weather.novox.rain", // no scope the controller knows
|
||||
"machine..secret-given", // an empty token
|
||||
"machine.novox.secret given", // a space the controller makes `_`
|
||||
"machine.novox:1.secret-given", // a colon the controller makes `_`
|
||||
"delivery.novox/mesh-controller@d2e9dc61590f.stalled", // `@` too
|
||||
"machine.*.silent", // a wildcard on the bus
|
||||
"machine.>", // a wildcard on the bus
|
||||
"machine.ace.silent\nmore", // a second line
|
||||
"machine." + strings.Repeat("a", 300) + ".silent",
|
||||
} {
|
||||
if _, err := checkTarget(LinkCondition, k); err == nil {
|
||||
t.Errorf("%q was taken as a condition's key", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A delivery link takes every id mesh-delivery gives (novox/hq issue 475): the ids are real, as
|
||||
// `mesh-delivery.deliveries` answered on 2026-10-11.
|
||||
var realDeliveryIDs = []string{
|
||||
"novox/mesh-controller@0f853e93fa11",
|
||||
"novox/mesh-catalog@7714f164c33f",
|
||||
"novox/hq@34bdb1a34f5a",
|
||||
"novox/mesh-media-catalog@dd3f0abb3f78",
|
||||
"novox/mesh-tools@82aefe1988c2",
|
||||
}
|
||||
|
||||
func TestADeliveryLinkTakesEveryIdMeshDeliveryGives(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "a delivery stalls", "mesh-catalog")
|
||||
for _, id := range realDeliveryIDs {
|
||||
if _, err := h.Link(LinkAsk{Number: n, Kind: LinkDelivery, Target: id}); err != nil {
|
||||
t.Errorf("%s: %v", id, err)
|
||||
}
|
||||
}
|
||||
for _, id := range []string{
|
||||
"",
|
||||
"mesh-controller@0f853e93fa11", // no owner
|
||||
"novox/mesh-controller", // no commit
|
||||
"novox/mesh-controller@0f853e93fa11aa", // more than twelve characters of the commit
|
||||
"Novox/Mesh-Controller@0f853e93fa11", // mesh-delivery lower-cases the repository
|
||||
"novox/mesh-controller@0F853E93FA11", // a commit is lower-case hex
|
||||
"novox/mesh-controller@main", // a branch, not a commit
|
||||
"novox/mesh/controller@0f853e93fa11", // a repository is owner/name, no deeper
|
||||
"novox/mesh-controller_d2e9dc61590f", // a condition key's form of it, not the id
|
||||
"novox/mesh-controller@0f853e93fa11\nmore", // a second line
|
||||
} {
|
||||
if _, err := checkTarget(LinkDelivery, id); err == nil {
|
||||
t.Errorf("%q was taken as a delivery's id", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,7 +506,7 @@ func Tools(t *Tracker) []stdio.Tool {
|
||||
Description: "Link an issue to something, or with remove, take a link away. Kinds: duplicates or relates-to " +
|
||||
"another issue (its number), pull-request (such as mesh-controller#246), decision (a decision record's " +
|
||||
"number, such as 0275), design (a design document's path), condition (a condition's key) and delivery " +
|
||||
"(a delivery's id). Refused: a target not of its kind's shape, an issue that does not exist, a link it " +
|
||||
"(a delivery's id, such as novox/mesh-controller@d2e9dc61590f). Refused: a target not of its kind's shape, an issue that does not exist, a link it " +
|
||||
"already has.",
|
||||
Input: schema([]string{"number", "kind", "target"}, map[string]any{
|
||||
"number": number("the issue's number"),
|
||||
|
||||
@@ -473,8 +473,8 @@ func TestLinksAreTypedAddedOnceAndRemoved(t *testing.T) {
|
||||
{Kind: LinkPullRequest, Target: "mesh-controller#246"},
|
||||
{Kind: LinkDecision, Target: "0275"},
|
||||
{Kind: LinkDesign, Target: "03-DESIGN/01-to-be/53-the-issue-tracker.md"},
|
||||
{Kind: LinkCondition, Target: "healer-wanted.ace.backup"},
|
||||
{Kind: LinkDelivery, Target: "d-2026-10-10.3"},
|
||||
{Kind: LinkCondition, Target: "mesh.hand-acts.backup.healer-wanted"},
|
||||
{Kind: LinkDelivery, Target: "novox/mesh-controller@0f853e93fa11"},
|
||||
}
|
||||
for _, l := range good {
|
||||
l.Number = a
|
||||
@@ -494,7 +494,7 @@ func TestLinksAreTypedAddedOnceAndRemoved(t *testing.T) {
|
||||
{LinkAsk{Kind: LinkDecision, Target: "275"}, "four-digit"},
|
||||
{LinkAsk{Kind: LinkDesign, Target: "03-DESIGN/../../etc/passwd"}, "design document"},
|
||||
{LinkAsk{Kind: LinkCondition, Target: "a key\nwith a line"}, "condition"},
|
||||
{LinkAsk{Kind: LinkCondition, Target: "token=abcdefgh123"}, "secret"},
|
||||
{LinkAsk{Kind: LinkCondition, Target: "machine.ace.token=abcdefgh123.leaked"}, "secret"},
|
||||
{LinkAsk{Kind: LinkRelatesTo, Target: keyOf(b), Remove: true}, ""},
|
||||
{LinkAsk{Kind: LinkRelatesTo, Target: keyOf(b), Remove: true}, "no relates-to link"},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A delivery's condition, whose key holds its repository as owner/name, is matched to the issue that tracks it and
|
||||
// its message names that issue (novox/hq issue 475). The key is the real one `mesh-controller.status` named; the
|
||||
// event is decoded as the controller says it, so the key travels as it does on the bus.
|
||||
func TestADeliverysConditionWithASlashInItsKeyNamesTheIssueThatTracksIt(t *testing.T) {
|
||||
const key = "delivery.novox/mesh-controller_d2e9dc61590f.stalled"
|
||||
h, tg, _, c, _ := newHolder(t)
|
||||
h.Ask = func(address string, body any) (json.RawMessage, error) {
|
||||
if address != TrackingVerb {
|
||||
return json.RawMessage(`{"ok":true}`), nil
|
||||
}
|
||||
return json.RawMessage(`{"conditions":{"` + key + `":[{"number":475,"status":"open"}]}}`), nil
|
||||
}
|
||||
k, err := DecodeCondition(EventRaised, []byte(`{"key":"`+key+`","kind":"stalled","severity":"urgent",`+
|
||||
`"subject":{"scope":"delivery","id":"novox/mesh-controller_d2e9dc61590f"},`+
|
||||
`"headline":"Delivery of mesh-controller stalled",`+
|
||||
`"explanation":"Needs you: let the walk go or drop it. The delivery of mesh-controller waits for a walk that never starts.",`+
|
||||
`"needs":"let the walk go or drop it.","summary":"the delivery of mesh-controller waits for its walk"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if k.Key != key {
|
||||
t.Fatalf("the key was read as %q", k.Key)
|
||||
}
|
||||
k.Raised = c.now()
|
||||
h.Condition(EventRaised, k)
|
||||
settle(h, c)
|
||||
if len(tg.sends) != 1 {
|
||||
t.Fatalf("telegram %d", len(tg.sends))
|
||||
}
|
||||
body := tg.sends[0].Body
|
||||
if !strings.Contains(body, "Tracked as issue 475") || strings.Contains(body, "No issue tracks this yet.") {
|
||||
t.Fatalf("body: %q", body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user