F-delivery 7: the builder's old-form window steps by mesh-sdk's delivery.StepWindow

A read naming no node (an empty node_haves, the placement not read yet) closed the window for good (security
review F3, second of a kind). The window embeds delivery.WindowHeld and steps by the shared rule, named being
the placement's nodes before any is dropped. The builder's expiring keys share one state, bounds (the publish
bound of F-delivery 5 renamed), for the old-form bound next.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 16:40:44 +01:00
parent 78fc3ffa35
commit 00903e9149
10 changed files with 91 additions and 65 deletions
@@ -159,8 +159,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}
}}
// A publish that ends badly is a finding (design 51 *Health and conditions*): a holder's refused, a version no holder
// answered within its bound (a key of publish-bounds with its own time to live), a seat's outcome that does not read.
pubBoundsKV := process.AwaitBucket(ctx, js, conn, versions.PublishBoundsBucket, "a publish no holder answers is not raised", logger.Printf)
// answered within its bound (a key of the state bounds with its own time to live), a seat's outcome that does not read.
pubBoundsKV := process.AwaitBucket(ctx, js, conn, versions.BoundsBucket, "a publish no holder answers, and a node holding the old form seven days, are not raised", logger.Printf)
pubs := &publishes{bounds: pubBoundsKV.KV, versions: numberer.Versions, findings: found, bound: versions.PublishBound, logf: logger.Printf}
numberer.Publishing = pubs.arm
b := &build.Builder{
@@ -373,7 +373,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
pubBoundsKV.Ready(func(kv jetstream.KeyValue) {
each := func(e jetstream.KeyValueEntry) { pubs.ended(ctx, e) }
if pw, err := settle(ctx, kv, each); err != nil {
process.NotWatched(logger.Printf, versions.PublishBoundsBucket, err, "a publish no holder answers is not raised until the builder starts again")
process.NotWatched(logger.Printf, versions.BoundsBucket, err, "a publish no holder answers, and a node holding the old form seven days, are not raised until the builder starts again")
} else {
go func() { errs <- follow(ctx, pw, each) }()
}
@@ -80,11 +80,13 @@ func (o *oldForm) dropped() map[string]bool {
}
// step moves the window to what counted states.
func (o *oldForm) step(ctx context.Context, counted map[string][]string) {
// named is every node the read named (the placement's nodes before any is dropped), counted the statements of those
// still counted: a read naming no node judges nothing (delivery.StepWindow), so it never closes the window.
func (o *oldForm) step(ctx context.Context, named []string, counted map[string][]string) {
o.mu.Lock()
defer o.mu.Unlock()
before := o.w
next, closed, overdue := o.w.Step(versions.Holding(o.forms, counted, o.w.DroppedSet()), o.now())
next, closed, overdue := o.w.Step(named, versions.Holding(o.forms, counted, o.w.DroppedSet()), o.now())
o.w = next
if !slices.Equal(before.Holding, next.Holding) || before.Open != next.Open || closed {
o.keep(ctx)
@@ -14,7 +14,7 @@ import (
)
// publishes raises and clears the builder's findings of a publish that ended badly (versions/publish.go): a holder's
// refused, a version unanswered past its bound, kept as a key of publish-bounds with its own time to live, and a seat's
// refused, a version unanswered past its bound, kept as a key of the state bounds with its own time to live, and a seat's
// outcome that does not read.
type publishes struct {
bounds func() jetstream.KeyValue
@@ -28,10 +28,10 @@ type publishes struct {
func (p *publishes) arm(ctx context.Context, e versions.Entry) {
kv := p.bounds()
if kv == nil {
p.logf("the publish bound of %s@%s is not written (%s is not on the bus yet), so it is not raised if no holder answers", e.Name, e.Version, versions.PublishBoundsBucket)
p.logf("the publish bound of %s@%s is not written (%s is not on the bus yet), so it is not raised if no holder answers", e.Name, e.Version, versions.BoundsBucket)
return
}
if _, err := kv.Create(ctx, versions.Key(e.Name, e.Version), []byte(`{}`), jetstream.KeyTTL(p.bound)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) {
if _, err := kv.Create(ctx, versions.PublishBoundKey(e.Name, e.Version), []byte(`{}`), jetstream.KeyTTL(p.bound)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) {
p.logf("the publish bound of %s@%s is not written, so it is not raised if no holder answers: %v", e.Name, e.Version, err)
}
}
@@ -41,7 +41,7 @@ func (p *publishes) arm(ctx context.Context, e versions.Entry) {
func (p *publishes) heard(ctx context.Context, seat, event string, o versions.Outcome) {
if o.Version != "" {
if kv := p.bounds(); kv != nil {
if err := kv.Delete(ctx, versions.Key(o.Name, o.Version)); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
if err := kv.Delete(ctx, versions.PublishBoundKey(o.Name, o.Version)); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
p.logf("the publish bound of %s@%s is not ended: %v", o.Name, o.Version, err)
}
}
@@ -78,13 +78,14 @@ func (p *publishes) read(ctx context.Context, seat string) {
p.findings.clear(ctx, versions.SeatOutcomeUnread, health.ScopeModule, seat)
}
// ended is told each change of publish-bounds: a bound whose time ended (the bus's marker) on a version still numbered
// ended is told each change of the state bounds: a bound whose time ended (the bus's marker) on a version still numbered
// raises publish-unanswered.
func (p *publishes) ended(ctx context.Context, e jetstream.KeyValueEntry) {
if e.Operation() != jetstream.KeyValuePurge {
key, ok := versions.PublishBoundOf(e.Key())
if e.Operation() != jetstream.KeyValuePurge || !ok {
return
}
raw, _, found, err := p.versions.Store.Get(ctx, e.Key())
raw, _, found, err := p.versions.Store.Get(ctx, key)
if err != nil || !found {
return
}
@@ -53,7 +53,14 @@ func (r *engineReads) stepWindow() {
ctx = context.Background()
}
_, counted := r.forms()
r.window.step(ctx, counted)
r.window.step(ctx, r.named(), counted)
}
// named are the nodes the placement names, none dropped: what the window's step judges a read by.
func (r *engineReads) named() []string {
r.mu.Lock()
defer r.mu.Unlock()
return slices.Clone(r.counted)
}
// bind keeps the statements in kv and hears it: every node's last statement at the start, a person's ask again and drop
@@ -138,7 +145,7 @@ func (r *engineReads) built() (present, counted map[string][]string) {
if ctx == nil {
ctx = context.Background()
}
r.window.step(ctx, counted)
r.window.step(ctx, r.named(), counted)
}
return present, counted
}
@@ -361,7 +361,7 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) {
if got, err := health.DecodeFinding(st.vals[refusedKey]); err != nil || !strings.Contains(got.Summary, "refused to publish m/program@0.1.3") || !strings.Contains(got.Summary, "bytes differ") {
t.Fatalf("a holder's refused: the finding = %+v, %v", got, err)
}
pubs.ended(context.Background(), purged{key: versions.Key("m/program", "0.1.4")})
pubs.ended(context.Background(), purged{key: versions.PublishBoundKey("m/program", "0.1.4")})
unansweredKey := health.FindingKey(health.ScopeModule, "m/program", versions.PublishUnanswered)
if got, err := health.DecodeFinding(st.vals[unansweredKey]); err != nil || !strings.Contains(got.Summary, "neither published nor refused of m/program@0.1.4") {
t.Fatalf("a bound ended unanswered: the finding = %+v, %v", got, err)
@@ -399,19 +399,24 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) {
reads := &engineReads{keeper: &enginereads.Keeper{}, window: window, counted: []string{"a", "b"}}
newest := []string{forms.FormNeed("oci")}
counted := map[string][]string{"a": newest, "b": nil}
window.step(context.Background(), counted)
window.step(context.Background(), []string{"a", "b"}, counted)
heldKey := health.FindingKey(health.ScopeMachine, "b", versions.OldFormHeld)
if !window.w.Open || !slices.Equal(window.w.Holding, []string{"b"}) || st.vals[heldKey] != nil {
t.Fatalf("one node lacking the newest form: %+v", window.w)
}
// Security-upgrade F3 (review round 1): an empty node_haves read, naming no node, leaves the window open.
window.step(context.Background(), nil, map[string][]string{})
if !window.w.Open || !slices.Equal(window.w.Holding, []string{"b"}) || len(closings) != 0 {
t.Fatalf("an empty read closed the window: %+v, closings %+v", window.w, closings)
}
now = now.Add(versions.FindingAfter)
window.step(context.Background(), counted)
window.step(context.Background(), []string{"a", "b"}, counted)
if got, err := health.DecodeFinding(st.vals[heldKey]); err != nil || !strings.Contains(got.Summary, "the node-engine of b") {
t.Fatalf("seven days held: the finding = %+v, %v", got, err)
}
counted["b"] = newest
window.step(context.Background(), counted)
window.step(context.Background(), counted)
window.step(context.Background(), []string{"a", "b"}, counted)
window.step(context.Background(), []string{"a", "b"}, counted)
if window.w.Open || len(closings) != 1 || !slices.Equal(closings[0].LastHolding, []string{"b"}) || st.vals[heldKey] != nil {
t.Errorf("the node back with the newest form: %+v, closings %+v, finding left %v", window.w, closings, st.vals[heldKey] != nil)
}