syncova-backup/migrations/000011_agent_tasks.up.sql
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

116 lines
4.9 KiB
SQL

-- Aufträge, die ein Agent auf seinem System ausführt (Phase 5).
--
-- Bis hierher war der Agent ein angemeldeter Beobachter: Er meldete sich an und
-- gab Lebenszeichen, aber der Server konnte ihm nichts zu tun geben. Zentral
-- gesteuerte Sicherungen liefen deshalb nur für Quellen, die der Control-Server
-- selbst erreicht.
--
-- Der Agent **holt** seine Aufträge ab; der Server drückt sie ihm nicht zu. Das
-- ist keine Bequemlichkeit, sondern die einzige Wahl, die in echten Netzen
-- funktioniert: Ein Agent steht hinter einer Firewall, oft hinter NAT, und ist
-- vom Server aus nicht erreichbar. Die Verbindung geht immer vom Agenten aus.
CREATE TABLE agent_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Der ausführende Agent.
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
-- Der Lauf, zu dem der Auftrag gehört.
--
-- Über ihn findet das Ergebnis zurück in die Auftragsverwaltung. Ohne
-- diesen Bezug wäre eine Agentensicherung ein Backup ohne Herkunft.
job_run_id UUID NOT NULL REFERENCES backup_job_runs(id) ON DELETE CASCADE,
-- Die zu sichernde Quelle.
job_source_id UUID NOT NULL REFERENCES backup_job_sources(id) ON DELETE CASCADE,
task_type TEXT NOT NULL DEFAULT 'backup',
status TEXT NOT NULL DEFAULT 'pending',
-- Der vollständige Auftrag als JSON.
--
-- Er enthält Quellpfad, Repositorypfad, Backupkennung, Kette, Elternbackup
-- und die Einstellungen. Als JSON und nicht als Spalten, weil der Auftrag
-- ein **Vertrag** zwischen Server und Agent ist: Beide Seiten werden
-- getrennt aktualisiert, und ein zusätzliches Feld darf keine Migration auf
-- jedem Agenten erzwingen.
task_payload JSONB NOT NULL,
-- Fortschritt und Ergebnis.
bytes_processed BIGINT NOT NULL DEFAULT 0,
bytes_written BIGINT NOT NULL DEFAULT 0,
files_processed BIGINT NOT NULL DEFAULT 0,
files_skipped BIGINT NOT NULL DEFAULT 0,
backup_id_in_repository TEXT,
error_code TEXT,
error_message TEXT,
failure_class TEXT,
-- Lebendmeldung des ausführenden Agenten.
--
-- Stirbt der Agent mitten im Lauf, bliebe der Auftrag sonst dauerhaft auf
-- 'running' stehen und blockierte den Agenten für immer — ohne dass jemand
-- einen Fehler sähe. Dieselbe Überlegung wie beim Scheduler (Phase 8).
heartbeat_at TIMESTAMPTZ,
claimed_at TIMESTAMPTZ,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT agent_tasks_status_valid CHECK (
status IN ('pending', 'claimed', 'running', 'succeeded', 'partial_failure',
'failed', 'cancelled')
),
CONSTRAINT agent_tasks_type_valid CHECK (task_type IN ('backup', 'restore')),
-- Ein abgeschlossener Auftrag hat einen Abschlusszeitpunkt.
CONSTRAINT agent_tasks_completed_when_done CHECK (
status IN ('pending', 'claimed', 'running') OR completed_at IS NOT NULL
),
-- Übergangene Objekte und Erfolg schließen sich aus.
--
-- Dieselbe Regel wie bei den Sicherungsläufen (Phase 8): Ein Lauf mit
-- übergangenen Objekten ist niemals ein Erfolg, sondern ein Teilfehler
-- (verbindliche Regel 1). Sie steht als CHECK in der Datenbank, weil sie im
-- Code an jeder Stelle eingehalten werden müsste — und eine vergisst es.
CONSTRAINT agent_tasks_skipped_is_not_success CHECK (
files_skipped = 0 OR status <> 'succeeded'
),
-- Ein gescheiterter Auftrag nennt seine Fehlerklasse.
--
-- Ohne sie kann die Ausführungsschleife nicht entscheiden, ob eine
-- Wiederholung sinnvoll ist (Phase 21).
CONSTRAINT agent_tasks_failure_is_classified CHECK (
status <> 'failed' OR failure_class IS NOT NULL
)
);
-- Ein Agent führt höchstens einen Auftrag gleichzeitig aus.
--
-- Der Teilindex ist die Durchsetzung: Zwei gleichzeitige Sicherungen desselben
-- Systems konkurrierten um dieselben Dateien und dieselbe Schreibsperre des
-- Repositorys. Im Code ließe sich das nur mit einer Prüfung zwischen Lesen und
-- Schreiben lösen — und dazwischen passt ein zweiter Control-Server.
CREATE UNIQUE INDEX agent_tasks_single_active_idx
ON agent_tasks (agent_id)
WHERE status IN ('claimed', 'running');
-- Die Abholung sucht den ältesten offenen Auftrag eines Agenten.
CREATE INDEX agent_tasks_pending_idx
ON agent_tasks (agent_id, created_at)
WHERE status = 'pending';
-- Die Zuordnung eines Ergebnisses zum Lauf.
CREATE INDEX agent_tasks_job_run_idx ON agent_tasks (job_run_id);
-- Die Freigabe verwaister Aufträge sucht über die Lebendmeldung.
CREATE INDEX agent_tasks_heartbeat_idx
ON agent_tasks (heartbeat_at)
WHERE status IN ('claimed', 'running');