depictio ↔ Spectio harmonisation — code audit and proposal¶
Source: depictio checkout at scratchpad/dep (paths below are relative to dep/depictio/ unless they start with packages/). Read on 2026-10-25.
1. What depictio does today¶
1.1 Identity and authentication¶
- User (
models/models/users.py):UserBase=email,is_admin,is_anonymous,is_temporary,expiration_time;UserBaseUIaddsis_active,is_verified,last_login,registration_date,display_name,title,login,sso_provider,must_change_password;Useraddspassword. Stored with Beanie (UserBeanie, MongoDB). - Group (
models/models/users.py, classGroup):name,users_ids,admin_ids,pi_id(principal investigator),pending_pi_email,sso_managed. Admins and the PI are always members (validator_admins_and_pi_are_members). - Sysadmin: a single boolean
is_adminon the user; sysadmins bypass every permission (api/v1/permissions.pydocstring). -
Auth modes (
api/v1/configs/settings_models.py,AuthConfig, env prefixDEPICTIO_AUTH_):single_user_mode: "Grants admin privileges to the anonymous user for full functionality without authentication"; the anonymous user email defaults toanonymous@depict.io(anonymous_user_email).public_mode(aliasunauthenticated_mode): visitors get temporary users (temporary_user_expiry_hours), optionalpublic_access_code.demo_mode: public mode plus guided tour.registration_disabled,password_login_disabled.- The UI asks
GET /auth/me/optional-style info returningauth_mode ∈ {single_user, unauthenticated, standard}(api/v1/endpoints/user_endpoints/routes.py~l.633–703).
-
Login methods: email + password (
POST /auth/login), Google OAuth (/auth/google,google_oauth_*settings), SAML 2.0 SSO tested with Keycloak (/auth/saml,saml_*settings,api/v1/endpoints/auth_endpoints/saml_routes.py), magic links (MagicLinkTicket,POST /auth/me/magic_link,/auth/magic/exchange). No generic OIDC provider. - SSO group sync:
saml_attribute_groups(e.g.memberOf) mirrorssso_managedgroups on each login (api/v1/endpoints/auth_endpoints/sso_sync.py). - Tokens (
models/models/users.py,TokenData/TokenBase): JWT (sub= user ObjectId,exp, signed RS256 by default with keys inkeys_dir,AuthConfig.keys_algorithm),token_lifetime ∈ {short-lived, long-lived, permanent}, access + refresh tokens, optionalname. Users create named long-lived CLI tokens withPOST /auth/me/tokens(user_endpoints/routes.pyl.1260). - Service-to-service:
internal_api_key(auto-generated) and a separateprovisioning_api_keyused by pipelines to create-or-get a passwordless user and obtain a long-lived run token (POST /auth/provision_user,routes.pyl.327). - CLI config (
models/models/cli.py,CLIConfig): YAML withapi_base_url,user(email,is_admin,id,token…),s3_storage(MinIO/S3 credentials for direct Delta writes),instance_label,remote_storage_options. Default path~/.depictio/CLI.yaml; env overridesDEPICTIO_CLI_TOKEN,DEPICTIO_CLI_API_BASE_URL,DEPICTIO_CLI_CONFIG_PATH(documented incli/configs/nextflow/depictio.config). Generated byPOST /auth/generate_agent_config, which includes the S3 secret (restricted to admins in single-user/public modes,routes.pyl.1422).
1.2 Tenancy and permissions¶
- Project (
models/models/projects.py):name,data_management_platform_project_url(link to an external data-management platform),workflows,data_collections,joins,links,yaml_config_path,permissions,is_public,project_type ∈ {basic, advanced},realtime,template_origin,registration_time,hash. - Permission (
models/models/users.py, classPermission), embedded in each permission-carrying document (projects, dashboards, workflow runs…):owners,editors,viewers(viewers may contain the wildcard"*"), plusgroup_owners,group_editors,group_viewers. One direct role per user and one role per group. - Enforcement (
api/v1/permissions.py): three levelsviewer < editor < owner; effective role = highest of direct role and any group role;is_public/"*"give a viewer floor; anonymous users see only public documents; group membership resolved live from thegroupscollection; helpersbuild_access_query,has_permission,resolve_effective_roleused by every endpoint.
1.3 Workflows, runs and data¶
- Workflow (
models/models/workflows.py):name,engine(name,version),version,catalog(name,url),workflow_tag,repository_url,data_collections,runs(dict ofWorkflowRun),config(WorkflowConfig:workflow_parameters,engine_name,pipeline_version,nextflow_version,tools_executed),data_location(structure,locations,runs_regex). - WorkflowRun (same file):
workflow_id,run_tag,files_id,workflow_config_id,run_location,creation_time,last_modification_time,registration_time,run_hash,scan_results,permissions. It is a results directory registered for dashboards, not an execution. - WorkflowRunInfo (
models/models/run_info.py): engine-agnostic provenance read from a results directory (engine,pipeline_name,pipeline_version,engine_version,run_name,params,tools_executed, paths to software versions, params JSON, execution report/trace, DAG). Readers are pluggable connectors (Nextflow reader inmodels/models/nextflow.py,NextflowRunInfoReader); used bydepictio ingest <results dir>to auto-select a template. - Jobs (
models/models/jobs.py):JobState = pending | running | success | failed | cancelled,JobKind = deltatable.upsert | project.ingest, withidempotency_key,project_id, progress — background ingestion jobs (Celery). - Data collections (
models/models/data_collections.pyanddata_collections_types/: table, image, multiqc, jbrowse, geojson, indexed_file, phylogeny…), scanned withScanRecursive(regex + wildcards),ScanSingle,ScanURL,ScanS3Prefix,ScanManifest. - Templates and catalog: per-pipeline, per-version project templates for ~35 nf-core pipelines (
projects/nf-core/<pipeline>/<version>/template.yaml, e.g.projects/nf-core/rnaseq/3.26.0/template.yamlwithtemplate_id: nf-core/rnaseq/3.26.0, variablesDATA_ROOT,SAMPLESHEET_FILE, skip flags, bundled dashboards). A tool catalog maps nf-core outputs → recipes → dashboard components (catalog/README.md, one folder per module). - Ingestion CLI (
cli/cli/commands/run.py,ingest):depictio ingest DATA_DIRwith--template,--project-config-path,--manifest,--project,--attach-run(add a run to an existing project),--update-config,--pipeline-id nf-core/ampliseq/2.16.0,--triggered-by("a pipeline's completion trigger passes its engine"),--user+--provisioning-key(run as a provisioned user, emit a login link),--provenance-file,--result-json(project/dashboard ids and links for scripts),--compose(build from the catalog when no template fits). - Existing Nextflow completion trigger:
cli/configs/nextflow/depictio.configinstalls aworkflow.onCompletehandler that runsdepictio-cli ingestonparams.outdir(paramsdepictio_enabled,depictio_template,depictio_project,depictio_attach,depictio_user, …). No Snakemake equivalent was found. - Storage: MongoDB via Beanie (
MongoDBConfig), S3/MinIO + Delta tables for data (S3DepictioCLIConfig,models/models/deltatables.py), Redis cache and Celery workers (settings_models.py~l.794–827). - IDs: MongoDB ObjectId (
PyObjectIdinmodels/models/base.py). - API: prefix
/depictio/api/v{major}(api/main.pyl.264,version.pyget_api_version), routers/projects,/workflows,/runs,/datacollections,/files,/deltatables,/dashboards,/groups,/auth,/events,/jobs, … (api/v1/endpoints/routers.py). Routes are partly verb-style (/runs/list/{workflow_id},/runs/get/{run_id},/runs/upsert_batch).
1.4 Events and external hooks¶
- WebSocket
/events/wswith the JWT passed as a query parameter (api/v1/endpoints/events_endpoints/routes.py);EventMessagecarriesevent_type,source_type,project_id,dashboard_id,data_collection_id,payload(models/models/realtime.py); projects opt in withRealtimeConfig. - No outgoing webhooks were found (the only "webhook" match is a secret-redaction regex in
api/v1/endpoints/projects_endpoints/run_folders.py). - External automation relies on the provisioning key, long-lived tokens and the CLI.
1.5 Front end¶
- Shared React core
packages/depictio-react-core(Mantine theme inbrandTheme.ts, chrome variants). - Instance branding (
models/models/branding.py,BrandTheme):app_name, logo modes,primary/secondary/tertiary,success/warning/danger,tint_mode, light and dark surfaces, plot colorway, fonts, radius.brandTheme.tsmaps primary → Mantineblue, secondary →teal, tertiary → palettebrandTertiary. - Section colours (
viewer/src/chrome/AppSidebar.tsx): Dashboards = orange (role tertiary), Projects = teal (secondary), Administration = blue (primary), About = gray. - Admin (
viewer/src/admin/AdminApp.tsxl.74–103): nav groups People (Users, Groups), Content (Projects, Dashboards), Activity (Tasks, Ingestion, Watchers, Logs, Health), Instance (Branding, Backups, Maintenance, Settings). - Profile (
viewer/src/profile/ProfileApp.tsxl.642–653): Email, Name, Title, Role (Administrator/User), Database ID, registration date, last login, avatar, CLI tokens. - Project permissions (
viewer/src/projects/detail/PermissionsApp.tsxl.842–1042): sections Visibility (public toggle), Members (role per email), Groups (role per group). - Groups (
viewer/src/groups/GroupsApp.tsx).
2. Harmonisation proposal¶
(a) Identity¶
- One identity provider for both. depictio speaks SAML 2.0 (validated against Keycloak) and Google OAuth, not OIDC. Spectio should support the same SAML configuration keys (or put Keycloak in front as a broker that offers SAML to depictio and OIDC/SAML to Spectio). Same IdP, same
memberOfattribute → the same SSO-managed groups appear in both apps with the same names. - Co-deployed phase (before SSO): depictio is the identity source. Spectio accepts depictio JWTs (verify the RS256 signature with depictio's public key, or call
GET /depictio/api/v1/auth/me), creates or updates a principal keyed bydepictio:user/<ObjectId>with the same email, and mirrors groups (users_ids,admin_ids,pi_id) on demand. One login, one token for a user's browser. - Service-to-service: Spectio holds a depictio service identity (provisioning key, or a long-lived token of a depictio service user) and uses
--user/POST /auth/provision_userto ingest as the run's owner, so dashboards land in that person's projects. - Single-user: depictio single-user mode = anonymous admin (
anonymous@depict.io); Spectio single-user = local principal. When both run in single-user mode, Spectio maps its local user to depictio's anonymous admin; no tokens are needed on localhost.
(b) Entity mapping¶
| Spectio | depictio | Notes |
|---|---|---|
Principal (kind user) |
User |
match on email; store depictio:user/<id> as an identity link |
Principal (kind service/agent) |
none (provisioning key / long-lived token of a service user) | depictio has no service-account model |
| Instance admin | User.is_admin |
same meaning (bypass) |
| Group, member / admin | Group.users_ids / admin_ids |
add pi to Spectio group roles to match pi_id; keep sso_managed |
| Project | Project |
1:1 link through external_refs (depictio:project/<id>); Spectio project visibility instance ↔ is_public |
| Grant role viewer / owner | viewers / owners |
direct mapping |
| Grant roles launcher, reviewer, maintainer | editors |
depictio editor ↔ Spectio maintainer by default; launcher and reviewer exist only in Spectio |
| Group grant | group_owners / group_editors / group_viewers |
same semantics (highest role wins) |
| Workflow + version | Workflow (engine, version, catalog, repository_url) |
depictio template id nf-core/<name>/<version> = Spectio workflow version key |
| Run (execution) | WorkflowRun (results directory) + ingestion Job |
one Spectio run → one depictio WorkflowRun after ingestion; run_tag = Spectio run ID |
| Artifact (report, output) | DataCollection + files |
depictio decides the data collections from its template |
| Run provenance / RO-Crate | WorkflowRunInfo + --provenance-file |
pass Spectio's run summary as the provenance file |
| Event | EventMessage (WebSocket) |
different transports; see (c) |
(c) Run → dashboard hand-off¶
depictio already supports pipeline-triggered ingestion (depictio.config + depictio ingest --pipeline-id --triggered-by --attach-run --user --result-json). Spectio should drive it rather than invent a new path:
-
In
spectio.yml(or the workflow's Spectio settings), an optional block:depictio: on: completed # or: gate:<id>, stage:<n> template: auto # auto = pick by pipeline id (nf-core/rnaseq/3.27.0); or an explicit template id project: "{project.name}" attach_run: true # one depictio project per Spectio project, one run per Spectio run -
When the run completes, the runner (which already has file access next to the results) runs
depictio-cli ingest <outdir> --pipeline-id <workflow>/<version> --triggered-by spectio --project <name> --attach-run --user <owner email> --provenance-file <spectio run summary> --result-json <file>, with credentials from the server (provisioning key or service token). - The runner returns the result JSON; the server stores
depictio:project/<id>and dashboard links on the run, shows "Open in depictio" in Run detail, and emitsspectio.run.dashboard_ready. - Spectio disables depictio's own Nextflow
onCompletetrigger for runs it launches (--depictio_enabled false) to avoid a double ingestion. For Snakemake, Spectio provides the trigger that depictio lacks. - Later, a server-side ingestion call (depictio
Jobkindproject.ingest) can replace the CLI call when depictio can read the storage directly.
(d) IDs and naming¶
- Keep each side's native IDs (ObjectId in depictio, prefixed ULIDs in Spectio) and link them with
external_refsboth ways (depictio:project/…,spectio:run/…). - Use the same pipeline identifier string everywhere:
<org>/<name>/<version>(depictio template ids and--pipeline-idalready use it). - Role vocabulary: expose depictio-compatible role names in Spectio's sharing API (
owner,editor,viewer) and treatlauncher,revieweras Spectio capabilities layered oneditor; or map as in (b). Decide once. - API paths for co-hosting on one domain: depictio is
/depictio/api/v1; mount Spectio at/spectio/api/v1(and the UI at/spectio/) so both live behind one reverse proxy and share cookies/SSO. - CLI: both CLIs keep their own config (
~/.depictio/CLI.yaml,~/.spectio/config.yaml), butspectio logincan reuse an existing depictio token when the server is configured to trust depictio.
(e) UI consistency¶
- Same Mantine theme and same branding model: Spectio reads the instance
BrandTheme(primary / secondary / tertiary, surfaces, fonts, radius) when co-deployed, so an institute brands both apps once. - Align section colours: depictio uses Projects = teal (secondary), Dashboards = orange (tertiary), Administration = blue (primary). Spectio's own menus (Runs, Review, Launch, Workflows, Environments) should use colours distinct from depictio's, except Projects/Settings, which should match. Today Spectio's Workflows orange (
#C9771A) is close to depictio's Dashboards orange: to revisit. - Mirror depictio's admin structure in Spectio Settings: People (Users, Groups, Service accounts), Content (Projects, Workflows, Environments), Activity (Runs, Events, Logs, Health), Instance (Branding, AI & agents, Webhooks, Settings).
- Same Profile fields (email, name, title, role, ID, dates, avatar, tokens) and the same project-permissions screen layout (Visibility, Members, Groups).
- An app switcher in the top bar (depictio ↔ Spectio) and deep links both ways (run → dashboard, dashboard → producing run).
(f) Gaps and risks¶
- Databases differ (MongoDB vs SQLite/Postgres): no shared tables; integration only through APIs, tokens and identity links. Keep it that way.
- No OIDC in depictio: Spectio's planned OIDC must be complemented by SAML, or both put behind Keycloak.
- Role mismatch (3 levels vs 5 roles) needs an explicit mapping, otherwise a depictio editor may get launch rights unintentionally.
- Single-user semantics differ (anonymous admin vs named local user): only matters when both run together; map explicitly.
- Secrets: depictio's agent config contains the S3 root secret (
generate_agent_config); Spectio runners must not receive it unless needed for ingestion, and should use a scoped token instead where possible. - depictio API style is partly verb-based with deprecated routes; Spectio should wrap the few calls it needs (
/auth/me,/auth/provision_user,/projects, ingestion) in one adapter plugin (output sink "depictio"). - Event transports differ (depictio WebSocket only, Spectio SSE + webhooks): bridge only what is useful (Spectio
run.dashboard_ready→ depictio project refresh event).