own-take-running has a person's switch, own_take_switch, kept in the vault's store (issue 1126, cause C4)

This commit is contained in:
2026-10-28 09:39:16 +01:00
parent 47ed384d97
commit 671a6a1f47
5 changed files with 112 additions and 8 deletions
+3 -1
View File
@@ -218,7 +218,9 @@ The replaced value is kept sealed as the record's `previous` (dropped by any lat
`secret-changed` and `own-replaced` are said, and the act is in `secret_handed` (`own-replaced`, `own-same`,
`own-take-refused`). A covered bus account so replaced has its hash written again. `own_drift` lists, from every
node's latest shadow answer (`<node>.node` in `node-engine.have`), each own secret whose node says another value,
with what the vault holds and whether the node's ask would be taken now.
with what the vault holds and whether the node's ask would be taken now. **The switch** is the vault's: a person
switches every machine's asks off or on with `own_take_switch` (`on`, `why`), kept in the vault's store over a
restart; it is on until switched off, and `own_drift` shows it.
## Events
@@ -415,6 +415,18 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
if _, err := take("node.anchor", pg, "a-value-older-than-the-vaults"); err == nil || !strings.Contains(err.Error(), "less than") {
t.Errorf("a running value over one the vault just took: %v", err)
}
// Switched off by a person, every ask is refused, saying who and why; on again, taken again.
later += time.Hour
if _, err := v.SwitchOwnTake("jochen", false, "the test"); err != nil {
t.Fatal(err)
}
if _, err := take("node.anchor", pg, "after-the-switch"); err == nil || !strings.Contains(err.Error(), "switched off (by jochen") {
t.Errorf("an ask while switched off: %v", err)
}
if _, err := v.SwitchOwnTake("jochen", true, "the test"); err != nil {
t.Fatal(err)
}
later -= time.Hour
// own_drift reads it from the node's latest shadow answer, mesh-wide.
answer := delivery.CandidateAnswer{Head: "h", Verdict: delivery.CandidateDiffers, Differs: []delivery.ModuleDiffers{{
Module: "postgres", Why: "the resource differs", Bodies: []delivery.ResourceBodies{{ID: "needs-superuser-password",
+77 -6
View File
@@ -22,8 +22,11 @@ package main
// (accounts.go), so the bus-accounts key is of the value the bus runs.
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -125,10 +128,12 @@ func (v *Vault) OwnTakeOpen(caller string, of OwnTakeOf) (Opened, error) {
if err != nil {
return Opened{}, err
}
refused := ""
if !held {
refused := v.ownTakeOff()
switch {
case refused != "":
case !held:
refused = fmt.Sprintf("the vault holds no secret named %s, so there is nothing to replace; nothing is taken", secret)
} else {
default:
refused = ownTakeRefusal(rec, v.Now())
}
if refused != "" {
@@ -237,9 +242,10 @@ type DriftNode struct {
// Drift is own_drift's answer.
type Drift struct {
Secrets []DriftRow `json:"secrets"`
Nodes []DriftNode `json:"nodes"`
Note string `json:"note"`
Switch OwnTakeSwitch `json:"switch"`
Secrets []DriftRow `json:"secrets"`
Nodes []DriftNode `json:"nodes"`
Note string `json:"note"`
}
// OwnDrift reads every node's own have key (`<node>.node`) and lists, from its latest answers to the placement
@@ -303,6 +309,10 @@ func (v *Vault) OwnDrift() (Drift, error) {
names = append(names, n)
}
sort.Strings(names)
if out.Switch, err = v.Store.OwnTakeSwitch(); err != nil {
return Drift{}, err
}
off := v.ownTakeOff()
for _, n := range names {
r := rows[n]
sort.Strings(r.Groups)
@@ -318,9 +328,70 @@ func (v *Vault) OwnDrift() (Drift, error) {
r.Previous = rec.Previous.Generation
}
r.Refused = ownTakeRefusal(rec, v.Now())
if off != "" {
r.Refused = off
}
r.Takeable = r.Refused == ""
}
out.Secrets = append(out.Secrets, *r)
}
return out, nil
}
// ---- own_take_switch ----------------------------------------------------------------------------------------
// OwnTakeSwitch is whether own-take-running takes values, who switched it last, when and why. On until a person
// switches it off; kept in the vault's own store, so it holds over a restart.
type OwnTakeSwitch struct {
On bool `json:"on"`
By string `json:"by,omitempty"`
At time.Time `json:"at,omitzero"`
Why string `json:"why,omitempty"`
}
func (s *Store) ownTakeSwitchPath() string { return filepath.Join(s.dir, "own-take-running.json") }
// OwnTakeSwitch is the switch as kept: on when nothing was ever switched.
func (s *Store) OwnTakeSwitch() (OwnTakeSwitch, error) {
raw, err := os.ReadFile(s.ownTakeSwitchPath())
if errors.Is(err, os.ErrNotExist) {
return OwnTakeSwitch{On: true}, nil
}
if err != nil {
return OwnTakeSwitch{}, err
}
var sw OwnTakeSwitch
if err := json.Unmarshal(raw, &sw); err != nil {
return OwnTakeSwitch{}, fmt.Errorf("the switch of %s is not readable: %w", OwnTakeRunningVerb, err)
}
return sw, nil
}
// SwitchOwnTake switches own-take-running on or off, a person's act with why; the caller is recorded as the bus
// proved it.
func (v *Vault) SwitchOwnTake(caller string, on bool, why string) (OwnTakeSwitch, error) {
why = strings.TrimSpace(why)
if why == "" || len(why) > 300 || strings.ContainsAny(why, "\r\n\x00") {
return OwnTakeSwitch{}, errors.New("say why in one short line; nothing was switched")
}
sw := OwnTakeSwitch{On: on, By: someoneOr(caller, stdio.Unproven), At: v.Now().UTC(), Why: why}
raw, _ := json.MarshalIndent(sw, "", " ")
if err := writeWhole(v.Store.ownTakeSwitchPath(), append(raw, '\n')); err != nil {
return OwnTakeSwitch{}, err
}
v.log("%s is switched %s by %s: %s", OwnTakeRunningVerb, map[bool]string{true: "on", false: "off"}[on], sw.By, why)
return sw, nil
}
// ownTakeOff says why own-take-running takes nothing now, or "".
func (v *Vault) ownTakeOff() string {
sw, err := v.Store.OwnTakeSwitch()
if err != nil {
return err.Error() + "; nothing is taken"
}
if !sw.On {
return fmt.Sprintf("%s is switched off (by %s at %s: %s); nothing is taken", OwnTakeRunningVerb, sw.By,
sw.At.Format(time.RFC3339), sw.Why)
}
return ""
}
@@ -218,6 +218,24 @@ func definitions(env *Env) []toolDef {
return v.OwnTakeOpen(caller, OwnTakeOf{Node: arg(a, "node"), Module: arg(a, "module"), Name: arg(a, "name"),
Secret: arg(a, "secret"), Why: arg(a, "why")})
}},
{Name: "own_take_switch",
Description: "Switch own-take-running on or off for every machine, a person's act with why (on until " +
"switched off; kept over a restart). While off, every machine's ask is refused, saying who switched it " +
"off and why. own_drift shows the switch.",
Input: map[string]any{"type": "object", "required": []string{"on", "why"}, "properties": map[string]any{
"on": map[string]any{"type": "boolean", "description": "true takes running values, false takes none"},
"why": str("why, in one line")}},
run: func(caller string, a map[string]any) (any, error) {
v, err := env.ready()
if err != nil {
return nil, err
}
on, ok := a["on"].(bool)
if !ok {
return nil, errors.New("say on: true or false; nothing was switched")
}
return v.SwitchOwnTake(caller, on, arg(a, "why"))
}},
{Name: "own_drift",
Description: "Every own secret whose node's latest shadow answer says the vault holds another value than runs " +
"there (the masked word \"another value\", issue 1126), for all nodes in one call: the secret, the " +
+2 -1
View File
@@ -165,7 +165,8 @@
"provisioner_delete",
"vault_status",
"vault_health",
"own_drift"
"own_drift",
"own_take_switch"
],
"claims": [
{