Admin API
The Admin API is the only runtime write surface for workflow authoring and publication. It is implemented in src/rimthan_admin_api/server.py and exposed by the rimthan-admin-api console script. The participant MCP server never exposes these writes; see Participant Architecture.
Configuration and middleware
AdminApiConfig.from_environment requires:
RIMTHAN_ADMIN_PUBLIC_URL: exact HTTPS origin with no path extras;RIMTHAN_ADMIN_DATABASE_URL: PostgreSQL URL;RIMTHAN_ADMIN_BEARER_TOKEN: at least 32 non-whitespace characters;RIMTHAN_PUBLICATION_SIGNING_KEY_ID;RIMTHAN_PUBLICATION_ED25519_PRIVATE_KEY_B64: raw 32-byte Ed25519 private key in unpadded base64url;- optional
RIMTHAN_ADMIN_ALLOWED_ORIGINSfor Studio CORS; - bind host and port.
AdminAuthAndLimitMiddleware protects all routes except /healthz, /readyz, and OPTIONS. It checks Authorization: Bearer ... with secrets.compare_digest, limits request bodies to maximum_request_bytes (2 MiB default), converts RegistryError into JSON errors, and adds private no-store and frame/sniffing headers.
When allowed_origins is non-empty, create_admin_app adds Starlette CORSMiddleware with those exact origins, methods GET, POST, and OPTIONS, allowed headers Authorization, Content-Type, and X-Rimthan-Actor, no credentials, and max_age=600. _json_body accepts only JSON objects and maps malformed JSON or non-object bodies to PublicationError. _actor derives actor attribution from X-Rimthan-Actor, defaults to admin, strips whitespace, and requires 1-200 characters; save, migrate, publish, and channel activation pass that actor to store/service methods so revisions, jobs, channels, and audit events are attributed consistently.
App bootstrapping
create_admin_app constructs a PostgresRegistryStore, applies migrations with roles, loads the packaged built-in release through ReleaseLoader, imports that release into PostgreSQL, creates an Ed25519ManifestSigner, builds a trust root for published releases, and wraps everything in AdminDomain.
AdminDomain.service(workflow_kind=...) selects WorkflowEngineDraftCompiler for step-gate workflows and WorkflowDraftCompiler for legacy module-graph workflows. For step-gate publication it attempts to use the currently active production release as source, so aggregate releases preserve previously published workflow definitions, steps, assets, and validators.
Endpoint families
| Route | Method | Handler | Responsibility |
|---|---|---|---|
/healthz | GET | health | Process health without auth. |
/readyz | GET | ready | Verifies store can list channels. |
/admin/v1/workflows | GET | workflows | List current workflow draft heads. |
/admin/v1/workflows/starter | GET | workflow_starter | Return packaged starter/workflow-engine/research-decision.draft.json as a step-gate starter. |
/admin/v1/workflows | POST | save_workflow | Save a legacy or step-gate draft revision with optimistic concurrency and editor layout. |
/admin/v1/workflows/{workflow_id}/migrate-to-engine | POST | migrate_workflow | Explicitly replace a legacy draft head with a step-gate revision. |
/admin/v1/workflows/{workflow_id} | GET | workflow | Return current draft head. |
/admin/v1/workflows/{workflow_id}/revisions | GET | workflow_revisions | List immutable revision summaries. |
/admin/v1/workflows/{workflow_id}/revisions/{revision} | GET | workflow_revision | Return exact revision snapshot and editor layout. |
/admin/v1/workflows/{workflow_id}/validate | POST | validate_workflow | Compile an exact revision as preview and return manifest, digest, counts, and dependency diff. |
/admin/v1/workflows/{workflow_id}/publish | POST | publish_workflow | Publish an exact revision and optionally activate a channel. |
/admin/v1/modules | GET | modules | Return source modules and presentation bundles for Studio editing. |
/admin/v1/assets | GET | assets | Return registry asset and validator definition drafts. |
/admin/v1/releases | GET | releases | List immutable releases. |
/admin/v1/releases/{release_id} | GET | release_detail | Return metadata, manifest, workflow dependency view, and legacy module dependency view. |
/admin/v1/channels | GET | channels | List active channel states. |
/admin/v1/channels/{channel}/activate | POST | activate | Generation-fenced rollback or activation of an existing signed release. |
/admin/v1/trust-root | GET | trust | Return the public trust root used by MCP. |
Draft save and migration behavior
save_workflow chooses store.save_engine_workflow_draft when workflow_kind is step-gate or the document contains definition; otherwise it uses save_workflow_draft. The store validates the document, computes a semantic digest that excludes layout and revision metadata, enforces expected-revision concurrency, writes the mutable head, appends an immutable revision row, and records an audit event.
migrate_workflow is intentionally separate. It requires the route workflow ID to match document.workflow_id, requires an existing legacy module-graph head, advances revision by one, changes workflow_kind to step-gate, saves engine assets and validators, and records workflow_migrated_to_engine. This separation prevents accidental kind changes.
Validation preview and publication
validate_workflow loads one stored revision, selects the compiler by document shape, compiles with a preview release value (default 0.0.0-preview), checks the compiled source revision equals the stored semantic digest, and returns:
- workflow revision and source revision;
- compiled manifest SHA-256 and complete manifest;
- module, presentation, workflow definition, step, asset, and validator counts;
- source registry release;
- dependency diff from
_compiled_manifest_diffshowing added, removed, changed, and retained immutable identities.
publish_workflow delegates to PublicationService.publish with workflow ID, revision, target release, optional target channel, actor, idempotency key, and expected channel generation. It returns status: published, job ID, release ID, manifest SHA-256, and channel state.
Release detail
release_detail loads a release by UUID and verifies it with the trust root when release_mode == "published". It constructs workflow_dependencies by walking every workflow definition and its steps, collecting step instruction assets, output schemas, host assets, validator references, validator instruction assets, and validator output schemas. It also returns legacy module_dependencies. Workflow Studio uses this to display compiled dependency views.
Tests
tests/test_admin_api.py::test_admin_api_authors_publishes_and_lists_a_signed_releasecovers auth, CORS preflight, draft save, revision history, publish, release listing, channel listing, trust root, MCP refresh, rollback, migration to engine, aggregate publication, and workflow-engine MCP startup from published releases.tests/test_postgres_publication_e2e.pycovers the same domain below HTTP, including immutable triggers and channel generation conflicts.apps/admin-web/tests/studio.spec.tsmocks Admin API routes to prove Studio restores immutable history and exposes release dependency detail.
Focused validation
- Without PostgreSQL:
uv run pytest tests/test_workflow_engine.py tests/test_workflow_publication.py - With admin PostgreSQL DSN:
RIMTHAN_TEST_ADMIN_POSTGRES_DSN=... uv run pytest tests/test_admin_api.py