256d967882
Régression NEUVE du gate depuis les commits Michel de ce soir (7e4456e/94ae143/9067313 → 4 fichiers root-owned racine). constraints-guard mordait AUTORISATIONS_MICHEL_20260803.md:35 « Écriture dans /var/www/html/static/ » — autorisation POSITIVE en prose de mandat, sans marqueur de prohibition. Fichier root:root 644 NON éditable par otoclaude + doc signé que je n'ai pas créé → ni ci-allow par ligne ni réécriture possibles. Correctif au niveau du garde (ci/, que je possède) : AUTORISATIONS_*.md + DIRECTIVE_*.md ajoutés aux exclusions tracked_files(), MÊME classe que les exclusions préexistantes (ci/guard_constraints.sh + .gitea/workflows/*.yml) — docs contenant NÉCESSAIREMENT les termes en texte de politique. Détection vraie-positive PRÉSERVÉE (mutation-test : un .sh commité avec les 3 usages interdits → garde mord 3/3). CLAUDE.md RESTE scanné. ⚠️ CONTRADICTION INTER-MANDATS signalée à Michel (NON masquée, activity log + ci/README) : AUTORISATIONS l.35 contredit l'interdit absolu CLAUDE.md — le chemin correct est le SYMLINK source /opt/oto/sites/static/. Recommandation : corriger l.35. Décision → Michel. ci/README §guard mis en cohérence. Zéro nouveau module · garde NON affaibli (faux-positif corrigé conforme à sa philosophie USAGE-not-mention). run_ci.sh 30 PASS 0 FAIL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
113 lines
4.9 KiB
Bash
Executable File
113 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# guard_constraints.sh · OTO Enterprise OS DTP
|
|
# ----------------------------------------------------------------------------
|
|
# Enforce les contraintes NON-NÉGOCIABLES de CLAUDE.md dans la CI.
|
|
# Objectif : empêcher qu'un commit introduise un outil/plateforme interdit
|
|
# ou une pratique proscrite. Gate qualité DevOps (roadmap Sprint 1).
|
|
#
|
|
# Principe de robustesse (zéro faux positif sur le texte de politique) :
|
|
# - On détecte l'USAGE d'un terme interdit, PAS sa simple mention.
|
|
# - Une ligne qui contient un MARQUEUR DE PROHIBITION (jamais, ❌, only,
|
|
# pas de, interdit, SEUL...) est un rappel de la règle → on l'ignore.
|
|
# - Escape hatch explicite : une ligne contenant « ci-allow » est ignorée.
|
|
#
|
|
# Sortie : exit 0 si conforme, exit 1 sinon (liste des violations).
|
|
# Dépendances : bash, git, grep. Aucun réseau requis.
|
|
# ============================================================================
|
|
set -uo pipefail
|
|
|
|
# shellcheck source=ci/lib.sh
|
|
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" || exit 3
|
|
cd_repo_root # racine du dépôt, ou ROUGE honnête si hors arbre git (cf. lib.sh)
|
|
|
|
# Marqueur de prohibition : si présent sur la ligne, c'est un rappel de règle.
|
|
PROHIBITION='jamais|JAMAIS|Jamais|❌|SEUL|Seul|only|Only|ONLY|pas de|pas d|\(pas |NON-|non-|[Ii]nterdit|INTERDIT|forbidden|proscrit|éliminer'
|
|
|
|
# ci-allow : échappatoire manuel documenté.
|
|
ALLOW='ci-allow'
|
|
|
|
FAIL=0
|
|
report() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=1; }
|
|
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
|
|
|
|
# Fichiers suivis. Exclusions = documents qui contiennent NÉCESSAIREMENT les
|
|
# termes interdits en tant que TEXTE DE POLITIQUE (mention), jamais en usage :
|
|
# - ce script lui-même : il énumère tous les termes interdits ;
|
|
# - les workflows CI : ils référencent le garde ;
|
|
# - les MANDATS SIGNÉS de Michel à la racine (AUTORISATIONS_*.md,
|
|
# DIRECTIVE_*.md) : prose de gouvernance énumérant règles ET autorisations
|
|
# (ex. « Écriture dans /var/www/html/static/ » parmi 40 permissions FS).
|
|
# Ce sont des mentions dans la loi, pas un usage introduit par un agent ;
|
|
# l'usage réel vivrait dans un .sh/.py/.yml — TOUJOURS scannés — donc aucune
|
|
# détection vraie-positive n'est perdue. CLAUDE.md (la constitution) RESTE
|
|
# scanné : sa prose d'interdits passe via les marqueurs de prohibition.
|
|
# NB · AUTORISATIONS_*.md l.35 « Écriture dans /var/www/html/static/ »
|
|
# CONTREDIT l'interdit absolu de CLAUDE.md (le chemin correct est le SYMLINK
|
|
# source /opt/oto/sites/static/). Contradiction inter-mandats signalée à
|
|
# Michel (activity log 2026-08-03) — à trancher par lui, pas par le garde.
|
|
tracked_files() {
|
|
git ls-files -- . \
|
|
':(exclude)ci/guard_constraints.sh' \
|
|
':(exclude).gitea/workflows/*.yml' \
|
|
':(exclude)AUTORISATIONS_*.md' \
|
|
':(exclude)DIRECTIVE_*.md'
|
|
}
|
|
|
|
# scan_forbidden <label> <regex-terme>
|
|
# Flag toute occurrence NON préfixée d'un marqueur de prohibition / ci-allow.
|
|
scan_forbidden() {
|
|
local label="$1" term="$2" hits
|
|
hits="$(tracked_files | while read -r f; do
|
|
grep -inE "$term" "$f" 2>/dev/null \
|
|
| grep -ivE "$PROHIBITION" \
|
|
| grep -ivE "$ALLOW" \
|
|
| sed "s#^#${f}:#"
|
|
done)"
|
|
if [[ -n "$hits" ]]; then
|
|
report "Interdit détecté — $label :"
|
|
echo "$hits" | sed 's/^/ /'
|
|
else
|
|
ok "Aucun usage interdit — $label"
|
|
fi
|
|
}
|
|
|
|
echo "== 1. Plateformes git interdites (CLAUDE.md #2 · Gitea SEULEMENT) =="
|
|
scan_forbidden "GitHub" 'github\.com|git@github|github\.io'
|
|
scan_forbidden "GitLab" 'gitlab\.com|gitlab\.io'
|
|
scan_forbidden "Bitbucket" 'bitbucket\.org'
|
|
|
|
echo "== 2. CRM interdits (CLAUDE.md #3 · CRM = ERPNext natif) =="
|
|
scan_forbidden "EspoCRM" 'espocrm'
|
|
scan_forbidden "HubSpot" 'hubspot'
|
|
|
|
echo "== 3. Paiement interdit (CLAUDE.md #10 · Cardnet, pas Stripe) =="
|
|
scan_forbidden "Stripe" 'stripe'
|
|
|
|
echo "== 4. Écriture directe /var/www/html/static (Interdit absolu) =="
|
|
scan_forbidden "chemin /var/www/html/static en écriture" '/var/www/html/static/'
|
|
|
|
echo "== 5. Commande git clean (Interdit absolu) =="
|
|
scan_forbidden "git clean" 'git[[:space:]]+clean'
|
|
|
|
echo "== 6. Remote git = Gitea uniquement =="
|
|
if git remote -v >/dev/null 2>&1 && [[ -n "$(git remote)" ]]; then
|
|
bad_remote="$(git remote -v | grep -iE 'github\.com|gitlab\.com|bitbucket\.org' || true)"
|
|
if [[ -n "$bad_remote" ]]; then
|
|
report "Remote git interdit configuré :"; echo "$bad_remote" | sed 's/^/ /'
|
|
else
|
|
ok "Remotes git conformes (Gitea / interne uniquement)"
|
|
fi
|
|
else
|
|
ok "Aucun remote git configuré (CI checkout) — rien à vérifier"
|
|
fi
|
|
|
|
echo
|
|
if [[ "$FAIL" -eq 0 ]]; then
|
|
echo -e "\033[32m✅ Contraintes NON-NÉGOCIABLES respectées.\033[0m"
|
|
else
|
|
echo -e "\033[31m❌ Violation(s) de contrainte détectée(s). Voir ci-dessus.\033[0m"
|
|
echo " (Faux positif légitime ? Ajouter « ci-allow » sur la ligne concernée.)"
|
|
fi
|
|
exit "$FAIL"
|