Merge pull request 'G start: the runner admits first-view seat verbs before the catalog half is read; the SDK declares the verbs it calls' (#195) from fix/47-G-start into main
Reviewed-on: #195
This commit was merged in pull request #195.
This commit is contained in:
@@ -2,10 +2,13 @@ package approval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/operatorask"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// call is a fixture call of the marked tool wipe.
|
||||
@@ -78,3 +81,53 @@ func TestTheActAndItsAsk(t *testing.T) {
|
||||
t.Errorf("headline %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// unread is a catalog half whose first view is not read yet.
|
||||
type unread struct{}
|
||||
|
||||
func (unread) Ready() error { return errors.New("the registrations are not answered") }
|
||||
func (unread) Revision() string { return "" }
|
||||
func (unread) Served(string) (Entry, bool, string) { return Entry{}, false, "not read" }
|
||||
func (unread) Newest(string) (Entry, bool) { return Entry{}, false }
|
||||
func (unread) Definitions() map[string]seatdef.Definition { return nil }
|
||||
func (unread) Entries() []Entry { return nil }
|
||||
|
||||
// Design 47 G lead answers A-353, A-359: while its catalog half is unread, the gate lets through the four seat verbs that
|
||||
// half is read by, told from the call's seat and verb, and holds every other call as before.
|
||||
func TestAnUnreadCatalogHalfLetsItsOwnReadsThrough(t *testing.T) {
|
||||
waited := 0
|
||||
g := New(Deps{Node: "n1", Runner: "node-tools", Catalog: unread{}, Wait: func(time.Duration) error {
|
||||
waited++
|
||||
return errors.New("still unread")
|
||||
}})
|
||||
for _, tc := range []struct {
|
||||
seat, name string
|
||||
runs bool
|
||||
}{
|
||||
{"mesh-catalog", "registrations", true},
|
||||
{"mesh-catalog", "revision", true},
|
||||
{"mesh-catalog", "entry", true},
|
||||
{"mesh-delivery", "source", true},
|
||||
{"mesh-catalog", "register", false},
|
||||
{"mesh-delivery", "registrations", false},
|
||||
{"", "registrations", false}, // a module's tool of that name is no seat verb
|
||||
{"", "revision", false},
|
||||
} {
|
||||
ran := false
|
||||
address := tc.seat + "." + tc.name
|
||||
if tc.seat == "" {
|
||||
address = "mesh-delivery-indexer." + tc.name
|
||||
}
|
||||
_, err := g.Run(Call{Address: address, Module: "mesh-delivery-indexer", Seat: tc.seat, Name: tc.name, Fingerprint: "fp1",
|
||||
Caller: "node-tools"}, func() (any, error) { ran = true; return "ok", nil })
|
||||
if ran != tc.runs || (err == nil) != tc.runs {
|
||||
t.Errorf("%s: ran %v (%v), want ran %v", address, ran, err, tc.runs)
|
||||
}
|
||||
if !tc.runs && (err == nil || !strings.Contains(err.Error(), "are not read yet")) {
|
||||
t.Errorf("%s: refused with %v, want the marks not read yet", address, err)
|
||||
}
|
||||
}
|
||||
if waited != 4 {
|
||||
t.Errorf("the other calls waited for the catalog half %d times, want 4 (the four start reads wait for nothing)", waited)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -3,6 +3,7 @@ package approval
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/operatorask"
|
||||
@@ -67,13 +68,27 @@ func (g *Gate) Run(c Call, run func() (any, error)) (any, error) {
|
||||
// Binding is a Store that says whether the runner's membership binds the state now (§13).
|
||||
type Binding interface{ Bound() bool }
|
||||
|
||||
// marksOf is the module's marks as the catalog half has them now; before its first view, the call waits for it within
|
||||
// RecordWithin and is then judged, refused naming mesh_first_views when it is still unread (§19).
|
||||
// startReads are the seat verbs the catalog half is read by, as `<seat>.<verb>`: while it is unread the gate lets them
|
||||
// through, so the runner that serves them answers the reads its own catalog half waits on (design 47 G lead answers
|
||||
// A-353, A-359).
|
||||
var startReads = []string{"mesh-catalog.registrations", "mesh-catalog.revision", "mesh-catalog.entry", "mesh-delivery.source"}
|
||||
|
||||
// startRead says the call is one of startReads, told from its seat and verb.
|
||||
func startRead(c Call) bool {
|
||||
return c.Seat != "" && slices.Contains(startReads, c.Seat+"."+c.Name)
|
||||
}
|
||||
|
||||
// marksOf is the module's marks as the catalog half has them now; before its first view, a call of startReads runs
|
||||
// unmarked (A-353, A-359) and any other waits for it within RecordWithin and is then judged, refused naming
|
||||
// mesh_first_views when it is still unread (§19).
|
||||
func (g *Gate) marksOf(c Call) (Marks, error) {
|
||||
if g.d.Catalog == nil {
|
||||
return Marks{}, g.refused(c, "", "the tool runner reads no catalog half, so its marks are not read")
|
||||
}
|
||||
if err := g.d.Catalog.Ready(); err != nil {
|
||||
if startRead(c) {
|
||||
return Marks{Module: c.Module}, nil
|
||||
}
|
||||
if g.d.Wait != nil {
|
||||
err = g.d.Wait(RecordWithin)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@ func TestOwnManifestUsesNoWordTheRunningControllerRefuses(t *testing.T) {
|
||||
if g := packages["go"]; g.Name != "go" || !slices.Equal(g.Paths, []string{"go"}) {
|
||||
t.Errorf("mesh-sdk's Go package is %+v, want go built from go", g)
|
||||
}
|
||||
// The seat verbs its code calls on a module's behalf, in the invokes form, so the indexer orders every module built
|
||||
// against it after the running versions that serve its start (design 47 G lead answer A-357).
|
||||
for _, verb := range []string{"seat:mesh-catalog.registrations", "seat:mesh-catalog.revision", "seat:mesh-catalog.entry", "seat:mesh-delivery.source"} {
|
||||
if !slices.Contains(packages["go"].Calls, verb) {
|
||||
t.Errorf("mesh-sdk's Go package calls %v, want %s among them", packages["go"].Calls, verb)
|
||||
}
|
||||
}
|
||||
// Its named paths, so the indexer holds them and a change to them is a changed package (design 47 G A-285).
|
||||
if l := packages["typescript"]; l.Name != "lib" || !slices.Equal(l.Paths, []string{"package.json", "package-lock.json", "tsconfig.json", "src"}) {
|
||||
t.Errorf("mesh-sdk's TypeScript package is %+v, want lib built from package.json, package-lock.json, tsconfig.json and src", l)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ func TestEveryBootstrappedDefinitionEqualsItsContractsFile(t *testing.T) {
|
||||
{"operator-channel", "mesh-contracts", "operator-and-conversation/seats/operator-channel/seat.json"},
|
||||
{"mesh-catalog", "mesh-delivery", "modules/mesh-delivery-contracts/seats/mesh-catalog/seat.json"},
|
||||
{"mesh-delivery-checker", "mesh-delivery", "modules/mesh-delivery-contracts/seats/mesh-delivery-checker/seat.json"},
|
||||
{"mesh-delivery", "mesh-delivery", "modules/mesh-delivery-contracts/seats/mesh-delivery/seat.json"},
|
||||
}
|
||||
var have []string
|
||||
for _, d := range Bootstrapped() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -182,6 +183,13 @@ func TestTheBootstrapIsHeldAndNothingNewIsActedOnUntilTheSeatAnswers(t *testing.
|
||||
if got := len(r.Definitions()); got != len(boot) {
|
||||
t.Fatalf("the reader holds %d definitions, the bootstrap has %d", got, len(boot))
|
||||
}
|
||||
// Design 47 G lead answers A-353, A-362: before the list is read the runner admits the seat verbs its catalog half is
|
||||
// read by, mesh-delivery's source beside mesh-catalog's registrations, revision and entry.
|
||||
for _, sv := range [][2]string{{"mesh-catalog", "registrations"}, {"mesh-catalog", "revision"}, {"mesh-catalog", "entry"}, {"mesh-delivery", "source"}} {
|
||||
if !slices.ContainsFunc(r.Definitions()[sv[0]].Verbs, func(v seatdef.Verb) bool { return v.Name == sv[1] }) {
|
||||
t.Errorf("with the seat list unread the reader holds no %s.%s", sv[0], sv[1])
|
||||
}
|
||||
}
|
||||
st := r.Status()
|
||||
if st.Heard || len(st.Waiting) != len(boot) || st.LastError == "" || st.Health() == "" {
|
||||
t.Fatalf("status %+v: want the seat not heard, every bootstrapped seat waiting, the error and the health said", st)
|
||||
|
||||
@@ -101,18 +101,15 @@
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"description": "One watched branch's source revision (design 47 *Watched branches and the catalog graph per branch*: source.<branch> in place of source.main): The source revision the gate holds: every repository it names, its trunk branch and that trunk's head as the gate merged or seeded it, and the group that moved it last. Read, never written: the node-engines read it to call a reverted build's catch-up tool once per repository, never the gate's state (design 47).",
|
||||
"description": "One watched branch's source revision (design 47 *Watched branches and the catalog graph per branch*: source.<branch> in place of source.main): The source revision the gate holds: every repository it names, its trunk branch and that trunk's head as the gate merged or seeded it, and the group that moved it last. Read, never written: the node-engines read it to call a reverted build's catch-up tool once per repository, never the gate's state (design 47). Given no branch it answers branches alone, the branch of each source key the gate keeps; given a branch, that branch's source revision (design 47 G section 690).",
|
||||
"input": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch, escaped as a key name is"
|
||||
"description": "the watched branch, escaped as a key name is; absent: the answer is the list of the branches the gate keeps a source revision of (design 47 G section 690)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"branch"
|
||||
]
|
||||
}
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
@@ -139,6 +136,13 @@
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch the source revision is of"
|
||||
},
|
||||
"branches": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "given no branch: the branch name of each source.<branch> key the gate keeps, which every reader of the catalog half follows before the first registrations (design 47 G section 614, section 690)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -304,6 +308,22 @@
|
||||
"type": "boolean",
|
||||
"description": "a merge made at the forge, with no Approve"
|
||||
},
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "for a forge-made record, the repository, <owner>/<name>"
|
||||
},
|
||||
"pull": {
|
||||
"type": "integer",
|
||||
"description": "for a forge-made record, the pull request's number"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string",
|
||||
"description": "for a forge-made record, the merge commit"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "for a forge-made record, the branch the merge moved"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@@ -429,6 +449,14 @@
|
||||
"type": "string",
|
||||
"description": "the repository"
|
||||
},
|
||||
"module": {
|
||||
"type": "string",
|
||||
"description": "the module the merge changed, from the delivery record"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch the merge moved"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string",
|
||||
"description": "the merge commit"
|
||||
@@ -453,6 +481,65 @@
|
||||
"why"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "approval-carried",
|
||||
"subject": "approval-carried.<repository>.<pull>",
|
||||
"keep": {
|
||||
"max-age-days": 90
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "an Approve given before G, carried as that pull request's approval step: said at G's switch for each merges key the old bridge had not performed (design 47 G 542)",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "the repository"
|
||||
},
|
||||
"pull": {
|
||||
"type": "integer",
|
||||
"description": "the pull request's number",
|
||||
"minimum": 1
|
||||
},
|
||||
"commit": {
|
||||
"type": "string",
|
||||
"description": "the head the Approve named"
|
||||
},
|
||||
"approver": {
|
||||
"type": "string",
|
||||
"description": "who approved: the operator"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when it was said, RFC 3339",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repository",
|
||||
"pull",
|
||||
"commit",
|
||||
"approver",
|
||||
"at"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pins-stated",
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "the gate states every pin it holds once its pinned events are said, so an indexer whose state pins was made later hears them all; kept as pinned is, on the seat's family (design 47 G 579, 652)",
|
||||
"properties": {
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when it was said, RFC 3339",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"at"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
@@ -992,9 +1079,24 @@
|
||||
"catalog": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
"type": "object",
|
||||
"description": "one catalog graph revision, named by its watched branch and its number on the seat mesh-catalog (revisions are numbered per branch)",
|
||||
"properties": {
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "the watched branch the revision belongs to"
|
||||
},
|
||||
"revision": {
|
||||
"type": "integer",
|
||||
"description": "its number on that branch"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"branch",
|
||||
"revision"
|
||||
]
|
||||
},
|
||||
"description": "catalog graph revisions, by their number on the seat mesh-catalog"
|
||||
"description": "catalog graph revisions, by watched branch and number on the seat mesh-catalog"
|
||||
},
|
||||
"assignment": {
|
||||
"type": "array",
|
||||
|
||||
+1190
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -5,7 +5,8 @@
|
||||
"build": {
|
||||
"artifacts": [
|
||||
{ "name": "lib", "kind": "package", "language": "typescript", "paths": ["package.json", "package-lock.json", "tsconfig.json", "src"], "calls": ["finding-calls"] },
|
||||
{ "name": "go", "kind": "package", "language": "go", "paths": ["go"], "calls": ["finding-calls", "ledger-upkeep-calls", "put-sessions", "sent-objects-calls"] }
|
||||
{ "name": "go", "kind": "package", "language": "go", "paths": ["go"], "calls": ["finding-calls", "ledger-upkeep-calls", "put-sessions", "sent-objects-calls",
|
||||
"seat:mesh-catalog.registrations", "seat:mesh-catalog.revision", "seat:mesh-catalog.entry", "seat:mesh-delivery.source"] }
|
||||
]
|
||||
},
|
||||
"resources": []
|
||||
|
||||
Reference in New Issue
Block a user