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:
2026-11-11 21:32:40 +01:00
parent 92d938dd8c
commit 33c6fc4f7f
15 changed files with 537 additions and 30 deletions
@@ -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)}},
})
}
+107 -3
View File
@@ -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",
+264 -6
View File
@@ -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
View File
@@ -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)
}