From dbb195a4abd2c857b1ccac29872f0c4889ae152f Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Wed, 2 Sep 2026 22:30:37 -0500 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01PDqoCE1j3njokkZ9b1C5n9 --- pkg/appview/handlers/repository.go | 8 ++-- pkg/appview/jetstream/worker.go | 1 + .../jetstream/worker_collections_test.go | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 pkg/appview/jetstream/worker_collections_test.go diff --git a/pkg/appview/handlers/repository.go b/pkg/appview/handlers/repository.go index 69fd7bc..63c77a5 100644 --- a/pkg/appview/handlers/repository.go +++ b/pkg/appview/handlers/repository.go @@ -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) diff --git a/pkg/appview/jetstream/worker.go b/pkg/appview/jetstream/worker.go index 9f35aa4..3b91f66 100644 --- a/pkg/appview/jetstream/worker.go +++ b/pkg/appview/jetstream/worker.go @@ -663,6 +663,7 @@ func isRelevantCollection(collection string) bool { atproto.RepoPageCollection, atproto.SailorProfileCollection, atproto.StatsCollection, + atproto.DailyStatsCollection, atproto.CaptainCollection, atproto.CrewCollection, atproto.ScanCollection, diff --git a/pkg/appview/jetstream/worker_collections_test.go b/pkg/appview/jetstream/worker_collections_test.go new file mode 100644 index 0000000..674eca1 --- /dev/null +++ b/pkg/appview/jetstream/worker_collections_test.go @@ -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) + } + }) + } +}