···11+---
22+title: To-Dos
33+updated: 2026-03-23
44+---
55+66+A catch-all for ideas, issues/bugs, and future work that doesn't fit into the current specs or tasks. This is a "parking lot."
77+88+## App
99+1010+- Repo stars, forks, etc. are not properly parsed from JSON.
1111+- ATOM/RSS feed link for repos: (`tangled.org/{did}/{repo}/feed.atom`)
1212+1313+## API
+8-8
docs/api/tasks/phase-1-mvp.md
···51515252### Tasks
53535454-- [ ] Define Tap event DTOs matching the documented event shape:
5454+- [x] Define Tap event DTOs matching the documented event shape:
55555656 ```go
5757 type TapEvent struct {
···7878 }
7979 ```
80808181-- [ ] Implement WebSocket client:
8181+- [x] Implement WebSocket client:
8282 - Connect to `TAP_URL` (e.g., `wss://tap.railway.internal/channel`)
8383 - HTTP Basic auth with `admin:TAP_AUTH_PASSWORD`
8484 - Auto-reconnect with exponential backoff
8585 - Ack protocol: send event `id` back after successful processing
8686-- [ ] Implement ingestion loop:
8686+- [x] Implement ingestion loop:
8787 1. Receive event from WebSocket
8888 2. If `type == "identity"` → update handle cache, ack, continue
8989 3. If `type == "record"` → check collection allowlist
···9191 5. Decode `record.record` via adapter registry
9292 6. Normalize to `Document`
9393 7. Upsert to store
9494- 8. Schedule embedding job if eligible (Phase 2)
9494+ 8. Schedule embedding job if eligible ([Phase 2](phase-2-semantic.md))
9595 9. Persist cursor (event ID) after successful DB commit
9696 10. Ack the event
9797-- [ ] Implement collection allowlist from `INDEXED_COLLECTIONS` config
9898-- [ ] Handle state events (`sh.tangled.repo.issue.state`, `sh.tangled.repo.pull.status`) → update `record_state`
9999-- [ ] Handle normalization failures: log, skip, advance cursor
100100-- [ ] Handle DB failures: retry with backoff, do not advance cursor
9797+- [x] Implement collection allowlist from `INDEXED_COLLECTIONS` config
9898+- [x] Handle state events (`sh.tangled.repo.issue.state`, `sh.tangled.repo.pull.status`) → update `record_state`
9999+- [x] Handle normalization failures: log, skip, advance cursor
100100+- [x] Handle DB failures: retry with backoff, do not advance cursor
101101102102### Verification
103103
···11+CREATE TABLE IF NOT EXISTS identity_handles (
22+ did TEXT PRIMARY KEY,
33+ handle TEXT NOT NULL,
44+ is_active INTEGER NOT NULL DEFAULT 1,
55+ status TEXT,
66+ updated_at TEXT NOT NULL
77+);
88+99+CREATE INDEX IF NOT EXISTS idx_identity_handles_handle ON identity_handles(handle);
+48
packages/api/internal/store/sql_store.go
···130130 return nil
131131}
132132133133+func (s *SQLStore) UpsertIdentityHandle(ctx context.Context, did, handle string, isActive bool, status string) error {
134134+ now := time.Now().UTC().Format(time.RFC3339)
135135+ _, err := s.db.ExecContext(ctx, `
136136+ INSERT INTO identity_handles (did, handle, is_active, status, updated_at)
137137+ VALUES (?, ?, ?, ?, ?)
138138+ ON CONFLICT(did) DO UPDATE SET
139139+ handle = excluded.handle,
140140+ is_active = excluded.is_active,
141141+ status = excluded.status,
142142+ updated_at = excluded.updated_at`,
143143+ did, handle, isActive, status, now,
144144+ )
145145+ if err != nil {
146146+ return fmt.Errorf("upsert identity handle: %w", err)
147147+ }
148148+ return nil
149149+}
150150+151151+func (s *SQLStore) GetIdentityHandle(ctx context.Context, did string) (string, error) {
152152+ var handle sql.NullString
153153+ err := s.db.QueryRowContext(ctx, `SELECT handle FROM identity_handles WHERE did = ?`, did).Scan(&handle)
154154+ if errors.Is(err, sql.ErrNoRows) {
155155+ return "", nil
156156+ }
157157+ if err != nil {
158158+ return "", fmt.Errorf("get identity handle: %w", err)
159159+ }
160160+ return handle.String, nil
161161+}
162162+163163+func (s *SQLStore) EnqueueEmbeddingJob(ctx context.Context, documentID string) error {
164164+ now := time.Now().UTC().Format(time.RFC3339)
165165+ _, err := s.db.ExecContext(ctx, `
166166+ INSERT INTO embedding_jobs (document_id, status, attempts, last_error, scheduled_at, updated_at)
167167+ VALUES (?, 'pending', 0, NULL, ?, ?)
168168+ ON CONFLICT(document_id) DO UPDATE SET
169169+ status = 'pending',
170170+ last_error = NULL,
171171+ scheduled_at = excluded.scheduled_at,
172172+ updated_at = excluded.updated_at`,
173173+ documentID, now, now,
174174+ )
175175+ if err != nil {
176176+ return fmt.Errorf("enqueue embedding job: %w", err)
177177+ }
178178+ return nil
179179+}
180180+133181func scanDocument(row *sql.Row) (*Document, error) {
134182 doc := &Document{}
135183 var (