feat: complete client runtime foundation

This commit is contained in:
2026-07-22 16:25:38 +00:00
parent c11a7b5b5b
commit 1219117403
20 changed files with 1201 additions and 48 deletions
+91 -1
View File
@@ -4,7 +4,9 @@ from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import stat
from dataclasses import dataclass
from pathlib import Path
@@ -13,6 +15,10 @@ class CommandConflict(RuntimeError):
pass
class JobConflict(RuntimeError):
pass
@dataclass(frozen=True)
class CommandAcceptance:
duplicate: bool
@@ -25,6 +31,13 @@ class ClientStore:
def initialize(self) -> None:
self.database.parent.mkdir(parents=True, exist_ok=True)
existed = self.database.exists()
if existed:
metadata = self.database.stat()
if not stat.S_ISREG(metadata.st_mode):
raise RuntimeError("client database is not a regular file")
if metadata.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
raise RuntimeError("client database file permissions are unsafe")
with self._connect() as connection:
version = connection.execute("PRAGMA user_version").fetchone()[0]
if version > 1:
@@ -35,6 +48,8 @@ class ClientStore:
connection.execute("PRAGMA user_version = 1")
if connection.execute("PRAGMA foreign_key_check").fetchall():
raise RuntimeError("client database foreign-key check failed")
if not existed:
os.chmod(self.database, 0o600)
def accept_command(
self, command_id: str, command_json: str, acknowledgement_json: str
@@ -45,7 +60,10 @@ class ClientStore:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"SELECT payload_sha256, command_json, acknowledgement_json FROM commands WHERE command_id = ?",
"""
SELECT payload_sha256, command_json, acknowledgement_json
FROM commands WHERE command_id = ?
""",
(command_id,),
).fetchone()
if existing:
@@ -75,6 +93,78 @@ class ClientStore:
).fetchall()
return [dict(row) for row in rows]
def job_snapshot_rows(
self, job_ids: list[str]
) -> list[dict[str, object]]:
if not job_ids:
return []
placeholders = ",".join("?" for _ in job_ids)
with self._connect() as connection:
rows = connection.execute(
f"""
SELECT job_id, definition_json, state, revision,
last_event_sequence, committed
FROM jobs WHERE job_id IN ({placeholders}) ORDER BY job_id
""",
job_ids,
).fetchall()
return [dict(row) for row in rows]
def save_job(
self,
job_id: str,
definition_json: str,
state: str,
revision: int,
last_event_sequence: int,
committed: bool,
) -> None:
definition = _canonical(json.loads(definition_json))
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"""
SELECT definition_json, state, revision,
last_event_sequence, committed
FROM jobs WHERE job_id = ?
""",
(job_id,),
).fetchone()
if existing and existing["definition_json"] != definition:
raise JobConflict("job definition is immutable")
if existing and (
revision < existing["revision"]
or last_event_sequence < existing["last_event_sequence"]
or (existing["committed"] and not committed)
):
raise JobConflict("job cursor cannot move backwards")
if existing and (
revision == existing["revision"]
and last_event_sequence == existing["last_event_sequence"]
and (
state != existing["state"]
or int(committed) != existing["committed"]
)
):
raise JobConflict("equal job cursor has conflicting state")
connection.execute(
"""
INSERT INTO jobs (
job_id, definition_json, state, revision,
last_event_sequence, committed
) VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(job_id) DO UPDATE SET
state = excluded.state,
revision = excluded.revision,
last_event_sequence = excluded.last_event_sequence,
committed = excluded.committed
""",
(
job_id, definition, state, revision,
last_event_sequence, int(committed),
),
)
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.database, isolation_level=None, timeout=5)
connection.row_factory = sqlite3.Row