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
105 lines
4.3 KiB
Go
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
|
|
}
|