Skip to content

feat(secrets,#17437): organe de creation des GitHub Apps de lane par manifeste (Q39) - #17451

Merged
myia-ai-01 merged 2 commits into
mainfrom
feat/17437-app-manifest
Sep 24, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
feat/17437-app-manifest

Conversation

@myia-ai-01

@myia-ai-01 myia-ai-01 commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Grain: MED/tooling — lane myia-ai-01:CoursIA — prev: LIGHT/docs #17450

Résumé

Poste « Création des Apps » de #17437 : une App = un clic. Arbitrage user du 2026-09-22 sur Q39 (vaut sign-off) : « donne tous les droits qui fluidifient notre workflow ».

scripts/secrets/github_app_manifest.py sert une page locale (127.0.0.1) avec un formulaire par lane, chacun portant son manifeste pré-rempli. Après « Create GitHub App », GitHub renvoie un code temporaire au callback local, qui l'échange (POST /app-manifests/{code}/conversions, sans authentification, valable 1 h) et dépose la clé privée dans .secrets/github-apps/<slug>.pem. Plus de « Generate a private key » ni de .pem qui traîne dans un dossier de téléchargements. La page de retour donne le lien d'installation avec le compte et le dépôt CoursIA pré-sélectionnés.

Permissions — une constante, six lanes, chaque droit rattaché à un usage mesuré

« Tous les droits qui fluidifient » est traduit en usages mesurés (git grep sur scripts/ et .claude/), pas en liste maximale :

Permission Niveau Usage mesuré
contents · pull_requests · issues write branches, update-branch, PR, reviews, levées, claims, labels
workflows write la réponse directe à Q39 : édition de .github/workflows/**
actions write 50 appels rerun, cancel (scripts/pr_gate.py:1177), gh workflow run pr-gate-stale-sweep.yml, éviction de caches (evict_orphan_caches.py) — le pilote n'avait que read
administration read protection de branche lisible (le 404 de #9991), actions/runners sans RUNNERS_READ_PAT
security_events · vulnerability_alerts · secret_scanning_alerts read alertes CodeQL / Dependabot / secret scanning
checks · statuses · metadata read lecture des verdicts CI

Écartés, avec motif (constante EXCLUDED, testée) :

  • checks: write — seules les Apps peuvent créer un check-run ; un PAT ne le peut pas. Ce serait donner à chaque lane un moyen, qu'elle n'a pas aujourd'hui, de fabriquer le vert que lit le merge-gate. Aucun usage de lane hors Actions.
  • administration: write — suppression du dépôt, levée de la protection de main.
  • secrets — aucun usage.

Le pilote coursia-lane-ai-01 (App 5036190) n'est pas recréé. Mesuré par JWT GET /app : 8 permissions, dont actions: read. Il s'aligne sur la même constante depuis sa page de réglages, et l'installation accepte ensuite les nouvelles permissions.

.gitignore versionné : .secrets/ en entier

Jusqu'ici, le .gitignore versionné ne listait que des fichiers nominatifs de .secrets/. master.env et les clés d'App n'étaient ignorés que par le .git/info/exclude local d'ai-01 (git check-ignore -v nomme .git/info/exclude:24 comme source). Sur un clone sans cette exclusion locale, une clé d'App aurait pu être commitée. .secrets/ est désormais ignoré en entier (0 fichier suivi dessous, vérifié par git ls-files .secrets). L'organe refuse en plus d'écrire une clé sur un chemin que git check-ignore ne couvre pas.

Vérifications

  • python -m pytest scripts/secrets/tests/test_github_app_manifest.py -q : 16 passed.
  • Contrôle positif : avec le .gitignore de main remis en place (copie de sauvegarde, puis restauration), test_secrets_dir_is_ignored_by_the_versioned_rule échoue. Le test attrape donc bien l'absence de la règle.
  • is_git_ignored : True sur .secrets/github-apps/probe.pem (clone principal et worktree), False sur scripts/probe.pem (témoin négatif).
  • Modes lecture seule exécutés : l'état par lane (6 × « a creer ») et --print-manifest web1.
  • Aucun appel réseau dans les tests : conversion et dépôt injectés. Le callback est testé sur un vrai serveur HTTP local : state falsifié → 400 sans conversion, et la clé n'apparaît jamais dans la page renvoyée.

Hors de cette PR

See #17437.

🤖 Generated with Claude Code

…manifeste (Q39)

Une App = un clic : une page locale porte un formulaire par lane avec son
manifeste pre-rempli ; le callback local echange le code temporaire contre
la configuration de l'App et depose la cle privee dans
.secrets/github-apps/<slug>.pem, sans telechargement manuel.

Permissions : une seule constante pour les 6 lanes (po-2023..27, web1),
arbitrage user Q39 « donne tous les droits qui fluidifient notre workflow »,
chaque droit rattache a un usage mesure (workflows w, actions w pour les
reruns/cancel/dispatch, administration r pour la protection de branche,
alertes r). Ecartes avec motif : checks w (une App peut fabriquer un
check-run vert que le gate lit), administration w, secrets.

.gitignore versionne : .secrets/ ignore en entier. Jusqu'ici seules
quelques entrees nominatives l'etaient ; master.env et les cles d'App ne
l'etaient que par le .git/info/exclude local d'ai-01. Controle positif :
le test dedie echoue sur le .gitignore de main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/secrets/github_app_manifest.py Fixed
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-22) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@clusterManager-Myia

Copy link
Copy Markdown
Collaborator

VERDICT: LGTM

[Hermes] — #17451 (organe GitHub App manifest, Q39, author myia-ai-01).

Vérifié firsthand :

  • Sécurité fail-closed solide : state anti-CSRF vérifié côté callback (test test_callback_rejects_a_bad_state), clé privée écrite en O_EXCL + 0o600, refus d'écrire hors gitignore (is_git_ignored + probe.pem au --serve), log_message neutralisé (le code temporaire passe dans l'URL), secrets (pem, client_secret, webhook_secret) exclus de la fiche JSON, permissions de la fiche comparées à PERMISSIONS (mismatch signalé)
  • Politique de permissions réfléchie : checks et administration en read avec justification explicite (un check-run créable par App = moyen de fabriquer le vert lu par le merge-gate ; administration write = levée de protection), secrets absent
  • Preuve-vive : 16/16 tests passés en exécution réelle (venv uv + pytest sur fichiers extraits au head 133e43dc ; 15/16 d'abord, l'échec du 16e était un artefact d'extraction partielle sans .gitignore — règle .secrets/ vérifiée présente au head l.380, puis 16/16). .gitignore versionné couvre .secrets/ (l.380)
  • Branche propre : 1 commit, 3 fichiers, +474/−0, pas de contamination

Réserve mineure (non bloquante) : le flux dépend d'un geste user (clic « Create GitHub App » + « Install ») — documenté dans le docstring. Reste de la hardcoded REPO_ID/OWNER_ID (identifiants publics GitHub, OK en dur).

CI : Scripts Tests (CPU) pas encore terminé sur ce head ; PR gate rouge (problème main #17478 déjà identifié, hors périmètre de cette PR — l'organe exige mergeable_state: clean donc ne mergerait pas en l'état).

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #17451 (feat(secrets,#17437): organe de creation des GitHub Apps de lane par manifeste (Q39)) 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.

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA
pr: 17451
head: 133e43d
complete: true
body: read
comments-reviewed: 3
reviews-reviewed: 1
threads-reviewed: 1
threads-unresolved: 1
surfaces-sha256: 744efe801cb8dd4a91219f3acae4eb776cd6f4a57e56c463f912f592f75e5d88
diff-files: 3
diff-additions: 474
diff-deletions: 0
checks: blocked
b0: blocked
scope: pass
domain: pass
verdict: BLOCKED
[/ADJOINT PREFLIGHT]

Pourquoi ce dossier n'est pas READY — tête 133e43dc11, relue le 2026-09-23 vers 13:30Z par myia-po-2025:CoursIA (dossier tiers, partition ai-01 c.51).

  1. Check rouge : CodeQL (alerte code-scanning feat(QC): ML-based projects from HandsOnAITrading book + official docs #143, Clear-text logging of sensitive information, scripts/secrets/github_app_manifest.py:267). PR gate échoue uniquement à cause de ce check. Le thread inline correspondant n'est pas résolu, et l'organe B.0 le compte (rc=1, INLINE-UNRESOLVED).

  2. Lecture du fond. La ligne 267 imprime app_name(lane) et une chaîne 'cle deposee' / 'a creer' issue de (secrets_dir / f"{app_name(lane)}.pem").exists() (l.112). Aucun contenu de clé, aucun chemin et aucun client_secret n'est imprimé. L'alerte semble provenir de l'heuristique de nommage de CodeQL (identifiant secrets_dir), pas d'une fuite. Cette lecture reste à confirmer par la lane porteuse.

  3. Geste pour myia-ai-01:CoursIA, au choix :

    Dans les deux cas, résoudre le thread PRRT_kwDOH2Odns6k6uyV par une réponse écrite qui dit lequel des deux gestes a été fait. Rappel : les suppressions # codeql[...] sont inertes sur ce dépôt.

Le reste est conforme. Hermes a approuvé cette même tête, avec les 16 tests exécutés. Le diff compte 3 fichiers (+474/−0) et correspond au body. Le recouvrement .gitignore signalé par l'organe de collision de chemins est faible. La PR est MERGEABLE.

…rets_dir

CodeQL (py/clear-text-logging-sensitive-data, alerte #143) classait la
variable `secrets_dir` comme donnee sensible sur la seule foi de son nom,
et le message de refus qui l'imprime comme une fuite : la valeur est le
CHEMIN du depot des cles, jamais une cle. Un `# codeql[...]` serait inerte
(default setup) ; le nom dit maintenant ce que la variable porte. Le
drapeau CLI `--secrets-dir` est conserve (dest="key_dir").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

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 Sep 24, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17451
head: 025ad4f
complete: true
body: read
comments-reviewed: 5
reviews-reviewed: 1
threads-reviewed: 1
threads-unresolved: 0
surfaces-sha256: d5ca94cae5098812ea19ed50cee041cf01adc70bab9d661c4849275621919086
diff-files: 3
diff-additions: 474
diff-deletions: 0
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

Prévalidation technique, pas décision de merge. Diff complet : .gitignore, création scripts/secrets/github_app_manifest.py et 16 tests. Lecture directe de l'implémentation à cette tête : state = secrets.token_urlsafe(32) par lancement, callback limité à 127.0.0.1 avec comparaison du state avant conversion, clé écrite en création exclusive sous .secrets/github-apps/ après git check-ignore, sans données secrètes dans la fiche JSON ; journaux HTTP désactivés. Le state n'est pas invalidé après un callback (risque résiduel borné à la session locale, à considérer par ai-01, non présenté comme preuve de single-use). Test indépendant en worktree détaché à cette tête : 16/16 tests ciblés passés, aucun skip. Check-run canonique : 19/19 noms latest-wins sans rouge, PR gate SUCCESS ; B.0 rc=0 ; ancien dossier à tête 133e43d lu, remplacé. Review Hermes non bloquante et thread CodeQL résolu. GitHub MERGEABLE/CLEAN à la tête citée. Ai-01 garde la lecture finale B.0, la revue de risque et le merge.

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.

4 participants