Document server-side endpoint contract; correct Current state precision
- AGENTS.md: add Endpoints section — auth model (cloud operator-key path, license/install-id path, admin session cookie, BTCPay HMAC) plus full /relay/* surface (public + operator-key-only control plane), the /admin/* dashboard, and the /admin/internal-meetings/* API. - AGENTS.md: rewrite Current state with verified git facts — HEAD is the prior docs commit, HEAD~1 is v0.2.11, working tree at v_0_2_124, file counts pulled live from git status. - ROADMAP.md: log two doc-precision follow-ups caught in review (the working-tree counts drift fast; the admin-route shortlist silently omits three real routes).
This commit is contained in:
@@ -59,6 +59,61 @@ docs/issues-backlog.md detailed issue log
|
||||
6. **Extras** (`meeting-extras.js`).
|
||||
7. **Audio is deleted after processing** (success or failure) — the relay never retains uploaded audio.
|
||||
|
||||
## Endpoints (server-side contract)
|
||||
|
||||
All routes mount in `server/index.js`. Public paths sit under `/relay/*`; operator paths under `/admin/*`.
|
||||
|
||||
### Auth model
|
||||
|
||||
- **`X-Recap-Operator-Key`** + **`X-Recap-User-Id`** → "cloud" path. The Recaps cloud server (`recaps.cc`) authenticates once with a shared operator key (`relay_cloud_operator_key`) and names the acting user. Credit pool keyed `user:<id>`, tier comes from the relay's stored row, NOT a per-user license. See `server/identity.js`.
|
||||
- **`X-Recap-Install-Id`** (+ optional `Authorization: <license>`) → "license" path. Self-hosted installs and the operator's single-mode app. Credits/tier come from the resolved Keysat license + install id.
|
||||
- **Admin session cookie** → `/admin/*`. Cookie issued by `POST /admin/login`; `/admin/login` and `/admin/status` are exempt inside `setupAdminAuthMiddleware`.
|
||||
- **Webhook signature** → `POST /relay/btcpay/webhook` validates `BTCPay-Sig` against `relay_btcpay_webhook_secret`. Zaprite's webhook re-fetches the order through the Zaprite API to verify, so no shared-secret signing.
|
||||
- **`X-Recap-Job-Id`** is a billing key, not auth: the first call with a given id charges one credit; later calls with the same id are free (so transcribe + analyze for one summary = one credit total).
|
||||
|
||||
### `/relay/*` (public; per-call header auth)
|
||||
|
||||
- `GET /relay/health` — liveness; tolerates partial config. (`routes/health.js`)
|
||||
- `GET /relay/policy` — `{ tiers, core_total_credits, core_gemini_credits }`; no auth. (`routes/policy.js`)
|
||||
- `GET /relay/capabilities` — operator-wide feature flags (hardware ready, TTS backend choice, etc). `X-Recap-Install-Id` optional. (`routes/capabilities.js`)
|
||||
- `GET /relay/balance` — caller's credit balance (`routes/balance.js`).
|
||||
- `POST /relay/transcribe` — multipart audio → `{ text, segments, duration_seconds, model, ... }`. Body fields: `mime_type`, `title`, `channel`, `description`. (`routes/transcribe.js`)
|
||||
- `POST /relay/transcribe-url` — async; `{ media_url, type, mime_type, title, channel, description, chapters }` → `{ job_id }` then poll `GET /relay/jobs/:id`. (`routes/transcribe-url.js`)
|
||||
- `POST /relay/summarize-url` — async; same body shape, full transcribe+analyze pipeline → `{ job_id }` then stream `GET /relay/summarize-url/:jobId/events` (SSE). (`routes/summarize-url.js`)
|
||||
- `POST /relay/analyze` — `{ transcript, … }` → topic sections JSON. (`routes/analyze.js`)
|
||||
- `POST /relay/tts` — text → audio; gated by `capabilities.has_tts`. (`routes/tts.js`)
|
||||
- `GET /relay/credits/packages`, `POST /relay/credits/buy`, `GET /relay/credits/invoice/:id` — à-la-carte credit purchase (BTCPay). (`routes/credits.js`)
|
||||
- `POST /relay/btcpay/webhook` — BTCPay settle → either `extendUserTier` (subscription) or credit grant (à-la-carte). HMAC validated. (`routes/credits.js`)
|
||||
- `POST /relay/zaprite/webhook` — Zaprite settle → `extendUserTier` only. Re-fetches order to verify. (`routes/zaprite-webhook.js`)
|
||||
|
||||
### `/relay/*` (operator-key only — cloud → relay control plane)
|
||||
|
||||
All require a valid `X-Recap-Operator-Key`. Defined in `routes/user-tier.js`.
|
||||
|
||||
- `POST /relay/user-tier` — `{ user_id, tier: "core"|"pro"|"max", expires_at? }` → sets the cloud user's stored tier (operator comp grants live here).
|
||||
- `POST /relay/tier-invoice` — `{ user_id, tier: "pro"|"max", return_url }` → mints a BTCPay tier-purchase invoice (Lightning QR).
|
||||
- `POST /relay/tier-zaprite-order` — same idea on the card rail.
|
||||
- `GET /relay/tier-plans` — `{ ok, period_days, plans: [{tier, sats, fiat_amount, fiat_currency, credits_per_period}], card_available }`. `credits_per_period: null` → "Unlimited"; never hardcode this label.
|
||||
- `GET /relay/expiring-subscriptions?within_days=7&lapsed_days=3` — `{ ok, now, subscriptions: [{user_id, tier, expires_at, expired, days_left}] }`. The Recaps server maps user_id → email and sends the reminder; the relay never sees email.
|
||||
- `GET /relay/user-tier/:userId` — read the stored row.
|
||||
|
||||
### `/admin/*` (operator dashboard; cookie-gated)
|
||||
|
||||
`routes/admin.js`: `GET /admin/{usage,config,license-cache,hardware-queue,jobs,jobs-history,job/:id/details,dashboard,dashboard.csv,settings,output-store-stats}`, `POST /admin/{quotas,wipe-all}`, `PUT /admin/settings`, `DELETE /admin/job-outputs`. `routes/admin-test-run.js`: `POST /admin/test-run`. BTCPay setup wizard under `/admin/btcpay/*` (`routes/btcpay-setup.js`).
|
||||
|
||||
### `/admin/internal-meetings/*` (cookie-gated; `routes/internal-meetings.js`)
|
||||
|
||||
- `POST /upload` — multipart audio; runs the full pipeline (chunk → diarize → cluster → analyze → polish → extras → save). Audio is deleted after.
|
||||
- `GET /` → `{ meetings: [...] }`; `GET /:id` → full saved record (`rec`).
|
||||
- `GET /:id/markdown`, `GET /:id/html`, `GET /:id/download` — exports.
|
||||
- `GET /jobs/:id`, `GET /jobs/:id/stream` (SSE) — progress for a running upload.
|
||||
- `PATCH /:id/speakers` — rename a cluster (display-name only).
|
||||
- `PATCH /:id/entries` — per-line `speaker_override`.
|
||||
- `PATCH /:id/merge-speakers` — fold cluster(s) into one (split-as-two). Offline, no LLM.
|
||||
- `POST /:id/recluster` — re-run clustering at a new threshold (merged-as-one). Offline, uses `rec.diarization` fingerprints. Resets `speaker_names`, per-line overrides, and extras attributions. 400 if no fingerprints.
|
||||
- `POST /:id/repolish` — re-runs `runSummaryPolish` with the CURRENT names (no re-inference). Synchronous; needs hardware analyze online; 400 if no named speakers.
|
||||
- `DELETE /:id`.
|
||||
|
||||
## Conventions for this codebase specifically
|
||||
|
||||
- **A saved meeting record stores the per-chunk TitaNet fingerprints in `rec.diarization`.** Because the audio is gone, this is what makes re-clustering possible *offline* — no re-upload, no Spark Control round-trip.
|
||||
@@ -86,9 +141,9 @@ docs/issues-backlog.md detailed issue log
|
||||
- **Never edit a `startos/versions/<v>.ts` that's already been built/installed** — add a new version file.
|
||||
- **Don't push to GitHub by default** — remote is self-hosted Gitea.
|
||||
|
||||
## Current state (2026-06-13) — at `0.2.124`; only git commits lag
|
||||
## Current state — box AND working tree at `0.2.124`; git is the gap
|
||||
|
||||
- **Box AND local working tree are both at relay `0.2.124`** (app `0.2.155`). Confirmed on the StartOS UI (version + the Merge/Re-polish controls visible on the dashboard).
|
||||
- **The version files `v0.2.117`–`v0.2.124` are all in this working tree** (untracked). v0.2.124's note is a billing change ("tier Bitcoin invoices return the Lightning BOLT11 + per-period credit allotment"). A **concurrent chat session** during 2026-06-13 continued from this session's 0.2.117, bumped through 0.2.124, and built+installed it to the box — so the working tree matches the box. (Heads-up: more than one session may be editing this tree; re-read before assuming.)
|
||||
- **The post-hoc speaker tools are present and live**: `meeting-speaker-edits.js` (merge/recluster/repolish + backfill) and the matching `/admin/internal-meetings/:id/{merge-speakers,recluster,repolish}` routes; the dashboard shows the controls. Tests pass (32, `npm test`).
|
||||
- **The real gap is git, not versions.** Committed HEAD is `v0.2.11`; everything since — v0.2.12→v0.2.124, the entire internal-meetings feature, diarization, speaker-edit tools, billing — is **uncommitted** (≈28 modified + 153 untracked). "Catching up local git" = committing this large working tree (see ROADMAP). The 0.2.117 this session installed was superseded by the concurrent 0.2.124 — **no box downgrade occurred.**
|
||||
- **Box AND local working tree are both at relay `0.2.124`** (app at `0.2.155`). `startos/versions/index.ts` `current: v_0_2_124`; the StartOS dashboard reflects the same.
|
||||
- **Version files `v0.2.117`–`v0.2.124` are present in the working tree** (untracked). A concurrent 2026-06-13 session continued from this session's 0.2.117, bumped through 0.2.124, and shipped to the box — re-read the tree before assuming what's there.
|
||||
- **Post-hoc speaker tools are live**: `meeting-speaker-edits.js` (merge / recluster / repolish + backfill) and the matching `PATCH/POST /admin/internal-meetings/:id/{merge-speakers,recluster,repolish}` routes are present; the dashboard exposes the controls. Tests pass via `cd server && npm test`.
|
||||
- **The real gap is git, not versions.** `HEAD` is `6fa175a Add agent docs`; `HEAD~1` is `b7f7590 v0.2.11 /relay/capabilities + /relay/transcribe-url`. So the last code commit is at `v0.2.11`; everything from `v0.2.12` → `v0.2.124` — the entire internal-meetings feature, diarization, speaker-edit tools, billing, the user-tier control plane — is uncommitted. Working-tree counts: **28 modified, 150 untracked, 5 deleted (183 total)** as of this read. "Catching up git" = committing this tree (see ROADMAP).
|
||||
|
||||
Reference in New Issue
Block a user