trading-system-docs/tolaria/c5-sync-service/test_productive_incident_fixture.py

203 lines
8.3 KiB
Python

"""
AUTH.4C3 — test_productive_incident_fixture.py
==============================================
ISOLIERTE Fixture, die exakt den realen AUTH.4C2-Incident nachbildet.
Real-Incident (AUTH.4C2, produktiv):
JOB_ID: f18608df-0469-4760-8d84-d6bf1917e772
STATE: OUTCOME_UNKNOWN
attempt_count: 1
Canary: PRESENT
Path: tolaria/auth4c2-canary.md
Object ID: object/cfeafffb-415e-4743-af44-e8c92efb9ac5
Hash: 798ae0315c14aa9d7e3a3f759c952f7c18cbce5154844f447e597b7fd1776515
Tolaria Markdown Count: 85
Erwartung (AUTH.4C3):
Reconciliation bestätigt Erfolg (TARGET_EXACT)
SAVE_CALL_COUNT = 0
attempt_count bleibt 1
Historie bleibt erhalten (OUTCOME_UNKNOWN sichtbar im Audit)
KEINE produktive DB. KEIN produktiver Job. KEIN SAVE.
Exit 0 = alle PASS.
"""
from __future__ import annotations
import hashlib
import os
import shutil
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from job_schema import make_save_job
from job_store import JobStore
from job_state_machine import (
ST_OUTCOME_UNKNOWN, ST_SUCCEEDED, ST_CLAIMED, ST_EXECUTING,
)
from save_reconciliation import (
SaveReconciliationCore,
EV_RECONCILIATION_STARTED, EV_RECONCILIATION_TARGET_EXACT,
EV_CONFIRMED_EXECUTED,
)
# Exakte Werte aus dem realen Incident (ISOLIERTE Fixture, KEINE produktive DB)
INCIDENT_JOB_ID = "f18608df-0469-4760-8d84-d6bf1917e772"
INCIDENT_OBJECT_ID = "object/cfeafffb-415e-4743-af44-e8c92efb9ac5"
INCIDENT_PATH = "tolaria/auth4c2-canary.md"
INCIDENT_HASH = "798ae0315c14aa9d7e3a3f759c952f7c18cbce5154844f447e597b7fd1776515"
INCIDENT_ATTEMPTS = 1
class ProductiveIncidentFixtureTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Canary-Content: der EXAKTE reale Inhalt aus dem produktiven Vault
# (read-only verifiziert, Hash stimmt exakt mit dem Incident überein)
cls.canary_content = (
"---\n"
"id: object/cfeafffb-415e-4743-af44-e8c92efb9ac5\n"
"type: Note\n"
"title: AUTH.4C2 Canary\n"
"tags: [auth4c2, canary, c5-save]\n"
"created: 2026-08-27\n"
"mission: AUTH.4C2\n"
"---\n"
"\n"
"# AUTH.4C2 First Productive SAVE Canary\n"
"\n"
"Dieser Eintrag ist der kontrollierte produktive SAVE-Canary der Mission AUTH.4C2.\n"
"\n"
"- **Mission:** AUTH.4C2 — First Productive SAVE Canary\n"
"- **Zweck:** Beweis exakt eines produktiven SAVEs über den isolierten c5-save-executor-Pfad\n"
"- **Pfad:** tolaria/auth4c2-canary.md\n"
"- **Quelle:** Forgejo Source of Truth (commit-bound)\n"
"- **Fachlich:** neutral — kein Trading-Inhalt, keine Secrets\n"
"\n"
"Dieser Canary bleibt kontrolliert vorhanden für spätere AUTH.4E / Cleanup-Mission.\n"
)
# Verifiziere: Hash des Canary-Contents == Incident-Hash
computed = hashlib.sha256(cls.canary_content.encode("utf-8")).hexdigest()
assert computed == INCIDENT_HASH, (
f"Fixture-Hash mismatch: computed={computed} expected={INCIDENT_HASH}"
)
def setUp(self):
# Jeder Test bekommt eine FRISCHE isolierte DB (create_job ist idempotent
# per job_id — dieselbe INCIDENT_JOB_ID darf nicht über Tests kollidieren)
self.tmp = tempfile.mkdtemp(prefix="c5-4c3-incident-")
self.db = os.path.join(self.tmp, "incident.db")
self.store = JobStore(self.db, "SAVE")
def tearDown(self):
self.store.close()
shutil.rmtree(self.tmp, ignore_errors=True)
def _create_incident_job(self):
"""Erzeugt den Job exakt wie im realen Incident (OUTCOME_UNKNOWN, attempts=1)."""
job = make_save_job(
job_id=INCIDENT_JOB_ID,
mission_id="00000000-0000-0000-0000-000000000001",
object_id=INCIDENT_OBJECT_ID,
vault_path=INCIDENT_PATH,
source_commit="a" * 40,
provenance_hash=INCIDENT_HASH,
expected_state="present",
created_at="2026-08-27T00:00:00Z",
idempotency_key="00000000-0000-0000-0000-000000000002",
)
self.store.create_job(job)
self.store.mark_ready(job["job_id"])
# Simuliere Execution -> OUTCOME_UNKNOWN (attempt_count=1 via Claim)
self.store._transition(job["job_id"], ST_CLAIMED)
self.store._transition(job["job_id"], ST_EXECUTING)
self.store._transition(job["job_id"], ST_OUTCOME_UNKNOWN)
# attempt_count auf 1 setzen (wie im Incident)
self.store._conn.execute(
"UPDATE jobs SET attempt_count = ? WHERE job_id = ?",
(INCIDENT_ATTEMPTS, job["job_id"]),
)
self.store._conn.commit()
return self.store.get_job(job["job_id"])
def test_incident_reconciliation_confirms_success(self):
"""TARGET_EXACT: Reconciliation bestätigt Erfolg, Job -> SUCCEEDED."""
job = self._create_incident_job()
self.assertEqual(job["state"], ST_OUTCOME_UNKNOWN)
self.assertEqual(job["attempt_count"], INCIDENT_ATTEMPTS)
# Read-Back liefert exakt den Canary-Content (Target PRESENT + exact)
core = SaveReconciliationCore(
self.store,
lambda sc, oid, vp: self.canary_content, # source_loader
lambda vp: self.canary_content, # read_back
)
result = core.reconcile(job["job_id"], "worker-1")
self.assertEqual(result["state"], ST_SUCCEEDED)
def test_incident_zero_save_calls(self):
"""SAVE_CALL_COUNT = 0: Reconciliation ruft niemals write() auf."""
job = self._create_incident_job()
save_calls = {"count": 0}
def fake_save(*args, **kwargs):
save_calls["count"] += 1
raise AssertionError("Reconciliation darf NIE write() aufrufen!")
core = SaveReconciliationCore(
self.store,
lambda sc, oid, vp: self.canary_content,
lambda vp: self.canary_content,
)
# Reconciliation-Core hat KEIN write-Callable (technisch unmöglich)
self.assertFalse(hasattr(core, "tolaria_save"))
self.assertFalse(hasattr(core, "write"))
result = core.reconcile(job["job_id"], "worker-1")
self.assertEqual(result["state"], ST_SUCCEEDED)
self.assertEqual(save_calls["count"], 0)
def test_incident_attempt_count_unchanged(self):
"""attempt_count bleibt 1 (Reconciliation ist KEIN neuer Mutation Attempt)."""
job = self._create_incident_job()
core = SaveReconciliationCore(
self.store,
lambda sc, oid, vp: self.canary_content,
lambda vp: self.canary_content,
)
result = core.reconcile(job["job_id"], "worker-1")
self.assertEqual(result["state"], ST_SUCCEEDED)
self.assertEqual(result["attempt_count"], INCIDENT_ATTEMPTS)
def test_incident_history_preserved(self):
"""Historie bleibt erhalten: OUTCOME_UNKNOWN sichtbar im Audit-Trail."""
job = self._create_incident_job()
core = SaveReconciliationCore(
self.store,
lambda sc, oid, vp: self.canary_content,
lambda vp: self.canary_content,
)
core.reconcile(job["job_id"], "worker-1")
trail = self.store.audit_trail(job["job_id"])
events = [e["event_type"] for e in trail if "event_type" in e]
# Reconciliation-Evidence vorhanden
self.assertIn(EV_RECONCILIATION_STARTED, events)
self.assertIn(EV_RECONCILIATION_TARGET_EXACT, events)
self.assertIn(EV_CONFIRMED_EXECUTED, events)
# KEIN EXECUTED (Mutation wurde NICHT neu ausgeführt)
self.assertNotIn("EXECUTED", events)
# Der Job-Snapshot im Trail zeigt den finalen State SUCCEEDED,
# aber die OUTCOME_UNKNOWN-Historie ist in den Events sichtbar
# (der Job durchlief OUTCOME_UNKNOWN -> RECONCILED -> SUCCEEDED).
def test_incident_no_production_db(self):
"""Fixture nutzt eine ISOLIERTE temporäre DB, keine produktive DB."""
self.assertNotEqual(self.db, "/opt/data/forgejo/trading-system-docs/tolaria/c5-sync-service/c5a_save.db")
self.assertTrue(self.db.startswith(tempfile.gettempdir()) or "/tmp" in self.db or "c5-4c3-incident" in self.db)
if __name__ == "__main__":
unittest.main(verbosity=2)