Audit log
Every mutating control-plane action writes a row tocontrol.audit_log (actor type, actor id, tenant, action, resource, detail, timestamp); every mutating in-tenant action writes to that tenant schema’s own audit_log table, including actor email, session, IP, and detail. Impersonation is always logged, unconditionally. See Remediation for the tenant-facing GET /remediation/audit-log endpoint.
CAP (Corrective Action Plan)
A row in a tenant’scaps table: a remediation action tied to a target_type/target_id, with a title, an owner, a due date, and a status that moves through a fixed transition graph — proposed -> approved -> in_progress -> blocked/completed -> verified/rejected (services/api/remediation_api.py). CAPs are exposed and mutated through GET/POST /remediation/caps and PATCH /remediation/caps/{cap_id}, and exportable as GET /remediation/export.xlsx. See Remediation.
Checkpoint
An immutable, versioned row in a tenant’smodel_checkpoints table, produced by a train_tenant_model platform job. Records the clustering algorithm, embedding provider, metrics, and an is_active flag. See Model checkpoint and Training and checkpoints.
Control plane
Thecontrol PostgreSQL schema and its Alembic branch (control): employees, tenants, tenant_invitations, audit_log, platform_jobs, runtime_heartbeats. One shared copy across the whole platform, owned by the onboarding portal backend (services/api/onboarding_api.py). Contrast with tenant schema.
CSRF (double-submit cookie)
Cross-site request forgery protection shared by both auth surfaces (services/api/auth/csrf.py): a non-httponly cl_csrf-style cookie whose value must also be echoed on mutating requests. Login and invite-accept routes are exempt (there is no session yet to protect); every route acting on an existing session enforces it via require_csrf.
Data policy
A tenant’s residency (US/EU/APAC), retention (5 years/7 years/10 years), and PII-redaction preference, set through PUT /admin/tenants/{tenant_id}/data-policy and stored in that tenant’s settings row. See Data policy.
Employee
A Causeloop staff account incontrol.employees, with a control_role of onboarding_admin (can execute onboarding mutations) or support_readonly (view-only). Created only by the idempotent scripts/create_employee.py — there is no self-signup. Employees authenticate through /admin/login and carry the cl_admin_session cookie.
Impersonation
POST /admin/tenants/{tenant_id}/impersonate: mints a real tenant session for a live tenant’s earliest-created active user, letting an onboarding admin see exactly what that tenant’s own admin sees. Treated as a real access grant, not a preview — always audit-logged. See Invitations and impersonation.
Ingest job
A row in a tenant’singest_jobs table recording one connector sync’s status, stage, and progress — the tenant-scoped counterpart to a control-plane platform_jobs row, tracked per source rather than globally.
Insight collection
One materialized analytical output for a tenant, stored ininsight_collections and keyed by (tenant_id, collection, model_version, dataset_version). Five collections are model-backed (always attempted); five are LLM-backed (skipped, not failed, when no LLM is configured), of which one (cause_remediation) is export-only, leaving four LLM-backed collections a tenant can actually see in its snapshot. See Insights console.
Insight snapshot
The coherent bundle a tenant actually sees:GET /insights/snapshot selects one (dataset_version, model_version) pair and returns every insight collection materialized for that pair together, so a page load never mixes collections from two different pipeline runs.
Invitation
A row incontrol.tenant_invitations: email, target role-profile key, hashed single-use token, and status (pending, accepted, revoked, expired). Created by POST /admin/tenants/{tenant_id}/invite; consumed by POST /accept-invite.
Lifecycle state
A tenant’s current stage, held incontrol.tenants.lifecycle_state and constrained by a CHECK constraint to exactly: draft, provisioning, onboarding, training, review, live, suspended, offboarded. See the lifecycle diagram.
Materialization
The act of computing a tenant’s insight collections and writing them toinsight_collections, run as a materialize_insights platform job whenever a connector sync produces a new dataset version. Implemented in services/pipeline/materialize.py, reusing the same *_from_data computation functions the legacy file-based client API used.
Outbox (email outbox)
The pattern behindsend_email platform jobs: an invitation, password-reset, or notification row and its encrypted email job commit in the same PostgreSQL transaction as the action that triggered them. The product-worker claims the job afterward, decrypts it, and delivers over SMTP — no API request ever calls an email provider inline after committing authentication state.
Platform job
A row incontrol.platform_jobs: job_type (provision_tenant, train_tenant_model, materialize_insights, send_email), status (queued, running, succeeded, failed, cancelled), progress, and steps_completed. Claimed by a worker with FOR UPDATE SKIP LOCKED, retried with bounded backoff, recovered on lease expiry. See Workers and jobs.
Provisioning
Thelifecycle_state a tenant is in while services/api/tenancy/provisioner.py creates its schema, runs tenant_template migrations into it, grants the app_rw role, seeds the four default role profiles, and verifies the durable object store is reachable (_verify_object_storage) — this last step is a hard gate, not best-effort: provisioning fails with a RuntimeError if the configured object store isn’t ready. The storage_prefix value itself is assigned at tenant creation, not by this step. Idempotent and resumable — progress is tracked in control.platform_jobs so a crash mid-provision is safe to resume.
Role profile
A row in a tenant’srole_profiles table: a key (for example org_admin, risk_admin, analyst, viewer), a label, and a JSON list of permission strings — data-driven permissions, not hardcoded enums. require_permission("sources.manage") and similar FastAPI dependencies read this list off a tenant user’s assigned profile.
Review
A row in a tenant’sreviews table: a target_type/target_id, a decision (approved, rejected, escalated), notes, and the reviewing user. Exposed through GET /remediation/reviews and PATCH /remediation/reviews/{review_id}. See Remediation.
Source
A configured data connector for a tenant — a row insources with a kind, name, JSON config, and credentials encrypted via the configured secret provider (CAUSELOOP_SECRET_PROVIDER: Fernet locally, AWS KMS or Azure Key Vault in production) — never echoed back, only a last-4-style hint. See Sources.
Tenant
One customer organization: a row incontrol.tenants plus its own tenant_<slug> PostgreSQL schema and object-storage prefix. See Tenant.
Tenant schema
The privatetenant_<slug> PostgreSQL schema created once per tenant by the provisioner and migrated by the tenant_template Alembic branch. Holds tables including users, user_sessions, user_roles, role_profiles, sources, datasets, records, ingest_jobs, model_checkpoints, llm_settings, insight_collections, reviews, caps, settings, pii_field_policies, password_reset_tokens, audit_log, source_uploads — each with a tenant_id column and FORCE ROW LEVEL SECURITY on top of the schema boundary itself.
TenantSession / ControlSession
The only way a FastAPI handler gets a database connection scoped to a specific tenant or the control plane.get_tenant_session (services/api/tenancy/deps.py) yields a TenantSession, always resolved from a TenantRecord, never a bare tenant-id string, so an unscoped connection cannot accidentally read across tenants. get_admin_session yields a control-plane ControlSession (services/api/db/session.py) directly — there is no tenant to resolve.
Workspace
The end-user-facing name for a tenant.GET /workspaces is the public, pre-login directory of live tenants (slug, name, industry only) used by the login page’s workspace picker.