D6i: a merge taken in before the verdicts state binds has each manifest judged as new and said at the bind, in the indexer's whole-module test (issue 1966 note 154, ADR 0668 §4)

Without the unjudged state the bind's pass accepted the refused manifest with a
warning and the waiting one, and said manifest-accepted for both; the row settles
Q3: the indexer says what grants follows, so the grants part is unchanged (no D8).

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-10 03:48:21 +01:00
parent b48e9ca6ce
commit 7b3f213ae8
@@ -662,8 +662,9 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
js, ctx, _, modules := startIndexer(t, func(ctx context.Context, builds buildLog) {
builds.end(t, "novox/mesh-catalog", "avahi", "modules/avahi")
}, func(ctx context.Context, js jetstream.JetStream) {
// The verdicts state and the stream of the indexer's events, as the controller makes them from its manifest.
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.VerdictsBucket, History: index.VerdictsHistory}); err != nil {
// The indexer's state unjudged, as the controller makes it from its manifest; the verdicts state is made only once
// the merge is taken in (issue 1966 note 154: a take-in at the start, before the verdicts state's bind).
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.UnjudgedBucket, History: 1}); err != nil {
t.Fatal(err)
}
})
@@ -707,7 +708,20 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
t.Errorf("manifest = %+v err %v, want avahi and the file's bytes", got, err)
}
verdicts, err := js.KeyValue(ctx, index.VerdictsBucket)
// 1966 (ADR 0668 §4, note 154): the take-in wrote its four manifests with no verdicts state on the bus, so it keeps
// them in its state unjudged; when the verdicts state binds, each is judged as new (bad refused in full, never as a
// warning), every accepted one is said as manifest-accepted, and the state is left empty.
unjudged, err := js.KeyValue(ctx, index.UnjudgedBucket)
if err != nil {
t.Fatal(err)
}
kept, _ := unjudged.Keys(ctx)
for _, raw := range []string{string(manifest), judged["good"], judged["bad"], judged["later"]} {
if !slices.Contains(kept, meshcatalogclient.ManifestDigest([]byte(raw))) {
t.Errorf("the take-in's new manifest %s is not kept unjudged: the state holds %v", meshcatalogclient.ManifestDigest([]byte(raw)), kept)
}
}
verdicts, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.VerdictsBucket, History: index.VerdictsHistory})
if err != nil {
t.Fatal(err)
}
@@ -732,12 +746,20 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
if !strings.Contains(strings.Join(got["bad"].Problems, " "), `provides "amqp"`) || strings.Join(got["later"].Unknown, " ") != "a-field-from-later" {
t.Errorf("the refusal names no problem or the wait no field: %+v", got)
}
// The pass ends the state's keys after it wrote and said every verdict.
left, _ := unjudged.Keys(ctx)
for deadline := time.Now().Add(10 * time.Second); len(left) != 0 && time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
left, _ = unjudged.Keys(ctx)
}
if len(left) != 0 {
t.Errorf("the state unjudged still holds %v within 10 s, once every manifest has its verdict", left)
}
// manifest-refused, said once for each refusal (a wait is no refusal).
events, err := js.Stream(ctx, "EVENTS")
if err != nil {
t.Fatal(err)
}
said := map[string]bool{}
said, acceptedSaid := map[string]bool{}, map[string]bool{}
for seq := uint64(1); ; seq++ {
msg, err := events.GetMsg(ctx, seq)
if err != nil {
@@ -747,10 +769,17 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
if msg.Subject == "mesh.seat.mesh-catalog.event."+meshcatalog.EventManifestRefused && json.Unmarshal(msg.Data, &r) == nil {
said[r.Module] = true
}
var a meshcatalog.ManifestAcceptedEvent
if msg.Subject == "mesh.seat.mesh-catalog.event."+meshcatalog.EventManifestAccepted && json.Unmarshal(msg.Data, &a) == nil {
acceptedSaid[a.Module] = true
}
}
if !said["bad"] || said["later"] || said["good"] {
t.Errorf("manifest-refused said for %v, want bad only", said)
}
if !acceptedSaid["good"] || acceptedSaid["bad"] || acceptedSaid["later"] {
t.Errorf("manifest-accepted said for %v, want good and no refused or waiting one", acceptedSaid)
}
// How a composer holds a refused module by these verdicts is the node-engine's to test (ADR 0593 §7: the composer
// and its bus reader are its own).
}