Themelio compatibility reference. This page describes the retained Themelio implementation or its historical design. Check the parallel-operation guide before applying it to the current platform.
DD-0012: Job and Deployment Management System — Implementation
Related ADR: ADR-0012: Job and Deployment Management System
Status: Draft
Date: 2026-06-02
Authors: TBD
Overview
This document specifies the implementation of a PostgreSQL-based persistent job and deployment management system for Themelio. The system provides clear separation between short-running jobs and long-running deployments, with historical data retention, secret-based security for external updates, and explicit destroy functionality.
This design is implemented in k8s-root-actor (PostgreSQL via knex, the jobs/deployments/*_logs tables, the CRUD/destroy endpoints, secret hashing, and status tracking) and in the web service (repo- and namespace-scoped proxy endpoints, GitLab write-access checks, secret passthrough). The formal Status above stays Draft only because ADR-0012 is still Proposed (a DD is not marked Implemented ahead of its ADR being Accepted). Two details below were corrected to match the shipped code:
- The secret header is a single unified
x-action-secretheader (not separatex-job-secret/x-deployment-secretheaders). job_logs/deployment_logscarry an extrasource_statuscolumn that stores the granular template status (e.g.SLURM_JOB_OUTPUT,JOB_FINISHED).
Log/status update path (reconciled 2026-06-09): the original "external system → web PUT …/jobs/{id} with X-Resource-Secret" design was superseded. The as-built path is: the pod posts to a flat web endpoint, POST /api/{jobs,deployments}/{id}/notifications (keyed by the integer DB row id), authenticated by the x-action-secret header; the web service is a secret-authenticated passthrough (no GitLab check) that proxies to k8s-root-actor POST /{jobs,deployments}/{id}/notifications, which validates the secret against secret_hash. The repo-scoped PUT …/jobs/{id} endpoints described below were never wired and have been removed — read the verb as POST … /notifications and the header as x-action-secret throughout the rest of this document.
Goals
- Replace ephemeral Valkey storage with persistent PostgreSQL
- Separate job and deployment lifecycles with appropriate APIs
- Enable historical data retention for analytics and audit
- Provide secret-based authentication for external log/status updates
- Implement explicit destroy with history preservation
- Maintain single database owner pattern (k8s-root-actor only)
Design
1. Architecture
The system consists of two main components:
- k8s-root-actor - Owns PostgreSQL database, manages all job/deployment data
- Web service - Provides repo-scoped API endpoints, delegates data operations to k8s-root-actor
Communication Flow:
- Web service → k8s-root-actor: HTTP requests for all CRUD operations
- External systems (Kubernetes/SLURM) → Web service → k8s-root-actor: PUT log-and-status with secret
- k8s-root-actor → PostgreSQL: Direct database connection
2. Database Schema
Jobs Table:
Deployments Table:
Job Logs Table:
Deployment Logs Table:
3. API Endpoints
Web Service Endpoints
Jobs (scoped to repository):
Jobs (scoped to namespace):
Deployments (scoped to repository):
Deployments (scoped to namespace):
Note: Job and deployment creation happens via the existing action endpoints:
POST /api/[repoType]/[namespace]/[repo]/action/[branchName](web service)POST /:repoType/:namespace/:repoId/:branchName/action(k8s-root-actor)
k8s-root-actor Endpoints
Jobs:
GET /jobs/{job_id}- Get job detailsGET /jobs- List jobs (acceptsgitlab_project_idornamespacequery parameter, optionaltagfilter)POST /jobs/{row_id}/notifications- Update job log and status (validatesx-action-secret)DELETE /jobs/{job_id}- Destroy job
Deployments:
GET /deployments/{deployment_id}- Get deployment detailsGET /deployments- List deployments (acceptsgitlab_project_idornamespacequery parameter, optionaltagfilter)POST /deployments/{row_id}/notifications- Update deployment log and status (validatesx-action-secret)DELETE /deployments/{deployment_id}- Destroy deployment
Existing Action Endpoint Updates
The existing action endpoints must be updated to integrate with the new PostgreSQL-based job/deployment system.
Web Service Action Endpoint
Current Location: services/web/src/app/api/[repoType]/[namespace]/[repo]/action/[branchName]/route.ts
Updates Required:
- Keep existing POST handler for action execution
- Update to handle response from k8s-root-actor that includes
job_id/deployment_idandsecret - Return these to the caller for future status/log/destroy operations
Request Flow:
Response Format:
Note: The secret is generated and stored by k8s-root-actor but is NOT returned in the response. The secret must be obtained and managed separately for use in PUT log-and-status requests.
k8s-root-actor Action Endpoint
Current Location: services/k8s-root-actor/src/index.ts
Current Endpoint: POST /:repoType/:namespace/:repoId/:branchName/action
Updates Required:
- Before creating Kubernetes resources, determine if action type is
slurm-job(job) ork8s-deployment/k8s-inferenceservice(deployment) - Generate secret and create job/deployment record in PostgreSQL with hashed secret
- Store the
gitlab_project_id(URL-encoded<repoType>/<namespace>/<repoId>) - After creating Kubernetes resources, return the job/deployment ID and secret
- On status changes, update PostgreSQL record
Updated Request Flow:
Note: The secret is generated and stored by k8s-root-actor but must be distributed to external systems through a separate mechanism (not via the web service response).
Integration Points:
Status Tracking:
k8s-root-actor must update the PostgreSQL record when:
- Job starts:
status = "running",started_at = NOW() - Job completes:
status = "succeeded"or"failed",completed_at = NOW() - Deployment becomes ready:
status = "running",deployed_at = NOW() - Deployment fails:
status = "failed" - Job/deployment destroyed:
status = "destroyed",destroyed_at = NOW()
4. Data Flow
Creation Flow (via Action Endpoints)
Status Update Flow (External)
User Polling Flow
List Jobs/Deployments Flow
Destroy Flow
5. Secret Management
Generation (k8s-root-actor only):
Validation (k8s-root-actor only):
Passthrough (Web service):
Secret Distribution: The secret is generated by k8s-root-actor but must be distributed to external systems (Kubernetes/SLURM) through a separate mechanism (not specified in this document). External systems use this secret to authenticate PUT log-and-status requests.
6. GitLab Project ID Format
The gitlab_project_id is a URL-encoded string: <repoType>/<namespace>/<repoId>
Examples:
models/namespace/my-modeldatasets/username/my-datasetspaces/org-name/my-space
This format is used for:
- Database filtering (query jobs/deployments by project)
- Repository-scoped endpoint routing
- GitLab access validation
Data Model
Job Status Values
Deployment Status Values
Log Level Values
info- Informational messageswarning- Warning messageserror- Error messages
API Design
Note: Job and deployment creation happens via the existing action endpoints.
Get Job Details Response
GET /api/[repoType]/[namespace]/[repo]/jobs/{job_id}
Response:
List Jobs Response
GET /api/[repoType]/[namespace]/[repo]/jobs
Response:
GET /api/jobs/namespace/{namespace}?tag=training
Response: (same format, filtered by namespace and optional tag)
List Deployments Response
GET /api/[repoType]/[namespace]/[repo]/deployments
Response:
GET /api/deployments/namespace/{namespace}?tag=inference
Response: (same format, filtered by namespace and optional tag)
Update Log and Status Request
POST /api/jobs/{row_id}/notifications (flat, keyed by the integer DB row id; deployments use /api/deployments/{row_id}/notifications)
Headers:
x-action-secret: <secret>(unified for both jobs and deployments)
Response:
Error Responses:
401 Unauthorized- Secret not provided403 Forbidden- Invalid secret (k8s-root-actor validation)
Testing Strategy
Unit Tests
-
Database Models:
- Test CRUD operations for jobs and deployments
- Test log insertion and querying
- Test gitlab_project_id filtering
-
Secret Management:
- Test secret generation and hashing
- Test secret validation
- Test invalid secret rejection
Integration Tests
-
End-to-End Flow:
- Trigger action via web service action endpoint
- Verify k8s-root-actor creates job/deployment record in PostgreSQL
- Verify response includes id and secret
- Update status via PUT endpoint with secret
- Verify status persisted in PostgreSQL
- Query via GET endpoint
- Verify response includes status and logs
- Destroy and verify record marked destroyed, historical data preserved
-
Action Endpoint Integration:
- Test slurm-job action → creates job record
- Test k8s-deployment action → creates deployment record
- Test k8s-inferenceservice action → creates deployment record
- Verify gitlab_project_id correctly stored
- Verify secret generated and returned
- Verify Kubernetes resources created after database record
-
Security Testing:
- Access without GitLab write access → 403
- PUT without secret → k8s-root-actor returns 401 Unauthorized
- PUT with invalid secret → k8s-root-actor returns 403 Forbidden
- PUT with valid secret → success
- Web service passes secret to k8s-root-actor without validating
-
Repository Scoping:
- Verify endpoint routing by repoType/namespace/repo
- Verify gitlab_project_id filtering
- Test cross-repository access denial
Manual Validation
-
Database Setup:
- PostgreSQL accessible from k8s-root-actor
- Schema created and indexed correctly
- Connection pooling configured
-
API Validation:
- All endpoints respond correctly
- Error handling works as expected
- Secrets validated properly
Deployment & Rollout
Phase 1: Database Setup
- Provision PostgreSQL database
- Create schema and tables
- Configure connection pooling
- Verify k8s-root-actor connectivity
Phase 2: k8s-root-actor Updates
- Add database connection code
- Add internal endpoints
- Test database operations
- Deploy to staging
Phase 3: Web Service Updates
- Add repo-scoped endpoints
- Add GitLab access validation
- Add k8s-root-actor client calls
- Test integration
- Deploy to staging
Open Questions
- GitLab Access Caching: How should web service cache GitLab access checks to avoid repeated API calls?
References
Page source
Source snapshot · Revision f928834c8.