from __future__ import annotations import json import uuid from sqlmodel import Session, select from ..core.time import utc_now from ..db.models import JobRun def list_jobs(session: Session): return session.exec(select(JobRun).order_by(JobRun.started_at.desc())).all() def get_job(session: Session, job_id: str) -> JobRun | None: return session.get(JobRun, job_id) def create_job( session: Session, *, action_id: str, target: str, source: str, risk: str, policy_decision: str, steps: list[str], audit_event_id: str | None = None, ) -> JobRun: job = prepare_job( action_id=action_id, target=target, source=source, risk=risk, policy_decision=policy_decision, steps=steps, audit_event_id=audit_event_id, ) session.add(job) session.commit() session.refresh(job) return job def prepare_job( *, action_id: str, target: str, source: str, risk: str, policy_decision: str, steps: list[str], audit_event_id: str | None = None, ) -> JobRun: """Build a job without committing it so admission can be atomic.""" now = utc_now() return JobRun( id=f'job-{uuid.uuid4().hex[:12]}', type=action_id, status='queued', source=source, action_id=action_id, target=target, risk=risk, policy_decision=policy_decision, steps_json=json.dumps(steps), progress=0, started_at=now, audit_event_id=audit_event_id, ) def start_job(session: Session, job: JobRun) -> JobRun: job.status = 'running' job.started_at = job.started_at or utc_now() job.progress = 5 session.add(job) session.commit() session.refresh(job) return job def complete_job(session: Session, job: JobRun, *, status: str, result_summary: str) -> JobRun: job.status = status job.finished_at = utc_now() job.result_summary = result_summary job.progress = 100 if status == 'succeeded' else job.progress if job.started_at: job.duration_ms = int((job.finished_at - job.started_at).total_seconds() * 1000) session.add(job) session.commit() session.refresh(job) return job def cancel_job(session: Session, job: JobRun, *, result_summary: str = 'Cancelled by operator.') -> JobRun: job.status = 'cancelled' job.finished_at = utc_now() job.result_summary = result_summary if job.started_at: job.duration_ms = int((job.finished_at - job.started_at).total_seconds() * 1000) session.add(job) session.commit() session.refresh(job) return job def set_job_progress(session: Session, job: JobRun, progress: int, result_summary: str | None = None) -> JobRun: job.progress = max(0, min(100, int(progress))) if result_summary: job.result_summary = result_summary session.add(job) session.commit() session.refresh(job) return job