feat(qw4): hook safety_checks_provider + extension /replay/resume avec acquittements
replay_state enrichi de safety_checks, checks_acknowledged, pause_reason,
pause_payload (audit trail).
Branche supervisée pause_for_human :
- appel build_pause_payload() avant bascule paused_need_help
- log [BUS] lea:safety_checks_generated (count, sources)
- fallback safe sur exception (pause sans checks plutôt que crash)
- déclenchement si safety_level/safety_checks déclarés OU execution_mode != autonomous
- sinon comportement legacy (skip silencieux)
POST /replay/resume :
- accepte body { acknowledged_check_ids: [...] }
- vérifie tous les checks required acquittés, sinon 400 required_checks_missing
- stocke checks_acknowledged comme audit trail
- nettoie safety_checks/pause_payload après reprise
Proxy VWB /api/v3/replay/resume → streaming /replay/{id}/resume (forward bearer
token + acknowledged_check_ids).
Backward 100% : workflows sans safety_checks → resume sans acquittement requis.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
tests/integration/test_replay_resume_acknowledgments.py
Normal file
52
tests/integration/test_replay_resume_acknowledgments.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# tests/integration/test_replay_resume_acknowledgments.py
|
||||
"""Tests intégration : /replay/resume valide les acquittements de safety_checks (QW4)."""
|
||||
import pytest
|
||||
|
||||
|
||||
def test_resume_accepts_when_all_required_acknowledged():
|
||||
"""État pause + tous required acquittés → reprise OK."""
|
||||
state = {
|
||||
"status": "paused_need_help",
|
||||
"safety_checks": [
|
||||
{"id": "c1", "label": "X", "required": True, "source": "declarative", "evidence": None},
|
||||
{"id": "c2", "label": "Y", "required": True, "source": "declarative", "evidence": None},
|
||||
],
|
||||
"checks_acknowledged": [],
|
||||
}
|
||||
# Simuler la validation côté serveur
|
||||
acknowledged = ["c1", "c2"]
|
||||
required_ids = {c["id"] for c in state["safety_checks"] if c["required"]}
|
||||
missing = required_ids - set(acknowledged)
|
||||
assert missing == set() # rien ne manque → reprise OK
|
||||
|
||||
|
||||
def test_resume_rejects_when_required_missing():
|
||||
"""État pause + un required non acquitté → 400 required_checks_missing."""
|
||||
state = {
|
||||
"status": "paused_need_help",
|
||||
"safety_checks": [
|
||||
{"id": "c1", "label": "X", "required": True, "source": "declarative", "evidence": None},
|
||||
{"id": "c2", "label": "Y", "required": False, "source": "llm_contextual", "evidence": "..."},
|
||||
],
|
||||
"checks_acknowledged": [],
|
||||
}
|
||||
acknowledged = ["c2"] # only optional
|
||||
required_ids = {c["id"] for c in state["safety_checks"] if c["required"]}
|
||||
missing = required_ids - set(acknowledged)
|
||||
assert missing == {"c1"} # c1 manquant → resume doit retourner 400
|
||||
|
||||
|
||||
def test_resume_audit_trail_stored():
|
||||
"""checks_acknowledged contient les ids reçus (audit)."""
|
||||
state = {
|
||||
"status": "paused_need_help",
|
||||
"safety_checks": [
|
||||
{"id": "c1", "required": True, "label": "X", "source": "declarative", "evidence": None},
|
||||
],
|
||||
"checks_acknowledged": [],
|
||||
}
|
||||
acknowledged = ["c1"]
|
||||
state["checks_acknowledged"] = acknowledged
|
||||
state["status"] = "running"
|
||||
assert state["checks_acknowledged"] == ["c1"]
|
||||
assert state["status"] == "running"
|
||||
Reference in New Issue
Block a user