#!/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.
#   - le DESIGN SYSTEM de référence à la racine (OTO_DESIGN_SYSTEM_*.md) :
#     document root-owned, ajouté par un commit REFERENCE (non créé par un agent
#     worker, non éditable → escape ci-allow par ligne inapplicable). Son §15
#     « Fichiers canoniques sur VPS » est un INVENTAIRE des emplacements servis
#     (nginx sert /var/www/html/static, SYMLINK de /opt/oto/sites/static) — une
#     mention descriptive, pas une écriture exécutable. Même raison que ci-dessus :
#     l'usage réel vivrait dans du code (.sh/.py/.yml, toujours scanné). NB · ce
#     §15 liste le chemin SERVI /var/www/html/static/ alors que le mandat préfère
#     la SOURCE /opt/oto/sites/static/ — même inconsistance chemin-servi-vs-source
#     que la l.35 d'AUTORISATIONS, re-signalée à Michel (activity log 2026-08-03).
tracked_files() {
  git ls-files -- . \
    ':(exclude)ci/guard_constraints.sh' \
    ':(exclude).gitea/workflows/*.yml' \
    ':(exclude)AUTORISATIONS_*.md' \
    ':(exclude)DIRECTIVE_*.md' \
    ':(exclude)OTO_DESIGN_SYSTEM_*.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"
