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
98 changes: 88 additions & 10 deletions scripts/check_lane_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@
re.IGNORECASE,
)
_KEYWORDS = ("CLAIMED", "RELEASED", "CANCELLED", "ABANDONED", "DONE", "OVERRIDE", "DELIVERED")
# #15982 -- tokens que `_MARKER_RE` lit REELLEMENT, donc a ne jamais signaler
# comme quasi-marqueurs. `CLAIMED-AMEND` en fait partie mais n'est PAS dans
# `_KEYWORDS` (il n'a pas la semantique d'un mot-cle simple : c'est un OPEN
# remplacant, cf `_OPEN`) -- c'est cette absence qui faisait signaler un
# marqueur canonique des que la classification composee l'a regarde.
_ENACTED_MARKERS = frozenset(_KEYWORDS) | {"CLAIMED-AMEND"}


def _blank_keeping_shape(line: str) -> str:
Expand Down Expand Up @@ -2018,16 +2024,75 @@ def _nearest_keyword(word: str) -> tuple[str | None, int]:
return best, best_d


def _close_keyword(quasi: dict) -> "str | None":
"""Mot-cle de FERMETURE porte par le token quasi, ou None (#15982).

Teste TOUS les composants du token, pas seulement sa tete : le cas fondateur
`[CLAIMED-RELEASED]` a pour tete `CLAIMED` (famille « prise ») alors que son
sens est une levee. Une regle par la tete seule manquerait precisement le cas
qu'elle doit attraper -- et pire, recommanderait de reposter `[CLAIMED]`, donc
de reprendre le grain que l'auteur vient de rendre.

Le vocabulaire reste `_CLOSE`, la constante du reduceur : une seconde liste
locale deriverait en silence. Distinguer une quasi-LEVEE d'une quasi-PRISE
sert au BLOCAGE -- les deux sont invisibles a l'organe, mais seule la
premiere explique qu'une lane attende ; lui conseiller de « lever » sur une
quasi-prise serait un conseil que son auteur n'a pas a suivre.
"""
for part in re.split(r"[-_\s]+", quasi.get("token") or ""):
if part.upper() in _CLOSE:
return part.upper()
nearest = (quasi.get("nearest") or "").upper()
return nearest if nearest in _CLOSE else None


def is_release_shaped(quasi: dict) -> bool:
"""Ce quasi-marqueur ressemble-t-il a une LEVEE plutot qu'a une prise ?"""
return _close_keyword(quasi) is not None


def _composed_keyword(word: str) -> "str | None":
"""Mot-cle de tete d'un token COMPOSE, ou None (#15982).

``[CLAIMED-RELEASED]`` esquivait les trois lecteurs a la fois : `_MARKER_RE`
exige le mot-cle seul, `_MALFORMED_MARKER_RE` exige l'absence de crochets, et
la classification quasi tombait dans la branche « distance <= 2 » parce que
`_QUASI_MARKER_RE` capture son premier groupe avec la classe
`[A-Za-z][A-Za-z_-]{2,}` -- **qui contient le tiret**. Le groupe valait donc
le token entier, la partie « suffixe » etait vide, et un token de 16
caracteres n'est proche d'aucun mot-cle.

Mesure 2026-09-13 : po-2023 a leve son claim sur #15835 avec
``[CLAIMED-RELEASED]`` ; l'organe a continue de le dire vivant et la PR
#15846 d'une autre lane est restee bloquee 48 h, alors que la levee etait
ecrite.

Renvoie le mot-cle de TETE : c'est lui qui dit la FAMILLE du marqueur, et
l'appelant en fait le `nearest` du WARN. La TETE ne dit PAS le sens -- le cas
fondateur est justement un ``CLAIMED-*`` qui leve ; c'est ``_close_keyword``
qui tranche le sens et fournit la forme a recommander. Doctrine #12624 : on
SIGNALE, on n'enacte pas.
"""
if word in _ENACTED_MARKERS:
return None # deja lu par `_MARKER_RE` -- le signaler serait un faux positif
head = re.split(r"[-_]", word, maxsplit=1)[0]
return head if head in _KEYWORDS else None


