DLV-C3r3 (Node): mesh-build-node places a group's tagged version of the SDK from the registry

ADR 0607 §3, §5: the fetch's package argument also takes {name, version}; the version is placed from the registry in
each TypeScript member whatever its lock names, checked against the lock's integrity when the lock names it, else the
registry's; nothing is packed then, and a version the registry lacks fails naming it. The packed {repository, commit}
form stays for a checker that hands it. Rows in the placement and argument tests.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 13:23:10 +01:00
parent cd66db54bf
commit ed7c6c4fb6
3 changed files with 106 additions and 21 deletions
@@ -93,9 +93,9 @@ func (m *Module) fetchTool() StepTool {
"ref": map[string]any{"type": "string", "description": "refs/pull/<n>/head when only a pull request's head carries the commit"},
"dir": sdkstep.FetchRequestProperties()["dir"],
"paths": sdkstep.FetchRequestProperties()["paths"],
"package": map[string]any{"type": "object", "description": "optional: the group's package member (ADR 0500 §5), packed here, publishing nothing, and served to each TypeScript member whose lock names its version, in place of the registry's",
"properties": map[string]any{"repository": map[string]any{"type": "string"}, "commit": map[string]any{"type": "string"}, "ref": map[string]any{"type": "string"}},
"required": []string{"repository", "commit"}},
"package": map[string]any{"type": "object", "description": "optional: the group's tagged version of the SDK, {name, version}, placed from the registry in each TypeScript member whatever its lock names (ADR 0607 §3); or the group's package member, {repository, commit, ref} (ADR 0500 §5), packed here, publishing nothing, and served to each TypeScript member whose lock names its version, in place of the registry's",
"properties": map[string]any{"name": map[string]any{"type": "string"}, "version": map[string]any{"type": "string"},
"repository": map[string]any{"type": "string"}, "commit": map[string]any{"type": "string"}, "ref": map[string]any{"type": "string"}}},
"base": map[string]any{"type": "string", "description": "optional trunk commit, 40 hex digits: the head is merged onto it as the forge's merge would, and the answer carries the merge commit, the files in conflict (an answer, the workspace is then left at the trunk commit), the changed paths and the tree of each module directory"},
},
"required": []string{"repository", "commit"},
@@ -168,7 +168,7 @@ func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, group
}
// The group's package, packed here and served to its members in place of the registry's version (ADR 0500 §5).
var served *packed
if group != nil {
if group != nil && !group.tagged() {
p, err := m.packGroup(ctx, root, gitRun, group)
if err != nil {
return nil, err
@@ -196,7 +196,7 @@ func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, group
}
if refused == "" {
// The SDK the module's own lock names, from the group or the registry (the lead's decision L10).
if err := m.placeSDK(ctx, dir, d, served); err != nil {
if err := m.placeSDK(ctx, dir, d, served, group); err != nil {
return nil, err
}
}
@@ -25,6 +25,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@@ -219,8 +220,13 @@ func (m *Module) buildAndPack(ctx context.Context, root, dir, rel string, write
}
// groupPackage is the fetch's `package` argument: the group's package member (ADR 0500 §5), which the fetch packs and
// serves to its TypeScript members in place of the registry's version.
type groupPackage struct{ Repository, Commit, Ref string }
// serves to its TypeScript members in place of the registry's version, or, by Name and Version, the group's tagged
// version of the SDK, which the fetch places from the registry in each TypeScript member, whatever version its lock
// names (novox/hq ADR 0607 §3, §5; design 51 *Tagged versions*): nothing is packed then.
type groupPackage struct{ Repository, Commit, Ref, Name, Version string }
// tagged says whether the argument names a tagged version to take from the registry, not a member to pack.
func (g *groupPackage) tagged() bool { return g != nil && g.Version != "" }
func groupPackageArg(v any) (*groupPackage, error) {
if v == nil {
@@ -228,9 +234,20 @@ func groupPackageArg(v any) (*groupPackage, error) {
}
o, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("package is {repository, commit, ref}")
return nil, fmt.Errorf("package is {repository, commit, ref} or {name, version}")
}
g := &groupPackage{}
g.Name, _ = o["name"].(string)
g.Version, _ = o["version"].(string)
if g.Name != "" || g.Version != "" {
if g.Name != sdkPackage {
return nil, fmt.Errorf("package: %q is not %s, the one package the fetch places in a member", g.Name, sdkPackage)
}
if !npmVersionOK.MatchString(g.Version) {
return nil, fmt.Errorf("package: %q is not a version (0.1.15-feat-x.2)", g.Version)
}
return g, nil
}
g.Repository, _ = o["repository"].(string)
g.Commit, _ = o["commit"].(string)
g.Ref, _ = o["ref"].(string)
@@ -246,6 +263,9 @@ func groupPackageArg(v any) (*groupPackage, error) {
return g, nil
}
// npmVersionOK is a semantic version as npm names one, with an optional pre-release (a tagged version's).
var npmVersionOK = regexp.MustCompile(`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)
// packGroup fetches the group's package member into the workspace, installs its dependencies, builds and packs it,
// publishing nothing (ADR 0500 §4).
func (m *Module) packGroup(ctx context.Context, root string, run forgekey.Runner, g *groupPackage) (packed, error) {
@@ -290,21 +310,45 @@ func sdkLocked(dir string) (version, integrity string, ok bool, err error) {
// placeSDK places in the module's node_modules the SDK version its lock names (the lead's decision L10): the group's
// packed package when it is that version (its integrity must be the lock's), else the registry's; a version neither
// holds is a wait (`waits for:`) when no group package was given, and a failure naming both when one was.
func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed) error {
// holds is a wait (`waits for:`) when no group package was given, and a failure naming both when one was. Given the
// group's tagged version (tagged, ADR 0607 §3), that version is placed from the registry whatever the lock names, its
// integrity the lock's when the lock names it, else the registry's own; one the registry lacks fails naming it.
func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed, tagged *groupPackage) error {
version, integrity, ok, err := sdkLocked(dir)
if err != nil || !ok {
return err
}
want := sdkPackage + "@" + version
var tgz []byte
switch {
case tagged.tagged():
if version != tagged.Version {
if m.Say != nil {
m.Say("%s: its lock names %s@%s; the group's tagged version %s is placed (ADR 0607 §3)", rel, sdkPackage, version, tagged.Version)
}
version, integrity = tagged.Version, ""
}
want := sdkPackage + "@" + version
got, registered, held, err := m.registryPackageChecked(ctx, sdkPackage, version)
if err != nil {
return err
}
if !held {
return fmt.Errorf("the registry holds no %s, the group's tagged version, so %s cannot be built against it", want, rel)
}
if integrity == "" {
integrity = registered
}
if integrity != "" && integrityOf(got) != integrity {
return fmt.Errorf("the registry's %s is %s, and %s names %s", want, integrityOf(got), rel, integrity)
}
tgz = got
case group != nil && group.Name == sdkPackage && group.Version == version:
if integrity != "" && integrity != group.Integrity {
return fmt.Errorf("the lock of %s names %s with the integrity %s, and the group's package packs to %s", rel, want, integrity, group.Integrity)
return fmt.Errorf("the lock of %s names %s with the integrity %s, and the group's package packs to %s", rel, sdkPackage+"@"+version, integrity, group.Integrity)
}
tgz = group.tgz
default:
want := sdkPackage + "@" + version
got, held, err := m.registryPackage(ctx, sdkPackage, version)
if err != nil {
return err
@@ -327,6 +371,16 @@ func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed) e
return unpackPackage(tgz, into)
}
// registryPackageChecked is registryPackage with the integrity the registry's document names of name@version.
func (m *Module) registryPackageChecked(ctx context.Context, name, version string) (tgz []byte, integrity string, held bool, err error) {
_, integrity, held, err = m.registryVersion(ctx, name, version)
if err != nil || !held {
return nil, "", held, err
}
tgz, held, err = m.registryPackage(ctx, name, version)
return tgz, integrity, held, err
}
// httpClient is the client of the registry's reads and publishes: HTTP when a test set it.
func (m *Module) httpClient() *http.Client {
if m.HTTP != nil {
@@ -98,18 +98,45 @@ func TestAMemberGetsTheSDKVersionItsLockNamesOrWaitsForIt(t *testing.T) {
held := tgzOf(t, sdkPackage, "0.1.15")
fakeRegistry(t, m, map[string][]byte{"0.1.15": held})
dir := memberLocking(t, "0.1.15", integrityOf(held))
if err := m.placeSDK(context.Background(), dir, "modules/m", nil); err != nil || placedVersion(dir) != "0.1.15" {
if err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil); err != nil || placedVersion(dir) != "0.1.15" {
t.Errorf("the registry's 0.1.15: %v, placed %q", err, placedVersion(dir))
}
dir = memberLocking(t, "0.1.16", "sha512-x")
err := m.placeSDK(context.Background(), dir, "modules/m", nil)
err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil)
if err == nil || err.Error() != delivery.WaitsFor+"@novox/mesh-sdk@0.1.16" {
t.Errorf("a version the registry lacks answered %v, want %s@novox/mesh-sdk@0.1.16", err, delivery.WaitsFor)
}
dir = memberLocking(t, "0.1.15", "sha512-another")
if err := m.placeSDK(context.Background(), dir, "modules/m", nil); err == nil || !strings.Contains(err.Error(), "sha512-another") {
if err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil); err == nil || !strings.Contains(err.Error(), "sha512-another") {
t.Errorf("a registry file of another integrity than the lock's answered %v", err)
}
// ADR 0607 §3, §5 (design 51 *Tagged versions*): given the group's tagged version, the fetch places it from the
// registry whatever the lock names, against the lock's integrity when the lock names it, else the registry's; one the
// registry lacks fails naming it, no wait (the check waited for its published).
tagged := tgzOf(t, sdkPackage, "0.1.16-feat-x.1")
fakeRegistry(t, m, map[string][]byte{"0.1.15": held, "0.1.16-feat-x.1": tagged})
group := &groupPackage{Name: sdkPackage, Version: "0.1.16-feat-x.1"}
for _, c := range []struct {
name, locks, integrity, fails string
}{
{name: "a lock naming another version", locks: "0.1.15", integrity: integrityOf(held)},
{name: "a lock naming the tagged version", locks: "0.1.16-feat-x.1", integrity: integrityOf(tagged)},
{name: "a lock naming it with another integrity", locks: "0.1.16-feat-x.1", integrity: "sha512-other", fails: "sha512-other"},
} {
dir = memberLocking(t, c.locks, c.integrity)
err := m.placeSDK(context.Background(), dir, "modules/m", nil, group)
switch {
case c.fails == "" && (err != nil || placedVersion(dir) != "0.1.16-feat-x.1"):
t.Errorf("%s: %v, placed %q, want the group's tagged version", c.name, err, placedVersion(dir))
case c.fails != "" && (err == nil || !strings.Contains(err.Error(), c.fails)):
t.Errorf("%s: %v, want it to fail naming %s", c.name, err, c.fails)
}
}
dir = memberLocking(t, "0.1.15", integrityOf(held))
missing := &groupPackage{Name: sdkPackage, Version: "0.1.16-feat-x.9"}
if err := m.placeSDK(context.Background(), dir, "modules/m", nil, missing); err == nil || !strings.Contains(err.Error(), "0.1.16-feat-x.9") || strings.HasPrefix(err.Error(), delivery.WaitsFor) {
t.Errorf("a tagged version the registry lacks answered %v, want it named and no wait", err)
}
}
// ADR 0500 §5 (S3, S4): the group's packed package is served in place of the registry's when the member's lock names its
@@ -121,24 +148,24 @@ func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) {
tgz := tgzOf(t, sdkPackage, "0.1.15")
group := &packed{Name: sdkPackage, Version: "0.1.15", Integrity: integrityOf(tgz), Digest: digestOf(tgz), tgz: tgz}
dir := memberLocking(t, "0.1.15", group.Integrity)
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "0.1.15" || asked.Load() != 0 {
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "0.1.15" || asked.Load() != 0 {
t.Errorf("the group's own version: %v, placed %q, the registry asked %d times", err, placedVersion(dir), asked.Load())
}
dir = memberLocking(t, "0.1.15", "sha512-other")
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err == nil || !strings.Contains(err.Error(), "sha512-other") || !strings.Contains(err.Error(), group.Integrity) {
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err == nil || !strings.Contains(err.Error(), "sha512-other") || !strings.Contains(err.Error(), group.Integrity) {
t.Errorf("another integrity answered %v, want both named", err)
}
dir = memberLocking(t, "0.1.14", integrityOf(tgzOf(t, sdkPackage, "0.1.14")))
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "0.1.14" {
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "0.1.14" {
t.Errorf("a member locking the registry's 0.1.14 in a group: %v, placed %q", err, placedVersion(dir))
}
dir = memberLocking(t, "0.1.13", "sha512-x")
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err == nil || !strings.Contains(err.Error(), "0.1.13") || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), delivery.WaitsFor) {
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err == nil || !strings.Contains(err.Error(), "0.1.13") || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), delivery.WaitsFor) {
t.Errorf("a version neither holds answered %v, want both named and no wait", err)
}
before := asked.Load()
dir = memberLocking(t, "", "")
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "" || asked.Load() != before {
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "" || asked.Load() != before {
t.Errorf("a lock naming no SDK: %v, placed %q, the registry asked", err, placedVersion(dir))
}
}
@@ -148,7 +175,8 @@ func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) {
func TestTheGroupPackageArgumentIsRefusedWhenItIsNotOne(t *testing.T) {
for _, bad := range []any{"novox/mesh-sdk", map[string]any{"repository": "novox/mesh-sdk", "commit": "main"},
map[string]any{"repository": "../x", "commit": strings.Repeat("a", 40)},
map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/heads/main"}} {
map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/heads/main"},
map[string]any{"name": "left-pad", "version": "1.0.0"}, map[string]any{"name": sdkPackage, "version": "latest"}} {
if _, err := groupPackageArg(bad); err == nil {
t.Errorf("%v was taken", bad)
}
@@ -157,4 +185,7 @@ func TestTheGroupPackageArgumentIsRefusedWhenItIsNotOne(t *testing.T) {
if err != nil || g.Ref != "refs/pull/9/head" {
t.Errorf("a pull request's head: %+v %v", g, err)
}
if g, err := groupPackageArg(map[string]any{"name": sdkPackage, "version": "0.1.16-feat-x.1"}); err != nil || !g.tagged() {
t.Errorf("the group's tagged version: %+v %v", g, err)
}
}