The indexer answers the seat mesh-catalog's verb revision in its own runtime, to exactly the parts that declare it; modules ends as a first view (milestone 47/G W4.4)
ADR 0663: the catalog half is read through revision, answered by the tools bundle from the catalog graph revisions before it asks anything, to the parts whose entry in the newest trunk revision declares seat:mesh-catalog.revision in invokes, the holder refused too; none kept, every reader waits. The first view modules, its callers list and its tool leave the manifest; the assigner declares revision instead (its code moves in W5.10); the grants part keeps its read of modules until W8. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -126,7 +126,8 @@ func TestManifest(t *testing.T) {
|
||||
if !slices.Equal(m.Consumes, []string{"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", buckets.TellerModule + "." + tellerevents.AskEnded, "node-engine.manifest-reads-changed"}) {
|
||||
t.Errorf("consumes = %v, want the indexer's views of graph, manifests and modules, the teller's ask-ended and the engines' reads changed", m.Consumes)
|
||||
}
|
||||
if want := []string{"mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules",
|
||||
if want := []string{"mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests",
|
||||
"seat:mesh-catalog.revision", // ADR 0663 §2: which modules exist
|
||||
"seat:mesh-catalog.seat-definitions", // ADR 0636 §4: the seat list
|
||||
"seat:mesh-hand-acts.hand-act", "seat:mesh-hand-acts.hand-acts", "seat:mesh-hand-acts.warranted", // ADR 0565 §9, §11: the seat alone
|
||||
"seat:node-backup.backed-up", "seat:node-backup.now",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"invokes": [
|
||||
"mesh-delivery-indexer.graph",
|
||||
"mesh-delivery-indexer.manifests",
|
||||
"mesh-delivery-indexer.modules",
|
||||
"seat:mesh-catalog.revision",
|
||||
"seat:mesh-catalog.seat-definitions",
|
||||
"seat:mesh-hand-acts.hand-act",
|
||||
"seat:mesh-hand-acts.hand-acts",
|
||||
|
||||
@@ -283,8 +283,12 @@ func main() {
|
||||
acts := meshhandactsclient.Recorder{Ask: stdio.Ask}
|
||||
tools := Tools(asks, time.Now, stdio.State(index.VerdictsState), heldCounts(stdio.State(index.OldSectionsState)), acts)
|
||||
// The modules and the module graph are the newest catalog graph revision's (milestone 47/G): revision.go.
|
||||
trunk := index.TrunkStates{Objects: bundleObjects{busstate.ObjectsOf(index.CatalogRevisionsObjects)}, Under: keptSource}
|
||||
revisions := bundleObjects{busstate.ObjectsOf(index.CatalogRevisionsObjects)}
|
||||
trunk := index.TrunkStates{Objects: revisions, Under: keptSource}
|
||||
tools = append(tools, catalogTools(trunk.State)...)
|
||||
// The seat mesh-catalog's verb revision, a first view of the catalog half answered in this runtime of its own to exactly
|
||||
// the parts whose entry declares it (novox/hq ADR 0663).
|
||||
tools = append(tools, index.RevisionTool(revisions, index.Module))
|
||||
tools = append(tools, viewTools(os.Getenv, trunk.State, time.Now)...)
|
||||
tools = append(tools, index.PendingTools(keptRead{stdio.State(index.PendingState)}, keptRead{stdio.State(index.OwnBuildStateName)}, asks, time.Now)...)
|
||||
tools = append(tools, index.FingerprintsTools(asks, time.Now, func(what, why string) error {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// RevisionInvoke is how a part declares the seat mesh-catalog's verb revision in its manifest's invokes, the one way a
|
||||
// part reads the catalog half (novox/hq ADR 0663 §2).
|
||||
const RevisionInvoke = "seat:" + meshcatalog.Seat + "." + meshcatalog.VerbRevision
|
||||
|
||||
// RevisionCallers are the parts the newest trunk revision's entries say declare the verb revision in their invokes,
|
||||
// sorted, the holder itself never among them (ADR 0663 §2: granted to exactly the parts that declare it).
|
||||
func RevisionCallers(entries []meshcatalog.CatalogEntry, holder string) stdio.Callers {
|
||||
var out stdio.Callers
|
||||
for _, e := range entries {
|
||||
if e.Module != holder && slices.Contains(e.Invokes, RevisionInvoke) {
|
||||
out = append(out, e.Module)
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Revision answers the seat mesh-catalog's verb revision from the indexer's own objects (ADR 0663 §1, §3): one catalog
|
||||
// graph revision, the trunk's newest when the input names none, a kept trunk revision by its number, or a candidate's by
|
||||
// its group and heads, with one page of its entries whole, by module name. The caller is the runtime's proof: it is
|
||||
// answered only when the newest trunk revision's entry of its module declares the verb in invokes, and the holder is
|
||||
// refused too. With no trunk revision kept yet nobody is answered and the reader waits (ErrNoRevision), never an empty
|
||||
// catalog.
|
||||
func Revision(ctx context.Context, o Objects, holder, caller string, in meshcatalog.RevisionInput) (meshcatalog.RevisionAnswer, error) {
|
||||
var out meshcatalog.RevisionAnswer
|
||||
newest, ok, err := newestTrunk(ctx, o)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("the catalog graph revisions cannot be read: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
return out, ErrNoRevision
|
||||
}
|
||||
_, trunkEntries, err := ReadRevision(ctx, o, RevisionObject(newest))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if err := views.Refusal(meshcatalog.Seat+"."+meshcatalog.VerbRevision, RevisionCallers(trunkEntries, holder), caller); err != nil {
|
||||
return out, fmt.Errorf("%w (novox/hq ADR 0663 §2: a part reads it by declaring %s in invokes)", err, RevisionInvoke)
|
||||
}
|
||||
name := RevisionObject(newest)
|
||||
switch {
|
||||
case in.Group != "" || in.Heads != "":
|
||||
if in.Group == "" || in.Heads == "" {
|
||||
return out, fmt.Errorf("a candidate's revision is named by its group and its heads, both")
|
||||
}
|
||||
name = RevisionObject(meshcatalog.CatalogRevision{Kind: "candidate", Group: in.Group, Heads: in.Heads})
|
||||
case in.Revision != 0 && in.Revision != newest.Revision:
|
||||
name = "trunk." + strconv.FormatInt(in.Revision, 10)
|
||||
}
|
||||
rev, entries := newest, trunkEntries
|
||||
if name != RevisionObject(newest) {
|
||||
if rev, entries, err = ReadRevision(ctx, o, name); err != nil {
|
||||
return out, err
|
||||
}
|
||||
if in.Revision != 0 && rev.Revision != in.Revision {
|
||||
return out, fmt.Errorf("the candidate %s.%s keeps its newest revision only, %d, not %d", in.Group, in.Heads, rev.Revision, in.Revision)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(entries, func(a, b meshcatalog.CatalogEntry) int { return strings.Compare(a.Module, b.Module) })
|
||||
out.Revision, out.Entries = rev, []meshcatalog.CatalogEntry{}
|
||||
size := 0
|
||||
for _, e := range entries {
|
||||
if e.Module <= in.After {
|
||||
continue
|
||||
}
|
||||
data, _ := json.Marshal(e)
|
||||
if len(out.Entries) > 0 && size+len(data) > views.PageBytes {
|
||||
out.Next = out.Entries[len(out.Entries)-1].Module
|
||||
break
|
||||
}
|
||||
size += len(data)
|
||||
out.Entries = append(out.Entries, e)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RevisionTool is the verb revision as the tool mesh-catalog.revision of the indexer's own runtime (ADR 0663 §1): the
|
||||
// tool runner describes it with the seat's definition, so it carries no description or input of its own.
|
||||
func RevisionTool(o Objects, holder string) stdio.Tool {
|
||||
return stdio.Tool{Name: meshcatalog.Seat + "." + meshcatalog.VerbRevision, Run: func(args map[string]any) (any, error) {
|
||||
var in meshcatalog.RevisionInput
|
||||
if raw, err := json.Marshal(args); err == nil {
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
}
|
||||
return Revision(context.Background(), o, holder, stdio.CallerOf(args), in)
|
||||
}}
|
||||
}
|
||||
@@ -549,6 +549,44 @@ func TestBasesAreComputedFirstAndNoFingerprintTakesTheBuilder(t *testing.T) { //
|
||||
if _, err := (TrunkStates{Objects: newMemObjects(0)}).State("graph").Keys(); !errors.Is(err, ErrNoRevision) {
|
||||
t.Errorf("the bundle's graph with no revision kept answered %v, want it to wait", err)
|
||||
}
|
||||
// W4.4, ADR 0663: the seat mesh-catalog's verb revision answers, from the indexer's own objects, exactly the parts whose
|
||||
// entry in the newest trunk revision declares it, the holder refused too; a candidate's by its group and heads; none
|
||||
// kept, everyone waits.
|
||||
verbObjects := newMemObjects(0)
|
||||
keep := func(rev meshcatalog.CatalogRevision, declares map[string]bool) {
|
||||
entries := map[string][]byte{}
|
||||
for _, name := range []string{"avahi", Module, "mesh-grants", "node-tools"} {
|
||||
e := meshcatalog.CatalogEntry{Module: name, Repository: catalogueRepo}
|
||||
if declares[name] {
|
||||
e.Invokes = []string{RevisionInvoke}
|
||||
}
|
||||
data, _ := json.Marshal(e)
|
||||
digest := fmt.Sprintf("%s-%d-%s", rev.Kind, rev.Revision, name)
|
||||
entries[digest] = data
|
||||
rev.Entries = append(rev.Entries, meshcatalog.EntryRef{Module: name, Digest: digest})
|
||||
}
|
||||
if err := KeepRevision(context.Background(), verbObjects, rev, entries); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := Revision(context.Background(), verbObjects, Module, "n1.node-tools", meshcatalog.RevisionInput{}); !errors.Is(err, ErrNoRevision) {
|
||||
t.Errorf("revision with none kept answered %v, want it to wait", err)
|
||||
}
|
||||
keep(meshcatalog.CatalogRevision{Revision: 3, Kind: "trunk"}, map[string]bool{"node-tools": true, Module: true})
|
||||
keep(meshcatalog.CatalogRevision{Revision: 4, Kind: "candidate", Group: "feat/x", Heads: "0123456789ab"}, map[string]bool{"node-tools": true, "mesh-grants": true})
|
||||
for caller, answered := range map[string]bool{"n1.node-tools": true, "n1.mesh-grants": false, "n1." + Module: false, "person.someone": false, "unproven": false} {
|
||||
got, err := Revision(context.Background(), verbObjects, Module, caller, meshcatalog.RevisionInput{})
|
||||
if (err == nil) != answered || (answered && (got.Revision.Revision != 3 || len(got.Entries) != 4 || got.Next != "")) {
|
||||
t.Errorf("revision to %s answered %+v (%v), want answered %v with trunk revision 3 whole", caller, got, err, answered)
|
||||
}
|
||||
}
|
||||
if got, err := Revision(context.Background(), verbObjects, Module, "n1.node-tools", meshcatalog.RevisionInput{Group: "feat/x", Heads: "0123456789ab", After: "mesh-grants"}); err != nil ||
|
||||
got.Revision.Kind != "candidate" || len(got.Entries) != 1 || got.Entries[0].Module != "node-tools" {
|
||||
t.Errorf("a candidate's revision after mesh-grants answered %+v (%v), want its one entry node-tools", got, err)
|
||||
}
|
||||
if _, err := Revision(context.Background(), verbObjects, Module, "n1.node-tools", meshcatalog.RevisionInput{Revision: 9}); err == nil {
|
||||
t.Error("a trunk revision not kept was answered")
|
||||
}
|
||||
// ADR 0614 §2, §3: the graph names what each module invokes and serves, and leaves out, over the whole graph, the
|
||||
// verb edges of two modules calling each other, from either side; a third calling one of them keeps its edge.
|
||||
invoking := func(name, invokes string) mod {
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
const MarksBucket = Module + "_" + views.MarksState
|
||||
|
||||
// FirstViewTools are the indexer's first-view tools (ADR 0621 §1, ADR 0622 §1, ADR 0631 §1): the tool, the state it answers, and
|
||||
// the words of what that state holds. Each answers only its callers list (CallersEnv), which the manifest writes.
|
||||
// the words of what that state holds. Each answers only its callers list (CallersEnv), which the manifest writes. The fourth
|
||||
// first view, of which modules exist, is the seat mesh-catalog's verb revision (ADR 0663 §1, RevisionTool), granted by
|
||||
// declaration, not by a list.
|
||||
var FirstViewTools = []ViewedTool{
|
||||
{Tool: "manifests", State: "manifests", Words: "every manifest the indexer keeps, by its digest."},
|
||||
{Tool: "modules", State: "modules", Words: "every module the indexer holds, its fingerprint, inputs and manifest digest."},
|
||||
{Tool: "verdicts", State: VerdictsState, Words: "the judge's verdict on each manifest: accepted, waiting or refused."},
|
||||
{Tool: "graph", State: graph.State, Words: "the module graph: each module's build, provision and verb edges, whose verb edges a want waits on."},
|
||||
}
|
||||
|
||||
@@ -175,7 +175,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
|
||||
// reads the verdicts state (indexer_verdicts) and asks the indexer to judge again through the takeins state
|
||||
// (indexer_judge; issue 774, clause 3). 1966/AC11: indexer_old_sections reads the count of the old sections (state oldsections).
|
||||
// ADR 0621 §1, ADR 0622 §1, ADR 0631 §1: the first-view tools manifests, modules, verdicts and graph, and design 25 § 12.2's read
|
||||
// ADR 0621 §1, ADR 0622 §1, ADR 0631 §1, ADR 0663 §1: the first-view tools manifests, verdicts and graph (modules is the seat
|
||||
// verb revision's, served by the claim), and design 25 § 12.2's read
|
||||
// tools, all of the tools bundle, which runs in a runtime of its own on the indexer's account (runs-as).
|
||||
want := []string{"indexer_take_in", "indexer_old_sections", "indexer_verdicts", "indexer_judge"}
|
||||
for _, v := range append(append([]index.ViewedTool{}, index.FirstViewTools...), index.ReadTools...) {
|
||||
@@ -189,12 +190,13 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
index.SeatDefinitionTool, index.SeatDefinitionsAgainTool, index.OutOfStepTool, // DLV-I19
|
||||
index.HoldsTool, index.EngineReadsTool, // ADR 0632 §2: the mesh-wide holds and the engines' reads they weigh
|
||||
index.PendingTool, index.TakeInPendingTool, index.DropPendingTool, index.TryCandidatesTool, index.DropTryCandidateTool, index.RecoverTriesTool, index.TakeInMergeTool, index.FingerprintsAgainTool) // issue 1966; 1585/T12, T13 (ADR 0633 §5)
|
||||
if !reflect.DeepEqual(m.Tools, want) || len(want) != 40 {
|
||||
if !reflect.DeepEqual(m.Tools, want) || len(want) != 40 || slices.Contains(m.Tools, "modules") {
|
||||
t.Errorf("tools = %v, want %v", m.Tools, want)
|
||||
}
|
||||
// Every read tool of the indexer a reader of this repository invokes is one it serves (the checker's seventh first
|
||||
// view, indexer_read_seat_definitions, was invoked and not served).
|
||||
readers, _ := filepath.Glob("../*/module.json")
|
||||
var declaring []string
|
||||
for _, p := range readers {
|
||||
raw, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
@@ -207,24 +209,36 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
t.Fatalf("%s: %v", p, err)
|
||||
}
|
||||
for _, inv := range r.Invokes {
|
||||
// The grants part reads modules until its own subtask (W8) moves it to revision; the join removes this line.
|
||||
if p == "../mesh-grants/module.json" && inv == "mesh-delivery-indexer.modules" {
|
||||
continue
|
||||
}
|
||||
if tool, ok := strings.CutPrefix(inv, "mesh-delivery-indexer."); ok && !slices.Contains(m.Tools, tool) {
|
||||
t.Errorf("%s invokes %s, which the indexer does not serve", p, inv)
|
||||
}
|
||||
if inv == index.RevisionInvoke {
|
||||
declaring = append(declaring, filepath.Base(filepath.Dir(p)))
|
||||
}
|
||||
}
|
||||
}
|
||||
// ADR 0663 §2: the assigner reads which modules exist by declaring the seat's verb revision (the grants part too, from W8).
|
||||
if !slices.Contains(declaring, "mesh-delivery-assigner") {
|
||||
t.Errorf("the parts of this repository declaring %s are %v, want the assigner", index.RevisionInvoke, declaring)
|
||||
}
|
||||
if m.RunsAs != m.Module || m.SecretsOwner != m.RunsAs {
|
||||
t.Errorf("runs-as = %q, secrets-owner %q: the tools bundle runs on the indexer's own account (ADR 0621 §1)", m.RunsAs, m.SecretsOwner)
|
||||
}
|
||||
// ADR 0621 §2, ADR 0622 §1, ADR 0631 §1: each first-view tool answers exactly its readers; the indexer is on none, and
|
||||
// graph answers the assigner alone.
|
||||
callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "modules": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants",
|
||||
// ADR 0663 §2: revision has no callers list; it answers the parts whose entry declares it.
|
||||
callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants",
|
||||
"graph": "mesh-delivery-assigner"}
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a.Name != "tools" {
|
||||
continue
|
||||
}
|
||||
if len(a.Env) != len(callers) {
|
||||
t.Errorf("tools env = %v, want only the four callers lists", a.Env)
|
||||
t.Errorf("tools env = %v, want only the three callers lists", a.Env)
|
||||
}
|
||||
for tool, list := range callers {
|
||||
if got := a.Env[views.CallersEnv(tool)]; got != list {
|
||||
@@ -254,10 +268,10 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
}
|
||||
}
|
||||
// ADR 0604 §2, ADR 0636 §4: it holds the seat mesh-catalog, serving its four verbs and seat-definitions from its tools
|
||||
// bundle (which stays without a request subject).
|
||||
// bundle (which stays without a request subject); ADR 0663 §1: and revision, in the same runtime of its own.
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != index.CatalogSeat || m.Claims[0].Scope != catalogue.ScopeMesh ||
|
||||
!slices.Equal(m.Claims[0].Serves, []string{"modules", "module", "provides", "dependents", index.SeatDefinitionsVerb}) {
|
||||
t.Errorf("claims = %+v, want mesh-catalog with its five verbs", m.Claims)
|
||||
!slices.Equal(m.Claims[0].Serves, []string{"modules", "module", "provides", "dependents", index.SeatDefinitionsVerb, "revision"}) {
|
||||
t.Errorf("claims = %+v, want mesh-catalog with its six verbs", m.Claims)
|
||||
}
|
||||
for _, e := range []string{index.SeatDefinitionChangedEvent, index.SeatDefinitionRemovedEvent} {
|
||||
if slices.Contains(m.Emits, e) {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"module",
|
||||
"provides",
|
||||
"dependents",
|
||||
"seat-definitions"
|
||||
"seat-definitions",
|
||||
"revision"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -22,7 +23,6 @@
|
||||
"indexer_verdicts",
|
||||
"indexer_judge",
|
||||
"manifests",
|
||||
"modules",
|
||||
"verdicts",
|
||||
"graph",
|
||||
"indexer_read_manifests",
|
||||
@@ -195,7 +195,6 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_VIEW_CALLERS_MANIFESTS": "mesh-grants,node-tools,mesh-delivery-assigner",
|
||||
"MESH_VIEW_CALLERS_MODULES": "mesh-grants,node-tools,mesh-delivery-assigner",
|
||||
"MESH_VIEW_CALLERS_VERDICTS": "mesh-grants",
|
||||
"MESH_VIEW_CALLERS_GRAPH": "mesh-delivery-assigner"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user