CT15 (take-in bodies, none-yet): the seat mesh-catalog's bodies give take-in.taken and take-in.pending; revision answers none-yet in a field
The seat's bodies hold TakeInTaken (branch, source, entries with repository and pull) and TakeInPending (branch); revision's answer gains none-yet and its revision is optional; meshcatalogclient reads the field, never the error's words. The holders' and readers' uses of the answer follow (a pointer revision). Builds: design 47 G §476, §493, §530, §454, §648 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -188,7 +187,7 @@ func (c *Catalog) ask(_ context.Context, after string) (views.Answer, error) {
|
||||
}
|
||||
c.mu.Lock()
|
||||
if after == "" || c.paging == nil {
|
||||
c.paging = &Half{Revision: a.Revision, Entries: map[string]seat.CatalogEntry{}}
|
||||
c.paging = &Half{Revision: *a.Revision, Entries: map[string]seat.CatalogEntry{}}
|
||||
} else if c.paging.Revision.Revision != a.Revision.Revision {
|
||||
was := c.paging.Revision.Revision
|
||||
c.paging = nil
|
||||
@@ -216,26 +215,27 @@ func (c *Catalog) ask(_ context.Context, after string) (views.Answer, error) {
|
||||
return views.Answer{V: 1, Owner: seat.Seat, State: c.Follower.State, Revision: uint64(rev), Created: catalogCreated, Values: values, Next: a.Next, At: time.Now()}, nil
|
||||
}
|
||||
|
||||
// errNoneYet is the seat's answer `none yet`: the branch has no revision yet.
|
||||
// errNoneYet is the seat's answer `none-yet`: the branch has no revision yet (design 47 G §530).
|
||||
var errNoneYet = errors.New("none yet")
|
||||
|
||||
func askRevision(call Call, in seat.RevisionInput) (seat.RevisionAnswer, error) {
|
||||
raw, err := call(RevisionVerb, in)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), views.NoneYet) {
|
||||
return seat.RevisionAnswer{}, errNoneYet
|
||||
}
|
||||
return seat.RevisionAnswer{}, err
|
||||
}
|
||||
answer, said, ok := buscalls.Unwrap(raw)
|
||||
if !ok {
|
||||
if strings.Contains(said, views.NoneYet) {
|
||||
return seat.RevisionAnswer{}, errNoneYet
|
||||
}
|
||||
return seat.RevisionAnswer{}, fmt.Errorf("%s answered: %s", RevisionVerb, said)
|
||||
}
|
||||
var a seat.RevisionAnswer
|
||||
if err := json.Unmarshal(answer, &a); err != nil || a.Revision.Revision <= 0 {
|
||||
if err := json.Unmarshal(answer, &a); err != nil {
|
||||
return seat.RevisionAnswer{}, fmt.Errorf("%s answered no revision: %s", RevisionVerb, clipBytes(answer))
|
||||
}
|
||||
// An answer is told by its fields, never by its words (§530): `none-yet` is a ready, empty view.
|
||||
if a.NoneYet {
|
||||
return seat.RevisionAnswer{}, errNoneYet
|
||||
}
|
||||
if a.Revision == nil || a.Revision.Revision <= 0 {
|
||||
return seat.RevisionAnswer{}, fmt.Errorf("%s answered no revision: %s", RevisionVerb, clipBytes(answer))
|
||||
}
|
||||
return a, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package meshcatalogclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -165,3 +166,31 @@ func TestCandidatesKeyEscapesTheGroupAndReadsBack(t *testing.T) {
|
||||
t.Error("an empty group keys")
|
||||
}
|
||||
}
|
||||
|
||||
// CT15 (design 47 G §476, §493, §648): the seat's `bodies` give the types the indexer writes and the runner reads: a
|
||||
// take-in.taken names its branch, source revision and entries (an entry of a merged message names its pull request),
|
||||
// a take-in.pending its branch, and a revision answer for a branch with no revision yet is `none-yet` alone.
|
||||
func TestTakeInBodiesAndNoneYetAreTheSeatsTypes(t *testing.T) {
|
||||
var taken seat.TakeInTaken
|
||||
body := `{"v":1,"kind":"taken","key":"m.00000000000000000007","what":"revision","branch":"beta","source":3590,"since":"2026-11-11T10:00:00Z",` +
|
||||
`"merges":[{"repository":"novox/mesh-sdk","number":9,"merge":"cc"}],` +
|
||||
`"entries":[{"module":"nats","commit":"cc","fingerprint":"src2:aa","repository":"novox/mesh-sdk","pull":9},{"module":"zsh","commit":"cc","fingerprint":"src2:bb"}]}`
|
||||
if err := json.Unmarshal([]byte(body), &taken); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if taken.Branch != "beta" || taken.Source != 3590 || len(taken.Entries) != 2 || taken.Entries[0].Pull != 9 || taken.Entries[1].Repository != "" {
|
||||
t.Errorf("take-in.taken read as %+v", taken)
|
||||
}
|
||||
var pending seat.TakeInPending
|
||||
if err := json.Unmarshal([]byte(`{"v":1,"kind":"pending","key":"m.1","what":"revision","branch":"beta","since":"2026-11-11T10:00:00Z","merges":[]}`), &pending); err != nil || pending.Branch != "beta" {
|
||||
t.Errorf("take-in.pending read as %+v, %v", pending, err)
|
||||
}
|
||||
var none seat.RevisionAnswer
|
||||
if err := json.Unmarshal([]byte(`{"none-yet":true}`), &none); err != nil || !none.NoneYet || none.Revision != nil {
|
||||
t.Errorf("a none-yet answer read as %+v, %v", none, err)
|
||||
}
|
||||
raw, _ := json.Marshal(seat.RevisionAnswer{NoneYet: true})
|
||||
if string(raw) != `{"none-yet":true}` {
|
||||
t.Errorf("a none-yet answer is written as %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ func (f *fakeRevisions) call(key string, body any) (json.RawMessage, error) {
|
||||
defer f.mu.Unlock()
|
||||
rev, ok := f.newest[in.Branch]
|
||||
if ok && rev == 0 {
|
||||
return nil, fmt.Errorf("revision of %s: none yet", in.Branch)
|
||||
return json.Marshal(seat.RevisionAnswer{NoneYet: true})
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("asked for the branch %q", in.Branch)
|
||||
}
|
||||
f.asks[in.Branch]++
|
||||
return json.Marshal(seat.RevisionAnswer{
|
||||
Revision: seat.CatalogRevision{Revision: rev, Kind: KindTrunk, Branch: in.Branch},
|
||||
Revision: &seat.CatalogRevision{Revision: rev, Kind: KindTrunk, Branch: in.Branch},
|
||||
Entries: []seat.CatalogEntry{{Module: fmt.Sprintf("m-of-%s-%d", in.Branch, rev)}},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1060,6 +1060,108 @@ type VersionEntry struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// TakeInEntry is one module of a taken-in revision (design 47 G section 476, section 648).
|
||||
type TakeInEntry struct {
|
||||
Commit string `json:"commit"`
|
||||
// the module's build-input fingerprint at the commit
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Module string `json:"module"`
|
||||
// from a merged message; absent for a pushed one (design 47 G section 648)
|
||||
Pull int64 `json:"pull,omitempty"`
|
||||
// from a merged message; absent for a pushed one (design 47 G section 648)
|
||||
Repository string `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
// TakeInMember is one pull request at one commit.
|
||||
type TakeInMember struct {
|
||||
Head string `json:"head"`
|
||||
Number int64 `json:"number"`
|
||||
Repository string `json:"repository"`
|
||||
}
|
||||
|
||||
// TakeInMerge is one merge a take-in event holds: the repository, its pull request and the merge commit the forge made of it.
|
||||
type TakeInMerge struct {
|
||||
Merge string `json:"merge"`
|
||||
Number int64 `json:"number"`
|
||||
Repository string `json:"repository"`
|
||||
}
|
||||
|
||||
// TakeInPending is the body of the indexer's take-in.pending: a wait for a revision or a group, with the watched branch (design 47 G section 493).
|
||||
type TakeInPending struct {
|
||||
// the watched branch the revision is of (design 47 G section 476, section 493)
|
||||
Branch string `json:"branch,omitempty"`
|
||||
// who acted by hand, on a taken event
|
||||
By string `json:"by,omitempty"`
|
||||
// a hand drop: the merges are not taken in
|
||||
Dropped bool `json:"dropped,omitempty"`
|
||||
// the delivery group, for a group being gathered
|
||||
Group string `json:"group,omitempty"`
|
||||
// the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered
|
||||
Key string `json:"key"`
|
||||
// the event's kind, pending or taken
|
||||
Kind string `json:"kind"`
|
||||
// the keys of merges whose message the forge bridge's stream no longer holds
|
||||
Lost []string `json:"lost,omitempty"`
|
||||
// every approved member of a group with its head
|
||||
Members []TakeInMember `json:"members,omitempty"`
|
||||
// the merges it holds, oldest first
|
||||
Merges []TakeInMerge `json:"merges"`
|
||||
// why the indexer holds a merge it does not take in although no indexer build is named
|
||||
OwnBuildUnknown string `json:"own-build-unknown,omitempty"`
|
||||
// why the manifest of the indexer the revision names was refused
|
||||
Refused string `json:"refused,omitempty"`
|
||||
// when the wait began
|
||||
Since time.Time `json:"since"`
|
||||
// the layout version
|
||||
V int64 `json:"v"`
|
||||
// the indexer build a revision waits for
|
||||
WaitsFor string `json:"waits-for,omitempty"`
|
||||
// what waits: a revision or a group
|
||||
What string `json:"what"`
|
||||
// why they acted by hand, on a taken event
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
// TakeInTaken is the body of the indexer's take-in.taken: a wait ended, with the branch, the source revision and the entries (design 47 G section 476, section 493).
|
||||
type TakeInTaken struct {
|
||||
// the watched branch the revision is of (design 47 G section 476, section 493)
|
||||
Branch string `json:"branch,omitempty"`
|
||||
// who acted by hand, on a taken event
|
||||
By string `json:"by,omitempty"`
|
||||
// a hand drop: the merges are not taken in
|
||||
Dropped bool `json:"dropped,omitempty"`
|
||||
// one entry per module of that revision; none for a hand drop (design 47 G section 476, section 493)
|
||||
Entries []TakeInEntry `json:"entries,omitempty"`
|
||||
// the delivery group, for a group being gathered
|
||||
Group string `json:"group,omitempty"`
|
||||
// the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered
|
||||
Key string `json:"key"`
|
||||
// the event's kind, pending or taken
|
||||
Kind string `json:"kind"`
|
||||
// the keys of merges whose message the forge bridge's stream no longer holds
|
||||
Lost []string `json:"lost,omitempty"`
|
||||
// every approved member of a group with its head
|
||||
Members []TakeInMember `json:"members,omitempty"`
|
||||
// the merges it holds, oldest first
|
||||
Merges []TakeInMerge `json:"merges"`
|
||||
// why the indexer holds a merge it does not take in although no indexer build is named
|
||||
OwnBuildUnknown string `json:"own-build-unknown,omitempty"`
|
||||
// why the manifest of the indexer the revision names was refused
|
||||
Refused string `json:"refused,omitempty"`
|
||||
// when the wait began
|
||||
Since time.Time `json:"since"`
|
||||
// the source revision taken in (design 47 G section 476)
|
||||
Source int64 `json:"source,omitempty"`
|
||||
// the layout version
|
||||
V int64 `json:"v"`
|
||||
// the indexer build a revision waits for
|
||||
WaitsFor string `json:"waits-for,omitempty"`
|
||||
// what waits: a revision or a group
|
||||
What string `json:"what"`
|
||||
// why they acted by hand, on a taken event
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
// ModulesInput is what modules takes.
|
||||
type ModulesInput struct {
|
||||
}
|
||||
@@ -1180,10 +1282,12 @@ type RevisionInput struct {
|
||||
// RevisionAnswer is what revision answers.
|
||||
type RevisionAnswer struct {
|
||||
// this page's entries whole, by module name
|
||||
Entries []CatalogEntry `json:"entries"`
|
||||
Entries []CatalogEntry `json:"entries,omitempty"`
|
||||
// the module to ask after for the next page; absent on the last
|
||||
Next string `json:"next,omitempty"`
|
||||
Revision CatalogRevision `json:"revision"`
|
||||
Next string `json:"next,omitempty"`
|
||||
// true for a watched branch with no revision yet: no revision, no entries and no error follow, and a reader holds the branch's view ready and empty (design 47 G section 530)
|
||||
NoneYet bool `json:"none-yet,omitempty"`
|
||||
Revision *CatalogRevision `json:"revision,omitempty"`
|
||||
}
|
||||
|
||||
// RegistrationsInput is what registrations takes.
|
||||
|
||||
@@ -141,6 +141,8 @@ func TestFixtures(t *testing.T) {
|
||||
decode: func(b []byte) (any, error) { var v RevisionAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "revision answers its last page", subject: "", given: "{\"entries\":[{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}],\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"definitions\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"seat\":\"node-build-go\"}],\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"mesh-build-go\"},{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\"}],\"kind\":\"trunk\",\"revision\":12,\"source\":3589,\"trunk\":[{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"repository\":\"novox/mesh-catalog\"}],\"v\":1}}", wire: "{\"entries\":[{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}],\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"definitions\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"seat\":\"node-build-go\"}],\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"mesh-build-go\"},{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\"}],\"kind\":\"trunk\",\"revision\":12,\"source\":3589,\"trunk\":[{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"repository\":\"novox/mesh-catalog\"}],\"v\":1}}",
|
||||
decode: func(b []byte) (any, error) { var v RevisionAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "revision answers none yet for a branch with no revision", subject: "", given: "{\"none-yet\":true}", wire: "{\"none-yet\":true}",
|
||||
decode: func(b []byte) (any, error) { var v RevisionAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "registrations of one module with its history", subject: "", given: "{\"history\":true,\"module\":\"nats\"}", wire: "{\"history\":true,\"module\":\"nats\"}",
|
||||
decode: func(b []byte) (any, error) { var v RegistrationsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "registrations answers one with its versions", subject: "", given: "{\"history\":[{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":1,\"warrant\":\"ask-m2kx9a1b3c4d5e\"},{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"artifacts\":{\"program\":{\"unit\":\"bypass\"}},\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":2,\"warrant\":\"ask-m2kx9a1b3c4d5e\"}],\"registrations\":[{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"artifacts\":{\"program\":{\"unit\":\"bypass\"}},\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":2,\"warrant\":\"ask-m2kx9a1b3c4d5e\"}]}", wire: "{\"history\":[{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":1,\"warrant\":\"ask-m2kx9a1b3c4d5e\"},{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"artifacts\":{\"program\":{\"unit\":\"bypass\"}},\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":2,\"warrant\":\"ask-m2kx9a1b3c4d5e\"}],\"registrations\":[{\"at\":\"2026-11-10T10:00:00Z\",\"branches\":[\"main\"],\"by\":\"operator@shanks\",\"module\":\"nats\",\"origin\":\"novox/mesh-catalog\",\"path\":\"modules/nats/module.json\",\"policy\":{\"artifacts\":{\"program\":{\"unit\":\"bypass\"}},\"steps\":{\"approval\":\"bypass\",\"composition\":\"required\",\"integration\":\"required\",\"lint\":\"required\",\"review\":\"optional\",\"unit\":\"required\"}},\"v\":1,\"version\":2,\"warrant\":\"ask-m2kx9a1b3c4d5e\"}]}",
|
||||
|
||||
@@ -152,6 +152,13 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "revision answers none yet for a branch with no revision",
|
||||
"of": "verb:revision.answer",
|
||||
"given": {
|
||||
"none-yet": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "registrations of one module with its history",
|
||||
"of": "verb:registrations.input",
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
},
|
||||
{
|
||||
"name": "revision",
|
||||
"description": "The catalog half of the revision view (ADR 0656 \u00a76): one catalog graph revision with its entries whole, the trunk heads, the seat definitions by digest, and each manifest's verdict and fingerprint; the trunk's newest when no revision and no candidate is named, or a candidate's for its group and heads. Answered by the holder from what it holds, in its own runtime (ADR 0663), in pages of entries. Reads only.",
|
||||
"description": "The catalog half of the revision view (ADR 0656 \u00a76): one catalog graph revision with its entries whole, the trunk heads, the seat definitions by digest, and each manifest's verdict and fingerprint; the trunk's newest when no revision and no candidate is named, or a candidate's for its group and heads. Answered by the holder from what it holds, in its own runtime (ADR 0663), in pages of entries. Reads only. A watched branch with no revision yet answers {none-yet: true} alone (design 47 G section 530).",
|
||||
"input": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -292,12 +292,12 @@
|
||||
"next": {
|
||||
"type": "string",
|
||||
"description": "the module to ask after for the next page; absent on the last"
|
||||
},
|
||||
"none-yet": {
|
||||
"type": "boolean",
|
||||
"description": "true for a watched branch with no revision yet: no revision, no entries and no error follow, and a reader holds the branch's view ready and empty (design 47 G section 530)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"revision",
|
||||
"entries"
|
||||
]
|
||||
}
|
||||
},
|
||||
"class": "read"
|
||||
},
|
||||
@@ -2735,5 +2735,263 @@
|
||||
"language"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bodies": {
|
||||
"TakeInMerge": {
|
||||
"type": "object",
|
||||
"description": "one merge a take-in event holds: the repository, its pull request and the merge commit the forge made of it",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "integer"
|
||||
},
|
||||
"merge": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repository",
|
||||
"number",
|
||||
"merge"
|
||||
]
|
||||
},
|
||||
"TakeInMember": {
|
||||
"type": "object",
|
||||
"description": "one pull request at one commit",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "integer"
|
||||
},
|
||||
"head": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repository",
|
||||
"number",
|
||||
"head"
|
||||
]
|
||||
},
|
||||
"TakeInEntry": {
|
||||
"type": "object",
|
||||
"description": "one module of a taken-in revision (design 47 G section 476, section 648)",
|
||||
"properties": {
|
||||
"module": {
|
||||
"type": "string"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string"
|
||||
},
|
||||
"fingerprint": {
|
||||
"type": "string",
|
||||
"description": "the module's build-input fingerprint at the commit"
|
||||
},
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "from a merged message; absent for a pushed one (design 47 G section 648)"
|
||||
},
|
||||
"pull": {
|
||||
"type": "integer",
|
||||
"description": "from a merged message; absent for a pushed one (design 47 G section 648)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"module",
|
||||
"commit",
|
||||
"fingerprint"
|
||||
]
|
||||
},
|
||||
"TakeInPending": {
|
||||
"type": "object",
|
||||
"description": "the body of the indexer's take-in.pending: a wait for a revision or a group, with the watched branch (design 47 G section 493)",
|
||||
"properties": {
|
||||
"v": {
|
||||
"type": "integer",
|
||||
"description": "the layout version"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "the event's kind, pending or taken"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered"
|
||||
},
|
||||
"what": {
|
||||
"type": "string",
|
||||
"description": "what waits: a revision or a group"
|
||||
},
|
||||
"merges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/bodies/TakeInMerge"
|
||||
},
|
||||
"description": "the merges it holds, oldest first"
|
||||
},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"description": "the delivery group, for a group being gathered"
|
||||
},
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/bodies/TakeInMember"
|
||||
},
|
||||
"description": "every approved member of a group with its head"
|
||||
},
|
||||
"waits-for": {
|
||||
"type": "string",
|
||||
"description": "the indexer build a revision waits for"
|
||||
},
|
||||
"since": {
|
||||
"type": "string",
|
||||
"description": "when the wait began",
|
||||
"format": "date-time"
|
||||
},
|
||||
"by": {
|
||||
"type": "string",
|
||||
"description": "who acted by hand, on a taken event"
|
||||
},
|
||||
"why": {
|
||||
"type": "string",
|
||||
"description": "why they acted by hand, on a taken event"
|
||||
},
|
||||
"dropped": {
|
||||
"type": "boolean",
|
||||
"description": "a hand drop: the merges are not taken in"
|
||||
},
|
||||
"lost": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "the keys of merges whose message the forge bridge's stream no longer holds"
|
||||
},
|
||||
"refused": {
|
||||
"type": "string",
|
||||
"description": "why the manifest of the indexer the revision names was refused"
|
||||
},
|
||||
"own-build-unknown": {
|
||||
"type": "string",
|
||||
"description": "why the indexer holds a merge it does not take in although no indexer build is named"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch the revision is of (design 47 G section 476, section 493)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"v",
|
||||
"kind",
|
||||
"key",
|
||||
"what",
|
||||
"merges",
|
||||
"since"
|
||||
]
|
||||
},
|
||||
"TakeInTaken": {
|
||||
"type": "object",
|
||||
"description": "the body of the indexer's take-in.taken: a wait ended, with the branch, the source revision and the entries (design 47 G section 476, section 493)",
|
||||
"properties": {
|
||||
"v": {
|
||||
"type": "integer",
|
||||
"description": "the layout version"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "the event's kind, pending or taken"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered"
|
||||
},
|
||||
"what": {
|
||||
"type": "string",
|
||||
"description": "what waits: a revision or a group"
|
||||
},
|
||||
"merges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/bodies/TakeInMerge"
|
||||
},
|
||||
"description": "the merges it holds, oldest first"
|
||||
},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"description": "the delivery group, for a group being gathered"
|
||||
},
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/bodies/TakeInMember"
|
||||
},
|
||||
"description": "every approved member of a group with its head"
|
||||
},
|
||||
"waits-for": {
|
||||
"type": "string",
|
||||
"description": "the indexer build a revision waits for"
|
||||
},
|
||||
"since": {
|
||||
"type": "string",
|
||||
"description": "when the wait began",
|
||||
"format": "date-time"
|
||||
},
|
||||
"by": {
|
||||
"type": "string",
|
||||
"description": "who acted by hand, on a taken event"
|
||||
},
|
||||
"why": {
|
||||
"type": "string",
|
||||
"description": "why they acted by hand, on a taken event"
|
||||
},
|
||||
"dropped": {
|
||||
"type": "boolean",
|
||||
"description": "a hand drop: the merges are not taken in"
|
||||
},
|
||||
"lost": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "the keys of merges whose message the forge bridge's stream no longer holds"
|
||||
},
|
||||
"refused": {
|
||||
"type": "string",
|
||||
"description": "why the manifest of the indexer the revision names was refused"
|
||||
},
|
||||
"own-build-unknown": {
|
||||
"type": "string",
|
||||
"description": "why the indexer holds a merge it does not take in although no indexer build is named"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch the revision is of (design 47 G section 476, section 493)"
|
||||
},
|
||||
"source": {
|
||||
"type": "integer",
|
||||
"description": "the source revision taken in (design 47 G section 476)"
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/bodies/TakeInEntry"
|
||||
},
|
||||
"description": "one entry per module of that revision; none for a hand drop (design 47 G section 476, section 493)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"v",
|
||||
"kind",
|
||||
"key",
|
||||
"what",
|
||||
"merges",
|
||||
"since"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+106
-2
@@ -516,6 +516,108 @@ export interface VersionEntry {
|
||||
"version": string;
|
||||
}
|
||||
|
||||
/** one module of a taken-in revision (design 47 G section 476, section 648) */
|
||||
export interface TakeInEntry {
|
||||
"commit": string;
|
||||
/** the module's build-input fingerprint at the commit */
|
||||
"fingerprint": string;
|
||||
"module": string;
|
||||
/** from a merged message; absent for a pushed one (design 47 G section 648) */
|
||||
"pull"?: number;
|
||||
/** from a merged message; absent for a pushed one (design 47 G section 648) */
|
||||
"repository"?: string;
|
||||
}
|
||||
|
||||
/** one pull request at one commit */
|
||||
export interface TakeInMember {
|
||||
"head": string;
|
||||
"number": number;
|
||||
"repository": string;
|
||||
}
|
||||
|
||||
/** one merge a take-in event holds: the repository, its pull request and the merge commit the forge made of it */
|
||||
export interface TakeInMerge {
|
||||
"merge": string;
|
||||
"number": number;
|
||||
"repository": string;
|
||||
}
|
||||
|
||||
/** the body of the indexer's take-in.pending: a wait for a revision or a group, with the watched branch (design 47 G section 493) */
|
||||
export interface TakeInPending {
|
||||
/** the watched branch the revision is of (design 47 G section 476, section 493) */
|
||||
"branch"?: string;
|
||||
/** who acted by hand, on a taken event */
|
||||
"by"?: string;
|
||||
/** a hand drop: the merges are not taken in */
|
||||
"dropped"?: boolean;
|
||||
/** the delivery group, for a group being gathered */
|
||||
"group"?: string;
|
||||
/** the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered */
|
||||
"key": string;
|
||||
/** the event's kind, pending or taken */
|
||||
"kind": string;
|
||||
/** the keys of merges whose message the forge bridge's stream no longer holds */
|
||||
"lost"?: string[];
|
||||
/** every approved member of a group with its head */
|
||||
"members"?: TakeInMember[];
|
||||
/** the merges it holds, oldest first */
|
||||
"merges": TakeInMerge[];
|
||||
/** why the indexer holds a merge it does not take in although no indexer build is named */
|
||||
"own-build-unknown"?: string;
|
||||
/** why the manifest of the indexer the revision names was refused */
|
||||
"refused"?: string;
|
||||
/** when the wait began */
|
||||
"since": string;
|
||||
/** the layout version */
|
||||
"v": number;
|
||||
/** the indexer build a revision waits for */
|
||||
"waits-for"?: string;
|
||||
/** what waits: a revision or a group */
|
||||
"what": string;
|
||||
/** why they acted by hand, on a taken event */
|
||||
"why"?: string;
|
||||
}
|
||||
|
||||
/** the body of the indexer's take-in.taken: a wait ended, with the branch, the source revision and the entries (design 47 G section 476, section 493) */
|
||||
export interface TakeInTaken {
|
||||
/** the watched branch the revision is of (design 47 G section 476, section 493) */
|
||||
"branch"?: string;
|
||||
/** who acted by hand, on a taken event */
|
||||
"by"?: string;
|
||||
/** a hand drop: the merges are not taken in */
|
||||
"dropped"?: boolean;
|
||||
/** one entry per module of that revision; none for a hand drop (design 47 G section 476, section 493) */
|
||||
"entries"?: TakeInEntry[];
|
||||
/** the delivery group, for a group being gathered */
|
||||
"group"?: string;
|
||||
/** the wait's key: m.<sequence> of its oldest merge, or g.<group> for a group being gathered */
|
||||
"key": string;
|
||||
/** the event's kind, pending or taken */
|
||||
"kind": string;
|
||||
/** the keys of merges whose message the forge bridge's stream no longer holds */
|
||||
"lost"?: string[];
|
||||
/** every approved member of a group with its head */
|
||||
"members"?: TakeInMember[];
|
||||
/** the merges it holds, oldest first */
|
||||
"merges": TakeInMerge[];
|
||||
/** why the indexer holds a merge it does not take in although no indexer build is named */
|
||||
"own-build-unknown"?: string;
|
||||
/** why the manifest of the indexer the revision names was refused */
|
||||
"refused"?: string;
|
||||
/** when the wait began */
|
||||
"since": string;
|
||||
/** the source revision taken in (design 47 G section 476) */
|
||||
"source"?: number;
|
||||
/** the layout version */
|
||||
"v": number;
|
||||
/** the indexer build a revision waits for */
|
||||
"waits-for"?: string;
|
||||
/** what waits: a revision or a group */
|
||||
"what": string;
|
||||
/** why they acted by hand, on a taken event */
|
||||
"why"?: string;
|
||||
}
|
||||
|
||||
/** is what modules takes. */
|
||||
export interface ModulesInput {
|
||||
}
|
||||
@@ -632,10 +734,12 @@ export interface RevisionInput {
|
||||
/** is what revision answers. */
|
||||
export interface RevisionAnswer {
|
||||
/** this page's entries whole, by module name */
|
||||
"entries": CatalogEntry[];
|
||||
"entries"?: CatalogEntry[];
|
||||
/** the module to ask after for the next page; absent on the last */
|
||||
"next"?: string;
|
||||
"revision": CatalogRevision;
|
||||
/** true for a watched branch with no revision yet: no revision, no entries and no error follow, and a reader holds the branch's view ready and empty (design 47 G section 530) */
|
||||
"none-yet"?: boolean;
|
||||
"revision"?: CatalogRevision;
|
||||
}
|
||||
|
||||
/** is what registrations takes. */
|
||||
|
||||
@@ -1870,7 +1870,7 @@ func serveCatalog(t *testing.T, b *bed, nc *nats.Conn) {
|
||||
b.cat.mu.Lock()
|
||||
defer b.cat.mu.Unlock()
|
||||
if b.cat.rev[branch] == 0 {
|
||||
_ = m.Respond([]byte(`{"error":"` + views.NoneYet + `"}`))
|
||||
_ = m.Respond([]byte(`{"result":{"none-yet":true},"node":"n0"}`))
|
||||
return
|
||||
}
|
||||
rev := meshcatalog.CatalogRevision{Kind: "trunk", Branch: in.Branch, Revision: b.cat.rev[branch]}
|
||||
@@ -1879,7 +1879,7 @@ func serveCatalog(t *testing.T, b *bed, nc *nats.Conn) {
|
||||
entries = append(entries, b.cat.entries[branch][name])
|
||||
rev.Entries = append(rev.Entries, meshcatalog.EntryRef{Module: name, Digest: "sha256:" + strings.Repeat("0", 64)})
|
||||
}
|
||||
raw, _ := json.Marshal(map[string]any{"result": meshcatalog.RevisionAnswer{Revision: rev, Entries: entries}, "node": "n0"})
|
||||
raw, _ := json.Marshal(map[string]any{"result": meshcatalog.RevisionAnswer{Revision: &rev, Entries: entries}, "node": "n0"})
|
||||
_ = m.Respond(raw)
|
||||
}
|
||||
for _, subject := range []string{"mesh.seat.mesh-catalog.tool.revision", "mesh.seat.mesh-catalog.call.revision.*", "mesh.seat.mesh-catalog.tool.revision.*"} {
|
||||
|
||||
@@ -540,7 +540,7 @@ func startBed(t *testing.T) *bed {
|
||||
for repo, commit := range trunkView.Load().(map[string]string) {
|
||||
heads = append(heads, meshcatalog.Head{Repository: repo, Commit: commit})
|
||||
}
|
||||
body, _ := json.Marshal(meshcatalog.RevisionAnswer{Revision: meshcatalog.CatalogRevision{Kind: "trunk", Revision: 1, Trunk: heads}})
|
||||
body, _ := json.Marshal(meshcatalog.RevisionAnswer{Revision: &meshcatalog.CatalogRevision{Kind: "trunk", Revision: 1, Trunk: heads}})
|
||||
reply, _ := json.Marshal(map[string]any{"result": json.RawMessage(body), "node": "ctl"})
|
||||
_ = m.Respond(reply)
|
||||
}); err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestTrunkHeadsComeFromTheCatalogHalf(t *testing.T) {
|
||||
if key != "seat:mesh-catalog.revision" {
|
||||
t.Fatalf("asked %s", key)
|
||||
}
|
||||
return json.Marshal(meshcatalog.RevisionAnswer{Revision: meshcatalog.CatalogRevision{Kind: kind, Trunk: heads}})
|
||||
return json.Marshal(meshcatalog.RevisionAnswer{Revision: &meshcatalog.CatalogRevision{Kind: kind, Trunk: heads}})
|
||||
}
|
||||
}
|
||||
trunks := &check.Trunks{}
|
||||
|
||||
@@ -17,6 +17,9 @@ func TrunkHeads(raw json.RawMessage) (map[string]string, error) {
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return nil, fmt.Errorf("the catalog half's revision does not read: %w", err)
|
||||
}
|
||||
if a.Revision == nil {
|
||||
return nil, fmt.Errorf("the catalog half's revision answers no revision (none-yet: %v)", a.NoneYet)
|
||||
}
|
||||
if a.Revision.Kind != "trunk" {
|
||||
return nil, fmt.Errorf("the catalog half's revision %d is a %q revision, not a trunk's", a.Revision.Revision, a.Revision.Kind)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func Revision(ctx context.Context, o Objects, holder, caller string, in meshcata
|
||||
}
|
||||
}
|
||||
slices.SortFunc(entries, func(a, b meshcatalog.CatalogEntry) int { return strings.Compare(a.Module, b.Module) })
|
||||
out.Revision, out.Entries = rev, []meshcatalog.CatalogEntry{}
|
||||
out.Revision, out.Entries = &rev, []meshcatalog.CatalogEntry{}
|
||||
size := 0
|
||||
for _, e := range entries {
|
||||
if e.Module <= in.After {
|
||||
|
||||
@@ -187,7 +187,7 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
// catalogAnswer is the seat mesh-catalog's newest trunk revision: a with a fingerprint and the source revision it moved at,
|
||||
// b with none and the reason.
|
||||
var catalogAnswer = meshcatalog.RevisionAnswer{
|
||||
Revision: meshcatalog.CatalogRevision{Revision: 4, Kind: meshcatalogclient.KindTrunk, Source: 7},
|
||||
Revision: &meshcatalog.CatalogRevision{Revision: 4, Kind: meshcatalogclient.KindTrunk, Source: 7},
|
||||
Entries: []meshcatalog.CatalogEntry{
|
||||
{Module: "a", Repository: "novox/r", Commit: "c1", Path: ".", Manifest: map[string]any{"module": "a"}, Fp: fp2A, Inputs: &meshcatalog.EntryInputs{Src: 7}},
|
||||
{Module: "b", Repository: "novox/r", Commit: "c2", Path: "modules/b", Manifest: map[string]any{"module": "b"}, None: "builder's fingerprint not yet known"},
|
||||
|
||||
@@ -77,7 +77,7 @@ func serveCatalog(t testing.TB, ctx context.Context, js jetstream.JetStream, nc
|
||||
}
|
||||
answer := func(m *nats.Msg) {
|
||||
h.mu.Lock()
|
||||
a := meshcatalog.RevisionAnswer{Revision: meshcatalog.CatalogRevision{Revision: h.rev, Kind: meshcatalogclient.KindTrunk}}
|
||||
a := meshcatalog.RevisionAnswer{Revision: &meshcatalog.CatalogRevision{Revision: h.rev, Kind: meshcatalogclient.KindTrunk}}
|
||||
for _, e := range h.entries {
|
||||
a.Entries = append(a.Entries, e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user