Files
mesh-sdk/go/delivery/held.go
T
jschoubben 8295207077 Remove the controller's seat, the lease verbs and placement's adopt and hand-back; refuse any manifest naming the controller
The controller ended by its unassignment (ADR 0590), so its seat, the grants seat's lease write and
hand-back and placement's take-over from it serve nothing. The dependence check becomes strict
(clause 5): a read of the lease, a claim, a require or the module itself is refused like any other
declaration. held and artifacts-gone are the artifact sweep seat's (ADR 0571 §2). Issue 1126 B1, B2.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 11:20:28 +01:00

105 lines
4.3 KiB
Go

package delivery
import (
"encoding/json"
"fmt"
"sort"
"time"
)
// Whether the artifact store still holds what a done key names (novox/hq issue 1937). A build module pushes a bundle or
// an archive as a bare blob (ADR 0498 §1), and the store's nightly collector deletes every blob no manifest names. A done
// key can therefore name a blob that is gone, and reusing it sends a definition every machine fails on. So the builder
// asks before it reuses a done key, and the artifact sweep says when it found a gone blob. Both are the seat
// mesh-artifact-sweep's (novox/hq ADR 0571 §2, ADR 0590): the controller that served them first has ended.
const (
// HeldVerb is the artifact sweep seat's verb the builder asks, as a module's invokes names it: references in, each
// answered held or gone.
HeldVerb = "seat:mesh-artifact-sweep.held"
// ArtifactsGoneEvent is the artifact sweep seat's event said when the store does not hold an artifact a key names:
// as a consumer names it, `mesh-artifact-sweep.artifacts-gone`.
ArtifactsGoneEvent = "artifacts-gone"
// ArtifactsGoneSubject is where it is published (the seat's event subject).
ArtifactsGoneSubject = "mesh.seat.mesh-artifact-sweep.event." + ArtifactsGoneEvent
)
// HeldAnswer is the answer of HeldVerb: the references the store holds, and those it answered 404 for. A reference the
// store could not be asked about is in neither, and named in Unknown with why.
type HeldAnswer struct {
Store string `json:"store,omitempty"`
Held []string `json:"held"`
Gone []string `json:"gone"`
Unknown map[string]string `json:"unknown,omitempty"`
}
// DecodeHeldAnswer reads HeldVerb's answer: the verb answers {output, ok, answer}, the answer being HeldAnswer. A
// reference asked that the answer names in none of held, gone and unknown is an error: an answer that leaves one out
// would let a key with a gone blob be reused.
func DecodeHeldAnswer(raw []byte, asked []string) (HeldAnswer, error) {
var wrapped struct {
Output string `json:"output"`
OK bool `json:"ok"`
Answer json.RawMessage `json:"answer"`
}
if err := json.Unmarshal(raw, &wrapped); err != nil {
return HeldAnswer{}, fmt.Errorf("the answer of %s is not {output, ok, answer}: %w", HeldVerb, err)
}
if !wrapped.OK || len(wrapped.Answer) == 0 {
return HeldAnswer{}, fmt.Errorf("%s did not answer: %s", HeldVerb, wrapped.Output)
}
var a HeldAnswer
if err := json.Unmarshal(wrapped.Answer, &a); err != nil {
return HeldAnswer{}, fmt.Errorf("the answer of %s is not {held, gone}: %w", HeldVerb, err)
}
named := map[string]bool{}
for _, r := range a.Held {
named[r] = true
}
for _, r := range a.Gone {
named[r] = true
}
for r := range a.Unknown {
named[r] = true
}
for _, r := range asked {
if !named[r] {
return HeldAnswer{}, fmt.Errorf("the answer of %s names nothing of %s", HeldVerb, r)
}
}
return a, nil
}
// ArtifactsGone is the body of ArtifactsGoneEvent: the module whose builder's key names a gone artifact,
// the key and its fingerprint, and every reference of it the store does not hold.
type ArtifactsGone struct {
Event string `json:"event"`
Module string `json:"module"`
Key string `json:"key"`
Fingerprint string `json:"fingerprint"`
Gone []string `json:"gone"`
At time.Time `json:"at"`
}
// EncodeArtifactsGone is the event's body, the references sorted.
func EncodeArtifactsGone(e ArtifactsGone) ([]byte, error) {
e.Event = ArtifactsGoneEvent
e.Gone = append([]string(nil), e.Gone...)
sort.Strings(e.Gone)
if e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 {
return nil, fmt.Errorf("delivery: %s names a module, a fingerprint and at least one gone reference", ArtifactsGoneEvent)
}
return json.Marshal(e)
}
// DecodeArtifactsGone reads the event's body, refusing one that names no module, fingerprint or reference.
func DecodeArtifactsGone(raw []byte) (ArtifactsGone, error) {
var e ArtifactsGone
if err := json.Unmarshal(raw, &e); err != nil {
return ArtifactsGone{}, fmt.Errorf("delivery: %s is not one: %w", ArtifactsGoneEvent, err)
}
if e.Event != ArtifactsGoneEvent || e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 {
return ArtifactsGone{}, fmt.Errorf("delivery: %s names no module, fingerprint or gone reference: %s", ArtifactsGoneEvent, raw)
}
return e, nil
}