mesh-delivery-sdk kept: the version check, CheckName and IsHex64 are mesh-sdk keys' now; kept keeps Decode and CheckDigest (ruling 2, ADR 0634 §1; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 14:41:02 +01:00
parent 719ddabb14
commit acd8e985d1
8 changed files with 19 additions and 68 deletions
@@ -9,7 +9,7 @@ import (
"strings"
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
"git.novox.be/novox/mesh-delivery/sdk/kept"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The states of a verdict, the levels of a check and the compositions' states.
@@ -44,10 +44,10 @@ func CheckRepository(repository string) error {
if !ok || strings.Contains(name, "/") {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q is not <owner>/<name>, with exactly one slash", repository)
}
if err := kept.CheckName("repository owner", owner); err != nil {
if err := keys.CheckName("repository owner", owner); err != nil {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
}
if err := kept.CheckName("repository name", name); err != nil {
if err := keys.CheckName("repository name", name); err != nil {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
}
return nil
@@ -65,7 +65,7 @@ const (
// being the fingerprint's key part (fingerprint.KeyPart), since a bus key may not hold ':'. A module holding a dot or a
// character a key refuses, and a fingerprint not of the new form, are refused, naming them.
func BuilderBuildKey(module, fp string) (string, error) {
if err := kept.CheckName("module", module); err != nil {
if err := keys.CheckName("module", module); err != nil {
return "", err
}
part, err := fingerprint.KeyPart(fp)
@@ -81,7 +81,7 @@ func ParseBuilderBuildKey(key string) (module, fp string, err error) {
if !ok {
return "", "", fmt.Errorf("builds: %q is not a builder build key, <module>.<fingerprint hex>", key)
}
if err := kept.CheckName("module", module); err != nil {
if err := keys.CheckName("module", module); err != nil {
return "", "", fmt.Errorf("builds: %q is not a builder build key: %w", key, err)
}
if fp, err = fingerprint.ParseKeyPart(part); err != nil {
@@ -265,7 +265,7 @@ func (b BuilderBuild) check() error {
return fmt.Errorf("builds: the build state %q is not queued, building, done or failed", b.State)
}
if b.Manifest != "" {
if !kept.IsHex64(b.Manifest) {
if !keys.IsHex64(b.Manifest) {
return fmt.Errorf("builds: %q is not a manifest digest, 64 lowercase hex digits", b.Manifest)
}
}
@@ -355,7 +355,7 @@ func EncodeBuilderBuild(b BuilderBuild) ([]byte, error) {
return json.Marshal(b)
}
// DecodeBuilderBuild reads a builder build value; it refuses an unknown version (kept.ErrUnknownVersion) and what
// DecodeBuilderBuild reads a builder build value; it refuses an unknown version (keys.ErrUnknownVersion) and what
// EncodeBuilderBuild refuses.
func DecodeBuilderBuild(data []byte) (BuilderBuild, error) {
var b BuilderBuild
@@ -9,7 +9,6 @@ import (
"testing"
"git.novox.be/novox/mesh-delivery/sdk/buildrecord"
"git.novox.be/novox/mesh-delivery/sdk/kept"
"git.novox.be/novox/mesh-sdk/go/keys"
)
@@ -177,10 +176,10 @@ func TestBuilderBuildNamesItsToolchains(t *testing.T) {
}
}
// 1358/AC5: a value of another version, or none, is refused with kept.ErrUnknownVersion.
// 1358/AC5: a value of another version, or none, is refused with keys.ErrUnknownVersion.
func TestBuilderBuildVersion(t *testing.T) {
for _, in := range []string{`{"v":2,"state":"queued"}`, `{"state":"queued"}`} {
if _, err := DecodeBuilderBuild([]byte(in)); !errors.Is(err, kept.ErrUnknownVersion) {
if _, err := DecodeBuilderBuild([]byte(in)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("DecodeBuilderBuild(%s) = %v", in, err)
}
}
+1 -2
View File
@@ -6,7 +6,6 @@ import (
"sort"
"time"
"git.novox.be/novox/mesh-delivery/sdk/kept"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
"git.novox.be/novox/mesh-sdk/go/keys"
)
@@ -36,7 +35,7 @@ const (
// BuildLogKey is the key of a module's entries in the builder's log: the module's name, refused when a log key could not
// hold it.
func BuildLogKey(module string) (string, error) {
if err := kept.CheckName("module", module); err != nil {
if err := keys.CheckName("module", module); err != nil {
return "", err
}
return module, nil
@@ -6,7 +6,6 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-delivery/sdk/kept"
"git.novox.be/novox/mesh-sdk/go/keys"
)
@@ -26,7 +25,7 @@ func TestATakenValueRoundTripsAndRefusesWhatLacksARepositoryOrAMerge(t *testing.
t.Errorf("taken without merge: %v", err)
}
for _, in := range []string{`{"v":2,"repository":"a/b","merge":"c"}`, `{"repository":"a/b","merge":"c"}`} {
if _, err := DecodeTaken([]byte(in)); !errors.Is(err, kept.ErrUnknownVersion) {
if _, err := DecodeTaken([]byte(in)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("decode of %s = %v, want ErrUnknownVersion", in, err)
}
}
@@ -168,7 +168,7 @@ func EncodeViewAnswer(a ViewAnswer) ([]byte, error) {
return json.Marshal(a)
}
// DecodeViewAnswer reads an answer; it refuses an unknown version (kept.ErrUnknownVersion) and what EncodeViewAnswer refuses.
// DecodeViewAnswer reads an answer; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeViewAnswer refuses.
func DecodeViewAnswer(data []byte) (ViewAnswer, error) {
var a ViewAnswer
if err := kept.Decode(data, &a); err != nil {
+5 -44
View File
@@ -1,59 +1,20 @@
// Package kept holds what the layouts of the Delivery domain's kept values share: the version check on reading one, the
// rule a name must keep to be a key part, and the form of a digest. It names no seat and no module (ADR 0634 §2). The
// version itself (keys.Version) and the escaping of a name (keys.EscapeName) are mesh-sdk's, since more than one domain uses
// them.
// Package kept holds what the layouts of the Delivery domain's kept values share: the reading of one at its version and the form
// of a digest. It names no seat and no module (ADR 0634 §2). The version (keys.Version, keys.CheckVersion), the rule a name
// must keep to be a key part (keys.CheckName), keys.IsHex64 and the escaping of a name (keys.EscapeName) are mesh-sdk's,
// since more than one domain uses them.
package kept
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// ErrUnknownVersion is what Decode returns for a value whose version is missing or not keys.Version. Test it with errors.Is.
var ErrUnknownVersion = errors.New("kept: unknown value version")
var nameOK = regexp.MustCompile(`^[-/_=a-zA-Z0-9]+$`)
// CheckName refuses a name that is empty or holds a dot, a wildcard, whitespace or a character a bus key refuses.
func CheckName(kind, name string) error {
if name == "" {
return fmt.Errorf("kept: the %s name is empty", kind)
}
if !nameOK.MatchString(name) {
return fmt.Errorf("kept: the %s name %q holds a dot, a wildcard, whitespace or a character a key refuses", kind, name)
}
return nil
}
var hex64 = regexp.MustCompile(`^[0-9a-f]{64}$`)
// IsHex64 says whether s is 64 lowercase hex digits.
func IsHex64(s string) bool { return hex64.MatchString(s) }
// CheckVersion refuses a value with no version or one this reader does not read.
func CheckVersion(data []byte) error {
var probe struct {
V *int `json:"v"`
}
if err := json.Unmarshal(data, &probe); err != nil {
return fmt.Errorf("kept: value is not JSON: %w", err)
}
if probe.V == nil {
return fmt.Errorf("%w: no version in the value, this reader reads %d", ErrUnknownVersion, keys.Version)
}
if *probe.V != keys.Version {
return fmt.Errorf("%w: the value is version %d, this reader reads %d", ErrUnknownVersion, *probe.V, keys.Version)
}
return nil
}
// Decode reads data into into after CheckVersion.
func Decode(data []byte, into any) error {
if err := CheckVersion(data); err != nil {
if err := keys.CheckVersion(data); err != nil {
return err
}
if err := json.Unmarshal(data, into); err != nil {
+2 -9
View File
@@ -2,6 +2,7 @@ package kept
import (
"errors"
"git.novox.be/novox/mesh-sdk/go/keys"
"strings"
"testing"
)
@@ -10,21 +11,13 @@ func TestAKeptValueIsReadOnlyAtItsVersionAndAKeyPartOnlyOfKeyCharacters(t *testi
var v struct{ V int }
for data, ok := range map[string]bool{`{"v":1}`: true, `{"v":2}`: false, `{}`: false} {
err := Decode([]byte(data), &v)
if (err == nil) != ok || (!ok && !errors.Is(err, ErrUnknownVersion)) {
if (err == nil) != ok || (!ok && !errors.Is(err, keys.ErrUnknownVersion)) {
t.Errorf("Decode(%s) = %v", data, err)
}
}
if Decode([]byte(`nope`), &v) == nil {
t.Error("a value that is no JSON is read")
}
for name, ok := range map[string]bool{"a-b_c/d=": true, "": false, "a.b": false, "a b": false, "a*": false} {
if (CheckName("module", name) == nil) != ok {
t.Errorf("CheckName(%q) = %v", name, CheckName("module", name))
}
}
if !IsHex64(strings.Repeat("a", 64)) || IsHex64(strings.Repeat("A", 64)) || IsHex64("a") {
t.Error("IsHex64 reads a wrong form")
}
if CheckDigest("sha256:"+strings.Repeat("0", 64), "x") != nil || CheckDigest("sha256:0", "x") == nil {
t.Error("CheckDigest reads a wrong form")
}