test_production_operations_postgres.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. from __future__ import annotations
  2. import os
  3. import subprocess
  4. import threading
  5. import uuid
  6. from pathlib import Path
  7. import pytest
  8. from sqlalchemy import create_engine, text
  9. from sqlalchemy.exc import DBAPIError, IntegrityError
  10. pytestmark = pytest.mark.integration
  11. DATABASE_URL = os.environ.get("TEST_DATABASE_URL")
  12. ROOT = Path(__file__).resolve().parents[2]
  13. def _database_url():
  14. if not DATABASE_URL:
  15. pytest.skip("TEST_DATABASE_URL is required")
  16. return DATABASE_URL
  17. def _alembic(command: str, target: str) -> None:
  18. environment = dict(os.environ)
  19. environment["MIGRATION_DATABASE_URL"] = _database_url()
  20. subprocess.run(
  21. [
  22. str(ROOT / ".venv/bin/alembic"),
  23. "-c",
  24. str(ROOT / "alembic.ini"),
  25. command,
  26. target,
  27. ],
  28. cwd=ROOT,
  29. env=environment,
  30. check=True,
  31. capture_output=True,
  32. text=True,
  33. )
  34. def _seed(connection, suffix: str):
  35. actor = str(uuid.uuid5(uuid.NAMESPACE_URL, f"wp05-test-actor-{suffix}"))
  36. incident = str(uuid.uuid5(uuid.NAMESPACE_URL, f"wp05-test-incident-{suffix}"))
  37. alert = str(uuid.uuid5(uuid.NAMESPACE_URL, f"wp05-test-alert-{suffix}"))
  38. username = f"wp05-test-{suffix}"[:80]
  39. connection.execute(
  40. text(
  41. "INSERT INTO public.users "
  42. "(id,username,display_name,password_hash,status) "
  43. "VALUES (CAST(:id AS uuid),:username,:username,'test','active')"
  44. ),
  45. {"id": actor, "username": username},
  46. )
  47. connection.execute(
  48. text(
  49. "INSERT INTO public.data_incidents "
  50. "(uid,code,dedup_key,title,severity,status,owner_uid,escalation_level,"
  51. "first_detected_at,last_observed_at,created_by) VALUES "
  52. "(CAST(:uid AS uuid),:code,:digest,'wp05 test','critical','open',"
  53. "CAST(:actor AS uuid),1,clock_timestamp(),clock_timestamp(),"
  54. "CAST(:actor AS uuid))"
  55. ),
  56. {
  57. "uid": incident,
  58. "code": f"WP05-TEST-{suffix}",
  59. "digest": uuid.uuid5(uuid.NAMESPACE_URL, f"incident-{suffix}").hex,
  60. "actor": actor,
  61. },
  62. )
  63. connection.execute(
  64. text(
  65. "INSERT INTO public.data_observability_alerts "
  66. "(uid,dedup_key,incident_uid,layer,sli_type,title,severity,status,"
  67. "occurrence_count,escalation_level,owner_uid,first_observed_at,"
  68. "last_observed_at,delivery_status,evidence) VALUES "
  69. "(CAST(:uid AS uuid),:digest,CAST(:incident AS uuid),'service',"
  70. "'delivery','wp05 test','critical','open',1,1,CAST(:actor AS uuid),"
  71. "clock_timestamp(),clock_timestamp(),'pending','{}')"
  72. ),
  73. {
  74. "uid": alert,
  75. "digest": uuid.uuid5(uuid.NAMESPACE_URL, f"alert-{suffix}").hex,
  76. "incident": incident,
  77. "actor": actor,
  78. },
  79. )
  80. return {"actor": actor, "incident": incident, "alert": alert}
  81. def _cleanup_wp05_test_rows(engine) -> None:
  82. """Only remove this module's explicitly namespaced synthetic rows."""
  83. with engine.begin() as connection:
  84. # The production ledger is append-only even to its evidence owner. The
  85. # integration harness is the privileged migration operator and disables
  86. # only this user trigger briefly to remove its own namespaced fixtures.
  87. connection.execute(text("SET LOCAL ROLE dataops_edge_evidence_owner"))
  88. connection.execute(
  89. text(
  90. "ALTER TABLE public.production_observability_audits "
  91. "DISABLE TRIGGER USER"
  92. )
  93. )
  94. connection.execute(text("RESET ROLE"))
  95. connection.execute(
  96. text(
  97. "DELETE FROM public.production_observability_audits a "
  98. "USING public.users u WHERE a.actor_uid=u.id "
  99. "AND u.username LIKE 'wp05-test-%'"
  100. )
  101. )
  102. connection.execute(
  103. text(
  104. "SET LOCAL ROLE dataops_edge_evidence_owner"
  105. )
  106. )
  107. connection.execute(
  108. text(
  109. "ALTER TABLE public.production_observability_audits "
  110. "ENABLE TRIGGER USER"
  111. )
  112. )
  113. connection.execute(text("RESET ROLE"))
  114. if connection.execute(
  115. text("SELECT to_regclass('public.production_observability_itsm_references')")
  116. ).scalar_one():
  117. connection.execute(
  118. text(
  119. "DELETE FROM public.production_observability_itsm_references r "
  120. "USING public.data_incidents i WHERE r.incident_uid=i.uid "
  121. "AND i.code LIKE 'WP05-TEST-%'"
  122. )
  123. )
  124. connection.execute(
  125. text(
  126. "DELETE FROM public.production_observability_deliveries d "
  127. "USING public.data_incidents i WHERE d.incident_uid=i.uid "
  128. "AND i.code LIKE 'WP05-TEST-%'"
  129. )
  130. )
  131. connection.execute(
  132. text(
  133. "DELETE FROM public.production_observability_policies p "
  134. "USING public.users u WHERE p.created_by=u.id "
  135. "AND u.username LIKE 'wp05-test-%'"
  136. )
  137. )
  138. connection.execute(
  139. text(
  140. "DELETE FROM public.data_observability_alerts "
  141. "WHERE incident_uid IN (SELECT uid FROM public.data_incidents "
  142. "WHERE code LIKE 'WP05-TEST-%')"
  143. )
  144. )
  145. connection.execute(
  146. text("DELETE FROM public.data_incidents WHERE code LIKE 'WP05-TEST-%'")
  147. )
  148. connection.execute(
  149. text("DELETE FROM public.users WHERE username LIKE 'wp05-test-%'")
  150. )
  151. @pytest.fixture(scope="module")
  152. def pg_engine():
  153. _alembic("upgrade", "head")
  154. engine = create_engine(_database_url(), pool_pre_ping=True)
  155. _cleanup_wp05_test_rows(engine)
  156. yield engine
  157. _cleanup_wp05_test_rows(engine)
  158. engine.dispose()
  159. def test_persistent_delivery_claim_fencing_backoff_and_policy_constraints():
  160. url = os.environ.get("TEST_DATABASE_URL")
  161. if not url:
  162. pytest.skip("TEST_DATABASE_URL is required")
  163. from app.core.events.production_operations import (
  164. ProductionOperationsError,
  165. ProductionOperationsService,
  166. )
  167. from app.core.events.production_operations_repository import (
  168. SqlAlchemyProductionOperationsRepository,
  169. )
  170. engine = create_engine(url, pool_pre_ping=True)
  171. actor, incident, alert = (str(uuid.uuid4()) for _ in range(3))
  172. try:
  173. with engine.begin() as connection:
  174. connection.execute(
  175. text(
  176. "INSERT INTO public.users (id,username,display_name,password_hash,status) VALUES (CAST(:id AS uuid),:username,:username,'test','active')"
  177. ),
  178. {"id": actor, "username": f"wp05ops{actor[:8]}"},
  179. )
  180. connection.execute(
  181. text(
  182. "INSERT INTO public.data_incidents (uid,code,dedup_key,title,severity,status,owner_uid,escalation_level,first_detected_at,last_observed_at,created_by) VALUES (CAST(:uid AS uuid),:code,:digest,'synthetic','critical','open',CAST(:actor AS uuid),1,clock_timestamp(),clock_timestamp(),CAST(:actor AS uuid))"
  183. ),
  184. {
  185. "uid": incident,
  186. "code": f"INC-{incident[:8]}",
  187. "digest": "a" * 64,
  188. "actor": actor,
  189. },
  190. )
  191. connection.execute(
  192. text(
  193. "INSERT INTO public.data_observability_alerts (uid,dedup_key,incident_uid,layer,sli_type,title,severity,status,occurrence_count,escalation_level,owner_uid,first_observed_at,last_observed_at,delivery_status,evidence) VALUES (CAST(:uid AS uuid),:digest,CAST(:incident AS uuid),'service','delivery','synthetic','critical','open',1,1,CAST(:actor AS uuid),clock_timestamp(),clock_timestamp(),'pending','{}')"
  194. ),
  195. {
  196. "uid": alert,
  197. "digest": "b" * 64,
  198. "incident": incident,
  199. "actor": actor,
  200. },
  201. )
  202. repository = SqlAlchemyProductionOperationsRepository(connection)
  203. service = ProductionOperationsService(repository, now=lambda: 100)
  204. delivery = service.enqueue_incident_delivery(
  205. {
  206. "incident_uid": incident,
  207. "alert_uid": alert,
  208. "channel": "on_call",
  209. "summary": "synthetic",
  210. },
  211. actor_uid=actor,
  212. )
  213. claim = service.claim_delivery(delivery["uid"], worker_id="worker-a")
  214. assert (
  215. service.record_delivery_attempt(
  216. claim["uid"],
  217. worker_id="worker-a",
  218. lease_fence=claim["lease_fence"],
  219. delivered=False,
  220. actor_uid=actor,
  221. )["status"]
  222. == "pending"
  223. )
  224. with pytest.raises(ProductionOperationsError):
  225. service.claim_delivery(delivery["uid"], worker_id="worker-b")
  226. connection.execute(
  227. text(
  228. "UPDATE public.production_observability_deliveries SET next_attempt_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid)"
  229. ),
  230. {"uid": delivery["uid"]},
  231. )
  232. again = service.claim_delivery(delivery["uid"], worker_id="worker-b")
  233. assert again["lease_fence"] == 2
  234. with pytest.raises(IntegrityError):
  235. connection.execute(
  236. text(
  237. "INSERT INTO public.production_observability_policies (uid,code,version,layer,error_budget_ratio,status,created_by) VALUES (CAST(:uid AS uuid),'SVC','1.0.0','service',0.1,'active',CAST(:actor AS uuid)),(CAST(:uid2 AS uuid),'SVC','1.1.0','service',0.2,'active',CAST(:actor AS uuid))"
  238. ),
  239. {
  240. "uid": str(uuid.uuid4()),
  241. "uid2": str(uuid.uuid4()),
  242. "actor": actor,
  243. },
  244. )
  245. finally:
  246. with engine.begin() as connection:
  247. connection.execute(
  248. text(
  249. "DELETE FROM public.production_observability_audits WHERE actor_uid=CAST(:actor AS uuid)"
  250. ),
  251. {"actor": actor},
  252. )
  253. connection.execute(
  254. text(
  255. "DELETE FROM public.production_observability_deliveries WHERE incident_uid=CAST(:incident AS uuid)"
  256. ),
  257. {"incident": incident},
  258. )
  259. connection.execute(
  260. text(
  261. "DELETE FROM public.production_observability_policies WHERE created_by=CAST(:actor AS uuid)"
  262. ),
  263. {"actor": actor},
  264. )
  265. connection.execute(
  266. text(
  267. "DELETE FROM public.data_observability_alerts WHERE uid=CAST(:alert AS uuid)"
  268. ),
  269. {"alert": alert},
  270. )
  271. connection.execute(
  272. text(
  273. "DELETE FROM public.data_incidents WHERE uid=CAST(:incident AS uuid)"
  274. ),
  275. {"incident": incident},
  276. )
  277. connection.execute(
  278. text("DELETE FROM public.users WHERE id=CAST(:actor AS uuid)"),
  279. {"actor": actor},
  280. )
  281. engine.dispose()
  282. def test_failed_business_transaction_keeps_independent_rejection_audit():
  283. """RED: audit must not share the business transaction that is rolled back."""
  284. url = os.environ.get("TEST_DATABASE_URL")
  285. if not url:
  286. pytest.skip("TEST_DATABASE_URL is required")
  287. from app.core.events.production_operations_repository import (
  288. SqlAlchemyProductionOperationsRepository,
  289. )
  290. audit_uid = str(uuid.uuid4())
  291. actor = str(uuid.uuid4())
  292. engine = create_engine(url, pool_pre_ping=True)
  293. try:
  294. with engine.begin() as connection:
  295. connection.execute(text("INSERT INTO public.users (id,username,display_name,password_hash,status) VALUES (CAST(:id AS uuid),:name,:name,'test','active')"), {"id": actor, "name": f"wp05audit{actor[:8]}"})
  296. with engine.connect() as connection:
  297. transaction = connection.begin()
  298. SqlAlchemyProductionOperationsRepository(connection).append_rejection_audit_independently({"uid": audit_uid, "actor_uid": actor, "action": "delivery_rejected", "safe_detail": {"reason_code": "synthetic"}})
  299. transaction.rollback()
  300. with engine.connect() as connection:
  301. assert connection.execute(text("SELECT count(*) FROM public.production_observability_audits WHERE uid=CAST(:uid AS uuid)"), {"uid": audit_uid}).scalar_one() == 1
  302. finally:
  303. with engine.begin() as connection:
  304. connection.execute(text("SET LOCAL ROLE dataops_edge_evidence_owner"))
  305. connection.execute(
  306. text(
  307. "ALTER TABLE public.production_observability_audits "
  308. "DISABLE TRIGGER USER"
  309. )
  310. )
  311. connection.execute(text("RESET ROLE"))
  312. connection.execute(text("DELETE FROM public.production_observability_audits WHERE uid=CAST(:uid AS uuid)"), {"uid": audit_uid})
  313. connection.execute(text("SET LOCAL ROLE dataops_edge_evidence_owner"))
  314. connection.execute(
  315. text(
  316. "ALTER TABLE public.production_observability_audits "
  317. "ENABLE TRIGGER USER"
  318. )
  319. )
  320. connection.execute(text("RESET ROLE"))
  321. connection.execute(text("DELETE FROM public.users WHERE id=CAST(:id AS uuid)"), {"id": actor})
  322. engine.dispose()
  323. def test_two_postgresql_workers_barrier_claims_once_without_deadlock(pg_engine):
  324. """RED: database CAS must allow exactly one concurrent lease claimant."""
  325. from app.core.events.production_operations import (
  326. ProductionOperationsError,
  327. ProductionOperationsService,
  328. )
  329. from app.core.events.production_operations_repository import (
  330. SqlAlchemyProductionOperationsRepository,
  331. )
  332. suffix = "barrier-claim"
  333. _cleanup_wp05_test_rows(pg_engine)
  334. try:
  335. with pg_engine.begin() as connection:
  336. ids = _seed(connection, suffix)
  337. delivery = ProductionOperationsService(
  338. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 1
  339. ).enqueue_incident_delivery(
  340. {
  341. "incident_uid": ids["incident"],
  342. "alert_uid": ids["alert"],
  343. "channel": "on_call",
  344. "summary": "wp05 test barrier claim",
  345. },
  346. actor_uid=ids["actor"],
  347. )
  348. barrier = threading.Barrier(2)
  349. outcomes = []
  350. failures = []
  351. lock = threading.Lock()
  352. def claim(worker_id):
  353. try:
  354. with pg_engine.begin() as connection:
  355. service = ProductionOperationsService(
  356. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 1
  357. )
  358. barrier.wait(timeout=5)
  359. try:
  360. result = service.claim_delivery(
  361. delivery["uid"], worker_id=worker_id
  362. )
  363. except ProductionOperationsError as error:
  364. result = error
  365. with lock:
  366. outcomes.append(result)
  367. except BaseException as error: # assertions run on the parent thread
  368. with lock:
  369. failures.append(error)
  370. workers = [
  371. threading.Thread(target=claim, args=("wp05-worker-a",)),
  372. threading.Thread(target=claim, args=("wp05-worker-b",)),
  373. ]
  374. for worker in workers:
  375. worker.start()
  376. for worker in workers:
  377. worker.join(timeout=10)
  378. assert not failures
  379. assert not any(worker.is_alive() for worker in workers)
  380. claimed = [item for item in outcomes if isinstance(item, dict)]
  381. rejected = [item for item in outcomes if isinstance(item, ProductionOperationsError)]
  382. assert len(claimed) == 1
  383. assert len(rejected) == 1
  384. assert claimed[0]["lease_fence"] == 1
  385. finally:
  386. _cleanup_wp05_test_rows(pg_engine)
  387. def test_postgresql_db_time_rejects_expired_old_fence_before_reclaim(pg_engine):
  388. """RED: a late worker cannot write after DB lease expiry, even before reclaim."""
  389. from app.core.events.production_operations import (
  390. ProductionOperationsError,
  391. ProductionOperationsService,
  392. )
  393. from app.core.events.production_operations_repository import (
  394. SqlAlchemyProductionOperationsRepository,
  395. )
  396. suffix = "db-time-fence"
  397. _cleanup_wp05_test_rows(pg_engine)
  398. try:
  399. with pg_engine.begin() as connection:
  400. ids = _seed(connection, suffix)
  401. first = ProductionOperationsService(
  402. SqlAlchemyProductionOperationsRepository(connection), now=lambda: -10_000
  403. )
  404. delivery = first.enqueue_incident_delivery(
  405. {
  406. "incident_uid": ids["incident"],
  407. "alert_uid": ids["alert"],
  408. "channel": "smtp",
  409. "summary": "wp05 test expiry",
  410. },
  411. actor_uid=ids["actor"],
  412. )
  413. claim = first.claim_delivery(delivery["uid"], worker_id="wp05-old-worker")
  414. connection.execute(
  415. text(
  416. "UPDATE public.production_observability_deliveries "
  417. "SET lease_expires_at=clock_timestamp()-interval '1 second' "
  418. "WHERE uid=CAST(:uid AS uuid)"
  419. ),
  420. {"uid": delivery["uid"]},
  421. )
  422. with pg_engine.begin() as connection:
  423. old_worker = ProductionOperationsService(
  424. SqlAlchemyProductionOperationsRepository(connection), now=lambda: -99_999
  425. )
  426. for delivered in (True, False):
  427. with pytest.raises(ProductionOperationsError, match="expired or fenced"):
  428. old_worker.record_delivery_attempt(
  429. delivery["uid"],
  430. worker_id="wp05-old-worker",
  431. lease_fence=claim["lease_fence"],
  432. delivered=delivered,
  433. actor_uid=ids["actor"],
  434. )
  435. with pg_engine.begin() as connection:
  436. recovered = ProductionOperationsService(
  437. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 0
  438. )
  439. renewed = recovered.claim_delivery(
  440. delivery["uid"], worker_id="wp05-new-worker"
  441. )
  442. assert renewed["lease_fence"] == claim["lease_fence"] + 1
  443. assert (
  444. recovered.record_delivery_attempt(
  445. delivery["uid"],
  446. worker_id="wp05-new-worker",
  447. lease_fence=renewed["lease_fence"],
  448. delivered=True,
  449. actor_uid=ids["actor"],
  450. )["status"]
  451. == "delivered"
  452. )
  453. finally:
  454. _cleanup_wp05_test_rows(pg_engine)
  455. def test_postgresql_concurrent_enqueue_enforces_closed_hard_cap(pg_engine):
  456. """RED: concurrent distinct requests cannot exceed one configured queue slot."""
  457. from app.core.events.production_operations import (
  458. ProductionOperationsError,
  459. ProductionOperationsService,
  460. )
  461. from app.core.events.production_operations_repository import (
  462. SqlAlchemyProductionOperationsRepository,
  463. )
  464. suffix = "queue-cap"
  465. _cleanup_wp05_test_rows(pg_engine)
  466. try:
  467. with pg_engine.begin() as connection:
  468. ids = _seed(connection, suffix)
  469. barrier = threading.Barrier(2)
  470. results = []
  471. failures = []
  472. lock = threading.Lock()
  473. def enqueue(channel):
  474. try:
  475. with pg_engine.begin() as connection:
  476. service = ProductionOperationsService(
  477. SqlAlchemyProductionOperationsRepository(connection),
  478. now=lambda: 0,
  479. queue_hard_limit=1,
  480. )
  481. barrier.wait(timeout=5)
  482. try:
  483. result = service.enqueue_incident_delivery(
  484. {
  485. "incident_uid": ids["incident"],
  486. "alert_uid": ids["alert"],
  487. "channel": channel,
  488. "summary": "wp05 test queue cap",
  489. },
  490. actor_uid=ids["actor"],
  491. )
  492. except ProductionOperationsError as error:
  493. result = error
  494. with lock:
  495. results.append(result)
  496. except BaseException as error:
  497. with lock:
  498. failures.append(error)
  499. workers = [
  500. threading.Thread(target=enqueue, args=("monitoring",)),
  501. threading.Thread(target=enqueue, args=("on_call",)),
  502. ]
  503. for worker in workers:
  504. worker.start()
  505. for worker in workers:
  506. worker.join(timeout=10)
  507. assert not failures
  508. assert not any(worker.is_alive() for worker in workers)
  509. accepted = [item for item in results if isinstance(item, dict)]
  510. rejected = [item for item in results if isinstance(item, ProductionOperationsError)]
  511. assert len(accepted) == 1
  512. assert len(rejected) == 1
  513. assert "hard limit" in str(rejected[0])
  514. with pg_engine.begin() as connection:
  515. service = ProductionOperationsService(
  516. SqlAlchemyProductionOperationsRepository(connection),
  517. now=lambda: 0,
  518. queue_hard_limit=1,
  519. )
  520. payload = {
  521. "incident_uid": ids["incident"],
  522. "alert_uid": ids["alert"],
  523. "channel": accepted[0]["channel"],
  524. "summary": "wp05 test queue cap",
  525. }
  526. assert service.enqueue_incident_delivery(payload, actor_uid=ids["actor"])["uid"] == accepted[0]["uid"]
  527. with pytest.raises(ProductionOperationsError, match="idempotency conflict"):
  528. service.enqueue_incident_delivery(
  529. {**payload, "summary": "wp05 changed replay"}, actor_uid=ids["actor"]
  530. )
  531. assert connection.execute(
  532. text(
  533. "SELECT count(*) FROM public.production_observability_deliveries "
  534. "WHERE incident_uid=CAST(:incident AS uuid)"
  535. ),
  536. {"incident": ids["incident"]},
  537. ).scalar_one() == 1
  538. finally:
  539. _cleanup_wp05_test_rows(pg_engine)
  540. def test_new_repository_instances_restore_operations_state_and_references(pg_engine):
  541. """RED: persisted rows, policies, and both ITSM directions survive a restart."""
  542. from app.core.events.production_operations import (
  543. ProductionOperationsError,
  544. ProductionOperationsService,
  545. )
  546. from app.core.events.production_operations_repository import (
  547. SqlAlchemyProductionOperationsRepository,
  548. )
  549. suffix = "restart-state"
  550. _cleanup_wp05_test_rows(pg_engine)
  551. try:
  552. with pg_engine.begin() as connection:
  553. ids = _seed(connection, suffix)
  554. first = ProductionOperationsService(
  555. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 0
  556. )
  557. pending = first.enqueue_incident_delivery(
  558. {
  559. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  560. "channel": "monitoring", "summary": "wp05 pending",
  561. }, actor_uid=ids["actor"]
  562. )
  563. expired = first.enqueue_incident_delivery(
  564. {
  565. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  566. "channel": "smtp", "summary": "wp05 expired",
  567. }, actor_uid=ids["actor"]
  568. )
  569. first.claim_delivery(expired["uid"], worker_id="wp05-expired-worker")
  570. connection.execute(text(
  571. "UPDATE public.production_observability_deliveries "
  572. "SET lease_expires_at=clock_timestamp()-interval '1 second' "
  573. "WHERE uid=CAST(:uid AS uuid)"), {"uid": expired["uid"]})
  574. dead = first.enqueue_incident_delivery(
  575. {
  576. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  577. "channel": "on_call", "summary": "wp05 dead letter",
  578. }, actor_uid=ids["actor"]
  579. )
  580. connection.execute(text(
  581. "UPDATE public.production_observability_deliveries SET "
  582. "status='dead_letter',attempt_count=3,lease_owner=NULL,"
  583. "lease_expires_at=NULL WHERE uid=CAST(:uid AS uuid)"),
  584. {"uid": dead["uid"]})
  585. first.create_policy(
  586. {"code": "WP05_TEST", "version": "1.0.0", "layer": "service", "error_budget_ratio": 0.1},
  587. actor_uid=ids["actor"],
  588. )
  589. first.activate_policy("WP05_TEST", "1.0.0", actor_uid=ids["actor"])
  590. itsm = first.bind_itsm_reference(
  591. incident_uid=ids["incident"], external_ref="WP05-TEST-ITSM-1",
  592. idempotency_key="wp05-test-itsm-restart", actor_uid=ids["actor"],
  593. )
  594. with pg_engine.begin() as connection:
  595. restored = ProductionOperationsService(
  596. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 0
  597. )
  598. assert {item["status"] for item in restored.list_deliveries(ids["incident"])} == {
  599. "pending", "processing", "dead_letter"
  600. }
  601. assert restored.active_policy("WP05_TEST")["version"] == "1.0.0"
  602. assert restored.itsm_reference_for_incident(ids["incident"]) == itsm
  603. assert restored.platform_incident_for_itsm("WP05-TEST-ITSM-1") == ids["incident"]
  604. assert restored.bind_itsm_reference(
  605. incident_uid=ids["incident"], external_ref="WP05-TEST-ITSM-1",
  606. idempotency_key="wp05-test-itsm-restart", actor_uid=ids["actor"],
  607. ) == itsm
  608. with pytest.raises(ProductionOperationsError, match="idempotency conflict"):
  609. restored.bind_itsm_reference(
  610. incident_uid=ids["incident"], external_ref="WP05-TEST-ITSM-CHANGED",
  611. idempotency_key="wp05-test-itsm-restart", actor_uid=ids["actor"],
  612. )
  613. renewed = restored.claim_delivery(expired["uid"], worker_id="wp05-restarted")
  614. assert renewed["lease_fence"] == 2
  615. assert restored.record_delivery_attempt(
  616. renewed["uid"], worker_id="wp05-restarted", lease_fence=renewed["lease_fence"],
  617. delivered=True, actor_uid=ids["actor"],
  618. )["status"] == "delivered"
  619. assert restored.enqueue_incident_delivery(
  620. {
  621. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  622. "channel": "monitoring", "summary": "wp05 pending",
  623. }, actor_uid=ids["actor"]
  624. )["uid"] == pending["uid"]
  625. with pytest.raises(ProductionOperationsError, match="idempotency conflict"):
  626. restored.enqueue_incident_delivery(
  627. {
  628. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  629. "channel": "monitoring", "summary": "wp05 changed pending",
  630. }, actor_uid=ids["actor"]
  631. )
  632. finally:
  633. _cleanup_wp05_test_rows(pg_engine)
  634. def test_480_migration_round_trip_and_constraints(pg_engine):
  635. """RED: 479↔480 is reversible when exact wp05 test data is absent."""
  636. _cleanup_wp05_test_rows(pg_engine)
  637. try:
  638. _alembic("downgrade", "20260809_479")
  639. with pg_engine.connect() as connection:
  640. assert connection.execute(text("SELECT version_num FROM alembic_version")).scalar_one() == "20260809_479"
  641. assert connection.execute(text("SELECT to_regclass('public.production_observability_deliveries')")).scalar_one() is None
  642. _alembic("upgrade", "20260811_480")
  643. with pg_engine.connect() as connection:
  644. assert connection.execute(text("SELECT version_num FROM alembic_version")).scalar_one() == "20260811_480"
  645. tables = connection.execute(text("""
  646. SELECT relname FROM pg_class JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
  647. WHERE nspname='public' AND relname LIKE 'production_observability_%' AND relkind='r'
  648. """)).scalars().all()
  649. assert {
  650. "production_observability_deliveries",
  651. "production_observability_audits",
  652. "production_observability_policies",
  653. "production_observability_itsm_references",
  654. } <= set(tables)
  655. constraints = connection.execute(text("""
  656. SELECT conname FROM pg_constraint
  657. WHERE conrelid='public.production_observability_deliveries'::regclass
  658. """)).scalars().all()
  659. assert "production_observability_deliveries_status_check" in constraints
  660. itsm_constraints = connection.execute(text("""
  661. SELECT conname FROM pg_constraint
  662. WHERE conrelid='public.production_observability_itsm_references'::regclass
  663. """)).scalars().all()
  664. assert {
  665. "production_observability_itsm_references_incident_uid_key",
  666. "production_observability_itsm_references_external_ref_key",
  667. "production_observability_itsm_references_idempotency_key_key",
  668. } <= set(itsm_constraints)
  669. finally:
  670. _alembic("upgrade", "head")
  671. def test_runtime_cannot_mutate_append_only_audit_but_controlled_function_writes(pg_engine):
  672. """RED: runtime has SELECT+EXECUTE only; evidence remains after business rollback."""
  673. from app.core.events.production_operations_repository import (
  674. SqlAlchemyProductionOperationsRepository,
  675. )
  676. suffix = "audit-ledger"
  677. _cleanup_wp05_test_rows(pg_engine)
  678. try:
  679. with pg_engine.begin() as connection:
  680. ids = _seed(connection, suffix)
  681. audit_uid = str(uuid.uuid4())
  682. repository = SqlAlchemyProductionOperationsRepository(connection)
  683. repository.append_audit(
  684. {
  685. "uid": audit_uid,
  686. "delivery_uid": None,
  687. "action": "delivery_enqueued",
  688. "actor_uid": ids["actor"],
  689. "safe_detail": {"channel": "on_call"},
  690. }
  691. )
  692. assert connection.execute(
  693. text("SELECT count(*) FROM public.production_observability_audits WHERE uid=CAST(:uid AS uuid)"),
  694. {"uid": audit_uid},
  695. ).scalar_one() == 1
  696. for statement in (
  697. "INSERT INTO public.production_observability_audits (uid,action,actor_uid,safe_detail) VALUES (gen_random_uuid(),'delivery_enqueued',CAST(:actor AS uuid),'{}')",
  698. "UPDATE public.production_observability_audits SET action='delivery_delivered'",
  699. "DELETE FROM public.production_observability_audits",
  700. "TRUNCATE public.production_observability_audits",
  701. ):
  702. with pg_engine.begin() as connection:
  703. connection.execute(text("SET LOCAL ROLE dataops_app_runtime"))
  704. with pytest.raises(DBAPIError):
  705. connection.execute(text(statement), {"actor": ids["actor"]})
  706. finally:
  707. _cleanup_wp05_test_rows(pg_engine)
  708. def test_persistent_dead_letter_compensation_is_atomic_and_exact(pg_engine):
  709. """RED: only dead_letter can compensate; same receipt replays, a changed one conflicts."""
  710. from app.core.events.production_operations import (
  711. ProductionOperationsError,
  712. ProductionOperationsService,
  713. )
  714. from app.core.events.production_operations_repository import (
  715. SqlAlchemyProductionOperationsRepository,
  716. )
  717. suffix = "compensation"
  718. _cleanup_wp05_test_rows(pg_engine)
  719. try:
  720. with pg_engine.begin() as connection:
  721. ids = _seed(connection, suffix)
  722. service = ProductionOperationsService(
  723. SqlAlchemyProductionOperationsRepository(connection), now=lambda: 1
  724. )
  725. delivery = service.enqueue_incident_delivery(
  726. {
  727. "incident_uid": ids["incident"], "alert_uid": ids["alert"],
  728. "channel": "on_call", "summary": "compensation test",
  729. },
  730. actor_uid=ids["actor"],
  731. )
  732. connection.execute(
  733. text("UPDATE public.production_observability_deliveries SET status='dead_letter' WHERE uid=CAST(:uid AS uuid)"),
  734. {"uid": delivery["uid"]},
  735. )
  736. compensated = service.compensate_dead_letter(
  737. delivery["uid"], reason_code="operator_review",
  738. receipt_code="OPS_REPLAY_1", actor_uid=ids["actor"],
  739. )
  740. assert compensated["status"] == "compensated"
  741. assert service.compensate_dead_letter(
  742. delivery["uid"], reason_code="operator_review",
  743. receipt_code="OPS_REPLAY_1", actor_uid=ids["actor"],
  744. )["uid"] == delivery["uid"]
  745. with pytest.raises(ProductionOperationsError, match="compensation conflict"):
  746. service.compensate_dead_letter(
  747. delivery["uid"], reason_code="provider_recovered",
  748. receipt_code="OPS_REPLAY_2", actor_uid=ids["actor"],
  749. )
  750. finally:
  751. _cleanup_wp05_test_rows(pg_engine)