mesh-vault: the ledger keeps the ledger; retirement moves to retirement.go (review 255)
mesh/merge-gate head 958018e696e4 merged into trunk 8aa37e8b835d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 958018e696e4 merged into trunk 8aa37e8b835d — fail: its merge-check.sh failed: files asking for a setting without saying whether it is…
mesh/delivery superseded: a newer head of the same pull request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 01:38:27 +00:00
co-authored by Claude Opus 5.5
parent b71e696993
commit 958018e696
2 changed files with 0 additions and 505 deletions
-281
View File
@@ -25,7 +25,6 @@ import (
"regexp"
"sort"
"strings"
"sync"
"time"
)
@@ -250,283 +249,3 @@ func DeliveredFingerprint(c Contribution) (string, int, error) {
}
return Fingerprint(v), len(v), nil
}
// EventRetirement is the one event a provider says about retirement (novox/hq ADR 0230 §35); the controller
// derives the permission to emit it for every module that receives contributions.
const EventRetirement = "provisioner.retirement"
// The rules of ADR 0230 §15 and §18.
const (
StablePasses = 5
StableFor = 10 * time.Minute
RetireAtOnce = 3
RetireFraction = 0.5
SayAgainEvery = time.Hour
)
// Custody is the pass over the contributions file that keeps the ledger, and what it remembers between
// passes about the holders the mesh no longer asks for.
type Custody struct {
Ledger *Ledger
Receives string
Emit func(event string, body map[string]any) error
Now func() time.Time
Log func(format string, args ...any)
mu sync.Mutex
key string
count int
firstSeen time.Time
asked map[string]bool
read bool
waiting *heldSet
rejected *heldSet
}
type heldSet struct {
set []string
since time.Time
saidAt time.Time
by, why string
}
// Pass records every delivered value, re-enables a retired holder asked for again, and counts the holders
// no longer asked for toward their retirement.
func (c *Custody) Pass() {
c.mu.Lock()
defer c.mu.Unlock()
asked, err := Contributions(c.Receives)
if err != nil {
c.Log("THE CONTRIBUTIONS FILE IS NOT USABLE (%s): %v — nothing is recorded or retired on this pass, and "+
"the count toward retiring anyone starts again", c.Receives, err)
c.key, c.count = "", 0
return
}
now := c.Now()
keep := map[string]bool{}
for _, a := range asked {
keep[a.As] = true
value, err := delivered(a)
if err != nil {
c.Log("%s: %v", a.As, err)
continue
}
held, outcome, err := c.Ledger.Record(a.As, a.Node, value, now)
if err != nil {
c.Log("%s: %v", a.As, err)
continue
}
if held.Retired != nil {
if err := c.Ledger.Mark(a.As, nil); err != nil {
c.Log("%s: asked for again, and its retirement mark could not be cleared: %v", a.As, err)
} else {
c.Log("%s: asked for again — re-enabled, its retirement mark cleared", a.As)
c.say(EventRetirement, map[string]any{"change": "reenabled", "consumers": []string{a.As}})
}
}
if outcome == Unchanged {
continue
}
event := "provisioned"
if outcome == Rotated {
event = "rotated"
}
c.Log("%s: %s (%s…, rotations %d)", held.As, outcome, held.Fingerprint[:19], held.Rotations)
c.say(event, map[string]any{"consumer": held.Consumer, "as": held.As, "fingerprint": held.Fingerprint, "rotations": held.Rotations})
}
c.asked, c.read = keep, true
c.countUnasked(now)
}
func (c *Custody) active() []Held {
var out []Held
for _, h := range c.Ledger.List() {
if h.Retired == nil {
out = append(out, h)
}
}
return out
}
func (c *Custody) countUnasked(now time.Time) {
active := c.active()
var unasked []string
for _, h := range active {
if !c.asked[h.As] {
unasked = append(unasked, h.As)
}
}
key := strings.Join(unasked, ",")
switch {
case len(unasked) == 0:
c.settle("every holder is asked for again")
c.key, c.count = "", 0
return
case key != c.key:
if c.waiting != nil || c.rejected != nil {
c.settle("the set the mesh no longer asks for changed")
}
c.key, c.count, c.firstSeen = key, 1, now
c.Log("no longer asked for: %s; retired once the same set holds for %d passes and %s (novox/hq ADR 0230)",
key, StablePasses, StableFor)
default:
c.count++
}
if c.rejected != nil || c.count < StablePasses || now.Sub(c.firstSeen) < StableFor {
return
}
if overTheBound(len(unasked), len(active)) {
c.wait(unasked, len(active), now)
return
}
c.retire(unasked, fmt.Sprintf("the mesh stopped asking for it: the same result in %d consecutive passes over %s",
c.count, now.Sub(c.firstSeen).Round(time.Second)), now)
}
func overTheBound(n, held int) bool {
return n > RetireAtOnce || (held > 1 && float64(n) > RetireFraction*float64(held))
}
func (c *Custody) wait(set []string, held int, now time.Time) {
if c.waiting == nil {
c.waiting = &heldSet{set: set, since: now}
c.Log("RETIREMENT WAITS FOR A PERSON: the mesh no longer asks for %d of the %d holder(s) (%s), more than %d "+
"at once or more than half. Nothing is retired until provisioner_retire_approve or "+
"provisioner_retire_reject (novox/hq ADR 0230)", len(set), held, strings.Join(set, ", "), RetireAtOnce)
} else if now.Sub(c.waiting.saidAt) < SayAgainEvery {
return
}
c.waiting.saidAt = now
c.say(EventRetirement, map[string]any{"change": "waiting", "consumers": set, "held": held,
"since": c.waiting.since.UTC().Format(time.RFC3339)})
}
func (c *Custody) settle(why string) {
var set []string
switch {
case c.waiting != nil:
set = c.waiting.set
case c.rejected != nil:
set = c.rejected.set
default:
return
}
c.waiting, c.rejected = nil, nil
c.Log("the set waiting on retirement (%s) is settled: %s", strings.Join(set, ", "), why)
c.say(EventRetirement, map[string]any{"change": "settled", "consumers": set, "why": why})
}
// retire marks each holder in set retired: the ledger keeps it, and its history, until a person deletes it.
func (c *Custody) retire(set []string, why string, now time.Time) {
at := now.UTC().Format(isoMillis)
var done []string
for _, as := range set {
if err := c.Ledger.Mark(as, &RetiredMark{At: at, Why: why}); err != nil {
c.Log("%s: could not be marked retired: %v", as, err)
continue
}
done = append(done, as)
}
c.key, c.count, c.waiting = "", 0, nil
if len(done) == 0 {
return
}
c.Log("retired, kept in the ledger until a person deletes it: %s — %s", strings.Join(done, ", "), why)
c.say(EventRetirement, map[string]any{"change": "retired", "consumers": done, "why": why})
}
// Approve retires exactly the set waiting (or rejected) for a person.
func (c *Custody) Approve(by, why string) ([]string, error) {
c.mu.Lock()
defer c.mu.Unlock()
if strings.TrimSpace(why) == "" {
return nil, errors.New("an approval says why")
}
set := c.waiting
if set == nil {
set = c.rejected
}
if set == nil {
return nil, errors.New("no set waits for retirement")
}
c.say(EventRetirement, map[string]any{"change": "approved", "consumers": set.set, "by": by, "why": why})
members := set.set
c.rejected = nil
c.retire(members, "approved by "+by+": "+why, c.Now())
return members, nil
}
// Reject keeps the waiting set active, and asks no more about it while it stays the same set.
func (c *Custody) Reject(by, why string) ([]string, error) {
c.mu.Lock()
defer c.mu.Unlock()
if strings.TrimSpace(why) == "" {
return nil, errors.New("a rejection says why")
}
if c.waiting == nil {
return nil, errors.New("no set waits for retirement")
}
c.rejected = &heldSet{set: c.waiting.set, since: c.Now(), by: by, why: why}
c.waiting = nil
c.Log("retirement of %s rejected by %s: %s", strings.Join(c.rejected.set, ", "), by, why)
c.say(EventRetirement, map[string]any{"change": "rejected", "consumers": c.rejected.set, "by": by, "why": why})
return c.rejected.set, nil
}
// Delete forgets one retired holder, on a person's word. Refused: an active holder, one the mesh asks for,
// and any without a why.
func (c *Custody) Delete(as, by, why string) error {
c.mu.Lock()
defer c.mu.Unlock()
if strings.TrimSpace(why) == "" {
return errors.New("a deletion says why")
}
h, ok := c.Ledger.Get(as)
if !ok {
return fmt.Errorf("the ledger holds nothing for %s", as)
}
if h.Retired == nil {
return fmt.Errorf("%s is active, and only a retired holder is deleted (novox/hq ADR 0230 §31)", as)
}
if c.asked[as] {
return fmt.Errorf("%s is asked for, and is not deleted", as)
}
if !c.Ledger.Delete(as) {
return fmt.Errorf("%s could not be deleted", as)
}
c.Log("%s deleted from the ledger by %s: %s", as, by, why)
c.say(EventRetirement, map[string]any{"change": "deleted", "consumers": []string{as}, "by": by, "why": why})
return nil
}
// Retirement is what is held, waiting, rejected and retired.
func (c *Custody) Retirement() map[string]any {
c.mu.Lock()
defer c.mu.Unlock()
active, retired := []string{}, []map[string]any{}
for _, h := range c.Ledger.List() {
if h.Retired == nil {
active = append(active, h.As)
} else {
retired = append(retired, map[string]any{"consumer": h.As, "node": h.Consumer, "retired_at": h.Retired.At, "why": h.Retired.Why})
}
}
out := map[string]any{"active": active, "retired": retired, "stable_passes": StablePasses,
"stable_for_seconds": int(StableFor.Seconds()), "counting": c.key, "passes": c.count, "waiting": nil, "rejected": nil}
if c.waiting != nil {
out["waiting"] = map[string]any{"consumers": c.waiting.set, "since": c.waiting.since.UTC().Format(time.RFC3339)}
}
if c.rejected != nil {
out["rejected"] = map[string]any{"consumers": c.rejected.set, "by": c.rejected.by, "why": c.rejected.why}
}
return out
}
func (c *Custody) say(event string, body map[string]any) {
if c.Emit == nil {
return
}
if err := c.Emit(event, body); err != nil {
c.Log("%s could not be said: %v", event, err)
}
}
@@ -120,230 +120,6 @@ func TestWhatTheMeshDeliversIsFingerprintedNeverReturned(t *testing.T) {
}
}
// custodyAt is a custody pass over a contributions file in dir, on a clock the test moves.
type custodyAt struct {
*Custody
bus *published
receives string
dir string
clock *time.Time
}
func newCustody(t *testing.T) custodyAt {
dir := t.TempDir()
l, _ := NewLedger(filepath.Join(dir, "ledger"))
bus := &published{}
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
c := custodyAt{bus: bus, receives: filepath.Join(dir, "mesh.json"), dir: dir, clock: &clock}
c.Custody = &Custody{Ledger: l, Receives: c.receives, Emit: bus.emit, Now: func() time.Time { return *c.clock }, Log: t.Logf}
return c
}
// ask writes a contributions file asking for these logins, each with its value.
func (c custodyAt) ask(values map[string]string) {
given := []map[string]string{}
for as, v := range values {
f := filepath.Join(c.dir, as+".secret")
_ = os.WriteFile(f, []byte(v), 0o600)
given = append(given, map[string]string{"node": "anchor", "as": as, "secret": f})
}
doc, _ := json.Marshal(map[string]any{"requirement": "secret", "given": given})
_ = os.WriteFile(c.receives, doc, 0o600)
}
// passes runs n passes a minute apart.
func (c custodyAt) passes(n int) {
for i := 0; i < n; i++ {
c.Pass()
*c.clock = c.clock.Add(time.Minute)
}
}
func (c custodyAt) changes() []string {
var out []string
for _, e := range c.bus.events {
if e["event"] == EventRetirement {
out = append(out, e["body"].(map[string]any)["change"].(string))
} else {
out = append(out, e["event"].(string))
}
}
return out
}
// The custody pass: a delivered value is recorded and announced by fingerprint, a changed one is a rotation.
func TestTheCustodyPassRecordsAndRotates(t *testing.T) {
c := newCustody(t)
c.ask(map[string]string{"anchor-redis": "v1"})
c.passes(1)
c.ask(map[string]string{"anchor-redis": "v2"})
c.passes(1)
if got := strings.Join(c.changes(), ","); got != "provisioned,rotated" {
t.Errorf("announced %s", got)
}
for _, raw := range c.bus.raw {
if strings.Contains(raw, `"v1"`) || strings.Contains(raw, `"v2"`) {
t.Errorf("announced a value: %s", raw)
}
}
}
// A contributions file that does not say who is asked for — unreadable, not JSON, without a given list, a
// null one, or for another requirement — is no answer: nobody is retired, and the count starts again.
func TestAFileThatSaysNothingRetiresNobody(t *testing.T) {
for _, doc := range []string{"{not json", `{"requirement":"secret"}`, `{"requirement":"secret","given":null}`,
`{"requirement":"redis-cache","given":[]}`} {
c := newCustody(t)
c.ask(map[string]string{"anchor-redis": "v1"})
c.passes(1)
_ = os.WriteFile(c.receives, []byte(doc), 0o600)
c.passes(30)
if h, ok := c.Ledger.Get("anchor-redis"); !ok || h.Retired != nil {
t.Errorf("%s: the holder was retired or lost: %+v %v", doc, h, ok)
}
if _, err := Contributions(c.receives); err == nil {
t.Errorf("%s was read as an answer", doc)
}
}
if _, err := Contributions(filepath.Join(t.TempDir(), "missing.json")); err == nil {
t.Error("a missing file was read as an answer")
}
}
// A holder the mesh stops asking for is retired only after five passes AND ten minutes, is never deleted
// by a pass, keeps its history, and is re-enabled when asked for again.
func TestAHolderNoLongerAskedForIsRetiredNotDeleted(t *testing.T) {
c := newCustody(t)
c.ask(map[string]string{"a-app": "1", "b-app": "2", "c-app": "3"})
c.passes(1)
c.ask(map[string]string{"a-app": "1", "b-app": "2"})
c.Pass() // the first pass that sees c-app unasked
for i := 0; i < 8; i++ {
*c.clock = c.clock.Add(10 * time.Second) // five passes, but in under ten minutes
c.Pass()
}
if h, _ := c.Ledger.Get("c-app"); h.Retired != nil {
t.Fatal("retired before ten minutes had passed")
}
*c.clock = c.clock.Add(10 * time.Minute)
c.Pass()
h, ok := c.Ledger.Get("c-app")
if !ok || h.Retired == nil || h.Retired.Why == "" {
t.Fatalf("not retired, or retired without a why: %+v %v", h, ok)
}
if h.Fingerprint != Fingerprint("3") {
t.Error("retiring lost what the ledger knew of the holder")
}
if _, err := os.Stat(filepath.Join(c.dir, "ledger", "c-app.json")); err != nil {
t.Error("a pass deleted a ledger entry")
}
c.ask(map[string]string{"a-app": "1", "b-app": "2", "c-app": "3"})
c.Pass()
if h, _ := c.Ledger.Get("c-app"); h.Retired != nil {
t.Error("asked for again, the holder stayed retired")
}
if got := strings.Join(c.changes(), ","); got != "provisioned,provisioned,provisioned,retired,reenabled" {
t.Errorf("announced %s", got)
}
}
// A different set, or a file that says nothing, starts the count again.
func TestTheCountStartsAgain(t *testing.T) {
c := newCustody(t)
c.ask(map[string]string{"a-app": "1", "b-app": "2", "c-app": "3"})
c.passes(1)
c.ask(map[string]string{"a-app": "1", "b-app": "2"})
c.passes(4)
_ = os.WriteFile(c.receives, []byte(`{"given":null}`), 0o600)
c.passes(1)
c.ask(map[string]string{"a-app": "1", "b-app": "2"})
c.passes(4)
*c.clock = c.clock.Add(time.Hour)
if h, _ := c.Ledger.Get("c-app"); h.Retired != nil {
t.Fatal("retired though the count was broken")
}
c.passes(1)
if h, _ := c.Ledger.Get("c-app"); h.Retired == nil {
t.Fatal("not retired after five unbroken passes and an hour")
}
}
// Too many at once waits for a person: approve retires exactly that set, reject keeps it.
func TestTooManyAtOnceWaitsForAPerson(t *testing.T) {
all := map[string]string{"a-app": "1", "b-app": "2", "c-app": "3", "d-app": "4", "e-app": "5"}
for _, decide := range []string{"approve", "reject"} {
c := newCustody(t)
c.ask(all)
c.passes(1)
c.ask(map[string]string{"a-app": "1"})
c.passes(5)
*c.clock = c.clock.Add(time.Hour)
c.passes(1)
r := c.Retirement()
if r["waiting"] == nil {
t.Fatalf("four of five no longer asked for did not wait: %v", r)
}
for _, h := range c.Ledger.List() {
if h.Retired != nil {
t.Fatalf("%s retired while the set waited", h.As)
}
}
if _, err := c.Approve("person.jochen", ""); err == nil {
t.Error("an approval without a why was taken")
}
if decide == "approve" {
set, err := c.Approve("person.jochen", "the four apps are gone")
if err != nil || len(set) != 4 {
t.Fatalf("%v %v", set, err)
}
if h, _ := c.Ledger.Get("b-app"); h.Retired == nil {
t.Error("approved, not retired")
}
if h, _ := c.Ledger.Get("a-app"); h.Retired != nil {
t.Error("an asked-for holder was retired")
}
} else {
if _, err := c.Reject("person.jochen", "they come back"); err != nil {
t.Fatal(err)
}
c.passes(10)
if h, _ := c.Ledger.Get("b-app"); h.Retired != nil {
t.Error("rejected, but retired")
}
if c.Retirement()["waiting"] != nil {
t.Error("a rejected set was asked about again")
}
}
}
}
// Only a person deletes, with a why, and only a retired holder the mesh no longer asks for.
func TestOnlyAPersonDeletesARetiredHolder(t *testing.T) {
c := newCustody(t)
c.ask(map[string]string{"a-app": "1", "b-app": "2", "c-app": "3"})
c.passes(1)
if err := c.Delete("c-app", "person.jochen", "gone"); err == nil {
t.Error("an active holder was deleted")
}
c.ask(map[string]string{"a-app": "1", "b-app": "2"})
c.passes(5)
*c.clock = c.clock.Add(time.Hour)
c.passes(1)
if err := c.Delete("c-app", "person.jochen", ""); err == nil {
t.Error("deleted without a why")
}
if err := c.Delete("c-app", "person.jochen", "gitea's redis is gone for good"); err != nil {
t.Fatal(err)
}
if _, ok := c.Ledger.Get("c-app"); ok {
t.Error("deleted, but still in the ledger")
}
last := c.changes()
if last[len(last)-1] != "deleted" {
t.Errorf("announced %v", last)
}
}
func logins(hs []Held) string {
var out []string
for _, h := range hs {