package main import ( "fmt" "regexp" "slices" "strings" "unicode" "unicode/utf8" ) // What every field may hold. Each refusal says what was wrong and what would be accepted (to-be 53 §2). // Limits on what one call may carry, in bytes. const ( MostTitle = 200 MostReport = 64 << 10 MostNote = 16 << 10 MostLine = 300 // one entry of located-in or fixed-by, a link's target, a replay MostReason = 2000 // a wontfix's reason, a replay-none MostEntries = 20 // entries in one list MostPhrase = 200 MostLinks = 200 MostListResult = 500 ) // singleLine is a field of one line: not empty, valid text, no control character, at most max bytes. func singleLine(field, s string, max int) (string, error) { s = strings.TrimSpace(s) if s == "" { return "", fmt.Errorf("%s is empty: give it in words", field) } if err := plainText(field, s, max, false); err != nil { return "", err } return s, nil } // multiLine is a field of text that may run over lines and indent with tabs, and nothing else unprintable. func multiLine(field, s string, max int) (string, error) { s = strings.TrimSpace(strings.ReplaceAll(s, "\r\n", "\n")) if s == "" { return "", fmt.Errorf("%s is empty: give it in words", field) } if err := plainText(field, s, max, true); err != nil { return "", err } return s, nil } func plainText(field, s string, max int, lines bool) error { if len(s) > max { return fmt.Errorf("%s is %d bytes; at most %d are kept", field, len(s), max) } if !utf8.ValidString(s) { return fmt.Errorf("%s is not valid UTF-8 text", field) } for _, r := range s { if lines && (r == '\n' || r == '\t') { continue } if r == '\u2028' || r == '\u2029' { return fmt.Errorf("%s carries the line separator %U; break lines with a newline", field, r) } if unicode.IsControl(r) || unicode.Is(unicode.Cf, r) && r != '‍' { if lines { return fmt.Errorf("%s carries the unprintable character %U; only line breaks and tabs are kept", field, r) } return fmt.Errorf("%s carries the unprintable character %U; it is one line of plain text", field, r) } } if what := secretShape(s); what != "" { return fmt.Errorf("%s looks like it carries %s; an issue is read by every agent on the mesh and "+ "carries no secret — say what it is, not its value", field, what) } return nil } // secretShapes are the shapes of a credential that are told by their form alone. A guard against the // ordinary mistake of pasting one, not a determined one: no check can tell every secret from text. var secretShapes = []struct { what string re *regexp.Regexp }{ {"a private key", regexp.MustCompile(`-----BEGIN [A-Z ]*PRIVATE KEY-----`)}, {"an Anthropic key", regexp.MustCompile(`\bsk-ant-[A-Za-z0-9_-]{16,}`)}, {"an API key", regexp.MustCompile(`\bsk-[A-Za-z0-9]{32,}`)}, {"a forge token", regexp.MustCompile(`\b(ghp|gho|ghs|github_pat|glpat)_[A-Za-z0-9_]{20,}`)}, {"a chat token", regexp.MustCompile(`\bxox[abpr]-[A-Za-z0-9-]{10,}`)}, {"a cloud access key", regexp.MustCompile(`\bAKIA[0-9A-Z]{16}\b`)}, {"a bus credential", regexp.MustCompile(`\b(SU|SA|SO)[A-Z2-7]{56}\b`)}, {"a signed token", regexp.MustCompile(`\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}`)}, {"a password", regexp.MustCompile(`(?i)\b(password|passwd|secret|token|api[_-]?key)=\S{8,}`)}, } func secretShape(s string) string { for _, sh := range secretShapes { if sh.re.MatchString(s) { return sh.what } } return "" } // slug is the name part of an issue's folder: lower-case words joined by hyphens, nothing else. var slug = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`) // RecordOf is the folder in hq an issue numbered n keeps its narrative in, from the slug its opener gave. func RecordOf(n uint64, s string) string { return fmt.Sprintf("04-ISSUES/%03d-%s", n, s) } func checkSlug(s string) (string, error) { s = strings.TrimSpace(s) if len(s) > 120 || !slug.MatchString(s) { return "", fmt.Errorf("record %q is not a folder's name: lower-case words joined by hyphens, at most 120 "+ "characters, without the number (the tracker gives it) and without a path", s) } return s, nil } // entries is a list of one-line entries, each checked by one. func entries(field string, list []string, one func(string) (string, error)) ([]string, error) { if len(list) > MostEntries { return nil, fmt.Errorf("%s names %d entries; at most %d", field, len(list), MostEntries) } var out []string seen := map[string]bool{} for _, e := range list { v, err := one(e) if err != nil { return nil, err } if seen[v] { return nil, fmt.Errorf("%s names %q twice", field, v) } seen[v] = true out = append(out, v) } return out, nil } // Owners is the registered list `located-in` may name: repositories, and modules as // ` modules/` (to-be 53 §3). type Owners []string // owns is the registered owner an entry names — the longest one it is, or starts with followed by a space // or a slash and where in it — or "". An entry that names a module of a repository (` // modules/`) is owned only by that module registered, never by the repository alone, so a module // not on the list is not taken as a place in the repository. func (o Owners) owns(entry string) string { best := "" for _, owner := range o { if (entry == owner || strings.HasPrefix(entry, owner+" ") || strings.HasPrefix(entry, owner+"/")) && len(owner) > len(best) { best = owner } } if repo, rest, ok := strings.Cut(entry, " "); ok && strings.HasPrefix(rest, "modules/") && best == repo { return "" } return best } // locatedIn checks one entry of located-in against the registered list. func (o Owners) locatedIn(e string) (string, error) { v, err := singleLine("an entry of located-in", e, MostLine) if err != nil { return "", err } if o.owns(v) == "" { return "", fmt.Errorf("located-in %q names no registered owner: an entry starts with a repository such as "+ "mesh-host, or a module such as \"mesh-catalog modules/nats\", optionally followed by where in it; "+ "the registered list is the module's setting `owners`", v) } return v, nil } // core is an owner whose issues are core issues (hq cycle.py, to-be 45): the node-engine, // the tool runner and the console, the SDK, the delivery parts, and the catalogue's bus, forge and build modules // (issue 1041; ADR 0524 §5). var core = regexp.MustCompile(`\b(mesh-host|mesh-tools|mesh-sdk|node-tools|mesh-delivery|` + `mesh-build-(go|node|image)|mesh-catalog\s+modules/(nats|gitea))\b`) // IsCore says whether an issue located in these owners is a core issue (ADR 0237). func IsCore(locatedIn []string) bool { for _, e := range locatedIn { if core.MatchString(e) { return true } } return false } // replayID is a replay's id in mesh-lab's replays register, optionally followed by words. var replayID = regexp.MustCompile(`^R[0-9]{1,6}\b`) // designPath is a design document in hq, as `amended-design` names one. var designPath = regexp.MustCompile(`^03-DESIGN/(00-as-is|01-to-be)/[0-9]{2}-[a-z0-9]+(-[a-z0-9]+)*\.md$`) func checkDesign(e string) (string, error) { v := strings.TrimSpace(e) if !designPath.MatchString(v) { return "", fmt.Errorf("amended-design %q is not a design document: 03-DESIGN/00-as-is/NN-name.md or "+ "03-DESIGN/01-to-be/NN-name.md", v) } return v, nil } // The kinds of link (to-be 53 §3), each with what its target is. const ( LinkDuplicates = "duplicates" LinkRelatesTo = "relates-to" // LinkDependsOn is an issue that must be delivered first (ADR 0279). LinkDependsOn = "depends-on" LinkPullRequest = "pull-request" LinkDecision = "decision" LinkDesign = "design" LinkCondition = "condition" LinkDelivery = "delivery" // LinkMilestone is the one milestone an issue belongs to, design/id (ADR 0493 §1, §2). LinkMilestone = "milestone" // LinkCommit is a commit that fixes the issue, repository@full sha (ADR 0493 §4). LinkCommit = "commit" ) // LinkKinds is every kind, in the order they are told. var LinkKinds = []string{LinkDuplicates, LinkRelatesTo, LinkDependsOn, LinkPullRequest, LinkDecision, LinkDesign, LinkCondition, LinkDelivery, LinkMilestone, LinkCommit} // madeByPeople are the kinds only a person or a session links, never the triage (ADR 0493 §4). var madeByPeople = []string{LinkPullRequest, LinkMilestone, LinkCommit} var ( 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 the delivery gate's `IDOf` (modules/mesh-delivery-gate/cmd/mesh-delivery-gate/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 `..`, 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) ok := false shape := "" switch kind { case LinkDuplicates, LinkRelatesTo, LinkDependsOn: ok, shape = issueNumber.MatchString(t), "another issue's number, such as 312" case LinkPullRequest: ok, shape = pullRequest.MatchString(t), "a repository and a pull request's number, such as mesh-host#246" case LinkDecision: ok, shape = decision.MatchString(t), "a decision record's four-digit number, such as 0275" 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 = isConditionKey(t), "a condition's key as the controller names it: .., "+ "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 mesh-delivery names it: the repository as owner/name, @ and twelve "+ "characters of its commit, such as novox/mesh-host@d2e9dc61590f" case LinkMilestone: var c string c, ok = checkMilestoneTarget(t) if ok { t = c } shape = "a milestone as design/id, such as 47/R6" case LinkCommit: ok, shape = commitRef.MatchString(t), "a repository, @ and the commit's full 40-character id, such as mesh-catalog@"+strings.Repeat("0", 40) default: return "", fmt.Errorf("kind %q is not a kind of link: one of %s", kind, strings.Join(LinkKinds, ", ")) } if !ok { return "", fmt.Errorf("a %s link's target is %s, not %q", kind, shape, t) } if what := secretShape(t); what != "" { return "", fmt.Errorf("the target looks like it carries %s; a link names a thing, never a secret", what) } return t, nil }