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
63 changes: 41 additions & 22 deletions scripts/check_adjoint_prevalidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,31 @@ def validate_dossier(dossier: Dossier, snapshot: dict[str, Any]) -> list[str]:
return errors


def covered_blocked_dossier(
previous: list[Dossier], head: str
) -> Dossier | None:
"""Dernier dossier BLOCKED anterieur a la meme tete, ou ``None`` (#19869).

Cœur unique de la detection de couverture. Le gate
(`mute_contradictions`) et l'emetteur (`find_previous_blocked_same_head`,
qui alimente le pre-remplissage de `--emit`) doivent voir **le meme**
dossier : deux recherches independantes derivent, et l'emetteur finit par
pre-remplir un `supersedes` que le gate refuse -- c'est exactement le
defaut rapporte par #19869, ou le seul organe qui mordait etait celui
qu'on interrogeait en second.

La recherche est **tete a tete** : un dossier anterieur sur une tete
differente est deja perime par exact-head, et il n'y a rien a refuter.
"""
for dossier in reversed(previous):
if (
dossier.fields.get("verdict") == VERDICT_BLOCKED
and dossier.fields.get("head") == head
):
return dossier
return None


def mute_contradictions(
dossier: Dossier,
candidates: list[tuple[Dossier, list[str]]],
Expand All @@ -1292,14 +1317,9 @@ def mute_contradictions(
"""
if dossier.fields.get("verdict") != VERDICT_READY:
return []
covered = next(
(
previous
for previous, _errors in reversed(candidates[:-1])
if previous.fields.get("verdict") == VERDICT_BLOCKED
and previous.fields.get("head") == dossier.fields.get("head")
),
None,
covered = covered_blocked_dossier(
[previous for previous, _errors in candidates[:-1]],
dossier.fields.get("head") or "",
)
if covered is None:
return []
Expand Down Expand Up @@ -1706,34 +1726,33 @@ def render_template(snapshot: dict[str, Any], lane: str = ADJOINT_LANE) -> str:
def find_previous_blocked_same_head(
snapshot: dict[str, Any], current_head: str
) -> tuple[int, Dossier] | None:
"""#19869 -- miroir de `mute_contradictions` pour l'emetteur.
"""#19869 -- dossier BLOCKED anterieur a la meme tete, vu par l'emetteur.

Trouve le dossier BLOCKED anterieur a la meme tete, miroir de la
recherche que `mute_contradictions` effectue au moment du gate. La
Lit le fil, puis **delegue** a `covered_blocked_dossier` : la recherche
que l'emetteur emploie pour pre-remplir `supersedes` est litteralement
celle du gate, pas un miroir qui pourrait en diverger (#19869). La
position est 1-based (celle que `restamp_warning` affiche deja), pour
que l'auto-remplissage par `--emit` rime avec le verdict du gate sans
qu'aucune re-edition soit necessaire.

Renvoie ``(position, Dossier)`` du dossier BLOCKED anterieur, ou
``None`` si rien ne correspond. La recherche est **tete-a-tete** : un
dossier anterieur sur une tete differente est deja perime par
exact-head, et il n'y a rien a refuter.
``None`` si rien ne correspond.
"""
comments = snapshot.get("comments") or []
for index in range(len(comments) - 1, -1, -1):
comment = comments[index]
dossiers: list[Dossier] = []
for index, comment in enumerate(comments):
dossier, _errors = parse_dossier(
comment.get("body") or "",
index,
_login(comment),
comment.get("createdAt") or "",
)
if dossier is None:
continue
if (dossier.fields.get("verdict") == VERDICT_BLOCKED
and dossier.fields.get("head") == current_head):
return (index + 1, dossier)
return None
if dossier is not None:
dossiers.append(dossier)
covered = covered_blocked_dossier(dossiers, current_head)
if covered is None:
return None
return (covered.comment_index + 1, covered)


def render_emitted_dossier(
Expand Down
75 changes: 75 additions & 0 deletions scripts/tests/test_check_adjoint_prevalidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,81 @@ def test_find_previous_blocked_same_head_ignores_ready_predecessors():
assert dossier.fields.get("verdict") == mod.VERDICT_BLOCKED


def test_emitter_and_gate_name_the_same_covered_dossier():
"""#19869 -- l'emetteur et le gate designent le MEME dossier couvert.

C'est le controle causal du defaut : deux recherches independantes
derivent, et l'emetteur pre-remplit alors un `supersedes` que le gate
refuse. Ici la position rendue par `find_previous_blocked_same_head`
(ce que `--emit` ecrit) et celle que le gate cite dans son refus
doivent coincider -- sur la meme pile de dossiers.
"""
snapshot = _two_blocked_then_ready()
found = mod.find_previous_blocked_same_head(snapshot, HEAD)
assert found is not None
emitted_position, _dossier = found
# Deux BLOCKED sur la meme tete : l'ordre compte (le plus recent gagne).
# Sans cette discrimination, une recherche divergente passerait inapercue.
assert emitted_position == 3

_verdict, errors = mod.evaluate(snapshot)
assert len(errors) == 1
# Le gate nomme le dossier couvert en prose : "comment <pos> of <N>".
assert f"comment {emitted_position} of" in errors[0]
assert f"'supersedes: {emitted_position}'" in errors[0]


def _two_blocked_then_ready() -> dict:
"""Pile : ordinary (1), BLOCKED (2), BLOCKED (3), READY muet (4).

Deux BLOCKED a la meme tete font que l'ORDRE discrimine : le dossier
couvert est le plus recent (position 3), pas le premier. Un READY final
est ce qui declenche le refus du gate -- un BLOCKED apres un BLOCKED
n'exige rien (direction conservatrice de #18934).
"""
snapshot = _base_snapshot()
for index in (1, 2):
fields = {
"verdict": "BLOCKED",
"b0": "blocked",
"comments-reviewed": str(index),
"head": HEAD,
}
fields["surfaces-sha256"] = mod.surfaces_fingerprint(snapshot, index)
snapshot["comments"].append(_comment(_body(**fields)))
ready = {"comments-reviewed": "3"}
ready["surfaces-sha256"] = mod.surfaces_fingerprint(snapshot, 3)
snapshot["comments"].append(_comment(_body(**ready)))
return snapshot


def test_covered_blocked_dossier_is_the_shared_core():
"""Le cœur unique est appele par les DEUX chemins (#19869).

Une recherche en dur reintroduite d'un cote seul ferait diverger les
organes sans qu'aucun test ne rougisse : ce controle verifie que
`mute_contradictions` ET `find_previous_blocked_same_head` passent bien
par `covered_blocked_dossier`.
"""
snapshot = _two_blocked_then_ready()
dossiers = []
for index, comment in enumerate(snapshot["comments"]):
dossier, _errors = mod.parse_dossier(
comment.get("body") or "",
index,
comment.get("author", {}).get("login") or "jsboige",
comment.get("createdAt") or "",
)
if dossier is not None:
dossiers.append(dossier)
expected = mod.covered_blocked_dossier(dossiers, HEAD)
assert expected is not None
assert expected.fields.get("verdict") == mod.VERDICT_BLOCKED

found = mod.find_previous_blocked_same_head(snapshot, HEAD)
assert found == (expected.comment_index + 1, expected)


def test_render_emitted_dossier_autofills_supersedes_for_ready(monkeypatch):
"""Un READY au-dessus d'un BLOCKED a meme tete : supersedes+why poses
automatiquement, dans le bloc, avant END, par --emit."""
Expand Down
Loading