diff --git a/.gitignore b/.gitignore index 3f98af11..85631640 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ /redis-provisioner # And where `go build` in cmd/mesh-controller leaves it (novox/hq issue 233). /cmd/mesh-controller/mesh-controller + +# No repository commits its dependencies (issue 1305). +vendor/ diff --git a/Dockerfile b/Dockerfile index 2d6fb855..f4fe65b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,17 +21,15 @@ ARG GO_BASE=golang@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7 FROM ${GO_BASE} AS build WORKDIR /src -# **Nothing is fetched** (novox/hq to-be 45 Phase 1): every dependency is in vendor/, committed, so -# the image builds from this repository alone — the host's validator among them, whose module no -# public proxy is asked for. Dependencies first, so a change to the source does not re-copy them. +# **No dependency is committed** (issue 1305): go.sum names each one's version and checksum, and the build +# resolves them with -mod=mod. Dependencies first, so a change to the source does not re-copy them. COPY go.mod go.sum ./ -COPY vendor/ vendor/ # `COPY . .` brings `.git` along, so Go is told not to stamp the commit: the version is said by -X, and a # rebuild of an unchanged program is the same bytes (novox/hq ADR 0267 rule 6, issue 363). COPY . . ARG VERSION=development -RUN CGO_ENABLED=0 GOFLAGS=-mod=vendor GOPROXY=off go build -trimpath -buildvcs=false \ +RUN CGO_ENABLED=0 GOFLAGS=-mod=mod go build -trimpath -buildvcs=false \ -ldflags "-s -w -X main.version=${VERSION}" \ -o /mesh-controller ./cmd/mesh-controller diff --git a/cmd/mesh-controller/meshcli_reads_integration_test.go b/cmd/mesh-controller/meshcli_reads_integration_test.go index 5a76dfd2..6a33bfcc 100644 --- a/cmd/mesh-controller/meshcli_reads_integration_test.go +++ b/cmd/mesh-controller/meshcli_reads_integration_test.go @@ -10,11 +10,12 @@ import ( "github.com/novox/mesh-controller/internal/link" ) +// 1931/AC1: reading lines run with `doctor` in place of the removed `queue`. func TestAReadCallWritesNoJournalLine(t *testing.T) { said := journalled(t) ctx := link.WithCallID(context.Background(), "call-1") ordinary := cliVerdict{why: "not the terminal"} - for _, line := range [][]string{{"plans"}, {"status"}, {"conditions"}, {"node", "list"}, {"builds"}, {"queue"}, + for _, line := range [][]string{{"plans"}, {"status"}, {"conditions"}, {"node", "list"}, {"builds"}, {"doctor"}, {"settings", "show", "zsh"}, {"plans", "--json"}} { if a := runForMeshCLI(ctx, "g14", cliAsked("jochen", 1000, line...), ordinary); a.Exit != 0 || a.Refused != "" { t.Fatalf("%q did not run: %+v", line, a) diff --git a/cmd/mesh-controller/no_vendor_test.go b/cmd/mesh-controller/no_vendor_test.go new file mode 100644 index 00000000..d86025fd --- /dev/null +++ b/cmd/mesh-controller/no_vendor_test.go @@ -0,0 +1,27 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// 1305/AC1: the tree holds no vendor directory (root and modules/route-proxy), .gitignore names vendor/, and the root +// Dockerfile builds with -mod=mod. +func TestTheTreeCommitsNoDependencies(t *testing.T) { + root := filepath.Join("..", "..") + for _, d := range []string{"vendor", filepath.Join("modules", "route-proxy", "vendor")} { + if _, err := os.Stat(filepath.Join(root, d)); err == nil { + t.Errorf("%s is committed", d) + } + } + ignore, err := os.ReadFile(filepath.Join(root, ".gitignore")) + if err != nil || !strings.Contains(string(ignore), "\nvendor/\n") { + t.Errorf(".gitignore does not name vendor/: %v", err) + } + docker, err := os.ReadFile(filepath.Join(root, "Dockerfile")) + if err != nil || strings.Contains(string(docker), "-mod=vendor") || !strings.Contains(string(docker), "-mod=mod") { + t.Errorf("the Dockerfile does not build with -mod=mod: %v", err) + } +} diff --git a/cmd/mesh-controller/release_plan.go b/cmd/mesh-controller/release_plan.go index fa254d87..9ed917a6 100644 --- a/cmd/mesh-controller/release_plan.go +++ b/cmd/mesh-controller/release_plan.go @@ -328,8 +328,8 @@ func applied(module string, builtAt time.Time, running []string, reports []inven // askTier asks the build machine for the named modules of the plan's tier — all of it, or what of it is still // unasked (novox/hq issue 754) — and marks each asked. A module -// the catalogue no longer holds, or whose ask could not be made, is a failure of the plan: a tier -// half asked is a tier that will never complete. +// the catalogue no longer holds ends as deleted (issue 1922); one whose ask could not be made is a failure of the +// plan: a tier half asked is a tier that will never complete. func askTier(ctx context.Context, inv *inventory.Inventory, p *inventory.Plan, names []string) error { entries, err := inv.Catalogued(ctx) if err != nil { @@ -390,10 +390,10 @@ func askModule(ctx context.Context, p *inventory.Plan, name string, byName map[s } e, known := byName[name] if !known { - state.State = "failed" + // **A module the catalogue no longer holds ends as deleted** (issue 1922): the plan's own merge removed it, so + // there is nothing to build or send; it is no failure, and a retry of a plan failed that way resumes. + state.State = planDeleted state.Why = "no longer in the catalogue" - p.State = inventory.PlanFailed - p.Note = name + " is no longer in the catalogue" return } // **Every module is taken from the builder's done key, asking no build** (novox/hq ADR 0503 §6, ADR 0501, ADR 0453 §5; @@ -1647,7 +1647,7 @@ func sayUnsent(p *inventory.Plan, rollsOut func(string) bool) { return } sort.Strings(unsent) - p.Note += "; built and never sent: " + strings.Join(unsent, ", ") + " — `push --behind` sends them" + p.Note += "; built and never sent: " + strings.Join(unsent, ", ") + " — `upgrade release-backlog --why ` releases them" } // planLine is one plan as `status` says it, given the least bound of a tier. diff --git a/cmd/mesh-controller/removed_module_test.go b/cmd/mesh-controller/removed_module_test.go new file mode 100644 index 00000000..298949d8 --- /dev/null +++ b/cmd/mesh-controller/removed_module_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "strings" + "testing" + + "github.com/novox/mesh-controller/internal/inventory" +) + +// 1922/AC1: a tier naming a module the catalogue no longer holds asks it without failing the plan: the module ends +// as deleted (not built, not sent) and the tier's other modules go on. +func TestAModuleTheCatalogueNoLongerHoldsEndsDeletedAndTheTierGoesOn(t *testing.T) { + p, byName := aWalkOfOne("sudo") + p.Tiers = [][]string{{"gone", "sudo"}} + for _, m := range p.Tiers[0] { + askModule(t.Context(), p, m, byName) + } + if g := p.Modules["gone"]; g == nil || g.State != planDeleted || g.Build != "" || g.SentAt != nil { + t.Fatalf("the removed module is not deleted: %+v", g) + } + if p.State == inventory.PlanFailed { + t.Fatalf("the plan failed: %q", p.Note) + } + if s := p.Modules["sudo"]; s == nil || s.State != "asked" { + t.Fatalf("the other module of the tier did not go on: %+v", s) + } +} + +// 1922/AC2: a plan that failed on such a module before the fix is retried: the removed module ends deleted and the +// plan is building again, to go on to its next tier. +func TestARetryOfAPlanFailedOnARemovedModuleResumes(t *testing.T) { + p := inventory.Plan{ID: "plan-1", State: inventory.PlanFailed, Note: "gone is no longer in the catalogue", + Tiers: [][]string{{"gone"}, {"next"}}, + Modules: map[string]*inventory.PlanModule{ + "gone": {State: "failed", Why: "no longer in the catalogue"}, + }} + failed := toRetry(&p, nil, nil) + if len(failed) != 1 || failed[0] != "gone" { + t.Fatalf("the failed set is %v", failed) + } + for _, m := range failed { + askModule(t.Context(), &p, m, map[string]inventory.Entry{}) + } + resumed(&p, "retried") + if p.State != inventory.PlanBuilding || p.Modules["gone"].State != planDeleted { + t.Fatalf("the retry did not resume: %s %+v", p.State, p.Modules["gone"]) + } +} + +// 1922/AC3: an ended plan's note names the verb that releases the modules waiting for a gate, not `push --behind`. +func TestAnEndedPlansHintNamesTheReleaseOfTheBacklog(t *testing.T) { + p := inventory.Plan{State: inventory.PlanFailed, Note: "failed", + Modules: map[string]*inventory.PlanModule{"agent": {State: "built"}}} + sayUnsent(&p, func(string) bool { return true }) + if strings.Contains(p.Note, "push --behind") || !strings.Contains(p.Note, "upgrade release-backlog") { + t.Fatalf("the note reads %q", p.Note) + } +} diff --git a/cmd/mesh-controller/rollout_first_test.go b/cmd/mesh-controller/rollout_first_test.go index 644f4be0..02996756 100644 --- a/cmd/mesh-controller/rollout_first_test.go +++ b/cmd/mesh-controller/rollout_first_test.go @@ -146,7 +146,7 @@ func TestAnEndedPlanSaysWhatItBuiltAndNeverSent(t *testing.T) { rollsOut := func(m string) bool { return m != "notes" } sayUnsent(&p, rollsOut) sayUnsent(&p, rollsOut) - if p.Note != "closed by hand at tier 1; built and never sent: agent — `push --behind` sends them" { + if p.Note != "closed by hand at tier 1; built and never sent: agent — `upgrade release-backlog --why ` releases them" { t.Fatalf("the note reads %q", p.Note) } } diff --git a/cmd/mesh-controller/seatverbs_schema_integration_test.go b/cmd/mesh-controller/seatverbs_schema_integration_test.go index 10ac2c9f..c66a5ec9 100644 --- a/cmd/mesh-controller/seatverbs_schema_integration_test.go +++ b/cmd/mesh-controller/seatverbs_schema_integration_test.go @@ -67,6 +67,7 @@ func TestNoArgumentAVerbIsGivenIsPassedOver(t *testing.T) { } } +// 1931/AC1: the removed verbs `queue`, `clear` and `replay` are not named; existing reading verbs stand in. func TestAnArgumentAVerbDoesNotDeclareIsRefused(t *testing.T) { for name, v := range servedSchemas(t) { names, _ := declaredArguments(v) @@ -86,13 +87,13 @@ func TestAnArgumentAVerbDoesNotDeclareIsRefused(t *testing.T) { } } } - if _, err := argvFor("clear", map[string]any{"dead": "yes"}); err == nil { + if _, err := argvFor("doctor", map[string]any{"run": "yes"}); err == nil { t.Error("a switch took a word that is neither true nor false, and would have read it as false") } if _, err := argvFor("node", map[string]any{"node": 7}); err == nil { t.Error("a number was taken as a machine's name, or as no machine") } - if argv, err := argvFor("clear", map[string]any{"dead": true}); err != nil || strings.Join(argv, " ") != "clear --dead" { + if argv, err := argvFor("doctor", map[string]any{"run": true}); err != nil || strings.Join(argv, " ") != "doctor run --json" { t.Errorf("a switch given as JSON true: %v %v", argv, err) } } diff --git a/cmd/mesh-controller/seatverbs_schema_test.go b/cmd/mesh-controller/seatverbs_schema_test.go index 0aed1a96..c34ff0f5 100644 --- a/cmd/mesh-controller/seatverbs_schema_test.go +++ b/cmd/mesh-controller/seatverbs_schema_test.go @@ -170,15 +170,12 @@ func stringLit(e ast.Expr) (string, bool) { var accountedFlags = map[string]map[string]string{ "status": {"json": "set by the verb: the answer is data"}, "seats": {"json": "set by the verb: the answer is data"}, - "queue": {"json": "set by the verb: the answer is data, its output the table (novox/hq issue 455)"}, "plan": {"json": "set by the verb unless files is asked"}, "push": { "wait": "set by the verb to 0: a tool call cannot hold a connection for a whole apply", "all": "withheld: the verb naming no machine is --behind, and a push of every machine is said at a " + "shell (novox/hq ADR 0217); `command` reaches it", }, - // Kept only to be refused by name, naming what replaced it (novox/hq ADR 0501 §5, ADR 0429 §18). - "replay": {"register": "withheld: refused by name, a registered replay being removed (ADR 0501 §5)"}, "builds": {"n": "=limit"}, "plans": {"n": "=limit", "what-if": "=repository"}, // The machine's tunnel key, named as the verb's other arguments are (novox/hq ADR 0169). diff --git a/go.mod b/go.mod index 699f18e1..f8f3f062 100644 --- a/go.mod +++ b/go.mod @@ -31,8 +31,6 @@ require ( // The node-engine's own validator (mesh-host/validate, novox/hq to-be 45 D1): one validator, the host's. // The host's module path names no forge a build can fetch from, so the module is read from the one -// that holds it, at the host's commit — **once, by whoever moves the pin, into vendor/**, which is -// committed. Every build (the build agent's `go build`, the Dockerfile) compiles from vendor/ and -// fetches nothing; go refuses to build when vendor/ and this file disagree, so a pin moved without -// `go mod vendor` fails loudly, at once, everywhere. +// that holds it, at the host's commit. No repository commits its dependencies (issue 1305): every build +// resolves them by this file and go.sum. replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5 diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/README.md b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/README.md deleted file mode 100644 index 58dcfa4e..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# mesh-sdk for Go - -`stdio.Serve("", tools)` speaks MCP over stdio to the node's tool runtime, as the module the runtime -names (novox/hq ADR 0188, ADR 0193). A `Tool` is a name, a description, an argument schema and a -function; calls run concurrently. `stdio.CallerOf(args)` inside a tool is who called it: the bus user the runtime took from the subject the call arrived on, named in the call's `_meta`, never read from the arguments — or `stdio.Unproven` when the runtime named none (novox/hq issue 365). A tool named `.` is the seat's implementation. -`stdio.Emit(type, body)` publishes an event as the module, `stdio.Subscribe(pattern, handler)` receives the module's events (taken when the handler returns nil, offered again when it returns an error — the one rule is in the repository README, "Taking an event") and `stdio.Ask(key, body)` calls a tool, and `stdio.State(name)` gets, puts, deletes, lists, watches and reads the kept history of the module's declared state, and `stdio.Log(name)` appends to, reads, and measures a log of operations the module declared — all through the runtime (novox/hq ADR 0193, 0198, 0201, 0297, 0307). `stdio.Config(key)` and `stdio.Bound(provision, key)` read, and `stdio.WatchConfig` and `stdio.WatchBound` watch, the module's own config values and the non-secret values of what it is bound to, for what its manifest declares: the runtime asks its machine's provisioner client as the module it launched, never a module the code names. `stdio.Secret(name)` is refused while the module's code runs in the tool runner; its secrets reach it through its files (novox/hq ADR 0349 §14, ADR 0370). `provider` is a providing module's half of a binding (novox/hq ADR 0349 §12, issue 678): it sees each consumer that binds the provider in the plans of the assignments mirror, writes one entry per consumer to `_bound` (`..[.]`: served values composed with the consumer's identity, the login, and the name of the vault's credential, never a secret), hands the provider's adapter the vault's value (`provision//` shared, `pair////[/]` per consumer), refuses a derived value to a consumer bound under a local name, naming both ends, and removes the entry of one that unbinds; the bus and the vault are interfaces the provider wires, the vault as `provider.ConsumersFile`, the file its machine's provisioner client fills from `${consumers:}` with each bound consumer's credential (novox/hq ADR 0373), since a provider in the tool runner never asks the vault itself. Standard library only. - -## Installing - -``` -GOPRIVATE=git.novox.be go get git.novox.be/novox/mesh-sdk/go@latest -``` - -Releases are tagged `go/vX.Y.Z` in this repository, the subdirectory form Go's module system expects, -at the same version as the TypeScript package. - -What it holds, under ADR 0039's test: the stdio loop and the tool type, and `cli`, the local protocol -`mesh-cli` speaks to the node-engine on its machine (novox/hq ADR 0272): the socket's path and one request and one -answer; and `seal`, the mesh's sealed box (novox/hq ADR 0206: X25519, HKDF-SHA256, AES-256-GCM, `{v, eph, iv, tag, ct}`), byte for byte the box the licence manager made before it lived here and the TypeScript `@novox/mesh-sdk/seal` makes. Nothing else — no bus, no client, nothing of any module's. `go build ./example` and pipe a `tools/list` request into it. - -From release 4a of design 47 (novox/hq ADR 0453 clause 3, ADR 0446 clause 4) it also holds the composition of a machine's declaration, over a plain input and with nothing behind it: `declaration` and `validate`, the node-engine's parser and its validator, copied from mesh-host; `catalogue`, the manifests, their resolution and rendering, and `overlay`, the private network's generator, copied from mesh-controller; `compose`, the controller's planFor, renderingFor and declarationWith as functions of an `Input` (the composition never allocates, makes, keeps or writes: what a send would do it names as foreseen or returns); `sdkversion`, which mesh-sdk version a program was built with; and, in `delivery`, the layouts of the keys the builder, the checker and the indexer write (the source fingerprint's new form, the builds, the verdicts and kept verdicts, the manifests by digest). None of them holds a bus, a store or a vault client, which `go list -deps` of `compose` shows. diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/announce/announce.go b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/announce/announce.go deleted file mode 100644 index 37dfd21d..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/announce/announce.go +++ /dev/null @@ -1,126 +0,0 @@ -// Package announce is how a module says on the bus which states it keeps and which of them others may read (novox/hq -// ADR 0495 clauses 2 and 3; issue 1790): the subject it says it on, the value it says, and where the controller keeps -// the last one said by each module. What a module announced is the truth of what it keeps; a reader is granted a read -// from it, never from a list or from the owner's manifest. -// -// A module announces from its code (process.Conn.Keeps, stdio.Keeps, the TypeScript SDK's keeps), never from its -// manifest, which only the core reads (clause 1). A module that keeps no state on the bus announces nothing. -package announce - -import ( - "encoding/json" - "fmt" - "regexp" - "sort" -) - -// Version is the version of the value this package writes. -const Version = 1 - -// Event is the event type of an announcement, under the module's own namespace. -const Event = "states" - -// Subject is where module announces what it keeps: its own event subject, mesh.mod..event.states, which its -// account may already publish to and the events stream keeps. -func Subject(module string) string { return "mesh.mod." + module + ".event." + Event } - -// SubjectAll is every module's announcement, what the controller consumes. -const SubjectAll = "mesh.mod.*.event." + Event - -// KeptBucket is the controller's bucket of the last announcement of each module, by Key. -const KeptBucket = "mesh-controller_states-announced" - -var name = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) - -// Key is module's key in KeptBucket: the module itself, which must be one plain name, since a module's name is part of -// its buckets' names (catalogue.StateProblems). -func Key(module string) (string, error) { - if !name.MatchString(module) { - return "", fmt.Errorf("announce: %q is not a module that keeps state: lower-case letters, digits and hyphens", module) - } - return module, nil -} - -// State is one state a module keeps, by its local name, and whether others may read it. Others never write it: a -// state another module writes is the owner's own code's business. -type State struct { - Name string `json:"name"` - Readable bool `json:"readable,omitempty"` -} - -// States is an announcement: the module, the machine whose instance said it, and every state the module keeps, sorted -// by name. An empty list says the module keeps nothing. -type States struct { - V int `json:"v"` - Module string `json:"module"` - Node string `json:"node,omitempty"` - States []State `json:"states"` -} - -// Keeps says whether the module announced keeping the state. -func (s States) Keeps(state string) bool { - for _, k := range s.States { - if k.Name == state { - return true - } - } - return false -} - -// Readable says whether the module announced the state readable by others. -func (s States) Readable(state string) bool { - for _, k := range s.States { - if k.Name == state { - return k.Readable - } - } - return false -} - -func (s *States) check() error { - if s.Module == "" { - return fmt.Errorf("announce: an announcement names no module") - } - if _, err := Key(s.Module); err != nil { - return err - } - seen := map[string]bool{} - for _, k := range s.States { - if !name.MatchString(k.Name) { - return fmt.Errorf("announce: %s announces the state %q, and a state is named locally: lower-case letters, digits and hyphens", s.Module, k.Name) - } - if seen[k.Name] { - return fmt.Errorf("announce: %s announces the state %q twice", s.Module, k.Name) - } - seen[k.Name] = true - } - return nil -} - -// Encode is s as JSON at Version, its states sorted; it refuses a module or a state no bucket could be named by and a -// state announced twice, naming it. -func Encode(s States) ([]byte, error) { - if err := s.check(); err != nil { - return nil, err - } - s.V = Version - s.States = append([]State{}, s.States...) - sort.Slice(s.States, func(i, j int) bool { return s.States[i].Name < s.States[j].Name }) - return json.Marshal(s) -} - -// Decode reads an announcement; it refuses another version and what Encode refuses. -func Decode(data []byte) (States, error) { - var s States - if err := json.Unmarshal(data, &s); err != nil { - return States{}, fmt.Errorf("announce: the announcement is not JSON: %w", err) - } - if s.V != Version { - return States{}, fmt.Errorf("announce: the announcement is version %d, and this reads version %d", s.V, Version) - } - if err := s.check(); err != nil { - return States{}, err - } - sort.Slice(s.States, func(i, j int) bool { return s.States[i].Name < s.States[j].Name }) - return s, nil -} diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/caller.go b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/caller.go deleted file mode 100644 index f9a108ac..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/caller.go +++ /dev/null @@ -1,59 +0,0 @@ -package stdio - -import ( - "reflect" - "regexp" - "sync" - "unsafe" -) - -// Unproven is the caller of a call the runtime named no caller for: one that arrived on the subject that names -// none, or from a runtime that names none (novox/hq issue 365). Said as a word, never left empty, so a record -// shows it was not proven. -const Unproven = "unproven" - -// MetaCaller is where in a `tools/call`'s `_meta` the runtime names the caller: the bus user that published the -// call, which the runtime takes from the subject the call arrived on — that subject's last token, which the -// bus lets each user publish with its own name alone (novox/hq issue 365, by ADR 0259 §3's precedent). The -// runtime writes `_meta`; a caller writes only `arguments`, so no caller can name another. -const MetaCaller = "mesh/caller" - -// busUser is the shape of a bus user's name: `.`, `person.`, `controller` — names whose -// parts are safe in a subject. Anything else the runtime named is not a caller and reads as Unproven. -var busUser = regexp.MustCompile(`^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$`) - -// calls holds the caller of every call while its tool runs, by the arguments map that call was handed. -var calls sync.Map // unsafe.Pointer → string - -func argsKey(args map[string]any) unsafe.Pointer { - return reflect.ValueOf(args).UnsafePointer() -} - -// CallerOf is who called the tool that was handed args, as the runtime proved it: the bus user that -// published the call, or Unproven. Read from the runtime, never from the arguments' contents — a `caller` -// argument names nobody. Valid while the tool runs; arguments no call was handed have no caller. -func CallerOf(args map[string]any) string { - if args == nil { - return Unproven - } - if c, ok := calls.Load(argsKey(args)); ok { - return c.(string) - } - return Unproven -} - -// callerIn is the caller a `tools/call`'s `_meta` names, or Unproven. -func callerIn(meta map[string]any) string { - if s, ok := meta[MetaCaller].(string); ok && busUser.MatchString(s) { - return s - } - return Unproven -} - -// runAs runs a tool with the caller held for its arguments for as long as it runs. -func runAs(t Tool, caller string, args map[string]any) (any, error) { - key := argsKey(args) - calls.Store(key, caller) - defer calls.Delete(key) - return t.Run(args) -} diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/keeps.go b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/keeps.go deleted file mode 100644 index cffdf9c7..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/keeps.go +++ /dev/null @@ -1,80 +0,0 @@ -package stdio - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "sync" - - "git.novox.be/novox/mesh-sdk/go/announce" -) - -// What this bundle said it keeps (novox/hq ADR 0495 clause 2; issue 1790). -var ( - keepsMu sync.Mutex - kept map[string]bool // nil until Keeps: no state is refused then - announced []byte // the announcement, published once the bundle serves -) - -// Keeps says on the bus, as this bundle's module, every state it keeps and which of them others may read (novox/hq -// ADR 0495 clause 2): what a reader is granted is what the owner announced (clause 3). Call it before Serve; the -// announcement is published through the runtime once the bundle serves, or at once when it already does. From then -// on a state of the module's own that it did not announce is refused at the call, naming it, so code and announcement -// cannot drift (clause 5). It refuses a state no bucket could be named by and a state given twice. -func Keeps(states ...announce.State) error { - module := os.Getenv("MESH_MODULE") - if module == "" { - module = os.Getenv(ServedAs) - } - data, err := announce.Encode(announce.States{Module: module, Node: os.Getenv("MESH_NODE"), States: states}) - if err != nil { - return err - } - names := map[string]bool{} - for _, s := range states { - names[s.Name] = true - } - keepsMu.Lock() - kept, announced = names, data - keepsMu.Unlock() - currentMu.Lock() - serving := current != nil - currentMu.Unlock() - if serving { - return announce1() - } - return nil -} - -// announce1 publishes the announcement Keeps made, if any. -func announce1() error { - keepsMu.Lock() - data := announced - keepsMu.Unlock() - if data == nil { - return nil - } - return Emit(announce.Event, json.RawMessage(data)) -} - -// refusedUnannounced is the error of a call on a state of the module's own that Keeps did not name, or nil. A read of -// another module's state (`.`) is the grant's business. -func refusedUnannounced(name string) error { - if strings.Contains(name, ".") { - return nil - } - keepsMu.Lock() - defer keepsMu.Unlock() - if kept != nil && !kept[name] { - return fmt.Errorf("the state %q is not among the states this module announced it keeps (stdio.Keeps)", name) - } - return nil -} - -// forgetKeeps is for a test: nothing announced. -func forgetKeeps() { - keepsMu.Lock() - kept, announced = nil, nil - keepsMu.Unlock() -} diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/log.go b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/log.go deleted file mode 100644 index 7b1030d8..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/log.go +++ /dev/null @@ -1,123 +0,0 @@ -package stdio - -import ( - "encoding/json" - "fmt" - "time" -) - -// Log — a module's record of operations on the bus, kept as long as the log is (novox/hq ADR 0297). Not the -// current value: that is state, and a log's state is its fold. Each entry is one operation on one key, its -// sequence in the log is its identity, and the order of the log is the order of the operations. A module -// declares each log it owns in its manifest (`logs`) and names it here as it named it there; a log is its -// owner's alone, and the runtime refuses, with the reason, one it did not issue. - -// LogEntry is one operation on one key, as the log holds it. -type LogEntry struct { - Sequence uint64 `json:"sequence"` - Key string `json:"key"` - Value json.RawMessage `json:"value"` - At time.Time `json:"at"` -} - -// LogPage is one page of a read: its entries, in order, the sequence to read on from (0 when nothing -// follows) and the log's last sequence when it was read. -type LogPage struct { - Entries []LogEntry `json:"entries"` - Next uint64 `json:"next"` - Last uint64 `json:"last"` -} - -// LogInfo is how much a log holds: its entries and bytes, its cap in bytes, and its first and last sequence. -type LogInfo struct { - Entries uint64 `json:"entries"` - Bytes uint64 `json:"bytes"` - MaxBytes uint64 `json:"max_bytes"` - First uint64 `json:"first"` - Last uint64 `json:"last"` -} - -// KeptLog is one log this module owns. -type KeptLog struct{ name string } - -// Log is a log this module owns, by its local name. -func Log(name string) KeptLog { return KeptLog{name: name} } - -const logTimeout = 30 * time.Second - -func (l KeptLog) ask(verb string, params map[string]any) (json.RawMessage, error) { - c, err := live() - if err != nil { - return nil, err - } - params["log"] = l.name - return c.ask("mesh/log."+verb, params, logTimeout) -} - -func (l KeptLog) append(params map[string]any) (uint64, error) { - raw, err := l.ask("append", params) - if err != nil { - return 0, err - } - var appended struct { - Sequence uint64 `json:"sequence"` - } - return appended.Sequence, json.Unmarshal(raw, &appended) -} - -// Append writes an entry for key, whatever came before it, and returns its sequence. A key is one or more -// names of letters, digits, `-` and `_`, joined by `.`; a value is JSON of at most 256 KiB. A full log -// refuses the entry: it never drops an older one to make room. -func (l KeptLog) Append(key string, value any) (uint64, error) { - return l.append(map[string]any{"key": key, "value": value}) -} - -// AppendAt writes an entry for key only if the key's last entry is still at expect — the sequence the caller -// read, 0 for a key with no entry yet — and returns its sequence. When another entry for the key came in -// between, nothing is written and the error is ErrChanged, as for a lost State update: read again and -// decide again. -func (l KeptLog) AppendAt(key string, value any, expect uint64) (uint64, error) { - return l.append(map[string]any{"key": key, "value": value, "expect": expect}) -} - -// Read is the entries from the sequence from on, oldest first, at most limit of them (0 is the runtime's -// page, 100; it gives at most 1000). keys narrows it: a key, with `*` for one name and `>` for the rest; -// empty is every key. Read on from the page's Next until it is 0. -func (l KeptLog) Read(from uint64, keys string, limit int) (*LogPage, error) { - if limit < 0 { - return nil, fmt.Errorf("a read of %s asks for at least no entries: limit %d", l.name, limit) - } - params := map[string]any{"from": from} - if keys != "" { - params["keys"] = keys - } - if limit > 0 { - params["limit"] = limit - } - raw, err := l.ask("read", params) - if err != nil { - return nil, err - } - var p LogPage - return &p, json.Unmarshal(raw, &p) -} - -// Last is a key's last entry, or nil when the key has none. -func (l KeptLog) Last(key string) (*LogEntry, error) { - raw, err := l.ask("last", map[string]any{"key": key}) - if err != nil || len(raw) == 0 || string(raw) == "null" { - return nil, err - } - var e LogEntry - return &e, json.Unmarshal(raw, &e) -} - -// Info is how much the log holds and how much it may. -func (l KeptLog) Info() (*LogInfo, error) { - raw, err := l.ask("info", map[string]any{}) - if err != nil { - return nil, err - } - var i LogInfo - return &i, json.Unmarshal(raw, &i) -} diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/membership/membership.go b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/membership/membership.go deleted file mode 100644 index c942ccc8..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/membership/membership.go +++ /dev/null @@ -1,165 +0,0 @@ -// Package membership is what the mesh issues an assignment to serve and to reach (novox/hq ADR 0160), as a -// process reads it from the bus: the Membership shape, the one subject it is published on, the stream that -// holds it, and the bus address helpers a reader needs. The controller composes it and the route proxy reads -// it; both use this one definition, so there is no second copy to drift (novox/hq issue 1633). -package membership - -import ( - "encoding/json" - "strings" -) - -// AssignmentsStream holds every assignment's membership, the newest per subject. -const AssignmentsStream = "ASSIGNMENTS" - -// Subject is the one address a runtime derives for itself: where its own membership is published, from -// the two names its credential carries. Everything else is in the membership. -func Subject(node, module string) string { - return "mesh.assignment." + node + "." + module -} - -// DirectGetSubject is the subject-addressed direct get of a membership from its stream: the one request an -// account may make of the stream. -func DirectGetSubject(node, module string) string { - return "$JS.API.DIRECT.GET." + AssignmentsStream + "." + Subject(node, module) -} - -// Membership is one assignment's subjects: what this instance of a module on this machine serves, -// and what it may reach. -type Membership struct { - Node string `json:"node"` - Module string `json:"module"` - // Serves is every address a tool of this instance answers on. `{tool}` stands for the tool's - // own name. An address with a queue is shared with the module's other instances. - Serves []Served `json:"serves"` - // Seats is every verb of a seat this instance holds, at the subject the seat's callers use. - Seats []SeatServed `json:"seats,omitempty"` - // Emits is where an event of this module lands; `{event}` stands for the event's name. - Emits string `json:"emits"` - // Reaches is each tool this module may call, `.`, to the subjects that reach it. - Reaches map[string][]string `json:"reaches,omitempty"` - // Tools is where this instance answers what it serves. - Tools string `json:"tools"` - // Receives is what this assignment is given for each requirement it receives, by requirement (novox/hq - // ADR 0167): kept as JSON because the catalogue owns the shape of a contribution. - Receives map[string]json.RawMessage `json:"receives,omitempty"` - // Mesh is every machine's address on the private network (novox/hq ADR 0167). - Mesh []string `json:"mesh,omitempty"` - // State is every bucket this module's code may reach, and whether it may write it (novox/hq ADR 0201). - State []StateIssued `json:"state,omitempty"` - // Logs is every log this module's code may reach (novox/hq ADR 0297). - Logs []LogIssued `json:"logs,omitempty"` - // SeatTraffic is what this module's code may submit, say, hear, take, ask, answer and read on seats - // that name their caller or their kind (novox/hq ADR 0259 §3). - SeatTraffic *SeatTraffic `json:"seat-traffic,omitempty"` - // Streams are this module's own streams, Hears the module streams it hears (novox/hq ADR 0363). - Streams []StreamIssued `json:"streams,omitempty"` - Hears []HeardIssued `json:"hears,omitempty"` - // Waits are the event families a session may wait on through the mesh MCP server (novox/hq ADR 0367 §2). - Waits []WaitIssued `json:"waits,omitempty"` -} - -// Served is one address a tool is answered on. -type Served struct { - Subject string `json:"subject"` - Queue string `json:"queue,omitempty"` -} - -// SeatServed is one verb of a held seat, where its callers ask. -type SeatServed struct { - Seat string `json:"seat"` - Verb string `json:"verb"` - Subject string `json:"subject"` -} - -// StateIssued is one bucket an assignment may reach, by the name its module uses for it (novox/hq ADR 0201). -// Key, when set, is the one key it may reach (novox/hq ADR 0260): this machine's. -type StateIssued struct { - Name string `json:"name"` - Bucket string `json:"bucket"` - Writes bool `json:"writes,omitempty"` - Key string `json:"key,omitempty"` -} - -// LogIssued is one log an assignment may reach, by the name its module uses for it (novox/hq ADR 0297). -type LogIssued struct { - Name string `json:"name"` - Stream string `json:"stream"` - Subject string `json:"subject"` - Writes bool `json:"writes"` -} - -// Worker is one durable consumer a holder pulls a seat's work from. -type Worker struct { - Stream string - Consumer string - Filter string -} - -// SeatTraffic is one module's seat traffic beyond tools. Publish and Subscribe are subject patterns, in the -// server's wildcards. -type SeatTraffic struct { - Publish []string `json:"publish,omitempty"` - Subscribe []string `json:"subscribe,omitempty"` - Answers []string `json:"answers,omitempty"` - Workers []Worker `json:"workers,omitempty"` - Records []string `json:"records,omitempty"` - Kinds []KindHeld `json:"kinds,omitempty"` -} - -// KindHeld is one kind of a kinded bench and who holds it. -type KindHeld struct { - Seat string `json:"seat"` - Kind string `json:"kind"` - Module string `json:"module"` - Node string `json:"node"` - Capabilities []string `json:"capabilities,omitempty"` -} - -// StreamIssued is one of a module's own streams as its membership names it (ADR 0363 §2). -type StreamIssued struct { - Stream string `json:"stream"` - Events []string `json:"events"` - Subject string `json:"subject"` -} - -// HeardIssued is one module stream an assignment hears (ADR 0363 §8). -type HeardIssued struct { - Stream string `json:"stream"` - Consumer string `json:"consumer"` -} - -// WaitIssued is one family as a membership issues it: the name a session asks for, and its subject. -type WaitIssued struct { - Event string `json:"event"` - Subject string `json:"subject"` -} - -// CredentialIn reads the credential out of a bus address, `[scheme://]user:password@host:port`, splitting on -// the last at-sign so a password containing one stays whole, and returns the address without it. -func CredentialIn(address string) (user, password, bare string) { - at := strings.LastIndex(address, "@") - if at < 0 { - return "", "", address - } - scheme := "" - rest := address[:at] - if i := strings.Index(rest, "://"); i >= 0 { - scheme, rest = rest[:i+3], rest[i+3:] - } - user, password, _ = strings.Cut(rest, ":") - return user, password, scheme + address[at+1:] -} - -// BareAddress is a bus address with any credential stripped, for something that only needs to know -// whether a server is answering there. -func BareAddress(address string) string { - _, _, bare := CredentialIn(address) - if bare == "" { - return address - } - if strings.Contains(bare, "://") { - return bare - } - return "nats://" + bare -} diff --git a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/merge-check.sh b/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/merge-check.sh deleted file mode 100644 index 00587326..00000000 --- a/modules/route-proxy/vendor/git.novox.be/novox/mesh-sdk/go/merge-check.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/sh -# mesh-check-toolchain: go -# -# The Go part of the SDK's own check (novox/hq ADR 0237 as amended, ADR 0272): the Go binding, run by the build -# seat in the mesh's Go toolchain, its own container, because the SDK's merge-check.sh declares it -# (`# mesh-check-also: go go/merge-check.sh`) — and by hand, from anywhere: `sh go/merge-check.sh`. -# -# Formatted, vetted (with and without the build tag `integration`), and run at two levels (novox/hq ADR 0391): -# -# 1. the unit level: `go test ./...` untagged, no bus or other process, timed and held to a bound -# (unit_bound below); over it the check fails and names the slowest tests (issue 895); -# 2. the integration level: the whole suite with `-tags integration`, each test against a bus of its -# own (testbus, which issue 1171 brings with the first test that needs a bus) so nothing is -# serialised. With no tagged test yet, the level runs the unit tests again and says their count. -# -# **A test that starts a bus or another process carries `//go:build integration`** as the first line of -# its file; a file that held both kinds is two files (`_integration_test.go` beside `_test.go`). -# Each level says its test count and its time. -# -# **A test that needs bubblewrap carries `//go:build integration && isolation`** (issue 1309's proof that a run step -# is shut off): this script vets it but does not run it, since the toolchain it runs in has no bubblewrap; the checker -# runs the `isolation` tag on the build nodes, which have it (R4e). -# -# No dependency is committed (no vendor directory: .gitignore; the operator, 2026-10-19; issue 1304). Every dependency -# is resolved by the version and checksum go.sum names (novox/hq ADR 0405 §5), from its public source: this binding -# requires nothing of the mesh's own forge. `go mod verify` fails by module name when a downloaded dependency does not -# match go.sum, and -mod=readonly fails a build whose go.sum lacks an entry instead of writing it. gofmt reads only -# the binding's own files. -# -# Exit 0 is a pass; anything else fails `mesh/repo-check` with the last line printed. -set -eu -cd "$(dirname "$0")" -# The toolchain the check runs in sets -mod=vendor; there is no vendor directory, so it is replaced. -GOFLAGS="$(printf '%s' "${GOFLAGS:-}" | sed 's/-mod=[a-z]*//g') -mod=readonly" -export GOFLAGS -export GOPRIVATE=git.novox.be - -# **The unit level's bound, in seconds** (issue 895): the slowest package's own test time of the untagged run, -# the compile not counted. A run over it fails the check and names the slowest tests. The SDK's unit suite is -# small (its slowest package measured well under a second on a quiet machine), so 2 s leaves room for a busier -# build node without hiding a test that should carry `integration` instead. -unit_bound=2 - -# judge_unit_run says the unit level as `go test -v` reported it and fails when it was over the bound: $1 the -# file holding the output of the untagged `go test -v` of every package (packages one after another, each -# ending in its `ok