A want names the release whose Approve answered its build's record ask, and a have waiting for a yes says whether the move changes the build alone (ADR 0601 §1)

This commit is contained in:
2026-11-03 16:41:06 +01:00
parent 439b77e07d
commit 636c2c1b0c
4 changed files with 27 additions and 6 deletions
+5 -1
View File
@@ -193,6 +193,10 @@ type Want struct {
// of policy record that the node agent judged to recreate a container or move its data is applied only when its
// want names the composition so allowed. Empty when none was asked.
Yes string `json:"yes,omitempty"`
// Release names the release whose Approve answered the record ask of this want's build (novox/hq ADR 0601 §1): the
// group of pull requests mesh-delivery's gate merged on that Approve. Set with the yes it gave; empty when a person's
// answer to a record ask gave it, or none was needed.
Release string `json:"release,omitempty"`
// Self says the node composes this module itself (ADR 0547 §2): the want names the build alone, and no composition.
// A want that is not Self names the one composition it allows (ADR 0539 clause 1), as before.
Self bool `json:"self,omitempty"`
@@ -200,7 +204,7 @@ type Want struct {
func (w Want) check() error {
if w.Removed {
if w.FP != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" {
if w.FP != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" || w.Release != "" {
return fmt.Errorf("delivery: a removed want names no fingerprint, composition, rule, backup answer or put")
}
return nil
+7 -2
View File
@@ -351,6 +351,11 @@ type Have struct {
// composition that waits for a person's yes (ADR 0547 §3); empty otherwise.
Judgement Judgement `json:"judgement,omitempty"`
Waits string `json:"waits,omitempty"`
// BuildAlone says, in phase waiting-yes, that the move changes the module's build alone (novox/hq ADR 0601 §1): the
// node composed the build it runs again with today's settings and secrets and got the composition it runs, so the
// release that carries the new build answers the yes. False when a setting or a secret changed too, or the node could
// not tell: the move is then asked of a person as before.
BuildAlone bool `json:"build-alone,omitempty"`
// Sealed is, for a module the node composes itself, each value its running composition carries sealed to the node's
// key (a secret, a credential, a vault box), as applied (ADR 0547 §3, issue 1126): what the node's composer reuses
// after a restart while it opens to the vault's value, so a value sealed again moves no composition. Openable by the
@@ -476,8 +481,8 @@ func decode(data []byte, into any) error {
func (h Have) check() error {
switch h.Phase {
case PhaseBackingUp, PhaseApplying, PhaseChecking, PhaseRunning, PhaseFailed, PhaseRemoved, PhaseHeld:
if h.Judgement != "" || h.Waits != "" {
return fmt.Errorf("delivery: only a have waiting for a yes names a judgement and the composition waiting")
if h.Judgement != "" || h.Waits != "" || h.BuildAlone {
return fmt.Errorf("delivery: only a have waiting for a yes names a judgement, the composition waiting and whether it changes the build alone")
}
case PhaseWaitingYes:
if h.Judgement != JudgedRecreates && h.Judgement != JudgedMovesData && h.Judgement != JudgedCannot {
+12 -3
View File
@@ -380,10 +380,19 @@ func testPhaseHealthRefused(t *testing.T) {
} else if back, err := DecodeHave(raw); err != nil || back.Judgement != JudgedRecreates || back.Waits != waits.Waits {
t.Errorf("a have waiting for a yes came back %+v, %v", back, err)
}
// ADR 0601 §1: and whether the move changes the build alone, read back as said.
alone := waits
alone.BuildAlone = true
if raw, err := EncodeHave(alone); err != nil {
t.Errorf("a have waiting for a yes to its build alone: %v", err)
} else if back, err := DecodeHave(raw); err != nil || !back.BuildAlone {
t.Errorf("a have waiting for a yes to its build alone came back %+v, %v", back, err)
}
for name, h := range map[string]Have{
"neither judged": {Phase: PhaseWaitingYes, Health: HealthOK, Judgement: JudgedNeither, Waits: waits.Waits},
"no waiting digest": {Phase: PhaseWaitingYes, Health: HealthOK, Judgement: JudgedMovesData},
"running judged": {Phase: PhaseRunning, Health: HealthOK, Judgement: JudgedRecreates},
"running build alone": {Phase: PhaseRunning, Health: HealthOK, BuildAlone: true},
"neither judged": {Phase: PhaseWaitingYes, Health: HealthOK, Judgement: JudgedNeither, Waits: waits.Waits},
"no waiting digest": {Phase: PhaseWaitingYes, Health: HealthOK, Judgement: JudgedMovesData},
"running judged": {Phase: PhaseRunning, Health: HealthOK, Judgement: JudgedRecreates},
} {
if _, err := EncodeHave(h); err == nil {
t.Errorf("%s: encoded", name)
+3
View File
@@ -24,6 +24,8 @@ func TestAWantNamesItsComposition(t *testing.T) {
// ADR 0547 §2, §3 (issue 1126): from the second milestone a want names the build alone, and the yes a person gave.
{FP: fp2of('a'), Self: true},
{FP: fp2of('a'), Self: true, Yes: hashOf('3')},
// ADR 0601 §1: the yes the release's Approve gave names the release.
{FP: fp2of('a'), Self: true, Yes: hashOf('3'), Release: "feat/l3b-bus"},
} {
raw, err := EncodeWant(w)
if err != nil {
@@ -43,6 +45,7 @@ func TestAWantNamesItsComposition(t *testing.T) {
{Want{FP: fp2of('a'), Self: true, Composed: hashOf('1')}, "names a composition"},
{Want{FP: fp2of('a'), Self: true, Yes: "sha256:12"}, "a want's yes allows"},
{Want{Removed: true, Self: true}, "removed want names no"},
{Want{Removed: true, Release: "feat/l3b-bus"}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: "sha256:12"}, "not sha256"},
{Want{Removed: true, Composed: hashOf('1')}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: hashOf('1'), Backup: "later"}, `backup answer "later"`},