Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/check.go
T
jschoubben 98d494619f
mesh/merge-gate pass: builds mesh-issues → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh-issues: take a delivery's id as mesh-delivery forms it, owner/repo@commit (hq issue 475)
The delivery link's own pattern refused the slash and the @ every delivery id
carries, so no delivery could be linked either. Mirror mesh-delivery's IDOf.
2026-10-11 19:24:23 +02:00

293 lines
11 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
// `<repository> modules/<module>` (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 (`<repository>
// modules/<module>`) 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-controller, 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 controller, the node-engine,
// the tool runner and the console, the SDK, and the catalogue's bus, forge and build agent.
var core = regexp.MustCompile(`\b(mesh-controller|mesh-host|mesh-tools|mesh-sdk|node-tools|` +
`mesh-catalog\s+modules/(nats|gitea|build-agent))\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"
)
// LinkKinds is every kind, in the order they are told.
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}$`)
// 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)
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-controller#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: <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 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, ", "))
}
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
}