D1: the build step part gains the state again, run, again-at, passing and the verb drop; the three build seats regenerated (ADR 0664 §1, §4, issue 1748)

Shared lines for the join with milestone G: contracts/parts/build-step.json, the three seats' fixtures and generated contract files.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-09 22:54:42 +01:00
parent 29e491e715
commit 440d02afb3
22 changed files with 599 additions and 13 deletions
+71 -3
View File
@@ -18,6 +18,7 @@ const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
VerbDrop = "drop"
)
const (
@@ -28,7 +29,7 @@ const (
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
return []string{VerbCurrent, VerbPause, VerbResume, VerbDrop}
}
// AcceptSubjectDelivery is where delivery is sent.
@@ -117,6 +118,41 @@ func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error)
return v, nil
}
// PausesByDroppedIDKey is the key of one value of the state pauses under the form "dropped.<id>".
func PausesByDroppedIDKey(id string) string { return "dropped." + id }
// ParsePausesByDroppedIDKey reads the parts of a key of the form "dropped.<id>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByDroppedIDKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "dropped.") {
return "", false
}
rest = rest[8:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByDroppedIDValue reads one value of the state pauses under the form "dropped.<id>", refusing a field PausesByDroppedIDValue does not know and any text after the value.
func DecodePausesByDroppedIDValue(raw []byte) (PausesByDroppedIDValue, error) {
var v PausesByDroppedIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
@@ -245,6 +281,16 @@ type ComposeAnswer struct {
Verdict any `json:"verdict,omitempty"`
}
// Drop is an ask a caller no longer waits for.
type Drop struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
@@ -433,19 +479,25 @@ type RunProgram struct {
// Step is one step a build module runs on a node.
type Step struct {
// when the bus gives the ask again, for the state again (RFC 3339 time)
AgainAt *time.Time `json:"again-at,omitempty"`
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so
Passing bool `json:"passing,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// which run of the ask this is, counted by the bus's delivery count (1 for the first)
Run int64 `json:"run,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
// running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
@@ -588,6 +640,19 @@ type ResumeInput struct {
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DropInput is what drop takes.
type DropInput struct {
// who drops it
By string `json:"by,omitempty"`
// the ask's id
ID string `json:"id"`
// why the caller no longer waits
Why string `json:"why"`
}
// DropAnswer is what drop answers.
type DropAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
@@ -603,6 +668,9 @@ type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByDroppedIDValue is one value of the state pauses under the key dropped.<id>.
type PausesByDroppedIDValue = Drop
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
@@ -53,6 +53,12 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.check$")},
{name: "a finished run step whose program exited non-zero", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"r5e6\",\"node\":\"ace\",\"result\":{\"built\":true,\"exit\":2,\"key\":\"program-0123456789abcdef\",\"said\":\"the facts name no head\"},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"failed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"r5e6\",\"node\":\"ace\",\"result\":{\"built\":true,\"exit\":2,\"key\":\"program-0123456789abcdef\",\"said\":\"the facts name no head\"},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"failed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a step ended on a transport fault, given again by the bus", subject: "", given: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}", wire: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask dropped by the caller that no longer waits", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v PausesByDroppedIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+71 -3
View File
@@ -18,6 +18,7 @@ const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
VerbDrop = "drop"
)
const (
@@ -28,7 +29,7 @@ const (
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
return []string{VerbCurrent, VerbPause, VerbResume, VerbDrop}
}
// AcceptSubjectDelivery is where delivery is sent.
@@ -117,6 +118,41 @@ func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error)
return v, nil
}
// PausesByDroppedIDKey is the key of one value of the state pauses under the form "dropped.<id>".
func PausesByDroppedIDKey(id string) string { return "dropped." + id }
// ParsePausesByDroppedIDKey reads the parts of a key of the form "dropped.<id>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByDroppedIDKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "dropped.") {
return "", false
}
rest = rest[8:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByDroppedIDValue reads one value of the state pauses under the form "dropped.<id>", refusing a field PausesByDroppedIDValue does not know and any text after the value.
func DecodePausesByDroppedIDValue(raw []byte) (PausesByDroppedIDValue, error) {
var v PausesByDroppedIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
@@ -245,6 +281,16 @@ type ComposeAnswer struct {
Verdict any `json:"verdict,omitempty"`
}
// Drop is an ask a caller no longer waits for.
type Drop struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
@@ -433,19 +479,25 @@ type RunProgram struct {
// Step is one step a build module runs on a node.
type Step struct {
// when the bus gives the ask again, for the state again (RFC 3339 time)
AgainAt *time.Time `json:"again-at,omitempty"`
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so
Passing bool `json:"passing,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// which run of the ask this is, counted by the bus's delivery count (1 for the first)
Run int64 `json:"run,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
// running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
@@ -588,6 +640,19 @@ type ResumeInput struct {
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DropInput is what drop takes.
type DropInput struct {
// who drops it
By string `json:"by,omitempty"`
// the ask's id
ID string `json:"id"`
// why the caller no longer waits
Why string `json:"why"`
}
// DropAnswer is what drop answers.
type DropAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
@@ -603,6 +668,9 @@ type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByDroppedIDValue is one value of the state pauses under the key dropped.<id>.
type PausesByDroppedIDValue = Drop
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
@@ -39,6 +39,12 @@ func TestFixtures(t *testing.T) {
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-image.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.accept\\.delivery$")},
{name: "a step ended on a transport fault, given again by the bus", subject: "", given: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}", wire: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask dropped by the caller that no longer waits", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v PausesByDroppedIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+71 -3
View File
@@ -18,6 +18,7 @@ const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
VerbDrop = "drop"
)
const (
@@ -28,7 +29,7 @@ const (
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
return []string{VerbCurrent, VerbPause, VerbResume, VerbDrop}
}
// AcceptSubjectDelivery is where delivery is sent.
@@ -117,6 +118,41 @@ func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error)
return v, nil
}
// PausesByDroppedIDKey is the key of one value of the state pauses under the form "dropped.<id>".
func PausesByDroppedIDKey(id string) string { return "dropped." + id }
// ParsePausesByDroppedIDKey reads the parts of a key of the form "dropped.<id>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByDroppedIDKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "dropped.") {
return "", false
}
rest = rest[8:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByDroppedIDValue reads one value of the state pauses under the form "dropped.<id>", refusing a field PausesByDroppedIDValue does not know and any text after the value.
func DecodePausesByDroppedIDValue(raw []byte) (PausesByDroppedIDValue, error) {
var v PausesByDroppedIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
@@ -245,6 +281,16 @@ type ComposeAnswer struct {
Verdict any `json:"verdict,omitempty"`
}
// Drop is an ask a caller no longer waits for.
type Drop struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
@@ -433,19 +479,25 @@ type RunProgram struct {
// Step is one step a build module runs on a node.
type Step struct {
// when the bus gives the ask again, for the state again (RFC 3339 time)
AgainAt *time.Time `json:"again-at,omitempty"`
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so
Passing bool `json:"passing,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// which run of the ask this is, counted by the bus's delivery count (1 for the first)
Run int64 `json:"run,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
// running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
@@ -588,6 +640,19 @@ type ResumeInput struct {
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DropInput is what drop takes.
type DropInput struct {
// who drops it
By string `json:"by,omitempty"`
// the ask's id
ID string `json:"id"`
// why the caller no longer waits
Why string `json:"why"`
}
// DropAnswer is what drop answers.
type DropAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
@@ -603,6 +668,9 @@ type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByDroppedIDValue is one value of the state pauses under the key dropped.<id>.
type PausesByDroppedIDValue = Drop
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
@@ -43,6 +43,12 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.accept\\.check$")},
{name: "a finished check step of a TypeScript merge preview", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a step ended on a transport fault, given again by the bus", subject: "", given: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}", wire: "{\"again-at\":\"2026-11-05T10:01:30Z\",\"ended\":\"2026-11-05T10:00:30Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"passing\":true,\"reason\":\"fetching the commit: kex_exchange_identification: read: Connection reset by peer\",\"run\":1,\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"again\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask dropped by the caller that no longer waits", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"mesh-delivery-builder\",\"v\":1,\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v PausesByDroppedIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}",
decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+68 -1
View File
@@ -38,6 +38,36 @@
"type": "object",
"description": "whether the node is paused"
}
},
{
"name": "drop",
"description": "End an ask this node holds for a caller that no longer waits for it: a step not yet running is refused when it is pulled, one running is cancelled and ends refused, naming who dropped it and why. Writes dropped.<id> in the pauses state; the build module removes the key when it has acted.",
"callers": "the build seats' asker (the builder and the checker)",
"input": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "the ask's id"
},
"by": {
"type": "string",
"description": "who drops it"
},
"why": {
"type": "string",
"description": "why the caller no longer waits"
}
},
"required": [
"id",
"why"
]
},
"answer": {
"type": "object",
"description": "whether the ask was held here"
}
}
],
"defs": {
@@ -404,7 +434,7 @@
},
"state": {
"type": "string",
"description": "running, passed, failed or refused"
"description": "running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused"
},
"started": {
"type": "string",
@@ -435,6 +465,19 @@
},
"owner": {
"type": "string"
},
"run": {
"type": "integer",
"description": "which run of the ask this is, counted by the bus's delivery count (1 for the first)"
},
"again-at": {
"type": "string",
"description": "when the bus gives the ask again, for the state again (RFC 3339 time)",
"format": "date-time"
},
"passing": {
"type": "boolean",
"description": "true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so"
}
},
"required": [
@@ -476,6 +519,30 @@
],
"description": "the toolchain a node reports"
},
"Drop": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"by": {
"type": "string"
},
"why": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
}
},
"required": [
"why"
],
"description": "an ask a caller no longer waits for"
},
"Pause": {
"type": "object",
"properties": {
@@ -312,5 +312,32 @@
"said": "the facts name no head"
}
}
},
{
"name": "a step ended on a transport fault, given again by the bus",
"of": "state:steps/<node>.<id>",
"given": {
"v": 1,
"id": "a1b2",
"tool": "build",
"node": "ace",
"state": "again",
"started": "2026-11-05T10:00:00Z",
"ended": "2026-11-05T10:00:30Z",
"reason": "fetching the commit: kex_exchange_identification: read: Connection reset by peer",
"run": 1,
"again-at": "2026-11-05T10:01:30Z",
"passing": true
}
},
{
"name": "an ask dropped by the caller that no longer waits",
"of": "state:pauses/dropped.<id>",
"given": {
"v": 1,
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it",
"at": "2026-11-05T10:00:00Z"
}
}
]
@@ -0,0 +1,11 @@
[
{
"name": "drop names the ask and why",
"of": "verb:drop.input",
"given": {
"id": "a1b2",
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it"
}
}
]
+7
View File
@@ -58,6 +58,13 @@
},
"description": "whether a node is paused"
},
{
"key": "dropped.<id>",
"value": {
"$ref": "#/defs/Drop"
},
"description": "an ask its caller no longer waits for"
},
{
"key": "capacity.<node>",
"value": {
@@ -59,5 +59,32 @@
]
}
}
},
{
"name": "a step ended on a transport fault, given again by the bus",
"of": "state:steps/<node>.<id>",
"given": {
"v": 1,
"id": "a1b2",
"tool": "build",
"node": "ace",
"state": "again",
"started": "2026-11-05T10:00:00Z",
"ended": "2026-11-05T10:00:30Z",
"reason": "fetching the commit: kex_exchange_identification: read: Connection reset by peer",
"run": 1,
"again-at": "2026-11-05T10:01:30Z",
"passing": true
}
},
{
"name": "an ask dropped by the caller that no longer waits",
"of": "state:pauses/dropped.<id>",
"given": {
"v": 1,
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it",
"at": "2026-11-05T10:00:00Z"
}
}
]
@@ -0,0 +1,11 @@
[
{
"name": "drop names the ask and why",
"of": "verb:drop.input",
"given": {
"id": "a1b2",
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it"
}
}
]
@@ -58,6 +58,13 @@
},
"description": "whether a node is paused"
},
{
"key": "dropped.<id>",
"value": {
"$ref": "#/defs/Drop"
},
"description": "an ask its caller no longer waits for"
},
{
"key": "capacity.<node>",
"value": {
@@ -131,5 +131,32 @@
}
}
}
},
{
"name": "a step ended on a transport fault, given again by the bus",
"of": "state:steps/<node>.<id>",
"given": {
"v": 1,
"id": "a1b2",
"tool": "build",
"node": "ace",
"state": "again",
"started": "2026-11-05T10:00:00Z",
"ended": "2026-11-05T10:00:30Z",
"reason": "fetching the commit: kex_exchange_identification: read: Connection reset by peer",
"run": 1,
"again-at": "2026-11-05T10:01:30Z",
"passing": true
}
},
{
"name": "an ask dropped by the caller that no longer waits",
"of": "state:pauses/dropped.<id>",
"given": {
"v": 1,
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it",
"at": "2026-11-05T10:00:00Z"
}
}
]
@@ -0,0 +1,11 @@
[
{
"name": "drop names the ask and why",
"of": "verb:drop.input",
"given": {
"id": "a1b2",
"by": "mesh-delivery-builder",
"why": "a newer fingerprint supersedes it"
}
}
]
@@ -58,6 +58,13 @@
},
"description": "whether a node is paused"
},
{
"key": "dropped.<id>",
"value": {
"$ref": "#/defs/Drop"
},
"description": "an ask its caller no longer waits for"
},
{
"key": "capacity.<node>",
"value": {
@@ -10,6 +10,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
export const accepts = {
@@ -66,6 +67,23 @@ export function parsePausesByPausedNodeKey(_key: string): { node: string } | und
return { node };
}
/** The key of one value of the state pauses under the form "dropped.<id>". */
export function pausesByDroppedIDKey(id: string): string {
return "dropped." + id;
}
/** The parts of a key of the form "dropped.<id>", or undefined for a key of another form. */
export function parsePausesByDroppedIDKey(_key: string): { id: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("dropped.")) return undefined;
_rest = _rest.slice(8);
if (_rest === "") return undefined;
const id = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { id };
}
/** The key of one value of the state pauses under the form "capacity.<node>". */
export function pausesByCapacityNodeKey(node: string): string {
return "capacity." + node;
@@ -110,6 +128,9 @@ export type CheckAnswer = buildStep.CheckAnswer;
export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Drop = buildStep.Drop;
export type DropAnswer = buildStep.DropAnswer;
export type DropInput = buildStep.DropInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
@@ -159,6 +180,9 @@ export type StepsByNodeIDValue = Step;
/** is one value of the state pauses under the key paused.<node>. */
export type PausesByPausedNodeValue = Pause;
/** is one value of the state pauses under the key dropped.<id>. */
export type PausesByDroppedIDValue = Drop;
/** is one value of the state pauses under the key capacity.<node>. */
export type PausesByCapacityNodeValue = Capacity;
@@ -6,6 +6,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
/** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */
@@ -62,6 +63,16 @@ export interface ComposeAnswer {
"verdict"?: unknown;
}
/** an ask a caller no longer waits for */
export interface Drop {
/** when (RFC 3339 time) */
"at"?: string;
"by"?: string;
/** the layout version */
"v"?: number;
"why": string;
}
export interface FetchBesideItem {
"commit": string;
"repository": string;
@@ -244,19 +255,25 @@ export interface RunProgram {
/** one step a build module runs on a node */
export interface Step {
/** when the bus gives the ask again, for the state again (RFC 3339 time) */
"again-at"?: string;
/** when (RFC 3339 time) */
"ended"?: string;
"id": string;
"node": string;
"owner"?: string;
/** true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so */
"passing"?: boolean;
"phase"?: string;
/** when (RFC 3339 time) */
"phase-at"?: string;
"reason"?: string;
"result"?: StepResult;
/** which run of the ask this is, counted by the bus's delivery count (1 for the first) */
"run"?: number;
/** when (RFC 3339 time) */
"started": string;
/** running, passed, failed or refused */
/** running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused */
"state": string;
"tool": string;
/** the layout version */
@@ -396,3 +413,16 @@ export interface ResumeInput {
/** is what resume answers. */
export type ResumeAnswer = Record<string, unknown>;
/** is what drop takes. */
export interface DropInput {
/** who drops it */
"by"?: string;
/** the ask's id */
"id": string;
/** why the caller no longer waits */
"why": string;
}
/** is what drop answers. */
export type DropAnswer = Record<string, unknown>;
@@ -10,6 +10,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
export const accepts = {
@@ -66,6 +67,23 @@ export function parsePausesByPausedNodeKey(_key: string): { node: string } | und
return { node };
}
/** The key of one value of the state pauses under the form "dropped.<id>". */
export function pausesByDroppedIDKey(id: string): string {
return "dropped." + id;
}
/** The parts of a key of the form "dropped.<id>", or undefined for a key of another form. */
export function parsePausesByDroppedIDKey(_key: string): { id: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("dropped.")) return undefined;
_rest = _rest.slice(8);
if (_rest === "") return undefined;
const id = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { id };
}
/** The key of one value of the state pauses under the form "capacity.<node>". */
export function pausesByCapacityNodeKey(node: string): string {
return "capacity." + node;
@@ -110,6 +128,9 @@ export type CheckAnswer = buildStep.CheckAnswer;
export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Drop = buildStep.Drop;
export type DropAnswer = buildStep.DropAnswer;
export type DropInput = buildStep.DropInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
@@ -159,6 +180,9 @@ export type StepsByNodeIDValue = Step;
/** is one value of the state pauses under the key paused.<node>. */
export type PausesByPausedNodeValue = Pause;
/** is one value of the state pauses under the key dropped.<id>. */
export type PausesByDroppedIDValue = Drop;
/** is one value of the state pauses under the key capacity.<node>. */
export type PausesByCapacityNodeValue = Capacity;
@@ -6,6 +6,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
/** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */
@@ -62,6 +63,16 @@ export interface ComposeAnswer {
"verdict"?: unknown;
}
/** an ask a caller no longer waits for */
export interface Drop {
/** when (RFC 3339 time) */
"at"?: string;
"by"?: string;
/** the layout version */
"v"?: number;
"why": string;
}
export interface FetchBesideItem {
"commit": string;
"repository": string;
@@ -244,19 +255,25 @@ export interface RunProgram {
/** one step a build module runs on a node */
export interface Step {
/** when the bus gives the ask again, for the state again (RFC 3339 time) */
"again-at"?: string;
/** when (RFC 3339 time) */
"ended"?: string;
"id": string;
"node": string;
"owner"?: string;
/** true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so */
"passing"?: boolean;
"phase"?: string;
/** when (RFC 3339 time) */
"phase-at"?: string;
"reason"?: string;
"result"?: StepResult;
/** which run of the ask this is, counted by the bus's delivery count (1 for the first) */
"run"?: number;
/** when (RFC 3339 time) */
"started": string;
/** running, passed, failed or refused */
/** running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused */
"state": string;
"tool": string;
/** the layout version */
@@ -396,3 +413,16 @@ export interface ResumeInput {
/** is what resume answers. */
export type ResumeAnswer = Record<string, unknown>;
/** is what drop takes. */
export interface DropInput {
/** who drops it */
"by"?: string;
/** the ask's id */
"id": string;
/** why the caller no longer waits */
"why": string;
}
/** is what drop answers. */
export type DropAnswer = Record<string, unknown>;
@@ -10,6 +10,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
export const accepts = {
@@ -66,6 +67,23 @@ export function parsePausesByPausedNodeKey(_key: string): { node: string } | und
return { node };
}
/** The key of one value of the state pauses under the form "dropped.<id>". */
export function pausesByDroppedIDKey(id: string): string {
return "dropped." + id;
}
/** The parts of a key of the form "dropped.<id>", or undefined for a key of another form. */
export function parsePausesByDroppedIDKey(_key: string): { id: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("dropped.")) return undefined;
_rest = _rest.slice(8);
if (_rest === "") return undefined;
const id = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { id };
}
/** The key of one value of the state pauses under the form "capacity.<node>". */
export function pausesByCapacityNodeKey(node: string): string {
return "capacity." + node;
@@ -110,6 +128,9 @@ export type CheckAnswer = buildStep.CheckAnswer;
export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Drop = buildStep.Drop;
export type DropAnswer = buildStep.DropAnswer;
export type DropInput = buildStep.DropInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
@@ -159,6 +180,9 @@ export type StepsByNodeIDValue = Step;
/** is one value of the state pauses under the key paused.<node>. */
export type PausesByPausedNodeValue = Pause;
/** is one value of the state pauses under the key dropped.<id>. */
export type PausesByDroppedIDValue = Drop;
/** is one value of the state pauses under the key capacity.<node>. */
export type PausesByCapacityNodeValue = Capacity;
@@ -6,6 +6,7 @@ export const verbs = {
current: "current",
pause: "pause",
resume: "resume",
drop: "drop",
} as const;
/** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */
@@ -62,6 +63,16 @@ export interface ComposeAnswer {
"verdict"?: unknown;
}
/** an ask a caller no longer waits for */
export interface Drop {
/** when (RFC 3339 time) */
"at"?: string;
"by"?: string;
/** the layout version */
"v"?: number;
"why": string;
}
export interface FetchBesideItem {
"commit": string;
"repository": string;
@@ -244,19 +255,25 @@ export interface RunProgram {
/** one step a build module runs on a node */
export interface Step {
/** when the bus gives the ask again, for the state again (RFC 3339 time) */
"again-at"?: string;
/** when (RFC 3339 time) */
"ended"?: string;
"id": string;
"node": string;
"owner"?: string;
/** true when the step ended on a fault that passes (a transport fault), so a failed end after the last run says so */
"passing"?: boolean;
"phase"?: string;
/** when (RFC 3339 time) */
"phase-at"?: string;
"reason"?: string;
"result"?: StepResult;
/** which run of the ask this is, counted by the bus's delivery count (1 for the first) */
"run"?: number;
/** when (RFC 3339 time) */
"started": string;
/** running, passed, failed or refused */
/** running, again (ended on a transport fault, to be given again by the bus after again-at), passed, failed or refused */
"state": string;
"tool": string;
/** the layout version */
@@ -396,3 +413,16 @@ export interface ResumeInput {
/** is what resume answers. */
export type ResumeAnswer = Record<string, unknown>;
/** is what drop takes. */
export interface DropInput {
/** who drops it */
"by"?: string;
/** the ask's id */
"id": string;
/** why the caller no longer waits */
"why": string;
}
/** is what drop answers. */
export type DropAnswer = Record<string, unknown>;