fix(artwork): trace an external tier that never reaches an agent

A configured 'external' token vanished from the chain when no enabled agent provided
images for that entity type, and for synthetic artists, leaving the trace unable to
say whether the tier was even considered.
This commit is contained in:
Deluan 2026-08-14 13:25:14 -04:00
commit 18476ff811
3 changed files with 54 additions and 2 deletions

View file

@ -47,10 +47,17 @@ func fetchArtistImage(ctx context.Context, ag *agents.Agents, gate gateFunc, ar
// Synthetic artists would otherwise get an unrelated agent result assigned to them.
switch ar.ID {
case consts.UnknownArtistID, consts.VariousArtistsID:
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped, Detail: "synthetic artist"})
return nil, "", false
}
name := externalName(ar.Name)
for _, a := range ag.ArtistImageAgents() {
imageAgents := ag.ArtistImageAgents()
if len(imageAgents) == 0 {
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped,
Detail: "no enabled agent provides artist images"})
return nil, "", false
}
for _, a := range imageAgents {
reader, _, err := gate(a.Name, func() (io.ReadCloser, string, error) {
imgs, err := a.Retriever.GetArtistImages(ctx, ar.ID, name, ar.MbzArtistID)
if err != nil {
@ -76,7 +83,13 @@ func fetchArtistImage(ctx context.Context, ag *agents.Agents, gate gateFunc, ar
// fetchAlbumImage is the album counterpart of fetchArtistImage.
func fetchAlbumImage(ctx context.Context, ag *agents.Agents, gate gateFunc, al model.Album) (r io.ReadCloser, agentName string, extErr bool) {
name, artist := externalName(al.Name), externalName(al.AlbumArtist)
for _, a := range ag.AlbumImageAgents() {
imageAgents := ag.AlbumImageAgents()
if len(imageAgents) == 0 {
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped,
Detail: "no enabled agent provides album images"})
return nil, "", false
}
for _, a := range imageAgents {
reader, _, err := gate(a.Name, func() (io.ReadCloser, string, error) {
imgs, err := a.Retriever.GetAlbumImages(ctx, name, artist, al.MbzAlbumID)
if err != nil {

View file

@ -173,6 +173,30 @@ var _ = Describe("agent images", func() {
Expect(a.artistCalls).To(Equal(0), "synthetic artists never reach the agents")
})
It("records a skipped external candidate when no agent provides artist images", func() {
ag := imageAgents()
t := &chainTrace{}
r, _, extErr := fetchArtistImage(withTrace(ctx, t), ag, passthroughGate, model.Artist{ID: "ar1"})
Expect(r).To(BeNil())
Expect(extErr).To(BeFalse())
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: outcomeSkipped,
Detail: "no enabled agent provides artist images"}}),
"a configured external token must never be silently absent from the chain")
})
It("records a skipped external candidate for synthetic artists", func() {
a := &fakeImageAgent{name: "agentA", imgs: []agents.ExternalImage{img("/a", 100)}}
ag := imageAgents(a)
t := &chainTrace{}
_, _, _ = fetchArtistImage(withTrace(ctx, t), ag, passthroughGate,
model.Artist{ID: consts.VariousArtistsID, Name: "Various Artists"})
Expect(t.Steps()).To(HaveLen(1))
Expect(t.Steps()[0].Outcome).To(Equal(outcomeSkipped))
Expect(t.Steps()[0].Detail).To(ContainSubstring("synthetic"))
})
It("clears typographic characters from the query name unless preserving unicode", func() {
conf.Server.DevPreserveUnicodeInExternalCalls = false
a := &fakeImageAgent{name: "agentA"}
@ -231,6 +255,18 @@ var _ = Describe("agent images", func() {
Expect(a.albumCalls).To(Equal(1))
})
It("records a skipped external candidate when no agent provides album images", func() {
ag := imageAgents()
t := &chainTrace{}
r, _, extErr := fetchAlbumImage(withTrace(ctx, t), ag, passthroughGate, model.Album{Name: "Album"})
Expect(r).To(BeNil())
Expect(extErr).To(BeFalse())
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: outcomeSkipped,
Detail: "no enabled agent provides album images"}}),
"a configured external token must never be silently absent from the chain")
})
It("reports extErr when the only agent fails transiently", func() {
a := &fakeImageAgent{name: "agentA", err: context.DeadlineExceeded}
ag := imageAgents(a)

View file

@ -18,6 +18,9 @@ const (
outcomeNotReached = "not-reached"
)
// externalCandidate labels the external tier itself, for the cases that never reach an agent.
const externalCandidate = "external"
// traceStep is one candidate the priority chain considered.
type traceStep struct {
Candidate string