From b7e251a46394a6cf759cd7df0acd0681c43d7d55 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 06:27:42 +0100 Subject: [PATCH 1/3] B5g3-1: a run's input is any size, RunInputBound goes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §712, §354, A-237 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../cmd/mesh-checking-program/main_test.go | 14 +++++++------- modules/mesh-delivery-checker/rules/seats.go | 4 ++-- .../mesh-delivery-sdk/buildstep/ask_test.go | 7 ++++--- modules/mesh-delivery-sdk/buildstep/run.go | 18 +++++------------- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go index cbd54007..972fa7d8 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go @@ -153,8 +153,8 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) { } // Review finding 3 of milestone 47/G: the heads of a real group carry every seat definition the reader holds, unchanged // (this repository's own contracts, laid as its head): the answer leaves them all to the checker's list and stays - // within the bound a run's answer is written under (buildstep.RunInputBound), where the whole list would not. - t.Run("heads carrying every definition the reader holds unchanged answer none of them, within the bound", func(t *testing.T) { + // far below the whole list. + t.Run("heads carrying every definition the reader holds unchanged answer none of them", func(t *testing.T) { real := under{prefix: "novox/mesh-delivery", fs: os.DirFS("../../../..")} group := rules.Facts{V: rules.FactsVersion, Members: []rules.Member{{Repository: "novox/mesh-delivery", Head: "h"}}} // An empty list of digests answers every definition whole (a checker that hands its list's digests, holding none). @@ -166,9 +166,9 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) { group.SeatDigests = wholeDigests(t, real, "novox/mesh-delivery") same, err := rules.SeatsOf(real, group) answer, _ := json.Marshal(same) - if err != nil || len(same.Seats) != 0 || same.Same != len(whole.Seats) || len(answer) > buildstep.RunInputBound { - t.Errorf("unchanged heads answer %d definitions (%d the same), %d bytes; want none, all %d the same, within %d: %v", - len(same.Seats), same.Same, len(answer), len(whole.Seats), buildstep.RunInputBound, err) + if err != nil || len(same.Seats) != 0 || same.Same != len(whole.Seats) { + t.Errorf("unchanged heads answer %d definitions (%d the same), %d bytes; want none, all %d the same: %v", + len(same.Seats), same.Same, len(answer), len(whole.Seats), err) } // Review round 2 of G: a checker from before G (B0's, which makes G's own check) hands no digests, and the answer // then keeps each seat's own description only, so a group's whole list stays within the bound; each still parses. @@ -206,8 +206,8 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) { t.Errorf("B0-shaped facts answer %s with %d inner descriptions, its own %q: %v; want only its own", raw[:min(len(raw), 80)], inner, d.Description, perr) } } - if answer, _ := json.Marshal(b0); len(answer) > buildstep.RunInputBound || len(answer) >= len(mustJSON(t, whole))*3/4 { - t.Errorf("B0-shaped facts answer %d bytes (whole %d), want within %d and far below the whole", len(answer), len(mustJSON(t, whole)), buildstep.RunInputBound) + if answer, _ := json.Marshal(b0); len(answer) >= len(mustJSON(t, whole))*3/4 { + t.Errorf("B0-shaped facts answer %d bytes (whole %d), want far below the whole", len(answer), len(mustJSON(t, whole))) } // One definition the heads change is answered alone. for seat := range group.SeatDigests { diff --git a/modules/mesh-delivery-checker/rules/seats.go b/modules/mesh-delivery-checker/rules/seats.go index 1e3a37c8..50040e14 100644 --- a/modules/mesh-delivery-checker/rules/seats.go +++ b/modules/mesh-delivery-checker/rules/seats.go @@ -21,8 +21,8 @@ import ( // milestones 47/B0 and 47/G): the checking program built from the heads reads the definitions the heads' contracts // modules carry, by the domain SDK's one reading (sdk/seatdefs), the indexer's too, and answers them; the running checker // lays them by seat over the list its reader holds from the seat mesh-catalog (verb seat-definitions) and reads no file -// of the heads. The reader's list is not in the program's input: the mesh's definitions are larger than a run input's -// bound (buildstep.RunInputBound). +// of the heads. The reader's list is not in the program's input: it is large, and the answer carries only what the heads +// change. // SeatsVersion is the version of SeatsAnswer this program writes. const SeatsVersion = 1 diff --git a/modules/mesh-delivery-sdk/buildstep/ask_test.go b/modules/mesh-delivery-sdk/buildstep/ask_test.go index 7676d0f4..258b5215 100644 --- a/modules/mesh-delivery-sdk/buildstep/ask_test.go +++ b/modules/mesh-delivery-sdk/buildstep/ask_test.go @@ -113,7 +113,7 @@ func TestAskCheckAndRoundTrip(t *testing.T) { {"input on a state", func(a *StepAsk) { a.Run.Input.Verb = "mesh-delivery-checker.inputs" }, "no seat:."}, {"input on one machine", func(a *StepAsk) { a.Run.Input.Verb = "seat:mesh-delivery-checker.input@ace" }, "no seat:."}, {"input by no digest", func(a *StepAsk) { a.Run.Input.Digest = "d1" }, "no sha256 digest"}, - {"input over its bound", func(a *StepAsk) { a.Run.Input.Size = RunInputBound + 1 }, "outside 1 to"}, + {"input of no bytes", func(a *StepAsk) { a.Run.Input.Size = 0 }, "under 1"}, {"argument of the shell", func(a *StepAsk) { a.Run.Args = []string{"-x; rm"} }, "no word or flag"}, } { a := runAsk() @@ -148,8 +148,9 @@ func TestAskCheckAndRoundTrip(t *testing.T) { if a := stepAsk(); !a.Step.Skips("./modules/nats/a", "sha256:bbbb") || a.Step.Skips("./modules/nats/a", "sha256:cccc") { t.Error("a kept result skips its package at its digest and level only") } - if _, err := RunInputOf("seat:mesh-delivery-checker.input", make([]byte, RunInputBound+1)); err == nil || !strings.Contains(err.Error(), "262145 bytes") { - t.Errorf("an input over its bound: %v", err) + // A-237: a run's input is any size. + if in, err := RunInputOf("seat:mesh-delivery-checker.input", make([]byte, 3<<20)); err != nil || in.Size != 3<<20 { + t.Errorf("an input of 3 MiB: %+v, %v", in, err) } in := runAsk().Run.Input if err := in.Verify([]byte(`{"v":1}`)); err != nil { diff --git a/modules/mesh-delivery-sdk/buildstep/run.go b/modules/mesh-delivery-sdk/buildstep/run.go index c98c6262..c69879b7 100644 --- a/modules/mesh-delivery-sdk/buildstep/run.go +++ b/modules/mesh-delivery-sdk/buildstep/run.go @@ -20,8 +20,8 @@ import ( // Input names, checks its digest and writes it to the file RunInputEnv names; then runs the program with Args, no // network and no bus account (ADR 0448 §2, §4), and answers RunAnswer on its step key. // -// The input is carried by reference, as a compose carries its manifests: the asker keeps it by its digest, measured -// against RunInputBound before it asks, and serves it on a verb of its seat (RunInput.Verb); the build module asks that +// The input is carried by reference, as a compose carries its manifests: the asker keeps it by its digest, of any +// size (A-237), and serves it on a verb of its seat (RunInput.Verb); the build module asks that // verb as its manifest's `invokes` grants it, never reading the asker's state (ADR 0596 §2), so the ask on the stream // never carries it (issue 1930). The layouts are the part build-step's `RunJob`, `RunProgram`, `RunHead`, `RunInput` // and `RunAnswer`. @@ -77,22 +77,14 @@ type RunAnswer struct { Said string `json:"said,omitempty"` } -// RunInputBound is the most bytes a run's input may hold: the bus's value limit the design already uses (256 KiB), so -// the asker keeps it as one value (the lead's ruling on milestone 47/B0, 2026-11-06). -const RunInputBound = 256 << 10 - // RunHeadsEnv names, for the program, the directory its heads are laid in; RunInputEnv the file its input is written to. const ( RunHeadsEnv = "MESH_RUN_HEADS" RunInputEnv = "MESH_RUN_INPUT" ) -// RunInputOf is the reference to body served on verb (`seat:.`), refusing a body over RunInputBound, naming -// its size, so an asker refuses it before asking. +// RunInputOf is the reference to body served on verb (`seat:.`); a body is of any size. func RunInputOf(verb string, body []byte) (RunInput, error) { - if len(body) > RunInputBound { - return RunInput{}, fmt.Errorf("buildstep: a run's input of %d bytes is over its bound of %d bytes", len(body), RunInputBound) - } sum := sha256.Sum256(body) in := RunInput{Verb: verb, Digest: hex.EncodeToString(sum[:]), Size: len(body)} return in, in.check("") @@ -118,8 +110,8 @@ func (i RunInput) check(id string) error { return fmt.Errorf("buildstep: the run ask %s serves its input on %q, which is no seat:. of no one machine", id, i.Verb) case !runDigestOK.MatchString(i.Digest): return fmt.Errorf("buildstep: the run ask %s names its input by %q, which is no sha256 digest", id, i.Digest) - case i.Size <= 0 || i.Size > RunInputBound: - return fmt.Errorf("buildstep: the run ask %s names an input of %d bytes, outside 1 to %d", id, i.Size, RunInputBound) + case i.Size <= 0: + return fmt.Errorf("buildstep: the run ask %s names an input of %d bytes, under 1", id, i.Size) } return nil } From ccbefc5653dfd039b798e1f09a053fa4cdc7f91b Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 09:14:28 +0100 Subject: [PATCH 2/3] B5g3-2: the mesh-catalog seat's input verb answers in chunks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §712, §354, §681, A-237, A-139 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../go/seats/meshcatalog/contract.go | 6 ++- .../go/seats/meshcatalog/contract_test.go | 4 +- .../seats/mesh-catalog/fixtures/verbs.json | 7 ++-- .../seats/mesh-catalog/seat.json | 38 +++++++++++-------- .../ts/seats/meshcatalog/index.d.ts | 6 ++- 5 files changed, 37 insertions(+), 24 deletions(-) diff --git a/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract.go b/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract.go index 504752fc..74876e63 100644 --- a/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract.go +++ b/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract.go @@ -1379,16 +1379,18 @@ type OutdatedAnswer struct { // InputInput is what input takes. type InputInput struct { + // 128 random bits the caller chooses: the subject token of this send's chunks and acknowledgements + Call string `json:"call"` // the input's sha256 Digest string `json:"digest"` } // InputAnswer is what input answers. type InputAnswer struct { + // how many chunks are sent; 0 when there is no input of that digest + Chunks int64 `json:"chunks"` // the input's sha256 Digest string `json:"digest"` - // the input's exact bytes, base64 - Input string `json:"input"` } // EntryInput is what entry takes. diff --git a/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract_test.go b/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract_test.go index b3ad08e4..d9e87f7c 100644 --- a/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract_test.go +++ b/modules/mesh-delivery-contracts/go/seats/meshcatalog/contract_test.go @@ -171,9 +171,9 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v OutdatedInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "outdated answers an entry behind", subject: "", given: "{\"behind\":[{\"act\":\"rebuild\",\"dependency\":{\"artifact\":\"mesh-sdk:go\",\"branch\":\"main\",\"source\":3589},\"kind\":1,\"module\":\"nats\",\"newest\":\"0.1.15 sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"recorded\":\"0.1.14 sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"version\":\"1.2.3\"}]}", wire: "{\"behind\":[{\"act\":\"rebuild\",\"dependency\":{\"artifact\":\"mesh-sdk:go\",\"branch\":\"main\",\"source\":3589},\"kind\":1,\"module\":\"nats\",\"newest\":\"0.1.15 sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"recorded\":\"0.1.14 sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"version\":\"1.2.3\"}]}", decode: func(b []byte) (any, error) { var v OutdatedAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "input asks a step's input by digest", subject: "", given: "{\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", wire: "{\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", + {name: "input asks a step's input by digest", subject: "", given: "{\"call\":\"0123456789abcdef0123456789abcdef\",\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", wire: "{\"call\":\"0123456789abcdef0123456789abcdef\",\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", decode: func(b []byte) (any, error) { var v InputInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "input answers the exact bytes", subject: "", given: "{\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"input\":\"eyJ2IjoxfQ==\"}", wire: "{\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"input\":\"eyJ2IjoxfQ==\"}", + {name: "input answers how many chunks carry the exact bytes", subject: "", given: "{\"chunks\":3,\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", wire: "{\"chunks\":3,\"digest\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", decode: func(b []byte) (any, error) { var v InputAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "entry asks by fingerprint", subject: "", given: "{\"fingerprint\":\"src2:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", wire: "{\"fingerprint\":\"src2:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}", decode: func(b []byte) (any, error) { var v EntryInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, diff --git a/modules/mesh-delivery-contracts/seats/mesh-catalog/fixtures/verbs.json b/modules/mesh-delivery-contracts/seats/mesh-catalog/fixtures/verbs.json index 9b84c76a..e5f0651a 100644 --- a/modules/mesh-delivery-contracts/seats/mesh-catalog/fixtures/verbs.json +++ b/modules/mesh-delivery-contracts/seats/mesh-catalog/fixtures/verbs.json @@ -424,15 +424,16 @@ "name": "input asks a step's input by digest", "of": "verb:input.input", "given": { + "call": "0123456789abcdef0123456789abcdef", "digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } }, { - "name": "input answers the exact bytes", + "name": "input answers how many chunks carry the exact bytes", "of": "verb:input.answer", "given": { - "digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "input": "eyJ2IjoxfQ==" + "chunks": 3, + "digest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } }, { diff --git a/modules/mesh-delivery-contracts/seats/mesh-catalog/seat.json b/modules/mesh-delivery-contracts/seats/mesh-catalog/seat.json index 0233171e..8c9841db 100644 --- a/modules/mesh-delivery-contracts/seats/mesh-catalog/seat.json +++ b/modules/mesh-delivery-contracts/seats/mesh-catalog/seat.json @@ -171,7 +171,7 @@ }, { "name": "seat-definitions", - "description": "The seat list (ADR 0636 \u00a74): with no seat, every trunk seat with its digest and the revision of the list; with a seat, that trunk definition resolved with the parts it includes. Reads only.", + "description": "The seat list (ADR 0636 §4): with no seat, every trunk seat with its digest and the revision of the list; with a seat, that trunk definition resolved with the parts it includes. Reads only.", "input": { "properties": { "seat": { @@ -250,7 +250,7 @@ }, { "name": "revision", - "description": "The catalog half of the revision view (ADR 0656 \u00a76): one catalog graph revision with its entries whole, the trunk heads, the seat definitions by digest, and each manifest's verdict and fingerprint; the trunk's newest when no revision and no candidate is named, or a candidate's for its group and heads. Answered by the holder from what it holds, in its own runtime (ADR 0663), in pages of entries. Reads only. A watched branch with no revision yet answers {none-yet: true} alone (design 47 G section 530).", + "description": "The catalog half of the revision view (ADR 0656 §6): one catalog graph revision with its entries whole, the trunk heads, the seat definitions by digest, and each manifest's verdict and fingerprint; the trunk's newest when no revision and no candidate is named, or a candidate's for its group and heads. Answered by the holder from what it holds, in its own runtime (ADR 0663), in pages of entries. Reads only. A watched branch with no revision yet answers {none-yet: true} alone (design 47 G section 530).", "input": { "type": "object", "properties": { @@ -554,17 +554,23 @@ "seat:node-build-node", "seat:node-build-image" ], - "description": "A step's input by its digest, as the indexer keeps it for a validation step it asked a build seat to run (design 47 G section 354).", + "description": "A step's input by its digest, as the indexer keeps it for a validation step it asked a build seat to run (design 47 G section 354): its fields, then the bytes in chunks of at most 512 KiB on the chunk subject of the call, each acknowledged by the caller on the ack subject within 30 seconds; the input is of any size.", "input": { "type": "object", "properties": { "digest": { "type": "string", "description": "the input's sha256" + }, + "call": { + "type": "string", + "description": "128 random bits the caller chooses: the subject token of this send's chunks and acknowledgements", + "minLength": 1 } }, "required": [ - "digest" + "digest", + "call" ] }, "answer": { @@ -574,16 +580,18 @@ "type": "string", "description": "the input's sha256" }, - "input": { - "type": "string", - "description": "the input's exact bytes, base64" + "chunks": { + "type": "integer", + "minimum": 0, + "description": "how many chunks are sent; 0 when there is no input of that digest" } }, "required": [ "digest", - "input" + "chunks" ] - } + }, + "chunks": true }, { "name": "entry", @@ -614,7 +622,7 @@ } } ], - "description": "The catalog of the mesh: every module the trunk holds with its manifest, fingerprint and verdict, the packages published, and the seat list (ADR 0636 \u00a71, \u00a74).", + "description": "The catalog of the mesh: every module the trunk holds with its manifest, fingerprint and verdict, the packages published, and the seat list (ADR 0636 §1, §4).", "family": { "name": "mesh-catalog.events", "max-age-days": 7 @@ -1793,7 +1801,7 @@ "name": "inputs", "template": "state", "history": 1, - "description": "a step's input, named by its write time and its sha256 (design 47 G section 354)", + "description": "a step's input, named by its write time and its sha256; a value over one chunk is held in the state's objects store (design 47 G sections 354, 712)", "key-forms": [ { "key": "