Deliver a module stream's given-up message again only while its consumer is still granted it (ADR 0363 §7)
mesh/merge-gate head ede2772b3c90 merged into trunk c2ec0a601acb — pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus…
mesh/repo-check head ede2772b3c90 merged into trunk c2ec0a601acb — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
jochen
2026-10-14 21:55:55 +02:00
parent ee13e75bf3
commit ede2772b3c
3 changed files with 76 additions and 1 deletions
+25 -1
View File
@@ -259,7 +259,8 @@ func DeadLetterNamed(js nats.JetStreamContext, id uint64) (DeadLetter, error) {
// work queue has one worker per subject, so only the worker that gave it up takes it, and DeliverAgain
// removes the original from the queue first, so there are never two (novox/hq issue 334); only while the
// worker that gave it up is on the bus. Any other stream's
// message is refused, with why: an ask to a seat the controller does not ask would need a publish it is
// message is refused, with why — but a module's own stream's, which goes back as an event while its consumer is
// still granted it (novox/hq ADR 0363 §7): an ask to a seat the controller does not ask would need a publish it is
// not granted, in its asker's name (ADR 0264's consequences, ADR 0259 §3), and a stream that is neither
// would reach every consumer of its subject.
func AgainTo(js nats.JetStreamContext, d DeadLetter) (string, error) {
@@ -280,6 +281,29 @@ func AgainTo(js nats.JetStreamContext, d DeadLetter) (string, error) {
return "", fmt.Errorf("whether %s can receive dead letter %d cannot be read: %w", d.Who, d.ID, err)
}
return d.Subject, nil
case d.Stream != broker.EventsStream && broker.OnAModuleStream(d.Subject):
// **A module stream's event goes back to its consumer only while it is still granted** (novox/hq ADR
// 0363 §7): its consumer on that stream must still be there and still filter the subject, or delivering
// it again would hand a module what its grant no longer names. Then it is delivered as an event is, on
// EVENTS under the consumer's own again-subject, which the module's consumer there filters.
info, err := js.ConsumerInfo(d.Stream, d.Consumer)
if errors.Is(err, nats.ErrConsumerNotFound) {
return "", fmt.Errorf("dead letter %d was given up by %s, which no longer hears %s, so it would reach a "+
"module no longer granted it. Drop it", d.ID, d.Who, d.Stream)
}
if err != nil {
return "", fmt.Errorf("whether %s still hears %s cannot be read: %w", d.Who, d.Stream, err)
}
granted := false
for _, f := range append([]string{info.Config.FilterSubject}, info.Config.FilterSubjects...) {
if f != "" && broker.SubjectsOverlap(f, d.Subject) {
granted = true
}
}
if !granted {
return "", fmt.Errorf("dead letter %d is on %s, which %s is no longer granted. Drop it", d.ID, d.Subject, d.Who)
}
d.Stream = broker.EventsStream
case d.Stream != broker.EventsStream:
return "", fmt.Errorf("dead letter %d is from %s, and only an event or an ask the controller made is "+
"delivered again: publishing it again would reach every consumer of its subject, or need a grant the "+