mesh-delivery-contracts: the seat mesh-artifact-sweep is a whole seat: held's answer (HeldAnswer) and the artifacts-gone body (ArtifactsGone) typed, with fixtures from the holder's test values, generated Go and TypeScript (B2f; ADR 0571 §2, ADR 0634 §2; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:58:29 +01:00
parent d0f1afbede
commit 7fcd2d0b64
12 changed files with 485 additions and 3 deletions
@@ -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
+20
View File
@@ -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": []}
}
]
+103 -3
View File
@@ -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"
}
}
}
+12
View File
@@ -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"]
}