The input a node answers a check with says what holds each held module and carries its build (issue 1921, milestone 47/G H3)

A module the running indexer refused stood in the input as its stub with no build, so a check's composer that
judges the change's manifest itself had nothing to compose it at. Each held module now says whether its verdict
or the decoder holds it; for a check's answer the node also takes in the build it would compose it at, when the
builder holds it done, beside the stub and never into what the node composes.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-07 02:33:21 +01:00
parent e96a7ada50
commit c114b786e8
3 changed files with 79 additions and 7 deletions
@@ -218,6 +218,26 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
t.Errorf("spiegel in InOwn wanted at %s: %v, held %v, read as %s; want its build's image", sp.Fp2, err,
read.ComposedHow, raw)
}
// Issue 1921: held by its manifest's verdict, spiegel is still held in what the node composes, and the input a check
// is answered with says what holds it and carries the build the node would compose it at, taken in while done; a
// node's own read takes in no build of it.
sp.Verdict, sp.Problems = meshcatalogclient.VerdictRefused, []string{"p"}
bb.modules["spiegel"] = sp
for _, done := range []bool{true, false} {
if !done {
bb.builds["spiegel "+sp.Fp2].State = builds.BuildBuilding
}
read, err = ComposingInput(ctx, bb, Mesh{}, Scope{Node: "n1"}, map[string]string{"lemurs": fp("e"), "spiegel": sp.Fp2})
hold := read.Holds["spiegel"]
raw, _ = json.Marshal(hold.Built)
if err != nil || !read.Catalogue["spiegel"].Stubbed || hold.By != compose.HeldByVerdict || hold.Build != sp.Fp2 ||
strings.Contains(string(raw), strings.Repeat("7", 64)) != done || (hold.Built == nil) == done {
t.Errorf("spiegel refused, its build done %v: %v, hold %+v (built %s)", done, err, hold, raw)
}
}
if own, err := Input(ctx, bb, Mesh{}, Scope{Node: "n1"}); err != nil || own.Holds["spiegel"] != (compose.Hold{By: compose.HeldByVerdict}) {
t.Errorf("a node's own read of spiegel refused: %v, hold %+v", err, own.Holds["spiegel"])
}
}
// (C) Issue 1126, C5: what the others read of a module carried as it runs is what runs, never its stub's: the filter
+34 -7
View File
@@ -167,6 +167,10 @@ type Scope struct {
// Kept is told, in words, each module composed as what runs shows because its manifest does not yet say it (the tool
// runner, the core's credentials; ADR 0601 §4), so the node says it. Nil: said nowhere.
Kept func(module, words string)
// takeInHeld, set for the input a node answers a check with (ComposingInput), takes in the build of each module placed
// on the node that its manifest's verdict or its decoder holds, into compose.Input.Holds, never into what the node composes: the
// check's composer, judging the change's manifest itself, composes it at that build (issue 1921).
takeInHeld bool
}
// InOwn is, of the modules p places on its node, those whose resources the node's own composition it runs
@@ -214,21 +218,23 @@ func Input(ctx context.Context, r Reader, mesh Mesh, s Scope) (compose.Input, er
// taken is a module's manifest as a reader takes it (novox/hq ADR 0578 §3): decoded leniently, with the indexer's verdict
// from its modules value. A refused one, or bytes that do not decode, stands as its stub (unusable) and why says so; a
// manifest not read past a field it set aside holds its name alone and is held the same.
func taken(name string, raw []byte, m meshcatalog.ModulesByModuleValue) (man catalogue.Manifest, why string) {
//
// by is what holds it (compose.HeldByVerdict or compose.HeldByDecoder), empty when nothing does.
func taken(name string, raw []byte, m meshcatalog.ModulesByModuleValue) (man catalogue.Manifest, why, by string) {
if v := meshcatalogclient.ModuleVerdict(name, m); v != nil {
if refused, _ := meshcatalogclient.TakeVerdict(v); refused {
return unusable(name, raw), meshcatalogclient.VerdictWhy(*v)
return unusable(name, raw), meshcatalogclient.VerdictWhy(*v), compose.HeldByVerdict
}
}
man, err := catalogue.Decode(raw)
switch {
case err != nil:
return unusable(name, raw), fmt.Sprintf("the manifest of %s does not decode: %v", name, err)
return unusable(name, raw), fmt.Sprintf("the manifest of %s does not decode: %v", name, err), compose.HeldByDecoder
case man.ReadNoFurther():
return unusable(name, raw), fmt.Sprintf("the manifest of %s could not be read past a field this reader does not "+
"know: %s", name, man.SetAside())
"know: %s", name, man.SetAside()), compose.HeldByDecoder
}
return man, ""
return man, "", ""
}
// input is Input; with refuse set, a module placed on the composing node whose build cannot be read or taken in, or whose
@@ -374,9 +380,13 @@ func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) {
// follows (issue 774: power's bar item refused every node's composition, 2026-10-30; ADR 0578 §3: one refused
// manifest silenced disk-load for 45 hours). It stands in as its stub, so what it provides and requires still
// shapes the rest; it is held on the composing node where it is placed there, and elsewhere only its offer is read.
man, why := taken(name, raw, m)
man, why, by := taken(name, raw, m)
if why != "" {
named(&in.Refused, name, why)
if in.Holds == nil {
in.Holds = map[string]compose.Hold{}
}
in.Holds[name] = compose.Hold{By: by}
if ownPlaced[name] {
b.ownRefused = append(b.ownRefused, [2]string{name, why})
}
@@ -600,6 +610,7 @@ func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]s
// credential: the sandbox composes no pair value.
func ComposingInput(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]string) (compose.Input, error) {
held := map[string]string{}
s.takeInHeld = true
in, err := input(ctx, r, mesh, s, func(module string, err error) { held[module] = err.Error() }, fps)
if err != nil {
return compose.Input{}, err
@@ -909,7 +920,23 @@ func fromBuilds(ctx context.Context, r Reader, build func(ctx context.Context, m
continue
}
if _, refused := in.Refused[pm.Module]; refused {
continue // held as its stub: no build of a refused manifest is taken in (ADR 0578 §3)
// Held as its stub: no build of a refused manifest is composed (ADR 0578 §3). For a check, the build the node
// would compose it at is said beside it, and taken in when done, for the check's composer alone.
if hold, said := in.Holds[pm.Module]; s.takeInHeld && said {
hold.Build = m.Fp2
if w := wanted[pm.Module]; w != "" {
hold.Build = w
}
// Its stub names no build, so the builder is asked: a build not done, or not taken in, is awaited.
if b, err := build(ctx, pm.Module, hold.Build); err == nil && b != nil && b.State == builds.BuildDone {
if resolved, err := built(pm.Module, hold.Build, b); err == nil {
hold.Built = &resolved
}
}
in.Holds = maps.Clone(in.Holds)
in.Holds[pm.Module] = hold
}
continue
}
if man, ok := in.Catalogue[pm.Module]; !ok || (man.Build == nil && !man.Templated()) {
continue // nothing is built of it: its manifest is what it composes from
+25
View File
@@ -114,11 +114,36 @@ type Input struct {
// (compose.Stub), so it is held: nothing of its own is composed, while what it provides and requires still shapes the
// rest. A module not named is accepted, a missing verdict included.
Refused map[string]string `json:",omitempty"`
// Holds is, by module Refused names, what holds it and the build its node would compose it at (issue 1921): a check's
// composer that judges the change's manifest itself (novox/hq ADR 0578 §2) composes a module refused by the running
// indexer alone from that build, and names one whose build it is not given. Nil from a node-engine before it.
Holds map[string]Hold `json:",omitempty"`
// SetAside is each module whose manifest has a field the reader's decoder does not know, by module, with what was set
// aside (catalogue.Manifest.SetAside): the rest of it is composed, and the reader names it (ADR 0578 §6).
SetAside map[string]string `json:",omitempty"`
}
// What holds a module Refused names (Hold.By).
const (
// HeldByVerdict is a module the indexer's judge refused the manifest of (its verdict).
HeldByVerdict = "verdict"
// HeldByDecoder is a module whose bytes this reader's decoder does not decode, or cannot read past a field it does not
// know: no judge of the manifest overrules it.
HeldByDecoder = "decoder"
)
// Hold is what holds one module Refused names, and the build its node would compose it at (Input.Holds).
type Hold struct {
// By is HeldByVerdict or HeldByDecoder.
By string
// Build is the fingerprint the node would compose it at, its want's or else the indexer's newest; empty where it is not
// placed on the composing node or names no build.
Build string `json:",omitempty"`
// Built is its manifest as the node takes it in from that build, when the builder holds it done; nil while that build
// is awaited (or not asked: only a node's answer to a check asks it).
Built *catalogue.Manifest `json:",omitempty"`
}
// Issued is one pair credential made for a consumer of a provider (inv.Secret): ForProvider is its copy sealed to the
// provider, optional.
type Issued struct {