diff --git a/contracts/seats/mesh-wants/fixtures/events.json b/contracts/seats/mesh-wants/fixtures/events.json new file mode 100644 index 00000000..70fbc77c --- /dev/null +++ b/contracts/seats/mesh-wants/fixtures/events.json @@ -0,0 +1,170 @@ +[ + { + "name": "an assignment graph revision kept", + "of": "event:assignment-revision", + "subject": "mesh.seat.mesh-wants.event.assignment-revision", + "given": { + "v": 1, + "revision": 40, + "kind": "trunk", + "catalogs": [ + { + "branch": "main", + "revision": 12, + "source": 3589 + } + ], + "cause": "placement-changed", + "at": "2026-11-09T19:00:00Z" + } + }, + { + "name": "a difference worked out", + "of": "event:difference", + "subject": "mesh.seat.mesh-wants.event.difference", + "given": { + "v": 1, + "from": 40, + "to": 41, + "source": 3589, + "changes": 1, + "nodes": [ + "n1" + ] + } + }, + { + "name": "an entry wanted from a trunk revision", + "of": "event:wanted", + "subject": "mesh.seat.mesh-wants.event.wanted", + "given": { + "v": 1, + "node": "n1", + "module": "xclip", + "fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "revision": 40, + "source": 3589, + "at": "2026-11-09T19:00:00Z" + } + }, + { + "name": "a composing wait held with its bound", + "of": "event:wait-held", + "subject": "mesh.seat.mesh-wants.event.wait-held", + "given": { + "step": "compose", + "part": "node-engine", + "version": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f", + "node": "n1", + "module": "xclip", + "fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "since": "2026-11-09T19:00:00Z", + "bound": "30m" + } + }, + { + "name": "a wait ended, released by hand", + "of": "event:wait-ended", + "subject": "mesh.seat.mesh-wants.event.wait-ended", + "given": { + "step": "compose", + "part": "node-engine", + "version": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f", + "node": "n1", + "module": "xclip", + "fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "since": "2026-11-09T19:00:00Z", + "bound": "30m", + "how": "released", + "by": "lead@shanks: the node is away" + } + }, + { + "name": "the store past 80 % of its cap", + "of": "event:filling", + "subject": "mesh.seat.mesh-wants.event.filling", + "given": { + "store": "assignment-revisions", + "bytes": 450000000, + "cap": 536870912, + "at": "2026-11-09T19:00:00Z" + } + }, + { + "name": "the store full, keeping its last revision", + "of": "event:full", + "subject": "mesh.seat.mesh-wants.event.full", + "given": { + "store": "assignment-revisions", + "bytes": 536870912, + "cap": 536870912, + "at": "2026-11-09T19:00:00Z", + "refused": "the next revision; no new want is written" + } + }, + { + "name": "the store back below its cap", + "of": "event:room", + "subject": "mesh.seat.mesh-wants.event.room", + "given": { + "store": "assignment-revisions", + "bytes": 300000000, + "cap": 536870912, + "at": "2026-11-09T19:00:00Z" + } + }, + { + "name": "the composition step ended passed on the assigner's node, a node asleep warned", + "of": "event:step-ended", + "subject": "mesh.seat.mesh-wants.event.step-ended.novox", + "given": { + "v": 1, + "id": "m2kx9a1b3c4d6a", + "pipeline": "main.12.nats", + "step": "composition", + "module": "nats", + "commit": "0123456789abcdef0123456789abcdef01234567", + "fingerprint": "fp-0123", + "node": "novox", + "outcome": "passed", + "warnings": [ + "g14 asleep: composed from its last capabilities" + ], + "started": "2026-11-10T10:00:00Z", + "ended": "2026-11-10T10:01:00Z" + } + }, + { + "name": "the composition step failed, two modules owning one path", + "of": "event:step-ended", + "subject": "mesh.seat.mesh-wants.event.step-ended.novox", + "given": { + "v": 1, + "id": "m2kx9a1b3c4d6b", + "pipeline": "main.12.nats", + "step": "composition", + "module": "nats", + "commit": "0123456789abcdef0123456789abcdef01234567", + "fingerprint": "fp-0123", + "node": "novox", + "outcome": "failed", + "said": "nats and xclip both own /etc/nats.conf on ace" + } + }, + { + "name": "a version delivered with the machines away", + "of": "event:version-delivered", + "subject": "mesh.seat.mesh-wants.event.version-delivered", + "given": { + "v": 1, + "module": "mesh-sdk", + "version": "0.1.15", + "parts": { + "mesh-sdk:go": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + }, + "away": [ + "laptop" + ] + } + } +] diff --git a/contracts/seats/mesh-wants/fixtures/states.json b/contracts/seats/mesh-wants/fixtures/states.json index cb38a399..818850d5 100644 --- a/contracts/seats/mesh-wants/fixtures/states.json +++ b/contracts/seats/mesh-wants/fixtures/states.json @@ -43,5 +43,100 @@ "heads": "0123456789ab", "state": "pass" } + }, + { + "name": "a trunk assignment graph revision naming its inputs", + "of": "state:assignment-revisions/trunk.", + "given": { + "v": 1, + "revision": 40, + "kind": "trunk", + "catalogs": [ + { + "branch": "main", + "revision": 12, + "source": 3589 + } + ], + "placements": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "capabilities": [ + { + "node": "n1", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "cause": "catalog-revision", + "at": "2026-11-09T19:00:00Z", + "entries": [ + { + "node": "n1", + "module": "xclip", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + } + }, + { + "name": "a candidate's assignment graph revision", + "of": "state:assignment-revisions/candidate..", + "given": { + "v": 1, + "revision": 41, + "kind": "candidate", + "catalogs": [ + { + "branch": "main", + "revision": 13, + "source": 3589 + } + ], + "placements": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "capabilities": [ + { + "node": "n1", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "cause": "catalog-revision", + "at": "2026-11-09T19:00:00Z", + "entries": [ + { + "node": "n1", + "module": "xclip", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "group": "feat/47-G", + "heads": "0123456789ab" + } + }, + { + "name": "an assignment entry with an acting edge and a provisioning edge", + "of": "state:assignment-revisions/entry.", + "given": { + "v": 1, + "node": "n1", + "module": "xclip", + "branch": "main", + "fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "catalog": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "edges": [ + { + "kind": "acting", + "node": "n1", + "module": "node-engine", + "fp": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f", + "seat": "node-engine", + "step": "compose", + "bound": "30m" + }, + { + "kind": "provisioning", + "node": "n2", + "module": "postgres", + "provision": "database" + } + ] + } } ] diff --git a/contracts/seats/mesh-wants/fixtures/verbs.json b/contracts/seats/mesh-wants/fixtures/verbs.json new file mode 100644 index 00000000..3a50a8c5 --- /dev/null +++ b/contracts/seats/mesh-wants/fixtures/verbs.json @@ -0,0 +1,173 @@ +[ + { + "name": "revision asks the trunk's newest", + "of": "verb:revision.input", + "given": {} + }, + { + "name": "revision asks a candidate's", + "of": "verb:revision.input", + "given": { + "group": "feat/47-G", + "heads": "0123456789ab" + } + }, + { + "name": "revision answers one revision naming its inputs", + "of": "verb:revision.answer", + "given": { + "revision": { + "v": 1, + "revision": 40, + "kind": "trunk", + "catalogs": [ + { + "branch": "main", + "revision": 12, + "source": 3589 + } + ], + "placements": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "capabilities": [ + { + "node": "n1", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "cause": "catalog-revision", + "at": "2026-11-09T19:00:00Z", + "entries": [ + { + "node": "n1", + "module": "xclip", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + } + } + }, + { + "name": "entry asks by node and module in a revision", + "of": "verb:entry.input", + "given": { + "revision": 40, + "node": "n1", + "module": "xclip" + } + }, + { + "name": "entry answers the entry with its edges", + "of": "verb:entry.answer", + "given": { + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "entry": { + "v": 1, + "node": "n1", + "module": "xclip", + "branch": "main", + "fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "catalog": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "edges": [ + { + "kind": "acting", + "node": "n1", + "module": "node-engine", + "fp": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f", + "seat": "node-engine", + "step": "compose", + "bound": "30m" + }, + { + "kind": "provisioning", + "node": "n2", + "module": "postgres", + "provision": "database" + } + ] + } + } + }, + { + "name": "difference asks a candidate's against trunk", + "of": "verb:difference.input", + "given": { + "group": "feat/47-G", + "heads": "0123456789ab" + } + }, + { + "name": "difference answers the changes and their order", + "of": "verb:difference.answer", + "given": { + "from": 40, + "to": 41, + "group": "feat/47-G", + "heads": "0123456789ab", + "changes": [ + { + "node": "n1", + "module": "xclip", + "from": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56", + "to": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f" + } + ], + "nodes": [ + "n1" + ], + "tiers": [ + [ + { + "node": "n1", + "module": "xclip", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + ] + } + }, + { + "name": "difference refuses a changed fingerprint with no difference, by name", + "of": "verb:difference.answer", + "given": { + "from": 40, + "to": 41, + "changes": [], + "refused": [ + { + "module": "xclip", + "why": "its fingerprint changed and its difference is empty" + } + ] + } + }, + { + "name": "a composition step asked by the runner", + "of": "accept:step", + "subject": "mesh.seat.mesh-wants.accept.step", + "given": { + "pipeline": "main.12.nats", + "step": "composition", + "module": "nats", + "branch": "main", + "commit": "0123456789abcdef0123456789abcdef01234567", + "revision": 12, + "source": 3589, + "fingerprint": "fp-0123" + } + }, + { + "name": "input asks a composition step's input by digest", + "of": "verb:input.input", + "given": { + "digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + }, + { + "name": "input answers the exact bytes", + "of": "verb:input.answer", + "given": { + "digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "input": "eyJ2IjoxfQ==" + } + } +] diff --git a/contracts/seats/mesh-wants/seat.json b/contracts/seats/mesh-wants/seat.json index a8834526..e002258b 100644 --- a/contracts/seats/mesh-wants/seat.json +++ b/contracts/seats/mesh-wants/seat.json @@ -51,6 +51,160 @@ "type": "object", "description": "what a bus upgrade would do, read only" } + }, + { + "name": "revision", + "description": "The assignment half of the revision view (ADR 0656 \u00a76): one assignment graph revision, named by its own number and naming its inputs, with its entries' digests; the trunk's newest when none is named, or a candidate's for its group and heads. Reads only.", + "input": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "description": "a kept revision; absent: the newest of trunk, or of the candidate named" + }, + "group": { + "type": "string", + "description": "a candidate's group" + }, + "heads": { + "type": "string", + "description": "a candidate's heads" + } + } + }, + "answer": { + "type": "object", + "properties": { + "revision": { + "$ref": "#/defs/AssignmentRevision" + } + }, + "required": [ + "revision" + ] + }, + "class": "read" + }, + { + "name": "entry", + "description": "One entry of an assignment graph revision whole, by its digest or by its node and module in a revision, with the seat rule that gave each edge. Reads only.", + "input": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "description": "a kept revision; absent: the newest of trunk, or of the candidate named" + }, + "group": { + "type": "string", + "description": "a candidate's group" + }, + "heads": { + "type": "string", + "description": "a candidate's heads" + }, + "digest": { + "type": "string" + }, + "node": { + "type": "string" + }, + "module": { + "type": "string" + } + } + }, + "answer": { + "type": "object", + "properties": { + "digest": { + "type": "string" + }, + "entry": { + "$ref": "#/defs/AssignmentEntry" + } + }, + "required": [ + "digest", + "entry" + ] + }, + "class": "read" + }, + { + "name": "difference", + "description": "A change's graph difference, which is its delivery: two kept revisions compared, or a candidate's newest against trunk's newest, with the changed module versions, the touched nodes and the order as tiers. Reads only.", + "input": { + "type": "object", + "properties": { + "from": { + "type": "integer" + }, + "to": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "heads": { + "type": "string" + } + } + }, + "answer": { + "$ref": "#/defs/Difference" + }, + "class": "read" + }, + { + "name": "input", + "class": "read", + "callers": [ + "seat:node-build-go", + "seat:node-build-node", + "seat:node-build-image" + ], + "description": "A step's input by its digest, as the assigner keeps it for the composition step it asked a build seat to run (design 47 G section 354).", + "input": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the input's sha256" + } + }, + "required": [ + "digest" + ] + }, + "answer": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the input's sha256" + }, + "input": { + "type": "string", + "description": "the input's exact bytes, base64" + } + }, + "required": [ + "digest", + "input" + ] + } + } + ], + "accepts": [ + { + "name": "step", + "body": { + "$ref": "#/defs/StepRun" + }, + "callers": [ + "seat:mesh-pipeline-runner" + ] } ], "events": [ @@ -59,6 +213,220 @@ "body": { "type": "object" } + }, + { + "name": "assignment-revision", + "body": { + "type": "object", + "description": "an assignment graph revision kept", + "properties": { + "v": { + "type": "integer" + }, + "revision": { + "type": "integer" + }, + "kind": { + "type": "string" + }, + "cause": { + "type": "string" + }, + "group": { + "type": "string" + }, + "heads": { + "type": "string" + }, + "catalogs": { + "type": "array", + "items": { + "$ref": "#/defs/CatalogOf" + }, + "minItems": 1, + "description": "the catalog graph revision of each watched branch a placement names, which the entries come from" + }, + "at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "v", + "revision", + "kind", + "catalogs" + ] + } + }, + { + "name": "difference", + "body": { + "type": "object", + "description": "a difference worked out between two revisions", + "properties": { + "v": { + "type": "integer" + }, + "from": { + "type": "integer" + }, + "to": { + "type": "integer" + }, + "source": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "heads": { + "type": "string" + }, + "changes": { + "type": "integer", + "description": "how many module versions it changes" + }, + "nodes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "v", + "from", + "to" + ] + } + }, + { + "name": "wanted", + "body": { + "type": "object", + "description": "an entry written as a want, from a trunk revision only", + "properties": { + "v": { + "type": "integer" + }, + "node": { + "type": "string" + }, + "module": { + "type": "string" + }, + "fp": { + "type": "string" + }, + "revision": { + "type": "integer" + }, + "source": { + "type": "integer" + }, + "at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "v", + "node", + "module", + "fp", + "revision" + ] + } + }, + { + "name": "wait-held", + "body": { + "$ref": "#/defs/Wait" + } + }, + { + "name": "wait-ended", + "body": { + "$ref": "#/defs/Wait" + } + }, + { + "name": "filling", + "body": { + "$ref": "#/defs/Fill" + } + }, + { + "name": "full", + "body": { + "$ref": "#/defs/Fill" + } + }, + { + "name": "room", + "body": { + "$ref": "#/defs/Fill" + } + }, + { + "name": "step-ended", + "subject": "step-ended.", + "body": { + "$ref": "#/defs/StepEnded" + }, + "keep": { + "max-age-days": 90, + "max-mib": 32 + } + }, + { + "name": "version-delivered", + "keep": { + "max-age-days": 90, + "max-mib": 64 + }, + "body": { + "type": "object", + "description": "a version delivered to every present machine that places it, and healthy (design 47 G section 376)", + "properties": { + "v": { + "type": "integer" + }, + "module": { + "type": "string", + "description": "the module" + }, + "version": { + "type": "string", + "description": "MAJOR.MINOR.PATCH" + }, + "parts": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "the digest of each of its parts, by artifact reference" + }, + "away": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the machines away when it was said" + }, + "at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "v", + "module", + "version", + "parts" + ] + } } ], "family": { @@ -120,6 +488,17 @@ }, "self": { "type": "boolean" + }, + "version": { + "type": "string", + "description": "the module's released version, for people; none for a build of another watched branch (design 47 G section 379)" + }, + "parts": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "the digest of each of the module's parts, by artifact reference; the node-engine fetches and checks each part by it (design 47 G section 379)" } } }, @@ -279,6 +658,577 @@ ], "history": 5, "description": "composition verdicts of a group's check; the checker reads them" + }, + { + "name": "assignment-revisions", + "template": "objects", + "description": "the assignment graph revisions, each a list of its entries' digests naming its inputs, and each entry once by its digest; a revision is kept while a delivery record pins it or it is current (design 47)", + "key-forms": [ + { + "key": "trunk.", + "value": { + "$ref": "#/defs/AssignmentRevision" + }, + "description": "a trunk revision" + }, + { + "key": "candidate..", + "value": { + "$ref": "#/defs/AssignmentRevision" + }, + "description": "a candidate's newest revision, dropped on heads-replaced" + }, + { + "key": "entry.", + "value": { + "$ref": "#/defs/AssignmentEntry" + }, + "description": "an entry, written once, removed when no kept revision lists it" + } + ] + }, + { + "name": "inputs", + "template": "state", + "history": 1, + "description": "a step's input, named by its write time and its sha256 (design 47 G section 354)", + "key-forms": [ + { + "key": "