A module may declare its own seats (novox/hq ADR 0118)

The manifest carries seats and uses; registration refuses a mesh-* name, a
duplicate declarer, an undeclared uses or claim, a seat with no protocol, a
scope mismatch, and a holder that does not answer what its seat promises.

The parser stops judging unknown claim names, because it cannot: another
module may declare that seat, and one manifest cannot tell. The test that
encoded the old rule is rewritten to assert the refusal at registration, and
a new one pins the case the parser could not have distinguished.

Tools are declared for the first time, under their own key — serves already
means a provision's facts.
This commit is contained in:
2026-09-26 22:17:17 +02:00
parent 112cbd294d
commit 7232d6df4b
5 changed files with 408 additions and 12 deletions
+24
View File
@@ -192,6 +192,26 @@ type Manifest struct {
// that every new module would force its predecessors to update.
Claims []Claim `json:"claims,omitempty"`
// Seats this module declares of its own, with their protocols (novox/hq ADR 0118). The set
// of seats a mesh has is the mesh's own plus these, derived from what is registered rather
// than written in the controller — closed, and extensible without changing the mesh.
Seats []SeatDeclaration `json:"seats,omitempty"`
// Uses are seats this module sends to. It names the *seat*, never the module holding it, so
// the implementation can be replaced under it and no caller changes. A caller gets publish
// on that seat's inbound subjects and nothing else — not its outbound events, and not a
// subscription to the queue it writes to (design 29 §2).
Uses []string `json:"uses,omitempty"`
// Tools are the tools this module answers — request and reply, awaited.
//
// **New, and not `serves`**, which this manifest already uses for the facts a consumer needs
// in order to reach a provision. Two meanings under one key would be a footgun in the one
// file a module author reads most. Until now a module's tools were known only at runtime,
// from MESH_TOOL_MODULES in its image; declaring them is what lets the mesh check that a
// module claiming a seat answers what that seat's protocol promises (novox/hq ADR 0118).
Tools []string `json:"tools,omitempty"`
// Capabilities the machine must have. A different field from Requires because the remedy
// differs: a missing module can be assigned, and a missing capability means the wrong
// machine.
@@ -970,6 +990,10 @@ func ParseManifest(raw []byte) (Manifest, error) {
if wellFormed {
problems = append(problems, claimProblems(m)...)
}
// What one manifest can be judged on: a declaration's shape, its scope, and the reserved
// prefix. Whether a seat anybody names exists, and whether a holder answers for it, are
// facts about the catalogue and are checked at registration (CatalogueProblems).
problems = append(problems, declaredSeatProblems(m)...)
if m.Computed != "" && len(m.Resources) > 0 {
// One or the other. A module that both ships files and has them computed would leave
// nobody able to say where a given file came from.