From 39ac1917de249f4f9ae89413e91a843c5891c1c8 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 17:05:04 +0100 Subject: [PATCH] =?UTF-8?q?Delete=20go/provisions:=20no=20importer=20is=20?= =?UTF-8?q?left=20in=20any=20tree;=20the=20list=20of=20packages=20and=20no?= =?UTF-8?q?tInSDK=20follow=20(S-7,=20ADR=200634=20=C2=A75;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/packages_list_test.go | 7 +- go/provisions/provisions.go | 310 ------------------------------- go/provisions/provisions_test.go | 121 ------------ go/testdata/seat-names.txt | 2 - 4 files changed, 3 insertions(+), 437 deletions(-) delete mode 100644 go/provisions/provisions.go delete mode 100644 go/provisions/provisions_test.go diff --git a/go/packages_list_test.go b/go/packages_list_test.go index 002ad7c..187444d 100644 --- a/go/packages_list_test.go +++ b/go/packages_list_test.go @@ -25,7 +25,7 @@ import ( var meshSDKPackages = []string{ ".", "announce", "announcetest", "buildstep", "buscalls", "buscompose", "busstate", "busretry", "calladdress", "catalogue", "declaration", "delivery", "forgekey", "health", "internal/beside", "keys", "kvlimit", "membership", "motion", - "objectlink", "outward", "overlay", "pernode", "process", "provisions", + "objectlink", "outward", "overlay", "pernode", "process", "sdkversion", "seal", "seatdef", "seatlist", "seatlist/seatlisttest", "testbus", "views", "views/viewstest", } @@ -39,14 +39,13 @@ var clientSeats = []string{"operator-channel", "mesh-vault", "node-engine", "npm var moduleWord = regexp.MustCompile(`\bmesh-(?:delivery|grants|vault|host|config|provisioner|broker|knowledge|enrolment)\b[-a-z]*|\b(?:indexer|assigner|teller|watchman|checker|builder)(?:-views|_read_)`) // notInSDK are the packages that moved to a seat's client or contract (ADR 0627 §1, §4) and may not come back. -var notInSDK = []string{"asks", "asker", "publish", "composer", "parts", "provider", "handacts", "channel"} +var notInSDK = []string{"asks", "asker", "publish", "composer", "parts", "provider", "handacts", "channel", "provisions"} // untilTheJoin is what ADR 0627 still moves at the group's join, each `: `, naming what the checks below -// would refuse; it only shrinks. The vault module's name in delivery and provisions stays past this milestone's pin +// would refuse; it only shrinks. The vault module's name in delivery stays past this milestone's pin // (issue 1966 note 28) and goes in the next milestone, when the vault's answers are its contract's. var untilTheJoin = []string{ "delivery/signers.go: mesh-vault", - "provisions/provisions.go: mesh-vault", // Names of whole seats in packages that leave in S-7 (delivery, buscompose's grants-part names), with their packages. "delivery/builder.go: container-image-registry", "delivery/builder.go: oci-artifact-registry", diff --git a/go/provisions/provisions.go b/go/provisions/provisions.go deleted file mode 100644 index 3889dc3..0000000 --- a/go/provisions/provisions.go +++ /dev/null @@ -1,310 +0,0 @@ -// Package provisions is the contract of the seat mesh-provisioner's records (novox/hq ADR 0570 clauses 1, 2 and 5; issue -// 1954): every provision one key of the state `provisions`, holding a reference and never a value, and every handed -// module's provisioning plan one key of the state `plans`. The module mesh-provisioner alone writes both; the vault, -// each node's provisioner client and every provider read them through `reads`. -package provisions - -import ( - "bytes" - "encoding/json" - "fmt" - "regexp" - "sort" - "strings" - "time" - - "git.novox.be/novox/mesh-sdk/go/catalogue" -) - -// The seat, its holder and its two states, and the names a reader declares in its `reads`. -const ( - Seat = "mesh-provisioner" - Module = "mesh-provisioner" - ProvisionsState = "provisions" - PlansState = "plans" - ProvisionsRead = Module + "." + ProvisionsState - PlansRead = Module + "." + PlansState - // ProvisionsBucket and PlansBucket are the states' buckets. - ProvisionsBucket = Module + "_" + ProvisionsState - PlansBucket = Module + "_" + PlansState -) - -// The states of a provision (clause 1). -const ( - // Asked is a provision its consumer needs that its provider has not answered yet, or whose dependencies are not - // all provided (clause 3). - Asked = "asked" - // Provided is a provision the provider said it made: set only from the provider's outcome (clause 3). - Provided = "provided" - // Waits is a provision only a person can give, naming what is awaited (clause 7). - Waits = "waits" - // Failed is a provision the provider could not make, with why. - Failed = "failed" - // Retiring is a provision no want needs, asked of the operator before its credential goes (clause 5). - Retiring = "retiring" -) - -// States is every state a provision may be in. -var States = []string{Asked, Provided, Waits, Failed, Retiring} - -// Version is the version of the records' layout. -const Version = 1 - -// token is one part of a key: what a bucket's key takes, no dot. -var token = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) - -// Key is a provision's key: `..`, and `.` where the consumer keeps several values of -// one provision (ADR 0094). The same key the provider's own entry for that consumer has (provider.Entry.Key). -func Key(node, consumer, provision, local string) (string, error) { - parts := []string{node, consumer, provision} - if local != "" { - parts = append(parts, local) - } - for _, p := range parts { - if !token.MatchString(p) { - return "", fmt.Errorf("provisions: %q cannot be part of a provision's key", p) - } - } - return strings.Join(parts, "."), nil -} - -// ParseKey is the inverse of Key. -func ParseKey(key string) (node, consumer, provision, local string, err error) { - parts := strings.Split(key, ".") - if len(parts) < 3 || len(parts) > 4 { - return "", "", "", "", fmt.Errorf("provisions: %q is not ..[.]", key) - } - for _, p := range parts { - if !token.MatchString(p) { - return "", "", "", "", fmt.Errorf("provisions: %q is not ..[.]", key) - } - } - if len(parts) == 4 { - local = parts[3] - } - return parts[0], parts[1], parts[2], local, nil -} - -// PlanKey is a plan's key in `plans`: `.` (clause 2). -func PlanKey(node, module string) (string, error) { - if !token.MatchString(node) || !token.MatchString(module) { - return "", fmt.Errorf("provisions: %s.%s is not a plan's key, .", node, module) - } - return node + "." + module, nil -} - -// The forms of a provision's credential, as a plan's entry names them (catalogue.PlanProvision.CredentialForm): the -// catalogue's one list (catalogue.CredentialForms, issue 774), under the names this package has used. -const ( - FormPair = catalogue.CredentialFormPair - FormShared = catalogue.CredentialFormShared - FormIssued = catalogue.CredentialFormIssued - FormGiven = catalogue.CredentialFormGiven - // FormNone is an offer whose consumers present no credential (catalogue.CredentialFormNone): the vault mints nothing, - // and the provision depends on no credential. - FormNone = catalogue.CredentialFormNone -) - -// VaultName is the vault's name of a provision's credential in form, as its verb `own` names it and the provider asks -// it: `pair////[/]` for a login of the consumer's own (ADR 0158 -// §11), `provision//` for the provider's one shared value, and `provision////` for a holder's own issued or given value. Empty for a form with no -// credential. -func VaultName(form string, p Provision) string { - switch form { - case FormPair: - n := "pair/" + p.Provider.Node + "/" + p.Provision + "/" + p.Consumer.Node + "/" + p.Consumer.Module - if p.Local != "" { - n += "/" + p.Local - } - return n - case FormShared: - return "provision/" + p.Provider.Node + "/" + p.Provision - case FormIssued, FormGiven: - return "provision/" + p.Provider.Node + "/" + p.Provision + "/" + p.Consumer.Node + "/" + p.Consumer.Module - } - return "" -} - -// VaultModule is the provider of every provision's credential (novox/hq ADR 0570 clauses 1, 6). -const VaultModule = "mesh-vault" - -// CredentialKey is the key of a provision's credential, itself a provision whose provider is the vault (clause 6; the -// key form the lead settled on 2026-10-29): `..secret-[.]` for a value of the -// consumer's own (pair, issued, given), and `..secret-` for the provider's one -// shared value. False for a form with no vault value, and for a provision the vault itself provides. -func CredentialKey(form string, consumer, provider End, provision, local string) (string, bool) { - if provider.Module == VaultModule { - return "", false // the vault's own offer is the value: it has no credential beside it (the lead, 2026-10-29) - } - name := catalogue.CredentialProvisionPrefix + provision - switch form { - case FormPair, FormIssued, FormGiven: - k, err := Key(consumer.Node, consumer.Module, name, local) - return k, err == nil - case FormShared: - k, err := Key(provider.Node, provider.Module, name, "") - return k, err == nil - } - return "", false -} - -// DependsOn is what a provision waits for before its provider makes it (clause 3): its credential's key, where its form -// has a vault value, and the key of every provision its provider module itself requires on its own node, read from -// the provider's plan there (nil when the provider is not handed and has none). Sorted, none twice. -func DependsOn(consumer End, entry catalogue.PlanProvision, providerPlan *catalogue.Plan) []string { - seen := map[string]bool{} - var out []string - add := func(k string) { - if k != "" && !seen[k] { - seen[k] = true - out = append(out, k) - } - } - provider := End{Module: entry.ProviderModule, Node: entry.Provider} - if k, ok := CredentialKey(entry.CredentialForm, consumer, provider, entry.Provision, entry.Local); ok { - add(k) - } - if providerPlan != nil { - for _, own := range providerPlan.Provisions { - if k, err := Key(provider.Node, provider.Module, own.Provision, own.Local); err == nil { - add(k) - } - } - } - sort.Strings(out) - return out -} - -// End is one end of a provision: a module on a node. -type End struct { - Module string `json:"module"` - Node string `json:"node"` -} - -// Reference is a value the vault keeps, by its id and the generation of the value: never the value, sealed or not -// (ADR 0349 §7). The holder writes the generation the vault says in its own entry (clause 6). ID is the vault's name of -// the credential exactly as its verb `own` names it (VaultName): the consuming node's engine checks both the name and -// the generation of what it is handed. -type Reference struct { - ID string `json:"id"` - Generation int `json:"generation"` -} - -// Warrant is the operator's answer to a retiring provision's ask, as the holder writes it into the key (clause 5). -type Warrant struct { - Ask string `json:"ask"` - Option string `json:"option"` - By string `json:"by"` - At time.Time `json:"at"` -} - -// Provision is one key of `provisions`. -type Provision struct { - V int `json:"v"` - Provision string `json:"provision"` - Local string `json:"local,omitempty"` - Consumer End `json:"consumer"` - Provider End `json:"provider"` - // DependsOn are the keys of the provisions this one waits for (clause 3): a database user whose password is the - // vault's. - DependsOn []string `json:"depends-on,omitempty"` - State string `json:"state"` - // Why is the reason for waits, failed and retiring, and for asked when a dependency holds it. - Why string `json:"why,omitempty"` - Reference *Reference `json:"reference,omitempty"` - // Ask is a retiring provision's ask of the operator; Warrant the answer, once given. - Ask string `json:"ask,omitempty"` - Warrant *Warrant `json:"warrant,omitempty"` - // Plan is the plan entry the key was composed from, at the consumer's wanted build or its newest (issue 1126): the - // provider binds the consumer from it, so a requirement the newest build adds is provided before the want that - // waits for it moves, though the plan the seat keeps at the wanted build names it not yet. - Plan *catalogue.PlanProvision `json:"plan,omitempty"` - At time.Time `json:"at"` -} - -// Key is the provision's key. -func (p Provision) Key() (string, error) { - return Key(p.Consumer.Node, p.Consumer.Module, p.Provision, p.Local) -} - -func (p Provision) check() error { - if _, err := p.Key(); err != nil { - return err - } - if p.Provider.Module == "" || p.Provider.Node == "" { - return fmt.Errorf("provisions: %s names no provider", p.Provision) - } - known := false - for _, s := range States { - known = known || s == p.State - } - switch { - case !known: - return fmt.Errorf("provisions: a provision's state is one of %s, not %q", strings.Join(States, ", "), p.State) - case (p.State == Waits || p.State == Failed || p.State == Retiring) && strings.TrimSpace(p.Why) == "": - return fmt.Errorf("provisions: a %s provision says why", p.State) - case p.Warrant != nil && p.State != Retiring: - return fmt.Errorf("provisions: only a retiring provision carries the operator's warrant") - } - for _, d := range p.DependsOn { - if _, _, _, _, err := ParseKey(d); err != nil { - return err - } - } - return nil -} - -// Encode is p as JSON at Version; it refuses what a holder may not write. -func Encode(p Provision) ([]byte, error) { - p.V = Version - if err := p.check(); err != nil { - return nil, err - } - return json.Marshal(p) -} - -// Decode reads a key's value. **A field the layout does not name is read past and dropped** (issue 1126 review 1): a -// reader on an older mesh-sdk must not refuse a key a newer writer adds a field to. No value rides in a provision all -// the same (clause 1, ADR 0201 §13): Encode writes the layout alone, so what a reader does not know is never kept. -func Decode(data []byte) (Provision, error) { - var p Provision - dec := json.NewDecoder(bytes.NewReader(data)) - if err := dec.Decode(&p); err != nil { - return Provision{}, fmt.Errorf("provisions: a provision is not readable: %w", err) - } - if p.V != Version { - return Provision{}, fmt.Errorf("provisions: a provision of version %d, and this reads %d", p.V, Version) - } - return p, p.check() -} - -// Plan is one key of `plans`: a module's provisioning plan on a node, never a value (clause 2). -type Plan struct { - V int `json:"v"` - Node string `json:"node"` - Module string `json:"module"` - Plan catalogue.Plan `json:"plan"` -} - -// EncodePlan is p as JSON at Version. -func EncodePlan(p Plan) ([]byte, error) { - p.V = Version - if _, err := PlanKey(p.Node, p.Module); err != nil { - return nil, err - } - return json.Marshal(p) -} - -// DecodePlan reads a plan's key. -func DecodePlan(data []byte) (Plan, error) { - var p Plan - if err := json.Unmarshal(data, &p); err != nil { - return Plan{}, fmt.Errorf("provisions: a plan is not readable: %w", err) - } - if p.V != Version { - return Plan{}, fmt.Errorf("provisions: a plan of version %d, and this reads %d", p.V, Version) - } - _, err := PlanKey(p.Node, p.Module) - return p, err -} diff --git a/go/provisions/provisions_test.go b/go/provisions/provisions_test.go deleted file mode 100644 index f9e5ab8..0000000 --- a/go/provisions/provisions_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package provisions - -import ( - "strings" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/catalogue" -) - -// ADR 0570 clauses 1, 2 and 5 (issue 1954 L6-P2): a provision's key is its consumer's node and module, the provision and -// its local name, the provider's entry's key; its value round-trips, names its state and why, and refuses any field the -// layout does not name, so no value rides in it; a plan's key is its node and module. -func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) { - at := time.Date(2026, 10, 29, 22, 0, 0, 0, time.UTC) - db := Provision{Provision: "postgres-database", Consumer: End{Module: "keycloak", Node: "novox"}, - Provider: End{Module: "postgres", Node: "novox"}, DependsOn: []string{"novox.keycloak.secret.db"}, State: Asked, - Reference: &Reference{ID: "provision/postgres/postgres-database", Generation: 2}, At: at} - for _, c := range []struct { - name string - edit func(*Provision) - key string - refused string - }{ - {name: "asked, depending on the vault's", key: "novox.keycloak.postgres-database"}, - {name: "under a local name", edit: func(p *Provision) { p.Local = "replica" }, key: "novox.keycloak.postgres-database.replica"}, - {name: "retiring with the operator's warrant", edit: func(p *Provision) { - p.State, p.Why, p.Ask = Retiring, "keycloak left novox", "a-1" - p.Warrant = &Warrant{Ask: "a-1", Option: "yes", By: "the operator", At: at} - }, key: "novox.keycloak.postgres-database"}, - {name: "an unknown state", edit: func(p *Provision) { p.State = "done" }, refused: "one of"}, - {name: "failed without why", edit: func(p *Provision) { p.State = Failed }, refused: "says why"}, - {name: "a warrant on a provided one", edit: func(p *Provision) { p.State, p.Warrant = Provided, &Warrant{Ask: "a"} }, refused: "only a retiring"}, - {name: "no provider", edit: func(p *Provision) { p.Provider = End{} }, refused: "no provider"}, - {name: "a dotted consumer", edit: func(p *Provision) { p.Consumer.Module = "a.b" }, refused: "cannot be part"}, - {name: "a dependency that is no key", edit: func(p *Provision) { p.DependsOn = []string{"x"} }, refused: "is not"}, - } { - p := db - if c.edit != nil { - c.edit(&p) - } - raw, err := Encode(p) - if c.refused != "" { - if err == nil || !strings.Contains(err.Error(), c.refused) { - t.Errorf("%s: %v, want refused saying %q", c.name, err, c.refused) - } - continue - } - back, err := Decode(raw) - key, kerr := back.Key() - if err != nil || kerr != nil || key != c.key || back.V != Version || back.Reference.Generation != 2 { - t.Errorf("%s: read back as %+v under %q, %v %v", c.name, back, key, err, kerr) - } - if node, consumer, provision, local, err := ParseKey(key); err != nil || node != "novox" || consumer != "keycloak" || - provision != "postgres-database" || local != p.Local { - t.Errorf("%s: %s parses as %s %s %s %s, %v", c.name, key, node, consumer, provision, local, err) - } - } - // A field a later layout adds is read past, never kept (issue 1126 review 1: a reader on an older mesh-sdk must not - // refuse a key a newer writer adds a field to). A value still never rides in a provision: Encode writes the layout - // alone, so what a reader does not know is dropped, and encoding it again carries none of it. - later, err := Decode([]byte(`{"v":1,"provision":"p","consumer":{"module":"c","node":"n"},"provider":{"module":"p","node":"n"},` + - `"state":"provided","at":"2026-10-29T22:00:00Z","a-later-field":{"x":1},"password":"hunter2"}`)) - if err != nil || later.Provision != "p" || later.State != Provided { - t.Errorf("a provision with a field of a later layout: %+v, %v", later, err) - } - if again, _ := Encode(later); strings.Contains(string(again), "hunter2") || strings.Contains(string(again), "a-later-field") { - t.Errorf("a field the layout does not name was kept: %s", again) - } - plan := Plan{Node: "novox", Module: "restic", Plan: catalogue.Plan{}} - raw, err := EncodePlan(plan) - if err != nil { - t.Fatal(err) - } - if back, err := DecodePlan(raw); err != nil || back.Node != "novox" || back.Module != "restic" { - t.Errorf("a plan read back as %+v, %v", back, err) - } - if k, err := PlanKey("novox", "restic"); k != "novox.restic" || err != nil { - t.Errorf("a plan's key is %q, %v", k, err) - } - // A reference's id is the vault's name as its verb own names it, in each form (the host agent, 2026-10-29). - for form, want := range map[string]string{FormPair: "pair/novox/postgres-database/novox/keycloak", FormShared: "provision/novox/postgres-database", - FormIssued: "provision/novox/postgres-database/novox/keycloak", FormNone: ""} { - if got := VaultName(form, db); got != want { - t.Errorf("the %s name is %q", form, got) - } - } - if got := VaultName(FormPair, Provision{Provision: "p", Local: "r", Provider: End{Node: "a"}, Consumer: End{Node: "b", Module: "m"}}); got != "pair/a/p/b/m/r" { - t.Errorf("a local name's pair is %q", got) - } - // A provision waits for its credential, the vault's provision `secret-`, and for what its provider itself - // requires on its node (clause 3; the key form the lead settled on 2026-10-29). - keycloak, postgres := End{Module: "keycloak", Node: "novox"}, End{Module: "postgres", Node: "ace"} - providerPlan := &catalogue.Plan{Provisions: []catalogue.PlanProvision{{Provision: "backup-target", Provider: "ace", ProviderModule: "restic"}}} - for _, c := range []struct { - form string - plan *catalogue.Plan - want string - }{ - {FormPair, nil, "novox.keycloak.secret-postgres-database"}, - {FormShared, nil, "ace.postgres.secret-postgres-database"}, - {FormPair, providerPlan, "ace.postgres.backup-target,novox.keycloak.secret-postgres-database"}, - {"", nil, ""}, - // An offer with no credential (the lead, 2026-10-29): nothing minted, nothing to wait for. - {FormNone, nil, ""}, - } { - entry := catalogue.PlanProvision{Provision: "postgres-database", Provider: postgres.Node, ProviderModule: postgres.Module, - CredentialForm: c.form} - if got := strings.Join(DependsOn(keycloak, entry, c.plan), ","); got != c.want { - t.Errorf("%q depends on %q, want %q", c.form, got, c.want) - } - } - // The vault's own offer is the value: no secret-secret beside it. - vaultEntry := catalogue.PlanProvision{Provision: "secret", Provider: "novox", ProviderModule: VaultModule, CredentialForm: FormPair} - if got := DependsOn(keycloak, vaultEntry, nil); len(got) != 0 { - t.Errorf("the vault's secret depends on %v", got) - } - if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" { - t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead) - } -} diff --git a/go/testdata/seat-names.txt b/go/testdata/seat-names.txt index 3a67be4..086c3da 100644 --- a/go/testdata/seat-names.txt +++ b/go/testdata/seat-names.txt @@ -42,6 +42,4 @@ health/findings.go: mesh-healers health/findings.go: mesh-self-check internal/beside/beside.go: mesh-catalog overlay/overlay.go: the-private-network -provisions/provisions.go: mesh-provisioner -provisions/provisions.go: mesh-vault seatlist/seatlist.go: mesh-catalog