R3-3: the node's own composer has no path without an entry call; its tests compose from the catalog half

Builds: design 47 G §628, §421, A-312, A-317

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 23:33:31 +01:00
parent 321a0744d8
commit 650910dc9e
6 changed files with 117 additions and 171 deletions
+11 -38
View File
@@ -89,47 +89,20 @@ func (c *Composer) publishComposed(ctx context.Context) {
// is left out. A module whose newest build the builder holds failed is also returned in failed, by module, with the
// indexer's fingerprint and the build's error: it is published as a refusal, not composed.
func (c *Composer) newestFPs(ctx context.Context, at *published) (out map[string]string, failed map[string][2]string) {
if c.byEntries() {
// The newest build is the indexer's (and the builder's done key says it is built): neither is read, and the node
// follows no branch (design 47 G §421, §628), so the state holds the wanted builds alone. A placed module's newest
// build that ended is the last trunk end the builder said of it (NoteBuildEnded): a failed one is refused (A-293).
failed = map[string][2]string{}
c.mu.Lock()
if c.endsRead {
for _, module := range at.placed {
if e, ok := c.ends[module]; ok && e.Outcome == builds.BuildFailed {
failed[module] = [2]string{e.Fingerprint, e.Error}
}
}
}
c.mu.Unlock()
return maps.Clone(at.fps), failed
}
out = map[string]string{}
// The newest build is the indexer's (and the builder's done key says it is built): neither is read, and the node
// follows no branch (design 47 G §421, §628), so the state holds the wanted builds alone. A placed module's newest
// build that ended is the last trunk end the builder said of it (NoteBuildEnded): a failed one is refused (A-293).
failed = map[string][2]string{}
r := reader{c: c}
for _, module := range at.placed {
c.mu.Lock()
fp2 := c.modules[module].Fp2
c.mu.Unlock()
if fp2 != "" {
if b, err := r.Build(ctx, module, fp2); err == nil && b != nil {
switch b.State {
case builds.BuildDone:
out[module] = fp2
continue
case builds.BuildFailed:
failed[module] = [2]string{fp2, b.Error}
}
c.mu.Lock()
if c.endsRead {
for _, module := range at.placed {
if e, ok := c.ends[module]; ok && e.Outcome == builds.BuildFailed {
failed[module] = [2]string{e.Fingerprint, e.Error}
}
}
if fp, ok := at.fps[module]; ok {
out[module] = fp
} else if fp, ok := at.ran[module]; ok {
out[module] = fp
}
}
return out, failed
c.mu.Unlock()
return maps.Clone(at.fps), failed
}
// writeComposed works out the state's keys from at and writes those that differ, then deletes what is past keeping.
@@ -291,7 +264,7 @@ func (c *Composer) composedValues(ctx context.Context, at *published) (map[strin
func (c *Composer) endedRefusals(values map[string][]byte) []string {
c.mu.Lock()
defer c.mu.Unlock()
if !c.byEntries() || !c.endsRead {
if !c.endsRead {
return nil
}
var out []string