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
40 changes: 32 additions & 8 deletions scripts/check_adjoint_prevalidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,28 @@ def _is_bot_advisory_pose(row: dict[str, Any]) -> bool:
return _bot_advisory_key(row) is not None


def _foreign_tail_rows(
comments: list[dict[str, Any]], dossier: Dossier
) -> list[dict[str, Any]]:
"""Commentaires posterieurs au dossier qui periment reellement le fil.

Predicat canonique partage par le gate et la queue : une row tardive
n'est etrangere que si elle n'est ni la voix du coordinateur
(``_is_own_later_act``) ni la pose consultative d'un bot marker-garde
(``_is_bot_advisory_pose``). La queue comptait ces lignes en brut avant
#16480-c2151 et reintroduisait, par un simple ``len``, exactement ce que
#17818/#18637 avaient neutralise cote gate (mesure 11/10 : #20094 et
#20372 STALE sur la seule pose PATH-COLLISION de ``github-actions[bot]``,
6 PR sur 102 a 18:21Z).
"""
return [
row
for row in comments[dossier.comment_index + 1:]
if not _is_own_later_act(row, "createdAt", dossier.created_at)
and not _is_bot_advisory_pose(row)
]


def _review_body_has_reserve_marker(author: str, body: str) -> bool:
"""True quand, en substance, cette review pose une reserve vivante.

Expand Down Expand Up @@ -1504,12 +1526,7 @@ def evaluate_with_dossier(
# wrote it, which it cannot be unaware of (see _is_own_later_act), or it
# is the first pose of a consultative marker-guarded bot comment, which
# attests nothing about the PR's substance (see _is_bot_advisory_pose).
foreign = [
row
for row in comments[dossier.comment_index + 1:]
if not _is_own_later_act(row, "createdAt", dossier.created_at)
and not _is_bot_advisory_pose(row)
]
foreign = _foreign_tail_rows(comments, dossier)
if foreign:
errors.append(
"discussion changed after dossier: a fresh adjoint preflight is required"
Expand Down Expand Up @@ -1819,15 +1836,22 @@ def entry(verdict_value: str, cause: str = "") -> QueueEntry:
)

tail = max(0, len(comments) - 1 - dossier.comment_index)
foreign = _foreign_tail_rows(comments, dossier)
age_minutes: int | None = None
if dossier.created_at:
stamp = _parse_ts(dossier.created_at)
if stamp is not None:
age_minutes = max(0, int((now - stamp).total_seconds() // 60))
if tail > 0:
if foreign:
# #16480-c2151 : le gate vient de valider ce dossier contre les
# memes predicats, donc un STALE ici ne leve que sur des lignes NON
# neutralisees -- le compte brut perimait des dossiers valides sur
# la seule pose consultative d'un bot (mesure 11/10 : #20094,
# #20372). ``tail`` reste le compte brut, a titre d'information.
return entry(
QUEUE_VERDICT_STALE,
f"{tail} comment(s) posted after the dossier -- re-read the tail, "
f"{len(foreign)} foreign comment(s) posted after the dossier "
f"({tail} total) -- re-read the tail, "
"the exact-head contract no longer holds",
)
if age_minutes is not None and age_minutes > stale_after_minutes:
Expand Down
45 changes: 45 additions & 0 deletions scripts/tests/test_check_adjoint_prevalidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3098,6 +3098,51 @@ def test_queue_comment_after_dossier_is_stale():
snapshot["comments"].append(_comment("late concern"))
entry = _classified(snapshot)
assert entry.verdict == mod.QUEUE_VERDICT_STALE
# Le commentaire humain tardif fait echouer le GATE : la queue relaye le
# refus, elle n'a plus de compte brut propre a contredire (c2151).
assert "discussion changed after dossier" in entry.reject_cause


def test_queue_bot_advisory_after_dossier_is_ready():
"""#16480-c2151 (controle positif, queue) : la pose consultative d'un bot
apres le dossier ne rend PAS la file STALE. Le gate neutralise deja ces
poses (#17818 marker-guards, #18637 sticky/resumes) ; la queue comptait
le tail en brut et reintroduisait exactement ce que ces correctifs avaient
retire. Mesure fondatrice : #20094 et #20372 STALE sur la seule pose
PATH-COLLISION de ``github-actions[bot]`` (11/10, 6 PR sur 102 a 18:21Z).
"""
bot_forms = (
"<!-- PR-PATH-COLLISION:START -->\n## Path-collision (organ #1)\npaire: X / Y",
_STICKY_STALE,
)
for body in bot_forms:
snapshot = _reviewed_snapshot()
snapshot["comments"].append(_comment(body, login="github-actions[bot]"))
entry = _classified(snapshot)
assert entry.verdict == mod.VERDICT_READY, body
# Le compte brut survit en information : la ligne reste a relire.
assert entry.tail_to_read == 1, body


def test_queue_coordinator_late_comment_after_dossier_is_ready():
"""Voix du coordinateur apres le dossier : neutre pour le gate
(``_is_own_later_act``, horodatee apres le dossier), neutre pour la
queue -- meme predicat partage (_foreign_tail_rows).
"""
snapshot = _reviewed_snapshot()
snapshot["comments"][-1] = {
"author": {"login": "jsboige"},
"body": snapshot["comments"][-1]["body"],
"createdAt": "2026-10-08T18:00:00Z",
}
snapshot["comments"].append({
"author": {"login": "myia-ai-01"},
"body": "reserve levee par ecrit sur la tete exacte",
"createdAt": "2026-10-08T19:00:00Z",
})
entry = _classified(snapshot)
assert entry.verdict == mod.VERDICT_READY
assert entry.tail_to_read == 1


def test_queue_dossier_older_than_stale_after_is_stale():
Expand Down
Loading