// 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). const ( FormPair = "pair" FormShared = "shared" FormIssued = "issued" FormGiven = "given" // FormNone is an offer whose consumers present no credential (catalogue.CredentialFormNone): the vault mints nothing, // and the provision depends on no credential. FormNone = "none" ) // 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"` 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 strictly: a field the layout does not name is refused, so no value, sealed or not, rides in // a provision (clause 1, ADR 0201 §13). func Decode(data []byte) (Provision, error) { var p Provision dec := json.NewDecoder(bytes.NewReader(data)) dec.DisallowUnknownFields() 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 }