DLV-C3: a group's check says tag-wanted for each package its members declare and builds no member before its published
The checker's family asks (stream asks, seven days, in the old streams section) carries tag-wanted
{repository, branch, head, module, package, pull}; the tag is left to the builder, which makes it from the
branch. Each package's published is heard from the two package registry seats on the checker's durable
consumer of the shared events stream; none within the ten-minute bound fails the check naming the package,
before any member is built. A lone pull request and a try ask nothing (design 51 *Tagged versions*).
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -45,7 +45,9 @@ type judgeDeps struct {
|
||||
nodes *nodeNames
|
||||
// predefined is the indexer's kept list of predefined keys; nil reads none.
|
||||
predefined check.PredefinedList
|
||||
logf func(format string, args ...any)
|
||||
// tags says a group's tag-wanted and hears its packages' published (design 51 *Tagged versions*); nil asks none.
|
||||
tags check.TagAsks
|
||||
logf func(format string, args ...any)
|
||||
}
|
||||
|
||||
// keptPredefined reads the indexer's key predefined.list (delivery.PredefinedKept), bound once its state appears.
|
||||
@@ -157,7 +159,7 @@ func newJudge(d judgeDeps) check.Judge {
|
||||
nodes = d.nodes
|
||||
}
|
||||
ex := &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools,
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, Forge: d.forge, Runs: d.repos, Builds: d.builds, BuildEnds: d.ends, BuildWait: d.buildWait, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Read: d.repos, Predefined: d.predefined, Logf: d.logf}
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, Forge: d.forge, Runs: d.repos, Builds: d.builds, BuildEnds: d.ends, BuildWait: d.buildWait, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Read: d.repos, Predefined: d.predefined, Tags: d.tags, Logf: d.logf}
|
||||
if d.nodes != nil && d.builds != nil && d.repos != nil {
|
||||
ex.Serving = runningServes{have: d.nodes, builds: d.builds, read: d.repos, logf: d.logf}
|
||||
}
|
||||
|
||||
@@ -214,11 +214,30 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}()
|
||||
})
|
||||
ch.Runs = repos // the node-engine a check reads beside at its running commit (ADR 0548 §6)
|
||||
// A group's tagged versions (design 51 *Tagged versions*): tag-wanted on the checker's stream asks, and the package
|
||||
// registry seats' published heard on the checker's durable consumer of the shared events stream. Where that consumer is
|
||||
// not there yet a group's check asks and waits in vain until its bound, said in its verdict.
|
||||
tags := tagAsks{js: js, node: cred.Node, heard: &check.PublishedHeard{}, logf: logger.Printf, now: time.Now}
|
||||
switch cons, err := cred.EventConsumer(ctx, js, process.EventsStream); {
|
||||
case err != nil:
|
||||
logger.Printf("the consumer %s_%s on %s does not exist yet, so no package registry's published is heard and a group's check fails at its publish bound until the checker starts again: %v",
|
||||
cred.Node, cred.Module, process.EventsStream, err)
|
||||
default:
|
||||
cc, err := process.HearEvents(cons, func(m jetstream.Msg) {
|
||||
if !tags.heardPublished(m) {
|
||||
_ = m.Ack() // nothing else the checker consumes rides the shared stream
|
||||
}
|
||||
}, logger.Printf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cc.Stop()
|
||||
}
|
||||
ch.Judge = newJudge(judgeDeps{
|
||||
asks: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}, tools: tools, trunks: trunks,
|
||||
kept: kvStore{buckets[delivery.CheckerKeptBucket]}, previews: kvStore{buckets[delivery.PreviewsBucket]},
|
||||
repos: repos, published: published, builds: builds, ends: ends, buildWait: set.buildWait, forge: forgeReads{call: caller.Call}, logf: logger.Printf,
|
||||
announce: announce, compositions: compositions,
|
||||
announce: announce, compositions: compositions, tags: tags,
|
||||
nodes: openNodeNames(ctx, js, logger.Printf),
|
||||
predefined: keptPredefined{process.AwaitBucket(ctx, js, conn, delivery.IndexerModule+"_"+delivery.IndexerPredefinedState,
|
||||
"a change's manifests are judged with no predefined keys, each fallback as its value declares it", logger.Printf)},
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// publishedSeats are the two package registry seats' published, by subject (their contracts in mesh-contracts): heard on
|
||||
// the checker's durable consumer of the shared events stream, made from the manifest's consumes.
|
||||
var publishedSeats = map[string]string{
|
||||
gopackageregistry.SubjectPublished: gopackageregistry.Seat,
|
||||
npmpackageregistry.SubjectPublished: npmpackageregistry.Seat,
|
||||
}
|
||||
|
||||
// tagAsks is check.TagAsks on the bus: tag-wanted published on the checker's own stream asks, and the published heard.
|
||||
type tagAsks struct {
|
||||
js jetstream.JetStream
|
||||
node string
|
||||
heard *check.PublishedHeard
|
||||
logf func(string, ...any)
|
||||
now func() time.Time
|
||||
nextID func() string
|
||||
}
|
||||
|
||||
// Want publishes one tag-wanted with the checker's event headers; the message id makes a second publish of the same
|
||||
// member's head and package within the stream's duplicate window one event.
|
||||
func (t tagAsks) Want(ctx context.Context, w check.TagWanted) error {
|
||||
body, err := json.Marshal(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := nats.NewMsg(eventSubject(check.EventTagWanted))
|
||||
id := fmt.Sprintf("tag-wanted.%s.%s.%s.%s", w.Repository, w.Head, w.Module, w.Package)
|
||||
msg.Header.Set("x-event-id", id)
|
||||
msg.Header.Set("x-source", delivery.CheckerModule)
|
||||
msg.Header.Set("x-node", t.node)
|
||||
msg.Header.Set("x-time", t.now().UTC().Format(time.RFC3339Nano))
|
||||
msg.Header.Set("content-type", "application/json")
|
||||
msg.Data = body
|
||||
_, err = t.js.PublishMsg(ctx, msg, jetstream.WithMsgID(id))
|
||||
return err
|
||||
}
|
||||
|
||||
func (t tagAsks) Await(ctx context.Context, seat, head string) (check.TaggedPublished, bool) {
|
||||
return t.heard.Await(ctx, seat, head)
|
||||
}
|
||||
|
||||
// heardPublished takes a message of the events consumer in when it is a package registry seat's published, and answers
|
||||
// whether it was: a tagged version wakes the checks waiting on it; any other published is passed over.
|
||||
func (t tagAsks) heardPublished(m jetstream.Msg) bool {
|
||||
seat, ok := publishedSeats[m.Subject()]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
var p check.TaggedPublished
|
||||
if err := json.Unmarshal(m.Data(), &p); err != nil {
|
||||
t.logf("%s holds what is no published of %s, left: %v", m.Subject(), seat, err)
|
||||
} else if t.heard.Heard(seat, p) {
|
||||
t.logf("%s published %s %s from %s (tag %s)", seat, p.Name, p.Version, p.Commit, p.Tag)
|
||||
}
|
||||
if err := m.Ack(); err != nil {
|
||||
t.logf("%s cannot be acknowledged, so the bus gives it again: %v", m.Subject(), err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -11,6 +11,8 @@ require (
|
||||
require git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-20261102193506-d221c633799a
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
|
||||
@@ -109,6 +109,10 @@ type Examiner struct {
|
||||
BuildWait time.Duration
|
||||
// Announce says a preview was written under the key (EventPreviewWritten); nil says nothing.
|
||||
Announce Announcer
|
||||
// Tags says a group's tag-wanted and hears its packages' published (design 51 *Tagged versions*); nil asks none.
|
||||
Tags TagAsks
|
||||
// PublishBound is how long a group's check waits for each package's published; DefaultPublishBound when 0.
|
||||
PublishBound time.Duration
|
||||
// Packages names, of a repository, the paths (repository-relative) its TypeScript package artifacts are built from, from
|
||||
// the indexer's modules (delivery.Module.ArtifactPaths, lead decision L8); nil serves no group package.
|
||||
Packages interface {
|
||||
@@ -388,6 +392,11 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
}
|
||||
return 0
|
||||
})
|
||||
// A group's members are built only after each package they declare is published as the group's tagged version
|
||||
// (design 51 *Tagged versions*): a package not published within the bound fails the check before any member is built.
|
||||
if v := st.tagged(ctx); v != nil {
|
||||
return v
|
||||
}
|
||||
var groupPackage map[string]any
|
||||
var failures []*delivery.Verdict
|
||||
for _, p := range pulls {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -60,6 +64,84 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
if got := packagesOfFetch(t, b, "novox/mesh-catalog"); len(got) != 2 || got[SDKModulePath] != trunkS || got[contract] != trunkA {
|
||||
t.Errorf("a pull request alone is built against %v, want every package at its latest published version", got)
|
||||
}
|
||||
|
||||
// Design 51 *Tagged versions* (DLV-C3): a group's check says tag-wanted for each package a member declares and builds no
|
||||
// member before its published; a lone pull request asks nothing; no published within the bound fails naming the package.
|
||||
sdk := catalogue.Manifest{Module: "mesh-sdk", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{
|
||||
{Name: "go", Kind: catalogue.ArtifactPackage, Language: "go"}, {Name: "lib", Kind: catalogue.ArtifactPackage, Language: "typescript"}}}}
|
||||
digest := strings.Repeat("c", 64)
|
||||
sdkPull := func() delivery.CandidatePull {
|
||||
p := pullOf(repoSDK, 12, head2)
|
||||
p.Modules = []string{"mesh-sdk"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: digest}}
|
||||
return p
|
||||
}
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
pulls []delivery.CandidatePull
|
||||
published []string // the seats that publish the SDK member's head
|
||||
wants int
|
||||
fails string
|
||||
}{
|
||||
{name: "a group", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()},
|
||||
published: []string{"go-package-registry", "npm-package-registry"}, wants: 2},
|
||||
{name: "a lone pull request", pulls: []delivery.CandidatePull{sdkPull()}},
|
||||
{name: "a package never published", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()},
|
||||
published: []string{"go-package-registry"}, wants: 2,
|
||||
fails: "no published of the tagged version of lib of mesh-sdk (novox/mesh-sdk#12) on npm-package-registry within 50ms"},
|
||||
} {
|
||||
b := newBench(t)
|
||||
b.fetch.Modules = []string{"modules/m"}
|
||||
b.ex.Read = fixedManifests{digest: sdk}
|
||||
tags := &recordedTags{heard: &PublishedHeard{}, publish: row.published}
|
||||
b.ex.Tags, b.ex.PublishBound = tags, 50*time.Millisecond
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
if len(tags.wanted) != row.wants {
|
||||
t.Errorf("%s: a member was asked before every tag-wanted was said: %d of %d", row.name, len(tags.wanted), row.wants)
|
||||
}
|
||||
return b.answer(a)
|
||||
}
|
||||
v := b.judge(t, caseOf(t, row.pulls...)).Verdict
|
||||
if len(tags.wanted) != row.wants {
|
||||
t.Errorf("%s: tag-wanted said %d times, want %d: %+v", row.name, len(tags.wanted), row.wants, tags.wanted)
|
||||
}
|
||||
for _, w := range tags.wanted {
|
||||
if w.Repository != repoSDK || w.Branch != "feat/x" || w.Head != head2 || w.Module != "mesh-sdk" || w.Pull != 12 || w.Tag != "" {
|
||||
t.Errorf("%s: tag-wanted = %+v", row.name, w)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case row.fails == "" && v.State != delivery.VerdictPass:
|
||||
t.Errorf("%s: verdict = %+v", row.name, v)
|
||||
case row.fails != "" && (v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)):
|
||||
t.Errorf("%s: verdict = %+v, want it to fail saying %q", row.name, v, row.fails)
|
||||
case row.fails != "" && len(b.steps.tool("fetch")) != 0:
|
||||
t.Errorf("%s: a member was built though a package was not published", row.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recordedTags are the checker's tag asks as a test hears them: each tag-wanted kept, and the seats in publish answering it
|
||||
// at once with a tagged version, as the builder and a registry seat would.
|
||||
type recordedTags struct {
|
||||
heard *PublishedHeard
|
||||
publish []string
|
||||
mu sync.Mutex
|
||||
wanted []TagWanted
|
||||
}
|
||||
|
||||
func (r *recordedTags) Want(_ context.Context, w TagWanted) error {
|
||||
r.mu.Lock()
|
||||
r.wanted = append(r.wanted, w)
|
||||
r.mu.Unlock()
|
||||
for _, seat := range r.publish {
|
||||
r.heard.Heard(seat, TaggedPublished{Name: w.Package, Kind: "package", Commit: w.Head, Tag: "feat-x", Version: "0.1.15-feat-x.1"})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *recordedTags) Await(ctx context.Context, seat, head string) (TaggedPublished, bool) {
|
||||
return r.heard.Await(ctx, seat, head)
|
||||
}
|
||||
|
||||
type runsAt map[string]string
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// EventTagWanted is the checker's event that asks the builder for a group member's tagged versions (design 51 *Tagged
|
||||
// versions*, ADR 0610 §1), on the checker's own family asks (its stream asks, kept seven days), apart from the stream
|
||||
// checks, so a waiter on a verdict does not wake on it.
|
||||
const EventTagWanted = "tag-wanted"
|
||||
|
||||
// AsksStream is the checker's stream that carries tag-wanted and verdict, in its old streams section (ADR 0619): the
|
||||
// bus names it ASKS.
|
||||
const AsksStream = "asks"
|
||||
|
||||
// DefaultPublishBound is how long a group's check waits for the published of each package its members declare before it
|
||||
// fails naming the package (design 51 *Tagged versions*: the publish's bound, 10 minutes).
|
||||
const DefaultPublishBound = 10 * time.Minute
|
||||
|
||||
// TagWanted is the body of tag-wanted: the seat definition of mesh-delivery-checker, its holder event tag-wanted, with
|
||||
// the branch the builder keeps the ask under (the lead's ruling of 2026-11-04 12:5x). The tag is left to the builder,
|
||||
// which makes it from the branch, so one part alone names a branch's tag.
|
||||
type TagWanted struct {
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch"`
|
||||
Head string `json:"head"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Module string `json:"module"`
|
||||
Package string `json:"package"`
|
||||
Pull int64 `json:"pull,omitempty"`
|
||||
}
|
||||
|
||||
// PackageSeats are the registry seats a package of each language is published through (design 51 *The seats*).
|
||||
var PackageSeats = map[string]string{"go": "go-package-registry", "typescript": "npm-package-registry"}
|
||||
|
||||
// TaggedPublished is what a registry seat's published says of a tagged version, as the checker reads it.
|
||||
type TaggedPublished struct {
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// TagAsks says tag-wanted and hears the registry seats' published: nil asks nothing, and a group's check builds its
|
||||
// members as it did before tagged versions.
|
||||
type TagAsks interface {
|
||||
// Want says one tag-wanted on the checker's family asks.
|
||||
Want(ctx context.Context, t TagWanted) error
|
||||
// Await answers the tagged version a seat published from a head, once heard; false when ctx ends first.
|
||||
Await(ctx context.Context, seat, head string) (TaggedPublished, bool)
|
||||
}
|
||||
|
||||
// PublishedHeard is the registry seats' published of tagged versions as the checker heard them on its durable consumer
|
||||
// of the shared events stream, by seat and source commit: a waiter wakes on the event, never on a timer.
|
||||
type PublishedHeard struct {
|
||||
mu sync.Mutex
|
||||
heard map[string]TaggedPublished
|
||||
waiters map[string][]chan TaggedPublished
|
||||
}
|
||||
|
||||
func publishedKey(seat, head string) string { return seat + " " + head }
|
||||
|
||||
// Heard takes one published of a seat in; one of no tagged version (no tag, or no commit) is left out and answers false.
|
||||
func (h *PublishedHeard) Heard(seat string, p TaggedPublished) bool {
|
||||
if p.Tag == "" || p.Commit == "" {
|
||||
return false
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.heard == nil {
|
||||
h.heard, h.waiters = map[string]TaggedPublished{}, map[string][]chan TaggedPublished{}
|
||||
}
|
||||
k := publishedKey(seat, p.Commit)
|
||||
h.heard[k] = p
|
||||
for _, w := range h.waiters[k] {
|
||||
w <- p
|
||||
}
|
||||
delete(h.waiters, k)
|
||||
return true
|
||||
}
|
||||
|
||||
// Await is TagAsks.Await over what was heard.
|
||||
func (h *PublishedHeard) Await(ctx context.Context, seat, head string) (TaggedPublished, bool) {
|
||||
k := publishedKey(seat, head)
|
||||
h.mu.Lock()
|
||||
if h.heard == nil {
|
||||
h.heard, h.waiters = map[string]TaggedPublished{}, map[string][]chan TaggedPublished{}
|
||||
}
|
||||
if p, ok := h.heard[k]; ok {
|
||||
h.mu.Unlock()
|
||||
return p, true
|
||||
}
|
||||
w := make(chan TaggedPublished, 1)
|
||||
h.waiters[k] = append(h.waiters[k], w)
|
||||
h.mu.Unlock()
|
||||
select {
|
||||
case p := <-w:
|
||||
return p, true
|
||||
case <-ctx.Done():
|
||||
h.mu.Lock()
|
||||
h.waiters[k] = slices.DeleteFunc(h.waiters[k], func(c chan TaggedPublished) bool { return c == w })
|
||||
h.mu.Unlock()
|
||||
return TaggedPublished{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// declaredPackage is one package a group member declares: the module, the artifact's name and the seat it is published
|
||||
// through.
|
||||
type declaredPackage struct {
|
||||
pull Pull
|
||||
module, name string
|
||||
language, seat string
|
||||
}
|
||||
|
||||
// declaredPackages are the packages the case's members declare, read from each touched module's manifest at the merge
|
||||
// preview (the candidate's), else at the trunk (the indexer's manifests): a member that declares none is not asked for.
|
||||
func (st *state) declaredPackages(ctx context.Context) ([]declaredPackage, error) {
|
||||
if st.j.Read == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var trunk map[string]string
|
||||
if st.j.Manifests != nil {
|
||||
trunk = st.j.Manifests.ManifestDigests()
|
||||
}
|
||||
var out []declaredPackage
|
||||
for _, p := range st.c.Pulls {
|
||||
for _, module := range p.Modules {
|
||||
digest := p.Fps[module].Manifest
|
||||
if digest == "" {
|
||||
digest = trunk[module]
|
||||
}
|
||||
if digest == "" {
|
||||
continue
|
||||
}
|
||||
m, err := st.j.Read.Manifest(ctx, digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the manifest of %s (%s) cannot be read, so the packages it declares are not known: %w", module, pullKey(p), err)
|
||||
}
|
||||
if m.Build == nil {
|
||||
continue
|
||||
}
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a.Kind != catalogue.ArtifactPackage {
|
||||
continue
|
||||
}
|
||||
seat, ok := PackageSeats[a.Language]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the package %s of %s (%s) is in %q, which no registry seat publishes", a.Name, module, pullKey(p), a.Language)
|
||||
}
|
||||
out = append(out, declaredPackage{pull: p, module: module, name: a.Name, language: a.Language, seat: seat})
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// tagged asks a group's tagged versions before any member is built (design 51 *Tagged versions*): one tag-wanted per
|
||||
// package a member declares, then each package's published awaited within the publish bound. A lone pull request and a
|
||||
// try ask nothing. A package not published within the bound fails the check naming it.
|
||||
func (st *state) tagged(ctx context.Context) *delivery.Verdict {
|
||||
if st.j.Tags == nil || st.c.Try != nil || len(st.c.Pulls) < 2 {
|
||||
return nil
|
||||
}
|
||||
decl, err := st.declaredPackages(ctx)
|
||||
if err != nil {
|
||||
return fail(delivery.CheckerModule, err.Error())
|
||||
}
|
||||
if len(decl) == 0 {
|
||||
return nil
|
||||
}
|
||||
st.c.Progress.Set("tagged versions", "")
|
||||
var names []string
|
||||
for _, d := range decl {
|
||||
t := TagWanted{Repository: d.pull.Repository, Branch: st.c.Group, Head: d.pull.Head, Module: d.module, Package: d.name, Pull: int64(d.pull.Number)}
|
||||
if err := st.j.Tags.Want(ctx, t); err != nil {
|
||||
return fail(delivery.CheckerModule, fmt.Sprintf("the tagged version of %s of %s (%s) cannot be asked: %v", d.name, d.module, pullKey(d.pull), err))
|
||||
}
|
||||
names = append(names, d.module+"/"+d.name)
|
||||
}
|
||||
bound := st.j.PublishBound
|
||||
if bound <= 0 {
|
||||
bound = DefaultPublishBound
|
||||
}
|
||||
waits := fmt.Sprintf("waits on the tagged versions of %s before its members are built, at most %s (design 51 *Tagged versions*)", strings.Join(names, ", "), bound)
|
||||
st.c.note(waits)
|
||||
st.j.logf("%s %s", st.c.Key, waits)
|
||||
at, cancel := context.WithTimeout(ctx, bound)
|
||||
defer cancel()
|
||||
var late []string
|
||||
for _, d := range decl {
|
||||
p, ok := st.j.Tags.Await(at, d.seat, d.pull.Head)
|
||||
if !ok {
|
||||
late = append(late, fmt.Sprintf("%s of %s (%s) on %s", d.name, d.module, pullKey(d.pull), d.seat))
|
||||
continue
|
||||
}
|
||||
st.c.note(fmt.Sprintf("%s of %s is built as %s %s (tag %s)", d.name, d.module, p.Name, p.Version, p.Tag))
|
||||
}
|
||||
if len(late) > 0 {
|
||||
if ctx.Err() != nil {
|
||||
return fail(delivery.CheckerModule, ctx.Err().Error())
|
||||
}
|
||||
return fail(delivery.CheckerModule, fmt.Sprintf("no published of the tagged version of %s within %s, so no member is built (design 51 *Tagged versions*)", strings.Join(late, "; "), bound))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -33,8 +33,9 @@ type artifact struct {
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
Name string `json:"name"`
|
||||
Events []string `json:"events"`
|
||||
Name string `json:"name"`
|
||||
Events []string `json:"events"`
|
||||
MaxAgeDays int `json:"max-age-days,omitempty"`
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
@@ -121,7 +122,8 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
t.Errorf("uses = %v, want the Go and Node build seats", m.Uses)
|
||||
}
|
||||
// The checker is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it starts.
|
||||
if want := []string{"mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
||||
// And a group's check by the package registry seats' published of its tagged versions (design 51 *Tagged versions*).
|
||||
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
||||
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
||||
}
|
||||
if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" {
|
||||
@@ -131,7 +133,9 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
t.Errorf("secrets-owner = %q", m.SecretsOwner)
|
||||
}
|
||||
// The checker says each time it wrote a preview or a status, so the indexer's and the forge bridge's watches find the buckets (ADR 0484 clauses 2 and 3).
|
||||
if want := []stream{{Name: "checks", Events: []string{"preview.**", "owed.**"}}}; !reflect.DeepEqual(m.Streams, want) {
|
||||
// Its family asks (design 51 *Tagged versions*, design 26): tag-wanted and verdict, seven days, apart from checks so a
|
||||
// waiter on a verdict does not wake on a tag ask; in the old streams section (ADR 0619).
|
||||
if want := []stream{{Name: "checks", Events: []string{"preview.**", "owed.**"}}, {Name: "asks", Events: []string{"verdict", "tag-wanted"}, MaxAgeDays: 7}}; !reflect.DeepEqual(m.Streams, want) {
|
||||
t.Errorf("streams = %+v, want %+v", m.Streams, want)
|
||||
}
|
||||
// The read tools (issue 1686) are served by the tools bundle, not by the checker's process.
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"go-package-registry.published",
|
||||
"mesh-build-go.toolchain.reported",
|
||||
"mesh-build-node.toolchain.reported"
|
||||
"mesh-build-node.toolchain.reported",
|
||||
"npm-package-registry.published"
|
||||
],
|
||||
"streams": [
|
||||
{ "name": "checks", "events": ["preview.**", "owed.**"] }
|
||||
{ "name": "checks", "events": ["preview.**", "owed.**"] },
|
||||
{ "name": "asks", "events": ["verdict", "tag-wanted"], "max-age-days": 7 }
|
||||
],
|
||||
"state": [
|
||||
{ "name": "asks", "history": 1 },
|
||||
|
||||
Reference in New Issue
Block a user