The forge applies the rule named for a branch, else the first glob covering it; the judge passed a branch whose applied rule let pushes when a later rule happened to guard it. And a registration whose id the forge could not give cleared the id already recorded (the confirmation review of 2026-10-09).
381 lines
17 KiB
Go
381 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"hash/fnv"
|
|
"os"
|
|
"os/exec"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// The routes to root a review of ADR 0266 found (novox/hq ADR 0266 §7): an agent makes a repository of its
|
|
// own — or forks one the mesh builds from — commits a module.json naming a module the mesh runs everywhere
|
|
// (`sudo`, granting itself a rule without a password; `mesh-host`, the node-engine), and asks the `build` verb
|
|
// for it. Its outcome was registered under that name from the agent's repository, and the next push sent it.
|
|
|
|
// onTrunk is an outcome of a commit on its repository's trunk, as the build seat says it.
|
|
func onTrunk(id, repository, seat, path string, manifest map[string]any) link.BuildResult {
|
|
raw, _ := json.Marshal(manifest)
|
|
r := link.BuildResult{ID: id, Repository: "http://forge.internal:20000/" + repository + ".git", Path: path,
|
|
Ref: "main", On: "anchor", Commit: "c0ffee0123456789", Manifest: raw,
|
|
Trunk: "main", OnTrunk: true, Branches: []string{"main"}}
|
|
if seat != "" {
|
|
r.Source = &link.SourceOnSeat{Seat: seat, Repository: repository}
|
|
}
|
|
return r
|
|
}
|
|
|
|
// keptAsked keeps a build request as the asker would: through a verb, or at the terminal.
|
|
func keptAsked(t *testing.T, inv *inventory.Inventory, id, repository, path string, atTerminal bool) {
|
|
t.Helper()
|
|
if err := inv.RecordBuildRequest(t.Context(), inventory.BuildRequest{ID: id, Repository: repository, Seat: "git",
|
|
Path: path, For: "build", AtTerminal: atTerminal}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// theCatalogue is a mesh whose sudo is built from the catalogue repository and whose node-engine from its own.
|
|
func theCatalogue(t *testing.T) *stores {
|
|
t.Helper()
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
for _, b := range []link.BuildResult{
|
|
onTrunk("build-sudo", "novox/mesh-catalog", "git", "modules/sudo", map[string]any{"module": "sudo", "version": "1"}),
|
|
onTrunk("build-host", "novox/mesh-host", "git", "", map[string]any{"module": "mesh-host", "version": "1"}),
|
|
} {
|
|
keptAsked(t, open.inventory, b.ID, b.Source.Repository, b.Path, true)
|
|
if _, _, err := takeIn(ctx, open.inventory, b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return open
|
|
}
|
|
|
|
func TestABuildFromAnAgentsRepositoryIsNotRegisteredAsAModuleTheMeshHolds(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
for _, c := range []struct {
|
|
name, repository, path, module string
|
|
}{
|
|
{"its own repository naming sudo", "agent/sudo", "modules/sudo", "sudo"},
|
|
{"a fork of the catalogue", "agent/mesh-catalog", "modules/sudo", "sudo"},
|
|
{"a fork of the node-engine", "agent/mesh-host", "", "mesh-host"},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
id := "build-" + strings.ReplaceAll(c.repository, "/", "-")
|
|
keptAsked(t, open.inventory, id, c.repository, c.path, false) // through the build verb
|
|
evil := onTrunk(id, c.repository, "git", c.path, map[string]any{"module": c.module, "version": "evil"})
|
|
_, _, err := takeIn(ctx, open.inventory, evil)
|
|
if !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("a build of %s was taken in as %s: %v", c.repository, c.module, err)
|
|
}
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := shelf[c.module].Version; got != "1" {
|
|
t.Fatalf("%s is now %q, from %s", c.module, got, c.repository)
|
|
}
|
|
if _, found, _ := open.inventory.BuildByID(ctx, id); !found {
|
|
t.Errorf("the refused build %s is not recorded", id)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestANewModuleFromARepositoryTheCatalogueDoesNotBuildFromIsNotRegistered(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
keptAsked(t, open.inventory, "build-new", "agent/tools", "", false)
|
|
_, _, err := takeIn(ctx, open.inventory, onTrunk("build-new", "agent/tools", "git", "",
|
|
map[string]any{"module": "agent-tools", "version": "1"}))
|
|
if !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("a new module from an agent's repository was taken in: %v", err)
|
|
}
|
|
// And one asked of nobody here — an outcome on the bus no request was kept for — the same.
|
|
_, _, err = takeIn(ctx, open.inventory, onTrunk("build-unasked", "agent/tools", "git", "",
|
|
map[string]any{"module": "agent-tools", "version": "1"}))
|
|
if !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("an outcome nobody asked for was taken in: %v", err)
|
|
}
|
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["agent-tools"].Module != "" {
|
|
t.Fatal("the refused module is in the catalogue")
|
|
}
|
|
}
|
|
|
|
// The operator at the terminal may still move a module, or add one from a new repository.
|
|
func TestAtTheTerminalAnotherRepositoryIsRegistered(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
keptAsked(t, open.inventory, "build-moved", "novox/sudo", "", true)
|
|
if _, _, err := takeIn(ctx, open.inventory, onTrunk("build-moved", "novox/sudo", "git", "",
|
|
map[string]any{"module": "sudo", "version": "2"})); err != nil {
|
|
t.Fatalf("a move the operator asked for at the terminal was refused: %v", err)
|
|
}
|
|
if src, _ := open.inventory.SourceOf(ctx, "sudo"); src.Repository != "novox/sudo" {
|
|
t.Fatalf("sudo is built from %q", src.Repository)
|
|
}
|
|
if err := open.inventory.RecordBuildRequest(ctx, inventory.BuildRequest{ID: "build-external",
|
|
Repository: "http://forge.internal:20000/someone/app.git", For: "build", AtTerminal: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := takeIn(ctx, open.inventory, onTrunk("build-external", "someone/app", "", "",
|
|
map[string]any{"module": "app", "version": "1"})); err != nil {
|
|
t.Fatalf("a new module the operator asked for at the terminal was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// The delivery's flow is untouched: a merge's rebuild of a module from its own repository, and a merge adding
|
|
// a module to a repository the catalogue builds from (novox/hq issue 300), are registered with no terminal.
|
|
func TestADeliveryFromTheRegisteredRepositoryIsRegistered(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
rebuilt := onTrunk("build-plan", "novox/mesh-catalog", "git", "modules/sudo", map[string]any{"module": "sudo", "version": "2"})
|
|
if err := open.inventory.RecordBuildRequest(ctx, inventory.BuildRequest{ID: rebuilt.ID,
|
|
Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/sudo", For: "plan"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := takeIn(ctx, open.inventory, rebuilt); err != nil {
|
|
t.Fatalf("a plan's build of the module's own repository was refused: %v", err)
|
|
}
|
|
added := onTrunk("build-merge", "novox/mesh-catalog", "git", "modules/zram", map[string]any{"module": "zram", "version": "1"})
|
|
if _, _, err := takeIn(ctx, open.inventory, added); err != nil {
|
|
t.Fatalf("a module a merge added to the catalogue repository was refused: %v", err)
|
|
}
|
|
shelf, _ := open.inventory.Catalogue(ctx)
|
|
if shelf["sudo"].Version != "2" || shelf["zram"].Module == "" {
|
|
t.Fatalf("not registered: sudo %q, zram %q", shelf["sudo"].Version, shelf["zram"].Module)
|
|
}
|
|
}
|
|
|
|
// Through a verb, a build of a repository the catalogue builds nothing from is not even asked: the build node
|
|
// would run what the agent wrote.
|
|
func TestAVerbAsksNoBuildOfARepositoryTheCatalogueDoesNotBuildFrom(t *testing.T) {
|
|
theCatalogue(t)
|
|
ctx := t.Context()
|
|
t.Setenv(verbVar, "build")
|
|
t.Setenv(link.CallerVar, "node-tools.anchor, through the mesh-controller seat")
|
|
err := verbMayAsk(ctx, buildSource{Repository: "agent/sudo", Seat: "git"}, "http://forge.internal:20000/agent/sudo.git")
|
|
var policy *heldAtTheTerminal
|
|
if !errors.As(err, &policy) {
|
|
t.Fatalf("a verb's build of an agent's repository was asked: %v", err)
|
|
}
|
|
if err := verbMayAsk(ctx, buildSource{Repository: "novox/mesh-catalog", Seat: "git"},
|
|
"http://forge.internal:20000/novox/mesh-catalog.git"); err != nil {
|
|
t.Fatalf("a verb's build of the catalogue repository was refused: %v", err)
|
|
}
|
|
t.Setenv(verbVar, "")
|
|
t.Setenv(link.CallerVar, "")
|
|
if err := verbMayAsk(ctx, buildSource{Repository: "agent/sudo", Seat: "git"}, ""); err != nil {
|
|
t.Fatalf("the terminal was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// asTheOperator keeps a build as asked at the controller's terminal, as the operator's first build of a module
|
|
// from a repository the catalogue does not yet build from is (novox/hq ADR 0266), and hands it back.
|
|
func asTheOperator(t *testing.T, inv *inventory.Inventory, b link.BuildResult) link.BuildResult {
|
|
t.Helper()
|
|
repository, seat := b.Repository, ""
|
|
if b.Source != nil {
|
|
repository, seat = b.Source.Repository, b.Source.Seat
|
|
}
|
|
if err := inv.RecordBuildRequest(t.Context(), inventory.BuildRequest{ID: b.ID, Repository: repository, Seat: seat,
|
|
Path: b.Path, For: "build", AtTerminal: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// A rollback puts back only a build of the module's own repository: an agent's build of the module's name,
|
|
// recorded and refused, at the very commit the machine ran before (a fork carries it), is never registered by
|
|
// the back door of a failed gate.
|
|
func TestARollbackNeverPutsBackABuildFromAnotherRepository(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
fork := onTrunk("build-1791500000000000000", "agent/mesh-catalog", "git", "modules/sudo",
|
|
map[string]any{"module": "sudo", "version": "evil"})
|
|
fork.Commit = "c0ffee0123456789" // the commit sudo was registered at
|
|
keptAsked(t, inv, fork.ID, "agent/mesh-catalog", "modules/sudo", false)
|
|
if _, _, err := takeIn(ctx, inv, fork); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("the fork's build was taken in: %v", err)
|
|
}
|
|
failed := onTrunk("build-1791600000000000000", "novox/mesh-catalog", "git", "modules/sudo",
|
|
map[string]any{"module": "sudo", "version": "2"})
|
|
failed.Commit = "badbadbad0123456"
|
|
if _, _, err := takeIn(ctx, inv, failed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
record, _, err := inv.BuildByID(ctx, failed.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
previous, found, err := inv.PreviousBuild(ctx, "sudo", "c0ffee0123456789", record)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if found && previous.ID == fork.ID {
|
|
t.Fatalf("a rollback would put back the fork's build %s", previous.ID)
|
|
}
|
|
if !found || previous.ID != "build-sudo" {
|
|
t.Fatalf("a rollback puts back %q (found %v), want the registered build-sudo", previous.ID, found)
|
|
}
|
|
}
|
|
|
|
// theForge is what the forge says in a test, by owner/name: a repository not named here is protected as a
|
|
// trunk must be, with an id of its own.
|
|
var theForge sync.Map
|
|
|
|
func init() {
|
|
askTheForge = func(_ context.Context, owner, repo, _ string) (forgeFacts, error) {
|
|
if said, ok := theForge.Load(owner + "/" + repo); ok {
|
|
switch f := said.(type) {
|
|
case error:
|
|
return forgeFacts{}, f
|
|
case forgeFacts:
|
|
return f, nil
|
|
}
|
|
}
|
|
h := fnv.New32a()
|
|
_, _ = h.Write([]byte(owner + "/" + repo))
|
|
return forgeFacts{ID: int64(h.Sum32()), Guarded: true}, nil
|
|
}
|
|
}
|
|
|
|
// A module's trunk the forge does not protect — direct pushes, no required status — or a forge that cannot say,
|
|
// registers nothing through a verb: the trunk rule means nothing on a branch anyone pushes to.
|
|
func TestATrunkTheForgeDoesNotProtectRegistersNothing(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
theForge.Store("novox/unguarded", forgeFacts{ID: 7, Why: "the rule main lets a person push to main directly"})
|
|
t.Cleanup(func() { theForge.Delete("novox/unguarded") })
|
|
first := onTrunk("build-unguarded-1", "novox/unguarded", "git", "", map[string]any{"module": "unguarded", "version": "1"})
|
|
keptAsked(t, open.inventory, first.ID, "novox/unguarded", "", true)
|
|
if _, _, err := takeIn(ctx, open.inventory, first); err != nil {
|
|
t.Fatalf("at the terminal: %v", err)
|
|
}
|
|
again := onTrunk("build-unguarded-2", "novox/unguarded", "git", "", map[string]any{"module": "unguarded", "version": "2"})
|
|
if _, _, err := takeIn(ctx, open.inventory, again); !errors.Is(err, errNotItsSource) ||
|
|
!strings.Contains(err.Error(), "push to main directly") {
|
|
t.Fatalf("a rebuild from an unprotected trunk was taken in: %v", err)
|
|
}
|
|
theForge.Store("novox/unguarded", errors.New("nothing serves gitea.gitea_api"))
|
|
if _, _, err := takeIn(ctx, open.inventory, onTrunk("build-unguarded-3", "novox/unguarded", "git", "",
|
|
map[string]any{"module": "unguarded", "version": "3"})); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("a forge that could not say was read as a protected trunk: %v", err)
|
|
}
|
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["unguarded"].Version != "1" {
|
|
t.Fatalf("unguarded is %q", shelf["unguarded"].Version)
|
|
}
|
|
}
|
|
|
|
// A repository deleted and made again under the module's repository's name is another repository: the forge's
|
|
// id, recorded at registration, tells them apart.
|
|
func TestARepositoryMadeAgainUnderItsNameIsNotTheModulesSource(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
theForge.Store("novox/remade", forgeFacts{ID: 100, Guarded: true})
|
|
t.Cleanup(func() { theForge.Delete("novox/remade") })
|
|
first := onTrunk("build-remade-1", "novox/remade", "git", "", map[string]any{"module": "remade", "version": "1"})
|
|
keptAsked(t, open.inventory, first.ID, "novox/remade", "", true)
|
|
if _, _, err := takeIn(ctx, open.inventory, first); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if id, _ := open.inventory.SourceIdentity(ctx, "remade"); id != 100 {
|
|
t.Fatalf("the forge's id was not recorded: %d", id)
|
|
}
|
|
theForge.Store("novox/remade", forgeFacts{ID: 101, Guarded: true}) // deleted, and made again by an agent
|
|
if _, _, err := takeIn(ctx, open.inventory, onTrunk("build-remade-2", "novox/remade", "git", "",
|
|
map[string]any{"module": "remade", "version": "evil"})); !errors.Is(err, errNotItsSource) ||
|
|
!strings.Contains(err.Error(), "made again") {
|
|
t.Fatalf("a repository made again under the name was taken in: %v", err)
|
|
}
|
|
// And a new module from it, beside the one registered from the first, the same.
|
|
if _, _, err := takeIn(ctx, open.inventory, onTrunk("build-remade-3", "novox/remade", "git", "modules/other",
|
|
map[string]any{"module": "other", "version": "1"})); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("a new module from a repository made again was taken in: %v", err)
|
|
}
|
|
}
|
|
|
|
// The terminal's mark is the operator's for the repository and path they asked: an outcome of another, under that
|
|
// build's id, is not theirs.
|
|
func TestATerminalRequestCoversOnlyWhatItAsked(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
if err := open.inventory.RecordBuildRequest(ctx, inventory.BuildRequest{ID: "build-asked", Repository: "novox/app",
|
|
Seat: "git", Path: "modules/app", For: "build", AtTerminal: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other := onTrunk("build-asked", "agent/sudo", "git", "modules/app", map[string]any{"module": "sudo", "version": "evil"})
|
|
if _, _, err := takeIn(ctx, open.inventory, other); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("an outcome of another repository under a terminal request's id was taken in: %v", err)
|
|
}
|
|
elsewhere := onTrunk("build-asked", "novox/app", "git", "modules/sudo", map[string]any{"module": "sudo", "version": "evil"})
|
|
if _, _, err := takeIn(ctx, open.inventory, elsewhere); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("an outcome of another path under a terminal request's id was taken in: %v", err)
|
|
}
|
|
}
|
|
|
|
// The serving controller, and everything it starts, is never the terminal: a build asked in it reads as asked
|
|
// through the mesh even when no verb and no caller is named.
|
|
func TestTheServingControllerIsNeverTheTerminal(t *testing.T) {
|
|
t.Setenv(verbVar, "")
|
|
t.Setenv(link.CallerVar, "")
|
|
t.Setenv(servedVar, "")
|
|
if !startedAtTheTerminal() {
|
|
t.Fatal("a process started by hand is not the terminal")
|
|
}
|
|
markServed()
|
|
if startedAtTheTerminal() {
|
|
t.Fatal("the serving controller reads as the terminal")
|
|
}
|
|
child := exec.Command(os.Args[0], "-test.run=^$")
|
|
child.Env = os.Environ()
|
|
if !slices.Contains(child.Env, servedVar+"=1") {
|
|
t.Fatal("what the serving controller starts does not carry its mark")
|
|
}
|
|
}
|
|
|
|
// The forge applies one rule to a branch: the rule named for it, else the first glob that covers it. A stronger
|
|
// rule later in the list is not what guards the branch, and is not read as if it were (the confirmation review).
|
|
func TestTheRuleJudgedIsTheOneTheForgeApplies(t *testing.T) {
|
|
guarded := protectionRule{Rule: "*", RequiredStatuses: []string{"mesh/merge-gate"}}
|
|
open := protectionRule{Rule: "main", Push: true, RequiredStatuses: []string{"mesh/merge-gate"}}
|
|
if f := judgedRule([]protectionRule{guarded, open}, "main"); f.Guarded {
|
|
t.Error("an exact rule letting pushes was passed over for a glob that guards")
|
|
}
|
|
if f := judgedRule([]protectionRule{{Rule: "ma*", RequiredStatuses: nil}, {Rule: "*", RequiredStatuses: []string{"x"}}},
|
|
"main"); f.Guarded || !strings.Contains(f.Why, "ma*") {
|
|
t.Errorf("the first glob covering the branch was not the one judged: %+v", f)
|
|
}
|
|
if f := judgedRule([]protectionRule{{Rule: "release/*"}, {Rule: "main", RequiredStatuses: []string{"x"}}}, "main"); !f.Guarded {
|
|
t.Errorf("the rule named for the branch was not judged: %+v", f)
|
|
}
|
|
if f := judgedRule(nil, "main"); f.Guarded {
|
|
t.Error("no rule is no protection")
|
|
}
|
|
}
|
|
|
|
// An id the forge could not give keeps the id already recorded.
|
|
func TestAnUnknownIdentityKeepsTheRecordedOne(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
if err := open.inventory.SetSourceIdentity(ctx, "sudo", 100); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.SetSourceIdentity(ctx, "sudo", 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if id, _ := open.inventory.SourceIdentity(ctx, "sudo"); id != 100 {
|
|
t.Fatalf("the recorded id became %d", id)
|
|
}
|
|
}
|