def _find_suspected_typo_markers(payload: dict) -> list[dict]:
"""Bracketed line-head tokens that ALMOST form a marker (#12624 Defaut 1).

Covers the gap between `_MARKER_RE` (exact keyword, alone in brackets)
and `_MALFORMED_MARKER_RE` (bare keyword, no brackets): a bracketed
`[CLAGED]` / `[RELEASED claim-malformed]` at line head is read by
NEITHER, so the writer's gesture enacts nothing while they believe their
lock is posted. WARN-only, never enacted, never auto-corrected -- the
signal tells the writer to re-post the canonical form. Fenced blocks are
masked (a quoted quasi marker is a citation, not a gesture).
lock is posted. Same for a COMPOSED token (`[CLAIMED-RELEASED]`, #15982):
`_QUASI_MARKER_RE` matches it whole -- hyphen included -- leaving the
suffix group empty, so BOTH classification branches below (`suffix` and
"distance <= 2") used to miss it. WARN-only, never enacted, never
auto-corrected -- the signal tells the writer to re-post the canonical
form. Fenced blocks are masked (a quoted quasi marker is a citation, not
a gesture).
"""
found: list[dict] = []
for c in payload.get("comments", []):
Expand All @@ -2044,14 +2109,24 @@ def _find_suspected_typo_markers(payload: dict) -> list[dict]:
if word in _KEYWORDS:
kind, nearest = "suffix", word
else:
nearest, dist = _nearest_keyword(word)
# len >= 4: a 3-letter token is within distance 2 of DONE for
# almost any input -- the motif gate alone would not save us.
if nearest is None or dist > 2 or len(word) < 4:
continue
kind = "typo"
composed = _composed_keyword(word)
if composed is not None:
kind, nearest = "compose", composed
else:
nearest, dist = _nearest_keyword(word)
# len >= 4: a 3-letter token is within distance 2 of DONE for
# almost any input -- the motif gate alone would not save us.
if nearest is None or dist > 2 or len(word) < 4:
continue
kind = "typo"
# Forme a RECOMMANDER dans le WARN : pour un compose c'est le mot de
# fermeture porte par le token, jamais sa tete -- conseiller
# `[CLAIMED]` a l'auteur de `[CLAIMED-RELEASED]` lui ferait reprendre
# le grain qu'il vient de rendre (#15982).
canonical = _close_keyword({"token": m.group(1), "nearest": nearest}) or nearest
found.append({
"nearest": nearest,
"canonical": canonical,
"token": m.group(1),
"kind": kind,
"line": line if len(line) <= 160 else line[:160] + "…",
Expand Down Expand Up @@ -2228,12 +2303,15 @@ def _run_check(payload: dict, my_lane: str, stale_threshold=None,
who = f" by @{s['author']}" if s["author"] else ""
if s["kind"] == "typo":
why = f'"{s["token"]}" (distance <= 2 de {s["nearest"]})'
elif s["kind"] == "compose":
why = (f'"{s["token"]}" (deux mots-cles joints ; tete {s["nearest"]} '
f'-- l\'organe ne lit QUE le mot-cle seul entre crochets)')
else:
why = f'"{s["token"]}..." ({s["nearest"]} + suffixe dans les crochets)'
print(
f"WARN: quasi-marqueur {why}{who} -- l'organe ne le lit PAS "
f'(ni evenement, ni malformed_markers). Reposter la forme '
f'canonique "[{s["nearest"]}] lane <machine:workspace>" dans un '
f'canonique "[{s["canonical"]}] lane <machine:workspace>" dans un '
f"commentaire neuf ; ne jamais corriger a la main le marqueur "
f"existant (le createdAt serveur fait foi). {s['line']}",
file=sys.stderr,
Expand Down
19 changes: 19 additions & 0 deletions scripts/ci/lane_claim_required.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,25 @@ def check(
if fresh_others:
blocking = sorted(fresh_others)[0]
ev = fresh_others[blocking]
# #15982 : dire la quasi-levee plutot que de laisser un blocage muet.
# Un marqueur COMPOSE (`[CLAIMED-RELEASED]`) n'est lu par aucun des
# trois lecteurs de `check_lane_claim` : la lane titulaire croit avoir
# rendu le grain, l'organe le dit toujours vivant, et le verdict
# ci-dessous conseille de lui demander de lever -- ce qu'elle vient de
# faire. Mesure 2026-09-13 : #15846 bloquee 48 h pour cette raison.
# Le quasi-marqueur n'est PAS enacte (doctrine #12624) : il est dit,
# et l'auteur reposte la forme canonique.
for s in clc._find_suspected_typo_markers(payload):
if s["kind"] != "compose" or not clc.is_release_shaped(s):
continue # une prise quasi (`[CLAIMED-X]`) n'explique pas ce blocage
warnings.append(
f"#{num}: un marqueur COMPOSE `[{s['token']}]` poste par "
f"@{s['author'] or '?'} ressemble a une levee mais n'est PAS lu "
f"par l'organe (il n'enacte que le mot-cle seul entre crochets). "
f"Demander a cette lane de reposter la forme canonique "
f"`[{s['canonical']}]` plutot que d'attendre "
f"{stale_threshold:g}h (#15982)."
)
return {
"guard_pass": False,
"reason": (
Expand Down
126 changes: 126 additions & 0 deletions scripts/tests/test_check_lane_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,132 @@ def test_quasi_fenced_citation_not_flagged(capsys):
assert '"composite_single_line_markers": 0' in captured.out


# --- #15982: composed marker (`[CLAIMED-RELEASED]`) ---------------------------
# The measured incident: po-2023 LIFTED its claim on #15835 by writing
# `[CLAIMED-RELEASED]`. All three readers missed it -- `_MARKER_RE` wants the
# keyword alone in brackets, `_MALFORMED_MARKER_RE` wants no brackets at all,
# and the quasi classifier fell into the distance<=2 branch because
# `_QUASI_MARKER_RE` captures group(1) with a class that CONTAINS the hyphen,
# so the token was the whole 16-char string (near no keyword). The claim stayed
# alive and PR #15846, from another lane, was blocked for 48 h. WARN-only here
# too: the quasi marker is SIGNALLED, never enacted (doctrine #12624).

INCIDENT_COMPOSE_RELEASE_LINE = (
"[CLAIMED-RELEASED] lane myia-po-2023:CoursIA -- grain DEEP/lean, prev: "
"DEEP/lean #15812 -- paths: MyIA.AI.Notebooks/GameTheory/social_choice_lean/"
)

INCIDENT_COMPOSE_UNDERSCORE_LINE = (
"[CLAIMED_RELEASED] lane myia-po-2023:CoursIA -- paths: "
"MyIA.AI.Notebooks/GameTheory/social_choice_lean/"
)


def test_quasi_compose_release_surfaces_with_canonical_close_form(capsys):
# The incident line: flagged as a COMPOSE quasi marker, and the WARN must
# recommend `[RELEASED]` -- recommending the token's HEAD (`[CLAIMED]`)
# would tell the author to RE-CLAIM the grain they just handed back.
p = payload(comment(INCIDENT_COMPOSE_RELEASE_LINE, "2026-09-13T08:20:00Z"))
rc = clc._run_check(p, "myia-po-2026:CoursIA")
captured = capsys.readouterr()
assert rc == 0 # WARN-only, never blocks
assert '"suspected_typo_markers": 1' in captured.out
assert "deux mots-cles joints" in captured.err
assert 'canonique "[RELEASED] lane' in captured.err
assert 'canonique "[CLAIMED] lane' not in captured.err


def test_quasi_compose_underscore_variant_surfaces(capsys):
# `_QUASI_MARKER_RE` group(1) admits `_` as well as `-`; both compose forms
# must reach the same verdict (the separator is not the signal).
p = payload(comment(INCIDENT_COMPOSE_UNDERSCORE_LINE, "2026-09-13T08:21:00Z"))
rc = clc._run_check(p, "myia-po-2026:CoursIA")
captured = capsys.readouterr()
assert rc == 0
assert '"suspected_typo_markers": 1' in captured.out
assert 'canonique "[RELEASED] lane' in captured.err


def test_quasi_compose_is_not_enacted_as_a_release(capsys):
# The damage, stated as an assertion: the quasi-release leaves the writer's
# claim ACTIVE. That is precisely why the WARN exists -- the signal is the
# only remedy, since enacting it would be #12624's forbidden auto-correct.
p = payload(
comment("[CLAIMED] lane myia-po-2023:CoursIA -- paths: "
"MyIA.AI.Notebooks/GameTheory/social_choice_lean/",
"2026-09-13T08:00:00Z"),
comment(INCIDENT_COMPOSE_RELEASE_LINE, "2026-09-13T08:20:00Z"),
)
rc = clc._run_check(p, "myia-po-2023:CoursIA")
captured = capsys.readouterr()
assert rc == 0
assert '"my_active_claim": true' in captured.out


def test_quasi_compose_marker_kind_and_canonical_fields():
# Field-level pin: `nearest` stays the HEAD (it names the family, and the
# WARN prints it as "tete"), `canonical` carries the OPERATIVE keyword.
p = payload(comment(INCIDENT_COMPOSE_RELEASE_LINE, "2026-09-13T08:20:00Z"))
found = clc._find_suspected_typo_markers(p)
assert len(found) == 1
s = found[0]
assert s["token"] == "CLAIMED-RELEASED" # verbatim, original case
assert s["kind"] == "compose"
assert s["nearest"] == "CLAIMED"
assert s["canonical"] == "RELEASED"


def test_quasi_compose_negative_controls_stay_silent(capsys):
# NEGATIVE CONTROLS (acceptance #2). Each of these is a gesture the organ
# READS, or an almost-marker it must not invent: any WARN here is a false
# positive that teaches the fleet to ignore the lint.
# - `[RELEASED]` / `[CLAIMED]`: canonical (word alone in brackets).
# - `[CLAIMED-AMEND]`: an ENACTED composite -- `_MARKER_RE` lists it as a
# first-class alternative, so the classifier must not call it compose
# (the regression this guard is here to prevent).
for line in (
"[RELEASED] lane myia-po-2023:CoursIA -- paths: a/**",
"[CLAIMED] lane myia-po-2023:CoursIA -- paths: a/**",
"[CLAIMED-AMEND] lane myia-po-2023:CoursIA -- paths: a/**",
):
p = payload(comment(line, "2026-09-13T08:20:00Z"))
# Checked as the MARKER'S OWN lane: a real [CLAIMED] from another lane
# would block (exit 2) and hide the lint assertion behind an unrelated
# red. The subject here is the quasi count, not the block verdict.
rc = clc._run_check(p, "myia-po-2023:CoursIA")
captured = capsys.readouterr()
assert rc == 0, line
assert '"suspected_typo_markers": 0' in captured.out, line
assert "quasi-marqueur" not in captured.err, line


def test_quasi_compose_requires_claim_motif_and_brackets(capsys):
# Selectivity, same two gates as the other quasi kinds: no claim motif on
# the line (prose about the shape) and a fenced citation are both silent.
for body in (
"Le marqueur [CLAIMED-RELEASED] n'est lu par personne.",
"```\n" + INCIDENT_COMPOSE_RELEASE_LINE + "\n```\n(citation)",
):
p = payload(comment(body, "2026-09-13T08:20:00Z"))
rc = clc._run_check(p, "myia-po-2026:CoursIA")
captured = capsys.readouterr()
assert rc == 0, body
assert '"suspected_typo_markers": 0' in captured.out, body


def test_close_keyword_only_counts_enacting_keywords():
# `is_release_shaped` drives the BLOCKING gate's warning (#15982): it must
# fire for close vocabulary and stay quiet for a quasi-CLAIM, otherwise
# every quasi-claim would tell its author to release a grain they hold.
assert clc.is_release_shaped({"token": "CLAIMED-RELEASED", "nearest": "CLAIMED"})
assert clc.is_release_shaped({"token": "CLAIMED_RELEASED", "nearest": "CLAIMED"})
assert clc.is_release_shaped({"token": "CLAIMED-DELIVERED", "nearest": "CLAIMED"})
# A quasi-claim, a quasi-typo and an unreadable head are all NOT releases.
assert not clc.is_release_shaped({"token": "CLAIMED-AMENDED", "nearest": "CLAIMED"})
assert not clc.is_release_shaped({"token": "CLAGED", "nearest": "CLAIMED"})
assert not clc.is_release_shaped({"token": "", "nearest": ""})


def test_template_prose_line_not_composite(capsys):
# The claim template's own prose line ("Release with `[RELEASED]` when
# your PR lands.") has NO line-anchored head marker -- it must never be
Expand Down
68 changes: 68 additions & 0 deletions scripts/tests/test_lane_claim_required.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,71 @@ def test_10323_cli_pr_closing_refs_arg_parsed(tmp_path):
assert v["guard_pass"] is True # GitHub closes nothing -> no block
assert v["closing_issues"] == []
assert any("IGNORED_BY_GITHUB" in w for w in v["warnings"])


# --- #15982: the blocking verdict must NAME the quasi-release ----------------
#
# #15846 shape: po-2023 claims #15835, then writes `[CLAIMED-RELEASED]`. None of
# the three readers enacts it, so the claim is still fresh and the gate blocks a
# second lane's `Closes #15835`. The block itself is CORRECT (the release was
# never registered) -- what was missing is the reason: the verdict read as "a
# foreign lane is holding your grain", which sends the author to ask for an
# [OVERRIDE] or to wait 48 h, when the holder had already handed it back. The
# warning names the quasi marker and the canonical form to re-post. It is
# WARN-only: the marker is never enacted (doctrine #12624).

def test_15982_block_names_the_compose_release():
body = _body_with("Closes #15835")
fetch = fetcher_from({15835: issue_payload(
comment("[CLAIMED] lane myia-po-2023:CoursIA -- paths: a/**",
"2026-08-09T11:00:00Z"),
comment("[CLAIMED-RELEASED] lane myia-po-2023:CoursIA -- paths: a/**",
"2026-08-09T14:00:00Z"),
number=15835,
)})
v = lcr.check(body, fetch, now=NOW)
# The quasi-release LIFTS NOTHING -- this is the measured damage, and the
# fix must not paper over it by enacting the marker.
assert v["guard_pass"] is False
assert v["blocking_lane"] == "myia-po-2023:CoursIA"
named = [w for w in v["warnings"] if "CLAIMED-RELEASED" in w]
assert named, v["warnings"]
assert "[RELEASED]" in named[0]
assert "reposter" in named[0]
assert "#15982" in named[0]


def test_15982_quasi_claim_is_not_reported_as_a_release():
# NEGATIVE CONTROL (acceptance #3's discriminant): the warning is reserved
# for close-shaped markers. A compose quasi-CLAIM carries no release, so
# telling its author to re-post `[RELEASED]` would be advice to hand back a
# grain they are holding. The block persists on the REAL claim; only the
# release warning stays silent.
body = _body_with("Closes #15835")
fetch = fetcher_from({15835: issue_payload(
comment("[CLAIMED] lane myia-po-2023:CoursIA -- paths: a/**",
"2026-08-09T11:00:00Z"),
comment("[CLAIMED-NOTES] lane myia-po-2023:CoursIA -- paths: a/**",
"2026-08-09T14:00:00Z"),
number=15835,
)})
v = lcr.check(body, fetch, now=NOW)
assert v["guard_pass"] is False
assert not any("[RELEASED]" in w for w in v["warnings"])


def test_15982_prose_mention_of_the_compose_shape_is_silent():
# Selectivity: the gate inherits the quasi lint's two gates (claim motif on
# the line, real brackets). Prose about the shape is not a gesture, so a
# block on an unrelated fresh claim must not carry the release warning.
body = _body_with("Closes #15835")
fetch = fetcher_from({15835: issue_payload(
comment("[CLAIMED] lane myia-po-2023:CoursIA -- paths: a/**",
"2026-08-09T11:00:00Z"),
comment("Le marqueur [CLAIMED-RELEASED] n'est lu par personne.",
"2026-08-09T14:00:00Z"),
number=15835,
)})
v = lcr.check(body, fetch, now=NOW)
assert v["guard_pass"] is False
assert not any("[RELEASED]" in w for w in v["warnings"])
Loading