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
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.
293 lines
11 KiB
Go
293 lines
11 KiB
Go
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
|
||
}
|