One authenticated dashboard for every operator workflow
Every page outside /login, /blog, and /auth/callback requires a Supabase session whose email exists in the admin_users table before it renders.
Access model#
The admin check runs in src/middleware.ts on every matched page request: it resolves a Supabase session and requires the email in admin_users. A signed-in non-admin is signed back out. Access is operator-only: authorization is an explicit row in admin_users, with no signup path and no role model beyond admin. API routes are excluded from that middleware and each authorizes itself — an admin session, a shared secret, or nothing at all for the two public read surfaces.
The one unauthenticated liveness probe is GET /api/health, which returns {"status":"ok"} and nothing else.
User synchronization starts directly from the synchronous auth event callback; Echo does not defer it through a timer or return its promise to the auth notifier. Its completion releases the loading state. Token refresh updates the session without starting another user synchronization. A dismissed loading indicator is not proof of a successful user synchronization: failed synchronization reads and writes retain their errors in the browser console.
Operator workspaces#
- Content generation: image, video, audio, meme, lipsync, and LoRA pipelines.
- Asset and character library for reusable generation inputs.
- Social account automation, CRM, and outreach.
- Paid ads plus trends and competitor research.
- Blog admin: drafts, publishing, and landing-page revalidation.
- Onboarding Studio at /onboarding for journey drafting, publication, activation, and rollback.
- Diagnostics at /diagnostics and service balances at /balances.
Results without clock-driven feedback#
The product editor shows Saved after the product update response succeeds. The receipt remains beside the saved product until another save attempt, edit, or selection replaces it; a timer does not clear it.
Video Frame Grabber keeps a notification until another action replaces it or you select Dismiss. The browser's playback refusal appears in that notification with its cause, rather than calling a notification method on the local message state.
Civitai LoRAs search runs when you press Enter or select Search. Typing alone does not schedule a request. Submitting the same query refreshes it; changing a sort or base model starts the corresponding read. A replacement read or leaving the tab cancels the prior observation. Late canceled results cannot replace the current list. A failed read displays its cause and is not presented as No LoRAs found.
Training status uses an initial read and a held read for the final state. A held read failure, including HTTP 504, is reported instead of silently opening another read. A read failure does not prove the training job failed and does not submit another job. Status, elapsed time, ETA and the reference cost estimate belong to the observed job and its retained status observation; a clock does not advance them between reads. A late initial response cannot replace a final read. The reference estimate uses $7.35/hour and is not a charge receipt. Both training displays retain a failed status read even when an earlier step count remains available.
Video generation shows the received provider status instead of a ticking elapsed counter. For scene video, the first final result or failure controls the screen. If the stream closes without a final video result or a failure, Echo reports that missing result instead of leaving a progress message on screen.
Avatar configuration requires an explicit lipsync output resolution; Hedra also requires an aspect ratio. Those fields start empty, remain editable when switching providers and are sent unchanged through POST /api/generate/lipsync to Film. Missing values refuse before Film stages portrait or voice media. The renderer reports unsupported values instead of silently choosing a tier. The avatar generation step also requires the portrait model selected in the wizard before starting paid work.
Paid Ads overview keeps failed campaign or metric reads distinct from an empty report. GET /api/paid-ads/metrics returns the failed database operation and cause; the overview displays HTTP, transport and JSON-read failures with Try again. Changing the window or retrying cancels the earlier observation. Zero advertising spend alone does not hide an otherwise populated revenue report.
Revenue subscribers come only from the provider's active_subscriptions measurement, not from ad impressions. API totals and campaign rows expose subscribers separately; absent values stay null and the overview and table say Not measured. If any selected revenue campaign lacks that measurement, the aggregate subscriber total is null; the overview names the missing coverage rather than presenting the measured subset as a complete total.
The campaign table also checks both metrics and account reads before replacing its rows. HTTP refusals, malformed JSON and absent arrays appear with their cause, not as No campaigns found. Reload campaign data repeats the reads without replaying a campaign mutation; a superseded or canceled read cannot replace the current rows.
Paid report synchronization stores missing numeric measurements and budgets as null. A malformed observed number refuses the sync with the field, value and reported row; it is not rewritten to zero. Missing metrics arrays and malformed campaign arrays also refuse synchronization, so they cannot advance last_auto_sync_at as a successful empty report.
Revenue totals, daily revenue, platform revenue and campaign transaction counts remain null when any selected row lacks the required measurement. Every observed row is still validated: an earlier missing measurement does not hide a later malformed value. Numeric database strings must describe finite numbers; malformed values and sum overflow identify the campaign, date and field. Revenue views display conversion_value as money and conversions as transactions, not spend or clicks. Ad-only CTR, ROAS and acquisition cost are not inferred from revenue records; those table cells are Not applicable. The daily revenue chart omits unmeasured bars instead of drawing a zero.
From Echo Web, node tests/reporting/revenue.mjs --fixture PATH qualifies the real authenticated revenue report against independently read persisted provider metrics. Supply an exclusive isolated account, measured and unmeasured campaign IDs, the deployed source revision and retained deployment receipt; inject ECHO_TEST_SESSION_COOKIE and ECHO_TEST_SERVICE_ROLE_KEY through the runtime credential mechanism. It checks anonymous refusal, missing versus observed totals and daily rows, and unchanged stored metrics. Private .build/revenue-report-* output retains source identity, commands, exit status and full responses. This read-only API journey does not substitute for provider synchronization or graphical qualification.
Revenue qualification derives expected daily dates from the independently read provider records. Every unmeasured date must appear exactly once with revenue null; an omitted date, duplicate daily row or zero substituted for an absent measurement fails the journey.
Observability and cost#
/diagnostics shows recorded action outcomes through Platforms, Actions and Services. It keeps no separate list of registration steps or provider choices: Weles owns the execution definitions. The platform and action lists come from the returned records, not a hardcoded built/missing matrix. An empty successful read means no completed or failed actions were recorded in that window; it is not evidence that an action is unimplemented.
GET /api/diagnostics?days=<days> returns window_days, since and checked_at with the observed data. days is required and is any positive whole number; without it the API answers 400 "days is required: the number of days to read", and a value that is not a positive whole number answers 400 "days must be a positive whole number". The screen has a Days field and reads nothing until it holds such a number; /api/post-analytics and its summary panel take days the same way. Each action includes count, completed and failed; the screen displays exact completed/total counts instead of rounding a partial success up to 100%. A failed action never increments completed. Recent platform failures include the action name and retain available screenshots and recordings. Refreshing or naming another window replaces the observation; a canceled earlier response cannot overwrite the new one. A failed read is shown as a failure, not as empty successful statistics.
Services reports the outcomes of actions that recorded a service cost. These counts do not prove whether the service itself was healthy, and the displayed sum is recorded cost, not an independently checked charge receipt. Unhandled read failures go through Echo's shared failure response and server-side diagnostic reporting, including x-wisent-failure and x-wisent-failure-impact. An absent user session still returns HTTP 401 with Unauthorized. The CLI's echo-cli reliability <days> and echo-cli operations <days> remain read-only views; a credential or data-source refusal is a blocked read, not a reliability score.
The 41 cron schedules are the largest unattended cost source — several run every 3 to 30 minutes and trigger generation, scraping, and outreach. Service balances are polled and surfaced on /balances. A balance below ECHO_LOW_BALANCE_USD, the operator's stated threshold in dollars, raises a pending_actions row and a recovered balance dismisses it; with the setting unset no action is filed and the answer's pending_actions_error says the threshold is not stated. The answer carries low_balance_threshold_usd.