···1+# USING CODEGEN
2+3+i dont really understand how indigo lexgen works but this two works i guess
4+5+run this in the project root
6+7+### struct gen
8+you really only need to do this when lexicon changes
9+```bash
10+go run github.com/bluesky-social/indigo/cmd/lexgen/ \
11+--package labelmerge_lex --outdir ./labelmerge/lex \
12+--external-lexicons ./labelmerge/lex/generation/external/com.atproto.label.defs.json \
13+--build-file ./labelmerge/lex/generation/lexgen.json \
14+./labelmerge/lex/generation/defs
15+```
16+17+### server gen
18+this is really only needed to be done once per project and never again.
19+20+written here for future reference.
21+```bash
22+go run github.com/bluesky-social/indigo/cmd/lexgen/ \
23+--gen-server \
24+--gen-handlers \
25+--package main \
26+--outdir ./cmd/labelmerge/ \
27+--external-lexicons ./labelmerge/lex/generation/external/ \
28+--types-import app.reddwarf.labelmerge:tangled.org/whey.party/red-dwarf-server/labelmerge/lex \
29+--types-import com.atproto:github.com/bluesky-social/indigo/api/atproto \
30+--build-file ./labelmerge/lex/generation/lexgen.json \
31+./labelmerge/lex/defs/
32+```
33+34+### typescript client gen
35+36+uses a separate tool but its kinda related so im gonna put it here too
37+```bash
38+npx @atproto/lex-cli gen-api ./your/typescript/project/path ./labelmerge/lex/generation/defs/app.reddwarf.labelmerge.queryLabels.json ./labelmerge/lex/generation/external/com.atproto.label.defs.json
39+```
+45
labelmerge/lex/labelmergequeryLabels.go
···000000000000000000000000000000000000000000000
···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+// Lexicon schema: app.reddwarf.labelmerge.queryLabels
4+5+package labelmerge_lex
6+7+import (
8+ "context"
9+10+ comatproto "github.com/bluesky-social/indigo/api/atproto"
11+ lexutil "github.com/bluesky-social/indigo/lex/util"
12+)
13+14+// QueryLabels_Error is a "error" in the app.reddwarf.labelmerge.queryLabels schema.
15+type QueryLabels_Error struct {
16+ E *string `json:"e,omitempty" cborgen:"e,omitempty"`
17+ S string `json:"s" cborgen:"s"`
18+}
19+20+// QueryLabels_Output is the output of a app.reddwarf.labelmerge.queryLabels call.
21+type QueryLabels_Output struct {
22+ Error []*QueryLabels_Error `json:"error,omitempty" cborgen:"error,omitempty"`
23+ Labels []*comatproto.LabelDefs_Label `json:"labels" cborgen:"labels"`
24+}
25+26+// QueryLabels calls the XRPC method "app.reddwarf.labelmerge.queryLabels".
27+//
28+// l: List of label sources (labeler DIDs) to filter on.
29+// s: List of label subjects (strings).
30+// strict: If true then any errors will throw the entire query
31+func QueryLabels(ctx context.Context, c lexutil.LexClient, l []string, s []string, strict bool) (*QueryLabels_Output, error) {
32+ var out QueryLabels_Output
33+34+ params := map[string]interface{}{}
35+ params["l"] = l
36+ params["s"] = s
37+ if strict {
38+ params["strict"] = strict
39+ }
40+ if err := c.LexDo(ctx, lexutil.Query, "", "app.reddwarf.labelmerge.queryLabels", params, nil, &out); err != nil {
41+ return nil, err
42+ }
43+44+ return &out, nil
45+}
···1+package stream
2+3+import (
4+ "context"
5+ "fmt"
6+ "log/slog"
7+ "net/http"
8+ "net/url"
9+ "strconv"
10+ "strings"
11+ "sync"
12+ "time"
13+14+ comatproto "github.com/bluesky-social/indigo/api/atproto"
15+ "github.com/bluesky-social/indigo/atproto/identity"
16+ "github.com/bluesky-social/indigo/atproto/syntax"
17+ "github.com/bluesky-social/indigo/events"
18+ "github.com/bluesky-social/indigo/events/schedulers/parallel"
19+ "github.com/gorilla/websocket"
20+)
21+22+const LabelSubscribePath = "xrpc/com.atproto.label.subscribeLabels"
23+const MaxWorkers = 4
24+const EventChannelCapacity = 1000
25+26+// LabelEvent is the simplified, unified event object sent to the consumer.
27+type LabelEvent struct {
28+ SourceDid string // The DID of the labeler service that sent the event
29+ Cursor int64 // The sequence number (seq) of the event
30+ Value *comatproto.LabelDefs_Label
31+}
32+33+// SubscriptionContext holds the state for a single labeler connection.
34+type SubscriptionContext struct {
35+ DID string
36+ ServiceURL string // Resolved WSS URL
37+ CurrentCursor string // Sequence string used for connection restarts
38+ WorkerCancel context.CancelFunc
39+ WorkerCtx context.Context
40+ IsRunning bool
41+ mu sync.Mutex
42+}
43+44+// LabelerSubscriptionManager manages connections to multiple ATProto labeler services.
45+type LabelerSubscriptionManager struct {
46+ log *slog.Logger
47+ managerCtx context.Context
48+ managerCancel context.CancelFunc
49+ wg sync.WaitGroup
50+51+ resolver identity.Directory // DID Resolver dependency
52+53+ subscriptions map[string]*SubscriptionContext
54+ subMu sync.RWMutex
55+56+ eventCh chan LabelEvent
57+}
58+59+// NewLabelerSubscriptionManager creates a new instance of the manager.
60+func NewLabelerSubscriptionManager(log *slog.Logger) *LabelerSubscriptionManager {
61+ ctx, cancel := context.WithCancel(context.Background())
62+ dir := identity.DefaultDirectory() // Cached with 24hr TTL
63+ return &LabelerSubscriptionManager{
64+ log: log,
65+ managerCtx: ctx,
66+ managerCancel: cancel,
67+ subscriptions: make(map[string]*SubscriptionContext),
68+ eventCh: make(chan LabelEvent, EventChannelCapacity),
69+ resolver: dir,
70+ }
71+}
72+73+// Events returns the read-only channel where all aggregated LabelEvents are sent.
74+func (m *LabelerSubscriptionManager) Events() <-chan LabelEvent {
75+ return m.eventCh
76+}
77+78+// Start initiates the subscription manager. It attempts to resolve the URL and start workers
79+// for all currently added labelers.
80+func (m *LabelerSubscriptionManager) Start() {
81+ m.subMu.RLock()
82+ defer m.subMu.RUnlock()
83+84+ m.log.Info("Starting Labeler Subscription Manager", "count", len(m.subscriptions))
85+86+ for _, sub := range m.subscriptions {
87+ // Initial resolution check (the worker loop handles retries)
88+ did, err1 := syntax.ParseDID(sub.DID)
89+ if err1 != nil {
90+ m.log.Warn(sub.DID + "ResolutionFailure invalid did")
91+ } else {
92+ ident, err2 := m.resolver.LookupDID(context.TODO(), did)
93+ if err2 != nil {
94+ m.log.Warn(sub.DID + "ResolutionFailure not reachable")
95+ } else {
96+ labelerURL := ident.GetServiceEndpoint("atproto_labeler")
97+ if labelerURL == "" {
98+ m.log.Warn(sub.DID + "ResolutionFailure no service endpoint")
99+ } else {
100+ sub.ServiceURL = labelerURL
101+ m.log.Info("Initial DID resolved successfully", "did", sub.DID, "service_url", sub.ServiceURL)
102+ }
103+ }
104+ }
105+ m.startWorker(sub)
106+ }
107+}
108+109+// Stop gracefully shuts down all active connections and the manager.
110+func (m *LabelerSubscriptionManager) Stop() {
111+ m.managerCancel()
112+ m.log.Info("Waiting for all labeler workers to shut down...")
113+ m.wg.Wait()
114+ m.log.Info("Manager stopped gracefully.")
115+ close(m.eventCh)
116+}
117+118+// AddLabeler registers a new labeler DID. URL resolution is handled lazily (in Start or in the worker loop).
119+func (m *LabelerSubscriptionManager) AddLabeler(did string, cursor string) error {
120+ if did == "" {
121+ return fmt.Errorf("did cannot be empty")
122+ }
123+124+ m.subMu.Lock()
125+ defer m.subMu.Unlock()
126+127+ if _, exists := m.subscriptions[did]; exists {
128+ m.log.Warn("Labeler already exists", "did", did)
129+ return nil
130+ }
131+132+ workerCtx, workerCancel := context.WithCancel(m.managerCtx)
133+134+ sub := &SubscriptionContext{
135+ DID: did,
136+ ServiceURL: "", // Must be resolved before use
137+ CurrentCursor: cursor,
138+ WorkerCtx: workerCtx,
139+ WorkerCancel: workerCancel,
140+ IsRunning: false,
141+ }
142+143+ m.subscriptions[did] = sub
144+145+ // If the overall manager is running, try to resolve and start the worker immediately.
146+ select {
147+ case <-m.managerCtx.Done():
148+ // Manager is shutting down, just register the sub but don't start
149+ default:
150+ // Attempt immediate resolution and start
151+ // entry, err := m.resolver.Resolve(sub.DID)
152+ // if err == nil {
153+ // sub.ServiceURL = entry.Domain
154+ // m.log.Info("Immediate DID resolution successful for new labeler", "did", sub.DID, "service_url", sub.ServiceURL)
155+ // } else {
156+ // m.log.Warn("Immediate DID resolution failed for new labeler. Worker will retry.", "did", sub.DID, "err", err)
157+ // }
158+ // m.startWorker(sub)
159+ did, err1 := syntax.ParseDID(sub.DID)
160+ if err1 != nil {
161+ m.log.Warn(sub.DID + "ResolutionFailure invalid did")
162+ } else {
163+ ident, err2 := m.resolver.LookupDID(context.TODO(), did)
164+ if err2 != nil {
165+ m.log.Warn(sub.DID + "ResolutionFailure not reachable")
166+ } else {
167+ labelerURL := ident.GetServiceEndpoint("atproto_labeler")
168+ if labelerURL == "" {
169+ m.log.Warn(sub.DID + "ResolutionFailure no service endpoint")
170+ } else {
171+ sub.ServiceURL = labelerURL
172+ m.log.Info("Initial DID resolved successfully", "did", sub.DID, "service_url", sub.ServiceURL)
173+ }
174+ }
175+ }
176+ m.startWorker(sub)
177+ }
178+179+ m.log.Info("Labeler added", "did", did, "start_cursor", cursor)
180+ return nil
181+}
182+183+// RemoveLabeler stops the stream worker for the given DID and removes it from the manager.
184+func (m *LabelerSubscriptionManager) RemoveLabeler(did string) {
185+ m.subMu.Lock()
186+ defer m.subMu.Unlock()
187+188+ sub, exists := m.subscriptions[did]
189+ if !exists {
190+ return
191+ }
192+193+ m.log.Info("Removing labeler subscription", "did", did)
194+ sub.WorkerCancel()
195+ delete(m.subscriptions, did)
196+}
197+198+// startWorker spins up the connection management goroutine for a single labeler.
199+func (m *LabelerSubscriptionManager) startWorker(sub *SubscriptionContext) {
200+ sub.mu.Lock()
201+ if sub.IsRunning {
202+ sub.mu.Unlock()
203+ return
204+ }
205+ sub.IsRunning = true
206+ sub.mu.Unlock()
207+208+ m.wg.Add(1)
209+ go func() {
210+ defer m.wg.Done()
211+ defer func() {
212+ sub.mu.Lock()
213+ sub.IsRunning = false
214+ sub.mu.Unlock()
215+ }()
216+ m.manageSubscription(sub)
217+ }()
218+}
219+220+// manageSubscription handles connection retries, cursors, and running the stream processor.
221+func (m *LabelerSubscriptionManager) manageSubscription(sub *SubscriptionContext) {
222+ didLog := m.log.With("did", sub.DID)
223+ didLog.Info("Worker started")
224+225+ for {
226+ select {
227+ case <-sub.WorkerCtx.Done():
228+ didLog.Info("Worker received stop signal, shutting down.")
229+ return
230+ default:
231+ // Proceed
232+ }
233+ var err error
234+235+ // 1. Ensure ServiceURL is resolved
236+ if sub.ServiceURL == "" {
237+ didLog.Info("Service URL not resolved, attempting DID resolution.")
238+ // entry, err := m.resolver.Resolve(sub.DID)
239+ // if err != nil {
240+ // didLog.Error("DID resolution failed, retrying...", "err", err)
241+ // goto WaitAndRetry
242+ // }
243+ // sub.ServiceURL = entry.Domain
244+ // didLog.Info("DID resolution successful", "service_url", sub.ServiceURL)
245+ did, err1 := syntax.ParseDID(sub.DID)
246+ if err1 != nil {
247+ m.log.Warn(sub.DID + "ResolutionFailure invalid did")
248+ } else {
249+ ident, err2 := m.resolver.LookupDID(context.TODO(), did)
250+ if err2 != nil {
251+ m.log.Warn(sub.DID + "ResolutionFailure not reachable")
252+ goto WaitAndRetry
253+ } else {
254+ labelerURL := ident.GetServiceEndpoint("atproto_labeler")
255+ if labelerURL == "" {
256+ m.log.Warn(sub.DID + "ResolutionFailure no service endpoint")
257+ } else {
258+ sub.ServiceURL = labelerURL
259+ m.log.Info("Initial DID resolved successfully", "did", sub.DID, "service_url", sub.ServiceURL)
260+ }
261+ }
262+ }
263+ }
264+265+ // 2. Attempt to stream
266+ err = m.dialAndStream(sub)
267+268+ if sub.WorkerCtx.Err() != nil {
269+ return
270+ }
271+272+ if err != nil {
273+ didLog.Error("Stream failed, attempting restart", "err", err, "cursor", sub.CurrentCursor)
274+ } else {
275+ didLog.Info("Stream closed cleanly, attempting restart.")
276+ }
277+278+ WaitAndRetry:
279+ // Wait before retrying
280+ didLog.Info("Waiting 5s before reconnecting/retrying resolution...")
281+ select {
282+ case <-time.After(5 * time.Second):
283+ // Proceed with retry
284+ case <-sub.WorkerCtx.Done():
285+ return // Exit if canceled during wait
286+ }
287+ }
288+}
289+290+func httpToWS(s string) string {
291+ if strings.HasPrefix(s, "https://") {
292+ return "wss://" + s[len("https://"):]
293+ }
294+ if strings.HasPrefix(s, "http://") {
295+ return "ws://" + s[len("http://"):]
296+ }
297+ return s
298+}
299+300+// dialAndStream establishes the WebSocket connection and processes the stream.
301+func (m *LabelerSubscriptionManager) dialAndStream(sub *SubscriptionContext) error {
302+ didLog := m.log.With("did", sub.DID)
303+304+ fullURL := httpToWS(sub.ServiceURL) + "/" + LabelSubscribePath
305+ if sub.CurrentCursor != "" {
306+ fullURL = fmt.Sprintf("%s?cursor=%s", fullURL, sub.CurrentCursor)
307+ }
308+309+ u, err := url.Parse(fullURL)
310+ if err != nil {
311+ return fmt.Errorf("failed to parse URL: %w", err)
312+ }
313+314+ // 1. Establish WebSocket Connection
315+ dialer := websocket.DefaultDialer
316+ con, resp, err := dialer.Dial(u.String(), http.Header{
317+ "User-Agent": []string{"LabelerSubscriptionManager/1.0"},
318+ })
319+320+ if err != nil {
321+ if resp != nil {
322+ didLog.Error("WebSocket connection failed", "status", resp.StatusCode)
323+ }
324+ // If dial fails due to network/DNS/TLS error, clear the service URL to force re-resolution
325+ // on the next loop iteration.
326+ sub.mu.Lock()
327+ sub.ServiceURL = ""
328+ sub.mu.Unlock()
329+330+ return fmt.Errorf("failed to dial websocket to %s: %w", u.String(), err)
331+ }
332+ defer con.Close()
333+ didLog.Info("Successfully connected to Labeler firehose", "url", u.String())
334+335+ // 2. Define Event Callbacks
336+ rsc := &events.RepoStreamCallbacks{
337+ LabelLabels: func(evt *comatproto.LabelSubscribeLabels_Labels) error {
338+ if evt.Seq == 0 || evt.Labels == nil {
339+ return nil
340+ }
341+342+ // Update the cursor immediately
343+ sub.mu.Lock()
344+ sub.CurrentCursor = strconv.FormatInt(evt.Seq, 10)
345+ sub.mu.Unlock()
346+347+ // Process and simplify each label event
348+ for _, label := range evt.Labels {
349+ select {
350+ case m.eventCh <- LabelEvent{
351+ SourceDid: sub.DID,
352+ Cursor: evt.Seq,
353+ Value: label,
354+ }:
355+ // Sent successfully
356+ default:
357+ didLog.Warn("Event channel full, dropping label event", "seq", evt.Seq, "uri", label.Uri)
358+ }
359+ }
360+ return nil
361+ },
362+363+ LabelInfo: func(evt *comatproto.LabelSubscribeLabels_Info) error {
364+ if evt.Message != nil {
365+ didLog.Info("Stream Info Message", "name", evt.Name, "message", *evt.Message)
366+ }
367+ return nil
368+ },
369+370+ Error: func(evt *events.ErrorFrame) error {
371+ didLog.Error("Stream processing error frame", "error_type", evt.Error, "message", evt.Message)
372+ return fmt.Errorf("atproto stream error: %s", evt.Message)
373+ },
374+ }
375+376+ // 3. Create Scheduler and Start Processing
377+ scheduler := parallel.NewScheduler(
378+ MaxWorkers,
379+ EventChannelCapacity,
380+ fullURL,
381+ rsc.EventHandler,
382+ )
383+384+ // HandleRepoStream blocks until the connection closes or the context cancels.
385+ return events.HandleRepoStream(sub.WorkerCtx, con, scheduler, didLog)
386+}
+22
license
···0000000000000000000000
···1+MIT License
2+3+Copyright (c) 2025 Whey and contributors.
4+5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
22+
-81
main.go
···1-package main
2-3-import (
4- "context"
5- "fmt"
6- "log"
7- "net/http"
8- "os"
9- "time"
10-11- "tangled.org/whey.party/rdcs/microcosm/constellation"
12- "tangled.org/whey.party/rdcs/microcosm/slingshot"
13- "tangled.org/whey.party/rdcs/sticket"
14-15- // "github.com/bluesky-social/indigo/atproto/atclient"
16- // comatproto "github.com/bluesky-social/indigo/api/atproto"
17- // appbsky "github.com/bluesky-social/indigo/api/bsky"
18- // "github.com/bluesky-social/indigo/atproto/atclient"
19- // "github.com/bluesky-social/indigo/atproto/identity"
20- // "github.com/bluesky-social/indigo/atproto/syntax"
21- "github.com/bluesky-social/indigo/api/agnostic"
22- // "github.com/bluesky-social/jetstream/pkg/models"
23-)
24-25-const (
26- JETSTREAM_URL = "ws://localhost:6008/subscribe"
27- SPACEDUST_URL = "ws://localhost:9998/subscribe"
28- SLINGSHOT_URL = "http://localhost:7729"
29- CONSTELLATION_URL = "http://localhost:7728"
30-)
31-32-func main() {
33- fmt.Fprintf(os.Stdout, "RDCS started")
34-35- ctx := context.Background()
36- mailbox := sticket.New()
37- sl := slingshot.NewSlingshot(SLINGSHOT_URL)
38- cs := constellation.NewConstellation(CONSTELLATION_URL)
39- // spacedust is type definitions only
40- // jetstream types is probably available from jetstream/pkg/models
41-42- responsewow, _ := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.feed.profile", "did:plc:44ybard66vv44zksje25o7dz", "self")
43-44- fmt.Fprintf(os.Stdout, responsewow.Uri)
45-46- http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
47- mailbox.HandleWS(&w, r)
48- })
49-50- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
51- fmt.Fprintf(w, "hello worldio !")
52- clientUUID := sticket.GetUUIDFromRequest(r)
53- hasSticket := clientUUID != ""
54- if hasSticket {
55- go func(targetUUID string) {
56- // simulated heavy processing
57- time.Sleep(2 * time.Second)
58-59- lateData := map[string]any{
60- "postId": 101,
61- "newComments": []string{
62- "Wow great tutorial!",
63- "I am stuck on step 1.",
64- },
65- }
66-67- success := mailbox.SendToClient(targetUUID, "post_thread_update", lateData)
68- if success {
69- log.Println("Successfully sent late data via Sticket")
70- } else {
71- log.Println("Failed to send late data (client disconnected?)")
72- }
73- }(clientUUID)
74- }
75- })
76- http.ListenAndServe(":7152", nil)
77-}
78-79-func getPostThreadV2(w http.ResponseWriter, r *http.Request) {
80- fmt.Fprintf(w, "hello worldio !")
81-}
···1+# Red Dwarf Server
2+3+Golang Monorepo for all server-sided Red Dwarf stuff
4+5+> [!NOTE]
6+> uh im not very confident with the current directory structure, so files and folders might move around
7+8+## Runnables
9+run all of these using `go run .` inside the respective directories
10+11+### `/cmd/appview`
12+an appview, the api server that implements app.bsky.* XRPC methods.
13+14+development of the appview itself is on hold, but other parts of this red dwarf server repo are still being developed and will be used by the appview, probably, eventually
15+16+still very early in development
17+18+implemented routes:
19+- `app.bsky.actor.getProfiles`
20+- `app.bsky.actor.getProfile`
21+- `app.bsky.notification.listNotifications` (placeholder)
22+- `app.bsky.labeler.getServices`
23+- `app.bsky.feed.getFeedGenerators`
24+- `app.bsky.feed.getPosts` (post rendering is incomplete)
25+- `app.bsky.feed.getFeed` (post rendering is incomplete)
26+- `app.bsky.unspecced.getConfig` (placeholder)
27+- `app.bsky.unspecced.getPostThreadV2` (mostly working! doesnt use prefered sort, not performant yet)
28+29+30+### `/cmd/labelmerge`
31+queryLabel cache. uses a different XRPC method than the default queryLabels endpoint
32+33+- `/xrpc/app.reddwarf.labelmerge.queryLabels`
34+35+the full lexicon schema is [here](/labelmerge/lex/generation/defs/app.reddwarf.labelmerge.queryLabels.json)
36+37+### `/cmd/backstream`
38+experimental backfiller that kinda (but not really) conforms to the jetstream event shape. designed to be ingested by consumers expecting jetstream
39+40+### `/cmd/aturilist`
41+experimental listRecords replacement. is not backfilled. uses the official jetstream go client, which means it suffers from this [bug](https://github.com/bluesky-social/jetstream/pull/45)
42+43+## Packages
44+45+### `/auth`
46+taken from [go-bsky-feed-generator](https://github.com/jazware/go-bsky-feed-generator) but modified a bit.
47+48+handles all of the auth, modified to have a more lenient version to make `getFeed` work
49+50+### `/microcosm/*`
51+microcosm api clients, implements constellation slingshot and spacedust
52+53+slingshot's api client is compatible with `github.com/bluesky-social/indigo/*` stuff, like `agnostic.RepoGetRecord` and `util.LexClient`
54+55+### `/labelmerge/*`
56+labelmerge helpers, like:
57+- a badger LRU (of unknown reliability)
58+- labeler firehose ingester manager (still WIP, and unused)
59+- lexicon generation files and the generated structs
60+61+### `/shims/*`
62+most of Red Dwarf Server logic lives here. pulls data from upstream services like microcosm constellation and slingshot, transforms it, and spits out bsky api -like responses using the published app.bsky.* codegen from `github.com/bluesky-social/indigo/api/bsky`
63+64+65+### `/sticket`
66+unused leftover sorry
67+68+69+### `/store`
70+unused leftover sorry
71+72+## todo
73+74+- implement the many other parts of labelmerge
75+ - labeler firehose ingester, which will be used for:
76+ - keep caches up to date via firehose ingester
77+ - rolling cache of the latest few hours of labels
78+ - which will need a "was this record made in the latest few hours of labels" helper service
79+- clean up /cmd/appview/main.go , its a mess
80+- appview-side query caches
81+- notification service
82+- bookmarks service
83+- create aturilist service
84+- make backstream usable
85+- create jetrelay service