Files
mesh-contracts/parts/registry.json
T
jschoubben 859da7b1ce Registry seats: remove answers and says kept when a tagged version shares its digest
A store that cannot remove a tag alone (another tag names its digest) kept the tag and the holder said
removed, so the builder and every follower believed the tagged version gone while it stayed pullable:
work reported done that was not done. The shared registry part gains the outcome kept: in remove's
answer (kept, the reason shared-digest, the tags still naming the digest) and as its own event, in all
four registry seats. Regenerated with mesh-catalog's seatgen (join-mesh-catalog ccb7eaff); seatgen
-check passes; fixtures for the answer and the event in each seat.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:12:34 +01:00

437 lines
14 KiB
JSON

{
"format": 1,
"part": "registry",
"description": "The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved.",
"verbs": [
{
"name": "publish",
"description": "Open a transfer, naming the artifact and the digest of every part; answers the digests the holder lacks, which the caller then sends by publish-part. A transfer already open answers the parts held, so a cut transfer goes on from there. With no parts and a version, the held digest is published under that version and nothing is stored (the trunk's claim).",
"callers": "the build modules",
"input": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)"
},
"version": {
"type": "string",
"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone"
},
"kind": {
"type": "string",
"enum": [
"package",
"image",
"bundle",
"archive",
"upstream"
],
"description": "what it is"
},
"tag": {
"type": "string",
"description": "the tag it carries, for a tagged version"
},
"digest": {
"type": "string",
"description": "its digest, sha256:<hex> (a Go package's h1: hash beside it in the version's record)"
},
"fingerprint": {
"type": "string",
"description": "the build's fingerprint"
},
"commit": {
"type": "string",
"description": "the source commit it was built from"
},
"transfer": {
"type": "string",
"description": "the transfer's name, the caller's own; the same name opens the same transfer again"
},
"parts": {
"type": "array",
"items": {
"$ref": "#/defs/Part"
},
"description": "every part's digest and size; none when the digest is already held"
}
},
"required": [
"transfer",
"name",
"kind",
"digest"
]
},
"answer": {
"type": "object",
"properties": {
"transfer": {
"type": "string",
"description": "the transfer"
},
"lacks": {
"type": "array",
"items": {
"type": "string"
},
"description": "the digests of the parts the holder lacks, to send by publish-part"
},
"held": {
"type": "boolean",
"description": "the digest is held already: nothing to send; the outcome follows as an event"
}
},
"required": [
"transfer"
]
},
"optional": true
},
{
"name": "publish-part",
"description": "Send one chunk of one part of an open transfer; the last chunk of the last part ends the transfer, and the holder hashes what it received, asks builder_versions, stores it and says published or refused. A chunk's message stays under the bus's largest message (1 MiB), so at most 760 KiB of data, base64.",
"callers": "the build modules, and only the proved caller that opened the transfer",
"input": {
"type": "object",
"properties": {
"transfer": {
"type": "string",
"description": "the transfer"
},
"part": {
"type": "string",
"description": "the part's digest"
},
"sequence": {
"type": "integer",
"description": "the chunk's place in its part, from 0"
},
"last": {
"type": "boolean",
"description": "this chunk is the part's last"
},
"data": {
"type": "string",
"description": "the chunk's bytes, base64 (at most 760 KiB before encoding)"
}
},
"required": [
"transfer",
"part",
"sequence",
"last",
"data"
]
},
"answer": {
"type": "object",
"properties": {
"transfer": {
"type": "string",
"description": "the transfer"
},
"part": {
"type": "string",
"description": "the part"
},
"received": {
"type": "integer",
"description": "the chunks of the part held so far"
},
"complete": {
"type": "boolean",
"description": "the part is whole and its bytes match its digest"
}
},
"required": [
"transfer",
"part"
]
},
"optional": true
},
{
"name": "versions",
"description": "The versions the seat holds of one name, newest first: each one's version, tag, digest, kind and when it was published.",
"callers": "any caller whose role grants it",
"input": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the name"
},
"kind": {
"type": "string",
"description": "narrowed to one kind (optional)"
}
},
"required": [
"name"
]
},
"answer": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the name"
},
"versions": {
"type": "array",
"items": {
"$ref": "#/defs/Artifact"
},
"description": "newest first"
}
},
"required": [
"name",
"versions"
]
},
"optional": true
},
{
"name": "remove",
"description": "Remove one tagged version; the holder refuses an untagged one but for the retention removal of a bundle, archive or copied base by the artifact sweep's holder. Every removal emits removed with its reason. A tagged version the store cannot remove alone, because another tag names its digest, stays whole, its tag and bytes kept: the answer says kept with the reason shared-digest and the tags still naming the digest, removed false, and kept is emitted, never removed; the caller ends there without a retry.",
"callers": "the builder, for a tagged version; the artifact sweep's holder, for retention",
"input": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the name"
},
"version": {
"type": "string",
"description": "the version"
},
"tag": {
"type": "string",
"description": "the tag (for a tagged version)"
},
"digest": {
"type": "string",
"description": "the digest (for a bundle, archive or copied base)"
},
"reason": {
"type": "string",
"enum": [
"tag-gone",
"retention"
],
"description": "why it goes"
}
},
"required": [
"name",
"reason"
]
},
"answer": {
"type": "object",
"properties": {
"removed": {
"type": "boolean",
"description": "it is gone"
},
"kept": {
"type": "boolean",
"description": "it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags)"
},
"reason": {
"type": "string",
"enum": [
"shared-digest"
],
"description": "why it was kept: shared-digest, another tag names its digest"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "the other tags still naming its digest, when kept"
},
"artifact": {
"$ref": "#/defs/Artifact"
}
},
"required": [
"removed"
]
},
"optional": true
}
],
"events": [
{
"name": "published",
"body": {
"$ref": "#/defs/Outcome"
}
},
{
"name": "refused",
"body": {
"$ref": "#/defs/Outcome"
}
},
{
"name": "removed",
"body": {
"$ref": "#/defs/Outcome"
}
},
{
"name": "kept",
"body": {
"$ref": "#/defs/Outcome"
}
}
],
"defs": {
"Artifact": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)"
},
"version": {
"type": "string",
"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone"
},
"kind": {
"type": "string",
"enum": [
"package",
"image",
"bundle",
"archive",
"upstream"
],
"description": "what it is"
},
"tag": {
"type": "string",
"description": "the tag it carries, for a tagged version"
},
"digest": {
"type": "string",
"description": "its digest, sha256:<hex> (a Go package's h1: hash beside it in the version's record)"
},
"fingerprint": {
"type": "string",
"description": "the build's fingerprint"
},
"commit": {
"type": "string",
"description": "the source commit it was built from"
}
},
"required": [
"name",
"kind",
"digest"
],
"description": "one published thing, as every registry event and answer names it"
},
"Part": {
"type": "object",
"properties": {
"digest": {
"type": "string",
"description": "the part's digest, sha256:<hex>: an image's layer or config, a package's file, a bundle's blob"
},
"size": {
"type": "integer",
"description": "its size in bytes"
}
},
"required": [
"digest",
"size"
],
"description": "one part of a transfer"
},
"Outcome": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)"
},
"module": {
"type": "string",
"description": "the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact"
},
"version": {
"type": "string",
"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone"
},
"kind": {
"type": "string",
"enum": [
"package",
"image",
"bundle",
"archive",
"upstream"
],
"description": "what it is"
},
"tag": {
"type": "string",
"description": "the tag it carries, for a tagged version"
},
"digest": {
"type": "string",
"description": "its digest, sha256:<hex> (a Go package's h1: hash beside it in the version's record)"
},
"fingerprint": {
"type": "string",
"description": "the build's fingerprint"
},
"commit": {
"type": "string",
"description": "the source commit it was built from"
},
"holder": {
"type": "string",
"description": "the module holding the seat that said it"
},
"transfer": {
"type": "string",
"description": "the transfer it ends, when it ended one"
},
"reference": {
"type": "string",
"description": "the reference the seat answers: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> for a package (ADR 0605 §9)"
},
"reason": {
"type": "string",
"description": "why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest)"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "for kept: the other tags still naming its digest"
}
},
"required": [
"name",
"kind",
"digest",
"holder"
],
"description": "the body of published, refused and removed"
}
}
}