Join: merge A2 (wip/registry-a2)

This commit is contained in:
2026-11-04 15:12:01 +01:00
5 changed files with 133 additions and 24 deletions
@@ -10,7 +10,11 @@ package main
// written beside it, which go mod verify then checks the downloaded module against;
// - a commit Go cannot fetch (the head of a group's pull request) is found among the forge's pull request heads and
// placed beside the workspace, and the modfile replaces the package by it, as a pin on a head is (pins.go);
// - a version go.mod names is the oldest the module accepts: one newer than the asked is built, and said loudly.
// - a version go.mod names is the oldest the module accepts: one newer than the asked is built, and said loudly;
// - a semantic version (a delivery group's tagged version, `v0.1.15-feat-x.2`: novox/hq ADR 0607 §3, §5, design 51
// *Tagged versions*) is fetched by that version through the Go seat and built whatever go.mod names, since the
// group's check is of that version; one the Go seat does not hold fails the fetch naming it, and is never looked for
// among the pull request heads.
//
// Each package's outcome is named in the module's log and in the fetch's answer (FetchResult.Packages).
@@ -98,10 +102,16 @@ func (m *Module) takeVersion(ctx context.Context, root, dir string, tv *TakenVer
return err
}
pinsDir := filepath.Dir(modfile)
tagged := delivery.PackageSemver.MatchString(tv.Asked)
r := m.Run(ctx, m.goEnv(), "go", "-C", pinsDir, "mod", "download", "-json", tv.Package+"@"+tv.Asked)
var info downloadInfo
_ = json.Unmarshal([]byte(r.Stdout), &info)
if r.Status != 0 || r.Err != "" || info.Version == "" || info.Error != "" {
failed := r.Status != 0 || r.Err != "" || info.Version == "" || info.Error != ""
if failed && tagged {
failure := strings.TrimSpace(info.Error + "\n" + r.Err + "\n" + r.Stderr)
return fmt.Errorf("downloading the group's tagged version %s of %s through the Go seat failed (a download failure that may be retaken): %s", tv.Asked, tv.Package, causeLine(failure))
}
if failed {
// Not a commit Go can fetch: the head of a group's pull request (ADR 0573 §4), placed from the forge's heads.
// Whether it is one is the forge's fact (placePin), whatever go said.
failure := strings.TrimSpace(info.Error + "\n" + r.Err + "\n" + r.Stderr)
@@ -125,7 +135,9 @@ func (m *Module) takeVersion(ctx context.Context, root, dir string, tv *TakenVer
tv.Built, tv.Said = tv.Asked, "Go cannot fetch it: placed from the pull request's head, which has no checksum"
return nil
}
if compareVersions(tv.Named, info.Version) > 0 {
if tagged && compareVersions(tv.Named, info.Version) > 0 {
tv.Said = fmt.Sprintf("go.mod names %s, newer than the group's tagged version %s, which is built: the group's check is of its tagged version (ADR 0607 §3)", tv.Named, info.Version)
} else if compareVersions(tv.Named, info.Version) > 0 {
// A version go.mod names is the oldest the module accepts (ADR 0573 §2): said loudly, and go.mod's is built.
tv.Built = tv.Named
tv.Said = fmt.Sprintf("go.mod names %s, newer than the asked %s, so go.mod's is built (a version a repository names is the oldest it accepts, ADR 0573 §2)", tv.Named, info.Version)
@@ -50,6 +50,16 @@ func TestAnAskedVersionIsWrittenIntoTheModfileWhateverGoModNames(t *testing.T) {
{name: "a forge that cannot be listed is retaken", asked: commitSHA, failing: true, forgeDown: true,
failsWith: "unable to access the forge",
answer: `{"Path":"` + sdk + `","Error":"invalid version: unknown revision ` + pinShort + `"}`},
// ADR 0607 §3, §5 (design 51 *Tagged versions*): a group's tagged version is fetched by that version and built
// whatever go.mod names; one the Go seat lacks fails naming it, never looked for among the pull request heads.
{name: "a group's tagged version", asked: "v0.1.15-feat-x.2",
answer: `{"Path":"` + sdk + `","Version":"v0.1.15-feat-x.2","Sum":"h1:zip=","GoModSum":"h1:mod="}`,
built: "v0.1.15-feat-x.2", edit: "-require=" + sdk + "@v0.1.15-feat-x.2"},
{name: "a group's tagged version older than go.mod's", asked: "v0.1.14-feat-x.2",
answer: `{"Path":"` + sdk + `","Version":"v0.1.14-feat-x.2","Sum":"h1:zip=","GoModSum":"h1:mod="}`,
built: "v0.1.14-feat-x.2", said: "the group's tagged version", edit: "-require=" + sdk + "@v0.1.14-feat-x.2"},
{name: "a tagged version the Go seat lacks", asked: "v0.1.15-feat-x.3", failing: true, failsWith: "the group's tagged version v0.1.15-feat-x.3",
answer: `{"Path":"` + sdk + `","Error":"` + sdk + `@v0.1.15-feat-x.3: reading https://seat/go/@v/v0.1.15-feat-x.3.info: 404 Not Found"}`},
{name: "a refused connection is no pin", asked: offHead, failing: true, failsWith: "connection refused",
answer: `{"Path":"` + sdk + `","Error":"git ls-remote -q origin in /tmp/gopath: dial tcp 10.10.0.1:443: connect: connection refused"}`},
} {
@@ -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,15 @@ 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. The packed member is the old
// form, kept because the running checker hands it until the checker that hands the tagged version is delivered; its
// removal is a subtask of the milestone after that delivery (issue 1966).
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 +236,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 +265,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 +312,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 +373,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)
}
}