Skip to main content

Hub Architecture

Hub is the platform for team deployments. It manages accounts and roles, shared connections, workflow storage, schedules and an audit log, and it authorizes what Servers do on a user's behalf. Hub is in open beta. See Hub Open Beta for its limits.

Technology stack​

LayerTechnologyWhy
BackendFastAPIAsync, OpenAPI docs, dependency injection
DatabasePostgreSQL with SQLAlchemy 2.0 (async)ACID, migrations with Alembic
SchedulingAPScheduler (in process)Cron schedules without another service
FrontendReact with Vite and ZustandSame patterns as Studio
Password hashingArgon2idMemory-hard hashing
Credential encryptionFernet (cryptography)Authenticated symmetric encryption

Directory structure​

hub/
|-- backend/
| |-- app/
| | |-- main.py App, startup checks, background tasks
| | |-- config.py Settings (environment variables)
| | |-- api/
| | | |-- dependencies.py CurrentUser, RequireUser, RequireAdmin, RequireAuditor
| | | `-- v1/routes/ auth, users, groups, grant_groups, connections,
| | | servers, workflows, executions, schedules,
| | | capabilities, internal, audit, health
| | |-- db/
| | | |-- models.py SQLAlchemy models
| | | `-- migrations/ Alembic versions
| | |-- domain/
| | | |-- auth/ passwords, roles, sessions, server-token store
| | | |-- capabilities/ Issues Server tokens
| | | |-- connections/ Encrypted store, health poller
| | | |-- servers/ Server client, health poller
| | | |-- workflows/ File store, parser, revisions
| | | |-- executions/ Service, in-memory event broker
| | | |-- schedules/ Scheduler and service
| | | |-- artifacts/ Content-addressed output storage
| | | `-- audit/ Hash-chained audit events
| | `-- middleware/ Rate limiting
| |-- scripts/ Dev seed scripts
| `-- tests/
`-- frontend/
`-- src/
|-- pages/ User pages, plus pages/admin/ for administrators
|-- components/ Layout, route guards, schedule builder
|-- api/client.ts API client
`-- state/ Auth and settings stores

API​

All routes are under /api/v1. GET /api/v1/health needs no authentication. Every other route needs a session (Authorization: Bearer <session token>), except registration of the first account and login. Interactive docs are served at /docs.

ResourceRoutesRoles
Auth/auth/register, /auth/login, /auth/logout, /auth/me, /auth/statusPublic, then any user
Users/users and /users/{id} (with /groups)Auditor reads, Admin writes (within role rules)
User groups/groups, /groups/{id}/members, /groups/{id}/grant-groupsAuditor reads, Admin writes
Grant groups/grant-groups, /grant-groups/{id}/connections, /grant-groups/{id}/user-groupsAuditor reads, Admin writes
Connections/connections, /connections/{id}/check, /connections/testAdmin
Servers/servers, /servers/{id}/refresh, /servers/{id}/groupsAdmin writes, Auditor reads, self-registration by secret
Workflows/workflows, /workflows/{id}/content, /download, /revisions, /diffUser owns, Admin deletes, Auditor reads
Executions/executions, /executions/stream, /executions/{id}/cancel, /artifacts, and auditor-only /record, /code, /plans, /connections, /logsUser creates and reads own
Schedules/schedules, /schedules/{id}/toggle, /schedules/{id}/triggerUser owns
CapabilitiesPOST /capabilities/tokenAny authenticated user
InternalPOST /internal/authorizeServers (by their auth_token)
Audit/audit/events, /audit/verifyAuditor and above

Permission model​

Access to connections flows one way:

Users -> User Groups -> Grant Groups -> Connections
TablePurpose
usersAccounts, with a role
groupsUser groups
user_groupsJunction: users and groups
grant_groupsReusable permission bundles
user_group_grant_groupsJunction: groups and grant groups
grant_group_connectionsJunction: grant groups and connections
connections, connection_databases, connection_filesystemsConnection metadata and type-specific settings, with encrypted credentials
servers, server_groupsRegistered Servers, and which groups can see them
workflows, workflow_revisionsWorkflow metadata, and stored revisions
executions, execution_records, execution_artifactsRuns, their audit records and stored outputs
schedulesCron schedules
sessions, server_tokensLogin sessions, and Server tokens when TOKEN_STORE_BACKEND=postgres
audit_eventsThe hash-chained audit log

