···6363just api-dev remote
6464```
65656666+Run the API smoke checks from the repo root:
6767+6868+```bash
6969+uv run --project packages/scripts/api twister-api-smoke
7070+```
7171+7272+To verify admin endpoints as well, ensure `ADMIN_AUTH_TOKEN` is present in the
7373+environment before running the smoke script.
7474+6675To run the indexer in local file mode as well:
67766877```bash
···91100VITE_TWISTER_API_BASE_URL=http://localhost:8080
92101```
93102103103+### Local API DB
104104+105105+The experimental local API database lives at `packages/api/twister-dev.db`.
106106+Treat it as disposable unless you explicitly back it up.
107107+108108+Operational rules:
109109+110110+1. Stop the API before copying or restoring the file.
111111+2. Copy `twister-dev.db` and any matching `-wal` or `-shm` sidecars together.
112112+3. Prefer restore-or-rebuild over manual repair if the DB looks suspect.
113113+4. Let the file grow during experiments, then compact or delete it afterward.
114114+115115+Useful local commands:
116116+117117+```bash
118118+cd packages/api
119119+du -h twister-dev.db*
120120+ls -lh twister-dev.db*
121121+```
122122+94123## Infrastructure Setup
9512496125### Turso
···139168- `HTTP_BIND_ADDR`
140169- `SEARCH_DEFAULT_LIMIT`
141170- `SEARCH_MAX_LIMIT`
171171+- `ENABLE_ADMIN_ENDPOINTS`
172172+- `ADMIN_AUTH_TOKEN`
173173+- `READ_THROUGH_MODE`
174174+- `READ_THROUGH_COLLECTIONS`
175175+- `READ_THROUGH_MAX_ATTEMPTS`
142176143177Set these indexer-specific variables:
144178···1571913. Verify API readiness and indexer health.
1581924. Run `twister backfill` with your seed file.
1591935. Treat the environment as search-ready only after historical backfill completes.
160160-161161-## Docs
162162-163163-- Index: [`docs/README.md`](docs/README.md)
+1-1
apps/twisted/README.md
···6677- Node.js 20+
88- pnpm
99-- The Twister API running locally (see `packages/api/README.md`)
99+- The Twister API running locally
10101111## Running locally
1212
+2-1
docs/adr/storage.md
···1271273. Prefer restore-or-rebuild over repair if the DB becomes suspect.
1281284. Allow the file to grow during active experiments, then compact or delete it afterward.
129129130130-The concrete local backup, restore, and disk-growth procedures live in [packages/api/README.md](/Users/owais/Projects/Twisted/packages/api/README.md).
130130+The concrete local backup, restore, and disk-growth procedures live in
131131+[docs/reference/api.md](/Users/owais/Projects/Twisted/docs/reference/api.md).
131132132133## Migration Path To Production Turso
133134
+57-2
docs/reference/api.md
···61616262When `ENABLE_ADMIN_ENDPOINTS=true` with a configured `ADMIN_AUTH_TOKEN`:
63636464+- **`GET /admin/status`** — Tap cursor, JetStream cursor, document count, and
6565+ read-through queue status
6666+- **`GET /admin/indexing/jobs`** — List queue rows, filtered by `status`,
6767+ `source`, or `document`
6868+- **`GET /admin/indexing/audit`** — List append-only audit rows, filtered by
6969+ `source`, `decision`, or `document`
7070+- **`POST /admin/indexing/enqueue`** — Queue a single record by explicit body
6471- **`POST /admin/reindex`** — Trigger FTS re-sync
65727373+### Smoke Checks
7474+7575+Smoke checks for the API surface live in `packages/scripts/api/`.
7676+7777+From the repo root:
7878+7979+```sh
8080+uv run --project packages/scripts/api twister-api-smoke
8181+```
8282+8383+If `ADMIN_AUTH_TOKEN` is present in the environment, the smoke script can also
8484+verify `GET /admin/status`.
8585+6686### Static Content
67876888The API also serves a search site with live search and API documentation at `/` and `/docs*`, built with Alpine.js (no build step, embedded in `internal/view/`).
···8110182102**record_state** — Issue and PR state cache (open/closed/merged). Keyed by subject AT-URI.
83103104104+**indexing_jobs** — Durable read-through/admin queue with status, lease owner,
105105+lease expiry, retry counters, and terminal states (`failed`, `dead_letter`).
106106+107107+**indexing_audit** — Append-only record of enqueue decisions, retries, skips,
108108+completions, and dead letters.
109109+84110**document_embeddings** — Vector storage (768-dim F32_BLOB with DiskANN cosine index). Schema ready but not yet populated.
8511186112**embedding_jobs** — Async embedding job queue. Schema ready but worker not yet active.
···941203. Normalize into a document (extract title, body, summary, metadata)
951214. Optionally enrich via XRPC (resolve author handle, repo name, web URL)
961225. Upsert into the database (auto-syncs FTS)
9797-6. Advance cursor and acknowledge to Tap
123123+6. Persist the Tap cursor and then acknowledge the event
124124+125125+The indexer resumes from its last cursor on restart and replays idempotently.
126126+It logs status every 30 seconds and uses exponential backoff (1s–5s) for
127127+transient failures.
981289999-The indexer resumes from its last cursor on restart (no duplicate processing). It logs status every 30 seconds and uses exponential backoff (1s–5s) for transient failures.
129129+Read-through indexing is `missing` by default. Only allowed collections can be
130130+queued, detail reads queue single focal records, and bulk list handlers no
131131+longer enqueue whole collections.
100132101133## Record Normalizers
102134···140172| `TAP_URL` | — | Tap WebSocket URL |
141173| `TAP_AUTH_PASSWORD` | — | Tap admin password |
142174| `INDEXED_COLLECTIONS` | all | Collection allowlist (CSV, supports wildcards) |
175175+| `READ_THROUGH_MODE` | missing | `off`, `missing`, or `broad` |
176176+| `READ_THROUGH_COLLECTIONS` | `INDEXED_COLLECTIONS` | Read-through allowlist |
177177+| `READ_THROUGH_MAX_ATTEMPTS`| 5 | Retries before `dead_letter` |
143178| `HTTP_BIND_ADDR` | `:8080` | API server bind address |
144179| `INDEXER_HEALTH_ADDR` | `:9090` | Indexer health probe address |
145180| `LOG_LEVEL` | info | debug/info/warn/error |
···159194- **tap** — Tap instance (external dependency)
160195161196All services share the same Turso database. The API and indexer are separate deployments of the same binary with different subcommands.
197197+198198+## Experimental Local DB
199199+200200+The local development database lives at `packages/api/twister-dev.db` when the
201201+API runs with `--local`.
202202+203203+Operational rules:
204204+205205+1. Stop the API before backup or restore.
206206+2. Copy `twister-dev.db` and any matching `-wal` or `-shm` files together.
207207+3. Prefer restore-or-rebuild over repair if the file becomes suspect.
208208+4. Let the DB grow during active experiments, then compact or delete it later.
209209+210210+Useful local inspection:
211211+212212+```sh
213213+cd packages/api
214214+du -h twister-dev.db*
215215+ls -lh twister-dev.db*
216216+```
+13-11
docs/reference/resync.md
···2121when the `indexer` consumes events from Tap. Completeness depends on which DIDs
2222Tap is tracking.
23232424-**Read-through indexing** closes gaps on demand: when the API fetches a record
2525-not yet in the index, it enqueues a background job. This supplements Tap but is
2626-not a substitute for it.
2424+**Read-through indexing** now runs in `missing` mode by default: when the API
2525+fetches a record that is absent or stale, and the collection is allowed, it
2626+enqueues a background job. Bulk list reads no longer enqueue entire collections.
27272828**JetStream** feeds only the activity cache (`/activity`). It does not contribute
2929to the search index.
···110110 ```
1111111121122. Once Tap is tracking the DID, the `indexer` will deliver historical events.
113113- Monitor progress via `GET /admin/status` (requires `ENABLE_ADMIN_ENDPOINTS=true`).
113113+ Monitor progress via `GET /admin/status` and inspect backlog or failures with
114114+ `GET /admin/indexing/jobs` and `GET /admin/indexing/audit`.
114115115115-3. If you need the record indexed immediately, fetch it through the API — the
116116- read-through indexer will enqueue it automatically.
116116+3. If you need the record indexed immediately, fetch the detail endpoint through
117117+ the API or enqueue it explicitly with `POST /admin/indexing/enqueue`.
117118118119### Enrichment gaps
119120···178179179180Response includes:
180181181181-- `tap.cursor` — last Tap event ID processed by the indexer
182182-- `tap.updated_at` — when the cursor was last advanced
183183-- `jetstream.cursor` — JetStream timestamp cursor (activity cache only)
184184-- `documents` — total searchable document count
185185-- `pending_jobs` — read-through indexing jobs not yet processed
182182+- `tap.cursor` and `tap.updated_at`
183183+- `jetstream.cursor` and `jetstream.updated_at`
184184+- `documents`
185185+- `read_through.pending`, `processing`, `completed`, `failed`, `dead_letter`
186186+- `read_through.oldest_pending_age_s` and `oldest_running_age_s`
187187+- `read_through.last_completed_at` and `last_processed_at`
+9-4
docs/specs/search.md
···9696- the production backend choice is documented with explicit tradeoffs
9797- the chosen production backend has a migration path from the experimental local setup
98989999-The concrete local DB operating procedure lives in `packages/api/README.md`.
9999+The concrete local DB operating procedure lives in `docs/reference/api.md`.
100100The production migration path is documented in `docs/adr/storage.md`.
101101102102### Read-Through Indexing
103103104104-When the API fetches a repo, issue, PR, profile, or similar record directly from upstream, it should enqueue background indexing work if that record is not already searchable. Tap remains the primary ingest path; read-through indexing only closes gaps.
104104+When the API fetches a repo, issue, PR, profile, or similar detail record
105105+directly from upstream, it should enqueue background indexing work only when
106106+that record is missing or stale. Tap remains the primary ingest path;
107107+read-through indexing only closes gaps.
105108106109Requirements:
107110108111- add a durable job table for on-demand indexing
109112- deduplicate jobs by stable document identity
110113- reuse the existing normalization and upsert path
111111-- trigger jobs from the handlers that already fetch upstream records
114114+- trigger jobs from detail handlers that already fetch upstream records
115115+- do not enqueue whole collections from list or browse handlers
112116113117Acceptance:
114118115119- a fetched-but-missing record becomes searchable shortly after the first successful API read
116120- repeated page views do not create unbounded duplicate work
121121+- queue state and terminal failures are inspectable through admin endpoints
117122- failures are visible through logs and smoke tests
118123119124### Activity Cache
···215220}
216221```
217222218218-## Pragmatic Search Strategy
223223+## Search Strategy
219224220225Indexing via Tap is useful but has proven unreliable for maintaining complete, up-to-date coverage. The approach:
221226
+2-185
packages/api/README.md
···11-# Twister
22-33-Tap-based indexing and search API for Tangled. Acts as a proxy layer between the Twisted app and all upstream AT Protocol services (knots, PDS, Bluesky, Constellation, Jetstream).
44-55-## Requirements
66-77-- Go 1.25+
88-- A Turso database (or local SQLite for development)
99-1010-## Running locally
1111-1212-```sh
1313-cd packages/api
1414-1515-# Start the API server with a local SQLite database (twister-dev.db)
1616-go run . api --local
1717-```
1818-1919-The server listens on `:8080` by default. Logs are printed as text when `--local` is set.
2020-2121-## API Smoke Tests
2222-2323-Smoke checks for the API surface live in a uv-managed Python project at
2424-`packages/scripts/api/`.
2525-2626-From the repo root:
2727-2828-```sh
2929-uv run --project packages/scripts/api twister-api-smoke
3030-```
3131-3232-Optional base URL override:
3333-3434-```sh
3535-TWISTER_API_BASE_URL=http://localhost:8080 \
3636- uv run --project packages/scripts/api twister-api-smoke
3737-```
3838-3939-## Experimental Local DB Operations
4040-4141-The experimental local database lives at `packages/api/twister-dev.db` when you run Twister from `packages/api` with `--local`.
4242-4343-This database is for local experimentation only. Treat it as disposable unless you explicitly back it up.
4444-4545-### Backup
4646-4747-Recommended procedure:
4848-4949-1. Stop the Twister process using the local DB.
5050-2. Copy the database file and any SQLite sidecar files if they exist.
5151-5252-Example:
5353-5454-```sh
5555-cd packages/api
5656-mkdir -p backups
5757-timestamp="$(date +%Y%m%d-%H%M%S)"
5858-cp twister-dev.db "backups/twister-dev-${timestamp}.db"
5959-test -f twister-dev.db-wal && cp twister-dev.db-wal "backups/twister-dev-${timestamp}.db-wal"
6060-test -f twister-dev.db-shm && cp twister-dev.db-shm "backups/twister-dev-${timestamp}.db-shm"
6161-```
6262-6363-For this experimental DB, stop-and-copy is preferred over hot backup complexity.
6464-6565-### Restore
6666-6767-Recommended procedure:
11+# Twister API
6826969-1. Stop the Twister process.
7070-2. Move the current local DB aside if you want to keep it.
7171-3. Copy the backup file back to `twister-dev.db`.
7272-4. Restore matching `-wal` and `-shm` files only if they were captured with the same backup set.
7373-7474-Example:
7575-7676-```sh
7777-cd packages/api
7878-mv twister-dev.db "twister-dev.db.broken.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
7979-cp backups/twister-dev-YYYYMMDD-HHMMSS.db twister-dev.db
8080-```
8181-8282-After restore, restart Twister and let the app run migrations normally.
8383-8484-### Disk Growth
8585-8686-The local DB will grow during experimentation because of:
8787-8888-- indexed documents
8989-- FTS tables
9090-- activity cache rows
9191-- repeated backfill or reindex runs
9292-9393-Recommended operating procedure:
9494-9595-1. Check file growth periodically.
9696-2. Delete and rebuild the experimental DB freely when the dataset is no longer useful.
9797-3. Run `VACUUM` only when you intentionally want to compact a long-lived local DB.
9898-4. Keep old backups out of the repo and rotate them manually.
9999-100100-Example inspection commands:
101101-102102-```sh
103103-cd packages/api
104104-du -h twister-dev.db*
105105-ls -lh twister-dev.db*
106106-```
107107-108108-For experimental use, the simplest policy is usually:
109109-110110-- back up anything worth keeping
111111-- remove the DB when the experiment is over
112112-- let Twister rebuild from migrations and backfill paths
113113-114114-### Failure Recovery Rule
115115-116116-If the experimental DB becomes suspicious or inconsistent, prefer restore-or-rebuild over manual repair. This is a developer convenience database, not the source of truth.
117117-118118-## Environment variables
119119-120120-Copy `.env.example` to `.env` in the repo root (or `packages/api/`). The server loads `.env`, `../.env`, and `../../.env` automatically.
121121-122122-| Variable | Default | Description |
123123-| -------------------------- | -------------------------------------- | ------------------------------------------------------- |
124124-| `TURSO_DATABASE_URL` | — | Turso/libSQL connection URL (required unless `--local`) |
125125-| `TURSO_AUTH_TOKEN` | — | Auth token (required for non-file URLs) |
126126-| `HTTP_BIND_ADDR` | `:8080` | Address the HTTP server listens on |
127127-| `LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warn`, `error`) |
128128-| `LOG_FORMAT` | `json` | Log format (`json` or `text`) |
129129-| `SEARCH_DEFAULT_LIMIT` | `20` | Default result count for search |
130130-| `SEARCH_MAX_LIMIT` | `100` | Maximum result count for search |
131131-| `ENABLE_ADMIN_ENDPOINTS` | `false` | Expose `/admin/*` endpoints |
132132-| `ADMIN_AUTH_TOKEN` | — | Bearer token required for admin endpoints |
133133-| `CONSTELLATION_URL` | `https://constellation.microcosm.blue` | Constellation API base URL |
134134-| `CONSTELLATION_USER_AGENT` | `twister/1.0 …` | User-Agent sent to Constellation |
135135-| `TAP_URL` | — | Tap firehose URL (indexer only) |
136136-| `TAP_AUTH_PASSWORD` | — | Tap auth password (indexer only) |
137137-| `INDEXED_COLLECTIONS` | — | Comma-separated AT collections to index |
138138-139139-## CLI commands
140140-141141-```sh
142142-twister api # Start the HTTP API server
143143-twister indexer # Start the Tap firehose consumer
144144-twister backfill # Seed the index from upstream APIs
145145-twister reindex # Re-process existing documents (re-syncs FTS)
146146-twister enrich # Backfill RepoName, AuthorHandle, WebURL on existing documents
147147-```
148148-149149-### enrich
150150-151151-Resolves missing `author_handle`, `repo_name`, and `web_url` fields on documents already
152152-in the database. Run this after deploying enrichment changes or when search results show
153153-documents with empty author handles.
154154-155155-```sh
156156-twister enrich --local # all documents
157157-twister enrich --local --collection sh.tangled.repo
158158-twister enrich --local --did did:plc:abc123
159159-twister enrich --local --dry-run # preview without writing
160160-```
161161-162162-Flags: `--collection`, `--did`, `--document`, `--dry-run`, `--concurrency` (default 5).
163163-164164-## Proxy endpoints
165165-166166-The API proxies all upstream AT Protocol and social-graph requests so the app has a single origin:
167167-168168-| Route | Upstream |
169169-| ------------------------------- | ------------------------------------------------------------- |
170170-| `GET /proxy/knot/{host}/{nsid}` | `https://{host}/xrpc/{nsid}` |
171171-| `GET /proxy/pds/{host}/{nsid}` | `https://{host}/xrpc/{nsid}` |
172172-| `GET /proxy/bsky/{nsid}` | `https://public.api.bsky.app/xrpc/{nsid}` |
173173-| `GET /identity/resolve` | `https://bsky.social/xrpc/com.atproto.identity.resolveHandle` |
174174-| `GET /identity/did/{did}` | `https://plc.directory/{did}` or `/.well-known/did.json` |
175175-| `GET /backlinks/count` | Constellation `getBacklinksCount` (cached) |
176176-| `WS /activity/stream` | `wss://jetstream2.us-east.bsky.network/subscribe` |
177177-178178-## Admin endpoints
179179-180180-Available when `ENABLE_ADMIN_ENDPOINTS=true`. Require `Authorization: Bearer <ADMIN_AUTH_TOKEN>` when
181181-`ADMIN_AUTH_TOKEN` is set.
182182-183183-| Route | Description |
184184-| ---------------------- | -------------------------------------------------------- |
185185-| `GET /admin/status` | Tap cursor, JetStream cursor, document count, job queue |
186186-| `POST /admin/reindex` | Re-sync all (or filtered) documents into the FTS index |
33+This package's operational docs live in [`doc.go`](./doc.go).
+149
packages/api/doc.go
···11+// Twister is the Tap-backed indexing and search API for Tangled.
22+//
33+// It proxies upstream AT Protocol services such as knots, PDS endpoints,
44+// Bluesky, Constellation, and Jetstream so the app can use a single origin.
55+//
66+// Requirements
77+//
88+// - Go 1.25+
99+// - A Turso database, or local SQLite for development
1010+//
1111+// Running locally
1212+//
1313+// cd packages/api
1414+// go run . api --local
1515+//
1616+// The local API listens on :8080 by default and uses packages/api/twister-dev.db.
1717+// Logs are printed as text when --local is set.
1818+//
1919+// # API smoke tests
2020+//
2121+// Smoke checks live in packages/scripts/api/. From the repo root:
2222+//
2323+// uv run --project packages/scripts/api twister-api-smoke
2424+//
2525+// Optional base URL override:
2626+//
2727+// TWISTER_API_BASE_URL=http://localhost:8080 \
2828+// uv run --project packages/scripts/api twister-api-smoke
2929+//
3030+// # Experimental local DB operations
3131+//
3232+// The experimental local database lives at packages/api/twister-dev.db when
3333+// you run Twister with --local. Treat it as disposable unless you explicitly
3434+// back it up.
3535+//
3636+// Backup:
3737+//
3838+// 1. Stop the Twister process using the local DB.
3939+// 2. Copy the database file and any SQLite sidecar files if they exist.
4040+//
4141+// Example:
4242+//
4343+// cd packages/api
4444+// mkdir -p backups
4545+// timestamp="$(date +%Y%m%d-%H%M%S)"
4646+// cp twister-dev.db "backups/twister-dev-${timestamp}.db"
4747+// test -f twister-dev.db-wal && cp twister-dev.db-wal "backups/twister-dev-${timestamp}.db-wal"
4848+// test -f twister-dev.db-shm && cp twister-dev.db-shm "backups/twister-dev-${timestamp}.db-shm"
4949+//
5050+// Restore:
5151+//
5252+// 1. Stop the Twister process.
5353+// 2. Move the current local DB aside if you want to keep it.
5454+// 3. Copy the backup file back to twister-dev.db.
5555+// 4. Restore matching -wal and -shm files only if they came from the same set.
5656+//
5757+// Example:
5858+//
5959+// cd packages/api
6060+// mv twister-dev.db "twister-dev.db.broken.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
6161+// cp backups/twister-dev-YYYYMMDD-HHMMSS.db twister-dev.db
6262+//
6363+// Disk growth:
6464+//
6565+// The local DB grows because of indexed documents, FTS tables, activity cache
6666+// rows, and repeated backfill or reindex runs.
6767+//
6868+// Recommended operating procedure:
6969+//
7070+// 1. Check file growth periodically.
7171+// 2. Delete and rebuild the DB freely when the dataset is no longer useful.
7272+// 3. Run VACUUM only when you intentionally want to compact a long-lived DB.
7373+// 4. Keep old backups out of the repo and rotate them manually.
7474+//
7575+// Inspection commands:
7676+//
7777+// cd packages/api
7878+// du -h twister-dev.db*
7979+// ls -lh twister-dev.db*
8080+//
8181+// Failure recovery: prefer restore-or-rebuild over manual repair if the
8282+// experimental DB becomes
8383+// suspicious or inconsistent. It is a developer convenience database, not the
8484+// source of truth.
8585+//
8686+// # Environment variables
8787+//
8888+// Copy .env.example to .env in the repo root or packages/api/. The server loads
8989+// .env, ../.env, and ../../.env automatically.
9090+//
9191+// - TURSO_DATABASE_URL: Turso/libSQL connection URL, required unless --local
9292+// - TURSO_AUTH_TOKEN: auth token, required for non-file URLs
9393+// - HTTP_BIND_ADDR: default :8080
9494+// - LOG_LEVEL: debug, info, warn, or error; default info
9595+// - LOG_FORMAT: json or text; default json
9696+// - SEARCH_DEFAULT_LIMIT: default 20
9797+// - SEARCH_MAX_LIMIT: default 100
9898+// - ENABLE_ADMIN_ENDPOINTS: default false
9999+// - ADMIN_AUTH_TOKEN: bearer token for admin endpoints
100100+// - CONSTELLATION_URL: default https://constellation.microcosm.blue
101101+// - CONSTELLATION_USER_AGENT: user-agent sent to Constellation
102102+// - TAP_URL: Tap firehose URL, indexer only
103103+// - TAP_AUTH_PASSWORD: Tap auth password, indexer only
104104+// - INDEXED_COLLECTIONS: comma-separated AT collections to index
105105+// - READ_THROUGH_MODE: off, missing, or broad; default missing
106106+// - READ_THROUGH_COLLECTIONS: read-through allowlist, default INDEXED_COLLECTIONS
107107+// - READ_THROUGH_MAX_ATTEMPTS: max retries before dead_letter, default 5
108108+//
109109+// CLI commands
110110+//
111111+// twister api
112112+// twister indexer
113113+// twister backfill
114114+// twister reindex
115115+// twister enrich
116116+//
117117+// Enrich:
118118+//
119119+// Resolves missing author_handle, repo_name, and web_url fields on documents
120120+// already in the database.
121121+//
122122+// twister enrich --local
123123+// twister enrich --local --collection sh.tangled.repo
124124+// twister enrich --local --did did:plc:abc123
125125+// twister enrich --local --dry-run
126126+//
127127+// Flags: --collection, --did, --document, --dry-run, --concurrency (default 5).
128128+//
129129+// Proxy endpoints
130130+//
131131+// - GET /proxy/knot/{host}/{nsid} -> https://{host}/xrpc/{nsid}
132132+// - GET /proxy/pds/{host}/{nsid} -> https://{host}/xrpc/{nsid}
133133+// - GET /proxy/bsky/{nsid} -> https://public.api.bsky.app/xrpc/{nsid}
134134+// - GET /identity/resolve -> https://bsky.social/xrpc/com.atproto.identity.resolveHandle
135135+// - GET /identity/did/{did} -> https://plc.directory/{did} or /.well-known/did.json
136136+// - GET /backlinks/count -> Constellation getBacklinksCount, cached
137137+// - WS /activity/stream -> wss://jetstream2.us-east.bsky.network/subscribe
138138+//
139139+// # Admin endpoints
140140+//
141141+// Admin routes require ENABLE_ADMIN_ENDPOINTS=true. If ADMIN_AUTH_TOKEN is set,
142142+// requests must send Authorization: Bearer <ADMIN_AUTH_TOKEN>.
143143+//
144144+// - GET /admin/status: cursor state, queue counts, oldest ages, last activity
145145+// - GET /admin/indexing/jobs: inspect queue rows by status, source, or document
146146+// - GET /admin/indexing/audit: inspect append-only indexing audit rows
147147+// - POST /admin/indexing/enqueue: queue one explicit record for indexing
148148+// - POST /admin/reindex: re-sync all or filtered documents into the FTS index
149149+package main
···11+ALTER TABLE indexing_jobs
22+ ADD COLUMN source TEXT NOT NULL DEFAULT 'read_through';
33+44+ALTER TABLE indexing_jobs
55+ ADD COLUMN lease_owner TEXT DEFAULT '';
66+77+ALTER TABLE indexing_jobs
88+ ADD COLUMN lease_expires_at TEXT DEFAULT '';
99+1010+ALTER TABLE indexing_jobs
1111+ ADD COLUMN completed_at TEXT DEFAULT '';
1212+1313+CREATE INDEX IF NOT EXISTS idx_indexing_jobs_claim
1414+ ON indexing_jobs(status, scheduled_at, lease_expires_at, updated_at);
1515+1616+CREATE TABLE IF NOT EXISTS indexing_audit (
1717+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1818+ source TEXT NOT NULL,
1919+ document_id TEXT NOT NULL,
2020+ collection TEXT NOT NULL,
2121+ cid TEXT NOT NULL,
2222+ decision TEXT NOT NULL,
2323+ attempt INTEGER NOT NULL DEFAULT 0,
2424+ error TEXT,
2525+ created_at TEXT NOT NULL
2626+);
2727+2828+CREATE INDEX IF NOT EXISTS idx_indexing_audit_created
2929+ ON indexing_audit(created_at DESC);
3030+3131+CREATE INDEX IF NOT EXISTS idx_indexing_audit_document
3232+ ON indexing_audit(document_id, created_at DESC);