Merge b2/registry (B2f: the builder on the generated mesh-artifact-sweep seat, the kept state's client) into b7/registry; the builder keeps the hub for the seats' events (B7j)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
|
||||
|
||||
// Package meshartifactsweep is the contract of the seat "mesh-artifact-sweep", defined whole (seats/mesh-artifact-sweep/seat.json): no description.
|
||||
package meshartifactsweep
|
||||
|
||||
import "time"
|
||||
|
||||
// Seat is the seat's name.
|
||||
const Seat = "mesh-artifact-sweep"
|
||||
|
||||
const (
|
||||
VerbArtifacts = "artifacts"
|
||||
VerbHeld = "held"
|
||||
VerbCollect = "collect"
|
||||
VerbMirrors = "mirrors"
|
||||
VerbImages = "images"
|
||||
)
|
||||
|
||||
const (
|
||||
EventArtifactsGone = "artifacts-gone"
|
||||
)
|
||||
|
||||
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
|
||||
func Verbs() []string {
|
||||
return []string{VerbArtifacts, VerbHeld, VerbCollect, VerbMirrors, VerbImages}
|
||||
}
|
||||
|
||||
// Events is every event published on the seat's subject: a new copy each call.
|
||||
func Events() []string {
|
||||
return []string{EventArtifactsGone}
|
||||
}
|
||||
|
||||
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
|
||||
const (
|
||||
Family = "mesh-artifact-sweep.events"
|
||||
FamilyMaxAgeDays = 7
|
||||
)
|
||||
|
||||
// SubjectArtifactsGone is where artifacts-gone is published.
|
||||
const SubjectArtifactsGone = "mesh.seat.mesh-artifact-sweep.event.artifacts-gone"
|
||||
|
||||
// ArtifactsGone is the body of artifacts-gone.
|
||||
type ArtifactsGone struct {
|
||||
// when the sweep found it
|
||||
At *time.Time `json:"at,omitempty"`
|
||||
// artifacts-gone
|
||||
Event string `json:"event"`
|
||||
// the key's fingerprint
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
// every reference of the key the store does not hold, sorted
|
||||
Gone []string `json:"gone"`
|
||||
// the builder's done key
|
||||
Key string `json:"key,omitempty"`
|
||||
// the module whose builder's key names a gone artifact
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
// ArtifactsInput is what artifacts takes.
|
||||
type ArtifactsInput struct {
|
||||
// "true": list the collected references too (counted always)
|
||||
Collected string `json:"collected,omitempty"`
|
||||
// only this repository's references, as <module>/<artifact> (the counts stay whole)
|
||||
Repository string `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
// The values of ArtifactsInput's collected.
|
||||
const (
|
||||
ArtifactsInputCollectedTrue = "true"
|
||||
ArtifactsInputCollectedFalse = "false"
|
||||
)
|
||||
|
||||
// ArtifactsAnswer is what artifacts answers.
|
||||
type ArtifactsAnswer = map[string]any
|
||||
|
||||
// HeldInput is what held takes.
|
||||
type HeldInput struct {
|
||||
// the artifacts' references, artifact-store://<module>/<artifact>/blobs/sha256:<hex>: a list, or one text separated by commas
|
||||
References []string `json:"references"`
|
||||
}
|
||||
|
||||
// HeldAnswer is what held answers.
|
||||
type HeldAnswer struct {
|
||||
// the references the store answered 404 for
|
||||
Gone []string `json:"gone"`
|
||||
// the references the store holds
|
||||
Held []string `json:"held"`
|
||||
// the store asked, host and port
|
||||
Store string `json:"store,omitempty"`
|
||||
// a reference the store could not be asked about, to why
|
||||
Unknown map[string]string `json:"unknown,omitempty"`
|
||||
}
|
||||
|
||||
// CollectInput is what collect takes.
|
||||
type CollectInput struct {
|
||||
// "true": let go of what is eligible (needs why); without it nothing is done
|
||||
Confirm string `json:"confirm,omitempty"`
|
||||
// let go of at most this many (500 by default, at most 5000)
|
||||
Most string `json:"most,omitempty"`
|
||||
// with confirm: why, required and recorded
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
// The values of CollectInput's confirm.
|
||||
const (
|
||||
CollectInputConfirmTrue = "true"
|
||||
CollectInputConfirmFalse = "false"
|
||||
)
|
||||
|
||||
// CollectAnswer is what collect answers.
|
||||
type CollectAnswer = map[string]any
|
||||
|
||||
// MirrorsInput is what mirrors takes.
|
||||
type MirrorsInput struct {
|
||||
}
|
||||
|
||||
// MirrorsAnswer is what mirrors answers.
|
||||
type MirrorsAnswer = map[string]any
|
||||
|
||||
// ImagesInput is what images takes.
|
||||
type ImagesInput struct {
|
||||
// the machine's name
|
||||
Node string `json:"node"`
|
||||
}
|
||||
|
||||
// ImagesAnswer is what images answers.
|
||||
type ImagesAnswer = map[string]any
|
||||
|
||||
// ArtifactsGoneEvent is the body of the event artifacts-gone.
|
||||
type ArtifactsGoneEvent = ArtifactsGone
|
||||
@@ -0,0 +1,54 @@
|
||||
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
|
||||
|
||||
package meshartifactsweep
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFixtures reads every fixture of seats/mesh-artifact-sweep/fixtures: each decodes into its type, a field the type does not
|
||||
// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's.
|
||||
func TestFixtures(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, subject, given, wire string
|
||||
decode func([]byte) (any, error)
|
||||
subjectIs *regexp.Regexp
|
||||
}{
|
||||
{name: "artifacts-gone on the seat's subject", subject: "mesh.seat.mesh-artifact-sweep.event.artifacts-gone", given: "{\"at\":\"2026-11-05T15:04:05Z\",\"event\":\"artifacts-gone\",\"fingerprint\":\"f1\",\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"key\":\"ca-trust.archive.f1\",\"module\":\"ca-trust\"}", wire: "{\"at\":\"2026-11-05T15:04:05Z\",\"event\":\"artifacts-gone\",\"fingerprint\":\"f1\",\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"key\":\"ca-trust.archive.f1\",\"module\":\"ca-trust\"}",
|
||||
decode: func(b []byte) (any, error) { var v ArtifactsGoneEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-artifact-sweep\\.event\\.artifacts-gone$")},
|
||||
{name: "held asks the references", subject: "", given: "{\"references\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"]}", wire: "{\"references\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"]}",
|
||||
decode: func(b []byte) (any, error) { var v HeldInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "held answers held, gone and unknown", subject: "", given: "{\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"held\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"store\":\"store.test:5100\",\"unknown\":{\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\":\"the store answered 503\"}}", wire: "{\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"held\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"store\":\"store.test:5100\",\"unknown\":{\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\":\"the store answered 503\"}}",
|
||||
decode: func(b []byte) (any, error) { var v HeldAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "held answers with nothing unknown", subject: "", given: "{\"gone\":[],\"held\":[],\"store\":\"store.test:5100\"}", wire: "{\"gone\":[],\"held\":[],\"store\":\"store.test:5100\"}",
|
||||
decode: func(b []byte) (any, error) { var v HeldAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
}
|
||||
for _, c := range cases {
|
||||
v, err := c.decode([]byte(c.given))
|
||||
if err != nil {
|
||||
t.Errorf("%s: does not decode: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
back, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("%s: does not encode: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
var got, want any
|
||||
if err := json.Unmarshal(back, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(c.wire), &want); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire)
|
||||
}
|
||||
if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) {
|
||||
t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep
|
||||
|
||||
go 1.26.0
|
||||
@@ -23,6 +23,16 @@
|
||||
"series": "0.1",
|
||||
"generated-from": "."
|
||||
},
|
||||
{
|
||||
"name": "go-meshartifactsweep",
|
||||
"kind": "package",
|
||||
"language": "go",
|
||||
"paths": [
|
||||
"go/seats/meshartifactsweep"
|
||||
],
|
||||
"series": "0.1",
|
||||
"generated-from": "."
|
||||
},
|
||||
{
|
||||
"name": "go-meshcatalog",
|
||||
"kind": "package",
|
||||
@@ -186,6 +196,16 @@
|
||||
"series": "0.1",
|
||||
"generated-from": "."
|
||||
},
|
||||
{
|
||||
"name": "ts-meshartifactsweep",
|
||||
"kind": "package",
|
||||
"language": "typescript",
|
||||
"paths": [
|
||||
"ts/seats/meshartifactsweep"
|
||||
],
|
||||
"series": "0.1",
|
||||
"generated-from": "."
|
||||
},
|
||||
{
|
||||
"name": "ts-meshcatalog",
|
||||
"kind": "package",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"name": "artifacts-gone on the seat's subject",
|
||||
"of": "event:artifacts-gone",
|
||||
"subject": "mesh.seat.mesh-artifact-sweep.event.artifacts-gone",
|
||||
"given": {
|
||||
"event": "artifacts-gone",
|
||||
"module": "ca-trust",
|
||||
"key": "ca-trust.archive.f1",
|
||||
"fingerprint": "f1",
|
||||
"gone": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
|
||||
"at": "2026-11-05T15:04:05Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"name": "held asks the references",
|
||||
"of": "verb:held.input",
|
||||
"why": "the references are the holder's own test values (the tool refuses a call without them through the console)",
|
||||
"given": {"references": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"]}
|
||||
},
|
||||
{
|
||||
"name": "held answers held, gone and unknown",
|
||||
"of": "verb:held.answer",
|
||||
"why": "the answer of the holder's test (cmd/artifact-store-tools): every reference in one of the three",
|
||||
"given": {
|
||||
"store": "store.test:5100",
|
||||
"held": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
|
||||
"gone": ["artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
|
||||
"unknown": {"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc": "the store answered 503"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "held answers with nothing unknown",
|
||||
"of": "verb:held.answer",
|
||||
"given": {"store": "store.test:5100", "held": [], "gone": []}
|
||||
}
|
||||
]
|
||||
@@ -2,7 +2,7 @@
|
||||
"format": 1,
|
||||
"seat": "mesh-artifact-sweep",
|
||||
"scope": "mesh",
|
||||
"grain": "seat-list",
|
||||
"grain": "whole",
|
||||
"domain": "Delivery",
|
||||
"decision": "novox/hq ADR 0571",
|
||||
"verbs": [
|
||||
@@ -25,6 +25,10 @@
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
"description": "what artifacts answers, as its description says"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -44,6 +48,41 @@
|
||||
"references"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"store": {
|
||||
"type": "string",
|
||||
"description": "the store asked, host and port"
|
||||
},
|
||||
"held": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "the references the store holds"
|
||||
},
|
||||
"gone": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "the references the store answered 404 for"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "a reference the store could not be asked about, to why"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"held",
|
||||
"gone"
|
||||
],
|
||||
"description": "what held answers: a reference is in held, gone or unknown, never none of them"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,6 +108,10 @@
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
"description": "what collect answers, as its description says"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -77,6 +120,10 @@
|
||||
"input": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
"description": "what mirrors answers, as its description says"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -93,12 +140,65 @@
|
||||
"node"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"answer": {
|
||||
"type": "object",
|
||||
"description": "what images answers, as its description says"
|
||||
}
|
||||
}
|
||||
],
|
||||
"family": {
|
||||
"name": "mesh-artifact-sweep.events",
|
||||
"max-age-days": 7
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "artifacts-gone"
|
||||
"name": "artifacts-gone",
|
||||
"body": {
|
||||
"$ref": "#/defs/ArtifactsGone"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"defs": {
|
||||
"ArtifactsGone": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event": {
|
||||
"type": "string",
|
||||
"description": "artifacts-gone"
|
||||
},
|
||||
"module": {
|
||||
"type": "string",
|
||||
"description": "the module whose builder's key names a gone artifact"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "the builder's done key"
|
||||
},
|
||||
"fingerprint": {
|
||||
"type": "string",
|
||||
"description": "the key's fingerprint"
|
||||
},
|
||||
"gone": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "every reference of the key the store does not hold, sorted"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "when the sweep found it"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"event",
|
||||
"module",
|
||||
"fingerprint",
|
||||
"gone"
|
||||
],
|
||||
"description": "the body of artifacts-gone"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"lockfileVersion": 3,
|
||||
"name": "@novox/mesh-delivery-contracts-mesh-artifact-sweep",
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@novox/mesh-delivery-contracts-mesh-artifact-sweep",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
},
|
||||
"requires": true,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"description": "The contract of the seat mesh-artifact-sweep, generated from its definition.",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"name": "@novox/mesh-delivery-contracts-mesh-artifact-sweep",
|
||||
"type": "module",
|
||||
"types": "./src/index.ts",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
|
||||
|
||||
// The contract of the seat "mesh-artifact-sweep", defined whole (seats/mesh-artifact-sweep/seat.json): no description.
|
||||
|
||||
export const seat = "mesh-artifact-sweep";
|
||||
|
||||
export const verbs = {
|
||||
artifacts: "artifacts",
|
||||
held: "held",
|
||||
collect: "collect",
|
||||
mirrors: "mirrors",
|
||||
images: "images",
|
||||
} as const;
|
||||
|
||||
export const events = {
|
||||
artifactsGone: "artifacts-gone",
|
||||
} as const;
|
||||
|
||||
export const family = { name: "mesh-artifact-sweep.events", maxAgeDays: 7 } as const;
|
||||
|
||||
export const artifactsGoneSubject = "mesh.seat.mesh-artifact-sweep.event.artifacts-gone";
|
||||
|
||||
/** the body of artifacts-gone */
|
||||
export interface ArtifactsGone {
|
||||
/** when the sweep found it */
|
||||
"at"?: string;
|
||||
/** artifacts-gone */
|
||||
"event": string;
|
||||
/** the key's fingerprint */
|
||||
"fingerprint": string;
|
||||
/** every reference of the key the store does not hold, sorted */
|
||||
"gone": string[];
|
||||
/** the builder's done key */
|
||||
"key"?: string;
|
||||
/** the module whose builder's key names a gone artifact */
|
||||
"module": string;
|
||||
}
|
||||
|
||||
/** is what artifacts takes. */
|
||||
export interface ArtifactsInput {
|
||||
/** "true": list the collected references too (counted always) */
|
||||
"collected"?: "true" | "false";
|
||||
/** only this repository's references, as <module>/<artifact> (the counts stay whole) */
|
||||
"repository"?: string;
|
||||
}
|
||||
|
||||
/** is what artifacts answers. */
|
||||
export type ArtifactsAnswer = Record<string, unknown>;
|
||||
|
||||
/** is what held takes. */
|
||||
export interface HeldInput {
|
||||
/** the artifacts' references, artifact-store://<module>/<artifact>/blobs/sha256:<hex>: a list, or one text separated by commas */
|
||||
"references": string[];
|
||||
}
|
||||
|
||||
/** is what held answers. */
|
||||
export interface HeldAnswer {
|
||||
/** the references the store answered 404 for */
|
||||
"gone": string[];
|
||||
/** the references the store holds */
|
||||
"held": string[];
|
||||
/** the store asked, host and port */
|
||||
"store"?: string;
|
||||
/** a reference the store could not be asked about, to why */
|
||||
"unknown"?: Record<string, string>;
|
||||
}
|
||||
|
||||
/** is what collect takes. */
|
||||
export interface CollectInput {
|
||||
/** "true": let go of what is eligible (needs why); without it nothing is done */
|
||||
"confirm"?: "true" | "false";
|
||||
/** let go of at most this many (500 by default, at most 5000) */
|
||||
"most"?: string;
|
||||
/** with confirm: why, required and recorded */
|
||||
"why"?: string;
|
||||
}
|
||||
|
||||
/** is what collect answers. */
|
||||
export type CollectAnswer = Record<string, unknown>;
|
||||
|
||||
/** is what mirrors takes. */
|
||||
export interface MirrorsInput {
|
||||
}
|
||||
|
||||
/** is what mirrors answers. */
|
||||
export type MirrorsAnswer = Record<string, unknown>;
|
||||
|
||||
/** is what images takes. */
|
||||
export interface ImagesInput {
|
||||
/** the machine's name */
|
||||
"node": string;
|
||||
}
|
||||
|
||||
/** is what images answers. */
|
||||
export type ImagesAnswer = Record<string, unknown>;
|
||||
|
||||
/** is the body of the event artifacts-gone. */
|
||||
export type ArtifactsGoneEvent = ArtifactsGone;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ func (h *heldVerb) serve(t *testing.T, b *bed) {
|
||||
References []string `json:"references"`
|
||||
}
|
||||
_ = json.Unmarshal(m.Data, &args)
|
||||
a := delivery.HeldAnswer{Store: "store.test:5100", Held: []string{}, Gone: []string{}}
|
||||
a := sweep.HeldAnswer{Store: "store.test:5100", Held: []string{}, Gone: []string{}}
|
||||
h.mu.Lock()
|
||||
for _, r := range args.References {
|
||||
h.asked = append(h.asked, r)
|
||||
@@ -69,7 +70,7 @@ func eventsConsumer(t *testing.T, b *bed, node string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.js.CreateOrUpdateConsumer(b.ctx, eventsStream, jetstream.ConsumerConfig{Durable: node + "_mesh-delivery-builder",
|
||||
AckPolicy: jetstream.AckExplicitPolicy, FilterSubjects: []string{delivery.ArtifactsGoneSubject}}); err != nil {
|
||||
AckPolicy: jetstream.AckExplicitPolicy, FilterSubjects: []string{sweep.SubjectArtifactsGone}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -168,12 +169,13 @@ func TestArtifactsGoneLeadsTheBuilderToBuildTheKeyAgain(t *testing.T) {
|
||||
collected = true
|
||||
mu.Unlock()
|
||||
key, _ := delivery.BuilderBuildKey("ca-trust", fp)
|
||||
body, err := delivery.EncodeArtifactsGone(delivery.ArtifactsGone{Module: "ca-trust", Key: key, Fingerprint: fp,
|
||||
Gone: []string{ref}, At: time.Now().UTC()})
|
||||
at := time.Now().UTC()
|
||||
body, err := json.Marshal(sweep.ArtifactsGone{Event: sweep.EventArtifactsGone, Module: "ca-trust", Key: key, Fingerprint: fp,
|
||||
Gone: []string{ref}, At: &at})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.js.Publish(b.ctx, delivery.ArtifactsGoneSubject, body); err != nil {
|
||||
if _, err := b.js.Publish(b.ctx, sweep.SubjectArtifactsGone, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := b.await(t, "ca-trust", func(v delivery.BuilderBuild) bool {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
runner "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/ociartifactregistry"
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
@@ -352,7 +353,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
reader.Start(ctx, js, cred, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "builder_first_views reads nothing and builder_first_view_again is not heard", logger.Printf))
|
||||
// Every part has registered: the hub pulls the module's durable. Where the consumer is not there yet the builder says so
|
||||
// and binds it at a later change (heard.Writer; ADR 0615 §5).
|
||||
_ = notHeard.Bind(eventsStream, delivery.ArtifactsGoneEvent+", what wakes a floor, the registry seats' words, the views and manifest-reads-changed are not heard until the builder starts again", func() error {
|
||||
_ = notHeard.Bind(eventsStream, sweep.EventArtifactsGone+", what wakes a floor, the registry seats' words, the views and manifest-reads-changed are not heard until the builder starts again", func() error {
|
||||
return hub.Start(ctx, js, cred)
|
||||
})
|
||||
go func() { <-ctx.Done(); hub.Stop() }()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/enginereads"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/forms"
|
||||
@@ -223,7 +224,7 @@ func aFloorsWakeIsHeard(t *testing.T) {
|
||||
if !f.heard(context.Background(), cleared) || !strings.Contains(logs.String(), "the floor of m/img is asked again: condition-cleared") {
|
||||
t.Fatalf("condition-cleared: log %q", logs.String())
|
||||
}
|
||||
if f.heard(context.Background(), &wakeMsg{subject: delivery.ArtifactsGoneSubject}) {
|
||||
if f.heard(context.Background(), &wakeMsg{subject: sweep.SubjectArtifactsGone}) {
|
||||
t.Fatal("artifacts-gone was taken as a floor's wake")
|
||||
}
|
||||
// DLV-B4: a registry seat's outcome on the same consumer moves a version; a published wakes a name whose seat was
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-builder/internal/build"
|
||||
)
|
||||
@@ -35,7 +35,7 @@ func storedVia(call toolCaller) func(ctx context.Context, references []string) (
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("asking held: %w", err)
|
||||
}
|
||||
a, err := delivery.DecodeHeldAnswer(raw, references)
|
||||
a, err := decodeHeld(raw, references)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -54,13 +54,62 @@ func storedVia(call toolCaller) func(ctx context.Context, references []string) (
|
||||
// heardGone takes one artifacts-gone event in: acknowledged, then handed to the builder, which asks the store and builds
|
||||
// the key again. A body that is not one is said and acknowledged, so it is not handed over again.
|
||||
func heardGone(ctx context.Context, b *build.Builder, m jetstream.Msg, logf func(string, ...any)) {
|
||||
e, err := delivery.DecodeArtifactsGone(m.Data())
|
||||
e, err := decodeGone(m.Data())
|
||||
if ackErr := m.Ack(); ackErr != nil {
|
||||
logf("acknowledging %s: %v", m.Subject(), ackErr)
|
||||
}
|
||||
if err != nil {
|
||||
logf("%s holds what is not %s, left: %v", m.Subject(), delivery.ArtifactsGoneEvent, err)
|
||||
logf("%s holds what is not %s, left: %v", m.Subject(), sweep.EventArtifactsGone, err)
|
||||
return
|
||||
}
|
||||
b.ArtifactsGone(ctx, e)
|
||||
}
|
||||
|
||||
// decodeHeld reads the seat's answer to held: the verb answers {output, ok, answer}, the answer being the seat's
|
||||
// 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 decodeHeld(raw []byte, asked []string) (sweep.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 sweep.HeldAnswer{}, fmt.Errorf("the answer of %s is not {output, ok, answer}: %w", SeatHeld, err)
|
||||
}
|
||||
if !wrapped.OK || len(wrapped.Answer) == 0 {
|
||||
return sweep.HeldAnswer{}, fmt.Errorf("%s did not answer: %s", SeatHeld, wrapped.Output)
|
||||
}
|
||||
var a sweep.HeldAnswer
|
||||
if err := json.Unmarshal(wrapped.Answer, &a); err != nil {
|
||||
return sweep.HeldAnswer{}, fmt.Errorf("the answer of %s is not {held, gone}: %w", SeatHeld, 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 sweep.HeldAnswer{}, fmt.Errorf("the answer of %s names nothing of %s", SeatHeld, r)
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// decodeGone reads the event's body, refusing one that names no module, fingerprint or reference.
|
||||
func decodeGone(raw []byte) (sweep.ArtifactsGone, error) {
|
||||
var e sweep.ArtifactsGone
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
return sweep.ArtifactsGone{}, fmt.Errorf("%s is not one: %w", sweep.EventArtifactsGone, err)
|
||||
}
|
||||
if e.Event != sweep.EventArtifactsGone || e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 {
|
||||
return sweep.ArtifactsGone{}, fmt.Errorf("%s names no module, fingerprint or gone reference: %s", sweep.EventArtifactsGone, raw)
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
@@ -47,3 +47,7 @@ require (
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep => ../../contracts/go/seats/meshartifactsweep
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -184,14 +185,14 @@ func TestArtifactsGoneMakesTheBuilderAskAndBuildAgain(t *testing.T) {
|
||||
held.mu.Lock()
|
||||
held.gone[goneRef] = true // collected after the builder's look; the controller's take-in found it
|
||||
held.mu.Unlock()
|
||||
r.b.ArtifactsGone(context.Background(), delivery.ArtifactsGone{Event: delivery.ArtifactsGoneEvent, Module: "m",
|
||||
r.b.ArtifactsGone(context.Background(), sweep.ArtifactsGone{Event: sweep.EventArtifactsGone, Module: "m",
|
||||
Fingerprint: "src2:" + strings.Repeat("9", 64), Gone: []string{goneRef}})
|
||||
r.b.Wait()
|
||||
if r.st.revs[r.key] != rev || !log.has("a fingerprint the module does not have now") {
|
||||
t.Fatalf("an event for another fingerprint moved the key (revision %d, was %d): %v", r.st.revs[r.key], rev, log.lines)
|
||||
}
|
||||
|
||||
r.b.ArtifactsGone(context.Background(), delivery.ArtifactsGone{Event: delivery.ArtifactsGoneEvent, Module: "m",
|
||||
r.b.ArtifactsGone(context.Background(), sweep.ArtifactsGone{Event: sweep.EventArtifactsGone, Module: "m",
|
||||
Key: r.key, Fingerprint: fp1, Gone: []string{goneRef}})
|
||||
r.b.Wait()
|
||||
v := valueOf(t, r.st, r.key)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/forms"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
@@ -281,7 +282,7 @@ func (b *Builder) storedOnce(key string) func(ctx context.Context, references []
|
||||
// that the store does not hold what the module's key names. The module is looked at again, which asks the store (ClaimOptions.Stored) and
|
||||
// builds the key again; an event for a fingerprint the module no longer has is said and left. The event only wakes the
|
||||
// builder: what is built rests on the store's answer, never on the event alone.
|
||||
func (b *Builder) ArtifactsGone(ctx context.Context, e delivery.ArtifactsGone) {
|
||||
func (b *Builder) ArtifactsGone(ctx context.Context, e sweep.ArtifactsGone) {
|
||||
b.init()
|
||||
b.mu.Lock()
|
||||
m, ok := b.known[e.Module]
|
||||
@@ -295,11 +296,11 @@ func (b *Builder) ArtifactsGone(ctx context.Context, e delivery.ArtifactsGone) {
|
||||
b.mu.Unlock()
|
||||
if !ok || m.FP2 != e.Fingerprint {
|
||||
b.logf("%s: heard %s for %s, a fingerprint the module does not have now: nothing to build", e.Module,
|
||||
delivery.ArtifactsGoneEvent, e.Fingerprint)
|
||||
sweep.EventArtifactsGone, e.Fingerprint)
|
||||
return
|
||||
}
|
||||
b.logf("%s: heard %s (%s): asking the artifact store and building the key again", e.Module,
|
||||
delivery.ArtifactsGoneEvent, strings.Join(e.Gone, ", "))
|
||||
sweep.EventArtifactsGone, strings.Join(e.Gone, ", "))
|
||||
b.consider(ctx, e.Module, m)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user