A single audit path answers "why can user X use connection Y?". Grant groups cannot contain grant groups or users, so cycles are impossible. Assignments are made from the user-group side only. The grant-group side shows them read-only.

Roles​

domain/auth/roles.py defines owner, admin, auditor and user with a hierarchy. Route dependencies in api/dependencies.py:

DependencyAllows
CurrentUserAny valid session
RequireAuditorOwner, Admin, Auditor
RequireAdminOwner, Admin
RequireUserThe user role only (not Admin or Owner)
@router.post("/grant-groups")
async def create_grant_group(
request: GrantGroupCreate,
admin: SessionInfo = RequireAdmin,
db: AsyncSession = Depends(get_db),
): ...

can_manage_role and can_assign_role implement who may create, edit and delete whom. See Roles and Permissions.

Authorization flow​

Studio requests a token at POST /capabilities/token. CapabilityIssuer picks an online Server, creates an opaque token in the token store (memory or postgres) with the user ID, the Server ID and a TTL, and returns it with the Server's endpoint. When the Server calls POST /internal/authorize, Hub authenticates the Server by its auth_token, atomically consumes the token, checks the user's grants for every requested connection, resolves the credentials and file paths, and writes an audit event. See Component Relationships.

Background work​

Everything below runs inside the Hub process, started in the app's lifespan hook in main.py:

TaskDoes
Server health pollerChecks every Server each SERVER_POLL_INTERVAL_SECONDS, with backoff and an offline threshold
Connection health pollerTests every connection about hourly
SchedulerAPScheduler polls due database occurrences every second; a schedule row lock serializes occurrence advancement and execution insertion
Session cleanupDeletes expired sessions in batches
Token store cleanupDrops expired in-memory tokens
Execution tasksOne asyncio task per execution, started by ExecutionService.trigger_execution; heartbeat every 15 seconds
Execution recoveryStartup and 60-second sweeps mark stale queued/running records failed; runners are cancelled on shutdown
Event brokerIn-memory fan-out of execution events to Server-Sent Events clients

Execution records and next schedule occurrences survive in PostgreSQL. A dispatcher locks a due schedule with FOR UPDATE SKIP LOCKED, uses the database clock, and advances its next occurrence in the same transaction that inserts the execution. A failed uncommitted claim can be retried by another process; overdue intervals are coalesced into one attempt. This prevents duplicate schedule dispatch, but does not make the execution runner durable: a restarted Hub marks stale records failed and cannot resume their Server requests. The in-memory event broker and default token store still require one Hub replica.

Frontend​

The Hub frontend is a Vite React app served by the backend in production (STATIC_FILES_PATH). Users see Home, Workflows, Executions and Schedules. Auditors and above see an admin area.

PageFile
Dashboardpages/admin/DashboardTab.tsx
Activitypages/admin/ActivityTab.tsx
Audit Logpages/admin/AuditTab.tsx
Serverspages/admin/ServersTab.tsx
Users, User Groups, Grant GroupsUsersTab.tsx, GroupsTab.tsx, GrantGroupsTab.tsx
Connectionspages/admin/ConnectionsTab.tsx
Settingspages/admin/SettingsPage.tsx (a placeholder)

api/client.ts re-exports typed endpoint functions from resource modules in api/. state/authStore.ts keeps the session. There are three themes (Sigilweaver, Dark and Light) driven by semantic CSS variables.

Testing​

Backend tests are in hub/backend/tests/, grouped by area: authentication and roles, connections and permissions per role, servers, workflows, schedules, executions, artifacts, the token store, rate limiting and health polling.

cd hub/backend
uv run pytest -v
uv run pytest tests/test_grant_group_permissions.py -v

Frontend tests use Vitest: cd hub/frontend && bun run test. ./scripts/test.py --hub runs both, plus lint and type checks.

A typical permission test creates a user, a group, a grant group and a connection, links them, and asserts that the user can list the connection through that path and not without it.