feat(v2): persist execution event revisions

This commit is contained in:
2026-07-27 23:00:18 +02:00
parent bb87b04ca8
commit 63c977cea5
5 changed files with 104 additions and 19 deletions
+29 -3
View File
@@ -7,7 +7,7 @@ from sqlalchemy import select, update
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from backup_tool.db.models import Execution, Job
from backup_tool.db.models import Execution, ExecutionEvent, Job
from backup_tool.security.redaction import redact
ACTIVE_STATES = frozenset({"queued", "preparing", "running", "verifying", "cancelling"})
@@ -67,6 +67,8 @@ async def enqueue(db: AsyncSession, job_id: str, trigger: str = "manual") -> Exe
"execution_active", "Job already has an active execution.", existing
) from error
await db.refresh(execution)
await record_event(db, execution)
await db.commit()
return execution
@@ -89,7 +91,11 @@ async def claim(
await db.rollback()
return None
await db.commit()
return await db.get(Execution, execution_id)
execution = await db.get(Execution, execution_id)
if execution is not None:
await record_event(db, execution)
await db.commit()
return execution
async def heartbeat(
@@ -128,7 +134,11 @@ async def request_cancellation(db: AsyncSession, execution_id: str) -> Execution
await db.rollback()
return None
await db.commit()
return await db.get(Execution, execution_id)
execution = await db.get(Execution, execution_id)
if execution is not None:
await record_event(db, execution)
await db.commit()
return execution
async def complete_cancellation(db: AsyncSession, execution_id: str, owner: str) -> bool:
@@ -177,6 +187,8 @@ async def retry(db: AsyncSession, execution_id: str) -> Execution | None:
"execution_active", "Job already has an active execution.", active_id
) from error
await db.refresh(execution)
await record_event(db, execution)
await db.commit()
return execution
@@ -196,6 +208,19 @@ def _redact_progress(value: object) -> object:
return redact(value)
async def record_event(db: AsyncSession, execution: Execution) -> ExecutionEvent:
"""Append the public representation as the next durable event revision."""
execution.revision += 1
event = ExecutionEvent(
execution_id=execution.id,
revision=execution.revision,
payload=public_event(execution),
)
db.add(event)
await db.flush()
return event
def public_event(execution: Execution) -> dict[str, object]:
"""Return redacted progress suitable for polling or SSE."""
progress = _redact_progress(execution.progress)
@@ -203,6 +228,7 @@ def public_event(execution: Execution) -> dict[str, object]:
"id": execution.id,
"state": execution.state,
"attempt": execution.attempt,
"revision": execution.revision,
"reason_code": execution.reason_code,
"progress": progress,
}