appview: process daily stats, and drop the dead README branch fallback

io.atcr.hold.stats.daily was handled in the backfill collection list and in
processor.go's dispatch, but missing from isRelevantCollection, which gates
events before ProcessRecord ever sees them. So daily stats records arrived over
the socket and were discarded at the worker, and the trend charts that read them
got nothing live. This is the "present in one list, missing from the other"
shape CLAUDE.md's firehose checklist warns about. Checked the whole class: this
was the only gap. LayerCollection and ImageConfigCollection are absent
deliberately, having no processor handler, and the test now pins that intent.

Note this is currently masked by the relay outage, so fixing the relay alone
would not have restored the charts.

Separately, the README resolution tried "main" and fell back to "master", but
DeriveReadmeURL never fetches: it parses the source URL and interpolates the
branch, returning empty only for an unsupported platform, which is
branch-independent. So if the main call returned empty the master call returned
empty for the same reason, and the fallback could never fire. Removed, with a
comment recording that a branch fallback has to happen at fetch time after a
404. The other two call sites already do exactly that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PDqoCE1j3njokkZ9b1C5n9
This commit is contained in:
Evan Jarrett
2026-09-02 22:30:37 -05:00
co-authored by Claude Opus 5
parent a95c89aaef
commit dbb195a4ab
3 changed files with 50 additions and 3 deletions
+5 -3
View File
@@ -291,10 +291,12 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
// is a guess on our part.
explicit := readmeURL != ""
if readmeURL == "" && repo.SourceURL != "" {
// Only "main" is derived here. DeriveReadmeURL does no fetching: it
// interpolates the branch into a per-platform URL template and returns
// "" only for an unsupported platform, which is branch-independent. A
// main/master fallback therefore has to happen at fetch time, after a
// 404 (as the push and backfill paths do), not at derivation time.
readmeURL = readme.DeriveReadmeURL(repo.SourceURL, "main")
if readmeURL == "" {
readmeURL = readme.DeriveReadmeURL(repo.SourceURL, "master")
}
}
if readmeURL != "" {
html, raw, failed := resolveReadme(r.Context(), h.ReadmeFetcher, readmeURL, explicit)
+1
View File
@@ -663,6 +663,7 @@ func isRelevantCollection(collection string) bool {
atproto.RepoPageCollection,
atproto.SailorProfileCollection,
atproto.StatsCollection,
atproto.DailyStatsCollection,
atproto.CaptainCollection,
atproto.CrewCollection,
atproto.ScanCollection,
@@ -0,0 +1,44 @@
package jetstream
import (
"testing"
"atcr.io/pkg/atproto"
)
// TestIsRelevantCollection guards the gate that runs before ProcessRecord ever
// sees an event: a collection handled in processor.go or listed in backfill.go
// but missing here is silently dropped from the live stream.
func TestIsRelevantCollection(t *testing.T) {
tests := []struct {
name string
collection string
want bool
}{
{"manifest", atproto.ManifestCollection, true},
{"tag", atproto.TagCollection, true},
{"star", atproto.StarCollection, true},
{"repo page", atproto.RepoPageCollection, true},
{"sailor profile", atproto.SailorProfileCollection, true},
{"cumulative stats", atproto.StatsCollection, true},
{"daily stats", atproto.DailyStatsCollection, true},
{"captain", atproto.CaptainCollection, true},
{"crew", atproto.CrewCollection, true},
{"scan", atproto.ScanCollection, true},
{"bluesky profile", BlueskyProfileCollection, true},
// Collections we deliberately ignore: no processor handler exists.
{"layer", atproto.LayerCollection, false},
{"image config", atproto.ImageConfigCollection, false},
{"bluesky post", atproto.BskyPostCollection, false},
{"unknown", "com.example.unrelated", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isRelevantCollection(tt.collection); got != tt.want {
t.Errorf("isRelevantCollection(%q) = %v, want %v", tt.collection, got, tt.want)
}
})
}
}