The checker serves a checking program's input on its seat's verb input, and the build module asks it (issue 1921, B0 review round 1)
mesh/delivery ready: it is merged on the Approve
mesh/delivery-group group feat/b0-checking-program checking: 3 of 3 member(s) ready
mesh/check checking 9c9ed7a1d8f1 on 2d201f781ced | novox/mesh-delivery#162 in group feat/b0-checking-program

The operator's answer: mesh-build-go reading the checker's state inputs broke ADR 0596 §2. The run ask's input now names
the verb seat:mesh-delivery-checker.input; the checker's tools serve it from the state inputs (Reader.Input, checked
against its digest), and the state is the checker's own again, read by no other module.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-06 23:39:39 +01:00
parent f4ab7a2bc8
commit 9c9ed7a1d8
14 changed files with 185 additions and 38 deletions
@@ -229,6 +229,20 @@ func SeatTryTools(tr Trying) []stdio.Tool {
return out
}
// SeatInputTools are the seat's verb input (check.InputVerb): a checking program's input as the checker keeps it, asked by
// the Go build module that runs the program (delivery.RunInput.Read), never read from the checker's state by it.
func SeatInputTools(r check.Reader) []stdio.Tool {
return []stdio.Tool{{Name: Seat + "." + delivery.CheckerInputVerb,
Description: "A checking program's input the checker keeps, by the sha256 of its exact bytes, as a run ask names it: the digest " +
"and the bytes, base64. A digest it keeps no input of, or bytes that read back as another digest, is an error. Read-only.",
Input: map[string]any{"type": "object", "required": []string{"digest"}, "properties": map[string]any{
"digest": map[string]any{"type": "string", "description": "the input's sha256, 64 lowercase hex digits"}}},
Run: func(args map[string]any) (any, error) {
digest, _ := args["digest"].(string)
return r.Input(digest)
}}}
}
// Tools are the read tools over the checker's states.
func Tools(r check.Reader) []stdio.Tool {
return []stdio.Tool{
@@ -381,6 +395,7 @@ func main() {
return err
}}
tools := append(append(append(append(Tools(r), TryTools(trying)...), OpenTriesTools(trying)...), SeatTryTools(trying)...), RecoveryTools(r, written)...)
tools = append(tools, SeatInputTools(r)...)
if err := stdio.Serve("", logged(tools)); err != nil {
fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err)
os.Exit(1)
@@ -9,6 +9,7 @@ import (
"time"
"git.novox.be/novox/mesh-delivery/sdk/tryevent"
"git.novox.be/novox/mesh-sdk/go/delivery"
"mesh-delivery-checker/internal/check"
)
@@ -108,6 +109,21 @@ func TestTheTryToolsAskAndReadATry(t *testing.T) { // ADR 0501 §5, the agreed i
if _, err := verbs[0].Run(map[string]any{"repository": "novox/hq", "head": strings.Repeat("1", 40)}); err == nil {
t.Error("the verb tried a repository nothing checks")
}
// The seat's verb input (issue 1921, ADR 0596 §2): a checking program's input as kept, which the Go build module asks.
body := []byte(`{"v":1}`)
in, _ := delivery.RunInputOf(check.InputVerb, body)
inputs := SeatInputTools(check.Reader{InputState: check.StateRead{Get: func(key string) (json.RawMessage, error) {
if key == in.Digest {
return body, nil
}
return nil, nil
}}})
if len(inputs) != 1 || inputs[0].Name != Seat+".input" {
t.Fatalf("input verbs = %+v", inputs)
}
if a, err := inputs[0].Run(map[string]any{"digest": in.Digest}); err != nil || string(a.(delivery.RunInputAnswer).Input) != string(body) {
t.Errorf("verb input = %+v, %v", a, err)
}
// A group try (issue 1921): the members, each an exact commit; members and one repository together are refused; the
// description says what it does in the words a search for "try a group" finds.
later := TryTools(Trying{Tries: store, Ender: store, Now: func() time.Time { return time.Unix(2, 5) },
@@ -93,9 +93,7 @@ type fakeBuildModules struct {
beside map[string][]string
// names are the nodes' names the last check ask carried, for the names check (ADR 0567 §12).
names []string
// inputs is the checker's state of run inputs; runs counts the run asks answered, and broken, when set, is why the
// checking program does not build (issue 1921).
inputs jetstream.KeyValue
// runs counts the run asks answered, and broken, when set, is why the checking program does not build (issue 1921).
runs atomic.Int32
broken atomic.Value
// drop, when set, drops every run ask unanswered and counts it in dropped, as a build module that does not know the job
@@ -125,20 +123,20 @@ func (d droppingAsks) Next(ctx context.Context, kind string, wait time.Duration)
}
// run answers a run ask as mesh-build-go does: the checking program's work (rules.Program) over the input the ask names,
// read from the checker's state inputs and checked against its digest, with no heads of its own laid.
// asked of the seat verb it names on the bus and checked against its digest (delivery.RunInput.Read, as mesh-build-go
// reads it), with no heads of its own laid.
func (f *fakeBuildModules) run(ctx context.Context, a delivery.StepAsk) (any, error) {
f.runs.Add(1)
if why, _ := f.broken.Load().(string); why != "" {
return nil, errors.New(why)
}
answer := delivery.RunAnswer{Built: true, Key: "program-0123456789abcdef"}
out, err := rules.Program(a.Run.Args, func() ([]byte, error) {
e, err := f.inputs.Get(ctx, a.Run.Input.Digest)
if err != nil {
return nil, err
}
return e.Value(), a.Run.Input.Verify(e.Value())
}, fstest.MapFS{})
// As mesh-build-go does, the input is asked before the program runs, and a step whose input cannot be had fails.
body, err := a.Run.Input.Read(func(key string, body any) (json.RawMessage, error) { return askVerb(f.js.Conn(), key, body) })
if err != nil {
return nil, err
}
out, err := rules.Program(a.Run.Args, func() ([]byte, error) { return body, nil }, fstest.MapFS{})
if err != nil {
answer.Exit, answer.Said = 2, err.Error()
}
@@ -146,6 +144,66 @@ func (f *fakeBuildModules) run(ctx context.Context, a delivery.StepAsk) (any, er
return answer, nil
}
// askVerb asks a seat verb on the subject that names no caller and reads the runtime's reply, {result, error}.
func askVerb(nc *nats.Conn, key string, body any) (json.RawMessage, error) {
seat, verb, ok := strings.Cut(strings.TrimPrefix(key, "seat:"), ".")
if !ok || !strings.HasPrefix(key, "seat:") {
return nil, fmt.Errorf("%q names no seat verb", key)
}
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
msg, err := nc.Request("mesh.seat."+seat+".tool."+verb, data, 5*time.Second)
if err != nil {
return nil, err
}
var r struct {
Result json.RawMessage `json:"result"`
Error string `json:"error"`
}
if err := json.Unmarshal(msg.Data, &r); err != nil {
return nil, err
}
if r.Error != "" {
return nil, errors.New(r.Error)
}
return r.Result, nil
}
// serveInputVerb answers the checker seat's verb input on the bus as the checker's tools do (check.Reader.Input over the
// checker's state inputs), and counts what it answered (issue 1921, ADR 0596 §2).
func (b *bed) serveInputVerb(t *testing.T, nc *nats.Conn) {
t.Helper()
kv := b.buckets[check.InputsBucket]
r := check.Reader{InputState: check.StateRead{Get: func(key string) (json.RawMessage, error) {
e, err := kv.Get(b.ctx, key)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, nil
}
if err != nil {
return nil, err
}
return e.Value(), nil
}}}
if _, err := nc.Subscribe("mesh.seat.mesh-delivery-checker.tool."+delivery.CheckerInputVerb, func(m *nats.Msg) {
var ask delivery.RunInputAsk
reply := map[string]any{}
if err := json.Unmarshal(m.Data, &ask); err != nil {
reply["error"] = err.Error()
} else if a, err := r.Input(ask.Digest); err != nil {
reply["error"] = err.Error()
} else {
reply["result"] = a
b.inputsAnswered.Add(1)
}
data, _ := json.Marshal(reply)
_ = m.Respond(data)
}); err != nil {
t.Fatal(err)
}
}
func (f *fakeBuildModules) count(tool string) int {
f.mu.Lock()
defer f.mu.Unlock()
@@ -312,6 +370,8 @@ type bed struct {
js jetstream.JetStream
buckets map[string]jetstream.KeyValue
tools map[string]jetstream.KeyValue
// inputsAnswered counts the inputs the seat's verb input answered (issue 1921).
inputsAnswered atomic.Int32
fake *fakeBuildModules
srvURL string
announce []string
@@ -476,7 +536,8 @@ func startBed(t *testing.T) *bed {
b.setHealth(t, "n1", module)
}
b.serveForge(t, ctl)
b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}, inputs: b.buckets[check.InputsBucket]}
b.serveInputVerb(t, ctl)
b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}}
b.fake.serve(t, "mesh-build-go", b.buckets[delivery.CheckerKeptBucket])
b.fake.serve(t, "mesh-build-node", b.buckets[delivery.CheckerKeptBucket])
b.report(t, "mesh-build-go", "n1", "go", "go1.27.1", "1")
@@ -704,7 +765,7 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
t.Errorf("n1's engine was asked %+v, want the check's candidate %s", got, key)
}
// Issue 1921 (B0): the verdict is the checking program's, built at the group's mesh-delivery head over its input kept in
// the state inputs, two run asks a check (the seat list, the verdict); the verdict names the program's build.
// the state inputs and asked of the seat's verb input, two run asks a check (the seat list, the verdict); the verdict names the program's build.
if by := v.JudgedBy; by == nil || by.Program.Commit != head1 || by.Program.Package != check.CheckingProgramPackage ||
by.Key != "program-0123456789abcdef" || len(by.Heads) != 1 || by.Heads[0].Commit != head1 {
t.Errorf("judged by %+v, want the checking program at the head %s", v.JudgedBy, head1)
@@ -712,6 +773,10 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
if n := b.fake.runs.Load(); n != 4 {
t.Errorf("%d run asks, want two for each of the two checks", n)
}
// The operator's answer on B0: each run's input reached the build module through the seat's verb input on the bus.
if n := b.inputsAnswered.Load(); n != 4 {
t.Errorf("the verb input answered %d inputs, want one for each run ask", n)
}
b.fake.mu.Lock()
names := b.fake.names
b.fake.mu.Unlock()
@@ -1069,7 +1134,7 @@ func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1
}
if said.Module != "mesh-delivery-checker" || said.Node != "n1" || !said.Keeps("owed") || !said.Readable("owed") ||
!said.Readable("previews") || !said.Readable("verdicts") || !said.Readable("kept") || !said.Readable("tries") || said.Readable("hearing") ||
!said.Keeps("asks") || said.Readable("asks") || !said.Readable("inputs") || len(said.States) != 8 {
t.Fatalf("the checker said %+v, want its eight states, asks and hearing its own, with inputs, kept, owed, previews, tries and verdicts readable, tries by the delivery view, inputs by the Go build module (1929/AC4; 1887 A5b; build_usage; 1921)", said)
!said.Keeps("asks") || said.Readable("asks") || !said.Keeps("inputs") || said.Readable("inputs") || len(said.States) != 8 {
t.Fatalf("the checker said %+v, want its eight states, asks, hearing and inputs its own, with kept, owed, previews, tries and verdicts readable, tries by the delivery view, inputs asked through the seat's verb input (1929/AC4; 1887 A5b; build_usage; 1921)", said)
}
}
@@ -24,7 +24,7 @@ import (
// judgeDeps are the parts the judge of a check is made of.
type judgeDeps struct {
// inputs keeps the checking program's inputs (check.InputsState).
// inputs keeps the checking program's inputs (check.InputsStateName, served on check.InputVerb).
inputs check.Inputs
// beginBound is how long a run ask may wait for a build node to begin it; check.DefaultBeginBound when 0.
beginBound time.Duration
@@ -38,8 +38,8 @@ func TestTheCheckerAnnouncesEveryStateItKeepsAndOwedReadable(t *testing.T) { //
t.Errorf("announced %v, the manifest keeps %v", announced, manifest)
}
// kept is read by the Go build module for a check ask (issue 1887 design limit 1); tries by the delivery view (build_usage).
// inputs by the Go build module, which hands one to the checking program a run ask names (issue 1921).
if want := []string{"inputs", "kept", "owed", "previews", "tries", "verdicts"}; !equal(readable, want) {
// inputs are the checker's own: the Go build module asks the seat's verb input for one (ADR 0596 §2, issue 1921).
if want := []string{"kept", "owed", "previews", "tries", "verdicts"}; !equal(readable, want) {
t.Errorf("readable %v, want %v", readable, want)
}
}
@@ -72,9 +72,10 @@ var keptStates = []announce.State{
{Name: "hearing"},
// The asks each running check put (check.AsksState): read by the checker's own checker_ask_again alone.
{Name: "asks"},
// The checking program's inputs by their digest (check.InputsState): read by the Go build module, which hands one to
// the program a run ask names (issue 1921).
{Name: check.InputsStateName, Readable: true},
// The checking program's inputs by their digest (check.InputsStateName): the checker's own, read by its tools alone,
// which serve one on the seat's verb input to the Go build module that runs the program (check.InputVerb; ADR 0596 §2,
// issue 1921).
{Name: check.InputsStateName},
{Name: "kept", Readable: true},
{Name: "owed", Readable: true},
{Name: "previews", Readable: true},
@@ -429,7 +429,7 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679
}
in := b.runs[1].Run.Input // the facts kept as their exact bytes under their digest
body, _, found, _ := b.inputs.Get(context.Background(), in.Digest)
if !found || in.Verify(body) != nil || in.State != InputsState ||
if !found || in.Verify(body) != nil || in.Verb != InputVerb ||
!strings.Contains(string(body), `"group":"feat/x"`) {
t.Errorf("%s: the verdict's input %+v is not kept whole: %s", row.name, in, body)
}
@@ -138,7 +138,7 @@ type Examiner struct {
// and fingerprints a group try's manifest validator and composition judge (issue 1921); nil leaves a whole try with no
// manifest or composition judged, said.
Candidates TryCandidates
// Inputs keeps the checking program's inputs (the state InputsState); BeginBound is how long a run ask may wait for a
// Inputs keeps the checking program's inputs (the state InputsStateName, served on InputVerb); BeginBound is how long a run ask may wait for a
// build node to begin it (DefaultBeginBound when zero).
Inputs Inputs
BeginBound time.Duration
@@ -9,6 +9,7 @@ import (
"time"
"git.novox.be/novox/mesh-sdk/go/buildstep"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
@@ -22,12 +23,14 @@ import (
// verdict of the facts the asking gathered (`-verdict`). The running checker asks, keeps and records; it judges nothing.
const (
// InputsStateName is the checker's own state of run inputs, InputsBucket its bucket and InputsState its name as a run
// ask's input names it (delivery.RunInput.State): every input by the sha256 of its exact bytes, read by the Go build
// module, which checks the digest (delivery.RunInput.Verify).
// InputsStateName is the checker's own state of run inputs and InputsBucket its bucket: every input by the sha256 of
// its exact bytes, read by the checker alone. InputVerb is the seat verb a run ask's input names
// (delivery.RunInput.Verb), which the checker's tools serve from that state (Reader.Input): the Go build module asks it
// as its manifest's invokes grants it and checks the digest (delivery.RunInput.Read), never reading the state (ADR 0596
// §2; the operator's answer on milestone 47/B0).
InputsStateName = "inputs"
InputsBucket = delivery.CheckerModule + "_" + InputsStateName
InputsState = delivery.CheckerModule + "." + InputsStateName
InputVerb = "seat:" + catalogue.CheckerSeat + "." + delivery.CheckerInputVerb
// CheckingProgramDir and CheckingProgramPackage are where in mesh-delivery the checking program is built.
CheckingProgramDir = "modules/mesh-delivery-checker"
CheckingProgramPackage = "mesh-delivery-checker/cmd/mesh-checking-program"
@@ -83,14 +86,14 @@ func (st *state) runProgram(ctx context.Context, args []string, body []byte) (js
return nil, nil, fmt.Errorf("it cannot be built: the trunk head of %s is not known", DeliveryRepository)
}
if st.j.Inputs == nil {
return nil, nil, fmt.Errorf("its input cannot be kept: the checker keeps no state %s", InputsState)
return nil, nil, fmt.Errorf("its input cannot be kept: the checker keeps no state %s", InputsStateName)
}
in, err := delivery.RunInputOf(InputsState, body)
in, err := delivery.RunInputOf(InputVerb, body)
if err != nil {
return nil, nil, err
}
if err := st.j.Inputs.Keep(ctx, in.Digest, body); err != nil {
return nil, nil, fmt.Errorf("its input could not be kept in %s: %w", InputsState, err)
return nil, nil, fmt.Errorf("its input could not be kept in %s: %w", InputsStateName, err)
}
job := delivery.RunJob{Program: prog, Heads: st.runHeads(), Input: in, Args: args, Reader: "the checking program at " + at}
var ids []string
@@ -1,6 +1,8 @@
package check
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"slices"
@@ -37,7 +39,7 @@ type Reader struct {
VerdictState, KeptState, CandidateState StateRead
PreviewState StateRead // the checker's merge previews
InputState StateRead // the checking program's inputs (InputsState)
InputState StateRead // the checking program's inputs (InputsStateName)
StepStates map[string]StateRead // the build modules' steps states, by module
HaveState StateRead // the node-engine's have: whether a build module is on a node and running
StepBound time.Duration // how long the checker waits for one step; DefaultStepBound when 0
@@ -263,10 +265,33 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string,
return out, cut, nil
}
// Input is the answer of the seat's verb input (InputVerb): the input kept under digest, its exact bytes, which the Go build
// module hands the checking program a run ask names (delivery.RunInput.Read). A digest that is no sha256, one the checker
// keeps nothing under, and bytes that read back as another digest are errors, never an answer.
func (r Reader) Input(digest string) (delivery.RunInputAnswer, error) {
if r.InputState.Get == nil {
return delivery.RunInputAnswer{}, fmt.Errorf("the state %s cannot be read here", InputsStateName)
}
if len(digest) != 64 || strings.Trim(digest, "0123456789abcdef") != "" {
return delivery.RunInputAnswer{}, fmt.Errorf("%q is no sha256 digest of 64 lowercase hex digits", digest)
}
raw, err := r.InputState.Get(digest)
if err != nil {
return delivery.RunInputAnswer{}, fmt.Errorf("the input %s cannot be read: %w", digest, err)
}
if len(raw) == 0 {
return delivery.RunInputAnswer{}, fmt.Errorf("the checker keeps no input %s (an input is kept a day)", digest)
}
if sum := sha256.Sum256(raw); hex.EncodeToString(sum[:]) != digest {
return delivery.RunInputAnswer{}, fmt.Errorf("the input kept under %s reads back as %d bytes of another digest", digest, len(raw))
}
return delivery.RunInputAnswer{Digest: digest, Input: raw}, nil
}
// Inputs are the checking program's kept inputs: with a digest that input as kept, else every digest with its size.
func (r Reader) Inputs(digest string) (map[string]json.RawMessage, string, error) {
if r.InputState.Keys == nil || r.InputState.Get == nil {
return nil, "", fmt.Errorf("the state %s cannot be read here", InputsState)
return nil, "", fmt.Errorf("the state %s cannot be read here", InputsStateName)
}
if digest != "" {
raw, err := r.InputState.Get(digest)
@@ -125,7 +125,7 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { //
// Issue 1921: checker_inputs answers the checking program's inputs, every digest with its size, or one as kept.
inputs := newMemKV()
body := []byte(`{"v":1,"group":"feat/x"}`)
in, _ := delivery.RunInputOf(InputsState, body)
in, _ := delivery.RunInputOf(InputVerb, body)
_ = memInputs{inputs}.Keep(context.Background(), in.Digest, body)
rd.InputState = kvRead(inputs)
if all, _, err := rd.Inputs(""); err != nil || string(all[in.Digest]) != fmt.Sprintf(`{"size":%d}`, len(body)) {
@@ -137,6 +137,26 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { //
if _, _, err := rd.Inputs(strings.Repeat("0", 64)); err == nil {
t.Error("an input nothing kept was answered")
}
// The operator's answer on B0: the seat's verb input answers the exact bytes, which the build module's read takes;
// a digest that is none, one nothing is kept under, and bytes kept under another digest are errors.
if got, err := in.Read(func(key string, b any) (json.RawMessage, error) {
if key != "seat:mesh-delivery-checker.input" {
return nil, fmt.Errorf("asked %s", key)
}
a, err := rd.Input(b.(delivery.RunInputAsk).Digest)
if err != nil {
return nil, err
}
return json.Marshal(a)
}); err != nil || string(got) != string(body) {
t.Errorf("the input through the verb = %s, %v", got, err)
}
_, _ = inputs.Create(context.Background(), strings.Repeat("1", 64), []byte(`{"v":2}`))
for _, digest := range []string{"d1", strings.Repeat("0", 64), strings.Repeat("1", 64)} {
if a, err := rd.Input(digest); err == nil {
t.Errorf("the verb answered %s: %+v", digest, a)
}
}
}
// 1680 live acceptance: checker_previews shows the preview key of an open pull request, as stored, narrowed by repository and head.
@@ -167,10 +167,11 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
if want := []string{"checker_ask_again", "checker_inputs", "checker_kept", "checker_previews", "checker_pull", "checker_status", "checker_tries", "checker_try", "checker_try_end", "checker_try_read", "checker_verdicts"}; !reflect.DeepEqual(sorted(m.Tools), want) {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
// The try is the checker's own seat's verbs (ADR 0547 §10, issue 774), served beside the tools by the same code.
// The try is the checker's own seat's verbs (ADR 0547 §10, issue 774), served beside the tools by the same code; so is
// input, a checking program's input the Go build module asks (issue 1921, ADR 0596 §2).
if len(m.Claims) != 1 || m.Claims[0].Name != "mesh-delivery-checker" || m.Claims[0].Scope != "mesh" ||
!reflect.DeepEqual(m.Claims[0].Serves, []string{"try", "try-read", "try-end"}) {
t.Errorf("claims = %+v, want the mesh seat mesh-delivery-checker serving try, try-read and try-end", m.Claims)
!reflect.DeepEqual(m.Claims[0].Serves, []string{"try", "try-read", "try-end", "input"}) {
t.Errorf("claims = %+v, want the mesh seat mesh-delivery-checker serving try, try-read, try-end and input", m.Claims)
}
for _, k := range []string{"logs", "emits", "provides", "requires", "defines-seats", "seat", "seats", "bus"} { // config: compose-answer-bound (ADR 0593 §7)
if _, ok := top[k]; ok {
+2 -1
View File
@@ -9,7 +9,8 @@
"serves": [
"try",
"try-read",
"try-end"
"try-end",
"input"
]
}
],
@@ -4848,7 +4848,7 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
}
}
}
if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" {
if strings.Join(checkerVerbs, ",") != "try,try-read,try-end,input" { // input: a checking program's input (issue 1921)
t.Errorf("the checker seat serves %v", checkerVerbs)
}
// The four health seats (novox/hq ADR 0564 clause 1): at mesh scope, each held once, each naming ADR 0564, with the