Flow Analyzer — platform administrator manual
This guide is for the person who runs, deploys, and hardens Flow Analyzer: SaaS operators, DevOps engineers, and on-prem installers. It explains what you own on the host, how that differs from customer org admins, and where to find deeper runbooks.
Related docs: Deployment · Operator runbook · End-user SaaS manual · Organization administrator manual
In the app: sign in with the platform admin account configured via FLOW_ANALYZER_PLATFORM_ADMIN_EMAIL / FLOW_ANALYZER_PLATFORM_ADMIN_PASSWORD, then open `/admin`. The console includes dashboard, organizations, users, billing trace, support inbox, satisfaction, engagement board, public demo limits, connector environment status, and usage analytics. End-user help also lives under Help & guide in the product header.
Your role vs organization administrators
| Responsibility | Platform administrator (you) | Organization administrator (customer) |
|---|---|---|
| Install or upgrade the app | Yes | No |
| Postgres, TLS, reverse proxy, cron schedulers | Yes | No |
Deployment-wide secrets (AUTH_SECRET, CLICKUP_*, ASANA_*, LINEAR_*, TRELLO_API_KEY, Jira OAuth app, cron bearers) | Yes | No |
| Register ClickUp / Atlassian / Asana / Linear OAuth apps once per deployment | Yes | Uses the apps you registered |
| Create organizations (unless self-serve registration is on) | Sometimes | Self-serve when enabled |
| Add Jira, ClickUp, Asana, Linear, or Trello connections inside an org | No | Yes |
| Pick source scope (lists, projects, boards) and workspace config | No | Yes |
| Invite teammates, run day-to-day analysis | No | Yes |
Think of yourself as the landlord: you keep the building safe, powered, and reachable. Org admins are tenants: they furnish their floor (connections, workspaces, people) and use the product.
Prerequisites
Before you deploy Flow Analyzer for multi-tenant SaaS:
| Requirement | Notes |
|---|---|
| Node.js | Version 20 or newer (see repo package.json). |
| PostgreSQL | Required when FLOW_ANALYZER_SAAS_ENABLED=true. Snapshots, findings, tenant jobs, and encrypted credentials live here. |
| Docker (optional) | Recommended for production: root Dockerfile, docker-compose.yml, and deploy scripts under scripts/. You can also run Node directly on a host. |
| TLS termination | Reverse proxy or CDN with valid certificates; forward X-Forwarded-Proto: https and X-Forwarded-Host. |
| Outbound HTTPS | Jira Cloud (*.atlassian.net), ClickUp API (api.clickup.com), Asana API (app.asana.com), Linear GraphQL (api.linear.app), Trello API (api.trello.com), optional SMTP, optional HubSpot — see Egress notes. |
| Scheduler | Cron or platform jobs for live-workspace poll, usage snapshots, and report delivery when those features are enabled. |
Local development defaults: Next.js on port 4300 (npm run dev from repo root). Production Docker maps container port 3000 to a host loopback port (default 3041 via FLOW_ANALYZER_HOST_PORT).
Deployment overview
Flow Analyzer ships as a Next.js application (apps/web) with in-process API routes. Target topology:
```
Internet → TLS (Traefik / Nginx) → Next.js → PostgreSQL
optional → Redis
```
Do not duplicate full procedures here. Use these canonical references:
| Topic | Document |
|---|---|
| Topology sketch, Contabo VPS, FlowOS shared stack, Redis | deployment.md |
On-prem single-host Compose (docker-compose.onprem.yml) | deployment.md — On-prem single-host profile |
| Health checks, RLS, crons, security hardening, ClickUp operator notes | operator-runbook.md |
| Env template | apps/web/.env.example |
| Docker migrate after deploy | scripts/docker/docker-migrate-deploy.sh (invoked by deploy scripts after docker compose up) |
Typical production path:
- Create
apps/web/.envon the server (never commit secrets). - Build and start the container or Node process.
- Run `prisma migrate deploy` via
docker-migrate-deploy.shor your approved migration path after the app is up (deploy scripts do this automatically). - Verify `GET /api/health` and `GET /api/ready`.
- Configure schedulers for poll and usage crons when SaaS is enabled.
On-premise deployment profile
Use this when Flow Analyzer runs inside the customer network (air-gapped or self-managed) rather than as vendor-hosted SaaS. The application is the same codebase; behavior is controlled by `FA_DEPLOYMENT_MODE=onprem`.
When to choose on-prem
| Scenario | Typical choice |
|---|---|
| Jira Data Center on a private network | On-prem Flow Analyzer on the same network (primary DC story) |
| Corporate policy forbids SaaS egress to work tools | On-prem with internal IdP and optional offline license |
| Customer-managed Postgres and backups | On-prem Compose profile or your own orchestration |
Hosted SaaS uses `FA_DEPLOYMENT_MODE=saas` (default). Do not set per-user overrides for SSRF or TLS — deployment mode is instance-wide.
Install (Compose)
Canonical steps live in deployment.md — On-prem single-host profile. Summary:
- Copy
apps/web/.env.example→apps/web/.envand set production secrets (AUTH_SECRET,FLOW_ANALYZER_SAAS_CREDENTIALS_KEY, public URLs). - Set `FA_DEPLOYMENT_MODE=onprem` (already set in
docker-compose.onprem.yml). - Start:
docker compose -f docker-compose.onprem.yml up -d --build - Migrate:
docker compose -f docker-compose.onprem.yml exec web /app/docker-migrate-deploy.sh - Terminate TLS at Nginx/Traefik on loopback port `FLOW_ANALYZER_HOST_PORT` (default 3041).
The profile includes web + Postgres 18; add `--profile redis` when you set REDIS_URL. Analysis jobs run in-process inside the web container today (no separate worker service).
On-prem environment variables
| Variable | Purpose |
|---|---|
FA_DEPLOYMENT_MODE | onprem — relaxes SSRF for internal targets, enables license enforcement, turns product telemetry off by default |
FA_LICENSE_FILE | Path to vendor-signed offline license JWT (Ed25519). Required for continued new analyses on production on-prem |
FA_LICENSE_PUBLIC_KEY_PEM or FA_LICENSE_PUBLIC_KEY_FILE | Public key used to verify the license file (offline; no phone-home) |
FA_SSO_BOOTSTRAP_FILE | Optional JSON applied on server boot to materialize instance IdP before any UI login — see SSO migration guide |
FA_ALLOW_INSECURE_TLS | On-prem only: allow self-signed / internal CA for Jira DC or IdP targets (logged at startup) |
FLOW_ANALYZER_PRODUCT_TELEMETRY | Set true to opt in to product audit JSONL on on-prem (off by default) |
FLOW_ANALYZER_ONPREM_PG_PASSWORD | Postgres password for the bundled Compose profile |
Signing a license (vendor / internal): use scripts/license/sign-on-prem-license.mjs with a private Ed25519 key; distribute only the .jwt file and install the matching public key on the host. See deployment.md for backup, upgrade, and sizing notes.
License lifecycle (on-prem)
| State | Product behavior |
|---|---|
| Valid license | Normal operation — analyses, ingest, and dashboards |
| Expired or missing license | Read-only grace: users can still sign in and view existing snapshots and findings; a yellow banner explains the situation; new analyses are paused (API returns a clear error; scheduled poll/webhook enqueue is skipped) |
| Invalid signature | Same as missing — fix the license file or public key |
This is intentional: no data loss on expiry; renew the license file and restart is not required for read paths.
First-run bootstrap
On a completely empty database (after migrate), you can materialize the first organization and a break-glass org admin without using self-serve registration. The server applies this once per process on startup when FA_DEPLOYMENT_MODE=onprem and bootstrap config is present. If any company already exists, bootstrap is skipped (idempotent).
Option A — JSON file (recommended):
Set FA_ONPREM_BOOTSTRAP_FILE to a path mounted into the container, for example /etc/flow-analyzer/instance-bootstrap.json:
```json
{
"version": "1.0",
"company": {
"name": "ACME Corporation",
"billing_reference": "ACME-2026",
"max_seats": 50,
"maxsourceconnections": 5
},
"breakglassadmin": {
"email": "admin@acme.local",
"display_name": "Platform Administrator",
"password": "ChangeMeNow123!"
}
}
```
Option B — environment variables:
| Variable | Required | Purpose |
|---|---|---|
FA_ONPREM_BOOTSTRAP_COMPANY_NAME | Yes | First organization name |
FA_ONPREM_BOOTSTRAP_ADMIN_EMAIL | Yes | Break-glass admin email (also used for normal sign-in before SSO enforcement) |
FA_ONPREM_BOOTSTRAP_ADMIN_PASSWORD | Yes | Min. 10 characters; stored hashed; also enables break-glass login |
FA_ONPREM_BOOTSTRAP_ADMIN_DISPLAY_NAME | No | Defaults to the email local part |
FA_ONPREM_BOOTSTRAP_MAX_SEATS | No | Default 50 |
FA_ONPREM_BOOTSTRAP_MAX_SOURCE_CONNECTIONS | No | Default 5 |
SSO IdP bootstrap from FA_SSO_BOOTSTRAP_FILE runs after instance bootstrap on the same startup. Use it to pre-configure OIDC/SAML before anyone signs in through the UI. See SSO migration guide.
Security: treat bootstrap passwords like root credentials — store them in your secret manager, rotate after first sign-in, and prefer SSO enforcement once the IdP is verified.
Environment variables by category
All variables below belong in `apps/web/.env` (or your secret manager). See apps/web/.env.example for the full list and comments.
Authentication and public URL
| Variable | Purpose |
|---|---|
FLOW_ANALYZER_SAAS_ENABLED | Set true for multi-tenant mode (required for org settings, live connectors, registration). |
AUTH_SECRET or NEXTAUTH_SECRET | Mandatory in production SaaS. Long random string for session JWTs. Missing value → 503 on all routes except health/ready. |
NEXTAUTH_URL | Public origin for Auth.js callbacks (e.g. https://flowanalyzer.example.com). |
FLOW_ANALYZER_PUBLIC_BASE_URL | Canonical browser URL for email links, CORS, and cross-site checks. |
FLOW_ANALYZER_EXTRA_ALLOWED_ORIGINS | Optional comma-separated extra origins (www vs apex, staging alias). |
Enterprise SSO (when your deployment enables it): identity provider configuration is stored in the database; bootstrap options are described in SSO migration guide. Org users see Continue with SSO on the sign-in page when their company has SSO configured.
Database
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection for the runtime app user (subject to RLS in SaaS). |
FLOW_ANALYZER_MIGRATE_DATABASE_URL | Optional override for migration-only credentials when runtime user cannot run DDL. |
FLOW_ANALYZER_SKIP_MIGRATE_ON_START | Set 1 to skip docker-migrate-deploy.sh (manual migrate workflows only). |
Pooler warning: Use session pooling or a direct Postgres connection. Transaction-mode poolers (e.g. PgBouncer pool_mode=transaction) can break transaction-local tenant GUCs and self-serve registration.
SaaS feature flags
| Variable | Purpose | ||
|---|---|---|---|
FLOW_ANALYZER_DATA_SOURCE | Default empty \ | seed \ | live when no workspace context (usually seed for demos). |
FLOW_ANALYZER_ALLOW_SOURCE_UI | true only in staging/demo — in-app data-source switcher. Disabled automatically in SaaS for signed-in users. | ||
FLOW_ANALYZER_TENANT_INGEST_MAX_ISSUES | Cap per ingest job (default 500, max 5000). | ||
FLOW_ANALYZER_EVALUATION_NOTES_UI | Set 0 to hide Evaluation notes hub. |
Credential encryption (per-organization secrets at rest)
| Variable | Purpose |
|---|---|
FLOW_ANALYZER_SAAS_CREDENTIALS_KEY | Encrypts Jira PATs, ClickUp tokens, and OAuth bundles stored per org. Use a 64-character hex key (openssl rand -hex 32) or a strong passphrase. Rotating this key invalidates stored credentials until org admins re-save connections. |
Jira OAuth (deployment-wide, optional)
Required only when org admins should connect Jira via OAuth 2.0 instead of (or in addition to) personal API tokens.
| Variable | Purpose |
|---|---|
FLOW_ANALYZER_JIRA_OAUTH_CLIENT_ID | Atlassian OAuth app client ID. |
FLOW_ANALYZER_JIRA_OAUTH_CLIENT_SECRET | Client secret (keep secure). |
FLOW_ANALYZER_JIRA_OAUTH_REDIRECT_URI | Must match the Atlassian app redirect, e.g. https://your-domain.com/api/auth/jira-callback. |
Register the app at Atlassian Developer Console. Org admins still choose site and scope in the product; you only supply the OAuth app credentials once per deployment.
ClickUp OAuth (deployment-wide, optional)
Required when org admins should use Connect with ClickUp (OAuth). Personal API token connections do not need these variables.
| Variable | Purpose |
|---|---|
CLICKUP_CLIENT_ID | ClickUp app client ID. |
CLICKUP_CLIENT_SECRET | ClickUp app client secret (keep secure). |
CLICKUP_OAUTH_REDIRECT_URI | Must match the ClickUp app redirect, e.g. https://your-domain.com/api/integrations/clickup/oauth/callback. |
See Registering ClickUp OAuth below.
Webhooks, crons, and automation
| Variable | Purpose |
|---|---|
FLOW_ANALYZER_JIRA_WEBHOOK_SECRET | Optional global Jira webhook signing secret (org-level secrets can also be stored per connection). |
FLOW_ANALYZER_JIRA_POLL_CRON_SECRET | Bearer for `/api/cron/jira-live-workspaces-poll` — safety-net ingest for jira_live and clickup_live workspaces (schedule at least hourly). |
FLOW_ANALYZER_USAGE_CRON_SECRET | Bearer for `/api/cron/saas-usage-daily`. |
FLOW_ANALYZER_REPORT_CRON_SECRET | Bearer for scheduled report delivery cron. |
FLOW_ANALYZER_RETENTION_CRON_SECRET | Bearer for `/api/cron/data-retention` (password-reset tokens, old tenant jobs, audit JSONL TTL, Flow Story signal age purge). |
FLOW_ANALYZER_FLOW_SIGNALS_RETENTION_DAYS | Optional Flow Story signal TTL by occurredAt (default 90; bounds 1–3650). |
FLOW_ANALYZER_HEADLESS_API_TOKEN | Bearer for automation (/api/analysis/jobs, findings, export) without a browser session. |
Rotate cron and headless secrets in one maintenance window with your schedulers.
Email (optional)
FLOW_ANALYZER_REPORT_SMTP_* and FLOW_ANALYZER_REPORT_EMAIL_FROM power password reset, invitations (when sendEmail=true), scheduled reports, and the contact form. Without SMTP, those features degrade gracefully (tokens shown in UI for invites; password reset does not send mail).
Optional infrastructure
| Variable | Purpose |
|---|---|
REDIS_URL | Optional — hot snapshot L2 cache, shared API-key/MCP rate limits, tenant-job lock. Not required for single-process operation. Health check reports redis status. |
FLOW_ANALYZER_SNAPSHOT_RAW_ISSUES_* | Large snapshot offload to object storage (see Jira persistence allow-list). |
Database migrations
Flow Analyzer uses Prisma migrations under prisma/migrations/. In production you apply them with:
```bash
prisma migrate deploy
```
In Docker production, deploy scripts run `/app/docker-migrate-deploy.sh` after the container is up (see deployment.md).
Safety rules
- Never run `prisma migrate reset` on production. That destroys data.
- Never use `db push --force-reset` on a database that holds customer data.
- Prefer `migrate deploy` on a reviewed migration history; follow your organization's change-control process.
- If migrate and runtime users differ, set `FLOW_ANALYZER_MIGRATE_DATABASE_URL` for the migrate script only.
- After RLS phase-2 migrations, ensure pending migrations are fully applied before declaring the site healthy — see operator runbook — PostgreSQL RLS for symptom → fix tables.
What you should expect: migrations add columns, RLS policies, and helper functions. They do not delete tenant snapshots or findings when applied correctly.
Multi-tenant security (high level)
SaaS mode stores each company's data in PostgreSQL with row-level security (RLS) on tenant tables (fa_user, fa_company, fa_workspace, snapshots, findings, jobs, connections, and related tables).
Before tenant queries, the application sets transaction-local session variables (flow_analyzer.company_id, flow_analyzer.workspace_id, flow_analyzer.app_user_id, and others) inside short Prisma transactions. That limits blast radius if application code omits a WHERE company_id = … clause.
Your responsibilities:
- Run the app with a database user that is subject to RLS at runtime (not a superuser bypass).
- Use a migration-capable role only for
migrate deploy. - Terminate TLS and protect
AUTH_SECRET,FLOW_ANALYZER_SAAS_CREDENTIALS_KEY, and OAuth client secrets. - Schedule trusted crons with bearer secrets; they use separate trusted-transaction helpers.
Not a substitute for app checks: RLS is defense in depth. Route handlers still enforce org membership and workspace scope.
Details: operator runbook — PostgreSQL RLS · SaaS Prisma tenant read scope · NFR-03 security checklist.
Registering a ClickUp OAuth app
Org admins connect ClickUp through Organization and workspaces → Source connections or the Connect ClickUp wizard. OAuth requires you to register one ClickUp app per Flow Analyzer deployment.
Steps
- In ClickUp, create an OAuth app (ClickUp developer settings).
- Set the redirect URI to your public URL plus the callback path, matching
CLICKUP_OAUTH_REDIRECT_URI, for example:
https://flowanalyzer.example.com/api/integrations/clickup/oauth/callback
- Copy client ID and client secret into
CLICKUP_CLIENT_IDandCLICKUP_CLIENT_SECRETinapps/web/.env. - Set
CLICKUP_OAUTH_REDIRECT_URIto the exact same redirect URI registered in ClickUp. - Restart the app. Org admins can now choose Connect with ClickUp when adding a connection.
Notes
- ClickUp OAuth tokens do not expire and there is no refresh token — revoked tokens show as 401 on connection health checks.
- Personal API token path works without OAuth env vars; org admins paste a token and Team ID in the add-connection dialog.
- Inbound ClickUp webhooks hit your deployment at a URL scoped per connection; signatures use HMAC-SHA256 (
X-Signature). Outbound registration uses the stored OAuth or PAT.
Operator detail: operator runbook — ClickUp connector.
Registering Jira / Atlassian OAuth
Same pattern as ClickUp, but for Atlassian Cloud:
- Create an OAuth 2.0 (3LO) app at developer.atlassian.com.
- Set redirect URI to match
FLOW_ANALYZER_JIRA_OAUTH_REDIRECT_URI, e.g.https://your-domain.com/api/auth/jira-callback. - Set
FLOW_ANALYZER_JIRA_OAUTH_CLIENT_IDandFLOW_ANALYZER_JIRA_OAUTH_CLIENT_SECRET. - Restart the app.
Org admins can still use API token + email without OAuth env vars. Both auth methods work with the Jira configuration wizard and live ingest.
Webhook freshness: Jira webhooks and data freshness.
Registering a Linear OAuth app
Org admins connect Linear through Organization and workspaces → Source connections or the Connect Linear wizard. OAuth requires you to register one Linear OAuth app per Flow Analyzer deployment.
- Create a Linear OAuth app with redirect URI matching
LINEAR_OAUTH_REDIRECT_URI(default path:/api/integrations/linear/oauth/callbackon your public origin). - Set
LINEAR_CLIENT_ID,LINEAR_CLIENT_SECRET, andLINEAR_OAUTH_REDIRECT_URIinapps/web/.env. - Set
FLOW_ANALYZER_PUBLIC_APP_URLso webhook registration uses the correct public origin. - Restart the app.
Personal API key connections work without OAuth env vars; org admins paste a key from Linear settings.
Operator detail: operator runbook — Linear connector.
Trello API key (deployment-wide)
Org admins connect Trello with a personal API token per organization. You must set the deployment Power-Up API key once:
- Create or reuse a Trello Power-Up at trello.com/power-ups/admin and copy the API key.
- Set
TRELLO_API_KEYinapps/web/.env(optionalTRELLO_OAUTH_SECRETreserved for future OAuth). - Restart the app. Org admins paste member tokens from trello.com/app-key when adding a connection.
Inbound Trello webhooks use HEAD then POST to /api/webhooks/trello/{connectionId} on your public origin.
Operator detail: operator runbook — Trello connector.
Egress, air-gap, and SaaS-only connectors
Some connectors talk only to vendor cloud APIs. On an on-prem or air-gapped host, the app probes outbound HTTPS before validation and returns a clear `egress_required` message instead of a long timeout.
Hosts to allow (outbound HTTPS)
| Connector | Egress host | Notes |
|---|---|---|
| ClickUp | api.clickup.com | Catalog, tasks, time entries, webhooks register |
| Asana | app.asana.com | REST API (/api/1.0) |
| Jira Cloud | *.atlassian.net | Per site URL |
| Linear | api.linear.app | GraphQL |
| Trello | api.trello.com | REST |
| ScrumDesk Start | api.scrumdesk.com | Cloud API only |
Jira Data Center on your LAN does not require internet egress when Flow Analyzer is on-prem on the same network.
Jira Data Center connectivity and egress
Self-hosted Jira uses HTTPS REST v2 on your base URL (context paths and ports 443 / 8443 on SaaS). Three topologies are supported — see deployment.md — Jira Data Center connectivity:
| Topology | When to use | Operator notes |
|---|---|---|
| On-prem Flow Analyzer (recommended) | Jira and Flow Analyzer share the customer LAN | Set FA_DEPLOYMENT_MODE=onprem — SSRF guard relaxed for internal targets; webhooks from Jira to Flow Analyzer work when inbound HTTPS reaches the app |
| SaaS + customer-exposed Jira | Hosted Flow Analyzer, Jira published to the internet | Allowlist Flow Analyzer egress IPs (FA_SAAS_EGRESS_IPS or runbook list); only HTTPS ports 443 and 8443; private/metadata IPs blocked |
| Private link / VPN | Sales-assisted enterprise | May still fail SSRF if Jira resolves to RFC1918 from the SaaS egress path — validate with a test connection |
TLS for internal CAs: on-prem installs may trust a custom CA via NODE_EXTRA_CA_CERTS or per-connection CA material in providerConfigJson. FA_ALLOW_INSECURE_TLS=true is on-prem only and logs a warning at every process start — it is not available in SaaS mode.
Unreachable targets: connection validation returns topology guidance linking to deployment.md — not a generic 502. Org admins see the same copy in the UI when a DC URL cannot be reached, with links to this section and Organization and workspaces → Enterprise readiness.
Procurement pack: enterprise-procurement-pack.md — topologies, egress, SSO, version matrix, offline license, clean-room checklist.
Auth stored: PAT and Basic credentials follow the same encryption rules as other source connections (AES-256-GCM at rest, never logged).
Inbound HTTPS (webhooks and OAuth)
Allow inbound HTTPS to your public origin for:
- OAuth callbacks (ClickUp, Asana, Jira Cloud, Linear, …).
- Provider webhooks (
POSTto your deployment's per-connection webhook URLs). - Jira Data Center webhooks when Flow Analyzer is on-prem and Jira can reach your internal or published app URL.
If your network blocks arbitrary egress, document the outbound allow-list for security review. Org admins who try ClickUp or Asana on a host without egress see a message like: "This connector requires internet egress to api.clickup.com" — that is expected; either open the firewall or choose Jira DC / internal sources only.
ClickUp-specific operator notes
From the app host or container, allow outbound HTTPS to:
https://api.clickup.com/api/v2— catalog, tasks, bulk time-in-status, time entries, webhook register/delete.
Rate limiting: the connector defaults to 80 requests/minute per connection (headroom under ClickUp's plan limits). 429 responses trigger bounded retries with Retry-After / X-RateLimit-Reset.
Operator detail: operator runbook — ClickUp connector.
Monitoring and operations
Health endpoints
| Endpoint | Use |
|---|---|
GET /api/health | Process up; includes optional redis status. |
GET /api/ready | Snapshot resolution succeeded — use after deploy when config is stable. |
Connection and job health (operator view)
- Structured logs: successful analysis runs emit
flow_analyzer.analysis_run_completedJSON lines (job id, duration, finding counts). See operator runbook — Structured logs. - Security audit: SaaS sensitive actions append to
audit/saas-security.jsonlunder the data directory. - Tenant jobs: live ingest and rules runs are tracked as
FaTenantAnalysisJobrows; org admins see connection-level errors in Organization settings; you can correlate with DB and logs.
Org-facing connection columns (status, last validated, last webhook) are described in the organization administrator manual.
Performance regression gate
Before major connector or ingest releases, run the NFR harness:
```bash
npm run perf:nfr01
```
Targets and connector regression rules: performance and scalability (NFR).
Scheduled jobs you should configure (SaaS)
| Job | Schedule (typical) | Secret |
|---|---|---|
| Live workspace poll | Every 30–60 minutes | FLOW_ANALYZER_JIRA_POLL_CRON_SECRET |
| Daily usage snapshots | Once per UTC day | FLOW_ANALYZER_USAGE_CRON_SECRET |
| Report delivery | Per product doc | FLOW_ANALYZER_REPORT_CRON_SECRET |
| Data retention | Once per UTC day (recommended) | FLOW_ANALYZER_RETENTION_CRON_SECRET |
Example poll call (replace host and secret):
```bash
curl -sS -X GET 'https://your-host.example/api/cron/jira-live-workspaces-poll' \
-H 'Authorization: Bearer YOURJIRAPOLLCRONSECRET'
```
Release checklist (short)
npm run lintand relevant tests on the release branch.npm run security:auditfor dependency advisories.- Apply migrations on staging before production.
- Verify health, sign-in, and one live ingest path (Jira or ClickUp) after deploy.
Platform admin console (/admin)
After Slice 2, operators with env-configured platform admin credentials can use:
| Route | Purpose |
|---|---|
/admin | Cross-tenant dashboard (orgs, users, workspaces, subscriptions) |
/admin/analytics | Usage trends (registrations, jobs, logins), activation funnel, rule family noise, and in-product funnel |
Scenario prediction calibration (JSON, #174): GET /api/admin/analytics/scenario-calibration?days=90 — platform-admin only; PII-free cohort % inside_band for D5 (≥80% goal). See `doc/quality/scenario-prediction-calibration.md`.
Activation funnel (SaaS onboarding)
For who: Platform operators and customer-success staff using /admin.
Why: Daily registration totals alone do not show where new organizations stall after sign-up. The activation funnel answers “which orgs registered this period never connected a live source / never ran analysis / never got a finding?” so you can outreach before churn.
Where: Open `/admin/analytics`. The Activation funnel panel shares the same 7 / 30 / 90 day preset as usage growth charts. It is separate from the In-product funnel panel (authenticated product events).
What the five stages measure (org cohort = companies with createdAt in the selected range):
- Registered — company row exists in the cohort.
- First workspace — at least one non-deleted workspace.
- First live connection — at least one source connection with
lastValidatedAtset (seed-only without validation does not advance). - First analysis job — at least one tenant analysis job (any status).
- First finding — at least one persisted
AnalysisFindingvia workspace snapshots.
Each stage shows org count and conversion % from the previous stage. Click a stage with count > 0 to open a sortable org list; org names deep-link to /admin/organizations/[id]?tab=… (workspaces for stages 2–3, overview for stages 4–5). Zero-count stages show explanatory empty copy (not an error).
Org overview health: On organization detail Overview, the health strip shows member count, validated connection summary (and last error snippet when present), last analysis job status, and a sortable recent-jobs table. Use `?tab=billing` to open the billing tab directly.
Rule family noise (diagnosis quality)
For who: Platform operators and customer-success staff using /admin (not a customer Findings surface).
Why: After coaches dismiss noisy findings with a reason, operators still could not answer “which catalogue families fire the most and get dismissed the most across tenants?” This panel turns that into aggregate evidence for diagnosis-quality work and catalogue tuning — without reading tenant PII.
Where: Open `/admin/analytics`. The Rule family noise panel sits between usage/activation charts and the In-product funnel. It has its own 7 / 30 / 90 day preset chips and optional organization / workspace filters. It does not change the Activation or In-product funnel panels.
What the metrics mean:
| Metric | Definition |
|---|---|
| Workspaces | Non-deleted workspaces whose latest persisted snapshot persistedAt falls in the selected range (after optional org/workspace filters). |
| Fires | AnalysisFinding rows on those latest snapshots, rolled up by ruleFamilyFromRuleId (prefix before -). |
| Dismissals | Active finding-triage rows with status wont_fix for the same workspace scope. |
| Dismissal rate | dismissalCount / fireCount (shown as “—” when fires are zero). |
| Top 10 table | Families ranked by fire volume (ties: higher dismissal rate, then family code). Optional dismiss-reason counts only. |
Privacy: Aggregate family codes, labels, counts, rates, and reason-code counts only. The UI and GET /api/admin/analytics/rule-family-noise JSON never include issue keys, people labels, emails, or dismiss notes. Use Copy JSON / Download JSON for the same payload as the table.
Empty / error: No snapshots in scope → explanatory empty copy. Load failure → retry message (no stack traces). Non–platform-admin callers receive 401/403.
/admin/support | Live support inbox (org users via /api/support/conversations) |
|---|---|
/admin/satisfaction | In-app survey responses and NPS-style charts |
/admin/engagement | Customer-success Kanban by lifecycle stage |
/admin/public-try | Anonymous demo workspace caps and audit |
/admin/connectors | Deployment OAuth env status (Jira, ClickUp, Asana, Linear, Trello) |
Apply migration `20260725130000_platform_admin_slice2` before using support/survey tables (operator approval required). For self-serve registration billing profile inserts, also apply `20260725140000_registration_billing_profile_insert`.
Freemium organizations only run the curated rule subset documented in `doc/reference/FREEMIUM_RULE_TIER.md`; consultant and enterprise plans use the full catalogue at analysis time.
What platform administrators do not do
To set expectations with customers:
- You do not pick ClickUp lists, Jira projects, or JQL scope — org admins do that in wizards and workspace settings.
- You do not run analysis for end users or interpret findings — that is delivery/coaching work inside the org.
- You do not manage org membership (except when you operate a single-tenant internal pilot).
- You do not store org API tokens in tickets or chat — org secrets stay encrypted in Postgres; deployment secrets stay in your secret manager.
When someone reports "my workspace has no findings," triage connection health and failed jobs with them using the organization administrator manual; escalate to engineering only after confirming deployment version, migrations, and cron health.
Related documentation
| Audience | Document |
|---|---|
| Customer org admins | organization-administrator-manual.md |
| All SaaS users (coaches, analysts) | user-manual-saas.md |
| Deploy mechanics | deployment.md |
| Day-2 operations | operator-runbook.md |
| Tenant job runner | saas-tenant-analysis-job-runner.md |
Organization billing reference (#196)
Who: Platform operators matching invoices to CRM / AP records.
Why: Org admins can leave an optional purchase-order or customer reference at Create organization or under Organization and workspaces → Organization.
Where: Organization detail → Overview (/admin/organizations/[companyId]) → Organization profile → Billing reference. The value is the same company field (not a Stripe customer id); edit with the existing details save.
Billing dunning and soft org suspension (#153)
Who: Platform operators and customer success using `/admin`.
Why: Surface overdue invoices and pause unpaid orgs without hard-blocking login (abuse path stays separate).
Where: Organization detail → Billing tab (/admin/organizations/[companyId]?tab=billing).
| Action | Effect |
|---|---|
| Overdue alert | Shows when any invoice’s effective status is overdue (count, oldest due date, last billing audit event or empty copy). |
| Suspend (AlertDialog) | Sets billing profile subscriptionStatus to suspended. Members can still sign in; a persistent banner explains limited access. Analysis enqueue and seat-consuming invites/SSO JIT return 403 with error_code: organization_suspended. Existing product data remains readable. Does not set FaCompany.isBlocked. |
| Restore (AlertDialog) | Restores prior subscription status from the last suspend audit metadata. Banner clears on the next navigation; analysis enqueue works again (subject to other entitlements). |
| Audit | Each suspend/restore writes FaBillingEvent (billing.access.suspended / billing.access.restored) and a platform-admin audit row. Soft-suspend never deletes workspace snapshots or invoices. |
Contrast — hard block: Organization Block on the overview/details form sets isBlocked and denies credentials login (/account-blocked). Use hard block for abuse/security; use soft-suspend for unpaid billing.
Operator checklist (non-production first): Suspend a test org → confirm member banner + analysis 403 → Restore → confirm banner gone and enqueue allowed.
Schedule organization deletion (#265)
Who: Platform operators on `/admin`.
Why: Offboard customers, remove demo tenants, or honour erasure requests without joining the org as admin or running manual SQL.
Where: Organization detail → Overview tab (/admin/organizations/[companyId]) → Delete organization danger zone.
| Step | What happens |
|---|---|
| Open danger zone | Read grace-period copy: workspaces, connections, and billing data are removed after the grace window; member user accounts are kept. |
| Confirm in AlertDialog | Type the organization name exactly, then confirm. |
| After success | You return to the organization list; the org no longer appears among active tenants. |
Semantics: Same soft-delete as org-admin self-serve (Privacy & data → Delete organization): deleted_at, purge_after, and is_blocked are set; open invoices are voided; active Stripe subscriptions are canceled when billing is enabled. Hard purge runs via the data-retention cron after the grace period (default 90 days). See operator-runbook.md — organization soft-delete.
Operator checklist (test org only): Delete a disposable org → verify it disappears from /admin/organizations → confirm deleted_at / purge_after on the company row if you have read-only DB access.