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
92 changes: 91 additions & 1 deletion scripts/check_unaddressed_nits.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,24 @@
)
OVERRIDE_LANE = _OVERRIDE_LANE

# #16764 classe 2 -- siege qualifiant (contrat #15511). Quand le reviewer
# sous contrat #15511 ne peut emettre que COMMENT (self-review cap #3219 :
# il poste sous l'identite partagee jsboige), SA review nomme le relais
# « siege qualifiant » -- l'arbitre tiers designe par le contrat. La
# reconnaissance est bornee des DEUX cotes : le NIT declare le relais
# (corps du reviewer, recherche apres _strip_quoted) ET la LEVEE
# revendique le siege en TETE de ligne (meme ancre de pose que
# _OVERRIDE_LANE, #13030 -- une citation en milieu de phrase ne compte
# pas) ET l'auteur de la levee EST le siege (LIFT_OVERRIDE_LOGINS).
# Instance fondatrice #16608 : review Hermes r.5242448146 « relais a un
# siege qualifiant », levee d'ai-01 c.5728784093 « ## Siege qualifiant --
# les deux reserves sont levees » -- valide au contrat, invisible pour
# l'organe (la trappe override exigeait le marqueur [OVERRIDE]).
_QUALIFYING_SEAT_BODY_RE = re.compile(
r"si[èe]ge[ \t]+qualifiant", re.IGNORECASE)
_QUALIFYING_SEAT_HEAD_RE = re.compile(
r"(?m)^[#>*+\-\s]*si[èe]ge[ \t]+qualifiant\b", re.IGNORECASE)

# #14461 -- un marqueur d'override EN TÊTE est TOUT token bracketé dont
# l'étiquette porte OVERRIDE, quel que soit le garde émetteur : `[OVERRIDE]`,
# `[G-VAR-3 OVERRIDE]`, et les futurs overrides de gardes adjacents. Le
Expand Down Expand Up @@ -3540,6 +3558,60 @@ def _cited_shas(body: str) -> set[str]:
return out


# #16764 classe 1 -- un SHA cite dans une levee a deux usages syntaxiques :
# DATER la reserve (identifier LAQUELLE on leve : « la reserve posee sur
# <sha> », « (review 05:48Z, head <sha>) ») ou PROUVR qu'elle est traitee
# (nommer le commit qui l'adresse : « traitee en <sha> »). Le refus #13639
# ne doit viser que la preuve : un SHA de datation designe l'ETAT ou la
# reserve vivait, son rembobinage est attendu et ne desnue rien. Instance
# fondatrice : override d'ai-01 du 2026-09-18T20:46:45Z sur #16657
# (r.5252462567) « Je leve la reserve ... (review 05:48:49Z, head
# `c3095774`) » refuse, reposte 53 s plus tard sans aucun SHA
# (r.5252468566) -- la levee finale MOINS precise que la refusee. Un gate
# qui force a deformer la prose pour passer entraîne a ecrire pour
# l'organe. Gouverneurs bornes (set ferme, fenetre courte), jamais la
# prose libre (#14682).
_SHA_GOVERNOR_WINDOW = 60
_SHA_DATING_HEAD = re.compile(
r"\bhead\b[ \t]*(?:anterieur|precedent|courant|actuel)?[ \t]*[`'«]?\s*$")
_SHA_DATING_POSED = re.compile(
r"\b(?:posee?|posees|emise?|emises)\s+(?:sur|dans|au)\b[ \t]*[`'«]?\s*$")
_SHA_DATING_RESERVE = re.compile(
r"\b(?:reserve|nit|constat|concern|review|verdict)\b[^.\n]{0,40}?"
r"\b(?:sur|dans|de)\b[ \t]*[`'«]?\s*$")
# Anti-collision : « traitee sur le head anterieur <sha> » gouverne le head
# par un VERBE D'ADRESSE -- c'est une preuve vieillie, pas une datation.
# Le verbe + preposition dans la meme fenetre retire l'exemption.
_SHA_DATING_COLLIDES = re.compile(
r"\b(?:traite|traites|traitee|traitees|corrige|corriges|corrigee|"
r"corrigees|adresse|adresses|adressee|adressees|livre|livres|livree|"
r"livrees|repondu|repondue|reponse|fixe|fixes|fixee|fixees)\w*\s+"
r"(?:en|par|dans|avec|sur)\b[^.\n]{0,25}$")


