SD4 (step key parser): ParseStepKey reads both forms and answers the asker

<asker>.<node>.<id> and main's <node>.<id> (no asker); a third form is refused. sdk/buildrun follows.

Builds: design 47 G §484, §649

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 20:36:06 +01:00
parent 1ddc26087d
commit f5b11e1aa0
3 changed files with 38 additions and 9 deletions
+1 -1
View File
@@ -164,7 +164,7 @@ func EndStale(scan Scan, node string, before time.Time, logf func(string, ...any
}
var ended []string
for _, k := range keys {
n, _, perr := buildstep.ParseStepKey(k)
_, n, _, perr := buildstep.ParseStepKey(k)
if perr != nil || n != node {
continue
}
+19
View File
@@ -68,3 +68,22 @@ func TestTwoIDsInOneMillisecondDiffer(t *testing.T) {
}
}
}
// 1966 (G §484, §649): ParseStepKey reads both forms of a key, main's answering no asker, and refuses a third.
func TestParseStepKeyReadsBothFormsAndRefusesAThird(t *testing.T) {
for _, tc := range []struct{ key, asker, node, id string }{
{"ace.abc123", "", "ace", "abc123"},
{"runner.ace.abc123", "runner", "ace", "abc123"},
{"builder.g14.x1", "builder", "g14", "x1"},
} {
asker, node, id, err := ParseStepKey(tc.key)
if err != nil || asker != tc.asker || node != tc.node || id != tc.id {
t.Errorf("%s: %q %q %q %v", tc.key, asker, node, id, err)
}
}
for _, key := range []string{"ace", "a.b.c.d", "Runner.ace.abc123", "runner.ace.ABC", ".ace.abc123", "ace."} {
if _, _, _, err := ParseStepKey(key); err == nil {
t.Errorf("%q was read as a step key", key)
}
}
}
+18 -8
View File
@@ -68,6 +68,7 @@ func Ended(s Step) bool { return s.State != StepRunning && s.State != StepAgain
var (
nameOK = regexp.MustCompile(`^[-/_=a-zA-Z0-9]+$`)
stepIDOK = regexp.MustCompile(`^[a-z0-9]+$`)
askerOK = regexp.MustCompile(`^[a-z][a-z0-9-]*$`)
askIDOK = regexp.MustCompile(`^[0-9a-z]{8,9}[0-9a-f]{6}$`)
)
@@ -89,22 +90,31 @@ func StepKey(node, id string) (string, error) {
return node + "." + id, nil
}
// ParseStepKey is the inverse of StepKey.
func ParseStepKey(key string) (node, id string, err error) {
node, id, ok := strings.Cut(key, ".")
if !ok {
return "", "", fmt.Errorf("buildstep: %q is not a step key, <node>.<id>", key)
// ParseStepKey reads both forms of a step's key (design 47 G §484, §649): `<asker>.<node>.<id>`, and main's `<node>.<id>`,
// which answers no asker (an empty asker). A third form is refused.
func ParseStepKey(key string) (asker, node, id string, err error) {
parts := strings.Split(key, ".")
switch len(parts) {
case 2:
node, id = parts[0], parts[1]
case 3:
asker, node, id = parts[0], parts[1], parts[2]
if !askerOK.MatchString(asker) {
return "", "", "", fmt.Errorf("buildstep: %q is not a step key: %q is not an asker", key, asker)
}
default:
return "", "", "", fmt.Errorf("buildstep: %q is not a step key, <asker>.<node>.<id> or <node>.<id>", key)
}
if _, err := StepKey(node, id); err != nil {
return "", "", fmt.Errorf("buildstep: %q is not a step key: %w", key, err)
return "", "", "", fmt.Errorf("buildstep: %q is not a step key: %w", key, err)
}
return node, id, nil
return asker, node, id, nil
}
// ParseAskStepKey is ParseStepKey for the key of a pulled ask's step, whose id is the ask's (base-36 milliseconds
// and six hex digits).
func ParseAskStepKey(key string) (node, askID string, err error) {
node, askID, err = ParseStepKey(key)
_, node, askID, err = ParseStepKey(key)
if err != nil {
return "", "", err
}