Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions scripts/check_closure_dossier.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,14 @@ def validate_dossier(dossier: Dossier, snapshot: dict[str, Any],

def evaluate(snapshot: dict[str, Any],
replay: bool = False) -> tuple[str, list[str], Dossier | None]:
"""(verdict, errors, dossier) -- verdict CLOSE / KEEP / NO-DOSSIER."""
"""(verdict, errors, dossier) -- verdict CLOSE / KEEP / NO-DOSSIER.

Seul le DERNIER dossier compte (latest-wins, meme regle que le gate PR,
#18095) : un dossier plus ancien est remplace, jamais une erreur ni une
peremption du plus recent. Evaluer le premier laissait un dossier
malforme ou un KEEP perime masquer indefiniment le dossier correct
poste apres lui."""
latest: tuple[Dossier, list[str]] | None = None
for index, row in enumerate(snapshot["comments"]):
body = row.get("body") or ""
if not body.strip().startswith(START):
Expand All @@ -435,16 +442,17 @@ def evaluate(snapshot: dict[str, Any],
(row.get("author") or {}).get("login", ""),
row.get("created_at", ""),
)
if dossier is None:
continue
errors.extend(validate_dossier(dossier, snapshot, replay))
verdict = dossier.fields.get("verdict", "")
if errors:
return ("REFUSED", errors, dossier)
if verdict == VERDICT_CLOSE:
return (VERDICT_CLOSE, [], dossier)
return (VERDICT_KEEP, [], dossier)
return ("NO-DOSSIER", ["no [CLOSURE PREFLIGHT] comment on this issue"], None)
if dossier is not None:
latest = (dossier, errors)
if latest is None:
return ("NO-DOSSIER", ["no [CLOSURE PREFLIGHT] comment on this issue"], None)
dossier, errors = latest
errors.extend(validate_dossier(dossier, snapshot, replay))
if errors:
return ("REFUSED", errors, dossier)
if dossier.fields.get("verdict", "") == VERDICT_CLOSE:
return (VERDICT_CLOSE, [], dossier)
return (VERDICT_KEEP, [], dossier)


def render_template(snapshot: dict[str, Any], lane: str) -> str:
Expand Down
60 changes: 60 additions & 0 deletions scripts/tests/test_check_closure_dossier.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,66 @@ def test_commentaire_neutre_posterieur_ne_perime_pas():
assert errors == []


# --- latest-wins : seul le dernier dossier compte (#18095) -------------------

def test_dossier_malforme_puis_dossier_valide_ferme():
# Controle positif (forme #16916) : un premier dossier malforme ne masque
# plus le dossier correct poste apres lui.
malformed = _dossier_body().replace("residue: none\n", "")
snap = _snapshot(comments=[
_comment(malformed, created="2026-09-20T10:00:00Z"),
_comment(_dossier_body(comments_reviewed=1),
created="2026-09-21T10:00:00Z"),
])
verdict, errors, dossier = evaluate(snap)
assert verdict == "CLOSE"
assert errors == []
assert dossier.comment_index == 1


def test_dossier_valide_puis_dossier_malforme_refuse():
# Controle negatif : le dernier dossier fait foi, meme malforme -- un
# dossier valide plus ancien ne le rattrape pas.
malformed = _dossier_body(comments_reviewed=1).replace("residue: none\n", "")
snap = _snapshot(comments=[
_comment(_dossier_body(), created="2026-09-20T10:00:00Z"),
_comment(malformed, created="2026-09-21T10:00:00Z"),
])
verdict, errors, dossier = evaluate(snap)
assert verdict == "REFUSED"
assert any("missing fields: residue" in e for e in errors)
assert dossier.comment_index == 1


def test_keep_puis_close_rend_close(monkeypatch):
monkeypatch.setattr(ccd, "gh_json", lambda args: {
"number": 17910, "state": "OPEN"})
snap = _snapshot(comments=[
_comment(_dossier_body(verdict="KEEP", residue="followup #17910"),
created="2026-09-20T10:00:00Z"),
_comment(_dossier_body(comments_reviewed=1),
created="2026-09-21T10:00:00Z"),
])
verdict, errors, _ = evaluate(snap)
assert verdict == "CLOSE"
assert errors == []


def test_close_puis_keep_rend_keep(monkeypatch):
# Symetrique : un KEEP recent retire un CLOSE plus ancien.
monkeypatch.setattr(ccd, "gh_json", lambda args: {
"number": 17910, "state": "OPEN"})
snap = _snapshot(comments=[
_comment(_dossier_body(), created="2026-09-20T10:00:00Z"),
_comment(_dossier_body(verdict="KEEP", residue="followup #17910",
comments_reviewed=1),
created="2026-09-21T10:00:00Z"),
])
verdict, errors, _ = evaluate(snap)
assert verdict == "KEEP"
assert errors == []


def test_pr_ouverte_referencant_lissue_refuse():
# Le contrat dit open-prs: 0 ; le live en trouve une -> perime.
snap = _snapshot(comments=[_comment(_dossier_body())], open_prs=[18050])
Expand Down
Loading