def _sha_dates_reserve(lift_body: str, sha: str) -> bool:
"""Le SHA est-il gouverne par la DATATION (nomme la reserve) ?

Regarde la fenetre de caracteres AVANT chaque occurrence du SHA (corps
unaccente, minuscule) : le gouverneur d'un SHA est ce qui le precede
immediatement. Datation = apposition de head (« (review ..., head
<sha>) », « sur le head anterieur <sha> »), pose de la reserve
(« posee sur <sha> »), ou mot de reserve suivi de sur/dans/de
(« la reserve sur <sha> », « le nit de <sha> »).
"""
norm = _unaccent(lift_body or "").lower()
start = 0
while (i := norm.find(sha, start)) != -1:
window = norm[max(0, i - _SHA_GOVERNOR_WINDOW):i]
if not _SHA_DATING_COLLIDES.search(window):
if (_SHA_DATING_HEAD.search(window)
or _SHA_DATING_POSED.search(window)
or _SHA_DATING_RESERVE.search(window)):
return True
start = i + len(sha)
return False


# Proximite maximale (caracteres) entre un SHA cite et un marqueur de levee
# VIVANT pour que le SHA compte comme la PREUVE avancee par la phrase.
_LIFT_SHA_PROXIMITY = 150
Expand Down Expand Up @@ -4481,8 +4553,21 @@ def _lift_eligible(lift_author: str, nit_author: str,
# lanes (self-review cap #12319), un override jsboige est
# indiscernable d'une auto-levee de lane (replay #12737).
m = OVERRIDE_LANE.search(lift_body or "")
if not (lift_author in LIFT_OVERRIDE_LOGINS and m is not None):
# #16764 classe 2 -- siege qualifiant (contrat #15511) : le NIT
# declare le relais, la LEVEE revendique le siege en tete de ligne.
siege = (_QUALIFYING_SEAT_BODY_RE.search(_strip_quoted(nit_body or ""))
and _QUALIFYING_SEAT_HEAD_RE.search(_strip_quoted(lift_body or "")))
if not (lift_author in LIFT_OVERRIDE_LOGINS
and (m is not None or siege)):
return False
if siege and m is None:
# Le scope #14216 est porte par la DECLARATION du nit
# lui-meme : le reviewer designe son siege, le siege designe
# la review qu'il siege (« la review Hermes du ... », « les
# deux reserves »). La co-phrase nom+levee n'est pas exigee
# -- l'instance fondatrice wrappe « la review Hermes » et
# « Je leve » sur des lignes distinctes.
return True
# #14216 — l'override est scope PAR RESERVE, plus par PR : sans
# nomination de la reserve d'autrui (login ou persona Hermes), il ne
# leve que les siennes. La trappe reste fermee a l'auteur de la PR
Expand Down Expand Up @@ -4582,6 +4667,11 @@ def _lift_eligible(lift_author: str, nit_author: str,
continue # present dans la PR : preuve valide
if not _sha_in_lift_claim(lift_body, sha):
continue # citation de contexte : ni refus, ni signalement
if _sha_dates_reserve(lift_body, sha):
# #16764 : SHA de DATATION -- il nomme la reserve
# (l'etat ou elle vivait), pas la preuve ; son
# rembobinage est attendu et ne desnue rien.
continue
message = resolved.get(sha)
if message and _message_refs_pr(message, pr_refs):
# rembobine ET rattache. #15556 : avant de desnuer la
Expand Down
157 changes: 157 additions & 0 deletions scripts/tests/test_check_unaddressed_nits.py
Original file line number Diff line number Diff line change
Expand Up @@ -3805,6 +3805,163 @@ def test_15556_headrefoid_prefere_au_dernier_oid():
assert mod._pr_head_oid({"commits": [{"committedDate": at(19)}]}) == ""


# --- #16764 : deux faux positifs de blocage mesures sur ai-01 (18/09) ------
#
# Un gate qui force a deformer la prose pour passer entraîne a ecrire pour
# l'organe. Classe 1 : un SHA cite pour DATER la reserve (« (review ...,
# head <sha>) », « la reserve posee sur <sha> ») etait lu comme SHA de
# PREUVE -> refus #13639. Classe 2 : la levee par siege qualifiant
# (contrat #15511) n'etait pas vue -- la trappe tierce exigeait le
# marqueur [OVERRIDE]. Fixtures = corps REELS des instances.

# Verbatim fondateur r.5252462567,
# 2026-09-18T20:46:45Z sur #16657, refuse puis reposte sans aucun SHA
# (r.5252468566) -- la levee finale MOINS precise que la refusee.
_LIFT_16657_ORIGINAL = """[OVERRIDE] lane myia-ai-01:CoursIA

Je leve la reserve `VERDICT: CONCERNS` de NanoClaw (review 05:48:49Z, head `c3095774`) en tant qu'arbitre tiers, et voici sur quoi.

**La reserve n'a plus d'objet au head courant.** Elle visait un changement de patterns. Mesure au head `ec3ac10b71a` :
"""

# Verbatim fondateur de la review Hermes r.5242448146, 2026-09-17T23:34:29Z
# sur #16608 : elle CONCLUT elle-meme au relais (deux occurrences).
_HERMES_REVIEW_16608 = """VERDICT: CONCERNS (fond solide et conforme à l'arbitrage — deux réserves ci-dessous ; contrat #15511 : COMMENT, relais à un siège qualifiant)

**[Hermes]** Review au head exact `896075ed8d`.

**Réserves :**
1. **Le vert `Scripts Tests (CPU)` n'existe pas au head** — le job échoue « Out of memory ».
2. **L'option explicite de visibilité demandée par l'arbitrage** n'existe pas sous ce nom.

Auteur = jsboige → COMMENT-only (self-review cap #3219). Disposition de fusion : siège qualifiant.
"""

# Verbatim (intro + jambes) de la levee c.5728784093, 2026-09-18T10:34:50Z
# sur #16608 : valide au contrat #15511, invisible pour l'organe avant fix.
_SIEGE_LIFT_16608 = """## Siege qualifiant — les deux reserves sont levees, et je tranche la question posee

La review Hermes du 2026-09-17T23:34:29Z se conclut « relais a un siege qualifiant » (contrat
#15511 : l'auteur du post etant `jsboige`, le bot ne pouvait qu'emettre `COMMENT`). Je suis ce
siege, tiers a la lane. Je leve, apres verification a la main des deux jambes.

**Reserve 1 — vert `Scripts Tests` au head.** Levee, avec la preuve du bon genre : le rouge etait un
**avortement de runner** (OOM, run 35286571354), relance a 00:52Z, **PASS** en 8 m 38.

**Reserve 2 — l'option de visibilite.** Levee, et la reponse pose correctement le fait :
`--include-delivered` court-circuite **le bloc entier**.

Merge.
"""


def _concern_16657_review(at_hour=10):
return {"author": {"login": "clusterManager-Myia"}, "state": "COMMENTED",
"submittedAt": at(at_hour),
"body": ("VERDICT: CONCERNS\n\n**[NanoClaw]** review structurelle : "
"les deux patterns ajoutes existent deja dans main.")}


def test_16764_classe1_sha_datation_head_exempte_levee_fondateur():
"""Instance fondatrice #16657 : le SHA `c3095774` DATE la reserve
(apposition « (review 05:48:49Z, head `c3095774`) »), il ne la prouve
pas. Resolu cote serveur ET rattache a la PR, il doit etre EXEMPT --
ni refus, ni avertissement. Sous l'organe d'avant : blocked=True,
voided_lifts=['c3095774'] (mesure au dev)."""
lift = {"author": {"login": "myia-ai-01"}, "createdAt": at(12),
"body": _LIFT_16657_ORIGINAL}
res = run([lift], reviews=[_concern_16657_review()],
commits=[{"oid": NIT_OID, "committedDate": at(19)}],
_absent_sha_messages={"c3095774": "fix(gitignore,#0): duplique"})
assert res["blocked"] is False
assert res["voided_lifts"] == []
assert res["absent_sha_warnings"] == []


def test_16764_classe1_sha_datation_posee_sur_exempte():
"""Forme piste de l'issue : « la reserve posee sur <sha> est adressee
par <sha2> » -- le premier SHA date, le second prouve (et appartient
a la PR)."""
body = ("[OVERRIDE] lane myia-ai-01:CoursIA\n\n"
"Je leve la reserve de NanoClaw posee sur `c3095774`, "
"adressee par `ec3ac10b71`.")
lift = {"author": {"login": "myia-ai-01"}, "createdAt": at(12), "body": body}
res = run([lift], reviews=[_concern_16657_review()],
commits=[{"oid": "ec3ac10b71" + "0" * 30,
"committedDate": at(19)}],
_absent_sha_messages={"c3095774": "fix(gitignore,#0): duplique"})
assert res["blocked"] is False
assert res["voided_lifts"] == []
assert res["absent_sha_warnings"] == []


def test_16764_classe1_sha_preuve_perime_toujours_refuse():
"""Controle negatif OBLIGATOIRE (criteres de sortie) : « traitee en
<sha-perime> » gouverne le SHA par un VERBE D'ADRESSE -- c'est une
preuve. Absente + resolue + rattachee : refus, inchange."""
body = ("La reserve est traitee en `d1e2f3a4b`. Je leve la reserve.")
lift = {"author": {"login": "jsboige"}, "createdAt": at(12), "body": body}
res = run([USER_NIT, lift],
commits=[{"oid": NIT_OID, "committedDate": at(19)}],
_absent_sha_messages={"d1e2f3a4b": "fix(x,#0): typo"})
assert res["blocked"] is True
assert [v["sha"] for v in res["voided_lifts"]] == ["d1e2f3a4b"]


def test_16764_classe1_collision_traitee_sur_head_refuse():
"""Moitie garde (residu assume documente) : « traitee sur le head
anterieur <sha> » gouverne le head par un VERBE D'ADRESSE -- la
collision retire l'exemption de datation : preuve vieillie, refus."""
body = ("La reserve est traitee sur le head anterieur `d1e2f3a4b`. "
"Je leve la reserve.")
lift = {"author": {"login": "jsboige"}, "createdAt": at(12), "body": body}
res = run([USER_NIT, lift],
commits=[{"oid": NIT_OID, "committedDate": at(19)}],
_absent_sha_messages={"d1e2f3a4b": "fix(x,#0): typo"})
assert res["blocked"] is True
assert [v["sha"] for v in res["voided_lifts"]] == ["d1e2f3a4b"]


def test_16764_classe2_siege_qualifiant_leve_la_reserve():
"""Instance fondatrice #16608 : la review Hermes conclut « relais a un
siege qualifiant » (contrat #15511), la levee d'ai-01 revendique le
siege en tete. Valide au contrat, doit lever. Sous l'organe d'avant :
blocked=True, 1 BOT-CONCERN non leve (mesure au dev : gate live
BLOCKED, et differential main/fixe 3 nits -> 2)."""
hermes = {"author": {"login": "clusterManager-Myia"}, "state": "COMMENTED",
"submittedAt": at(10), "body": _HERMES_REVIEW_16608}
lift = {"author": {"login": "myia-ai-01"}, "createdAt": at(12),
"body": _SIEGE_LIFT_16608}
res = run([lift], reviews=[hermes])
assert res["blocked"] is False


def test_16764_classe2_sans_revendication_en_tete_reste_bloque():
"""Borne de la conjunction : la revendication du siege doit etre POSEE
en tete de ligne (ancre #13030, comme _OVERRIDE_LANE) -- un « siege
qualifiant » cite en milieu de prose ne compte pas."""
hermes = {"author": {"login": "clusterManager-Myia"}, "state": "COMMENTED",
"submittedAt": at(10), "body": _HERMES_REVIEW_16608}
lift = {"author": {"login": "myia-ai-01"}, "createdAt": at(12),
"body": _SIEGE_LIFT_16608.replace(
"## Siege qualifiant — les deux reserves sont levees",
"## Arbitrage — les deux reserves sont levees", 1)}
res = run([lift], reviews=[hermes])
assert res["blocked"] is True


def test_16764_classe2_siege_par_non_siege_reste_bloque():
"""Borne d'auteur : la revendication du siege n'ouvre que le siege
(LIFT_OVERRIDE_LOGINS) -- un tiers quelconque qui revendique le
siege ne leve rien."""
hermes = {"author": {"login": "clusterManager-Myia"}, "state": "COMMENTED",
"submittedAt": at(10), "body": _HERMES_REVIEW_16608}
lift = {"author": {"login": "myia-po-2025"}, "createdAt": at(12),
"body": _SIEGE_LIFT_16608}
res = run([lift], reviews=[hermes])
assert res["blocked"] is True


# --- #15973 : un rebase sur une base avancee n'est pas un rembobinage ------
#
# Mesure fondatrice (#15902) : la levee citait 53a7998effd1, absent de
Expand Down
Loading