Skip to content

perf(adjoint,#17315): gate 100% REST + reviewThreads batchees par alias + compteur publie - #19966

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/17315-graphql-quota
Oct 9, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/17315-graphql-quota

Conversation

@jsboige

@jsboige jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Grain: MED/refactor — lane myia-po-2026:CoursIA — prev: DEEP/notebook-python #19962

Cible

See #17315 — le quota GraphQL partagé par la flotte, dont le gate de prévalidation et l'organe B.0 sont les deux consommateurs. Incident mesuré le 2026-09-21 : une passe de 200 appels de gate a rendu 110 rc=2 consécutifs et laissé le gate mort une heure pour tout le monde.

Cette PR traite les leviers 1-2 et 3 côté gate, plus le critère du compteur. Elle ne traite pas le levier 4 (partition des buckets), qui exige un arbitrage user.

Les trois volets

1. _pr_metadata en REST pur

gh pr view --json (bucket GraphQL) est remplacé par repos/.../pulls/N. Les clés rendues gardent la forme exacte que gh pr view --json produisait — l'identité du bracket before/after et les empreintes de surface restent byte-compatibles avec les dossiers déjà estampés.

Le champ statusCheckRollup disparaît de l'organe. Sa dernière raison d'être était le chemin de compatibilité retiré au volet 2.

Le bracket de stabilité couvre désormais les seuls champs scalaires de la PR, et l'organe le dit dans son commentaire plutôt que de laisser croire qu'un changement de conclusion de check le déclenche : les check-runs sont lus dans le bracket mais indexés sur la tête lue avant lui, donc une poussée pendant la lecture déplace headRefOid, les deux lectures divergent et le snapshot est refusé (fail-closed) au lieu d'apparier les check-runs de l'ancienne tête avec la nouvelle.

2. legacy_surfaces_fingerprint retiré

Sa docstring énonçait sa propre condition de mort — « drop this function when no open dossier carries a legacy stamp ». Ses deux porteurs sont mergés (vérifié firsthand : #16950 MERGED 2026-09-22T02:59:58Z, #16891 MERGED 2026-09-22T20:17:55Z), et son dernier consommateur GraphQL part avec lui.

Le résidu dégrade fail-closed vers l'unique geste de récupération, un re-stamp mécanique --template, que le message de refus nommait déjà.

3. reviewThreads batchées par alias

Une opération GraphQL pour N PRs (lots de 8), au lieu d'une par PR :

query($owner:String!,$repo:String!,$p0:Int!,$p1:Int!,...){
  p0: repository(owner:$owner,name:$repo){pullRequest(number:$p0){reviewThreads(first:100){...}}}
  p1: repository(owner:$owner,name:$repo){pullRequest(number:$p1){reviewThreads(first:100){...}}}
}

Une PR dont les threads débordent la première page repart en pagination par curseur — pour elle seule. Le refus >100 commentaires inline (garde-fou qui empêche une page tronquée de se lire comme un thread complet) survit au batching.

4. Le coût est publié

Compteur API_USAGE incrémenté dans le funnel unique gh_json, exposé en clé api_usage du --json et en ligne stderr ; stdout reste le verdict que les appelants parsent.

Mesure avant / après

Compteur injecté sur le funnel, même PR (#19943), load_snapshot :

gh pr view (bucket GraphQL) gh api graphql opérations GraphQL REST
avant (main) 2 1 3 5
après 0 1 1 6

Invocation complète --json après la PR, sur #19943 (sortie réelle) :

API usage: 6 REST, 1 GraphQL operation(s)
{"pr": 19943, "ready": false, "verdict": "BLOCKED", ..., "blocking_fields": ["checks"],
 "api_usage": {"rest": 6, "graphql": 1}}

En passe de masse, la part threads passe de N opérations à ceil(N/8) ; la part structures reste à 1 opération par PR.

Un piège d'instrument rencontré, et pourquoi le run live est la seule preuve

La réponse d'une requête à alias imbrique la connexion sous pullRequest : c'est data["data"]["pN"]["pullRequest"]["reviewThreads"]. Ma première version lisait ["pN"]["reviewThreads"] et levait un KeyError.

Le premier jet est passé 165/165. Le fixture de test avait été écrit depuis la même lecture erronée que le code — il reproduisait fidèlement le bug, et deux tests verts certifiaient un chemin mort. C'est le run live sur #19943 (UNKNOWN: 'reviewThreads' en 1,4 s) qui l'a trouvé, puis la requête rejouée seule pour lire la forme réelle de la réponse.

Le commentaire du code porte la mesure et le fixture de test encode désormais la forme réelle : une régression vers l'ancienne lecture lève un KeyError et rougit.

Critères d'acceptation de #17315

Critère État
_pr_metadata ne consomme plus le bucket GraphQL fait (statusCheckRollup absent de l'organe)
#16950 / #16891 re-stampés, legacy_surfaces_fingerprint retiré fait — les deux PR sont mergées, la fonction est retirée
reviewThreads batchées par alias pour les passes de masse fait dans le gate ; la 3ᵉ surface B.0 (scripts/check_unaddressed_nits.py:4093) reste — l'issue l'assigne à la lane myia-po-2026:CoursIA-3, et je ne touche pas un fichier d'une autre lane
Compteur distinguant REST et GraphQL, chiffres publiés fait pour cet organe
Arbitrage user sur la partition des buckets hors périmètre lane — non traité ici

Les critères n'étant pas tous couverts, cette PR dit See #17315, pas Closes.

Collision et doublon signalés

#19926 (lane myia-ai-01:CoursIA-2, DEEP/tooling, ouverte, CONFLICTING/DIRTY) touche le même fichier. Vérification faite : son sujet (#19869) est déjà sur main via #19892 (3da0412cde — find_previous_blocked_same_head, pré-remplissage supersedes/supersedes-why, tests présents). C'est un doublon périmé, d'où le conflit. Le signalement [INFO] candidate-delivered est posté sur #19926 avec sa preuve ; la fermeture revient au coordinateur ou à l'adjoint, pas à une lane worker.

Cette PR se base sur main courant (qui porte #19892) : le conflit est donc du côté du doublon, pas du côté de cette PR.

Validation

  • py -3.13 -m pytest scripts/tests/test_check_adjoint_prevalidation.py -q → 165 passed
  • suites des importateurs directs (check_closure_dossier, check_run_state, merge_ready, post_dossier, frozen_campaigns, sweep_stale_pr_branches, update_stale_pr_branches, gh_identity) → 262 passed
  • balayage adjacent scripts/tests/ (-k "adjoint or prevalidation or gate or unaddressed") → 1366 passed, 2 skipped
  • run live de l'organe sur feat(19920,#19898): ANALYSE-07 Percolation-Critique -- pli 3 Origami (tranches A+B) #19943 : verdict lu correctement (dossier de la lane CoursIA-3), api_usage publié

Tests ajoutés : comptage REST/GraphQL par bucket · publication api_usage en --json + stderr · une opération pour N PRs · découpage au-delà de la taille de lot · déduplication · repli en pagination pour la seule PR qui déborde · refus >100 inline préservé · une PR seule = une opération.

Fichiers

scripts/check_adjoint_prevalidation.py
scripts/tests/test_check_adjoint_prevalidation.py

🤖 Generated with Claude Code

…as + compteur publie

Trois volets sur le gate de prevalidation, la mesure qui les fonde etant le
bucket GraphQL partage par la flotte (incident 2026-09-21 : 110 rc=2
consecutifs, gate mort une heure pour tout le monde).

1. `_pr_metadata` passe en REST pur (`repos/.../pulls/N`). Les cles gardent la
   forme exacte que `gh pr view --json` produisait, donc l'identite du bracket
   et les empreintes restent byte-compatibles avec les dossiers deja estampes.
   Le champ `statusCheckRollup` disparait de l'organe : il ne servait qu'au
   chemin de compatibilite retire ci-dessous.

2. `legacy_surfaces_fingerprint` (pre-#16957) est retire. Sa docstring enoncait
   sa propre condition de mort -- « drop when no open dossier carries a legacy
   stamp » -- et ses deux porteurs #16950 / #16891 sont merges. Le residu
   degrade fail-closed vers l'unique geste de recuperation, un re-stamp
   mecanique `--template` (deja nomme par le message de refus).

3. `reviewThreads` passe en appels groupes par alias GraphQL : une operation
   pour N PRs (lots de 8), avec repli en pagination par PR pour la seule PR
   dont les threads debordent la premiere page. Le refus >100 commentaires
   inline est preserve. Mesure LIVE sur #19943 : lecture d'alias
   `pN.pullRequest.reviewThreads` -- un fixture ecrit depuis la meme lecture
   erronde reproduisait l'erreur a l'identique (2 tests verts sur un chemin
   mort) ; le format de reponse se verifie sur un appel reel.

4. Le cout est publie : compteur REST/GraphQL incremente dans le funnel unique
   `gh_json`, expose en cle `api_usage` du `--json` et sur stderr, pour qu'une
   lane secretaire agrege le cout d'une campagne.

Mesure avant/apres sur la meme PR (#19943), par compteur injecte sur le funnel :
  avant (main) : 2 `gh pr view` (bucket GraphQL) + 1 `gh api graphql` = 3 ops
  apres        : 0 + 1 = 1 op  (et 6 REST)
Passe de masse : N operations Threads -> ceil(N/8), la part structures restant
a 1 operation par PR.

Tests : 165/165 sur test_check_adjoint_prevalidation.py, 262/262 sur les suites
des importateurs directs, 1366 passes sur le balayage adjacent.

Files:
  scripts/check_adjoint_prevalidation.py
  scripts/tests/test_check_adjoint_prevalidation.py

See #17315

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

[myia-po-2026:CoursIA] — le rouge Scripts Tests (CPU) de la tête cd063433d3 (jambe 19:56:41Z) n'est pas un défaut du diff : signature du hang xdist #16288, tué par le watchdog.

Lecture du log (job 113500331914, --log-failed) :

  • la suite progresse normalement jusqu'à 99 % — uniquement des points et des skips, zéro F ;
  • à 20:06:38 plus rien pendant 480 s (la limite du watchdog), puis :
##[error]XDIST-WATCHDOG: BLOQUE -- silence de sortie depuis 480 s (limite 480 s), mur du job non atteint
##[error]XDIST-WATCHDOG: workers morts : gw0
##[error]XDIST-WATCHDOG: zero octet emis pendant la fenetre (ni ligne ni fragment) -- le master etait vivant mais n'attendait pas du travail, signature #16288 ; kill du groupe de processus

C'est la même famille que le collect-crash KeyError: <WorkerController gw8> mesuré ce matin sur #19827/#19966 (classe CI-harness, gardée par #19917) : un worker meurt, le master reste vivant sans consommer. Aucun test n'a échoué — le wrapper a tué le groupe à 99 %.

La jambe est rejouée (même job). Si le hang revient de façon reproductible sur cette PR précise, ce serait un datapoint nouveau pour #16288/#19917 — mais rien dans le diff (gate 100 % REST + reviewThreads batchées) ne touche au scheduling pytest/xdist.

@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 19966
head: cd06343
complete: true
body: read
comments-reviewed: 2
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 4baf290fd972ae04f400357bab840b5c83e1afcc68fa395f20e2a6ded10f1d7e
diff-files: 2
diff-additions: 369
diff-deletions: 150
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19966
organ-rc: 0
[/ADJOINT PREFLIGHT]

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #19966 (perf(adjoint,#17315): gate 100% REST + reviewThreads batchees par alias + compteur publie) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@myia-ai-01
myia-ai-01 merged commit 156dd16 into main Oct 9, 2026
21 of 24 checks passed
jsboige pushed a commit that referenced this pull request Oct 9, 2026
Conflit resolu c.1492 -- union des deux intentions : main (#19966/#17315)
retire le parametre rollup de _pr_metadata (bracket REST-only, emprunte
GraphQL retirees) ; la branche (#19014) ajoute _base_liveness et la lecture
REST de base.ref qui la nourrit. Signature un-argument de main + fonction
et lecture de la branche ; les deux sections de tests sont conservees
(6 base-liveness + 10 api-usage/bulk), les appels with_rollup=False
adaptes. 175/175 tests verts.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants