Phases 6 et 7 : application SvelteKit, PWA, systemd, documentation
Application — rendu serveur sur adapter-node, conforme au §3bis : - tableau de bord, recherche à facettes, fiche détaillée, calendrier - /a-propos et /methode prérendues ; tout le reste en rendu serveur - API JSON ouverte : /api/textes, /api/textes/[slug], /api/echeances - recherche FTS5 avec surlignage et classement bm25 pondéré (le titre pèse dix fois plus que les points clés) - facettes recalculées sur le résultat filtré par les AUTRES facettes : cocher un thème doit recompter les statuts encore disponibles, sinon les compteurs mentent Identité OKI sobrifiée : tokens en CSS vanilla, thème sombre par défaut et clair opt-in, or comme seule couleur d'action, flag-bar une fois par écran, polices Archivo/Inter auto-hébergées. Politique de sécurité de contenu interdisant toute requête tierce. Les statuts ne reposent jamais sur la seule couleur : libellé en toutes lettres et pastille de forme distincte. Budget respecté très largement : 6,6 Ko de JavaScript sur l'accueil (3 Ko compressés) pour un plafond de 100 Ko. Les faits clés — AFD à 500 €, saisine 2026-915 DC, statut — sont lisibles sans JavaScript. Trois défauts trouvés en testant l'application, pas en relisant le code : - v_textes est une vue, et une vue SQLite n'a pas de rowid : la jointure avec l'index plein texte échouait (migration 005) - ppl-montagne remontait en « pertinence forte » alors que le corpus dit « sans portée pour la Guadeloupe » — la cotation comptait le mot sans lire la négation. Idem pour pjl-logement et accord-globe. Corrigé par une lecture du voisinage, avec quatre tests de non-régression. - les libellés d'affichage étaient importés depuis /server dans un composant, ce que SvelteKit interdit à raison PWA : service worker réseau-d'abord pour les pages, cache-d'abord pour le coffre du build. Une veille législative ne doit pas servir une page périmée quand le réseau répond — une date de promulgation change tout. Les réponses issues du cache portent un en-tête qui le dit. systemd : timer du pipeline (6 h, 18 h, dimanche 9 h, avec dispersion et rattrapage), application activée par socket avec mise en sommeil à 300 s. Unités durcies, validées par systemd-analyze. Documentation : README d'installation sur Debian/Ubuntu vierge, README-pipeline avec le piège des deux couples d'identifiants PISTE, et scripts/verifier-conformite.sh qui contrôle mécaniquement le §3bis et le §8. 172 tests, npm run check à 0 erreur, make verifier au vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
feb5544599
commit
34d81c66a8
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Contrôles de conformité au cahier des charges (§3bis et §8).
|
||||
#
|
||||
# Ces vérifications sont mécaniques : elles constatent, elles n'interprètent
|
||||
# pas. Un seul contrôle en échec fait échouer la commande.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echecs=0
|
||||
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
|
||||
ko() {
|
||||
printf ' \033[31m✗\033[0m %s\n' "$1"
|
||||
echecs=$((echecs + 1))
|
||||
}
|
||||
titre() { printf '\n\033[1m%s\033[0m\n' "$1"; }
|
||||
|
||||
titre "§3bis — architecture de rendu"
|
||||
|
||||
# Seule une déclaration exportée est lue par SvelteKit : chercher la chaîne
|
||||
# brute ferait échouer le contrôle sur les commentaires qui la mentionnent,
|
||||
# y compris ceux qui expliquent pourquoi on ne la pose pas.
|
||||
MOTIF_SSR='^[[:space:]]*export[[:space:]]+const[[:space:]]+ssr[[:space:]]*=[[:space:]]*false'
|
||||
MOTIF_CSR='^[[:space:]]*export[[:space:]]+const[[:space:]]+csr[[:space:]]*=[[:space:]]*false'
|
||||
|
||||
if grep -rnE "$MOTIF_SSR" web/src --include='*.ts' --include='*.js' --include='*.svelte' -q; then
|
||||
ko "un fichier déclare export const ssr = false (le mode SPA est écarté)"
|
||||
grep -rnE "$MOTIF_SSR" web/src --include='*.ts' --include='*.js' --include='*.svelte'
|
||||
else
|
||||
ok "aucun export const ssr = false : le rendu serveur est le défaut partout"
|
||||
fi
|
||||
|
||||
if grep -rnE "$MOTIF_CSR" web/src/routes/+layout.ts web/src/routes/+layout.server.ts 2>/dev/null -q; then
|
||||
ko "csr = false déclaré au layout racine"
|
||||
else
|
||||
ok "aucun csr = false global"
|
||||
fi
|
||||
|
||||
for page in a-propos methode; do
|
||||
if grep -q "prerender = true" "web/src/routes/$page/+page.ts" 2>/dev/null; then
|
||||
ok "prerender = true sur /$page"
|
||||
else
|
||||
ko "prerender = true absent de /$page"
|
||||
fi
|
||||
done
|
||||
|
||||
grep -q "adapter-node" web/svelte.config.js &&
|
||||
ok "adapter-node en place" || ko "adapter-node absent de svelte.config.js"
|
||||
|
||||
grep -q "precompress: true" web/svelte.config.js &&
|
||||
ok "precompress activé" || ko "precompress non activé"
|
||||
|
||||
titre "§3 — souveraineté des ressources"
|
||||
|
||||
tiers=$(grep -rnE "https?://(fonts\.googleapis|fonts\.gstatic|cdn\.|unpkg|jsdelivr|googletagmanager|google-analytics)" \
|
||||
web/src web/static 2>/dev/null | grep -v Binary || true)
|
||||
if [ -z "$tiers" ]; then
|
||||
ok "aucune ressource tierce référencée (polices, CDN, traceurs)"
|
||||
else
|
||||
ko "ressources tierces trouvées :"
|
||||
echo "$tiers"
|
||||
fi
|
||||
|
||||
nb_polices=$(find web/static/polices -name '*.woff2' 2>/dev/null | wc -l)
|
||||
[ "$nb_polices" -gt 0 ] &&
|
||||
ok "polices auto-hébergées ($nb_polices fichiers woff2)" ||
|
||||
ko "polices non auto-hébergées — lancer npm run polices"
|
||||
|
||||
titre "§8 — livrables"
|
||||
|
||||
for chemin in pipeline web data systemd tests README.md README-pipeline.md .env.example Makefile; do
|
||||
[ -e "$chemin" ] && ok "présent : $chemin" || ko "manquant : $chemin"
|
||||
done
|
||||
|
||||
[ -f web/build/index.js ] &&
|
||||
ok "build de production présent" || ko "build absent — lancer make build"
|
||||
|
||||
titre "§8.2 — intégrité des données"
|
||||
|
||||
if [ -f data/veille.db ]; then
|
||||
total=$(sqlite3 data/veille.db "SELECT COUNT(*) FROM textes")
|
||||
orphelins=$(sqlite3 data/veille.db \
|
||||
"SELECT COUNT(*) FROM textes t WHERE NOT EXISTS (SELECT 1 FROM sources s WHERE s.texte_id = t.id)")
|
||||
cc=$(sqlite3 data/veille.db "SELECT COUNT(*) FROM v_textes WHERE devant_cc = 1")
|
||||
sans_url=$(sqlite3 data/veille.db \
|
||||
"SELECT COUNT(*) FROM sources WHERE url NOT LIKE 'http%'")
|
||||
|
||||
[ "$total" -ge 49 ] && ok "$total textes (≥ 49 exigés)" || ko "$total textes, 49 exigés"
|
||||
[ "$orphelins" -eq 0 ] && ok "aucun texte sans source URL" || ko "$orphelins textes sans source"
|
||||
[ "$sans_url" -eq 0 ] && ok "toutes les sources ont une URL absolue" || ko "$sans_url sources sans URL"
|
||||
[ "$cc" -ge 7 ] && ok "$cc textes devant le Conseil constitutionnel (≥ 7)" || ko "$cc textes, 7 attendus"
|
||||
else
|
||||
ko "base absente — lancer make seed"
|
||||
fi
|
||||
|
||||
titre "Résultat"
|
||||
if [ "$echecs" -eq 0 ]; then
|
||||
printf ' \033[32mTous les contrôles passent.\033[0m\n\n'
|
||||
else
|
||||
printf ' \033[31m%d contrôle(s) en échec.\033[0m\n\n' "$echecs"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user