Compare commits

...

199 Commits

Author SHA1 Message Date
8605b7198f Merge pull request 'reset(archicrat-ia): resynchronise les sources DOCX/MDX canoniques' (#322) from chore/archicrat-ia-canonical-docx-mdx-resync-20260423 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 19s
CI / build-and-anchors (push) Successful in 36s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 8m5s
Reviewed-on: #322
2026-04-23 11:53:07 +00:00
d41aed040f reset(archicrat-ia): resynchronise les sources DOCX/MDX canoniques
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 33s
CI / build-and-anchors (pull_request) Successful in 37s
2026-04-23 13:51:27 +02:00
bf01a83268 Merge pull request 'Synchronise les contenus glossaire et ajoute les scripts de conversion DOCX/MDX' (#321) from chore/reinject-docx-mdx-and-glossaire-sync-20260423 into main
All checks were successful
CI / build-and-anchors (push) Successful in 35s
Proposer Apply (Queue) / apply-proposer (push) Successful in 28s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 9m56s
Reviewed-on: #321
2026-04-23 10:25:49 +00:00
5b427d5602 Synchronise les contenus glossaire et ajoute les scripts de conversion DOCX/MDX
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 37s
CI / build-and-anchors (pull_request) Successful in 34s
2026-04-23 12:04:31 +02:00
fa46971e76 Merge pull request 'reset(archicrat-ia): canonical docx import and anchors baseline' (#320) from reset/archicrat-ia-canonical-import-anchors into main
All checks were successful
CI / build-and-anchors (push) Successful in 54s
SMOKE / smoke (push) Successful in 12s
Deploy staging+live (annotations) / deploy (push) Successful in 8m25s
Proposer Apply (Queue) / apply-proposer (push) Successful in 8s
Reviewed-on: #320
2026-04-19 17:50:59 +02:00
c313587b26 reset(archicrat-ia): canonical docx import and anchors baseline
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 54s
CI / build-and-anchors (pull_request) Successful in 54s
2026-04-19 16:25:42 +02:00
4976ddcc16 Merge pull request 'fix/ch4-docx-sync-20260330' (#319) from fix/ch4-docx-sync-20260330 into main
All checks were successful
CI / build-and-anchors (push) Successful in 50s
Proposer Apply (Queue) / apply-proposer (push) Successful in 44s
SMOKE / smoke (push) Successful in 22s
Deploy staging+live (annotations) / deploy (push) Successful in 12m42s
Reviewed-on: #319
2026-03-30 16:08:47 +02:00
17e11f0322 fix(archicrat-ia): remove duplicated chapitre 4 heading
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 48s
2026-03-30 16:06:22 +02:00
7df18adfa8 fix(archicrat-ia): sync chapitre 4 from official docx and accept anchor reset
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 58s
2026-03-30 15:57:43 +02:00
535c5108e2 Merge pull request 'fix(archicrat-ia): drop duplicate chapter 3 heading' (#318) from fix/ch3-docx-sync-20260329 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 28s
CI / build-and-anchors (push) Successful in 47s
SMOKE / smoke (push) Successful in 12s
Deploy staging+live (annotations) / deploy (push) Successful in 11m38s
Reviewed-on: #318
2026-03-30 10:30:19 +02:00
20705f6c90 fix(archicrat-ia): drop duplicate chapter 3 heading
All checks were successful
SMOKE / smoke (push) Successful in 2s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-30 10:26:22 +02:00
eabd2f5f29 Merge pull request 'fix(archicrat-ia): sync chapitre 3 from official docx and accept anchor reset' (#317) from fix/ch3-docx-sync-20260329 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 23s
CI / build-and-anchors (push) Successful in 47s
SMOKE / smoke (push) Successful in 9s
Deploy staging+live (annotations) / deploy (push) Successful in 9m38s
Reviewed-on: #317
2026-03-29 21:57:44 +02:00
482151c31c fix(archicrat-ia): sync chapitre 3 from official docx and accept anchor reset
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 46s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-29 21:51:25 +02:00
6d9d5a460e Merge pull request 'fix(archicrat-ia): restore chapitre 1 doctrinal opening' (#315) from fix/ch1-restore-opening-20260329-144234 into main
All checks were successful
CI / build-and-anchors (push) Successful in 46s
Proposer Apply (Queue) / apply-proposer (push) Successful in 27s
SMOKE / smoke (push) Successful in 12s
Deploy staging+live (annotations) / deploy (push) Successful in 10m31s
Reviewed-on: #315
2026-03-29 14:53:31 +02:00
89d06ade16 fix(archicrat-ia): restore chapitre 1 doctrinal opening
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 1m25s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-29 14:46:51 +02:00
69b35df10c Merge pull request 'fix(archicrat-ia): restore advanced chapitre 1/2 sources and accept chapter 2 anchor reset' (#314) from fix/restore-ch1-ch2-sources-and-churn-20260329 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 43s
Deploy staging+live (annotations) / deploy (push) Successful in 12m16s
SMOKE / smoke (push) Successful in 10s
CI / build-and-anchors (push) Successful in 58s
Reviewed-on: #314
2026-03-29 11:31:33 +02:00
b5475e9be1 fix(archicrat-ia): restore advanced chapitre 1/2 sources and accept chapter 2 anchor reset
All checks were successful
SMOKE / smoke (push) Successful in 15s
CI / build-and-anchors (push) Successful in 49s
CI / build-and-anchors (pull_request) Successful in 1m4s
2026-03-29 11:28:19 +02:00
fdd3aace5a Merge pull request 'chore(tooling): add docx source audit and repair helpers' (#313) from chore/docx-tooling-source-audit-and-fix-v3-20260328 into main
All checks were successful
CI / build-and-anchors (push) Successful in 47s
Proposer Apply (Queue) / apply-proposer (push) Successful in 28s
SMOKE / smoke (push) Successful in 6s
Deploy staging+live (annotations) / deploy (push) Successful in 16m22s
Reviewed-on: #313
2026-03-28 23:55:55 +01:00
f86704d67e chore(tooling): add docx source audit and repair helpers
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 47s
2026-03-28 23:34:42 +01:00
ec8e29a313 Merge pull request 'content(archicrat-ia): drop duplicate chapter H1 in chapitres 1 and 2' (#311) from fix/archicrat-ia-drop-duplicate-h1-ch1-ch2-20260328 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 25s
Deploy staging+live (annotations) / deploy (push) Successful in 8m10s
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 51s
Reviewed-on: #311
2026-03-28 23:21:55 +01:00
1dc9a60580 content(archicrat-ia): drop duplicate chapter H1 in chapitres 1 and 2
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 48s
2026-03-28 23:20:01 +01:00
ee18b26d03 Merge pull request 'content(archicrat-ia): refresh official chapitre 2 docx' (#309) from chore/chapitre-2-docx-refresh-20260328 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 22s
Deploy staging+live (annotations) / deploy (push) Successful in 9m29s
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 46s
CI / build-and-anchors (pull_request) Successful in 43s
Reviewed-on: #309
2026-03-28 22:46:24 +01:00
5f4a0f74db content(archicrat-ia): refresh official chapitre 2 docx
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 46s
CI / build-and-anchors (pull_request) Successful in 49s
2026-03-28 22:42:26 +01:00
6b17df7320 Merge pull request 'content(archicrat-ia): refresh official chapitre 1 docx and anchors baseline' (#308) from chore/chapitre-1-docx-refresh-20260328 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 18s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 9m26s
Reviewed-on: #308
2026-03-28 19:14:50 +01:00
0c33495342 content(archicrat-ia): refresh official chapitre 1 docx and anchors baseline
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 46s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-28 19:12:21 +01:00
d8a09b1def Merge pull request 'chore/prologue-docx-corrections-20260328' (#307) from chore/prologue-docx-corrections-20260328 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 27s
CI / build-and-anchors (push) Successful in 49s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 9m15s
Reviewed-on: #307
2026-03-28 14:49:48 +01:00
39af501ea0 test(anchors): refresh prologue baseline after intentional text cleanup
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 51s
CI / build-and-anchors (pull_request) Successful in 47s
2026-03-28 14:44:16 +01:00
4c821d9e83 content(archicrat-ia): refresh official prologue docx corrections 2026-03-28 14:44:16 +01:00
deb4a91348 Merge pull request 'chore/prologue-reimport-final-20260327' (#306) from chore/prologue-reimport-final-20260327 into main
All checks were successful
CI / build-and-anchors (push) Successful in 46s
Proposer Apply (Queue) / apply-proposer (push) Successful in 31s
SMOKE / smoke (push) Successful in 12s
Deploy staging+live (annotations) / deploy (push) Successful in 9m18s
Reviewed-on: #306
2026-03-27 23:20:58 +01:00
5b36b8e54e test(anchors): refresh baseline after prologue reimport
All checks were successful
SMOKE / smoke (push) Successful in 14s
CI / build-and-anchors (push) Successful in 50s
CI / build-and-anchors (pull_request) Successful in 54s
2026-03-27 23:11:01 +01:00
eda5a877ef content(archicrat-ia): reimport official prologue and align importer defaults 2026-03-27 23:11:01 +01:00
5b615a6999 Merge pull request 'fix(glossaire): align reading follow top actions with glossary navigation' (#305) from fix/glossaire-reading-follow-relations-h2 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 20s
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 9s
Deploy staging+live (annotations) / deploy (push) Successful in 8m16s
Reviewed-on: #305
2026-03-26 22:33:10 +01:00
99cf0947da fix(glossaire): align reading follow top actions with glossary navigation
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-26 22:30:05 +01:00
dbd1e14e4e Merge pull request 'fix(glossaire): include standalone relation headings in reading follow' (#304) from fix/glossaire-reading-follow-relations-h2 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 9s
Deploy staging+live (annotations) / deploy (push) Successful in 7m55s
Reviewed-on: #304
2026-03-26 21:35:00 +01:00
7033354011 fix(glossaire): include standalone relation headings in reading follow
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-26 21:29:26 +01:00
7345730e3c Merge pull request 'fix(glossaire): expose relations heading to reading follow' (#303) from fix/glossaire-relations-follow-heading into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 24s
CI / build-and-anchors (push) Successful in 50s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m19s
Reviewed-on: #303
2026-03-26 20:47:02 +01:00
cea94c56db fix(glossaire): expose relations heading to reading follow
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-26 20:42:01 +01:00
c1e24736e3 Merge pull request 'feat(glossaire): deduplicate entry aside relation groups' (#302) from feat/glossaire-entry-aside-dedup into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
CI / build-and-anchors (push) Successful in 49s
SMOKE / smoke (push) Successful in 4s
Deploy staging+live (annotations) / deploy (push) Successful in 9m41s
Reviewed-on: #302
2026-03-26 20:27:31 +01:00
24bbfbc17f feat(glossaire): deduplicate entry aside relation groups
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 20:24:49 +01:00
a11e2f1d18 Merge pull request 'fix(glossaire): compact sticky entry hero on glossary pages' (#301) from fix/glossaire-entry-sticky-hero-collapse into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 32s
CI / build-and-anchors (push) Successful in 45s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m18s
Reviewed-on: #301
2026-03-26 18:32:19 +01:00
630b146d02 fix(glossaire): compact sticky entry hero on glossary pages
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 50s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 18:30:34 +01:00
551360db83 Merge pull request 'fix(ci): use local pagefind binary instead of npx wrapper' (#300) from fix/ci-pagefind-local-bin into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 26s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m45s
Reviewed-on: #300
2026-03-26 14:45:29 +01:00
a96c282780 fix(ci): use local pagefind binary instead of npx wrapper
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 14:41:02 +01:00
d2e0f147c2 Merge pull request 'audit(glossaire): tighten portal exposure and cross-page coherence' (#299) from audit/glossaire-transverse-coherence-fix into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 33s
SMOKE / smoke (push) Successful in 10s
Deploy staging+live (annotations) / deploy (push) Successful in 10m22s
CI / build-and-anchors (push) Successful in 45s
Reviewed-on: #299
2026-03-26 14:20:37 +01:00
ad95364021 audit(glossaire): tighten portal exposure and cross-page coherence
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 1m11s
CI / build-and-anchors (pull_request) Successful in 1m17s
2026-03-26 14:16:05 +01:00
e48e322363 Merge pull request 'feat(glossaire): harmonize portal pages and sticky reading ux' (#298) from feat/glossaire-portal-polish-final into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 27s
Deploy staging+live (annotations) / deploy (push) Successful in 10m51s
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 43s
Reviewed-on: #298
2026-03-26 13:01:46 +01:00
a9f2a5bbd4 feat(glossaire): harmonize portal pages and sticky reading ux
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 54s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-26 12:58:17 +01:00
0cba8f868e Merge pull request 'feat(glossaire): harmonize portal pages with shared components' (#297) from feat/glossaire-portals-harmonization into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 28s
CI / build-and-anchors (push) Successful in 43s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 10m36s
Reviewed-on: #297
2026-03-25 23:52:36 +01:00
f8e3ee4cca feat(glossaire): harmonize portal pages with shared components
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-25 23:49:00 +01:00
92e0ad01c6 Merge pull request 'refactor(glossaire): componentize glossary entry page' (#296) from refactor/glossaire-entry-componentization into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 22s
CI / build-and-anchors (push) Successful in 48s
SMOKE / smoke (push) Successful in 6s
Deploy staging+live (annotations) / deploy (push) Successful in 9m57s
Reviewed-on: #296
2026-03-25 19:28:10 +01:00
e6c18d6b16 refactor(glossaire): componentize glossary entry page
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-25 19:18:58 +01:00
a3092f5d5b Merge pull request 'refactor(glossaire): componentize glossary home sections' (#295) from refactor/glossaire-home-componentization into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 32s
Deploy staging+live (annotations) / deploy (push) Successful in 11m41s
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 46s
Reviewed-on: #295
2026-03-25 18:30:14 +01:00
7187b69935 refactor(glossaire): componentize glossary home sections
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 48s
2026-03-25 18:26:43 +01:00
4ba4453661 Merge pull request 'refactor(glossaire): centralize aside and home data' (#294) from feat/glossaire-relational-asides-and-home into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 9m5s
Reviewed-on: #294
2026-03-25 16:50:31 +01:00
ee42e391e3 refactor(glossaire): centralize aside and home data
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-25 16:48:43 +01:00
f7756be59e Merge pull request 'feat/glossaire-entry-relations-rendering' (#293) from feat/glossaire-entry-relations-rendering into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 27s
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 6s
Deploy staging+live (annotations) / deploy (push) Successful in 9m40s
Reviewed-on: #293
2026-03-25 15:39:41 +01:00
4abe70e10e refactor(glossaire): extract entry relations rendering
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-25 15:30:51 +01:00
b2b4ec35c0 refactor(glossaire): preserve editorial order for entry relations 2026-03-25 15:20:39 +01:00
b255436958 Merge pull request 'refactor(glossaire): centralize glossary relation helpers' (#292) from feat/glossaire-ui-relations-foundation into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 24s
Deploy staging+live (annotations) / deploy (push) Successful in 9m2s
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 43s
Reviewed-on: #292
2026-03-25 14:17:11 +01:00
ad06b34a85 refactor(glossaire): centralize glossary relation helpers
All checks were successful
CI / build-and-anchors (pull_request) Successful in 42s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 41s
2026-03-25 14:15:39 +01:00
a38f585f3d Merge pull request 'feat(glossaire): strengthen paradigms and support theories cross-links' (#291) from chore/glossaire-paradigmes-and-support-theories-mesh into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 14s
Deploy staging+live (annotations) / deploy (push) Successful in 9m54s
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 48s
Reviewed-on: #291
2026-03-25 10:30:37 +01:00
bf0dc125d1 feat(glossaire): strengthen paradigms and support theories cross-links
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 1m19s
2026-03-25 10:28:42 +01:00
f61dc15b47 Merge pull request 'feat(glossaire): strengthen meta-regimes and archicrations cross-links' (#290) from chore/glossaire-meta-regimes-and-archicrations-mesh into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 16s
Deploy staging+live (annotations) / deploy (push) Successful in 8m31s
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 45s
Reviewed-on: #290
2026-03-24 20:41:52 +01:00
1ac3d91a19 fix(glossaire): repair malformed normativo-politiques frontmatter
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-24 20:40:10 +01:00
100ba10409 feat(glossaire): strengthen meta-regimes and archicrations cross-links
Some checks failed
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Failing after 27s
2026-03-24 20:33:39 +01:00
5f14785abb Merge pull request 'chore/glossaire-pathologies-figures-and-reinstitution-mesh' (#289) from chore/glossaire-pathologies-figures-and-reinstitution-mesh into main
All checks were successful
CI / build-and-anchors (push) Successful in 47s
Proposer Apply (Queue) / apply-proposer (push) Successful in 32s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 11m12s
Reviewed-on: #289
2026-03-24 18:35:06 +01:00
c7043ae9d5 fix(glossaire): repair malformed seeAlso frontmatter
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 51s
2026-03-24 18:32:55 +01:00
bd1235f8c3 feat(glossaire): strengthen pathologies, figures, and reinstitution cross-links 2026-03-24 18:32:12 +01:00
7ae7b4dca3 Merge pull request 'chore/glossaire-scenes-topologies-audit-and-mesh2' (#288) from chore/glossaire-scenes-topologies-audit-and-mesh into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 18s
Deploy staging+live (annotations) / deploy (push) Successful in 8m20s
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 43s
Reviewed-on: #288
2026-03-24 17:57:50 +01:00
f088db57d4 feat(glossaire): strengthen scenes, topologies, and IA audit cross-links
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 43s
CI / build-and-anchors (pull_request) Successful in 42s
2026-03-24 17:56:13 +01:00
311e94ed91 Merge pull request 'feat(glossaire): strengthen tensions cross-links' (#287) from chore/glossaire-tensions-audit-and-mesh into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 18s
Deploy staging+live (annotations) / deploy (push) Successful in 9m53s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 41s
Reviewed-on: #287
2026-03-24 14:37:57 +01:00
e078f3f9ab feat(glossaire): strengthen tensions cross-links
All checks were successful
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 45s
SMOKE / smoke (push) Successful in 5s
2026-03-24 14:32:44 +01:00
7c4bb5a2cf Merge pull request 'fix(glossaire): sanitize imported artefacts and relation metadata' (#286) from chore/glossaire-sanitize-corpus into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 30s
Deploy staging+live (annotations) / deploy (push) Successful in 11m10s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 41s
Reviewed-on: #286
2026-03-24 13:37:47 +01:00
214e174635 fix(glossaire): sanitize imported artefacts and relation metadata
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 47s
2026-03-24 13:34:03 +01:00
f1b2f4605f Merge pull request 'feat/glossaire-sticky-entry-and-aside-polish-20260324' (#285) from feat/glossaire-sticky-entry-and-aside-polish-20260324 into main
All checks were successful
CI / build-and-anchors (push) Successful in 48s
Proposer Apply (Queue) / apply-proposer (push) Successful in 34s
SMOKE / smoke (push) Successful in 16s
Deploy staging+live (annotations) / deploy (push) Successful in 10m2s
Reviewed-on: #285
2026-03-24 00:32:36 +01:00
87955adf5d feat(glossaire): polish sticky entry flow and aside navigation
All checks were successful
SMOKE / smoke (push) Successful in 18s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 48s
2026-03-24 00:29:39 +01:00
e39a0c547d feat(glossaire): compress paradigme hero when reading follow is active 2026-03-23 10:27:28 +01:00
c89ddf7237 chore(glossaire): checkpoint before portal hero compression 2026-03-23 00:33:19 +01:00
615effe8bf Merge pull request 'fix(glossaire): description précise du correctif' (#284) from fix/nom-court-et-clair into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 15s
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 44s
Deploy staging+live (annotations) / deploy (push) Successful in 8m29s
Reviewed-on: #284
2026-03-21 21:41:19 +01:00
e952b344a0 fix(glossaire): description précise du correctif
All checks were successful
SMOKE / smoke (push) Successful in 2s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-21 21:39:02 +01:00
bb0572cc1a Merge pull request 'fix(glossaire): harmonize portal sticky hero and follow behavior' (#283) from fix/glossaire-portails-sticky-follow-20260321 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 20s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 9s
Deploy staging+live (annotations) / deploy (push) Successful in 9m19s
Reviewed-on: #283
2026-03-21 20:22:43 +01:00
f6a2347278 fix(glossaire): harmonize portal sticky hero and follow behavior
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-21 20:15:13 +01:00
d902c2bf98 Merge pull request 'feat(glossaire): refine portal pages and contextual asides' (#282) from feat/glossaire-portails-asides-polish into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 17s
CI / build-and-anchors (push) Successful in 48s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 8m30s
Reviewed-on: #282
2026-03-20 17:46:21 +01:00
baa2082f51 feat(glossaire): refine portal pages and contextual asides
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 49s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-20 17:42:09 +01:00
2f249b420f Merge pull request 'feat(glossaire): enrich entries and refine glossary navigation' (#281) from feat/glossaire-enrich-navigation into main
All checks were successful
CI / build-and-anchors (push) Successful in 48s
Proposer Apply (Queue) / apply-proposer (push) Successful in 29s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 10m44s
Reviewed-on: #281
2026-03-19 22:18:49 +01:00
d6b4eb82f4 feat(glossaire): enrich entries and refine glossary navigation
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 55s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-19 21:53:33 +01:00
bfa44fecda Merge pull request 'Add archicrations-esthetico-symboliques.md' (#280) from chore/add-archicrations-esthetico-symboliques into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 23s
CI / build-and-anchors (push) Successful in 47s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 10m53s
Reviewed-on: #280
2026-03-18 23:53:32 +01:00
e329235aa9 Add archicrations-esthetico-symboliques.md
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 51s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-18 23:51:48 +01:00
8cbaa5117c Merge pull request 'Document cockpit local, NAS supervision, and ops workflow' (#279) from docs-and-ops/cockpit-finalization into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 28s
Deploy staging+live (annotations) / deploy (push) Successful in 49s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 4s
Reviewed-on: #279
2026-03-18 18:24:44 +01:00
3086f333ed Document cockpit local, NAS supervision, and ops workflow
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 49s
CI / build-and-anchors (pull_request) Successful in 42s
2026-03-18 18:21:52 +01:00
c1c3c19d13 Merge pull request 'Use Europe/Paris build time for ops health manifest' (#278) from ops/builtat-paris-time into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 30s
Deploy staging+live (annotations) / deploy (push) Successful in 46s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 2s
Reviewed-on: #278
2026-03-18 11:14:11 +01:00
ddcd0acd4d Use Europe/Paris build time for ops health manifest
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 48s
CI / build-and-anchors (pull_request) Successful in 49s
2026-03-18 11:11:54 +01:00
9bc4eeb3e7 Merge pull request 'Add ops health manifest to deploy pipeline' (#277) from ops/ops-health-deploy into main
All checks were successful
CI / build-and-anchors (push) Successful in 44s
Proposer Apply (Queue) / apply-proposer (push) Successful in 19s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 11m8s
Reviewed-on: #277
2026-03-18 10:31:32 +01:00
7a9a5319ac Add ops health manifest to deploy pipeline
All checks were successful
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (push) Successful in 58s
CI / build-and-anchors (pull_request) Successful in 1m3s
2026-03-18 10:28:13 +01:00
7d75de5c9f Merge pull request 'docs: formalize localhost auto-sync architecture' (#276) from docs/localhost-auto-sync into main
All checks were successful
CI / build-and-anchors (push) Successful in 51s
Proposer Apply (Queue) / apply-proposer (push) Successful in 19s
SMOKE / smoke (push) Successful in 6s
Deploy staging+live (annotations) / deploy (push) Successful in 46s
Reviewed-on: #276
2026-03-16 21:19:51 +01:00
69c91cb661 docs: formalize localhost auto-sync architecture
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-16 21:14:41 +01:00
a1bfbf4405 Merge pull request 'proposer: apply 2 tickets on /archicrat-ia/prologue/' (#275) from bot/proposer-273-ce91b3d8cf02 into main
All checks were successful
CI / build-and-anchors (push) Successful in 44s
Proposer Apply (Queue) / apply-proposer (push) Successful in 33s
SMOKE / smoke (push) Successful in 12s
Deploy staging+live (annotations) / deploy (push) Successful in 9m57s
Reviewed-on: #275
2026-03-16 16:46:20 +01:00
archicratie-bot
be26b425d8 edit: apply ticket #274 (/archicrat-ia/prologue/#p-23-d91a7b78)
All checks were successful
CI / build-and-anchors (push) Successful in 51s
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (pull_request) Successful in 42s
2026-03-16 15:43:09 +00:00
archicratie-bot
abf88e7037 edit: apply ticket #273 (/archicrat-ia/prologue/#p-22-a416d473) 2026-03-16 15:42:47 +00:00
04fee32fdb Merge pull request 'proposer: apply 2 tickets on /archicrat-ia/prologue/' (#272) from bot/proposer-270-39655773c199 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 26s
CI / build-and-anchors (push) Successful in 43s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m20s
Reviewed-on: #272
2026-03-16 13:57:16 +01:00
archicratie-bot
fbddf5c3fc edit: apply ticket #271 (/archicrat-ia/prologue/#p-17-b8c5bf21)
All checks were successful
CI / build-and-anchors (push) Successful in 50s
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-16 12:54:46 +00:00
archicratie-bot
bad748df3a edit: apply ticket #270 (/archicrat-ia/prologue/#p-7-64a0ca9c) 2026-03-16 12:54:21 +00:00
0066cf8601 Merge pull request 'fix(actions): harden proposer queue against duplicate batch PRs' (#269) from hotfix/proposer-close-verify into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
Deploy staging+live (annotations) / deploy (push) Successful in 33s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 4s
Reviewed-on: #269
2026-03-16 13:42:52 +01:00
5d3473d66c fix(actions): harden proposer queue against duplicate batch PRs
All checks were successful
SMOKE / smoke (push) Successful in 2s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-16 13:39:09 +01:00
f9d34110e4 Merge pull request 'proposer: apply 2 tickets on /archicrat-ia/prologue/' (#266) from bot/proposer-264-20260316-120249 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 20s
CI / build-and-anchors (push) Successful in 41s
SMOKE / smoke (push) Successful in 7s
Deploy staging+live (annotations) / deploy (push) Successful in 7m47s
Reviewed-on: #266
2026-03-16 13:18:27 +01:00
archicratie-bot
84e9c3ead4 edit: apply ticket #265 (/archicrat-ia/prologue/#p-5-85126fa5)
All checks were successful
CI / build-and-anchors (pull_request) Successful in 43s
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 39s
2026-03-16 12:03:37 +00:00
archicratie-bot
72e59175fc edit: apply ticket #264 (/archicrat-ia/prologue/#p-4-8ed4f807) 2026-03-16 12:03:13 +00:00
81b69ac6d5 Merge pull request 'fix(actions): verify proposer issue closure after PR creation' (#263) from hotfix/proposer-close-verify into main
All checks were successful
CI / build-and-anchors (push) Successful in 43s
Deploy staging+live (annotations) / deploy (push) Successful in 47s
Proposer Apply (Queue) / apply-proposer (push) Successful in 33s
SMOKE / smoke (push) Successful in 2s
Reviewed-on: #263
2026-03-16 12:54:26 +01:00
513ae72e85 fix(actions): verify proposer issue closure after PR creation
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 43s
CI / build-and-anchors (pull_request) Successful in 47s
2026-03-16 12:52:47 +01:00
4c4dd1c515 Merge pull request 'fix(actions): remove fragile heredocs from proposer PR step' (#262) from hotfix/proposer-no-heredoc-pr-step into main
All checks were successful
Deploy staging+live (annotations) / deploy (push) Successful in 39s
CI / build-and-anchors (push) Successful in 46s
SMOKE / smoke (push) Successful in 7s
Proposer Apply (Queue) / apply-proposer (push) Successful in 1m30s
Reviewed-on: #262
2026-03-16 12:34:12 +01:00
46b15ed6ab fix(actions): remove fragile heredocs from proposer PR step
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 40s
2026-03-16 12:30:43 +01:00
a015e72f7c Merge pull request 'proposer: apply ticket #257' (#261) from bot/proposer-257-20260316-111742 into main
All checks were successful
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 8s
Proposer Apply (Queue) / apply-proposer (push) Successful in 1m26s
Deploy staging+live (annotations) / deploy (push) Successful in 7m52s
Reviewed-on: #261
2026-03-16 12:21:55 +01:00
archicratie-bot
d5df7d77a0 edit: apply ticket #257 (/archicrat-ia/prologue/#p-0-d7974f88)
All checks were successful
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 40s
SMOKE / smoke (push) Successful in 4s
2026-03-16 11:18:06 +00:00
ec3ceee862 Merge pull request 'fix(actions): tolerate empty label payload in proposer gate' (#260) from debug/proposer-257 into main
Some checks failed
CI / build-and-anchors (push) Successful in 46s
Deploy staging+live (annotations) / deploy (push) Successful in 48s
SMOKE / smoke (push) Successful in 2s
Proposer Apply (Queue) / apply-proposer (push) Failing after 1m39s
Reviewed-on: #260
2026-03-16 12:16:12 +01:00
867475c3ff fix(actions): tolerate empty label payload in proposer gate
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-16 12:14:01 +01:00
b024c5557c Merge pull request 'fix(editorial): preserve frontmatter in apply-ticket' (#259) from hotfix/preserve-frontmatter-apply-ticket into main
All checks were successful
CI / build-and-anchors (push) Successful in 47s
Proposer Apply (Queue) / apply-proposer (push) Successful in 38s
SMOKE / smoke (push) Successful in 11s
Deploy staging+live (annotations) / deploy (push) Successful in 9m38s
Reviewed-on: #259
2026-03-16 11:52:07 +01:00
93306f360d fix(editorial): preserve frontmatter in apply-ticket
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 51s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-16 11:48:08 +01:00
52847d999d Merge pull request 'fix(actions): silence anno workflow on proposer tickets' (#258) from hotfix/fix-proposer-runtime-v2 into main
Some checks failed
Deploy staging+live (annotations) / deploy (push) Successful in 51s
CI / build-and-anchors (push) Successful in 49s
SMOKE / smoke (push) Successful in 5s
Proposer Apply (Queue) / apply-proposer (push) Failing after 48s
Reviewed-on: #258
2026-03-16 11:11:28 +01:00
b9629b43ff fix(actions): silence anno workflow on proposer tickets
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 49s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-16 11:06:11 +01:00
06482a9f8d Merge pull request 'fix(actions): make proposer queue runtime-safe' (#254) from hotfix/fix-proposer-runtime-v2 into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
CI / build-and-anchors (push) Successful in 46s
SMOKE / smoke (push) Successful in 6s
Deploy staging+live (annotations) / deploy (push) Successful in 42s
Reviewed-on: #254
2026-03-16 00:59:50 +01:00
f2e4ae5ac2 fix(actions): make proposer queue runtime-safe
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 42s
2026-03-16 00:58:10 +01:00
71baf0f6da Merge pull request 'fix(actions): repair proposer workflow yaml' (#253) from hotfix/fix-proposer-workflow into main
Some checks failed
CI / build-and-anchors (push) Successful in 48s
Deploy staging+live (annotations) / deploy (push) Successful in 1m0s
SMOKE / smoke (push) Successful in 4s
Proposer Apply (Queue) / apply-proposer (push) Failing after 4s
Reviewed-on: #253
2026-03-16 00:42:17 +01:00
d02b6fc347 fix(actions): repair proposer workflow yaml
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 43s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-16 00:38:46 +01:00
431f1e347b Merge pull request 'chore(editorial): harden proposer queue and apply-ticket' (#252) from chore/editorial-hardening-v1-clean into main
Some checks are pending
Deploy staging+live (annotations) / deploy (push) Waiting to run
CI / build-and-anchors (push) Successful in 1m6s
SMOKE / smoke (push) Successful in 39s
Reviewed-on: #252
2026-03-16 00:02:31 +01:00
ab6f45ed5c chore(editorial): harden proposer queue and apply-ticket
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 43s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-15 23:58:11 +01:00
02c060d239 Merge pull request 'chore/reset-from-docx-clean-base' (#251) from chore/reset-from-docx-clean-base into main
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 45s
Deploy staging+live (annotations) / deploy (push) Successful in 9m50s
Reviewed-on: #251
2026-03-15 21:19:24 +01:00
be2029de82 ci(anchors): accept intentional chapitre 1 anchor reset
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-15 21:11:20 +01:00
e148eaeaf3 content(archicrat-ia): refresh chapitre 1 from official docx
Some checks failed
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Failing after 42s
2026-03-15 20:50:35 +01:00
c63a1e6ce4 chore(reset): reimport chapitre 1 from docx clean base
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 44s
2026-03-15 20:33:49 +01:00
b3a73a7781 Merge pull request 'chore(reset): reimport chapitre 1 from docx clean base' (#250) from chore/reset-from-docx-clean-base into main
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 9m32s
Reviewed-on: #250
2026-03-15 15:31:37 +01:00
1968585d0f chore(reset): reimport chapitre 1 from docx clean base
All checks were successful
CI / build-and-anchors (push) Successful in 57s
CI / build-and-anchors (pull_request) Successful in 50s
SMOKE / smoke (push) Successful in 5s
2026-03-15 15:29:09 +01:00
b33c758411 Merge pull request 'proposer: apply ticket #236' (#243) from bot/proposer-236-20260315-112808 into main
All checks were successful
Deploy staging+live (annotations) / deploy (push) Successful in 12m1s
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 43s
Reviewed-on: #243
2026-03-15 12:55:33 +01:00
afa543125c Merge pull request 'proposer: apply ticket #235' (#242) from bot/proposer-235-20260315-112515 into main
Some checks are pending
Deploy staging+live (annotations) / deploy (push) Has started running
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 51s
Reviewed-on: #242
2026-03-15 12:47:26 +01:00
archicratie-bot
0d0252cac0 edit: apply ticket #236 (/archicrat-ia/chapitre-1/#p-400-8959d62e)
All checks were successful
CI / build-and-anchors (push) Successful in 45s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-15 11:28:36 +00:00
archicratie-bot
a8bd9aeed5 edit: apply ticket #235 (/archicrat-ia/chapitre-1/#p-139-caa4e99d)
All checks were successful
CI / build-and-anchors (pull_request) Successful in 47s
CI / build-and-anchors (push) Successful in 41s
SMOKE / smoke (push) Successful in 3s
2026-03-15 11:25:43 +00:00
d277c61afd Merge pull request 'proposer: apply ticket #229' (#232) from bot/proposer-229-20260315-105537 into main
Some checks failed
CI / build-and-anchors (push) Successful in 51s
SMOKE / smoke (push) Successful in 10s
Deploy staging+live (annotations) / deploy (push) Has been cancelled
Reviewed-on: #232
2026-03-15 12:24:34 +01:00
archicratie-bot
86479952d1 edit: apply ticket #229 (/archicrat-ia/chapitre-1/#p-10-1a706744)
All checks were successful
CI / build-and-anchors (pull_request) Successful in 42s
CI / build-and-anchors (push) Successful in 42s
SMOKE / smoke (push) Successful in 5s
2026-03-15 10:56:03 +00:00
c94024a8ae Merge pull request 'feat(glossaire): add advanced external paradigms from chapter 3' (#228) from feat/glossaire-archicrations-cartographie into main
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 45s
Deploy staging+live (annotations) / deploy (push) Successful in 9m53s
Reviewed-on: #228
2026-03-15 10:08:48 +01:00
70611d16f8 feat(glossaire): add advanced external paradigms from chapter 3
All checks were successful
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-15 10:05:32 +01:00
354db231b8 Merge pull request 'feat(glossaire): enrichit la cartographie archicratique et les archicrations' (#227) from feat/glossaire-archicrations-cartographie into main
All checks were successful
SMOKE / smoke (push) Successful in 14s
CI / build-and-anchors (push) Successful in 45s
Deploy staging+live (annotations) / deploy (push) Successful in 9m37s
Reviewed-on: #227
2026-03-14 19:57:38 +01:00
9d8d60d00f feat(glossaire): enrichit la cartographie archicratique et les archicrations
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 38s
2026-03-14 19:55:16 +01:00
f5d25abbec Merge pull request 'feat(glossaire): add advanced external paradigms from chapter 3' (#226) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 10s
CI / build-and-anchors (push) Successful in 44s
Deploy staging+live (annotations) / deploy (push) Successful in 7m13s
Reviewed-on: #226
2026-03-13 18:55:10 +01:00
8e9f7314f5 feat(glossaire): add advanced external paradigms from chapter 3
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 40s
2026-03-13 18:48:33 +01:00
03b88b944d Merge pull request 'feat(glossaire): integrate paradigms portal into glossary navigation' (#225) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 21s
CI / build-and-anchors (push) Successful in 43s
Deploy staging+live (annotations) / deploy (push) Successful in 7m38s
Reviewed-on: #225
2026-03-13 18:06:43 +01:00
385c36f660 feat(glossaire): integrate paradigms portal into glossary navigation
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-13 18:04:13 +01:00
cfa092cd38 Merge pull request 'feat(glossaire): introduce doctrine as ontology type' (#224) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (push) Successful in 43s
Deploy staging+live (annotations) / deploy (push) Successful in 8m36s
Reviewed-on: #224
2026-03-13 16:47:52 +01:00
1a762f8f54 feat(glossaire): introduce doctrine as ontology type
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 38s
2026-03-13 16:45:29 +01:00
fbdaf72775 Merge pull request 'refactor(glossaire): introduce dedicated GlossaryLayout wrapper' (#223) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 10s
CI / build-and-anchors (push) Successful in 41s
Deploy staging+live (annotations) / deploy (push) Successful in 8m12s
Reviewed-on: #223
2026-03-13 15:15:32 +01:00
67128a9ca1 refactor(glossaire): introduce dedicated GlossaryLayout wrapper
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-13 15:11:39 +01:00
898759db3d Merge pull request 'feat(glossaire): refactor core archicratic concepts' (#222) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 39s
Deploy staging+live (annotations) / deploy (push) Successful in 8m6s
Reviewed-on: #222
2026-03-13 14:36:40 +01:00
4f009a9557 feat(glossaire): refactor core archicratic concepts
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 37s
2026-03-13 14:32:41 +01:00
378d0981f0 Merge pull request 'refactor(glossaire): disable reading-only controls in glossary' (#221) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 20s
CI / build-and-anchors (push) Successful in 41s
Deploy staging+live (annotations) / deploy (push) Successful in 9m36s
Reviewed-on: #221
2026-03-12 19:27:16 +01:00
8f3702f803 refactor(glossaire): disable reading-only controls in glossary
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-12 19:23:50 +01:00
cfd303fc85 Merge pull request 'Nouveaux Ajouts - Glossaire' (#220) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 39s
Deploy staging+live (annotations) / deploy (push) Successful in 8m26s
Reviewed-on: #220
2026-03-12 17:45:03 +01:00
0fc0976f8a refactor(glossaire): polish contextual aside navigation
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 42s
2026-03-12 17:42:16 +01:00
e247ea8ead Merge pull request 'feat(glossaire): strengthen conceptual and paradigmatic cross-links' (#219) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 12s
CI / build-and-anchors (push) Successful in 43s
Deploy staging+live (annotations) / deploy (push) Successful in 7m49s
Reviewed-on: #219
2026-03-12 14:44:17 +01:00
0c57c4bc6d feat(glossaire): strengthen conceptual and paradigmatic cross-links
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 37s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-12 14:39:46 +01:00
9b7998e1c3 Merge pull request 'feat(glossaire): add external paradigms and theoretical landscape' (#218) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 42s
Deploy staging+live (annotations) / deploy (push) Successful in 7m44s
Reviewed-on: #218
2026-03-12 13:57:00 +01:00
8997a00413 feat(glossaire): add external paradigms and theoretical landscape
All checks were successful
SMOKE / smoke (push) Successful in 10s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-12 13:53:11 +01:00
a2e6f6185f Merge pull request 'fix(glossaire): adjust section anchor offset for sticky header' (#217) from feat/glossaire-paradigmes-externes into main
All checks were successful
SMOKE / smoke (push) Successful in 13s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 9m8s
Reviewed-on: #217
2026-03-12 13:35:39 +01:00
c2715b01d7 fix(glossaire): adjust section anchor offset for sticky header
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 39s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-12 13:31:07 +01:00
6f09dfcd12 Merge pull request 'feat(glossaire): extend taxonomy and align Astro 6 content config' (#216) from feat/glossaire-paradigmes into main
All checks were successful
SMOKE / smoke (push) Successful in 12s
CI / build-and-anchors (push) Successful in 42s
Deploy staging+live (annotations) / deploy (push) Successful in 8m58s
Reviewed-on: #216
2026-03-12 12:08:15 +01:00
bb9f55a3b5 feat(glossaire): extend taxonomy and align Astro 6 content config
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-12 12:04:46 +01:00
298ee7492c Merge pull request 'EditionToc Modif' (#215) from chore/astro6-upgrade into main
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 42s
Deploy staging+live (annotations) / deploy (push) Successful in 7m16s
Reviewed-on: #215
2026-03-11 17:58:17 +01:00
37cb836246 chore(astro): upgrade to Astro 6 and fix dynamic collection routes
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-11 17:56:53 +01:00
19e3318125 Merge pull request 'chore(astro): upgrade to astro 6 with legacy content compatibility' (#214) from chore/astro6-upgrade into main
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 47s
Deploy staging+live (annotations) / deploy (push) Successful in 9m33s
Reviewed-on: #214
2026-03-11 17:19:04 +01:00
683b02f4a0 chore(astro): upgrade to astro 6 with legacy content compatibility
All checks were successful
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 38s
2026-03-11 17:17:02 +01:00
20aecc30b1 Merge pull request 'feat(glossary): add core glossarial taxonomy and foundational entries' (#213) from feat/glossaire-core2 into main
All checks were successful
SMOKE / smoke (push) Successful in 18s
CI / build-and-anchors (push) Successful in 45s
Deploy staging+live (annotations) / deploy (push) Successful in 8m42s
Reviewed-on: #213
2026-03-11 13:04:24 +01:00
daf57aa152 feat(glossary): add core glossarial taxonomy and foundational entries
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 45s
2026-03-11 12:50:17 +01:00
bfd693de92 Merge pull request 'refactor(import): unify core editorial manifest' (#212) from refactor/unify-manifest-core into main
All checks were successful
SMOKE / smoke (push) Successful in 10s
CI / build-and-anchors (push) Successful in 40s
Deploy staging+live (annotations) / deploy (push) Successful in 8m0s
Reviewed-on: #212
2026-03-11 11:36:47 +01:00
ea2ad0017b refactor(import): unify core editorial manifest
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 35s
CI / build-and-anchors (pull_request) Successful in 39s
2026-03-11 11:33:37 +01:00
82e7473cac Merge pull request 'fix(content): declare commencer collection and remove implicit ia collection' (#211) from fix/content-collections-stability into main
All checks were successful
SMOKE / smoke (push) Successful in 17s
CI / build-and-anchors (push) Successful in 39s
Deploy staging+live (annotations) / deploy (push) Successful in 8m57s
Reviewed-on: #211
2026-03-11 11:09:39 +01:00
315523e80f refactor(site): depublish non-core sections and refresh anchors baseline
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 40s
2026-03-11 11:07:21 +01:00
569b6de154 fix(content): declare commencer collection and remove implicit ia collection
Some checks failed
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Failing after 41s
CI / build-and-anchors (pull_request) Failing after 46s
2026-03-11 10:54:27 +01:00
95f8159554 Merge pull request 'proposer: apply ticket #209' (#210) from bot/proposer-209-20260311-090431 into main
All checks were successful
SMOKE / smoke (push) Successful in 12s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 8m16s
Reviewed-on: #210
2026-03-11 10:06:49 +01:00
archicratie-bot
5698c494f1 edit: apply ticket #209 (/cas-ia/introduction/#p-16-615e3d61)
All checks were successful
CI / build-and-anchors (pull_request) Successful in 43s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 37s
2026-03-11 09:04:53 +00:00
e640e66b8d Merge pull request 'proposer: apply ticket #207' (#208) from bot/proposer-207-20260311-082736 into main
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 7m40s
Reviewed-on: #208
2026-03-11 09:29:21 +01:00
archicratie-bot
9be7d170c6 edit: apply ticket #207 (/cas-ia/introduction/#p-10-ceba29a2)
All checks were successful
CI / build-and-anchors (pull_request) Successful in 40s
SMOKE / smoke (push) Successful in 2s
CI / build-and-anchors (push) Successful in 41s
2026-03-11 08:27:58 +00:00
c2c98c516b Merge pull request 'refactor(editorial): recentrer le site sur le noyau archicratique' (#206) from refactor/recentrage-noyau-archicratique into main
All checks were successful
SMOKE / smoke (push) Successful in 9s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 8m54s
Reviewed-on: #206
2026-03-10 20:43:16 +01:00
32554f5998 refactor(editorial): recentrer le site sur le noyau archicratique
All checks were successful
CI / build-and-anchors (pull_request) Successful in 39s
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 41s
2026-03-10 20:36:33 +01:00
308f4f92bc Merge pull request 'chore: merge main into fix branch' (#205) from fix/annotations-index-fail-open-20260304-223909 into main
All checks were successful
Deploy staging+live (annotations) / deploy (push) Successful in 53s
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 35s
Reviewed-on: #205
2026-03-06 11:18:04 +01:00
4dfd3b026b chore: merge main into fix branch
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 38s
2026-03-06 11:14:48 +01:00
c93f274f41 Merge pull request 'fix(annotations): fail-open when src/annotations is missing' (#204) from fix/annotations-index-fail-open-20260304-223909 into main
All checks were successful
SMOKE / smoke (push) Successful in 14s
CI / build-and-anchors (push) Successful in 46s
Deploy staging+live (annotations) / deploy (push) Successful in 8m58s
Reviewed-on: #204
2026-03-04 22:42:13 +01:00
dfa311fb5b fix(annotations): fail-open when src/annotations is missing
All checks were successful
SMOKE / smoke (push) Successful in 18s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-04 22:39:09 +01:00
3ef1dc2801 Merge pull request 'fix(annotations): fail-open when src/annotations missing + keep dir tracked' (#203) from chore/remove-test-anno-media-20260304-204810 into main
All checks were successful
SMOKE / smoke (push) Successful in 13s
CI / build-and-anchors (push) Successful in 44s
Deploy staging+live (annotations) / deploy (push) Successful in 8m47s
Reviewed-on: #203
2026-03-04 21:39:04 +01:00
435e41ed4d fix(annotations): fail-open when src/annotations missing + keep dir tracked
All checks were successful
SMOKE / smoke (push) Successful in 7s
CI / build-and-anchors (push) Successful in 43s
CI / build-and-anchors (pull_request) Successful in 41s
2026-03-04 21:31:53 +01:00
8825932159 Merge pull request 'chore: keep public/media directory (gitkeep)' (#202) from chore/remove-test-anno-media-20260304-204810 into main
All checks were successful
SMOKE / smoke (push) Successful in 10s
Deploy staging+live (annotations) / deploy (push) Successful in 34s
CI / build-and-anchors (push) Successful in 45s
Reviewed-on: #202
2026-03-04 21:06:45 +01:00
b55decbea4 chore: keep public/media directory (gitkeep)
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 46s
CI / build-and-anchors (pull_request) Successful in 38s
2026-03-04 21:03:55 +01:00
414a848db3 Merge pull request 'chore: keep src/annotations directory (gitkeep)' (#201) from chore/remove-test-anno-media-20260304-204810 into main
All checks were successful
SMOKE / smoke (push) Successful in 5s
Deploy staging+live (annotations) / deploy (push) Successful in 39s
CI / build-and-anchors (push) Successful in 44s
Reviewed-on: #201
2026-03-04 21:03:39 +01:00
cbd4f3a57f chore: keep src/annotations directory (gitkeep)
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 36s
CI / build-and-anchors (pull_request) Successful in 36s
2026-03-04 21:02:19 +01:00
49f8d6a95e Merge pull request 'chore: remove test annotations and media' (#200) from chore/remove-test-anno-media-20260304-204810 into main
Some checks failed
CI / build-and-anchors (push) Failing after 34s
Deploy staging+live (annotations) / deploy (push) Successful in 47s
SMOKE / smoke (push) Successful in 20s
Reviewed-on: #200
2026-03-04 20:59:21 +01:00
5afa5cbfda chore: remove test annotations and media
Some checks failed
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Failing after 32s
CI / build-and-anchors (pull_request) Failing after 34s
2026-03-04 20:48:26 +01:00
a1b1df38ba Merge pull request 'chore: remove test annotations and media' (#196) from chore/remove-test-anno-media-20260304-202811 into main
Some checks failed
Deploy staging+live (annotations) / deploy (push) Failing after 54s
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Failing after 33s
CI / build-and-anchors (pull_request) Failing after 30s
Reviewed-on: #196
2026-03-04 20:35:47 +01:00
d3f7d74da7 Merge pull request 'chore: remove test annotations and media' (#197) from chore/remove-test-anno-media-20260304-202451 into main
Some checks are pending
Deploy staging+live (annotations) / deploy (push) Has started running
CI / build-and-anchors (push) Has started running
SMOKE / smoke (push) Successful in 8s
Reviewed-on: #197
2026-03-04 20:35:40 +01:00
6919190107 chore: remove test annotations and media
Some checks failed
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Failing after 36s
CI / build-and-anchors (pull_request) Failing after 36s
2026-03-04 20:28:29 +01:00
021ef5abd7 chore: remove test annotations and media
Some checks failed
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Failing after 31s
CI / build-and-anchors (pull_request) Failing after 38s
2026-03-04 20:25:03 +01:00
76cdc85f9c Merge pull request 'ci(deploy): treat src/public/package changes as FULL rebuild' (#193) from chore/supprimer-annotations-20260304-191252 into main
Some checks failed
SMOKE / smoke (push) Successful in 16s
CI / build-and-anchors (push) Failing after 35s
Deploy staging+live (annotations) / deploy (push) Successful in 48s
Reviewed-on: #193
2026-03-04 19:22:36 +01:00
f2f6df2127 ci(deploy): treat src/public/package changes as FULL rebuild
Some checks failed
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Failing after 36s
CI / build-and-anchors (pull_request) Failing after 37s
2026-03-04 19:13:25 +01:00
dfe13757f7 Merge pull request 'ci(deploy): treat src/public/package changes as FULL rebuild' (#191) from chore/fix-deploy-gate-full-src-public-20260304-181357 into main
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 37s
Deploy staging+live (annotations) / deploy (push) Successful in 34s
Reviewed-on: #191
2026-03-04 18:16:30 +01:00
244 changed files with 63824 additions and 12035 deletions

View File

@@ -41,7 +41,7 @@ jobs:
run: |
set -euo pipefail
export EVENT_JSON="/var/run/act/workflow/event.json"
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
node --input-type=module - <<'NODE' > /tmp/anno.env
import fs from "node:fs";
@@ -66,7 +66,10 @@ jobs:
if (!owner || !repo) {
const m = cloneUrl.match(/[:/](?<o>[^/]+)\/(?<r>[^/]+?)(?:\.git)?$/);
if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; }
if (m?.groups) {
owner = owner || m.groups.o;
repo = repo || m.groups.r;
}
}
if (!owner || !repo) throw new Error("Cannot infer owner/repo");
@@ -81,7 +84,6 @@ jobs:
throw new Error("No issue number in event.json or workflow_dispatch input");
}
// label name: best-effort (non-bloquant)
let labelName = "workflow_dispatch";
const lab = ev?.label;
if (typeof lab === "string") labelName = lab;
@@ -95,7 +97,7 @@ jobs:
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
: origin;
function sh(s){ return JSON.stringify(String(s)); }
function sh(s) { return JSON.stringify(String(s)); }
process.stdout.write([
`CLONE_URL=${sh(cloneUrl)}`,
@@ -108,7 +110,7 @@ jobs:
].join("\n") + "\n");
NODE
echo "context:"
echo "context:"
sed -n '1,120p' /tmp/anno.env
- name: Early gate (label event fast-skip, but tolerant)
@@ -116,18 +118,16 @@ jobs:
set -euo pipefail
source /tmp/anno.env
echo " event label = $LABEL_NAME"
echo "event label = $LABEL_NAME"
# Fast skip on obvious non-approved label events (avoid noise),
# BUT do NOT skip if label payload is weird/unknown.
if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" && "$LABEL_NAME" != "" && "$LABEL_NAME" != "[object Object]" ]]; then
echo " label=$LABEL_NAME => skip early"
echo "label=$LABEL_NAME => skip early"
echo "SKIP=1" >> /tmp/anno.env
echo "SKIP_REASON=\"label_not_approved_event\"" >> /tmp/anno.env
exit 0
fi
echo "continue to API gating (issue=$ISSUE_NUMBER)"
echo "continue to API gating (issue=$ISSUE_NUMBER)"
- name: Fetch issue + hard gate on labels + Type
env:
@@ -135,9 +135,9 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; }
test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; }
curl -fsS \
-H "Authorization: token $FORGE_TOKEN" \
@@ -148,11 +148,12 @@ jobs:
node --input-type=module - <<'NODE' >> /tmp/anno.env
import fs from "node:fs";
const issue = JSON.parse(fs.readFileSync("/tmp/issue.json","utf8"));
const title = String(issue.title || "");
const issue = JSON.parse(fs.readFileSync("/tmp/issue.json", "utf8"));
const body = String(issue.body || "").replace(/\r\n/g, "\n");
const labels = Array.isArray(issue.labels) ? issue.labels.map(l => String(l.name || "")).filter(Boolean) : [];
const labels = Array.isArray(issue.labels)
? issue.labels.map(l => String(l.name || "")).filter(Boolean)
: [];
const hasApproved = labels.includes("state/approved");
function pickLine(key) {
@@ -164,14 +165,12 @@ jobs:
const typeRaw = pickLine("Type");
const type = String(typeRaw || "").trim().toLowerCase();
const allowed = new Set(["type/media","type/reference","type/comment"]);
const proposer = new Set(["type/correction","type/fact-check"]);
const allowedAnno = new Set(["type/media", "type/reference", "type/comment"]);
const proposerTypes = new Set(["type/correction", "type/fact-check"]);
const out = [];
out.push(`ISSUE_TITLE=${JSON.stringify(title)}`);
out.push(`ISSUE_TYPE=${JSON.stringify(type)}`);
// HARD gate: must currently have state/approved (avoids depending on event payload)
if (!hasApproved) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("not_approved_label_present")}`);
@@ -182,23 +181,23 @@ jobs:
if (!type) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("missing_type")}`);
} else if (allowed.has(type)) {
} else if (allowedAnno.has(type)) {
// proceed
} else if (proposer.has(type)) {
} else if (proposerTypes.has(type)) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("proposer_type:"+type)}`);
out.push(`SKIP_REASON=${JSON.stringify("proposer_type:" + type)}`);
} else {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:"+type)}`);
out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:" + type)}`);
}
process.stdout.write(out.join("\n") + "\n");
NODE
echo "gating result:"
echo "gating result:"
grep -E '^(ISSUE_TYPE|SKIP|SKIP_REASON)=' /tmp/anno.env || true
- name: Comment issue if skipped (Proposer / unsupported / missing Type)
- name: Comment issue if skipped (unsupported / missing Type only)
if: ${{ always() }}
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
@@ -208,9 +207,13 @@ jobs:
[[ "${SKIP:-0}" == "1" ]] || exit 0
# IMPORTANT: do NOT comment for "not_approved_label_present" (avoid spam on other label events)
if [[ "${SKIP_REASON:-}" == "not_approved_label_present" || "${SKIP_REASON:-}" == "label_not_approved_event" ]]; then
echo " skip reason=${SKIP_REASON} -> no comment"
echo "skip reason=${SKIP_REASON} -> no comment"
exit 0
fi
if [[ "${SKIP_REASON:-}" == proposer_type:* ]]; then
echo "proposer ticket detected -> anno stays silent"
exit 0
fi
@@ -219,15 +222,13 @@ jobs:
REASON="${SKIP_REASON:-}"
TYPE="${ISSUE_TYPE:-}"
if [[ "$REASON" == proposer_type:* ]]; then
MSG=" Ticket #${ISSUE_NUMBER} détecté comme **Proposer** (${TYPE}).\n\n- Ce type est **traité manuellement par les editors**.\n✅ Aucun traitement automatique."
elif [[ "$REASON" == unsupported_type:* ]]; then
MSG=" Ticket #${ISSUE_NUMBER} ignoré : Type non supporté par le bot (${TYPE}).\n\nTypes supportés : type/media, type/reference, type/comment."
if [[ "$REASON" == unsupported_type:* ]]; then
MSG="Ticket #${ISSUE_NUMBER} ignored: unsupported Type (${TYPE}). Supported types: type/media, type/reference, type/comment."
else
MSG=" Ticket #${ISSUE_NUMBER} ignoré : champ 'Type:' manquant ou illisible.\n\nAjoute : Type: type/media|type/reference|type/comment"
MSG="Ticket #${ISSUE_NUMBER} ignored: missing or unreadable 'Type:'. Expected: type/media|type/reference|type/comment"
fi
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")"
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
@@ -239,7 +240,7 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
rm -rf .git
git init -q
@@ -252,16 +253,16 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
npm ci --no-audit --no-fund
- name: Check apply script exists
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
test -f scripts/apply-annotation-ticket.mjs || {
echo "missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH"
echo "missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH"
ls -la scripts | sed -n '1,200p' || true
exit 1
}
@@ -270,16 +271,16 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
npm run build
test -f dist/para-index.json || {
echo "missing dist/para-index.json after build"
echo "missing dist/para-index.json after build"
ls -la dist | sed -n '1,200p' || true
exit 1
}
echo "dist/para-index.json present"
echo "dist/para-index.json present"
- name: Apply ticket on bot branch (strict+verify, commit)
continue-on-error: true
@@ -290,10 +291,10 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
test -d .git || { echo "not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
test -d .git || { echo "not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; }
test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; }
test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; }
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
@@ -340,11 +341,11 @@ jobs:
run: |
set -euo pipefail
source /tmp/anno.env || true
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
RC="${APPLY_RC:-0}"
if [[ "$RC" == "0" ]]; then
echo " no failure detected"
echo "no failure detected"
exit 0
fi
@@ -356,8 +357,8 @@ jobs:
BODY="(no apply log found)"
fi
MSG="apply-annotation-ticket a échoué (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
MSG="apply-annotation-ticket failed (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")"
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
@@ -374,9 +375,9 @@ jobs:
source /tmp/anno.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || { echo " apply failed -> skip push"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo " no-op -> skip push"; exit 0; }
test -d .git || { echo " no git repo -> skip push"; exit 0; }
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "apply failed -> skip push"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo "no-op -> skip push"; exit 0; }
test -d .git || { echo "no git repo -> skip push"; exit 0; }
AUTH_URL="$(node --input-type=module -e '
const [clone, tok] = process.argv.slice(1);
@@ -398,8 +399,8 @@ jobs:
source /tmp/anno.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || { echo " apply failed -> skip PR"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo " no-op -> skip PR"; exit 0; }
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "apply failed -> skip PR"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo "no-op -> skip PR"; exit 0; }
PR_TITLE="anno: apply ticket #${ISSUE_NUMBER}"
PR_BODY="PR auto depuis ticket #${ISSUE_NUMBER} (state/approved).\n\n- Branche: ${BRANCH}\n- Commit: ${END_SHA}\n\nMerge si CI OK."
@@ -420,10 +421,10 @@ jobs:
console.log(pr.html_url || pr.url || "");
' "$PR_JSON")"
test -n "$PR_URL" || { echo "PR URL missing. Raw: $PR_JSON"; exit 1; }
test -n "$PR_URL" || { echo "PR URL missing. Raw: $PR_JSON"; exit 1; }
MSG="PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}"
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
MSG="PR created for ticket #${ISSUE_NUMBER}: ${PR_URL}"
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")"
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
@@ -431,7 +432,7 @@ jobs:
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
--data-binary "$C_PAYLOAD"
echo "PR: $PR_URL"
echo "PR: $PR_URL"
- name: Finalize (fail job if apply failed)
if: ${{ always() }}
@@ -439,11 +440,11 @@ jobs:
set -euo pipefail
source /tmp/anno.env || true
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
RC="${APPLY_RC:-0}"
if [[ "$RC" != "0" ]]; then
echo "apply failed (rc=$RC)"
echo "apply failed (rc=$RC)"
exit "$RC"
fi
echo "apply ok"
echo "apply ok"

View File

@@ -297,6 +297,19 @@ jobs:
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
BUILD_TIME_RAW="$(TZ=Europe/Paris date '+%Y-%m-%dT%H:%M:%S%z')"
BUILD_TIME="${BUILD_TIME_RAW:0:${#BUILD_TIME_RAW}-2}:${BUILD_TIME_RAW:${#BUILD_TIME_RAW}-2}"
PUBLIC_OPS_ENV=staging \
PUBLIC_OPS_UPSTREAM=web_blue \
PUBLIC_BUILD_SHA="${AFTER}" \
PUBLIC_BUILD_TIME="${BUILD_TIME}" \
node scripts/write-ops-health.mjs
test -f public/__ops/health.json
echo "=== public/__ops/health.json (blue/staging) ==="
cat public/__ops/health.json
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
docker rm -f archicratie-web-blue || true
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
@@ -306,6 +319,11 @@ jobs:
wait_url "http://127.0.0.1:8081/annotations-index.json" "blue annotations-index"
wait_url "http://127.0.0.1:8081/pagefind/pagefind.js" "blue pagefind.js"
wait_url "http://127.0.0.1:8081/__ops/health.json" "blue ops health"
curl -fsS --max-time 6 "http://127.0.0.1:8081/__ops/health.json" \
| python3 -c 'import sys, json; j=json.load(sys.stdin); print("env=", j.get("env")); print("upstream=", j.get("upstream")); print("buildSha=", j.get("buildSha")); print("builtAt=", j.get("builtAt"))'
CANON="$(curl -fsS --max-time 6 "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
echo "canonical(blue)=$CANON"
echo "$CANON" | grep -q 'https://staging\.archicratie\.trans-hands\.synology\.me/' || {
@@ -353,6 +371,19 @@ jobs:
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green || true
}
BUILD_TIME_RAW="$(TZ=Europe/Paris date '+%Y-%m-%dT%H:%M:%S%z')"
BUILD_TIME="${BUILD_TIME_RAW:0:${#BUILD_TIME_RAW}-2}:${BUILD_TIME_RAW:${#BUILD_TIME_RAW}-2}"
PUBLIC_OPS_ENV=prod \
PUBLIC_OPS_UPSTREAM=web_green \
PUBLIC_BUILD_SHA="${AFTER}" \
PUBLIC_BUILD_TIME="${BUILD_TIME}" \
node scripts/write-ops-health.mjs
test -f public/__ops/health.json
echo "=== public/__ops/health.json (green/prod) ==="
cat public/__ops/health.json
# build/restart green
if ! docker compose -p "$PROJ" -f docker-compose.yml build web_green; then
echo "❌ build green failed"; rollback; exit 4
@@ -366,6 +397,11 @@ jobs:
if ! wait_url "http://127.0.0.1:8082/annotations-index.json" "green annotations-index"; then rollback; exit 4; fi
if ! wait_url "http://127.0.0.1:8082/pagefind/pagefind.js" "green pagefind.js"; then rollback; exit 4; fi
if ! wait_url "http://127.0.0.1:8082/__ops/health.json" "green ops health"; then rollback; exit 4; fi
curl -fsS --max-time 6 "http://127.0.0.1:8082/__ops/health.json" \
| python3 -c 'import sys, json; j=json.load(sys.stdin); print("env=", j.get("env")); print("upstream=", j.get("upstream")); print("buildSha=", j.get("buildSha")); print("builtAt=", j.get("builtAt"))'
CANON="$(curl -fsS --max-time 6 "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
echo "canonical(green)=$CANON"
echo "$CANON" | grep -q 'https://archicratie\.trans-hands\.synology\.me/' || {

View File

@@ -1,13 +1,16 @@
name: Proposer Apply (PR)
name: Proposer Apply (Queue)
on:
issues:
types: [labeled]
push:
branches: [main]
workflow_dispatch:
inputs:
issue:
description: "Issue number to apply (Proposer: correction/fact-check)"
required: true
description: "Issue number to prioritize (optional)"
required: false
default: ""
env:
NODE_OPTIONS: --dns-result-order=ipv4first
@@ -17,8 +20,8 @@ defaults:
shell: bash
concurrency:
group: proposer-apply-${{ github.event.issue.number || inputs.issue || 'manual' }}
cancel-in-progress: true
group: proposer-queue-main
cancel-in-progress: false
jobs:
apply-proposer:
@@ -34,14 +37,15 @@ jobs:
node --version
npm --version
- name: Derive context (event.json / workflow_dispatch)
- name: Derive context (event.json / workflow_dispatch / push)
env:
INPUT_ISSUE: ${{ inputs.issue }}
EVENT_NAME_IN: ${{ github.event_name }}
FORGE_API: ${{ vars.FORGE_API || vars.FORGE_BASE }}
run: |
set -euo pipefail
export EVENT_JSON="/var/run/act/workflow/event.json"
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
node --input-type=module - <<'NODE' > /tmp/proposer.env
import fs from "node:fs";
@@ -51,7 +55,7 @@ jobs:
const cloneUrl =
repoObj?.clone_url ||
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : "");
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/, "") + ".git") : "");
if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json");
@@ -66,8 +70,12 @@ jobs:
if (!owner || !repo) {
const m = cloneUrl.match(/[:/](?<o>[^/]+)\/(?<r>[^/]+?)(?:\.git)?$/);
if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; }
if (m?.groups) {
owner = owner || m.groups.o;
repo = repo || m.groups.r;
}
}
if (!owner || !repo) throw new Error("Cannot infer owner/repo");
const defaultBranch = repoObj?.default_branch || "main";
@@ -75,25 +83,30 @@ jobs:
const issueNumber =
ev?.issue?.number ||
ev?.issue?.index ||
(process.env.INPUT_ISSUE ? Number(process.env.INPUT_ISSUE) : 0);
if (!issueNumber || !Number.isFinite(Number(issueNumber))) {
throw new Error("No issue number in event.json or workflow_dispatch input");
}
(process.env.INPUT_ISSUE ? Number(process.env.INPUT_ISSUE) : 0) ||
0;
const labelName =
ev?.label?.name ||
ev?.label ||
"workflow_dispatch";
(typeof ev?.label === "string" ? ev.label : "") ||
"";
const eventName =
String(process.env.EVENT_NAME_IN || "").trim() ||
(ev?.issue ? "issues" : (ev?.before || ev?.after ? "push" : "workflow_dispatch"));
const u = new URL(cloneUrl);
const origin = u.origin;
const apiBase = (process.env.FORGE_API && String(process.env.FORGE_API).trim())
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
: origin;
const apiBase =
(process.env.FORGE_API && String(process.env.FORGE_API).trim())
? String(process.env.FORGE_API).trim().replace(/\/+$/, "")
: origin;
function sh(s) {
return JSON.stringify(String(s));
}
function sh(s){ return JSON.stringify(String(s)); }
process.stdout.write([
`CLONE_URL=${sh(cloneUrl)}`,
`OWNER=${sh(owner)}`,
@@ -101,82 +114,230 @@ jobs:
`DEFAULT_BRANCH=${sh(defaultBranch)}`,
`ISSUE_NUMBER=${sh(issueNumber)}`,
`LABEL_NAME=${sh(labelName)}`,
`EVENT_NAME=${sh(eventName)}`,
`API_BASE=${sh(apiBase)}`
].join("\n") + "\n");
NODE
echo "✅ context:"
sed -n '1,120p' /tmp/proposer.env
echo "Context:"
sed -n '1,200p' /tmp/proposer.env
- name: Gate on label state/approved
- name: Early gate (tolerant on empty issue label payload)
run: |
set -euo pipefail
source /tmp/proposer.env
if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" ]]; then
echo " label=$LABEL_NAME => skip"
echo "SKIP=1" >> /tmp/proposer.env
exit 0
fi
echo "✅ proceed (issue=$ISSUE_NUMBER)"
echo "event=$EVENT_NAME label=${LABEL_NAME:-<empty>}"
- name: Fetch issue + API-hard gate on (state/approved present + proposer type)
if [[ "$EVENT_NAME" == "issues" ]]; then
if [[ -n "${LABEL_NAME:-}" && "$LABEL_NAME" != "state/approved" ]]; then
echo "issues/labeled with explicit non-approved label=$LABEL_NAME -> skip"
echo 'SKIP=1' >> /tmp/proposer.env
echo 'SKIP_REASON="label_not_state_approved_event"' >> /tmp/proposer.env
exit 0
fi
fi
echo "Proceed to API-based selection/gating"
- name: Checkout default branch
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
rm -rf .git
git init -q
git remote add origin "$CLONE_URL"
git fetch --depth 1 origin "$DEFAULT_BRANCH"
git -c advice.detachedHead=false checkout -q FETCH_HEAD
git log -1 --oneline
- name: Detect app dir (repo-root vs ./site)
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
APP_DIR="."
if [[ -d "site" && -f "site/package.json" ]]; then
APP_DIR="site"
fi
echo "APP_DIR=$APP_DIR" >> /tmp/proposer.env
echo "APP_DIR=$APP_DIR"
test -f "$APP_DIR/package.json" || {
echo "package.json missing in APP_DIR=$APP_DIR"
exit 1
}
test -d "$APP_DIR/scripts" || {
echo "scripts/ missing in APP_DIR=$APP_DIR"
exit 1
}
- name: Select next proposer batch (by path)
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; }
test -n "${FORGE_TOKEN:-}" || {
echo "Missing secret FORGE_TOKEN"
exit 1
}
export GITEA_OWNER="$OWNER"
export GITEA_REPO="$REPO"
export FORGE_API="$API_BASE"
cd "$APP_DIR"
test -f scripts/pick-proposer-issue.mjs || {
echo "missing scripts/pick-proposer-issue.mjs in APP_DIR=$APP_DIR"
ls -la scripts | sed -n '1,200p' || true
exit 1
}
node scripts/pick-proposer-issue.mjs "${ISSUE_NUMBER:-0}" > /tmp/proposer.pick.env
cat /tmp/proposer.pick.env >> /tmp/proposer.env
source /tmp/proposer.pick.env
if [[ "${TARGET_FOUND:-0}" != "1" ]]; then
echo 'SKIP=1' >> /tmp/proposer.env
echo "SKIP_REASON=${TARGET_REASON:-no_target}" >> /tmp/proposer.env
echo "No target batch"
exit 0
fi
echo "Target batch:"
grep -E '^(TARGET_PRIMARY_ISSUE|TARGET_ISSUES|TARGET_COUNT|TARGET_CHEMIN)=' /tmp/proposer.env
- name: Derive deterministic batch identity
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
export TARGET_ISSUES TARGET_CHEMIN
node --input-type=module - <<'NODE'
import fs from "node:fs";
import crypto from "node:crypto";
const issues = String(process.env.TARGET_ISSUES || "")
.trim()
.split(/\s+/)
.filter(Boolean)
.sort((a, b) => Number(a) - Number(b));
const chemin = String(process.env.TARGET_CHEMIN || "").trim();
const keySource = `${chemin}::${issues.join(",")}`;
const hash = crypto.createHash("sha1").update(keySource).digest("hex").slice(0, 12);
const primary = issues[0] || "0";
const batchBranch = `bot/proposer-${primary}-${hash}`;
fs.appendFileSync(
"/tmp/proposer.env",
[
`BATCH_KEY=${JSON.stringify(keySource)}`,
`BATCH_HASH=${JSON.stringify(hash)}`,
`BATCH_BRANCH=${JSON.stringify(batchBranch)}`
].join("\n") + "\n"
);
NODE
echo "Batch identity:"
grep -E '^(BATCH_KEY|BATCH_HASH|BATCH_BRANCH)=' /tmp/proposer.env
- name: Inspect open proposer PRs
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
curl -fsS \
-H "Authorization: token $FORGE_TOKEN" \
-H "Accept: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER" \
-o /tmp/issue.json
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls?state=open&limit=100" \
-o /tmp/open_pulls.json
export TARGET_ISSUES="${TARGET_ISSUES:-}"
export BATCH_BRANCH="${BATCH_BRANCH:-}"
export BATCH_KEY="${BATCH_KEY:-}"
node --input-type=module - <<'NODE' >> /tmp/proposer.env
import fs from "node:fs";
const issue = JSON.parse(fs.readFileSync("/tmp/issue.json","utf8"));
const title = String(issue.title || "");
const body = String(issue.body || "").replace(/\r\n/g, "\n");
const labels = Array.isArray(issue.labels) ? issue.labels.map(l => String(l.name||"")).filter(Boolean) : [];
function pickLine(key) {
const re = new RegExp(`^\\s*${key}\\s*:\\s*([^\\n\\r]+)`, "mi");
const m = body.match(re);
return m ? m[1].trim() : "";
}
const pulls = JSON.parse(fs.readFileSync("/tmp/open_pulls.json", "utf8"));
const issues = String(process.env.TARGET_ISSUES || "")
.trim()
.split(/\s+/)
.filter(Boolean);
const typeRaw = pickLine("Type");
const type = String(typeRaw || "").trim().toLowerCase();
const batchBranch = String(process.env.BATCH_BRANCH || "");
const batchKey = String(process.env.BATCH_KEY || "");
const hasApproved = labels.includes("state/approved");
const proposer = new Set(["type/correction","type/fact-check"]);
const proposerOpen = Array.isArray(pulls)
? pulls.filter((pr) => String(pr?.head?.ref || "").startsWith("bot/proposer-"))
: [];
const sameBatch = proposerOpen.find((pr) => {
const ref = String(pr?.head?.ref || "");
const title = String(pr?.title || "");
const body = String(pr?.body || "");
if (batchBranch && ref === batchBranch) return true;
if (batchKey && body.includes(`Batch-Key: ${batchKey}`)) return true;
return issues.some((n) =>
ref.startsWith(`bot/proposer-${n}-`) ||
title.includes(`#${n}`) ||
body.includes(`#${n}`) ||
body.includes(`ticket #${n}`)
);
});
const out = [];
out.push(`ISSUE_TITLE=${JSON.stringify(title)}`);
out.push(`ISSUE_TYPE=${JSON.stringify(type)}`);
out.push(`HAS_APPROVED=${hasApproved ? "1":"0"}`);
if (!hasApproved) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("approved_not_present")}`);
} else if (!type) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("missing_type")}`);
} else if (!proposer.has(type)) {
out.push(`SKIP=1`);
out.push(`SKIP_REASON=${JSON.stringify("not_proposer:"+type)}`);
if (sameBatch) {
out.push("SKIP=1");
out.push(`SKIP_REASON=${JSON.stringify("issue_already_has_open_pr")}`);
out.push(`OPEN_PR_URL=${JSON.stringify(String(sameBatch.html_url || sameBatch.url || ""))}`);
out.push(`OPEN_PR_BRANCH=${JSON.stringify(String(sameBatch?.head?.ref || ""))}`);
} else if (proposerOpen.length > 0) {
const first = proposerOpen[0];
out.push("SKIP=1");
out.push(`SKIP_REASON=${JSON.stringify("queue_busy_open_proposer_pr")}`);
out.push(`OPEN_PR_URL=${JSON.stringify(String(first.html_url || first.url || ""))}`);
out.push(`OPEN_PR_BRANCH=${JSON.stringify(String(first?.head?.ref || ""))}`);
}
process.stdout.write(out.join("\n") + "\n");
process.stdout.write(out.join("\n") + (out.length ? "\n" : ""));
NODE
echo "✅ proposer gating:"
grep -E '^(ISSUE_TYPE|HAS_APPROVED|SKIP|SKIP_REASON)=' /tmp/proposer.env || true
- name: Guard on remote batch branch before heavy work
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
- name: Comment issue if skipped
if git ls-remote --exit-code --heads origin "$BATCH_BRANCH" >/dev/null 2>&1; then
echo 'SKIP=1' >> /tmp/proposer.env
echo 'SKIP_REASON="batch_branch_exists_without_pr"' >> /tmp/proposer.env
echo "OPEN_PR_BRANCH=${BATCH_BRANCH}" >> /tmp/proposer.env
echo "Remote batch branch already exists -> skip duplicate materialization"
exit 0
fi
echo "Remote batch branch is free"
- name: Comment issue if queued / skipped
if: ${{ always() }}
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
@@ -185,122 +346,149 @@ jobs:
source /tmp/proposer.env || true
[[ "${SKIP:-0}" == "1" ]] || exit 0
[[ "$LABEL_NAME" == "state/approved" || "$LABEL_NAME" == "workflow_dispatch" ]] || exit 0
[[ "${EVENT_NAME:-}" != "push" ]] || exit 0
REASON="${SKIP_REASON:-}"
TYPE="${ISSUE_TYPE:-}"
if [[ "$REASON" == "approved_not_present" ]]; then
MSG=" Proposer Apply: skip — le label **state/approved** n'est pas présent sur le ticket au moment du run (gate API-hard)."
elif [[ "$REASON" == "missing_type" ]]; then
MSG=" Proposer Apply: skip — champ **Type:** manquant/illisible. Attendu: type/correction ou type/fact-check."
else
MSG=" Proposer Apply: skip — Type non-Proposer (${TYPE}). (Ce workflow ne traite que correction/fact-check.)"
if [[ "${SKIP_REASON:-}" == "label_not_state_approved_event" || "${SKIP_REASON:-}" == "label_not_state_approved" ]]; then
echo "Skip reason=${SKIP_REASON} -> no comment"
exit 0
fi
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
test -n "${FORGE_TOKEN:-}" || exit 0
ISSUE_TO_COMMENT="${ISSUE_NUMBER:-0}"
if [[ "$ISSUE_TO_COMMENT" == "0" || -z "$ISSUE_TO_COMMENT" ]]; then
ISSUE_TO_COMMENT="${TARGET_PRIMARY_ISSUE:-0}"
fi
[[ "$ISSUE_TO_COMMENT" != "0" ]] || exit 0
case "${SKIP_REASON:-}" in
queue_busy_open_proposer_pr)
MSG="Ticket queued in proposer queue. An open proposer PR already exists: ${OPEN_PR_URL:-"(URL unavailable)"}. The workflow will resume after merge on main."
;;
issue_already_has_open_pr)
MSG="This batch already has an open proposer PR: ${OPEN_PR_URL:-"(URL unavailable)"}"
;;
batch_branch_exists_without_pr)
MSG="This batch already has a remote batch branch (${OPEN_PR_BRANCH:-"(unknown branch)"}). Manual inspection is required before any new proposer PR is created."
;;
batch_branch_already_materialized)
MSG="This batch was already materialized by another run on branch ${OPEN_PR_BRANCH:-"(unknown branch)"}. No duplicate PR was created."
;;
explicit_issue_missing_chemin)
MSG="Proposer Apply: cannot process this ticket automatically because field Chemin is missing or unreadable."
;;
explicit_issue_missing_type)
MSG="Proposer Apply: cannot process this ticket automatically because field Type is missing or unreadable."
;;
explicit_issue_not_approved)
MSG="Proposer Apply: this ticket is not currently labeled state/approved."
;;
explicit_issue_rejected)
MSG="Proposer Apply: this ticket has state/rejected and is not eligible for the proposer queue."
;;
no_open_approved_proposer_issue)
MSG="No approved proposer ticket is currently waiting."
;;
*)
MSG="Proposer Apply: skip - ${SKIP_REASON:-unspecified reason}."
;;
esac
export MSG
node --input-type=module - <<'NODE' > /tmp/proposer.skip.comment.json
const msg = process.env.MSG || "";
process.stdout.write(JSON.stringify({ body: msg }));
NODE
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
--data-binary "$PAYLOAD" || true
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_TO_COMMENT/comments" \
--data-binary @/tmp/proposer.skip.comment.json || true
- name: Checkout default branch
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
rm -rf .git
git init -q
git remote add origin "$CLONE_URL"
git fetch --depth 1 origin "$DEFAULT_BRANCH"
git -c advice.detachedHead=false checkout -q FETCH_HEAD
git log -1 --oneline
echo "✅ workspace:"
ls -la | sed -n '1,120p'
- name: Detect app dir (repo-root vs ./site)
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
APP_DIR="."
if [[ -d "site" && -f "site/package.json" ]]; then
APP_DIR="site"
fi
echo "APP_DIR=$APP_DIR" >> /tmp/proposer.env
echo "✅ APP_DIR=$APP_DIR"
ls -la "$APP_DIR" | sed -n '1,120p'
test -f "$APP_DIR/package.json" || { echo "❌ package.json missing in APP_DIR=$APP_DIR"; exit 1; }
test -d "$APP_DIR/scripts" || { echo "❌ scripts/ missing in APP_DIR=$APP_DIR"; exit 1; }
- name: NPM harden (reduce flakiness)
- name: NPM harden
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || exit 0
cd "$APP_DIR"
npm config set fetch-retries 5
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm config set registry https://registry.npmjs.org
- name: Install deps (APP_DIR)
- name: Install deps
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || exit 0
cd "$APP_DIR"
npm ci --no-audit --no-fund
- name: Build dist baseline (APP_DIR)
- name: Build dist baseline
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || exit 0
cd "$APP_DIR"
npm run build
- name: Apply ticket (alias + commit) on bot branch
- name: Apply proposer batch on bot branch
continue-on-error: true
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
BOT_GIT_NAME: ${{ secrets.BOT_GIT_NAME }}
BOT_GIT_EMAIL: ${{ secrets.BOT_GIT_EMAIL }}
FORGE_API: ${{ vars.FORGE_API || vars.FORGE_BASE }}
run: |
set -euo pipefail
source /tmp/proposer.env
[[ "${SKIP:-0}" != "1" ]] || { echo " skipped"; exit 0; }
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
START_SHA="$(git rev-parse HEAD)"
TS="$(date -u +%Y%m%d-%H%M%S)"
BR="bot/proposer-${ISSUE_NUMBER}-${TS}"
BR="$BATCH_BRANCH"
echo "BRANCH=$BR" >> /tmp/proposer.env
git checkout -b "$BR"
export GITEA_OWNER="$OWNER"
export GITEA_REPO="$REPO"
export FORGE_BASE="$API_BASE"
export FORGE_API="$API_BASE"
LOG="/tmp/proposer-apply.log"
set +e
(cd "$APP_DIR" && node scripts/apply-ticket.mjs "$ISSUE_NUMBER" --alias --commit) >"$LOG" 2>&1
RC=$?
set -e
: > "$LOG"
RC=0
FAILED_ISSUE=""
for ISSUE in $TARGET_ISSUES; do
echo "" >> "$LOG"
echo "== ticket #$ISSUE ==" >> "$LOG"
set +e
(cd "$APP_DIR" && node scripts/apply-ticket.mjs "$ISSUE" --alias --commit) >> "$LOG" 2>&1
STEP_RC=$?
set -e
if [[ "$STEP_RC" -ne 0 ]]; then
RC="$STEP_RC"
FAILED_ISSUE="$ISSUE"
break
fi
done
echo "APPLY_RC=$RC" >> /tmp/proposer.env
echo "FAILED_ISSUE=${FAILED_ISSUE}" >> /tmp/proposer.env
echo "== apply log (tail) =="
tail -n 200 "$LOG" || true
echo "Apply log (tail):"
tail -n 220 "$LOG" || true
END_SHA="$(git rev-parse HEAD)"
if [[ "$RC" -ne 0 ]]; then
echo "NOOP=0" >> /tmp/proposer.env
exit 0
@@ -313,7 +501,34 @@ jobs:
echo "END_SHA=$END_SHA" >> /tmp/proposer.env
fi
- name: Push bot branch
- name: Rebase bot branch on latest main
continue-on-error: true
run: |
set -euo pipefail
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
[[ "${NOOP:-0}" == "0" ]] || exit 0
LOG="/tmp/proposer-apply.log"
git fetch origin "$DEFAULT_BRANCH"
set +e
git rebase "origin/$DEFAULT_BRANCH" >> "$LOG" 2>&1
RC=$?
set -e
if [[ "$RC" -ne 0 ]]; then
git rebase --abort || true
fi
echo "REBASE_RC=$RC" >> /tmp/proposer.env
echo "Rebase log (tail):"
tail -n 220 "$LOG" || true
- name: Comment issues on failure
if: ${{ always() }}
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
@@ -322,9 +537,86 @@ jobs:
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || { echo " apply failed -> skip push"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo " no-op -> skip push"; exit 0; }
[[ -n "${BRANCH:-}" ]] || { echo " BRANCH unset -> skip push"; exit 0; }
APPLY_RC="${APPLY_RC:-0}"
REBASE_RC="${REBASE_RC:-0}"
if [[ "$APPLY_RC" == "0" && "$REBASE_RC" == "0" ]]; then
echo "No failure detected"
exit 0
fi
test -n "${FORGE_TOKEN:-}" || exit 0
if [[ -f /tmp/proposer-apply.log ]]; then
BODY="$(tail -n 160 /tmp/proposer-apply.log | sed 's/\r$//')"
else
BODY="(no proposer log found)"
fi
export BODY APPLY_RC REBASE_RC FAILED_ISSUE
if [[ "$APPLY_RC" != "0" ]]; then
export FAILURE_KIND="apply"
else
export FAILURE_KIND="rebase"
fi
node --input-type=module - <<'NODE' > /tmp/proposer.failure.comment.json
const body = process.env.BODY || "";
const applyRc = process.env.APPLY_RC || "0";
const rebaseRc = process.env.REBASE_RC || "0";
const failedIssue = process.env.FAILED_ISSUE || "unknown";
const kind = process.env.FAILURE_KIND || "apply";
const msg =
kind === "apply"
? `Batch proposer failed on ticket #${failedIssue} (rc=${applyRc}).\n\n\`\`\`\n${body}\n\`\`\`\n`
: `Rebase proposer failed on main (rc=${rebaseRc}).\n\n\`\`\`\n${body}\n\`\`\`\n`;
process.stdout.write(JSON.stringify({ body: msg }));
NODE
for ISSUE in ${TARGET_ISSUES:-}; do
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE/comments" \
--data-binary @/tmp/proposer.failure.comment.json || true
done
- name: Late guard against duplicate batch materialization
run: |
set -euo pipefail
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
[[ "${REBASE_RC:-0}" == "0" ]] || exit 0
[[ "${NOOP:-0}" == "0" ]] || exit 0
REMOTE_SHA="$(git ls-remote --heads origin "$BATCH_BRANCH" | awk 'NR==1 {print $1}')"
if [[ -n "${REMOTE_SHA:-}" && "${REMOTE_SHA}" != "${END_SHA:-}" ]]; then
echo 'SKIP=1' >> /tmp/proposer.env
echo 'SKIP_REASON="batch_branch_already_materialized"' >> /tmp/proposer.env
echo "OPEN_PR_BRANCH=${BATCH_BRANCH}" >> /tmp/proposer.env
echo "Remote batch branch already exists at $REMOTE_SHA -> skip duplicate push/PR"
exit 0
fi
echo "Late guard OK"
- name: Push bot branch
if: ${{ always() }}
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
run: |
set -euo pipefail
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "Apply failed -> skip push"; exit 0; }
[[ "${REBASE_RC:-0}" == "0" ]] || { echo "Rebase failed -> skip push"; exit 0; }
[[ "${NOOP:-0}" == "0" ]] || { echo "No-op -> skip push"; exit 0; }
[[ -n "${BRANCH:-}" ]] || { echo "BRANCH unset -> skip push"; exit 0; }
AUTH_URL="$(node --input-type=module -e '
const [clone, tok] = process.argv.slice(1);
@@ -337,7 +629,7 @@ jobs:
git remote set-url origin "$AUTH_URL"
git push -u origin "$BRANCH"
- name: Create PR + comment issue
- name: Create PR + comment issues + close issues
if: ${{ always() }}
env:
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
@@ -345,51 +637,152 @@ jobs:
set -euo pipefail
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
[[ "${REBASE_RC:-0}" == "0" ]] || exit 0
[[ "${NOOP:-0}" == "0" ]] || exit 0
[[ -n "${BRANCH:-}" ]] || { echo " BRANCH unset -> skip PR"; exit 0; }
[[ -n "${BRANCH:-}" ]] || { echo "BRANCH unset -> skip PR"; exit 0; }
PR_TITLE="proposer: apply ticket #${ISSUE_NUMBER}"
PR_BODY="PR auto depuis ticket #${ISSUE_NUMBER} (state/approved).\n\n- Branche: ${BRANCH}\n- Commit: ${END_SHA:-unknown}\n\nMerge si CI OK."
test -n "${FORGE_TOKEN:-}" || { echo "Missing FORGE_TOKEN"; exit 1; }
PR_PAYLOAD="$(node --input-type=module -e '
const [title, body, base, head] = process.argv.slice(1);
console.log(JSON.stringify({ title, body, base, head, allow_maintainer_edit: true }));
' "$PR_TITLE" "$PR_BODY" "$DEFAULT_BRANCH" "${OWNER}:${BRANCH}")"
OPEN_PRS_JSON="$(curl -fsS \
-H "Authorization: token $FORGE_TOKEN" \
-H "Accept: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls?state=open&limit=100")"
export OPEN_PRS_JSON BATCH_BRANCH BATCH_KEY
EXISTING_PR_URL="$(node --input-type=module -e '
const pulls = JSON.parse(process.env.OPEN_PRS_JSON || "[]");
const branch = String(process.env.BATCH_BRANCH || "");
const key = String(process.env.BATCH_KEY || "");
const current = Array.isArray(pulls)
? pulls.find((pr) => {
const ref = String(pr?.head?.ref || "");
const body = String(pr?.body || "");
return (branch && ref === branch) || (key && body.includes(`Batch-Key: ${key}`));
})
: null;
process.stdout.write(current ? String(current.html_url || current.url || "") : "");
')"
if [[ -n "${EXISTING_PR_URL:-}" ]]; then
echo "PR already exists for this batch: $EXISTING_PR_URL"
exit 0
fi
if [[ "${TARGET_COUNT:-0}" == "1" ]]; then
PR_TITLE="proposer: apply ticket #${TARGET_PRIMARY_ISSUE}"
else
PR_TITLE="proposer: apply ${TARGET_COUNT} tickets on ${TARGET_CHEMIN}"
fi
export PR_TITLE TARGET_CHEMIN TARGET_ISSUES BRANCH END_SHA DEFAULT_BRANCH OWNER BATCH_KEY
node --input-type=module -e '
import fs from "node:fs";
const issues = String(process.env.TARGET_ISSUES || "")
.trim()
.split(/\s+/)
.filter(Boolean);
const body = [
`PR auto depuis ticket${issues.length > 1 ? "s" : ""} ${issues.map((n) => `#${n}`).join(", ")} (state/approved).`,
"",
`- Chemin: ${process.env.TARGET_CHEMIN || "(inconnu)"}`,
"- Tickets:",
...issues.map((n) => ` - #${n}`),
`- Branche: ${process.env.BRANCH || ""}`,
`- Commit: ${process.env.END_SHA || "unknown"}`,
`- Batch-Key: ${process.env.BATCH_KEY || ""}`,
"",
"Merge si CI OK."
].join("\n");
fs.writeFileSync(
"/tmp/proposer.pr.json",
JSON.stringify({
title: process.env.PR_TITLE || "proposer: apply tickets",
body,
base: process.env.DEFAULT_BRANCH || "main",
head: `${process.env.OWNER}:${process.env.BRANCH}`,
allow_maintainer_edit: true
})
);
'
PR_JSON="$(curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls" \
--data-binary "$PR_PAYLOAD")"
--data-binary @/tmp/proposer.pr.json)"
PR_URL="$(node --input-type=module -e '
const pr = JSON.parse(process.argv[1] || "{}");
console.log(pr.html_url || pr.url || "");
' "$PR_JSON")"
PR_URL="$(node --input-type=module -e 'const pr = JSON.parse(process.argv[1] || "{}"); console.log(pr.html_url || pr.url || "");' "$PR_JSON")"
test -n "$PR_URL" || { echo "❌ PR URL missing. Raw: $PR_JSON"; exit 1; }
test -n "$PR_URL" || {
echo "PR URL missing. Raw: $PR_JSON"
exit 1
}
MSG="✅ PR Proposer créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}"
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
for ISSUE in $TARGET_ISSUES; do
export ISSUE PR_URL
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
--data-binary "$C_PAYLOAD"
node --input-type=module -e '
import fs from "node:fs";
- name: Finalize (fail job if apply failed)
const issue = process.env.ISSUE || "";
const url = process.env.PR_URL || "";
const msg =
`PR proposer creee pour le ticket #${issue} : ${url}\n\n` +
`Le ticket est cloture automatiquement ; la discussion peut se poursuivre dans la PR.`;
fs.writeFileSync(
"/tmp/proposer.issue.close.comment.json",
JSON.stringify({ body: msg })
);
'
curl -fsS -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE/comments" \
--data-binary @/tmp/proposer.issue.close.comment.json
curl -fsS -X PATCH \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" \
--data-binary '{"state":"closed"}'
ISSUE_STATE="$(curl -fsS \
-H "Authorization: token $FORGE_TOKEN" \
-H "Accept: application/json" \
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" | \
node --input-type=module -e 'let s=""; process.stdin.on("data", d => s += d); process.stdin.on("end", () => { const j = JSON.parse(s || "{}"); process.stdout.write(String(j.state || "")); });')"
[[ "$ISSUE_STATE" == "closed" ]] || {
echo "Issue #$ISSUE is still not closed after PATCH"
exit 1
}
done
echo "PR: $PR_URL"
- name: Finalize
if: ${{ always() }}
run: |
set -euo pipefail
source /tmp/proposer.env || true
[[ "${SKIP:-0}" != "1" ]] || exit 0
RC="${APPLY_RC:-0}"
if [[ "$RC" != "0" ]]; then
echo "❌ apply failed (rc=$RC)"
exit "$RC"
if [[ "${APPLY_RC:-0}" != "0" ]]; then
echo "Apply failed (rc=${APPLY_RC})"
exit "${APPLY_RC}"
fi
echo "✅ apply ok"
if [[ "${REBASE_RC:-0}" != "0" ]]; then
echo "Rebase failed (rc=${REBASE_RC})"
exit "${REBASE_RC}"
fi
echo "Proposer queue OK"

4
.gitignore vendored
View File

@@ -28,3 +28,7 @@ public/favicon_io.zip
# macOS
.DS_Store
# local temp workspace
.tmp/
public/__ops/health.json

View File

@@ -86,6 +86,10 @@ function rehypeDedupeIds() {
}
export default defineConfig({
legacy: {
collectionsBackwardsCompat: true,
},
output: "static",
trailingSlash: "always",
site: process.env.PUBLIC_SITE ?? "http://localhost:4321",

View File

@@ -0,0 +1,11 @@
{
"accepted_resets": {
"archicrat-ia/prologue/index.html": "Reset intentionnel des ancres après réimport DOCX et révision substantielle du prologue depuis la source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/chapitre-1/index.html": "Reset intentionnel des ancres après révision doctrinale substantielle du chapitre 1. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/chapitre-2/index.html": "Reset intentionnel des ancres après restauration doctrinale substantielle du chapitre 2 depuis la bonne source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/chapitre-3/index.html": "Reset intentionnel des ancres après réimport DOCX et perfectionnement doctrinal substantiel du chapitre 3 depuis la source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/chapitre-4/index.html": "Reset intentionnel des ancres après réimport DOCX et stabilisation doctrinale substantielle du chapitre 4 depuis la source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/chapitre-5/index.html": "Reset intentionnel des ancres après réimport DOCX et stabilisation doctrinale substantielle du chapitre 5 depuis la source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver.",
"archicrat-ia/conclusion/index.html": "Reset intentionnel des ancres après réimport DOCX et révision substantielle de la conclusion depuis la source officielle. Site neuf, sans annotations ni compatibilité descendante à préserver."
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,147 @@
# START-HERE — Archicratie / Édition Web (v2)
> Onboarding + exploitation “nickel chrome” (DEV → Gitea → CI → Release → Blue/Green → Edge/SSO)
# START-HERE — Archicratie / Édition Web (v3)
> Onboarding + exploitation “nickel chrome” (DEV → Gitea → CI → Release → Blue/Green → Edge/SSO → localhost auto-sync)
## 0) TL;DR (la règle dor)
- **Gitea = source canonique**.
- **main est protégé** : toute modification passe par **branche → PR → CI → merge**.
- **Le NAS nest pas la source** : si un hotfix est fait sur NAS, on **backporte** via PR immédiatement.
- **Le site est statique Astro** : la prod sert du HTML (nginx), laccès est contrôlé au niveau reverse-proxy (Traefik + Authelia).
- **Gitea = source canonique**.
- **`main` est protégée** : toute modification passe par **branche → PR → CI → merge**.
- **Le NAS nest pas la source** : si un hotfix est fait sur NAS, il doit être **backporté immédiatement** via PR.
- **Le site est statique Astro** : la prod sert du HTML via nginx ; laccès est contrôlé au niveau reverse-proxy (Traefik + Authelia).
- **Le localhost automatique nest pas le repo de dev** : il tourne depuis un **worktree dédié**, synchronisé sur `origin/main`.
---
## 1) Architecture mentale (ultra simple)
- **DEV (Mac Studio)** : édition + tests + commit + push
- **Gitea** : dépôt canon + PR + CI (CI.yaml)
- **NAS (DS220+)** : déploiement “blue/green”
- `web_blue` (staging upstream) → `127.0.0.1:8081`
- `web_green` (live upstream)`127.0.0.1:8082`
- **Edge (Traefik)** : route les hosts
- **DEV canonique (Mac Studio)** : édition, dev, tests, commits, pushes
- **Gitea** : dépôt canonique, PR, CI, workflows éditoriaux
- **NAS (DS220+)** : déploiement blue/green
- `web_blue` → staging upstream → `127.0.0.1:8081`
- `web_green` → live upstream → `127.0.0.1:8082`
- **Edge (Traefik)** : routage des hosts
- `staging.archicratie...` → 8081
- `archicratie...` → 8082
- **Authelia** devant, via middleware `chain-auth@file`
- **Localhost auto-sync**
- un **repo canonique de développement**
- un **worktree localhost miroir de `origin/main`**
- un **agent de sync**
- un **agent Astro**
---
## 2) Répertoires & conventions (repo)
### 2.1 Contenu canon (édition)
- `src/content/**` : contenu MD / MDX canon (Astro content collections)
- `src/pages/**` : routes Astro (index, [...slug], etc.)
- `src/components/**` : composants UI (SiteNav, TOC, SidePanel, etc.)
- `src/layouts/**` : layouts (EditionLayout, SiteLayout)
- `src/content/**` : contenu MD / MDX canon
- `src/pages/**` : routes Astro
- `src/components/**` : composants UI
- `src/layouts/**` : layouts
- `src/styles/**` : CSS global
### 2.2 Annotations (pré-Édition “tickets”)
- `src/annotations/<workKey>/<slug>.yml`
- Exemple : `src/annotations/archicrat-ia/prologue.yml`
- Objectif : stocker “Références / Médias / Commentaires” par page et par paragraphe (`p-...`).
- Exemple :
`src/annotations/archicrat-ia/prologue.yml`
Objectif :
stocker “Références / Médias / Commentaires” par page et par paragraphe (`p-...`).
### 2.3 Scripts (tooling / build)
- `scripts/inject-anchor-aliases.mjs` : injection aliases dans dist
- `scripts/dedupe-ids-dist.mjs` : retire IDs dupliqués dans dist
- `scripts/build-para-index.mjs` : index paragraphes (postbuild / predev)
- `scripts/build-annotations-index.mjs` : index annotations (postbuild / predev)
- `scripts/check-anchors.mjs` : contrat stabilité dancres (CI)
- `scripts/inject-anchor-aliases.mjs` : injection aliases dans `dist`
- `scripts/dedupe-ids-dist.mjs` : retrait IDs dupliqués
- `scripts/build-para-index.mjs` : index paragraphes
- `scripts/build-annotations-index.mjs` : index annotations
- `scripts/check-anchors.mjs` : contrat stabilité dancres
- `scripts/check-annotations*.mjs` : sanity YAML + médias
> Important : les scripts sont **partie intégrante** de la stabilité (IDs/ancres/indexation).
> On évite “la magie” : tout est scripté + vérifié.
> Important : ces scripts ne sont pas accessoires.
> Ils font partie du contrat de stabilité éditoriale.
## 3) Workflow Git “pro” (main protégé)
### 3.1 Cycle standard (toute modif)
en bash :
---
## 3) Les trois espaces à ne jamais confondre
### 3.1 Repo canonique de développement
```text
/Volumes/FunIA/dev/archicratie-edition/site
```
Usage :
- développement normal
- branches de travail
- nouvelles fonctionnalités
- corrections manuelles
- commits
- pushes
- PR
### 3.2 Worktree localhost miroir de `main`
```text
/Users/s-funia/ops-local/archicratie/localhost-worktree
```
Branche attendue :
```text
localhost-sync
```
Usage :
- exécuter le localhost automatique
- refléter `origin/main`
- ne jamais servir despace de développement
### 3.3 Ops local hors repo
```text
/Users/s-funia/ops-local/archicratie
```
Usage :
- scripts dexploitation
- état
- logs
- automatisation `launchd`
---
## 4) Pourquoi cette séparation existe
Il ne faut pas utiliser le repo canonique de développement comme serveur localhost permanent.
Sinon on mélange :
- travail en cours
- commits non poussés
- essais temporaires
- état réellement publié sur `main`
Le résultat devient ambigu.
La séparation retenue est donc :
- **repo canonique** = espace de développement
- **worktree localhost** = miroir exécutable de `origin/main`
- **ops local** = scripts et automatisation
Cest cette séparation qui rend le système lisible, robuste et opérable.
---
## 5) Workflow Git “pro” (main protégée)
### 5.1 Cycle standard (toute modif)
```bash
git checkout main
git pull --ff-only
@@ -60,37 +156,48 @@ npm run test:anchors
git add -A
git commit -m "xxx: description claire"
git push -u origin "$BR"
```
### 3.2 PR vers main
### 5.2 PR vers `main`
Ouvrir PR dans Gitea
- ouvrir une PR dans Gitea
- attendre une CI verte
- merger
- laisser les workflows faire le reste
CI doit être verte
### 5.3 Cas spécial : hotfix prod (NAS)
Merge PR → main
On peut faire un hotfix durgence côté NAS si nécessaire.
### 3.3 Cas spécial : hotfix prod (NAS)
Mais létat final doit toujours revenir dans Gitea :
On peut faire un hotfix “urgence” en prod/staging si nécessaire…
- branche
- PR
- CI
- merge
MAIS : létat final doit revenir dans Gitea : branche → PR → CI → merge.
---
## 4) Déploiement (NAS) — principe
### 4.1 Release pack
## 6) Déploiement (NAS) — principe
On génère un pack “reproductible” (source + config + scripts) puis on déploie.
### 6.1 Release pack
### 4.2 Blue/Green
On génère un pack reproductible, puis on déploie.
web_blue = staging upstream (8081)
### 6.2 Blue/Green
web_green = live upstream (8082)
- `web_blue` = staging (`8081`)
- `web_green` = live (`8082`)
Edge Traefik sélectionne quel host pointe vers quel upstream.
Le reverse-proxy choisit lupstream selon le host demandé.
## 5) Check-list “≤ 10 commandes” (happy path complet)
### 5.1 DEV (Mac)
---
## 7) Happy path complet
### 7.1 DEV (Mac)
```bash
git checkout main && git pull --ff-only
git checkout -b chore/my-change-$(date +%Y%m%d)
@@ -99,55 +206,258 @@ rm -rf .astro node_modules/.vite dist
npm run build
npm run test:anchors
npm run dev
```
### 5.2 Push + PR
### 7.2 Push + PR
```bash
git add -A
git commit -m "chore: my change"
git push -u origin chore/my-change-YYYYMMDD
# ouvrir PR dans Gitea
```
### 5.3 Déploiement NAS (résumé)
Puis ouvrir la PR dans Gitea.
Voir docs/runbooks/DEPLOY-BLUE-GREEN.md.
### 7.3 Déploiement NAS
## 6) Problèmes “classiques” + diagnostic rapide
### 6.1 “Le staging ne ressemble pas au local”
Voir :
# Comparer upstream direct 8081 vs 8082 :
```text
docs/runbooks/DEPLOY-BLUE-GREEN.md
```
---
## 8) Localhost auto-sync — ce quil faut retenir
Le localhost automatique sert à voir **la vérité de `main`**, pas à développer du neuf.
### 8.1 Scripts principaux
#### Script de sync
```text
~/ops-local/archicratie/auto-sync-localhost.sh
```
Rôle :
- fetch `origin/main`
- réaligner le worktree localhost
- lancer `npm ci` si besoin
- redéclencher lagent Astro si nécessaire
#### Script Astro
```text
~/ops-local/archicratie/run-astro-localhost.sh
```
Rôle :
- lancer `astro dev`
- depuis le bon worktree
- avec le bon runtime Node
- sur `127.0.0.1:4321`
> Oui : ce script est nécessaire.
> Il isole proprement le lancement du serveur Astro dans un contexte `launchd` stable.
### 8.2 LaunchAgents
#### Agent sync
```text
~/Library/LaunchAgents/me.archicratie.localhost-sync.plist
```
#### Agent Astro
```text
~/Library/LaunchAgents/me.archicratie.localhost-astro.plist
```
### 8.3 Document de référence
Pour tout le détail dexploitation du localhost automatique, lire :
```text
docs/OPS-LOCALHOST-AUTO-SYNC.md
```
---
## 9) Règle dor : il y a deux usages locaux distincts
### 9.1 Voir ce qui est réellement sur `main`
Utiliser :
```text
http://127.0.0.1:4321
```
Ce localhost doit être considéré comme :
**un miroir local exécutable de `origin/main`**
### 9.2 Développer / tester une nouvelle fonctionnalité
Utiliser le repo canonique :
```bash
cd /Volumes/FunIA/dev/archicratie-edition/site
npm run dev
```
Donc :
- **localhost auto-sync** = vérité de `main`
- **localhost de dev manuel** = expérimentation en cours
Il ne faut pas les confondre.
---
## 10) Ce quil ne faut pas faire
### 10.1 Ne pas développer dans le worktree localhost
Le worktree localhost est piloté automatiquement.
Il peut être :
- réaligné
- nettoyé
- redémarré
Donc :
- pas de commits dedans
- pas de dev feature dedans
- pas dexpérimentation de fond dedans
### 10.2 Ne pas utiliser le repo canonique comme miroir auto-sync
Sinon on mélange :
- espace de dev
- état publié
- serveur local permanent
### 10.3 Ne pas remettre les scripts ops sur un volume externe
Les scripts dops doivent rester sous `HOME`.
Le fait de les mettre sous `/Volumes/...` a déjà provoqué des erreurs du type :
```text
Operation not permitted
```
### 10.4 Ne pas supprimer `run-astro-localhost.sh`
Ce script fait partie de larchitecture actuelle.
Le supprimer reviendrait à réintroduire le flou entre sync Git et exécution dAstro.
---
## 11) Commandes de contrôle essentielles
### 11.1 État global
```bash
~/ops-local/archicratie/doctor-localhost.sh
```
### 11.2 État Git
```bash
git -C ~/ops-local/archicratie/localhost-worktree rev-parse HEAD
git -C /Volumes/FunIA/dev/archicratie-edition/site ls-remote origin refs/heads/main
git -C ~/ops-local/archicratie/localhost-worktree branch --show-current
```
### 11.3 État LaunchAgents
```bash
launchctl print "gui/$(id -u)/me.archicratie.localhost-sync" | sed -n '1,160p'
launchctl print "gui/$(id -u)/me.archicratie.localhost-astro" | sed -n '1,160p'
```
### 11.4 État logs
```bash
tail -n 120 ~/ops-local/archicratie/logs/auto-sync-localhost.log
tail -n 120 ~/ops-local/archicratie/logs/astro-localhost.log
tail -n 80 ~/Library/Logs/archicratie-localhost-sync.err.log
tail -n 80 ~/Library/Logs/archicratie-localhost-astro.err.log
```
### 11.5 État serveur
```bash
lsof -nP -iTCP:4321 -sTCP:LISTEN
PID="$(lsof -tiTCP:4321 -sTCP:LISTEN | head -n 1)"
ps -p "$PID" -o pid=,command=
lsof -a -p "$PID" -d cwd
```
### 11.6 Vérification contenu
```bash
curl -s http://127.0.0.1:4321/archicrat-ia/prologue/ | grep -n "taxe Zucman"
```
---
## 12) Problèmes classiques + diagnostic
### 12.1 “Le staging ne ressemble pas au local”
Comparer les upstream directs :
```bash
curl -sS http://127.0.0.1:8081/ | head -n 2
curl -sS http://127.0.0.1:8082/ | head -n 2
```
# Vérifier quel routeur edge répond (header diag) :
Vérifier le routeur edge :
```bash
curl -sSI -H 'Host: staging.archicratie.trans-hands.synology.me' http://127.0.0.1:18080/ \
| grep -iE 'HTTP/|location:|x-archi-router'
```
# Lire docs/runbooks/EDGE-TRAEFIK.md.
Voir :
### 6.2 Canonical incorrect (localhost en prod)
```text
docs/runbooks/EDGE-TRAEFIK.md
```
Cause racine : site dans Astro = PUBLIC_SITE non injecté au build.
### 12.2 Canonical incorrect
Fix canonique : voir docs/runbooks/ENV-PUBLIC_SITE.md.
Cause probable : `PUBLIC_SITE` mal injecté au build.
Test :
```bash
curl -sS http://127.0.0.1:8082/ | grep -oE 'rel="canonical" href="[^"]+"' | head -1
```
### 6.3 Contrat “anchors” en échec après migration dURL
Voir :
Quand on déplace des routes (ex: /archicratie/archicrat-ia/* → /archicrat-ia/*), le test dancres peut échouer même si les IDs nont pas changé, car les pages ont changé de chemin.
```text
docs/runbooks/ENV-PUBLIC_SITE.md
```
# Procédure safe :
### 12.3 Contrat anchors en échec après migration dURL
Backup baseline :
Procédure safe :
```bash
cp -a tests/anchors-baseline.json /tmp/anchors-baseline.json.bak.$(date +%F-%H%M%S)
Mettre à jour les clés (chemins) sans toucher aux IDs :
node - <<'NODE'
import fs from 'fs';
const p='tests/anchors-baseline.json';
@@ -161,16 +471,213 @@ fs.writeFileSync(p, JSON.stringify(out,null,2)+'\n');
console.log('updated keys:', Object.keys(j).length, '->', Object.keys(out).length);
NODE
Re-run :
npm run test:anchors
```
## 7) Ce que létape 9 doit faire (orientation)
### 12.4 “Le localhost auto-sync ne montre pas les dernières modifs”
Stabiliser le pipeline “tickets → YAML annotations”
Commande réflexe :
Formaliser la spec YAML + merge + anti-doublon (voir docs/EDITORIAL-ANNOTATIONS-SPEC.md)
```bash
~/ops-local/archicratie/doctor-localhost.sh
```
Durcir lonboarding (ce START-HERE + runbooks)
Puis :
Éviter les régressions par tests (anchors / annotations / smoke)
```bash
git -C ~/ops-local/archicratie/localhost-worktree rev-parse HEAD
git -C /Volumes/FunIA/dev/archicratie-edition/site ls-remote origin refs/heads/main
```
Si les SHA diffèrent :
- le sync na pas tourné
- ou lagent sync a un problème
### 12.5 “Le SHA est bon mais le contenu web est faux”
Vérifier quel Astro écoute réellement :
```bash
lsof -nP -iTCP:4321 -sTCP:LISTEN
PID="$(lsof -tiTCP:4321 -sTCP:LISTEN | head -n 1)"
ps -p "$PID" -o pid=,command=
lsof -a -p "$PID" -d cwd
```
Attendu :
- commande contenant `astro dev`
- cwd = `~/ops-local/archicratie/localhost-worktree`
### 12.6 Erreur `EBADENGINE`
Cause probable :
- Node 23 utilisé au lieu de Node 22
Résolution :
- forcer `node@22` dans les scripts et les LaunchAgents
### 12.7 Erreur `Operation not permitted`
Cause probable :
- scripts dops placés sous `/Volumes/...`
Résolution :
- garder les scripts sous :
```text
~/ops-local/archicratie
```
### 12.8 Erreur `EPERM` sur `astro.mjs`
Cause probable :
- ancien worktree sur volume externe
- ancien chemin résiduel
- Astro lancé depuis un mauvais emplacement
Résolution :
- worktree localhost sous :
```text
~/ops-local/archicratie/localhost-worktree
```
- scripts cohérents avec ce chemin
- réinstallation propre via :
```bash
~/ops-local/archicratie/install-localhost-sync.sh
```
---
## 13) Redémarrage machine
Après reboot, le comportement attendu est :
1. le LaunchAgent sync se recharge
2. le LaunchAgent Astro se recharge
3. le worktree localhost est réaligné
4. Astro redémarre sur `127.0.0.1:4321`
### Vérification rapide après reboot
```bash
~/ops-local/archicratie/doctor-localhost.sh
```
Si nécessaire :
```bash
~/ops-local/archicratie/install-localhost-sync.sh
```
---
## 14) Procédure de secours manuelle
### Forcer un sync
```bash
~/ops-local/archicratie/auto-sync-localhost.sh
```
### Réinstaller proprement le dispositif local
```bash
~/ops-local/archicratie/install-localhost-sync.sh
```
### Diagnostic complet
```bash
~/ops-local/archicratie/doctor-localhost.sh
```
---
## 15) Décision dexploitation finale
La politique retenue est la suivante :
- **repo canonique** = espace de développement
- **worktree localhost** = miroir automatique de `main`
- **ops sous HOME** = scripts, logs, automation
- **LaunchAgent sync** = réalignement Git
- **LaunchAgent Astro** = exécution stable du serveur local
- **Astro local** = lancé uniquement depuis le worktree localhost
Cette séparation rend le dispositif plus :
- lisible
- robuste
- opérable
- antifragile
---
## 16) Résumé opératoire
### Pour voir la vérité de `main`
Ouvrir :
```text
http://127.0.0.1:4321
```
Le serveur doit provenir de :
```text
/Users/s-funia/ops-local/archicratie/localhost-worktree
```
### Pour développer
Travailler dans :
```text
/Volumes/FunIA/dev/archicratie-edition/site
```
avec les commandes habituelles.
### Pour réparer vite
```bash
~/ops-local/archicratie/doctor-localhost.sh
~/ops-local/archicratie/auto-sync-localhost.sh
```
---
## 17) Mémoire courte
Si un jour plus rien nest clair, repartir de ces commandes :
```bash
~/ops-local/archicratie/doctor-localhost.sh
git -C ~/ops-local/archicratie/localhost-worktree rev-parse HEAD
git -C /Volumes/FunIA/dev/archicratie-edition/site ls-remote origin refs/heads/main
lsof -nP -iTCP:4321 -sTCP:LISTEN
```
Puis lire :
```bash
tail -n 120 ~/ops-local/archicratie/logs/auto-sync-localhost.log
tail -n 120 ~/ops-local/archicratie/logs/astro-localhost.log
```
---
## 18) Statut actuel visé
Quand tout fonctionne correctement :
- le worktree localhost pointe sur le même SHA que `origin/main`
- `astro dev` écoute sur `127.0.0.1:4321`
- son cwd est `~/ops-local/archicratie/localhost-worktree`
- le contenu servi correspond au contenu mergé sur `main`
Cest létat de référence à préserver.

View File

@@ -320,4 +320,227 @@ But : prouver que staging+live servent bien les endpoints essentiels (et que le
```bash
curl -fsSI http://127.0.0.1:8081/ | head -n 1
curl -fsSI http://127.0.0.1:8082/ | head -n 1
curl -fsSI http://127.0.0.1:8082/ | head -n 1
---
## 10) CI Deploy (Gitea Actions) — Gate SKIP / HOTPATCH / FULL (merge-proof) + preuves
Cette section documente le comportement **canonique** du workflow :
- `.gitea/workflows/deploy-staging-live.yml`
Objectif : **zéro surprise**.
On ne veut plus “penser à force=1”.
Le gate doit décider automatiquement, y compris sur des **merge commits**.
### 10.1 — Principe (ce que fait réellement le gate)
Le job `deploy` calcule les fichiers modifiés entre :
- `BEFORE` = commit précédent (avant le push sur main)
- `AFTER` = commit actuel (après le push / merge sur main)
Puis il classe le déploiement dans un mode :
- **MODE=full**
- rebuild image + restart `archicratie-web-blue` (8081) + `archicratie-web-green` (8082)
- warmup endpoints (para-index, annotations-index, pagefind.js)
- vérification canonical staging + live
- **MODE=hotpatch**
- rebuild dun `annotations-index.json` consolidé depuis `src/annotations/**`
- patch direct dans les conteneurs en cours dexécution (blue+green)
- copie des médias modifiés `public/media/**` vers `/usr/share/nginx/html/media/**`
- smoke sur `/annotations-index.json` des deux ports
- **MODE=skip**
- pas de déploiement (on évite le bruit)
⚠️ Important : le mode “hotpatch” **ne rebuild pas** Astro.
Donc toute modification de contenu, routes, scripts, anchors, etc. doit déclencher **full**.
### 10.2 — Matrice de décision (règles officielles)
Le gate définit deux flags :
- `HAS_FULL=1` si changement “build-impacting”
- `HAS_HOTPATCH=1` si changement “annotations/media only”
Règle de priorité :
1) Si `HAS_FULL=1` → **MODE=full**
2) Sinon si `HAS_HOTPATCH=1` → **MODE=hotpatch**
3) Sinon → **MODE=skip**
#### 10.2.1 — Changements qui déclenchent FULL (build-impacting)
Exemples typiques (non exhaustif, mais on couvre le cœur) :
- `src/content/**` (contenu MD/MDX)
- `src/pages/**` (routes Astro)
- `src/anchors/**` (aliases dancres)
- `scripts/**` (tooling postbuild : injection, index, tests)
- `src/layouts/**`, `src/components/**`, `src/styles/**` (rendu et scripts inline)
- `astro.config.mjs`, `package.json`, `package-lock.json`
- `Dockerfile`, `docker-compose.yml`, `nginx.conf`
- `.gitea/workflows/**` (changement infra CI/CD)
=> On veut **full** pour garantir cohérence et éviter “site partiellement mis à jour”.
#### 10.2.2 — Changements qui déclenchent HOTPATCH (sans rebuild)
Uniquement :
- `src/annotations/**` (shards YAML)
- `public/media/**` (assets média)
=> On veut hotpatch pour vitesse et éviter rebuild NAS.
### 10.3 — “Merge-proof” : pourquoi on ne lit PAS seulement `git show $SHA`
Sur un merge commit, `git show --name-only $SHA` peut être trompeur selon le contexte.
La méthode robuste est :
- utiliser `event.json` (Gitea Actions) pour récupérer `before` et `after`
- calculer `git diff --name-only BEFORE AFTER`
Cest ce qui rend le gate **merge-proof**.
### 10.4 — Tests de preuve A/B (reproductibles)
Ces tests valident le gate sans ambiguïté.
But : vérifier que le mode choisi est EXACTEMENT celui attendu.
#### Test A — toucher `src/content/...` (FULL auto)
1) Créer une branche test
2) Modifier 1 fichier dans `src/content/` (ex : ajouter une ligne de commentaire non destructive)
3) PR → merge dans `main`
4) Vérifier dans `deploy-staging-live.yml` :
Attendus :
- `Gate flags: HAS_FULL=1 HAS_HOTPATCH=0`
- `✅ build-impacting change -> MODE=full (rebuild+restart)`
- Les étapes FULL (blue puis green) sexécutent réellement
#### Test B — toucher `src/annotations/...` uniquement (HOTPATCH auto)
1) Créer une branche test
2) Modifier 1 fichier sous `src/annotations/**` (ex: un champ comment, ts, etc.)
3) PR → merge dans `main`
4) Vérifier dans `deploy-staging-live.yml` :
Attendus :
- `Gate flags: HAS_FULL=0 HAS_HOTPATCH=1`
- `✅ annotations/media change -> MODE=hotpatch`
- Les étapes FULL sont “skip” (durée 0s)
- Létape HOTPATCH sexécute réellement
### 10.5 — Preuve opérationnelle côté NAS (2 URLs + 2 commandes)
But : prouver que staging+live servent bien les endpoints essentiels (et que le déploiement na pas “fait semblant”).
#### 10.5.1 — Deux URLs à vérifier (staging et live)
- Staging (blue) : `http://127.0.0.1:8081/`
- Live (green) : `http://127.0.0.1:8082/`
#### 10.5.2 — Deux commandes minimales (zéro débat)
en bash :
curl -fsSI http://127.0.0.1:8081/ | head -n 1
curl -fsSI http://127.0.0.1:8082/ | head -n 1
Attendu : HTTP/1.1 200 OK des deux côtés.
10.6 — Preuve “alias injection” (ancre ancienne → nouvelle) sur une page
Contexte : lorsquun paragraphe change (ex: ticket “Proposer” appliqué),
lID de paragraphe peut changer, mais on doit préserver les liens anciens via :
src/anchors/anchor-aliases.json
injection build-time dans dist (span .para-alias)
10.6.1 — Check rapide (staging + live)
Remplacer OLD/NEW par tes ids réels :
Attendu : HTTP/1.1 200 OK des deux côtés.
10.6 — Preuve “alias injection” (ancre ancienne → nouvelle) sur une page
Contexte : lorsquun paragraphe change (ex: ticket “Proposer” appliqué),
lID de paragraphe peut changer, mais on doit préserver les liens anciens via :
src/anchors/anchor-aliases.json
injection build-time dans dist (span .para-alias)
10.6.1 — Check rapide (staging + live)
Remplacer OLD/NEW par tes ids réels :
OLD="p-1-60c7ea48"
NEW="p-1-a21087b0"
for P in 8081 8082; do
echo "=== $P ==="
HTML="$(curl -fsS "http://127.0.0.1:${P}/archicrat-ia/chapitre-3/" | tr -d '\r')"
echo "OLD count: $(printf '%s' "$HTML" | grep -o "$OLD" | wc -l | tr -d ' ')"
echo "NEW count: $(printf '%s' "$HTML" | grep -o "$NEW" | wc -l | tr -d ' ')"
printf '%s\n' "$HTML" | grep -nE "$OLD|$NEW|class=\"para-alias\"" | head -n 40 || true
done
Attendu :
présence dun alias : <span id="$OLD" class="para-alias"...>
présence du nouveau paragraphe : <p id="$NEW">...
10.6.2 — Check “lien ancien ne casse pas” (HTTP 200)
for P in 8081 8082; do
curl -fsSI "http://127.0.0.1:${P}/archicrat-ia/chapitre-3/#${OLD}" | head -n 1
done
Attendu : HTTP/1.1 200 OK et navigation fonctionnelle côté navigateur.
10.7 — Troubleshooting gate (symptômes typiques)
Symptom 1 : job bloqué “Set up job” très longtemps
Causes fréquentes :
runner indisponible / capacity saturée
runner ne récupère pas les tâches (fetch_timeout trop court + réseau instable)
erreur dans “Gate — decide …” qui casse bash (et donne limpression dun hang)
Commandes NAS (diagnostic rapide) :
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' | grep -E 'gitea-act-runner|registry|archicratie-web'
docker logs --since 30m --tail 400 gitea-act-runner | tail -n 200
Symptom 2 : conditional binary operator expected
Cause :
test bash du type [[ "$X" == "1" && "$Y" == "2" ]] mal formé
variable vide non quotée
usage dun opérateur non supporté dans la shell effective
Fix :
set -euo pipefail
toujours quoter : [[ "${VAR:-}" == "..." ]]
logguer BEFORE/AFTER/FORCE et sassurer quils ne sont pas vides
Symptom 3 : le gate liste “trop de fichiers” alors quon a changé 1 seul fichier
Cause :
comparaison faite sur le mauvais range (ex: git show sur merge, ou mauvais parent)
Fix :
toujours utiliser git diff --name-only "$BEFORE" "$AFTER" (merge-proof)
confirmer dans le log : Gate ctx: BEFORE=... AFTER=...

1565
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,8 @@
"clean": "rm -rf dist",
"build": "astro build",
"build:clean": "npm run clean && npm run build",
"postbuild": "node scripts/inject-anchor-aliases.mjs && node scripts/dedupe-ids-dist.mjs && node scripts/build-para-index.mjs && node scripts/build-annotations-index.mjs && node scripts/purge-dist-dev-whoami.mjs && npx pagefind --site dist",
"build:search": "pagefind --site dist",
"postbuild": "node scripts/inject-anchor-aliases.mjs && node scripts/dedupe-ids-dist.mjs && node scripts/build-para-index.mjs && node scripts/build-annotations-index.mjs && node scripts/purge-dist-dev-whoami.mjs && npm run build:search",
"import": "node scripts/import-docx.mjs",
"apply:ticket": "node scripts/apply-ticket.mjs",
"audit:dist": "node scripts/audit-dist.mjs",
@@ -25,11 +26,11 @@
"ci": "CI=1 npm test"
},
"dependencies": {
"@astrojs/mdx": "^4.3.13",
"astro": "^5.18.0"
"@astrojs/mdx": "^5.0.0",
"astro": "^6.0.2"
},
"devDependencies": {
"@astrojs/sitemap": "^3.7.0",
"@astrojs/sitemap": "^3.7.1",
"mammoth": "^1.11.0",
"pagefind": "^1.4.0",
"rehype-autolink-headings": "^7.1.0",

0
public/media/.gitkeep Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 KiB

View File

@@ -9,8 +9,9 @@ import { spawnSync } from "node:child_process";
*
* Conçu pour:
* - prendre un ticket [Correction]/[Fact-check] (issue) avec Chemin + Ancre + Proposition
* - retrouver le bon paragraphe dans le .mdx
* - retrouver le bon paragraphe dans le .mdx/.md
* - remplacer proprement
* - ne JAMAIS toucher au frontmatter
* - optionnel: écrire un alias dancre old->new (build-time) dans src/anchors/anchor-aliases.json
* - optionnel: committer automatiquement
* - optionnel: fermer le ticket (après commit)
@@ -39,7 +40,7 @@ Env (recommandé):
Notes:
- Si dist/<chemin>/index.html est absent, le script lance "npm run build" sauf si --no-build.
- Sauvegarde automatique: <fichier>.bak.issue-<N> (uniquement si on écrit)
- Sauvegarde automatique: .tmp/apply-ticket/<fichier>.bak.issue-<N> (uniquement si on écrit)
- Avec --alias : le script rebuild pour identifier le NOUVEL id, puis écrit l'alias old->new.
- Refuse automatiquement les Pull Requests (PR) : ce ne sont pas des tickets éditoriaux.
`);
@@ -89,6 +90,7 @@ const CWD = process.cwd();
const CONTENT_ROOT = path.join(CWD, "src", "content");
const DIST_ROOT = path.join(CWD, "dist");
const ALIASES_FILE = path.join(CWD, "src", "anchors", "anchor-aliases.json");
const BACKUP_ROOT = path.join(CWD, ".tmp", "apply-ticket");
/* -------------------------- utils texte / matching -------------------------- */
@@ -136,31 +138,26 @@ function scoreText(candidate, targetText) {
let hit = 0;
for (const w of tgtSet) if (blkSet.has(w)) hit++;
// Bonus si un long préfixe ressemble
const tgtNorm = normalizeText(stripMd(targetText));
const blkNorm = normalizeText(stripMd(candidate));
const prefix = tgtNorm.slice(0, Math.min(180, tgtNorm.length));
const prefixBonus = prefix && blkNorm.includes(prefix) ? 1000 : 0;
// Ratio bonus (0..100)
const ratio = hit / Math.max(1, tgtSet.size);
const ratioBonus = Math.round(ratio * 100);
return prefixBonus + hit + ratioBonus;
}
function bestBlockMatchIndex(blocks, targetText) {
let best = { i: -1, score: -1 };
for (let i = 0; i < blocks.length; i++) {
const sc = scoreText(blocks[i], targetText);
if (sc > best.score) best = { i, score: sc };
}
return best;
}
function splitParagraphBlocks(mdxText) {
const raw = String(mdxText ?? "").replace(/\r\n/g, "\n");
return raw.split(/\n{2,}/);
function rankedBlockMatches(blocks, targetText, limit = 5) {
return blocks
.map((b, i) => ({
i,
score: scoreText(b, targetText),
excerpt: stripMd(b).slice(0, 140),
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
function isLikelyExcerpt(s) {
@@ -172,6 +169,89 @@ function isLikelyExcerpt(s) {
return false;
}
/* --------------------------- frontmatter / structure ------------------------ */
function normalizeNewlines(s) {
return String(s ?? "").replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
}
function splitMdxFrontmatter(src) {
const text = normalizeNewlines(src);
const m = text.match(/^---\n[\s\S]*?\n---\n?/);
if (!m) {
return {
hasFrontmatter: false,
frontmatter: "",
body: text,
};
}
const frontmatter = m[0];
const body = text.slice(frontmatter.length);
return {
hasFrontmatter: true,
frontmatter,
body,
};
}
function joinMdxFrontmatter(frontmatter, body) {
if (!frontmatter) return String(body ?? "");
return String(frontmatter) + String(body ?? "");
}
function assertFrontmatterIntegrity({ hadFrontmatter, originalFrontmatter, finalText, filePath }) {
if (!hadFrontmatter) return;
const text = normalizeNewlines(finalText);
if (!text.startsWith("---\n")) {
throw new Error(`Frontmatter perdu pendant la mise à jour de ${filePath}`);
}
if (!text.startsWith(originalFrontmatter)) {
throw new Error(`Frontmatter altéré pendant la mise à jour de ${filePath}`);
}
}
function splitParagraphBlocksPreserve(bodyText) {
const text = normalizeNewlines(bodyText);
if (!text) {
return { blocks: [], separators: [] };
}
const blocks = [];
const separators = [];
const re = /(\n{2,})/g;
let last = 0;
let m;
while ((m = re.exec(text))) {
blocks.push(text.slice(last, m.index));
separators.push(m[1]);
last = m.index + m[1].length;
}
blocks.push(text.slice(last));
return { blocks, separators };
}
function joinParagraphBlocksPreserve(blocks, separators) {
if (!Array.isArray(blocks) || blocks.length === 0) return "";
let out = "";
for (let i = 0; i < blocks.length; i++) {
out += blocks[i];
if (i < separators.length) out += separators[i];
}
return out;
}
/* ------------------------------ utils système ------------------------------ */
function run(cmd, args, opts = {}) {
@@ -251,7 +331,9 @@ function pickSection(body, markers) {
.map((m) => ({ m, i: text.toLowerCase().indexOf(m.toLowerCase()) }))
.filter((x) => x.i >= 0)
.sort((a, b) => a.i - b.i)[0];
if (!idx) return "";
const start = idx.i + idx.m.length;
const tail = text.slice(start);
@@ -266,11 +348,13 @@ function pickSection(body, markers) {
"\n## Proposition",
"\n## Problème",
];
let end = tail.length;
for (const s of stops) {
const j = tail.toLowerCase().indexOf(s.toLowerCase());
if (j >= 0 && j < end) end = j;
}
return tail.slice(0, end).trim();
}
@@ -298,8 +382,6 @@ function extractAnchorIdAnywhere(text) {
function extractCheminFromAnyUrl(text) {
const s = String(text || "");
// Exemple: http://localhost:4321/archicratie/prologue/#p-3-xxxx
// ou: /archicratie/prologue/#p-3-xxxx
const m = s.match(/(\/[a-z0-9\-]+\/[a-z0-9\-\/]+\/)#p-\d+-[0-9a-f]{8}/i);
return m ? m[1] : "";
}
@@ -400,7 +482,7 @@ async function fetchIssue({ forgeApiBase, owner, repo, token, issueNum }) {
headers: {
Authorization: `token ${token}`,
Accept: "application/json",
"User-Agent": "archicratie-apply-ticket/2.0",
"User-Agent": "archicratie-apply-ticket/2.1",
},
});
if (!res.ok) {
@@ -416,7 +498,7 @@ async function closeIssue({ forgeApiBase, owner, repo, token, issueNum, comment
Authorization: `token ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent": "archicratie-apply-ticket/2.0",
"User-Agent": "archicratie-apply-ticket/2.1",
};
if (comment) {
@@ -425,7 +507,11 @@ async function closeIssue({ forgeApiBase, owner, repo, token, issueNum, comment
}
const url = `${base}/api/v1/repos/${owner}/${repo}/issues/${issueNum}`;
const res = await fetch(url, { method: "PATCH", headers, body: JSON.stringify({ state: "closed" }) });
const res = await fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify({ state: "closed" }),
});
if (!res.ok) {
const t = await res.text().catch(() => "");
@@ -529,10 +615,9 @@ async function main() {
console.log(`🔎 Fetch ticket #${issueNum} from ${owner}/${repo}`);
const issue = await fetchIssue({ forgeApiBase, owner, repo, token, issueNum });
// Guard PR (Pull Request = "Demande d'ajout" = pas un ticket éditorial)
if (issue?.pull_request) {
console.error(`❌ #${issueNum} est une Pull Request (demande dajout), pas un ticket éditorial.`);
console.error(`➡️ Ouvre un ticket [Correction]/[Fact-check] depuis le site (Proposer), puis relance apply-ticket sur ce numéro.`);
console.error("➡️ Ouvre un ticket [Correction]/[Fact-check] depuis le site (Proposer), puis relance apply-ticket sur ce numéro.");
process.exit(2);
}
@@ -553,7 +638,6 @@ async function main() {
ancre = (ancre || "").trim();
if (ancre.startsWith("#")) ancre = ancre.slice(1);
// fallback si ticket mal formé
if (!ancre) ancre = extractAnchorIdAnywhere(title) || extractAnchorIdAnywhere(body);
chemin = normalizeChemin(chemin);
@@ -592,7 +676,6 @@ async function main() {
const distHtmlPath = path.join(DIST_ROOT, chemin.replace(/^\/+|\/+$/g, ""), "index.html");
await ensureBuildIfNeeded(distHtmlPath);
// Texte cible: préférence au texte complet (ticket), sinon dist si extrait probable
let targetText = texteActuel;
let distText = "";
@@ -609,21 +692,24 @@ async function main() {
throw new Error("Impossible de reconstruire le texte du paragraphe (ni texte actuel, ni dist html).");
}
const original = await fs.readFile(contentFile, "utf-8");
const blocks = splitParagraphBlocks(original);
const originalRaw = await fs.readFile(contentFile, "utf-8");
const { hasFrontmatter, frontmatter, body: originalBody } = splitMdxFrontmatter(originalRaw);
const best = bestBlockMatchIndex(blocks, targetText);
const split = splitParagraphBlocksPreserve(originalBody);
const blocks = split.blocks;
const separators = split.separators;
if (!blocks.length) {
throw new Error(`Aucun bloc éditorial exploitable dans ${path.relative(CWD, contentFile)}`);
}
const ranked = rankedBlockMatches(blocks, targetText, 5);
const best = ranked[0] || { i: -1, score: -1, excerpt: "" };
const runnerUp = ranked[1] || null;
// seuil de sécurité
if (best.i < 0 || best.score < 40) {
console.error("❌ Match trop faible: je refuse de remplacer automatiquement.");
console.error(`➡️ Score=${best.score}. Recommandation: ticket avec 'Texte actuel (copie exacte du paragraphe)'.`);
const ranked = blocks
.map((b, i) => ({ i, score: scoreText(b, targetText), excerpt: stripMd(b).slice(0, 140) }))
.sort((a, b) => b.score - a.score)
.slice(0, 5);
console.error("Top candidates:");
for (const r of ranked) {
console.error(` #${r.i + 1} score=${r.score} ${r.excerpt}${r.excerpt.length >= 140 ? "…" : ""}`);
@@ -631,12 +717,34 @@ async function main() {
process.exit(2);
}
if (runnerUp) {
const ambiguityGap = best.score - runnerUp.score;
if (ambiguityGap < 15) {
console.error("❌ Match ambigu: le meilleur candidat est trop proche du second.");
console.error(`➡️ best=${best.score} / second=${runnerUp.score} / gap=${ambiguityGap}`);
console.error("Top candidates:");
for (const r of ranked) {
console.error(` #${r.i + 1} score=${r.score} ${r.excerpt}${r.excerpt.length >= 140 ? "…" : ""}`);
}
process.exit(2);
}
}
const beforeBlock = blocks[best.i];
const afterBlock = proposition.trim();
const nextBlocks = blocks.slice();
nextBlocks[best.i] = afterBlock;
const updated = nextBlocks.join("\n\n");
const updatedBody = joinParagraphBlocksPreserve(nextBlocks, separators);
const updatedRaw = joinMdxFrontmatter(frontmatter, updatedBody);
assertFrontmatterIntegrity({
hadFrontmatter: hasFrontmatter,
originalFrontmatter: frontmatter,
finalText: updatedRaw,
filePath: path.relative(CWD, contentFile),
});
console.log(`🧩 Matched block #${best.i + 1}/${blocks.length} score=${best.score}`);
@@ -650,13 +758,15 @@ async function main() {
return;
}
// backup uniquement si on écrit
const bakPath = `${contentFile}.bak.issue-${issueNum}`;
const relContentFile = path.relative(CWD, contentFile);
const bakPath = path.join(BACKUP_ROOT, `${relContentFile}.bak.issue-${issueNum}`);
await fs.mkdir(path.dirname(bakPath), { recursive: true });
if (!(await fileExists(bakPath))) {
await fs.writeFile(bakPath, original, "utf-8");
await fs.writeFile(bakPath, originalRaw, "utf-8");
}
await fs.writeFile(contentFile, updated, "utf-8");
await fs.writeFile(contentFile, updatedRaw, "utf-8");
console.log("✅ Applied.");
let aliasChanged = false;
@@ -677,13 +787,13 @@ async function main() {
if (aliasChanged) {
console.log(`✅ Alias ajouté: ${chemin} ${ancre} -> ${newId}`);
// MàJ dist sans rebuild complet (inject seulement)
run("node", ["scripts/inject-anchor-aliases.mjs"], { cwd: CWD });
} else {
console.log(` Alias déjà présent ou inutile (${ancre} -> ${newId}).`);
}
// garde-fous rapides
run("node", ["scripts/check-anchor-aliases.mjs"], { cwd: CWD });
run("node", ["scripts/verify-anchor-aliases-in-dist.mjs"], { cwd: CWD });
run("npm", ["run", "test:anchors"], { cwd: CWD });
run("node", ["scripts/check-inline-js.mjs"], { cwd: CWD });
}
@@ -713,7 +823,6 @@ async function main() {
return;
}
// mode manuel
console.log("Next (manuel) :");
console.log(` git diff -- ${path.relative(CWD, contentFile)}`);
console.log(
@@ -730,4 +839,4 @@ async function main() {
main().catch((e) => {
console.error("💥", e?.message || e);
process.exit(1);
});
});

72
scripts/audit-docx-source.py Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import sys
import unicodedata
import xml.etree.ElementTree as ET
from zipfile import ZipFile
NS = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
FORBIDDEN = [
"coviabilité",
"sacroinstitutionnelle",
"technologistique",
"scripturonormative",
"textesrepères",
"ellemême",
"opérateur de darchicration",
"systèmes plusieurs statuts",
"celle-ci se donne à voir",
"Pour autant il serait",
"Telles peuvent être le cas de",
"la co-viabilité devient ,",
]
def norm(s: str) -> str:
return unicodedata.normalize("NFC", s or "")
def main() -> int:
parser = argparse.ArgumentParser(description="Audit simple dun DOCX source officiel.")
parser.add_argument("docx", help="Chemin du fichier .docx")
args = parser.parse_args()
try:
with ZipFile(args.docx) as zf:
data = zf.read("word/document.xml")
except FileNotFoundError:
print(f"ECHEC: fichier introuvable: {args.docx}", file=sys.stderr)
return 2
except KeyError:
print("ECHEC: word/document.xml introuvable dans le DOCX.", file=sys.stderr)
return 2
except Exception as e:
print(f"ECHEC: impossible douvrir le DOCX: {e}", file=sys.stderr)
return 2
root = ET.fromstring(data)
found = False
for i, p in enumerate(root.findall(".//w:p", NS), start=1):
txt = "".join(t.text or "" for t in p.findall(".//w:t", NS))
txt_n = norm(txt)
hits = [needle for needle in FORBIDDEN if needle in txt_n]
if hits:
found = True
print(f"\n[paragraphe {i}]")
print("Hits :", ", ".join(hits))
print(txt_n)
if found:
print("\nECHEC: formes interdites encore présentes dans le DOCX.")
return 1
print("OK: aucune forme interdite trouvée dans le DOCX.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -74,7 +74,24 @@ function loadAllowMissing() {
return new Set(arr.map(String));
}
function loadAcceptedResets() {
const p = path.resolve("config/anchor-churn-allowlist.json");
if (!fssync.existsSync(p)) return {};
const raw = fssync.readFileSync(p, "utf8").trim();
if (!raw) return {};
const data = JSON.parse(raw);
if (!data || typeof data !== "object" || Array.isArray(data)) {
throw new Error("anchor-churn-allowlist.json must be an object");
}
const accepted = data.accepted_resets || {};
if (!accepted || typeof accepted !== "object" || Array.isArray(accepted)) {
throw new Error("anchor-churn-allowlist.json: accepted_resets must be an object");
}
return accepted;
}
const ALLOW_MISSING = loadAllowMissing();
const ACCEPTED_RESETS = loadAcceptedResets();
async function buildSnapshot() {
const absDist = path.resolve(DIST_DIR);
@@ -139,6 +156,7 @@ function diffPage(prevIds, curIds) {
let failed = false;
let changedPages = 0;
let acceptedPages = 0;
for (const p of pages) {
const prevIds = base[p] || null;
@@ -172,6 +190,7 @@ function diffPage(prevIds, curIds) {
const prevN = prevIds.length || 1;
const churn = (added.length + removed.length) / prevN;
const removedRatio = removed.length / prevN;
const acceptedReason = ACCEPTED_RESETS[p] || null;
console.log(
`~ ${p} prev=${prevIds.length} now=${curIds.length}` +
@@ -182,11 +201,23 @@ function diffPage(prevIds, curIds) {
console.log(` removed: ${removed.slice(0, 20).join(", ")}${removed.length > 20 ? " …" : ""}`);
}
if (prevIds.length >= MIN_PREV && churn > THRESHOLD) failed = true;
if (prevIds.length >= MIN_PREV && removedRatio > THRESHOLD) failed = true;
const exceeds =
(prevIds.length >= MIN_PREV && churn > THRESHOLD) ||
(prevIds.length >= MIN_PREV && removedRatio > THRESHOLD);
if (exceeds && acceptedReason) {
acceptedPages += 1;
console.log(` ✅ accepted reset: ${acceptedReason}`);
continue;
}
if (exceeds) failed = true;
}
console.log(`\nSummary: pages compared=${pages.length}, pages changed=${changedPages}`);
console.log(
`\nSummary: pages compared=${pages.length}, pages changed=${changedPages}, accepted resets=${acceptedPages}`
);
if (failed) {
console.error(`FAIL: anchor churn above threshold (threshold=${pct(THRESHOLD)} minPrev=${MIN_PREV})`);
process.exit(1);

241
scripts/convert_docx_to_mdx.py Executable file
View File

@@ -0,0 +1,241 @@
#!/usr/bin/env python3
import argparse
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
try:
import yaml
except ImportError:
print("Erreur : PyYAML n'est pas installé. Lance : pip3 install pyyaml")
sys.exit(1)
EDITION = "archicrat-ia"
STATUS = "essai_these"
VERSION = "0.1.0"
ORDER_MAP = {
"prologue": 10,
"chapitre-1": 20,
"chapitre-2": 30,
"chapitre-3": 40,
"chapitre-4": 50,
"chapitre-5": 60,
"conclusion": 70,
}
TITLE_MAP = {
"prologue": "Prologue — Fondation, finalité sociopolitique et historique",
"chapitre-1": "Chapitre 1 — Fondements épistémologiques et modélisation",
"chapitre-2": "Chapitre 2 — Archéogenèse des régimes de co-viabilité",
"chapitre-3": "Chapitre 3 — Philosophies du pouvoir et archicration",
"chapitre-4": "Chapitre 4 — Histoire archicratique des révolutions industrielles",
"chapitre-5": "Chapitre 5 — Tensions, co-viabilités et régulations",
"conclusion": "Conclusion — ArchiCraT-IA",
}
def slugify_name(path: Path) -> str:
stem = path.stem.lower().strip()
replacements = {
" ": "-",
"_": "-",
"": "-",
"": "-",
"é": "e",
"è": "e",
"ê": "e",
"ë": "e",
"à": "a",
"â": "a",
"ä": "a",
"î": "i",
"ï": "i",
"ô": "o",
"ö": "o",
"ù": "u",
"û": "u",
"ü": "u",
"ç": "c",
"'": "",
"": "",
}
for old, new in replacements.items():
stem = stem.replace(old, new)
stem = re.sub(r"-+", "-", stem).strip("-")
# normalisations spécifiques
stem = stem.replace("chapitre-1-fondements-epistemologiques-et-modelisation-archicratie-version-officielle-revise", "chapitre-1")
stem = stem.replace("chapitre-2", "chapitre-2")
stem = stem.replace("chapitre-3", "chapitre-3")
stem = stem.replace("chapitre-4", "chapitre-4")
stem = stem.replace("chapitre-5", "chapitre-5")
if "prologue" in stem:
return "prologue"
if "chapitre-1" in stem:
return "chapitre-1"
if "chapitre-2" in stem:
return "chapitre-2"
if "chapitre-3" in stem:
return "chapitre-3"
if "chapitre-4" in stem:
return "chapitre-4"
if "chapitre-5" in stem:
return "chapitre-5"
if "conclusion" in stem:
return "conclusion"
return stem
def extract_title_from_markdown(md_text: str) -> str | None:
for line in md_text.splitlines():
line = line.strip()
if not line:
continue
if line.startswith("# "):
return line[2:].strip()
return None
def remove_first_h1(md_text: str) -> str:
lines = md_text.splitlines()
out = []
removed = False
for line in lines:
if not removed and line.strip().startswith("# "):
removed = True
continue
out.append(line)
text = "\n".join(out).lstrip()
return text
def clean_markdown(md_text: str) -> str:
text = md_text.replace("\r\n", "\n").replace("\r", "\n")
# nettoyer espaces multiples
text = re.sub(r"\n{3,}", "\n\n", text)
# supprimer éventuels signets/artefacts de liens internes Pandoc
text = re.sub(r"\[\]\(#.*?\)", "", text)
# convertir astérismes parasites
text = re.sub(r"[ \t]+$", "", text, flags=re.MULTILINE)
return text.strip() + "\n"
def compute_level(slug: str) -> int:
if slug == "prologue":
return 1
if slug.startswith("chapitre-"):
return 1
if slug == "conclusion":
return 1
return 1
def convert_one_file(input_docx: Path, output_dir: Path, source_root: Path):
slug = slugify_name(input_docx)
output_mdx = output_dir / f"{slug}.mdx"
cmd = [
"pandoc",
str(input_docx),
"-f",
"docx",
"-t",
"gfm+smart",
]
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
md_text = result.stdout
detected_title = extract_title_from_markdown(md_text)
md_body = remove_first_h1(md_text)
md_body = clean_markdown(md_body)
title = TITLE_MAP.get(slug) or detected_title or input_docx.stem
order = ORDER_MAP.get(slug, 999)
level = compute_level(slug)
relative_source = input_docx
try:
relative_source = input_docx.relative_to(source_root)
except ValueError:
relative_source = input_docx.name
frontmatter = {
"title": title,
"edition": EDITION,
"status": STATUS,
"level": level,
"version": VERSION,
"concepts": [],
"links": [],
"order": order,
"summary": "",
"source": {
"kind": "docx",
"path": str(relative_source),
},
}
yaml_block = yaml.safe_dump(
frontmatter,
allow_unicode=True,
sort_keys=False,
default_flow_style=False,
).strip()
final_text = f"---\n{yaml_block}\n---\n{md_body if md_body.startswith(chr(10)) else chr(10) + md_body}"
output_mdx.write_text(final_text, encoding="utf-8")
print(f"{input_docx.name} -> {output_mdx.name}")
def main():
parser = argparse.ArgumentParser(description="Convertit un dossier DOCX en MDX avec frontmatter.")
parser.add_argument("input_dir", help="Dossier source contenant les DOCX")
parser.add_argument("output_dir", help="Dossier de sortie pour les MDX")
args = parser.parse_args()
input_dir = Path(args.input_dir).expanduser().resolve()
output_dir = Path(args.output_dir).expanduser().resolve()
if not shutil.which("pandoc"):
print("Erreur : pandoc n'est pas installé. Lance : brew install pandoc")
sys.exit(1)
if not input_dir.exists() or not input_dir.is_dir():
print(f"Erreur : dossier source introuvable : {input_dir}")
sys.exit(1)
output_dir.mkdir(parents=True, exist_ok=True)
docx_files = sorted(input_dir.glob("*.docx"))
if not docx_files:
print(f"Aucun DOCX trouvé dans : {input_dir}")
sys.exit(1)
for docx_file in docx_files:
convert_one_file(docx_file, output_dir, input_dir)
print()
print("Conversion DOCX -> MDX terminée.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,304 @@
#!/usr/bin/env python3
import argparse
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
import zipfile
try:
import yaml
except ImportError:
print("Erreur : PyYAML n'est pas installé. Lance : pip3 install pyyaml")
sys.exit(1)
try:
from docx import Document
except ImportError:
print("Erreur : python-docx n'est pas installé. Lance : pip3 install python-docx")
sys.exit(1)
def split_frontmatter(text: str):
if not text.startswith("---\n"):
return {}, text
match = re.match(r"^---\n(.*?)\n---\n(.*)$", text, flags=re.DOTALL)
if not match:
return {}, text
yaml_block = match.group(1)
body = match.group(2)
try:
metadata = yaml.safe_load(yaml_block) or {}
except Exception as e:
print(f"Avertissement : frontmatter YAML illisible : {e}")
metadata = {}
return metadata, body
def strip_mdx_artifacts(text: str):
# imports / exports MDX
text = re.sub(r"^\s*(import|export)\s+.+?$", "", text, flags=re.MULTILINE)
# composants autofermants : <Component />
text = re.sub(r"<[A-Z][A-Za-z0-9._-]*\b[^>]*\/>", "", text)
# composants bloc : <Component ...>...</Component>
text = re.sub(
r"<([A-Z][A-Za-z0-9._-]*)\b[^>]*>.*?</\1>",
"",
text,
flags=re.DOTALL,
)
# accolades seules résiduelles sur ligne
text = re.sub(r"^\s*{\s*}\s*$", "", text, flags=re.MULTILINE)
# lignes vides multiples
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip() + "\n"
def inject_h1_from_title(metadata: dict, body: str):
title = metadata.get("title", "")
if not title:
return body
if re.match(r"^\s*#\s+", body):
return body
return f"# {title}\n\n{body.lstrip()}"
def find_style_by_candidates(doc, candidates):
# Cherche d'abord par nom visible
for style in doc.styles:
for candidate in candidates:
if style.name == candidate:
return style
# Puis par style_id Word interne
for style in doc.styles:
style_id = getattr(style, "style_id", "")
if style_id in {"BodyText", "Heading1", "Heading2", "Heading3", "Heading4"}:
for candidate in candidates:
if candidate in {"Body Text", "Corps de texte"} and style_id == "BodyText":
return style
if candidate in {"Heading 1", "Titre 1"} and style_id == "Heading1":
return style
if candidate in {"Heading 2", "Titre 2"} and style_id == "Heading2":
return style
if candidate in {"Heading 3", "Titre 3"} and style_id == "Heading3":
return style
if candidate in {"Heading 4", "Titre 4"} and style_id == "Heading4":
return style
return None
def strip_leading_paragraph_numbers(text: str):
"""
Supprime les numéros de paragraphe du type :
2. Texte...
11. Texte...
101. Texte...
sans toucher aux titres Markdown (#, ##, ###).
"""
fixed_lines = []
for line in text.splitlines():
stripped = line.lstrip()
# Ne jamais toucher aux titres Markdown
if stripped.startswith("#"):
fixed_lines.append(line)
continue
# Supprime un numéro de paragraphe en début de ligne
line = re.sub(r"^\s*\d+\.\s+", "", line)
fixed_lines.append(line)
return "\n".join(fixed_lines) + "\n"
def normalize_non_heading_paragraphs(docx_path: Path):
"""
Force tous les paragraphes non-titres en Body Text / Corps de texte.
On laisse intacts les Heading 1-4.
"""
doc = Document(str(docx_path))
body_style = find_style_by_candidates(doc, ["Body Text", "Corps de texte"])
if body_style is None:
print(f"Avertissement : style 'Body Text / Corps de texte' introuvable dans {docx_path.name}")
return
heading_names = {
"Heading 1", "Heading 2", "Heading 3", "Heading 4",
"Titre 1", "Titre 2", "Titre 3", "Titre 4",
}
heading_ids = {"Heading1", "Heading2", "Heading3", "Heading4"}
changed = 0
for para in doc.paragraphs:
text = para.text.strip()
if not text:
continue
current_style = para.style
current_name = current_style.name if current_style else ""
current_id = getattr(current_style, "style_id", "") if current_style else ""
if current_name in heading_names or current_id in heading_ids:
continue
# Tout le reste passe en Body Text
para.style = body_style
changed += 1
doc.save(str(docx_path))
print(f" ↳ normalisation styles : {changed} paragraphe(s) mis en 'Body Text / Corps de texte'")
def remove_word_bookmarks(docx_path: Path):
"""
Supprime les bookmarks Word (signets) du DOCX.
Ce sont eux qui apparaissent comme crochets gris dans LibreOffice/Word
quand l'affichage des signets est activé.
"""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
# Dézipper le docx
with zipfile.ZipFile(docx_path, "r") as zin:
zin.extractall(tmpdir)
xml_targets = [
tmpdir / "word" / "document.xml",
tmpdir / "word" / "footnotes.xml",
tmpdir / "word" / "endnotes.xml",
tmpdir / "word" / "comments.xml",
]
removed = 0
for xml_file in xml_targets:
if not xml_file.exists():
continue
text = xml_file.read_text(encoding="utf-8")
# enlever <w:bookmarkStart .../> et <w:bookmarkEnd .../>
text, c1 = re.subn(r"<w:bookmarkStart\b[^>]*/>", "", text)
text, c2 = re.subn(r"<w:bookmarkEnd\b[^>]*/>", "", text)
removed += c1 + c2
xml_file.write_text(text, encoding="utf-8")
# Rezipper
tmp_output = docx_path.with_suffix(".cleaned.docx")
with zipfile.ZipFile(tmp_output, "w", zipfile.ZIP_DEFLATED) as zout:
for file in tmpdir.rglob("*"):
if file.is_file():
zout.write(file, file.relative_to(tmpdir))
tmp_output.replace(docx_path)
print(f" ↳ suppression signets : {removed} balise(s) supprimée(s)")
def convert_one_file(input_path: Path, output_path: Path, reference_doc: Path | None):
raw = input_path.read_text(encoding="utf-8")
metadata, body = split_frontmatter(raw)
body = strip_mdx_artifacts(body)
body = strip_leading_paragraph_numbers(body)
body = inject_h1_from_title(metadata, body)
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as tmp:
tmp.write(body)
tmp_md = Path(tmp.name)
cmd = [
"pandoc",
str(tmp_md),
"-f",
"markdown",
"-o",
str(output_path),
]
if reference_doc:
cmd.extend(["--reference-doc", str(reference_doc)])
try:
subprocess.run(cmd, check=True)
finally:
try:
tmp_md.unlink()
except FileNotFoundError:
pass
normalize_non_heading_paragraphs(output_path)
remove_word_bookmarks(output_path)
def main():
parser = argparse.ArgumentParser(
description="Convertit des fichiers MDX en DOCX en conservant H1/H2/H3/H4 et en forçant le corps en Body Text."
)
parser.add_argument("input_dir", help="Dossier contenant les .mdx")
parser.add_argument(
"--output-dir",
default=str(Path.home() / "Desktop" / "archicrat-ia-docx"),
help="Dossier de sortie DOCX"
)
parser.add_argument(
"--reference-doc",
default=None,
help="DOCX modèle Word à utiliser comme reference-doc"
)
args = parser.parse_args()
input_dir = Path(args.input_dir)
output_dir = Path(args.output_dir)
reference_doc = Path(args.reference_doc) if args.reference_doc else None
if not shutil.which("pandoc"):
print("Erreur : pandoc n'est pas installé. Installe-le avec : brew install pandoc")
sys.exit(1)
if not input_dir.exists() or not input_dir.is_dir():
print(f"Erreur : dossier introuvable : {input_dir}")
sys.exit(1)
if reference_doc and not reference_doc.exists():
print(f"Erreur : reference-doc introuvable : {reference_doc}")
sys.exit(1)
output_dir.mkdir(parents=True, exist_ok=True)
mdx_files = sorted(input_dir.glob("*.mdx"))
if not mdx_files:
print(f"Aucun fichier .mdx trouvé dans : {input_dir}")
sys.exit(1)
print(f"Conversion de {len(mdx_files)} fichier(s)...")
print(f"Entrée : {input_dir}")
print(f"Sortie : {output_dir}")
if reference_doc:
print(f"Modèle : {reference_doc}")
print()
for mdx_file in mdx_files:
docx_name = mdx_file.with_suffix(".docx").name
out_file = output_dir / docx_name
print(f"{mdx_file.name} -> {docx_name}")
convert_one_file(mdx_file, out_file, reference_doc)
print()
print("✅ Conversion terminée.")
if __name__ == "__main__":
main()

132
scripts/fix-docx-source.py Executable file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import shutil
import tempfile
import unicodedata
import xml.etree.ElementTree as ET
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
XML_NS = "http://www.w3.org/XML/1998/namespace"
NS = {"w": W_NS}
ET.register_namespace("w", W_NS)
REPLACEMENTS = {
"coviabilité": "co-viabilité",
"sacroinstitutionnelle": "sacro-institutionnelle",
"technologistique": "techno-logistique",
"scripturonormative": "scripturo-normative",
"textesrepères": "textes-repères",
"ellemême": "elle-même",
"opérateur de darchicration": "opérateur darchicration",
"systèmes plusieurs statuts": "systèmes à plusieurs statuts",
"celle-ci se donne à voir": "Celle-ci se donne à voir",
"Pour autant il serait": "Pour autant, il serait",
"Telles peuvent être le cas de": "Tels peuvent être les cas de",
}
# volontairement NON auto-corrigé : "la co-viabilité devient ,"
# ce cas demande une décision éditoriale humaine.
def qn(tag: str) -> str:
prefix, local = tag.split(":")
if prefix != "w":
raise ValueError(tag)
return f"{{{W_NS}}}{local}"
def norm(s: str) -> str:
return unicodedata.normalize("NFC", s or "")
def paragraph_text(p: ET.Element) -> str:
return "".join(t.text or "" for t in p.findall(".//w:t", NS))
def replaced_text(s: str) -> str:
out = norm(s)
for bad, good in REPLACEMENTS.items():
out = out.replace(bad, good)
return out
def rewrite_paragraph_text(p: ET.Element, new_text: str) -> None:
ppr = p.find("w:pPr", NS)
for child in list(p):
if ppr is not None and child is ppr:
continue
p.remove(child)
r = ET.Element(qn("w:r"))
t = ET.SubElement(r, qn("w:t"))
t.set(f"{{{XML_NS}}}space", "preserve")
t.text = new_text
p.append(r)
def process_document_xml(xml_path: Path) -> int:
tree = ET.parse(xml_path)
root = tree.getroot()
changed = 0
for p in root.findall(".//w:p", NS):
old = paragraph_text(p)
new = replaced_text(old)
if new != old:
rewrite_paragraph_text(p, new)
changed += 1
tree.write(xml_path, encoding="utf-8", xml_declaration=True)
return changed
def repack_docx(tmpdir: Path, out_docx: Path) -> None:
tmp_out = out_docx.with_suffix(out_docx.suffix + ".tmp")
with ZipFile(tmp_out, "w", ZIP_DEFLATED) as zf:
for p in sorted(tmpdir.rglob("*")):
if p.is_file():
zf.write(p, p.relative_to(tmpdir))
shutil.move(tmp_out, out_docx)
def main() -> int:
parser = argparse.ArgumentParser(description="Répare mécaniquement certaines scories DOCX.")
parser.add_argument("docx", help="Chemin du DOCX")
parser.add_argument("--in-place", action="store_true", help="Réécrit le DOCX en place")
args = parser.parse_args()
src = Path(args.docx)
if not src.exists():
print(f"ECHEC: fichier introuvable: {src}", file=sys.stderr)
return 2
out = src if args.in_place else src.with_name(src.stem + ".fixed.docx")
with tempfile.TemporaryDirectory(prefix="docx-fix-") as td:
td_path = Path(td)
with ZipFile(src) as zf:
zf.extractall(td_path)
document_xml = td_path / "word" / "document.xml"
if not document_xml.exists():
print("ECHEC: word/document.xml absent.", file=sys.stderr)
return 2
changed = process_document_xml(document_xml)
repack_docx(td_path, out)
print(f"OK: DOCX réparé par réécriture paragraphe/XML. Paragraphes modifiés: {changed}")
return 0
if __name__ == "__main__":
import sys
raise SystemExit(main())

View File

@@ -205,12 +205,28 @@ async function main() {
const manifestPath = path.resolve(args.manifest);
const items = await readManifest(manifestPath);
const selected = args.all ? items : items.filter(it => args.only.includes(it.slug));
const selected = args.all
? items
: items.filter((it) => {
const rawSlug = String(it.slug || "").trim();
const rawCollection = String(it.collection || "").trim();
const qualified = `${rawCollection}/${rawSlug}`;
return args.only.includes(rawSlug) || args.only.includes(qualified);
});
if (!args.all && selected.length !== args.only.length) {
const found = new Set(selected.map(s => s.slug));
const missing = args.only.filter(s => !found.has(s));
throw new Error(`Some --only slugs not found in manifest: ${missing.join(", ")}`);
if (!args.all) {
const found = new Set(
selected.flatMap((s) => {
const rawSlug = String(s.slug || "").trim();
const rawCollection = String(s.collection || "").trim();
return [rawSlug, `${rawCollection}/${rawSlug}`];
})
);
const missing = args.only.filter((s) => !found.has(s));
if (missing.length > 0) {
throw new Error(`Some --only slugs not found in manifest: ${missing.join(", ")}`);
}
}
const pandocOk = havePandoc();
@@ -267,15 +283,30 @@ async function main() {
// ✅ IMPORTANT: archicrat-ia partage edition/status avec archicratie (pas de migration frontmatter)
const schemaDefaultsByCollection = {
archicratie: { edition: "archicratie", status: "modele_sociopolitique", level: 1 },
"archicrat-ia": { edition: "archicrat-ia", status: "essai_these", level: 1 },
ia: { edition: "ia", status: "cas_pratique", level: 1 },
traite: { edition: "traite", status: "ontodynamique", level: 1 },
glossaire: { edition: "glossaire", status: "lexique", level: 1 },
atlas: { edition: "atlas", status: "atlas", level: 1 },
archicratie: { edition: "archicratie", status: "modele_sociopolitique", level: 1 },
"archicrat-ia": { edition: "archicrat-ia", status: "essai_these", level: 1 },
"cas-ia": { edition: "cas-ia", status: "application", level: 1 },
traite: { edition: "traite", status: "ontodynamique", level: 1 },
glossaire: { edition: "glossaire", status: "lexique", level: 1 },
atlas: { edition: "atlas", status: "atlas", level: 1 },
};
const defaults = schemaDefaultsByCollection[outCollection] || { edition: outCollection, status: "draft", level: 1 };
// Compat legacy :
// manifest collection="archicratie" + slug="archicrat-ia/..."
// => on écrit bien dans src/content/archicrat-ia/...
// => mais on conserve edition/status historiques de type archicratie/modele_sociopolitique
const defaultsKey =
String(it.collection || "").trim() === "archicratie" &&
String(it.slug || "").trim().startsWith("archicrat-ia/")
? "archicratie"
: outCollection;
const defaults =
schemaDefaultsByCollection[defaultsKey] || {
edition: defaultsKey,
status: "draft",
level: 1,
};
const fm = [
"---",

View File

@@ -0,0 +1,241 @@
#!/usr/bin/env node
import process from "node:process";
function getEnv(name, fallback = "") {
return String(process.env[name] ?? fallback).trim();
}
function sh(value) {
return JSON.stringify(String(value ?? ""));
}
function escapeRegExp(s) {
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function pickLine(body, key) {
const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*:\\s*([^\\n\\r]+)`, "mi");
const m = String(body || "").match(re);
return m ? m[1].trim() : "";
}
function pickHeadingValue(body, headingKey) {
const re = new RegExp(
`^##\\s*${escapeRegExp(headingKey)}[^\\n]*\\n([\\s\\S]*?)(?=\\n##\\s|\\n\\s*$)`,
"mi"
);
const m = String(body || "").match(re);
if (!m) return "";
const lines = m[1].split(/\r?\n/).map((l) => l.trim());
for (const l of lines) {
if (!l) continue;
if (l.startsWith("<!--")) continue;
return l.replace(/^\/?/, "/").trim();
}
return "";
}
function normalizeChemin(chemin) {
let c = String(chemin || "").trim();
if (!c) return "";
if (!c.startsWith("/")) c = "/" + c;
if (!c.endsWith("/")) c += "/";
return c;
}
function extractCheminFromAnyUrl(text) {
const s = String(text || "");
const m = s.match(/(\/[a-z0-9\-]+\/[a-z0-9\-\/]+\/)#p-\d+-[0-9a-f]{8}/i);
return m ? m[1] : "";
}
function inferType(issue) {
const title = String(issue?.title || "");
const body = String(issue?.body || "").replace(/\r\n/g, "\n");
const fromBody = String(pickLine(body, "Type") || "").trim().toLowerCase();
if (fromBody) return fromBody;
if (title.startsWith("[Correction]")) return "type/correction";
if (title.startsWith("[Fact-check]") || title.startsWith("[Vérification]")) return "type/fact-check";
return "";
}
function inferChemin(issue) {
const title = String(issue?.title || "");
const body = String(issue?.body || "").replace(/\r\n/g, "\n");
return normalizeChemin(
pickLine(body, "Chemin") ||
pickHeadingValue(body, "Chemin") ||
extractCheminFromAnyUrl(body) ||
extractCheminFromAnyUrl(title)
);
}
function labelsOf(issue) {
return Array.isArray(issue?.labels)
? issue.labels.map((l) => String(l?.name || "")).filter(Boolean)
: [];
}
function issueNumber(issue) {
return Number(issue?.number || issue?.index || 0);
}
function parseMeta(issue) {
const labels = labelsOf(issue);
const type = inferType(issue);
const chemin = inferChemin(issue);
const number = issueNumber(issue);
const hasApproved = labels.includes("state/approved");
const hasRejected = labels.includes("state/rejected");
const isProposer = type === "type/correction" || type === "type/fact-check";
const isOpen = String(issue?.state || "open") === "open";
const isPR = Boolean(issue?.pull_request);
const eligible =
number > 0 &&
isOpen &&
!isPR &&
hasApproved &&
!hasRejected &&
isProposer &&
Boolean(chemin);
return {
issue,
number,
type,
chemin,
labels,
hasApproved,
hasRejected,
eligible,
};
}
async function fetchJson(url, token) {
const res = await fetch(url, {
headers: {
Authorization: `token ${token}`,
Accept: "application/json",
"User-Agent": "archicratie-pick-proposer-issue/1.0",
},
});
if (!res.ok) {
const t = await res.text().catch(() => "");
throw new Error(`HTTP ${res.status} ${url}\n${t}`);
}
return await res.json();
}
async function fetchIssue(apiBase, owner, repo, token, n) {
const url = `${apiBase}/api/v1/repos/${owner}/${repo}/issues/${n}`;
return await fetchJson(url, token);
}
async function listOpenIssues(apiBase, owner, repo, token) {
const out = [];
let page = 1;
const limit = 100;
while (true) {
const url = `${apiBase}/api/v1/repos/${owner}/${repo}/issues?state=open&page=${page}&limit=${limit}`;
const batch = await fetchJson(url, token);
if (!Array.isArray(batch) || batch.length === 0) break;
out.push(...batch);
if (batch.length < limit) break;
page += 1;
}
return out;
}
function emitNone(reason) {
process.stdout.write(
[
`TARGET_FOUND="0"`,
`TARGET_REASON=${sh(reason)}`,
`TARGET_PRIMARY_ISSUE=""`,
`TARGET_ISSUES=""`,
`TARGET_COUNT="0"`,
`TARGET_CHEMIN=""`,
].join("\n") + "\n"
);
}
async function main() {
const token = getEnv("FORGE_TOKEN");
const owner = getEnv("GITEA_OWNER");
const repo = getEnv("GITEA_REPO");
const apiBase = (getEnv("FORGE_API") || getEnv("FORGE_BASE")).replace(/\/+$/, "");
const explicit = Number(process.argv[2] || 0);
if (!token) throw new Error("Missing FORGE_TOKEN");
if (!owner || !repo) throw new Error("Missing GITEA_OWNER / GITEA_REPO");
if (!apiBase) throw new Error("Missing FORGE_API / FORGE_BASE");
let metas = [];
if (explicit > 0) {
const issue = await fetchIssue(apiBase, owner, repo, token, explicit);
const meta = parseMeta(issue);
if (!meta.eligible) {
emitNone(
!meta.hasApproved
? "explicit_issue_not_approved"
: meta.hasRejected
? "explicit_issue_rejected"
: !meta.type
? "explicit_issue_missing_type"
: !meta.chemin
? "explicit_issue_missing_chemin"
: "explicit_issue_not_eligible"
);
return;
}
const openIssues = await listOpenIssues(apiBase, owner, repo, token);
metas = openIssues.map(parseMeta).filter((m) => m.eligible && m.chemin === meta.chemin);
} else {
const openIssues = await listOpenIssues(apiBase, owner, repo, token);
metas = openIssues.map(parseMeta).filter((m) => m.eligible);
if (metas.length === 0) {
emitNone("no_open_approved_proposer_issue");
return;
}
metas.sort((a, b) => a.number - b.number);
const first = metas[0];
metas = metas.filter((m) => m.chemin === first.chemin);
}
metas.sort((a, b) => a.number - b.number);
if (metas.length === 0) {
emitNone("no_batch_for_path");
return;
}
const primary = metas[0];
const issues = metas.map((m) => String(m.number));
process.stdout.write(
[
`TARGET_FOUND="1"`,
`TARGET_REASON="ok"`,
`TARGET_PRIMARY_ISSUE=${sh(primary.number)}`,
`TARGET_ISSUES=${sh(issues.join(" "))}`,
`TARGET_COUNT=${sh(issues.length)}`,
`TARGET_CHEMIN=${sh(primary.chemin)}`,
].join("\n") + "\n"
);
}
main().catch((e) => {
console.error("💥 pick-proposer-issue:", e?.message || e);
process.exit(1);
});

29
scripts/refresh-chapter2.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
DOCX="sources/docx/archicrat-ia/Chapitre_2Archeogenese_des_regimes_de_co-viabilite-version_officielle.docx"
MANIFEST="sources/manifest.yml"
ONLY="archicrat-ia/chapitre-2"
echo "== Audit source avant fix =="
if ! python3 scripts/audit-docx-source.py "$DOCX"; then
echo
echo "== Fix source =="
python3 scripts/fix-docx-source.py --in-place "$DOCX"
echo
echo "== Audit source après fix =="
python3 scripts/audit-docx-source.py "$DOCX"
fi
echo
echo "== Réimport =="
node scripts/import-docx.mjs --manifest "$MANIFEST" --only "$ONLY" --force
echo
echo "== Build =="
npm run build
echo
echo "== Tests =="
npm test

View File

@@ -0,0 +1,20 @@
import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const outDir = path.join(root, "public", "__ops");
const outFile = path.join(outDir, "health.json");
const payload = {
service: "archicratie-site",
env: process.env.PUBLIC_OPS_ENV || "unknown",
upstream: process.env.PUBLIC_OPS_UPSTREAM || "unknown",
buildSha: process.env.PUBLIC_BUILD_SHA || "unknown",
builtAt: process.env.PUBLIC_BUILD_TIME || new Date().toISOString(),
};
fs.mkdirSync(outDir, { recursive: true });
fs.writeFileSync(outFile, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
console.log(`✅ ops health written: ${outFile}`);
console.log(payload);

Binary file not shown.

View File

@@ -1,161 +1,123 @@
version: 1
docs:
# =========================
# Document dentrée
# =========================
- source: sources/docx/commencer/document-de-presentation.docx
collection: commencer
slug: document-de-presentation
title: "Document de présentation"
order: 0
# =========================
# Archicratie — Essai-thèse "ArchiCraT-IA"
# =========================
- source: sources/docx/archicrat-ia/Prologue—Archicratie-fondation_et_finalite_sociopolitique_et_historique-version_officielle.docx
collection: archicratie
slug: archicrat-ia/prologue
title: "Prologue — Fondation et finalité sociopolitique et historique"
collection: archicrat-ia
slug: prologue
title: "Prologue — Fondation, finalité sociopolitique et historique"
order: 10
- source: sources/docx/archicrat-ia/Chapitre_1—Fondements_epistemologiques_et_modelisation_Archicratie-version_officielle.docx
collection: archicratie
slug: archicrat-ia/chapitre-1
collection: archicrat-ia
slug: chapitre-1
title: "Chapitre 1 — Fondements épistémologiques et modélisation"
order: 20
- source: sources/docx/archicrat-ia/Chapitre_2Archeogenese_des_regimes_de_co-viabilite-version_officielle.docx
collection: archicratie
slug: archicrat-ia/chapitre-2
collection: archicrat-ia
slug: chapitre-2
title: "Chapitre 2 — Archéogenèse des régimes de co-viabilité"
order: 30
- source: sources/docx/archicrat-ia/Chapitre_3—Philosophies_du_pouvoir_et_Archicration-pour_une_topologie_differenciee_des_regimes_regulateurs-version_officielle.docx
collection: archicratie
slug: archicrat-ia/chapitre-3
collection: archicrat-ia
slug: chapitre-3
title: "Chapitre 3 — Philosophies du pouvoir et archicration"
order: 40
- source: sources/docx/archicrat-ia/Chapitre_4—Vers_une_histoire_archicratique_des_revolutions_industrielles-version_officielle.docx
collection: archicratie
slug: archicrat-ia/chapitre-4
collection: archicrat-ia
slug: chapitre-4
title: "Chapitre 4 — Histoire archicratique des révolutions industrielles"
order: 50
- source: sources/docx/archicrat-ia/Chapitre_5—Problematiques_des_tensions_des_co-viabilites_et_des_regulations_archicratiques-version_officielle.docx
collection: archicratie
slug: archicrat-ia/chapitre-5
collection: archicrat-ia
slug: chapitre-5
title: "Chapitre 5 — Tensions, co-viabilités et régulations"
order: 60
- source: sources/docx/archicrat-ia/Conclusion-Archicrat-IA-version_officielle.docx
collection: archicratie
slug: archicrat-ia/conclusion
collection: archicrat-ia
slug: conclusion
title: "Conclusion — ArchiCraT-IA"
order: 70
# =========================
# IA — Cas pratique (1 page = 1 chapitre)
# NOTE: on n'inclut PAS le monolithe "Cas_IA-... .docx" dans le manifeste.
# Cas pratique — Gouvernance des systèmes IA
# =========================
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Introduction_generale—Mettre_en_scene_un_systeme_IA.docx
collection: ia
slug: cas-pratique/introduction
title: "Cas pratique — Introduction générale : Mettre en scène un système IA"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Introduction.docx
collection: cas-ia
slug: introduction
title: "Introduction générale Mettre un système dIA en scène"
order: 110
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_I—Epreuve_de_detectabilite.docx
collection: ia
slug: cas-pratique/chapitre-1
title: "Cas pratique — Chapitre I : Épreuve de détectabilité"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_1_Epreuve_de_detectabilite.docx
collection: cas-ia
slug: chapitre-1
title: "Chapitre I Épreuve de détectabilité"
order: 120
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_II—Epreuve_topologique.docx
collection: ia
slug: cas-pratique/chapitre-2
title: "Cas pratique — Chapitre II : Épreuve topologique"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_2_Epreuve_Topologique.docx
collection: cas-ia
slug: chapitre-2
title: "Chapitre II Épreuve topologique"
order: 130
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_III—Epreuve_archeogenetique.docx
collection: ia
slug: cas-pratique/chapitre-3
title: "Cas pratique — Chapitre III : Épreuve archéogénétique"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_3_Epreuve_archeogenetique.docx
collection: cas-ia
slug: chapitre-3
title: "Chapitre III Épreuve archéogénétique"
order: 140
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_IV—Epreuve_morphologique.docx
collection: ia
slug: cas-pratique/chapitre-4
title: "Cas pratique — Chapitre IV : Épreuve morphologique"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_4_Epreuve_Morphologique.docx
collection: cas-ia
slug: chapitre-4
title: "Chapitre IV Épreuve morphologique"
order: 150
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_V—Epreuve_historique.docx
collection: ia
slug: cas-pratique/chapitre-5
title: "Cas pratique — Chapitre V : Épreuve historique"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_5_Epreuve_Historique.docx
collection: cas-ia
slug: chapitre-5
title: "Chapitre V Épreuve historique"
order: 160
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_VI—Epreuve_de_co-viabilite.docx
collection: ia
slug: cas-pratique/chapitre-6
title: "Cas pratique — Chapitre VI : Épreuve de co-viabilité"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_6_Epreuve_de_Co-viabilite.docx
collection: cas-ia
slug: chapitre-6
title: "Chapitre VI Épreuve de co-viabilité"
order: 170
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_VII—Gestes_archicratiques_concrets_pour_un_systeme_IA.docx
collection: ia
slug: cas-pratique/chapitre-7
title: "Cas pratique — Chapitre VII : Gestes archicratiques concrets"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_7_Gestes_archicratiques_concrets_pour_un_systeme_IA.docx
collection: cas-ia
slug: chapitre-7
title: "Chapitre VII Gestes archicratiques concrets pour un système dIA"
order: 180
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-Conclusion.docx
collection: ia
slug: cas-pratique/conclusion
title: "Cas pratique — Conclusion"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Conclusion.docx
collection: cas-ia
slug: conclusion
title: "Conclusion"
order: 190
- source: sources/docx/cas-ia/Cas_IA-Archicratie_et_gouvernance_des_systemes_IA-AnnexeGlossaire_archicratique_pour_audit_des_systemes_IA.docx
collection: ia
slug: cas-pratique/annexe-glossaire-audit
title: "Cas pratique — Annexe : Glossaire archicratique pour audit des systèmes IA"
- source: sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Annexe_Glossaire_Archicratique_Cas_IA.docx
collection: cas-ia
slug: annexe-glossaire-audit
title: "Annexe Glossaire archicratique pour laudit des systèmes dIA"
order: 195
# =========================
# Traité — Ontodynamique générative (1 page = 1 chapitre)
# NOTE: on n'inclut PAS le monolithe "Traite-...-version_officielle.docx" dans le manifeste.
# =========================
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Introduction-version_officielle.docx
collection: traite
slug: ontodynamique/introduction
title: "Traité — Introduction"
order: 210
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_1—Le_flux_ontogenetique-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-1
title: "Traité — Chapitre 1 : Le flux ontogénétique"
order: 220
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_2—economie_du_reel-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-2
title: "Traité — Chapitre 2 : Économie du réel"
order: 230
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_3—Le_reel_comme_systeme_regulateur-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-3
title: "Traité — Chapitre 3 : Le réel comme système régulateur"
order: 240
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_4—Arcalite-structures_formes_invariants-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-4
title: "Traité — Chapitre 4 : Arcalité — structures, formes, invariants"
order: 250
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_5-Cratialite-forces_flux_gradients-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-5
title: "Traité — Chapitre 5 : Cratialité — forces, flux, gradients"
order: 260
- source: sources/docx/traite/Traite-Ontodynamique_Generative-Fondements_Archicratie-Chapitre_6—Archicration-version_officielle.docx
collection: traite
slug: ontodynamique/chapitre-6
title: "Traité — Chapitre 6 : Archicration"
order: 270
# =========================
# Glossaire / Lexique
# =========================
@@ -169,4 +131,4 @@ docs:
collection: glossaire
slug: mini-glossaire-verbes
title: "Mini-glossaire des verbes de la scène archicratique"
order: 910
order: 910

View File

@@ -1,5 +1 @@
{
"/archicrat-ia/chapitre-3/": {
"p-1-60c7ea48": "p-1-a21087b0"
}
}
{}

0
src/annotations/.gitkeep Normal file
View File

View File

@@ -1,10 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-1
paras:
p-0-8d27a7f5:
refs:
- url: https://auth.archicratie.trans-hands.synology.me/authenticated
label: Lien web
kind: (livre / article / vidéo / site / autre) Site
ts: 2026-02-27T12:34:31.704Z
fromIssue: 142

View File

@@ -1,9 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-1
paras:
p-1-8a6c18bf:
comments_editorial:
- text: Yeaha
status: new
ts: 2026-02-27T12:40:39.462Z
fromIssue: 143

View File

@@ -1,18 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-3
paras:
p-0-ace27175:
media:
- type: image
src: /media/archicrat-ia/chapitre-3/p-0-ace27175/Capture_d_e_cran_2025-05-05_a_19.20.40.png
caption: "[Media] p-0-ace27175 — Chapitre 3 — Philosophies du pouvoir et
archicration"
credit: ""
ts: 2026-02-27T12:43:14.259Z
fromIssue: 144
refs:
- url: https://gitea.archicratie.trans-hands.synology.me
label: Gitea
kind: (livre / article / vidéo / site / autre) Site
ts: 2026-03-02T19:53:21.252Z
fromIssue: 169

View File

@@ -1,10 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-3
paras:
p-1-60c7ea48:
refs:
- url: https://gitea.archicratie.trans-hands.synology.me
label: Gitea
kind: (livre / article / vidéo / site / autre) Site
ts: 2026-03-02T20:01:55.858Z
fromIssue: 172#

View File

@@ -1,30 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-4
paras:
p-2-31b12529:
media:
- type: image
src: /media/archicrat-ia/chapitre-4/p-2-31b12529/Capture_d_e_cran_2026-02-16_a_13.05.58.png
caption: "[Media] p-2-31b12529 — Chapitre 4 — Histoire archicratique des
révolutions industrielles"
credit: ""
ts: 2026-02-25T18:58:32.359Z
fromIssue: 115
p-7-1da4a458:
media:
- type: image
src: /media/archicrat-ia/chapitre-4/p-7-1da4a458/Capture_d_e_cran_2026-02-16_a_13.05.58.png
caption: "[Media] p-7-1da4a458 — Chapitre 4 — Histoire archicratique des
révolutions industrielles"
credit: ""
ts: 2026-02-25T19:11:32.634Z
fromIssue: 121
p-11-67c14c09:
media:
- type: image
src: /media/archicrat-ia/chapitre-4/p-11-67c14c09/Capture_d_e_cran_2026-02-16_a_13.07.35.png
caption: "[Media] p-11-67c14c09 — Chapitre 4 — Histoire archicratique des
révolutions industrielles"
credit: ""
ts: 2026-02-26T13:17:41.286Z
fromIssue: 129

View File

@@ -1,19 +0,0 @@
schema: 1
page: archicrat-ia/chapitre-4
paras:
p-11-67c14c09:
media:
- type: image
src: /media/archicrat-ia/chapitre-4/p-11-67c14c09/Capture_d_e_cran_2026-02-16_a_13.07.35.png
caption: "[Media] p-11-67c14c09 — Chapitre 4 — Histoire archicratique des
révolutions industrielles"
credit: ""
ts: 2026-02-26T13:17:41.286Z
fromIssue: 129
- type: image
src: /media/archicrat-ia/chapitre-4/p-11-67c14c09/Capture_d_e_cran_2025-05-05_a_19.20.40.png
caption: "[Media] p-11-67c14c09 — Chapitre 4 — Histoire archicratique des
révolutions industrielles"
credit: ""
ts: 2026-02-27T09:17:04.386Z
fromIssue: 127

View File

@@ -1,50 +0,0 @@
schema: 1
paras:
p-0-d7974f88:
refs:
- label: "Happycratie — (Cabanas & Illouz) via Cairn"
url: "https://shs.cairn.info/revue-ethnologie-francaise-2019-4-page-813?lang=fr"
kind: "article"
- label: "Techno-féodalisme — Variations (OpenEdition)"
url: "https://journals.openedition.org/variations/2290"
kind: "article"
authors:
- "Eva Illouz"
- "Yanis Varoufakis"
quotes:
- text: "Dans Happycratie, Edgar Cabanas et Eva Illouz..."
source: "Happycratie, p.1"
- text: "En eux-mêmes, les actifs ne sont ni féodaux ni capitalistes..."
source: "Entretien Morozov/Varoufakis — techno-féodalisme"
media:
- type: "image"
src: "/public/media/archicratie/archicrat-ia/prologue/p-0-d7974f88/schema-1.svg"
caption: "Tableau explicatif"
credit: "ChatGPT"
- type: "image"
src: "/public/media/archicratie/archicrat-ia/prologue/p-0-d7974f88/schema-2.svg"
caption: "Diagramme dévolution"
credit: "Yanis Varoufakis"
comments_editorial:
- text: "TODO: nuancer / préciser — commentaire éditorial versionné (pas public)."
status: "draft"
p-1-2ef25f29:
refs:
- label: "Kafka et le pouvoir — Bernard Lahire (Cairn)"
url: "https://shs.cairn.info/franz-kafka--9782707159410-page-475?lang=fr"
kind: "book"
authors:
- "Bernard Lahire"
quotes:
- text: "Si lon voulait chercher quelque chose comme une vision du monde chez Kafka..."
source: "Bernard Lahire, Franz Kafka, p.475+"
comments_editorial: []

View File

@@ -1,48 +1,79 @@
---
import { getCollection } from "astro:content";
const { currentSlug } = Astro.props;
const {
currentSlug,
collection = "archicrat-ia",
basePath = "/archicrat-ia",
label = "Table des matières"
} = Astro.props;
// ✅ Après migration : TOC = collection "archicrat-ia"
const entries = (await getCollection("archicrat-ia"))
.sort((a, b) => (a.data.order ?? 0) - (b.data.order ?? 0));
const slugOf = (entry) => String(entry.id).replace(/\.(md|mdx)$/i, "");
const hrefOf = (entry) => `${basePath}/${slugOf(entry)}/`;
const href = (slug) => `/archicrat-ia/${slug}/`;
const collator = new Intl.Collator("fr", { sensitivity: "base", numeric: true });
const entries = [...await getCollection(collection)].sort((a, b) => {
const ao = Number(a.data.order ?? 9999);
const bo = Number(b.data.order ?? 9999);
if (ao !== bo) return ao - bo;
const at = String(a.data.title ?? a.data.term ?? slugOf(a));
const bt = String(b.data.title ?? b.data.term ?? slugOf(b));
return collator.compare(at, bt);
});
const tocId = `toc-global-${collection}-${String(basePath).replace(/[^\w-]+/g, "-")}`;
---
<nav class="toc-global" aria-label="Table des matières — ArchiCraT-IA">
<div class="toc-global__head">
<div class="toc-global__title">Table des matières</div>
</div>
<nav
class="toc-global"
aria-label={label}
data-toc-global
data-toc-key={`global:${collection}:${basePath}`}
>
<button
class="toc-global__head toc-global__toggle"
type="button"
aria-expanded="true"
aria-controls={tocId}
>
<span class="toc-global__title">{label}</span>
<span class="toc-global__chevron" aria-hidden="true">▾</span>
</button>
<ol class="toc-global__list">
{entries.map((e) => {
const active = e.slug === currentSlug;
return (
<li class={`toc-item ${active ? "is-active" : ""}`}>
<a class="toc-link" href={href(e.slug)} aria-current={active ? "page" : undefined}>
<span class="toc-link__row">
{active ? (
<span class="toc-active-indicator" aria-hidden="true">👉</span>
) : (
<span class="toc-active-spacer" aria-hidden="true"></span>
)}
<div class="toc-global__body-clip" id={tocId}>
<div class="toc-global__body">
<ol class="toc-global__list">
{entries.map((e) => {
const slug = slugOf(e);
const active = slug === currentSlug;
<span class="toc-link__title">{e.data.title}</span>
return (
<li class={`toc-item ${active ? "is-active" : ""}`}>
<a class="toc-link" href={hrefOf(e)} aria-current={active ? "page" : undefined}>
<span class="toc-link__row">
<span class={`toc-active-mark ${active ? "is-on" : ""}`} aria-hidden="true">
<span class="toc-active-mark__dot"></span>
</span>
{active && (
<span class="toc-badge" aria-label="Chapitre en cours">
En cours
<span class="toc-link__title">{e.data.title}</span>
{active && (
<span class="toc-badge" aria-label="Chapitre en cours">
En cours
</span>
)}
</span>
)}
</span>
{active && <span class="toc-underline" aria-hidden="true"></span>}
</a>
</li>
);
})}
</ol>
{active && <span class="toc-underline" aria-hidden="true"></span>}
</a>
</li>
);
})}
</ol>
</div>
</div>
</nav>
<style>
@@ -53,7 +84,22 @@ const href = (slug) => `/archicrat-ia/${slug}/`;
background: rgba(127,127,127,0.06);
}
.toc-global__toggle{
width: 100%;
appearance: none;
border: 0;
background: transparent;
color: inherit;
text-align: left;
padding: 0;
cursor: pointer;
}
.toc-global__head{
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed rgba(127,127,127,0.25);
@@ -66,11 +112,36 @@ const href = (slug) => `/archicrat-ia/${slug}/`;
opacity: .88;
}
.toc-global__chevron{
font-size: 12px;
opacity: .7;
transition: transform 180ms ease;
}
.toc-global__body-clip{
display: grid;
grid-template-rows: 1fr;
transition:
grid-template-rows 220ms ease,
opacity 160ms ease,
margin-top 220ms ease;
}
.toc-global__body{
min-height: 0;
overflow: hidden;
}
.toc-global__list{
list-style: none;
margin: 0;
padding: 0;
max-height: 44vh;
overflow: auto;
padding-right: 8px;
scrollbar-gutter: stable;
}
.toc-global__list li::marker{ content: ""; }
.toc-item{ margin: 6px 0; }
@@ -96,13 +167,33 @@ const href = (slug) => `/archicrat-ia/${slug}/`;
align-items: center;
}
.toc-active-indicator{
font-size: 14px;
line-height: 1;
.toc-active-mark{
width: 14px;
height: 14px;
display: inline-grid;
place-items: center;
border-radius: 999px;
border: 1px solid transparent;
opacity: .55;
}
.toc-active-spacer{
width: 14px;
.toc-active-mark__dot{
width: 5px;
height: 5px;
border-radius: 999px;
background: currentColor;
opacity: .65;
}
.toc-active-mark.is-on{
border-color: rgba(127,127,127,0.34);
opacity: 1;
}
.toc-active-mark.is-on .toc-active-mark__dot{
width: 6px;
height: 6px;
opacity: 1;
}
.toc-link__title{
@@ -140,11 +231,66 @@ const href = (slug) => `/archicrat-ia/${slug}/`;
border-radius: 999px;
}
.toc-global__list{
max-height: 44vh;
overflow: auto;
padding-right: 8px;
scrollbar-gutter: stable;
@media (max-width: 980px){
.toc-global{
padding: 10px 12px;
border-radius: 14px;
}
.toc-global__head{
margin-bottom: 0;
padding-bottom: 0;
border-bottom: 0;
min-height: 28px;
}
.toc-global__title{
font-size: 13px;
}
.toc-global__body-clip{
margin-top: 10px;
}
.toc-global.is-collapsed .toc-global__body-clip{
grid-template-rows: 0fr;
opacity: 0;
margin-top: 0;
}
.toc-global__body{
min-height: 0;
overflow: hidden;
transition: opacity 180ms ease;
}
.toc-global.is-collapsed .toc-global__body{
opacity: 0;
}
.toc-global.is-collapsed .toc-global__chevron{
transform: rotate(-90deg);
}
.toc-link{
padding: 7px 9px;
border-radius: 12px;
}
.toc-link__title{
font-size: 12.5px;
line-height: 1.22;
}
.toc-badge{
font-size: 10px;
padding: 2px 7px;
}
.toc-global__list{
max-height: min(42vh, 360px);
padding-right: 4px;
}
}
@media (prefers-color-scheme: dark){
@@ -152,12 +298,88 @@ const href = (slug) => `/archicrat-ia/${slug}/`;
.toc-link:hover{ background: rgba(255,255,255,0.06); }
.toc-item.is-active .toc-link{ background: rgba(255,255,255,0.06); }
.toc-badge{ background: rgba(255,255,255,0.06); }
.toc-active-mark.is-on{ border-color: rgba(255,255,255,0.22); }
}
</style>
<script is:inline>
(() => {
const active = document.querySelector(".toc-global .toc-item.is-active");
if (active) active.scrollIntoView({ block: "nearest" });
function init() {
document.querySelectorAll("[data-toc-global]").forEach((nav) => {
if (nav.dataset.tocReady === "1") return;
nav.dataset.tocReady = "1";
const toggle = nav.querySelector(".toc-global__toggle");
const bodyClip = nav.querySelector(".toc-global__body-clip");
const active = nav.querySelector(".toc-item.is-active");
const mq = window.matchMedia("(max-width: 980px)");
const key = `archicratie:${nav.dataset.tocKey || "toc-global"}`;
if (!toggle || !bodyClip) return;
const read = () => {
try {
const v = localStorage.getItem(key);
if (v === "open") return true;
if (v === "closed") return false;
} catch {}
return null;
};
const write = (open) => {
try { localStorage.setItem(key, open ? "open" : "closed"); } catch {}
};
const setOpen = (open, { persist = true } = {}) => {
const isMobile = mq.matches;
nav.classList.toggle("is-collapsed", isMobile && !open);
toggle.setAttribute("aria-expanded", open ? "true" : "false");
if (persist && isMobile) write(open);
};
const initState = () => {
if (!mq.matches) {
setOpen(true, { persist: false });
if (active) active.scrollIntoView({ block: "nearest" });
return;
}
const stored = read();
const open = stored == null ? false : stored;
setOpen(open, { persist: false });
if (open && active) active.scrollIntoView({ block: "nearest" });
};
toggle.addEventListener("click", () => {
const open = toggle.getAttribute("aria-expanded") !== "true";
setOpen(open);
if (open && active) active.scrollIntoView({ block: "nearest" });
if (open) {
window.dispatchEvent(new CustomEvent("archicratie:tocGlobalOpen"));
}
});
window.addEventListener("archicratie:tocLocalOpen", () => {
if (!mq.matches) return;
setOpen(false);
});
if (mq.addEventListener) {
mq.addEventListener("change", initState);
} else if (mq.addListener) {
mq.addListener(initState);
}
initState();
});
}
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", init, { once: true });
} else {
init();
}
})();
</script>

View File

@@ -0,0 +1,505 @@
---
import {
getGlossaryEntryAsideData,
getGlossaryPortalLinks,
hrefOfGlossaryEntry,
slugOfGlossaryEntry,
} from "../lib/glossary";
const {
currentEntry,
allEntries = [],
} = Astro.props;
const currentSlug = slugOfGlossaryEntry(currentEntry);
const {
displayFamily,
displayDomain,
displayLevel,
showNoyau,
showSameFamily,
fondamentaux,
sameFamilyTitle,
sameFamilyEntries,
relationSections,
contextualTheory,
} = getGlossaryEntryAsideData(currentEntry, allEntries);
const portalLinks = getGlossaryPortalLinks();
---
<nav class="glossary-aside" aria-label="Navigation du glossaire">
<div class="glossary-aside__block glossary-aside__block--intro">
<a class="glossary-aside__back" href="/glossaire/">← Retour au glossaire</a>
<div class="glossary-aside__title">Glossaire archicratique</div>
<div class="glossary-aside__pills" aria-label="Repères de lecture">
<span class="glossary-aside__pill glossary-aside__pill--family">
{displayFamily}
</span>
{displayDomain && (
<span class="glossary-aside__pill">{displayDomain}</span>
)}
{displayLevel && (
<span class="glossary-aside__pill">{displayLevel}</span>
)}
</div>
</div>
<details class="glossary-aside__block glossary-aside__disclosure" open>
<summary class="glossary-aside__summary">
<span class="glossary-aside__heading">Portails</span>
<span class="glossary-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-aside__panel">
<ul class="glossary-aside__list">
{portalLinks.map((item) => (
<li><a href={item.href}>{item.label}</a></li>
))}
</ul>
</div>
</details>
{showNoyau && (
<details class="glossary-aside__block glossary-aside__disclosure" open>
<summary class="glossary-aside__summary">
<span class="glossary-aside__heading">Noyau archicratique</span>
<span class="glossary-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-aside__panel">
<ul class="glossary-aside__list">
{fondamentaux.map((entry) => {
const active = slugOfGlossaryEntry(entry) === currentSlug;
return (
<li>
<a
href={hrefOfGlossaryEntry(entry)}
aria-current={active ? "page" : undefined}
class={active ? "is-active" : undefined}
>
{entry.data.term}
</a>
</li>
);
})}
</ul>
</div>
</details>
)}
{showSameFamily && (
<details class="glossary-aside__block glossary-aside__disclosure" open>
<summary class="glossary-aside__summary">
<span class="glossary-aside__heading">{sameFamilyTitle}</span>
<span class="glossary-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-aside__panel">
<ul class="glossary-aside__list">
{sameFamilyEntries.map((entry) => {
const active = slugOfGlossaryEntry(entry) === currentSlug;
return (
<li>
<a
href={hrefOfGlossaryEntry(entry)}
aria-current={active ? "page" : undefined}
class={active ? "is-active" : undefined}
>
{entry.data.term}
</a>
</li>
);
})}
</ul>
</div>
</details>
)}
{relationSections.length > 0 && (
<details class="glossary-aside__block glossary-aside__disclosure" open>
<summary class="glossary-aside__summary">
<span class="glossary-aside__heading">Autour de cette fiche</span>
<span class="glossary-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-aside__panel">
{relationSections.map((section) => (
<>
<h3 class="glossary-aside__subheading">{section.title}</h3>
<ul class="glossary-aside__list">
{section.items.map((entry) => (
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</>
))}
</div>
</details>
)}
{contextualTheory.length > 0 && (
<details class="glossary-aside__block glossary-aside__disclosure" open>
<summary class="glossary-aside__summary">
<span class="glossary-aside__heading">Paysage théorique</span>
<span class="glossary-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-aside__panel">
<ul class="glossary-aside__list">
{contextualTheory.map((entry) => (
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</div>
</details>
)}
</nav>
<style>
.glossary-aside{
display: flex;
flex-direction: column;
gap: 14px;
min-width: 0;
}
.glossary-aside__block{
border: 1px solid rgba(127,127,127,0.22);
border-radius: 16px;
padding: 14px;
background: rgba(127,127,127,0.05);
min-width: 0;
}
.glossary-aside__block--intro{
padding-top: 13px;
padding-bottom: 13px;
}
.glossary-aside__back{
display: inline-block;
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
text-decoration: none;
}
.glossary-aside__title{
font-size: 18px;
font-weight: 850;
letter-spacing: .1px;
line-height: 1.22;
}
.glossary-aside__pills{
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: 10px;
}
.glossary-aside__pill{
display: inline-flex;
align-items: center;
padding: 5px 10px;
border: 1px solid rgba(127,127,127,0.24);
border-radius: 999px;
background: rgba(127,127,127,0.04);
font-size: 13px;
line-height: 1.35;
opacity: .92;
min-width: 0;
}
.glossary-aside__pill--family{
border-color: rgba(127,127,127,0.38);
font-weight: 800;
}
.glossary-aside__disclosure{
padding: 0;
overflow: hidden;
}
.glossary-aside__summary{
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px;
cursor: pointer;
user-select: none;
}
.glossary-aside__summary::-webkit-details-marker{
display: none;
}
.glossary-aside__summary:hover{
background: rgba(127,127,127,0.035);
}
.glossary-aside__heading{
margin: 0;
font-size: 16px;
font-weight: 850;
line-height: 1.28;
opacity: .97;
}
.glossary-aside__chevron{
flex: 0 0 auto;
font-size: 14px;
line-height: 1;
opacity: .72;
transform: rotate(0deg);
transition: transform 160ms ease, opacity 160ms ease;
}
.glossary-aside__disclosure[open] .glossary-aside__chevron{
transform: rotate(180deg);
opacity: .96;
}
.glossary-aside__panel{
padding: 0 14px 14px;
}
.glossary-aside__subheading{
margin: 13px 0 8px;
font-size: 12.5px;
font-weight: 800;
line-height: 1.35;
opacity: .82;
text-transform: uppercase;
letter-spacing: .04em;
}
.glossary-aside__list{
list-style: none;
margin: 0;
padding: 0;
}
.glossary-aside__list li{
margin: 7px 0;
}
.glossary-aside__list a{
text-decoration: none;
font-size: 14px;
line-height: 1.4;
word-break: break-word;
}
.glossary-aside__list a.is-active{
font-weight: 800;
}
@media (max-width: 860px){
.glossary-aside{
gap: 10px;
}
.glossary-aside__block{
border-radius: 14px;
}
.glossary-aside__block--intro{
padding: 12px;
}
.glossary-aside__back{
margin-bottom: 8px;
font-size: 13px;
line-height: 1.28;
}
.glossary-aside__title{
font-size: 19px;
line-height: 1.18;
}
.glossary-aside__pills{
gap: 6px;
margin-top: 8px;
}
.glossary-aside__pill{
padding: 4px 9px;
font-size: 12px;
line-height: 1.26;
}
.glossary-aside__summary{
padding: 12px;
}
.glossary-aside__heading{
font-size: 17px;
line-height: 1.2;
}
.glossary-aside__panel{
padding: 0 12px 12px;
}
.glossary-aside__subheading{
margin: 10px 0 6px;
font-size: 11.5px;
line-height: 1.26;
}
.glossary-aside__list li{
margin: 5px 0;
}
.glossary-aside__list a{
font-size: 14px;
line-height: 1.34;
}
.glossary-aside__disclosure:not([open]) .glossary-aside__panel{
display: none;
}
}
@media (max-width: 860px){
.glossary-aside__disclosure{
background: rgba(127,127,127,0.045);
}
.glossary-aside__disclosure[open] .glossary-aside__summary{
border-bottom: 1px solid rgba(127,127,127,0.12);
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-aside{
gap: 8px;
}
.glossary-aside__block{
border-radius: 12px;
}
.glossary-aside__block--intro{
padding: 10px 11px;
}
.glossary-aside__back{
margin-bottom: 6px;
font-size: 12px;
line-height: 1.2;
}
.glossary-aside__title{
font-size: 16px;
line-height: 1.14;
}
.glossary-aside__pills{
gap: 5px;
margin-top: 7px;
}
.glossary-aside__pill{
padding: 3px 8px;
font-size: 11px;
line-height: 1.2;
}
.glossary-aside__summary{
padding: 10px 11px;
}
.glossary-aside__heading{
font-size: 15px;
line-height: 1.16;
}
.glossary-aside__panel{
padding: 0 11px 10px;
}
.glossary-aside__subheading{
margin: 8px 0 5px;
font-size: 11px;
line-height: 1.18;
}
.glossary-aside__list li{
margin: 4px 0;
}
.glossary-aside__list a{
font-size: 13px;
line-height: 1.28;
}
}
@media (min-width: 861px){
.glossary-aside__summary{
cursor: default;
}
.glossary-aside__chevron{
display: none;
}
}
@media (prefers-color-scheme: dark){
.glossary-aside__block,
.glossary-aside__pill{
background: rgba(255,255,255,0.04);
}
.glossary-aside__summary:hover{
background: rgba(255,255,255,0.03);
}
}
</style>
<script is:inline>
(() => {
const syncMobileDisclosure = () => {
const mobile = window.matchMedia("(max-width: 860px)").matches;
const smallLandscape = window.matchMedia(
"(orientation: landscape) and (max-width: 920px) and (max-height: 520px)"
).matches;
const compact = mobile || smallLandscape;
document
.querySelectorAll(".glossary-aside__disclosure")
.forEach((el, index) => {
if (!(el instanceof HTMLDetailsElement)) return;
if (compact) {
if (!el.dataset.mobileInit) {
el.open = index === 0;
el.dataset.mobileInit = "true";
}
} else {
el.open = true;
}
});
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", syncMobileDisclosure, { once: true });
} else {
syncMobileDisclosure();
}
window.addEventListener("resize", syncMobileDisclosure);
window.addEventListener("pageshow", syncMobileDisclosure);
})();
</script>

View File

@@ -0,0 +1,110 @@
---
import { hrefOfGlossaryEntry, type GlossaryEntry } from "../lib/glossary";
export interface Props {
entries?: GlossaryEntry[];
wide?: boolean;
}
const {
entries = [],
wide = false,
} = Astro.props;
---
<div class="glossary-cards">
{entries.map((entry) => (
<a
class:list={[
"glossary-card",
wide && "glossary-card--wide",
]}
href={hrefOfGlossaryEntry(entry)}
>
<strong>{entry.data.term}</strong>
<span>{entry.data.definitionShort}</span>
</a>
))}
</div>
<style>
.glossary-cards{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin-top: 12px;
}
.glossary-card{
display: flex;
flex-direction: column;
gap: 7px;
padding: 13px 14px;
border: 1px solid var(--glossary-border);
border-radius: 16px;
background: var(--glossary-bg-soft);
text-decoration: none;
transition: transform 120ms ease, background 120ms ease, border-color 120ms ease;
}
.glossary-card:hover{
transform: translateY(-1px);
background: var(--glossary-bg-soft-strong);
border-color: rgba(0,217,255,0.16);
text-decoration: none;
}
.glossary-card--wide{
grid-column: 1 / -1;
}
.glossary-card strong{
color: var(--glossary-accent);
font-size: 1.02rem;
line-height: 1.24;
}
.glossary-card span{
color: inherit;
font-size: .98rem;
line-height: 1.46;
opacity: .94;
}
@media (max-width: 760px){
.glossary-cards{
grid-template-columns: 1fr;
gap: 10px;
margin-top: 10px;
}
.glossary-card{
gap: 6px;
padding: 12px 12px;
border-radius: 14px;
}
.glossary-card strong{
font-size: .98rem;
}
.glossary-card span{
font-size: .94rem;
line-height: 1.42;
}
.glossary-card--wide{
grid-column: auto;
}
}
@media (prefers-color-scheme: dark){
.glossary-card{
background: rgba(255,255,255,0.04);
}
.glossary-card:hover{
background: rgba(255,255,255,0.07);
}
}
</style>

View File

@@ -0,0 +1,16 @@
<div class="glossary-entry-body">
<slot />
</div>
<style>
.glossary-entry-body{
margin-bottom: 28px;
}
:global(.glossary-entry-body h2),
:global(.glossary-entry-body h3),
:global(.glossary-relations h2),
:global(.glossary-relations h3){
scroll-margin-top: calc(var(--sticky-offset-px, 96px) + 18px);
}
</style>

View File

@@ -0,0 +1,260 @@
---
interface Props {
term: string;
definitionShort: string;
displayFamily: string;
displayDomain?: string;
displayLevel?: string;
mobilizedAuthors?: string[];
comparisonTraditions?: string[];
}
const {
term,
definitionShort,
displayFamily,
displayDomain = "",
displayLevel = "",
mobilizedAuthors = [],
comparisonTraditions = [],
} = Astro.props;
const hasScholarlyMeta =
mobilizedAuthors.length > 0 ||
comparisonTraditions.length > 0;
---
<header class="glossary-entry-head" data-ge-hero>
<div class="glossary-entry-head__title">
<h1>{term}</h1>
</div>
<div class="glossary-entry-summary">
<p class="glossary-entry-dek">
<em>{definitionShort}</em>
</p>
<div class="glossary-entry-signals" aria-label="Repères de lecture">
<span class="glossary-pill glossary-pill--family">
<strong>Famille :</strong> {displayFamily}
</span>
{displayDomain && (
<span class="glossary-pill">
<strong>Domaine :</strong> {displayDomain}
</span>
)}
{displayLevel && (
<span class="glossary-pill">
<strong>Niveau :</strong> {displayLevel}
</span>
)}
</div>
{hasScholarlyMeta && (
<div class="glossary-entry-meta">
{mobilizedAuthors.length > 0 && (
<p>
<strong>Auteurs mobilisés :</strong> {mobilizedAuthors.join(" / ")}
</p>
)}
{comparisonTraditions.length > 0 && (
<p>
<strong>Traditions de comparaison :</strong> {comparisonTraditions.join(" / ")}
</p>
)}
</div>
)}
</div>
</header>
<style>
.glossary-entry-head{
position: sticky;
top: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px));
z-index: 11;
margin: 0 0 22px;
border: 1px solid rgba(127,127,127,0.18);
border-radius: 24px;
background:
linear-gradient(180deg, rgba(0,0,0,0.60), rgba(0,0,0,0.92)),
radial-gradient(900px 240px at 20% 0%, rgba(0,217,255,0.08), transparent 60%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
overflow: hidden;
transition:
border-radius 180ms ease,
box-shadow 180ms ease,
border-color 180ms ease;
}
.glossary-entry-head__title{
padding:
var(--entry-hero-pad-top, 18px)
var(--entry-hero-pad-x, 18px)
calc(var(--entry-hero-pad-top, 18px) - 2px);
transition: padding 180ms ease;
}
.glossary-entry-head h1{
margin: 0;
font-size: var(--entry-hero-h1-size, clamp(2.2rem, 4vw, 3.15rem));
line-height: 1.02;
letter-spacing: -.04em;
font-weight: 850;
transition: font-size 180ms ease;
}
.glossary-entry-summary{
display: grid;
gap: var(--entry-hero-gap, 14px);
padding:
calc(var(--entry-hero-pad-bottom, 18px) - 2px)
var(--entry-hero-pad-x, 18px)
var(--entry-hero-pad-bottom, 18px);
border-top: 1px solid rgba(127,127,127,0.14);
background: rgba(255,255,255,0.02);
transition: gap 180ms ease, padding 180ms ease;
}
.glossary-entry-dek{
margin: 0;
max-width: var(--entry-hero-dek-maxw, 76ch);
font-size: var(--entry-hero-dek-size, 1.04rem);
line-height: var(--entry-hero-dek-lh, 1.55);
opacity: .94;
transition:
max-width 180ms ease,
font-size 180ms ease,
line-height 180ms ease;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
}
.glossary-entry-signals{
display: flex;
flex-wrap: wrap;
gap: 7px;
margin: 0;
transition: gap 180ms ease;
}
.glossary-pill{
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 9px;
border: 1px solid rgba(127,127,127,0.24);
border-radius: 999px;
background: rgba(127,127,127,0.05);
font-size: 12.5px;
line-height: 1.28;
transition:
padding 180ms ease,
font-size 180ms ease,
background 120ms ease,
border-color 120ms ease;
}
.glossary-pill--family{
border-color: rgba(127,127,127,0.36);
font-weight: 700;
}
.glossary-entry-meta{
margin: 0;
padding: 10px 12px;
border: 1px solid rgba(127,127,127,0.18);
border-radius: 12px;
background: rgba(127,127,127,0.04);
max-height: var(--entry-hero-meta-max-h, 12rem);
opacity: var(--entry-hero-meta-opacity, 1);
overflow: hidden;
transition:
max-height 180ms ease,
opacity 140ms ease,
padding 180ms ease,
border-color 180ms ease;
}
.glossary-entry-meta p{
margin: 0;
font-size: 13.5px;
line-height: 1.45;
}
.glossary-entry-meta p + p{
margin-top: 6px;
}
@media (max-width: 860px){
.glossary-entry-head{
position: static;
border-radius: 18px;
margin-bottom: 16px;
}
.glossary-entry-head__title{
padding: 12px 12px 10px;
}
.glossary-entry-summary{
gap: 9px;
padding: 10px 12px 12px;
}
.glossary-entry-dek{
max-width: none;
-webkit-line-clamp: 3;
}
.glossary-entry-signals{
gap: 6px;
}
.glossary-pill{
font-size: 12px;
padding: 4px 8px;
}
}
@media (max-width: 520px){
.glossary-entry-head{
border-radius: 16px;
margin-bottom: 14px;
}
.glossary-entry-head__title{
padding: 10px 10px 9px;
}
.glossary-entry-summary{
gap: 8px;
padding: 9px 10px 10px;
}
.glossary-entry-dek{
-webkit-line-clamp: 2;
}
.glossary-pill{
font-size: 11.5px;
padding: 3px 7px;
}
}
@media (prefers-color-scheme: dark){
.glossary-entry-meta{
background: rgba(255,255,255,0.03);
}
.glossary-pill{
background: rgba(255,255,255,0.04);
}
}
</style>

View File

@@ -0,0 +1,31 @@
---
interface Props {
canonicalHref: string;
term: string;
}
const { canonicalHref, term } = Astro.props;
---
<p class="glossary-legacy-note">
Cette entrée a été renommée. Lintitulé canonique est :
<a href={canonicalHref}>{term}</a>.
</p>
<style>
.glossary-legacy-note{
padding: 10px 12px;
border: 1px solid rgba(127,127,127,0.22);
border-radius: 12px;
background: rgba(127,127,127,0.05);
font-size: 14px;
line-height: 1.45;
margin-bottom: 18px;
}
@media (prefers-color-scheme: dark){
.glossary-legacy-note{
background: rgba(255,255,255,0.04);
}
}
</style>

View File

@@ -0,0 +1,289 @@
<script is:inline>
(() => {
const boot = () => {
const body = document.body;
const root = document.documentElement;
const hero = document.querySelector("[data-ge-hero]");
const follow = document.getElementById("reading-follow");
const mqMobile = window.matchMedia("(max-width: 860px)");
const mqSmallLandscape = window.matchMedia(
"(orientation: landscape) and (max-width: 920px) and (max-height: 520px)"
);
if (!body || !root || !hero || !follow) return;
const BODY_CLASS = "is-glossary-entry-page";
const FOLLOW_ON_CLASS = "glossary-entry-follow-on";
let lastHeight = -1;
let lastFollowOn = null;
let raf = 0;
body.classList.add(BODY_CLASS);
const isCompactViewport = () =>
mqMobile.matches || mqSmallLandscape.matches;
const heroHeight = () =>
Math.max(0, Math.round(hero.getBoundingClientRect().height || 0));
const neutralizeGlobalFollowIfCompact = () => {
if (!isCompactViewport()) {
follow.style.display = "";
return;
}
follow.classList.remove("is-on");
follow.setAttribute("aria-hidden", "true");
follow.style.display = "none";
root.style.setProperty("--followbar-h", "0px");
};
const computeFollowOn = () =>
!isCompactViewport() &&
follow.classList.contains("is-on") &&
follow.style.display !== "none" &&
follow.getAttribute("aria-hidden") !== "true";
const stripLocalSticky = () => {
document
.querySelectorAll(
".glossary-entry-body h2, .glossary-entry-body h3, .glossary-relations h2, .glossary-relations h3"
)
.forEach((el) => {
el.classList.remove("is-sticky");
el.removeAttribute("data-sticky-active");
});
};
const applyLocalStickyHeight = () => {
const h = isCompactViewport() ? 0 : heroHeight();
if (h === lastHeight) return;
lastHeight = h;
if (typeof window.__archiSetLocalStickyHeight === "function") {
window.__archiSetLocalStickyHeight(h);
} else {
root.style.setProperty("--glossary-local-sticky-h", `${h}px`);
}
};
const syncFollowState = () => {
const on = computeFollowOn();
if (on === lastFollowOn) return;
lastFollowOn = on;
body.classList.toggle(FOLLOW_ON_CLASS, on);
};
const syncAll = () => {
neutralizeGlobalFollowIfCompact();
stripLocalSticky();
syncFollowState();
applyLocalStickyHeight();
};
const schedule = () => {
if (raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
syncAll();
});
};
const followObserver = new MutationObserver(schedule);
followObserver.observe(follow, {
attributes: true,
attributeFilter: ["class", "style", "aria-hidden"],
subtree: false,
});
const heroResizeObserver =
typeof ResizeObserver !== "undefined"
? new ResizeObserver(schedule)
: null;
heroResizeObserver?.observe(hero);
window.addEventListener("resize", schedule);
window.addEventListener("pageshow", schedule);
if (document.fonts?.ready) {
document.fonts.ready.then(schedule).catch(() => {});
}
if (mqMobile.addEventListener) {
mqMobile.addEventListener("change", schedule);
} else if (mqMobile.addListener) {
mqMobile.addListener(schedule);
}
if (mqSmallLandscape.addEventListener) {
mqSmallLandscape.addEventListener("change", schedule);
} else if (mqSmallLandscape.addListener) {
mqSmallLandscape.addListener(schedule);
}
schedule();
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot, { once: true });
} else {
boot();
}
})();
</script>
<style>
:global(body.is-glossary-entry-page #reading-follow){
z-index: 10;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-head){
margin-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
box-shadow: 0 8px 20px rgba(0,0,0,0.10);
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-head h1){
letter-spacing: -.03em;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-summary){
gap: 8px;
padding-top: 10px;
padding-bottom: 8px;
border-top-color: rgba(127,127,127,0.10);
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-dek){
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-signals){
gap: 5px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-pill){
gap: 4px;
padding: 3px 7px;
font-size: 11px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-meta){
padding: 0;
border-color: transparent;
max-height: 0;
opacity: 0;
overflow: hidden;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on #reading-follow){
transform: translateY(-1px);
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on #reading-follow .reading-follow__inner){
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
:global(body.is-glossary-entry-page .glossary-entry-body h2.is-sticky),
:global(body.is-glossary-entry-page .glossary-entry-body h2[data-sticky-active="true"]),
:global(body.is-glossary-entry-page .glossary-entry-body h3.is-sticky),
:global(body.is-glossary-entry-page .glossary-entry-body h3[data-sticky-active="true"]),
:global(body.is-glossary-entry-page .glossary-relations h2.is-sticky),
:global(body.is-glossary-entry-page .glossary-relations h2[data-sticky-active="true"]),
:global(body.is-glossary-entry-page .glossary-relations h3.is-sticky),
:global(body.is-glossary-entry-page .glossary-relations h3[data-sticky-active="true"]){
position: static !important;
top: auto !important;
z-index: auto !important;
padding: 0 !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
@media (max-width: 860px){
:global(body.is-glossary-entry-page #reading-follow),
:global(body.is-glossary-entry-page #reading-follow .reading-follow__inner){
display: none !important;
opacity: 0 !important;
pointer-events: none !important;
visibility: hidden !important;
}
:global(body.is-glossary-entry-page){
--followbar-h: 0px !important;
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-head){
margin-bottom: 18px;
border-radius: 20px;
box-shadow: none;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-summary){
gap: 6px;
padding-top: 8px;
padding-bottom: 8px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-dek){
display: none;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-signals){
gap: 5px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-pill){
padding: 3px 6px;
font-size: 10.5px;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
:global(body.is-glossary-entry-page #reading-follow),
:global(body.is-glossary-entry-page #reading-follow .reading-follow__inner){
display: none !important;
opacity: 0 !important;
pointer-events: none !important;
visibility: hidden !important;
}
:global(body.is-glossary-entry-page){
--followbar-h: 0px !important;
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-head){
margin-bottom: 14px;
border-radius: 16px;
box-shadow: none;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-summary){
gap: 5px;
padding-top: 6px;
padding-bottom: 6px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-dek){
display: none;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-pill){
padding: 2px 6px;
font-size: 10px;
}
}
</style>

View File

@@ -0,0 +1,382 @@
---
import {
getFondamentaux,
getGlossaryHomeStats,
getGlossaryPortalLinks,
hrefOfGlossaryEntry,
} from "../lib/glossary";
const {
allEntries = [],
} = Astro.props;
const fondamentaux = getFondamentaux(allEntries);
const portalLinks = getGlossaryPortalLinks();
const {
totalEntries,
paradigmesCount,
doctrinesCount,
metaRegimesCount,
} = getGlossaryHomeStats(allEntries);
---
<nav class="glossary-home-aside" aria-label="Navigation du portail du glossaire">
<div class="glossary-home-aside__block glossary-home-aside__block--intro">
<div class="glossary-home-aside__title">Glossaire archicratique</div>
<div class="glossary-home-aside__meta">
portail de lecture · cartographie conceptuelle
</div>
<div class="glossary-home-aside__pills" aria-label="Repères de navigation">
<span class="glossary-home-aside__pill">{totalEntries} entrées</span>
<span class="glossary-home-aside__pill">{metaRegimesCount} méta-régimes</span>
<span class="glossary-home-aside__pill">
{doctrinesCount} doctrine{doctrinesCount > 1 ? "s" : ""} · {paradigmesCount} paradigme{paradigmesCount > 1 ? "s" : ""}
</span>
</div>
</div>
<details class="glossary-home-aside__block glossary-home-aside__disclosure" open>
<summary class="glossary-home-aside__summary">
<span class="glossary-home-aside__heading">Parcours du glossaire</span>
<span class="glossary-home-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-home-aside__panel">
<ul class="glossary-home-aside__list">
{portalLinks.map((item) => (
<li><a href={item.href}>{item.label}</a></li>
))}
</ul>
</div>
</details>
{fondamentaux.length > 0 && (
<details class="glossary-home-aside__block glossary-home-aside__disclosure" open>
<summary class="glossary-home-aside__summary">
<span class="glossary-home-aside__heading">Noyau archicratique</span>
<span class="glossary-home-aside__chevron" aria-hidden="true">▾</span>
</summary>
<div class="glossary-home-aside__panel">
<ul class="glossary-home-aside__list">
{fondamentaux.map((entry) => (
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</div>
</details>
)}
</nav>
<style>
.glossary-home-aside{
display: flex;
flex-direction: column;
gap: 14px;
min-width: 0;
}
.glossary-home-aside__block{
border: 1px solid rgba(127,127,127,0.22);
border-radius: 16px;
padding: 14px;
background: rgba(127,127,127,0.05);
min-width: 0;
}
.glossary-home-aside__block--intro{
padding-top: 13px;
padding-bottom: 13px;
}
.glossary-home-aside__title{
font-size: 18px;
font-weight: 850;
letter-spacing: .1px;
line-height: 1.22;
}
.glossary-home-aside__meta{
margin-top: 8px;
font-size: 13px;
line-height: 1.4;
opacity: .8;
}
.glossary-home-aside__pills{
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: 11px;
}
.glossary-home-aside__pill{
display: inline-flex;
align-items: center;
padding: 5px 10px;
border: 1px solid rgba(127,127,127,0.24);
border-radius: 999px;
background: rgba(127,127,127,0.04);
font-size: 13px;
line-height: 1.35;
opacity: .92;
min-width: 0;
}
.glossary-home-aside__disclosure{
padding: 0;
overflow: hidden;
}
.glossary-home-aside__summary{
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px;
cursor: pointer;
user-select: none;
}
.glossary-home-aside__summary::-webkit-details-marker{
display: none;
}
.glossary-home-aside__summary:hover{
background: rgba(127,127,127,0.035);
}
.glossary-home-aside__heading{
margin: 0;
font-size: 16px;
font-weight: 850;
line-height: 1.28;
opacity: .97;
}
.glossary-home-aside__chevron{
flex: 0 0 auto;
font-size: 14px;
line-height: 1;
opacity: .72;
transform: rotate(0deg);
transition: transform 160ms ease, opacity 160ms ease;
}
.glossary-home-aside__disclosure[open] .glossary-home-aside__chevron{
transform: rotate(180deg);
opacity: .96;
}
.glossary-home-aside__panel{
padding: 0 14px 14px;
}
.glossary-home-aside__list{
list-style: none;
margin: 0;
padding: 0;
}
.glossary-home-aside__list li{
margin: 7px 0;
}
.glossary-home-aside__list a{
text-decoration: none;
font-size: 14px;
line-height: 1.42;
word-break: break-word;
}
@media (max-width: 860px){
.glossary-home-aside{
gap: 10px;
}
.glossary-home-aside__block{
border-radius: 14px;
}
.glossary-home-aside__block--intro{
padding: 12px;
}
.glossary-home-aside__title{
font-size: 19px;
line-height: 1.18;
}
.glossary-home-aside__meta{
margin-top: 6px;
font-size: 12px;
line-height: 1.32;
}
.glossary-home-aside__pills{
gap: 6px;
margin-top: 9px;
}
.glossary-home-aside__pill{
padding: 4px 9px;
font-size: 12px;
line-height: 1.28;
}
.glossary-home-aside__summary{
padding: 12px;
}
.glossary-home-aside__heading{
font-size: 17px;
line-height: 1.2;
}
.glossary-home-aside__panel{
padding: 0 12px 12px;
}
.glossary-home-aside__list li{
margin: 5px 0;
}
.glossary-home-aside__list a{
font-size: 14px;
line-height: 1.34;
}
.glossary-home-aside__disclosure:not([open]) .glossary-home-aside__panel{
display: none;
}
}
@media (max-width: 860px){
.glossary-home-aside__disclosure{
background: rgba(127,127,127,0.045);
}
.glossary-home-aside__disclosure[open] .glossary-home-aside__summary{
border-bottom: 1px solid rgba(127,127,127,0.12);
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-home-aside{
gap: 8px;
}
.glossary-home-aside__block{
border-radius: 12px;
}
.glossary-home-aside__block--intro{
padding: 10px 11px;
}
.glossary-home-aside__title{
font-size: 16px;
line-height: 1.14;
}
.glossary-home-aside__meta{
font-size: 11px;
line-height: 1.26;
margin-top: 5px;
}
.glossary-home-aside__pills{
gap: 5px;
margin-top: 8px;
}
.glossary-home-aside__pill{
padding: 3px 8px;
font-size: 11px;
line-height: 1.2;
}
.glossary-home-aside__summary{
padding: 10px 11px;
}
.glossary-home-aside__heading{
font-size: 15px;
line-height: 1.16;
}
.glossary-home-aside__panel{
padding: 0 11px 10px;
}
.glossary-home-aside__list li{
margin: 4px 0;
}
.glossary-home-aside__list a{
font-size: 13px;
line-height: 1.28;
}
}
@media (min-width: 861px){
.glossary-home-aside__summary{
cursor: default;
}
.glossary-home-aside__chevron{
display: none;
}
}
@media (prefers-color-scheme: dark){
.glossary-home-aside__block,
.glossary-home-aside__pill{
background: rgba(255,255,255,0.04);
}
.glossary-home-aside__summary:hover{
background: rgba(255,255,255,0.03);
}
}
</style>
<script is:inline>
(() => {
const syncMobileDisclosure = () => {
const mobile = window.matchMedia("(max-width: 860px)").matches;
const smallLandscape = window.matchMedia(
"(orientation: landscape) and (max-width: 920px) and (max-height: 520px)"
).matches;
const compact = mobile || smallLandscape;
document
.querySelectorAll(".glossary-home-aside__disclosure")
.forEach((el, index) => {
if (!(el instanceof HTMLDetailsElement)) return;
if (compact) {
if (!el.dataset.mobileInit) {
el.open = index === 0;
el.dataset.mobileInit = "true";
}
} else {
el.open = true;
}
});
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", syncMobileDisclosure, { once: true });
} else {
syncMobileDisclosure();
}
window.addEventListener("resize", syncMobileDisclosure);
window.addEventListener("pageshow", syncMobileDisclosure);
})();
</script>

View File

@@ -0,0 +1,364 @@
---
export interface Props {
kicker?: string;
title?: string;
intro?: string;
}
const {
kicker = "Référentiel terminologique",
title = "Glossaire archicratique",
intro = "Ce glossaire nest pas seulement un index de définitions. Il constitue une porte dentrée dans la pensée archicratique : une cartographie raisonnée des concepts fondamentaux, des scènes, des dynamiques et des méta-régimes à partir desquels une société peut être décrite comme organisation de tensions et recherche de co-viabilité.",
} = Astro.props;
---
<header class="glossary-hero" id="glossary-hero">
<p class="glossary-kicker">{kicker}</p>
<h1>{title}</h1>
<div class="glossary-hero__collapsible">
<p
class="glossary-intro"
id="glossary-hero-intro"
aria-hidden="false"
>
{intro}
</p>
<button
class="glossary-hero__toggle"
id="glossary-hero-toggle"
type="button"
aria-controls="glossary-hero-intro"
aria-expanded="false"
hidden
>
lire la suite
</button>
</div>
<h2
class="glossary-hero-follow"
id="glossary-hero-follow"
aria-hidden="true"
></h2>
</header>
<style>
.glossary-hero{
position: sticky;
top: var(--glossary-sticky-top);
z-index: 12;
margin-bottom: 28px;
padding: 14px 16px 18px;
border: 1px solid rgba(127,127,127,0.18);
border-radius: 28px;
background:
linear-gradient(180deg, rgba(0,0,0,0.60), rgba(0,0,0,0.90)),
radial-gradient(900px 240px at 20% 0%, rgba(0,217,255,0.08), transparent 60%);
transition:
padding 220ms cubic-bezier(.22,.8,.22,1),
border-radius 220ms cubic-bezier(.22,.8,.22,1),
background 300ms cubic-bezier(.22,.8,.22,1),
border-color 300ms cubic-bezier(.22,.8,.22,1),
box-shadow 300ms cubic-bezier(.22,.8,.22,1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: grid;
row-gap: 12px;
min-width: 0;
overflow: clip;
}
.glossary-kicker{
margin: 0;
font-size: 12px;
letter-spacing: .12em;
text-transform: uppercase;
opacity: .72;
}
.glossary-hero h1{
margin: 0;
font-size: clamp(2.2rem, 4vw, 3.15rem);
line-height: 1.02;
letter-spacing: -.04em;
font-weight: 850;
transition:
font-size 220ms cubic-bezier(.22,.8,.22,1),
line-height 220ms cubic-bezier(.22,.8,.22,1);
min-width: 0;
}
.glossary-hero__collapsible{
display: grid;
row-gap: 6px;
min-width: 0;
}
.glossary-intro{
margin: 0;
max-width: 72ch;
font-size: 1.05rem;
line-height: 1.55;
opacity: .94;
min-width: 0;
transition:
font-size 220ms cubic-bezier(.22,.8,.22,1),
line-height 220ms cubic-bezier(.22,.8,.22,1),
max-height 220ms cubic-bezier(.22,.8,.22,1),
opacity 180ms ease;
}
.glossary-hero__toggle{
display: inline-flex;
align-items: center;
justify-content: center;
width: fit-content;
min-height: 30px;
padding: 3px 0;
border: 0;
border-radius: 0;
background: transparent;
color: inherit;
font-size: 12px;
line-height: 1.2;
letter-spacing: .01em;
opacity: .72;
cursor: pointer;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
transition:
opacity 120ms ease,
transform 120ms ease;
}
.glossary-hero__toggle:hover{
opacity: .92;
transform: translateY(-1px);
}
.glossary-hero__toggle:focus-visible{
outline: 2px solid rgba(0,217,255,0.24);
outline-offset: 4px;
border-radius: 4px;
}
.glossary-hero__toggle[hidden]{
display: none !important;
}
.glossary-hero-follow{
margin: 2px 0 0;
min-height: var(--glossary-follow-height);
display: block;
max-width: min(100%, 22ch);
opacity: 0;
transform: translateY(10px) scale(.985);
filter: blur(6px);
transition:
opacity 220ms cubic-bezier(.22,1,.36,1),
transform 320ms cubic-bezier(.22,1,.36,1),
filter 320ms cubic-bezier(.22,1,.36,1);
pointer-events: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
will-change: opacity, transform, filter;
min-width: 0;
}
.glossary-hero-follow.is-visible{
opacity: 1;
transform: translateY(0) scale(1);
filter: blur(0);
}
:global(body.glossary-home-follow-on) .glossary-hero{
padding: 12px 14px 14px;
border-bottom-left-radius: 18px;
border-bottom-right-radius: 18px;
}
:global(body.glossary-home-follow-on) .glossary-hero h1{
font-size: clamp(1.7rem, 3.2vw, 2.2rem);
line-height: 1.02;
}
:global(body.glossary-home-follow-on:not(.glossary-home-hero-expanded)) .glossary-intro{
font-size: .94rem;
line-height: 1.34;
max-height: 2.7em;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
:global(body.glossary-home-follow-on:not(.glossary-home-hero-expanded)) .glossary-hero__toggle{
display: inline-flex;
}
@media (max-width: 760px){
.glossary-hero{
top: calc(var(--glossary-sticky-top) - 2px);
padding: 12px 14px 16px;
border-radius: 22px;
row-gap: 10px;
}
.glossary-hero h1{
font-size: clamp(1.9rem, 8vw, 2.45rem);
line-height: 1.02;
letter-spacing: -.03em;
}
.glossary-hero__collapsible{
row-gap: 7px;
}
.glossary-intro{
max-width: 100%;
width: 100%;
font-size: .98rem;
line-height: 1.44;
}
:global(body.glossary-home-follow-on) .glossary-hero{
padding: 10px 13px 12px;
border-radius: 18px;
}
:global(body.glossary-home-follow-on) .glossary-hero h1{
font-size: clamp(1.45rem, 6vw, 1.8rem);
}
:global(body.glossary-home-follow-on:not(.glossary-home-hero-expanded)) .glossary-intro{
max-width: 100%;
width: 100%;
font-size: .86rem;
line-height: 1.24;
max-height: 2.48em;
-webkit-line-clamp: 2;
opacity: .9;
}
.glossary-hero__toggle{
min-height: 28px;
font-size: 11.5px;
}
.glossary-hero-follow{
max-width: min(100%, 24ch);
}
}
@media (max-width: 520px){
.glossary-hero{
padding: 11px 12px 14px;
border-radius: 20px;
}
.glossary-intro{
max-width: 100%;
width: 100%;
font-size: .94rem;
line-height: 1.4;
}
:global(body.glossary-home-follow-on) .glossary-hero{
padding: 9px 11px 11px;
}
:global(body.glossary-home-follow-on:not(.glossary-home-hero-expanded)) .glossary-intro{
max-width: 100%;
width: 100%;
font-size: .84rem;
line-height: 1.22;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-hero{
padding: 10px 12px 12px;
border-radius: 16px;
row-gap: 8px;
}
.glossary-kicker{
font-size: 10px;
letter-spacing: .1em;
}
.glossary-hero h1{
font-size: clamp(1.35rem, 4vw, 1.8rem);
line-height: 1;
}
.glossary-intro{
font-size: .84rem;
line-height: 1.24;
}
:global(body.glossary-home-follow-on) .glossary-hero{
padding: 9px 11px 10px;
border-radius: 16px;
}
:global(body.glossary-home-follow-on) .glossary-hero h1{
font-size: clamp(1.1rem, 3vw, 1.35rem);
}
:global(body.glossary-home-follow-on:not(.glossary-home-hero-expanded)) .glossary-intro{
font-size: .8rem;
line-height: 1.18;
max-height: 2.36em;
-webkit-line-clamp: 2;
opacity: .88;
}
.glossary-hero__toggle{
min-height: 24px;
font-size: 11px;
}
.glossary-hero-follow{
max-width: min(100%, 26ch);
}
}
@media (max-width: 860px){
.glossary-hero{
position: static !important;
top: auto !important;
z-index: auto !important;
margin-bottom: 18px !important;
}
.glossary-hero-follow{
display: none !important;
min-height: 0 !important;
opacity: 0 !important;
transform: none !important;
filter: none !important;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-hero{
position: static !important;
top: auto !important;
z-index: auto !important;
margin-bottom: 14px !important;
}
.glossary-hero-follow{
display: none !important;
min-height: 0 !important;
opacity: 0 !important;
transform: none !important;
filter: none !important;
}
}
</style>

View File

@@ -0,0 +1,133 @@
---
export interface Props {
id?: string;
title: string;
intro?: string;
followSection?: string;
ctaHref?: string;
ctaLabel?: string;
}
const {
id,
title,
intro,
followSection,
ctaHref,
ctaLabel,
} = Astro.props;
const resolvedFollowSection = (followSection || title || "").trim();
const showCta = Boolean(ctaHref && ctaLabel);
---
<section id={id} class="glossary-section">
<div class="glossary-section__head">
<div>
<h2 data-follow-section={resolvedFollowSection}>{title}</h2>
{intro && (
<p class="glossary-intro">{intro}</p>
)}
</div>
{showCta && (
<a class="glossary-cta" href={ctaHref}>
{ctaLabel}
</a>
)}
</div>
<slot />
</section>
<style>
.glossary-section{
margin-top: 34px;
scroll-margin-top: calc(var(--glossary-sticky-top) + 150px);
}
.glossary-section__head{
display: flex;
justify-content: space-between;
align-items: start;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.glossary-section h2{
margin: 0;
font-size: clamp(1.8rem, 3vw, 2.55rem);
line-height: 1.06;
letter-spacing: -.03em;
font-weight: 800;
}
.glossary-intro{
margin: 0;
max-width: 72ch;
font-size: 1rem;
line-height: 1.52;
opacity: .94;
}
.glossary-section__head .glossary-intro{
margin-top: 8px;
}
.glossary-cta{
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
border: 1px solid var(--glossary-border-strong);
border-radius: 999px;
padding: 6px 13px;
color: var(--glossary-accent);
text-decoration: none;
white-space: nowrap;
transition: transform 120ms ease, background 120ms ease;
}
.glossary-cta:hover{
background: var(--glossary-bg-soft-strong);
text-decoration: none;
transform: translateY(-1px);
}
@media (max-width: 760px){
.glossary-section{
margin-top: 24px;
scroll-margin-top: calc(var(--glossary-sticky-top) + 110px);
}
.glossary-section__head{
flex-direction: column;
align-items: stretch;
gap: 10px;
margin-bottom: 10px;
}
.glossary-section h2{
font-size: clamp(1.45rem, 6vw, 1.95rem);
line-height: 1.05;
}
.glossary-intro{
font-size: .95rem;
line-height: 1.42;
}
.glossary-section__head .glossary-intro{
margin-top: 6px;
}
.glossary-cta{
width: fit-content;
min-height: 35px;
padding: 5px 12px;
font-size: .95rem;
}
}
</style>

View File

@@ -0,0 +1,219 @@
---
interface LinkItem {
href: string;
label: string;
}
interface Props {
ariaLabel: string;
title: string;
meta?: string;
backHref?: string;
backLabel?: string;
pageItems?: LinkItem[];
usefulLinks?: LinkItem[];
}
const {
ariaLabel,
title,
meta,
backHref = "/glossaire/",
backLabel = "← Retour au glossaire",
pageItems = [],
usefulLinks = [],
} = Astro.props;
---
<nav class="glossary-portal-aside" aria-label={ariaLabel}>
<div class="glossary-portal-aside__block">
<a class="glossary-portal-aside__back" href={backHref}>{backLabel}</a>
<div class="glossary-portal-aside__title">{title}</div>
{meta && <div class="glossary-portal-aside__meta">{meta}</div>}
</div>
{pageItems.length > 0 && (
<div class="glossary-portal-aside__block">
<h2 class="glossary-portal-aside__heading">Dans cette page</h2>
<ul class="glossary-portal-aside__list">
{pageItems.map((item) => (
<li><a href={item.href}>{item.label}</a></li>
))}
</ul>
</div>
)}
{usefulLinks.length > 0 && (
<div class="glossary-portal-aside__block">
<h2 class="glossary-portal-aside__heading">Renvois utiles</h2>
<ul class="glossary-portal-aside__list">
{usefulLinks.map((item) => (
<li><a href={item.href}>{item.label}</a></li>
))}
</ul>
</div>
)}
</nav>
<style>
.glossary-portal-aside{
display: flex;
flex-direction: column;
gap: 14px;
}
.glossary-portal-aside__block{
border: 1px solid rgba(127,127,127,0.22);
border-radius: 16px;
padding: 14px;
background: rgba(127,127,127,0.05);
}
.glossary-portal-aside__back{
display: inline-block;
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
text-decoration: none;
}
.glossary-portal-aside__title{
font-size: 16px;
font-weight: 800;
letter-spacing: .2px;
line-height: 1.3;
}
.glossary-portal-aside__meta{
margin-top: 8px;
font-size: 13px;
line-height: 1.4;
opacity: .8;
}
.glossary-portal-aside__heading{
margin: 0 0 11px;
font-size: 14px;
font-weight: 800;
line-height: 1.35;
opacity: .94;
}
.glossary-portal-aside__list{
list-style: none;
margin: 0;
padding: 0;
}
.glossary-portal-aside__list li{
margin: 7px 0;
}
.glossary-portal-aside__list a{
text-decoration: none;
font-size: 14px;
line-height: 1.4;
}
@media (max-width: 980px){
.glossary-portal-aside{
gap: 12px;
}
.glossary-portal-aside__block{
padding: 12px;
border-radius: 14px;
}
}
@media (max-width: 760px){
.glossary-portal-aside{
gap: 10px;
}
.glossary-portal-aside__block{
padding: 11px 12px;
border-radius: 14px;
}
.glossary-portal-aside__back{
margin-bottom: 8px;
font-size: 13px;
}
.glossary-portal-aside__title{
font-size: 15px;
line-height: 1.22;
}
.glossary-portal-aside__meta{
margin-top: 6px;
font-size: 12px;
line-height: 1.32;
}
.glossary-portal-aside__heading{
margin-bottom: 8px;
font-size: 13px;
line-height: 1.22;
}
.glossary-portal-aside__list li{
margin: 5px 0;
}
.glossary-portal-aside__list a{
font-size: 12.5px;
line-height: 1.3;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-portal-aside{
gap: 8px;
}
.glossary-portal-aside__block{
padding: 9px 10px;
border-radius: 12px;
}
.glossary-portal-aside__back{
margin-bottom: 6px;
font-size: 12px;
}
.glossary-portal-aside__title{
font-size: 14px;
line-height: 1.18;
}
.glossary-portal-aside__meta{
margin-top: 4px;
font-size: 11px;
line-height: 1.24;
}
.glossary-portal-aside__heading{
margin-bottom: 6px;
font-size: 12px;
line-height: 1.18;
}
.glossary-portal-aside__list li{
margin: 4px 0;
}
.glossary-portal-aside__list a{
font-size: 11.5px;
line-height: 1.22;
}
}
@media (prefers-color-scheme: dark){
.glossary-portal-aside__block{
background: rgba(255,255,255,0.04);
}
}
</style>

View File

@@ -0,0 +1,67 @@
---
export interface Props {
href: string;
label: string;
icon?: string;
className?: string;
}
const {
href,
label,
icon = "↗",
className,
} = Astro.props;
---
<a class:list={["glossary-portal-cta", className]} href={href}>
<span>{label}</span>
<span aria-hidden="true">{icon}</span>
</a>
<style>
.glossary-portal-cta{
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
padding: 7px 14px;
border: 1px solid rgba(127,127,127,0.24);
border-radius: 999px;
background: rgba(127,127,127,0.05);
text-decoration: none;
line-height: 1.2;
transition:
transform 120ms ease,
background 120ms ease,
border-color 120ms ease;
}
.glossary-portal-cta:hover{
transform: translateY(-1px);
background: rgba(127,127,127,0.08);
border-color: rgba(0,217,255,0.18);
text-decoration: none;
}
.glossary-portal-cta:focus-visible{
outline: 2px solid rgba(0,217,255,0.28);
outline-offset: 3px;
}
@media (max-width: 760px){
.glossary-portal-cta{
min-height: 36px;
padding: 6px 12px;
font-size: 12px;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-portal-cta{
min-height: 32px;
padding: 5px 10px;
font-size: 11px;
}
}
</style>

View File

@@ -0,0 +1,118 @@
---
export type GlossaryPortalGridItem = {
href: string;
title: string;
description: string;
meta: string;
};
export interface Props {
items?: GlossaryPortalGridItem[];
secondary?: boolean;
}
const {
items = [],
secondary = false,
} = Astro.props;
---
<div
class:list={[
"glossary-portals",
secondary && "glossary-portals--secondary",
]}
>
{items.map((item) => (
<a class="glossary-portal-card" href={item.href}>
<strong>{item.title}</strong>
<span>{item.description}</span>
<small>{item.meta}</small>
</a>
))}
</div>
<style>
.glossary-portals{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
margin-top: 12px;
}
.glossary-portal-card{
display: flex;
flex-direction: column;
gap: 7px;
padding: 14px 15px;
border: 1px solid var(--glossary-border);
border-radius: 16px;
background: var(--glossary-bg-soft);
text-decoration: none;
transition: transform 120ms ease, background 120ms ease, border-color 120ms ease;
}
.glossary-portal-card:hover{
transform: translateY(-1px);
background: var(--glossary-bg-soft-strong);
border-color: rgba(0,217,255,0.16);
text-decoration: none;
}
.glossary-portal-card strong{
color: var(--glossary-accent);
font-size: 1.04rem;
line-height: 1.24;
}
.glossary-portal-card span{
color: inherit;
font-size: .98rem;
line-height: 1.46;
opacity: .94;
}
.glossary-portal-card small{
color: var(--glossary-accent);
font-size: .9rem;
line-height: 1.28;
opacity: .9;
}
@media (max-width: 760px){
.glossary-portals{
grid-template-columns: 1fr;
gap: 10px;
margin-top: 10px;
}
.glossary-portal-card{
padding: 12px 12px;
border-radius: 14px;
gap: 6px;
}
.glossary-portal-card strong{
font-size: .98rem;
}
.glossary-portal-card span{
font-size: .94rem;
line-height: 1.42;
}
.glossary-portal-card small{
font-size: .85rem;
}
}
@media (prefers-color-scheme: dark){
.glossary-portal-card{
background: rgba(255,255,255,0.04);
}
.glossary-portal-card:hover{
background: rgba(255,255,255,0.07);
}
}
</style>

View File

@@ -0,0 +1,260 @@
---
interface Props {
prefix: string;
kicker: string;
title: string;
intro: string;
moreParagraphs?: string[];
introMaxWidth?: string;
followIntroMaxWidth?: string;
moreMaxHeight?: string;
}
const {
prefix,
kicker,
title,
intro,
moreParagraphs = [],
introMaxWidth = "70ch",
followIntroMaxWidth = "62ch",
moreMaxHeight = "18rem",
} = Astro.props;
---
<div
class="glossary-portal-hero glossary-page-hero"
data-glossary-portal-hero
style={`--portal-hero-intro-max-w:${introMaxWidth}; --portal-hero-follow-intro-max-w:${followIntroMaxWidth}; --portal-hero-secondary-max-h:${moreMaxHeight};`}
>
<p class="glossary-portal-hero__kicker">{kicker}</p>
<h1>{title}</h1>
<p class="glossary-portal-hero__intro glossary-portal-hero__intro--lead">
{intro}
</p>
{moreParagraphs.length > 0 && (
<div class="glossary-portal-hero__collapsible">
<div
class="glossary-portal-hero__more"
id={`${prefix}-hero-more`}
data-glossary-portal-more
aria-hidden="false"
>
{moreParagraphs.map((paragraph) => (
<p class="glossary-portal-hero__intro glossary-portal-hero__intro--more">
{paragraph}
</p>
))}
</div>
<button
class="glossary-portal-hero__toggle"
id={`${prefix}-hero-toggle`}
data-glossary-portal-toggle
type="button"
aria-controls={`${prefix}-hero-more`}
aria-expanded="false"
hidden
>
lire la suite
</button>
</div>
)}
</div>
<style>
.glossary-portal-hero{
position: sticky;
top: var(--glossary-sticky-top);
z-index: 12;
margin-bottom: var(--portal-hero-margin-bottom, 28px);
padding:
var(--portal-hero-pad-top, 20px)
var(--portal-hero-pad-x, 18px)
var(--portal-hero-pad-bottom, 22px);
border: 1px solid rgba(127,127,127,0.18);
border-radius: 28px;
background:
linear-gradient(180deg, rgba(0,0,0,0.60), rgba(0,0,0,0.92)),
radial-gradient(980px 260px at 18% 0%, rgba(0,217,255,0.08), transparent 60%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: grid;
row-gap: var(--portal-hero-gap, 16px);
min-width: 0;
overflow: hidden;
transition:
background 280ms cubic-bezier(.22,.8,.22,1),
border-color 220ms cubic-bezier(.22,.8,.22,1),
box-shadow 220ms cubic-bezier(.22,.8,.22,1),
border-radius 220ms ease,
padding 220ms ease,
row-gap 220ms ease,
margin-bottom 220ms ease;
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.02),
0 10px 26px rgba(0,0,0,0.08);
}
.glossary-portal-hero__kicker{
margin: 0;
font-size: var(--portal-kicker-size, 12px);
line-height: var(--portal-kicker-lh, 1.2);
letter-spacing: var(--portal-kicker-spacing, .14em);
text-transform: uppercase;
font-weight: 650;
opacity: .74;
}
.glossary-portal-hero h1{
margin: 0;
font-size: var(--portal-hero-h1-size, clamp(3rem, 4.8vw, 4.15rem));
line-height: var(--portal-hero-h1-lh, .98);
letter-spacing: var(--portal-hero-h1-spacing, -.045em);
font-weight: 850;
text-wrap: balance;
transition:
font-size 180ms ease,
line-height 180ms ease,
letter-spacing 180ms ease;
}
.glossary-portal-hero__intro{
margin: 0;
max-width: var(--portal-hero-intro-max-w, 70ch);
font-size: var(--portal-hero-intro-size, 1.06rem);
line-height: var(--portal-hero-intro-lh, 1.6);
text-wrap: pretty;
transition:
font-size 180ms ease,
line-height 180ms ease,
max-width 180ms ease,
opacity 180ms ease;
}
.glossary-portal-hero__intro--lead{ opacity: .95; }
.glossary-portal-hero__intro--more{ opacity: .89; }
.glossary-portal-hero__collapsible{
display: grid;
row-gap: 8px;
min-width: 0;
}
.glossary-portal-hero__more{
display: grid;
gap: 12px;
max-height: var(--portal-hero-secondary-max-h, 20em);
overflow: hidden;
opacity: var(--portal-hero-secondary-opacity, .92);
min-width: 0;
transition:
max-height 220ms ease,
opacity 180ms ease;
}
.glossary-portal-hero__toggle{
display: inline-flex;
align-items: center;
justify-content: center;
width: fit-content;
min-height: 34px;
padding: 5px 0;
border: 0;
border-radius: 0;
background: transparent;
color: inherit;
font-size: 12.5px;
line-height: 1.2;
letter-spacing: .01em;
opacity: .72;
cursor: pointer;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
transition:
opacity 120ms ease,
transform 120ms ease;
}
.glossary-portal-hero__toggle:hover{
opacity: .92;
transform: translateY(-1px);
}
.glossary-portal-hero__toggle:focus-visible{
outline: 2px solid rgba(0,217,255,0.24);
outline-offset: 4px;
border-radius: 4px;
}
.glossary-portal-hero__toggle[hidden]{
display: none !important;
}
@media (max-width: 980px){
.glossary-portal-hero{
border-radius: 24px;
}
.glossary-portal-hero h1{
text-wrap: pretty;
}
.glossary-portal-hero__more{
gap: 10px;
}
}
@media (max-width: 860px){
.glossary-portal-hero{
position: static !important;
top: auto !important;
z-index: auto !important;
margin-bottom: 18px !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
}
.glossary-portal-hero h1,
.glossary-portal-hero__intro,
.glossary-portal-hero__more,
.glossary-portal-hero__collapsible{
min-width: 0 !important;
max-width: 100% !important;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-portal-hero{
position: static !important;
top: auto !important;
z-index: auto !important;
margin-bottom: 14px !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
border-radius: 16px !important;
}
.glossary-portal-hero h1,
.glossary-portal-hero__intro,
.glossary-portal-hero__more,
.glossary-portal-hero__collapsible{
min-width: 0 !important;
max-width: 100% !important;
}
}
@media (prefers-color-scheme: dark){
.glossary-portal-hero{
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.02),
0 14px 34px rgba(0,0,0,0.16);
}
}
</style>

View File

@@ -0,0 +1,127 @@
---
export interface Props {
id?: string;
title: string;
count?: string;
intro?: string;
surface?: boolean;
className?: string;
}
const {
id,
title,
count,
intro,
surface = false,
className,
} = Astro.props;
---
<div
class:list={[
"glossary-portal-panel",
surface && "glossary-portal-panel--surface",
className,
]}
>
<div class="glossary-portal-panel__head">
<h3 id={id}>{title}</h3>
{count && <span class="glossary-portal-panel__count">{count}</span>}
</div>
{intro && <p class="glossary-portal-panel__intro">{intro}</p>}
<slot />
</div>
<style>
.glossary-portal-panel{
display: grid;
gap: 10px;
}
.glossary-portal-panel--surface{
padding:
var(--portal-panel-pad-y, 16px)
var(--portal-panel-pad-x, 16px);
border: 1px solid var(--glossary-border, rgba(127,127,127,0.18));
border-radius: var(--portal-panel-radius, 18px);
background:
var(--glossary-bg-soft, rgba(127,127,127,0.035));
}
.glossary-portal-panel__head{
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.glossary-portal-panel__head h3{
margin: 0;
font-size: var(--portal-local-h3-size, clamp(1.35rem, 2vw, 1.7rem));
line-height: var(--portal-local-h3-lh, 1.15);
letter-spacing: -.02em;
}
.glossary-portal-panel__count{
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 0 9px;
border: 1px solid rgba(127,127,127,0.20);
border-radius: 999px;
background: rgba(127,127,127,0.04);
font-size: 11.5px;
line-height: 1.2;
opacity: .8;
white-space: nowrap;
}
.glossary-portal-panel__intro{
margin: 0;
font-size: var(--portal-card-text-size, 14px);
line-height: var(--portal-card-text-lh, 1.45);
opacity: .92;
}
@media (max-width: 760px){
.glossary-portal-panel{
gap: 8px;
}
.glossary-portal-panel__head{
gap: 8px;
}
.glossary-portal-panel__count{
min-height: 23px;
padding: 0 8px;
font-size: 10.5px;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-portal-panel{
gap: 6px;
}
.glossary-portal-panel__head{
gap: 6px;
}
.glossary-portal-panel__count{
min-height: 21px;
padding: 0 7px;
font-size: 10px;
}
}
@media (prefers-color-scheme: dark){
.glossary-portal-panel--surface{
background: rgba(255,255,255,0.04);
}
}
</style>

View File

@@ -0,0 +1,143 @@
---
interface Props {
id: string;
title: string;
count?: string;
intro?: string;
final?: boolean;
className?: string;
}
const {
id,
title,
count,
intro,
final = false,
className,
} = Astro.props;
---
<section class:list={["glossary-portal-section", final && "glossary-portal-section--final", className]}>
<div class="glossary-portal-section__head">
<h2 id={id}>{title}</h2>
{count && <span class="glossary-portal-section__count">{count}</span>}
</div>
{intro && <p class="glossary-portal-section__intro">{intro}</p>}
<slot />
</section>
<style>
.glossary-portal-section{
margin-top: 30px;
}
.glossary-portal-section h2{
margin: 0;
font-size: clamp(1.8rem, 3vw, 2.35rem);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 800;
}
.glossary-portal-section__head{
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.glossary-portal-section__count{
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 10px;
border: 1px solid rgba(127,127,127,0.20);
border-radius: 999px;
background: rgba(127,127,127,0.04);
font-size: 12px;
line-height: 1.2;
opacity: .8;
white-space: nowrap;
}
.glossary-portal-section__intro{
margin: 0;
max-width: 76ch;
font-size: var(--portal-body-size, 1rem);
line-height: var(--portal-body-lh, 1.55);
opacity: .94;
}
.glossary-portal-section--final{
margin-top: 34px;
}
@media (max-width: 980px){
.glossary-portal-section{
margin-top: 24px;
}
.glossary-portal-section h2{
font-size: clamp(1.6rem, 4.4vw, 2rem);
line-height: 1.04;
}
}
@media (max-width: 760px){
.glossary-portal-section{
margin-top: 20px;
}
.glossary-portal-section__head{
gap: 8px;
margin-bottom: 8px;
}
.glossary-portal-section h2{
font-size: clamp(1.34rem, 6.5vw, 1.72rem);
line-height: 1.04;
letter-spacing: -.022em;
}
.glossary-portal-section__count{
min-height: 24px;
padding: 0 8px;
font-size: 11px;
}
.glossary-portal-section--final{
margin-top: 24px;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
.glossary-portal-section{
margin-top: 16px;
}
.glossary-portal-section__head{
gap: 6px;
margin-bottom: 6px;
}
.glossary-portal-section h2{
font-size: clamp(1.12rem, 4.2vw, 1.34rem);
line-height: 1.02;
}
.glossary-portal-section__count{
min-height: 22px;
padding: 0 7px;
font-size: 10px;
}
.glossary-portal-section--final{
margin-top: 18px;
}
}
</style>

View File

@@ -0,0 +1,487 @@
---
interface Props {
heroMoreId: string;
heroToggleId: string;
sectionHeadSelector?: string;
mobileBreakpoint?: number;
autoCollapseDelta?: number;
}
const {
heroMoreId,
heroToggleId,
sectionHeadSelector = ".glossary-portal-section__head",
mobileBreakpoint = 860,
autoCollapseDelta = 160,
} = Astro.props;
---
<script
is:inline
define:vars={{ heroMoreId, heroToggleId, sectionHeadSelector, mobileBreakpoint, autoCollapseDelta }}
>
(() => {
const boot = () => {
const body = document.body;
const root = document.documentElement;
const hero = document.querySelector("[data-glossary-portal-hero]");
const follow = document.getElementById("reading-follow");
const heroMore = document.getElementById(heroMoreId);
const heroToggle = document.getElementById(heroToggleId);
if (!body || !root || !hero || !follow) return;
const BODY_CLASS = "is-glossary-portal-page";
const FOLLOW_ON_CLASS = "glossary-portal-follow-on";
const EXPANDED_CLASS = "glossary-portal-hero-expanded";
const CONDENSED_CLASS = "glossary-portal-hero-condensed";
const mqMobile = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`);
const mqSmallLandscape = window.matchMedia(
"(orientation: landscape) and (max-width: 920px) and (max-height: 520px)"
);
let expandedAtY = null;
let lastScrollY = window.scrollY || 0;
let raf = 0;
let lastFollowOn = null;
let lastCondensed = null;
let lastHeroHeight = -1;
body.classList.add(BODY_CLASS);
const isCompactViewport = () =>
mqMobile.matches || mqSmallLandscape.matches;
const stripLocalSticky = () => {
document.querySelectorAll(sectionHeadSelector).forEach((el) => {
el.classList.remove("is-sticky");
el.removeAttribute("data-sticky-active");
});
};
const readStickyTop = () => {
const raw = getComputedStyle(document.documentElement)
.getPropertyValue("--glossary-sticky-top")
.trim();
const n = Number.parseFloat(raw);
return Number.isFinite(n) ? n : 64;
};
const computeFollowOn = () =>
!isCompactViewport() &&
follow.classList.contains("is-on") &&
follow.style.display !== "none" &&
follow.getAttribute("aria-hidden") !== "true";
const computeCondensed = () => {
if (isCompactViewport()) return false;
const heroRect = hero.getBoundingClientRect();
const stickyTop = readStickyTop();
return heroRect.top <= stickyTop + 2;
};
const measureHeroHeight = () =>
Math.max(0, Math.round(hero.getBoundingClientRect().height || 0));
const PIN_EPS = 3;
const isHeroPinned = () => {
if (isCompactViewport()) return false;
const rect = hero.getBoundingClientRect();
const stickyTop = readStickyTop();
const cs = getComputedStyle(hero);
if (cs.position !== "sticky") return false;
const pinnedOnRail = Math.abs(rect.top - stickyTop) <= PIN_EPS;
const stillVisible = rect.bottom > stickyTop + 24;
return pinnedOnRail && stillVisible;
};
const applyLocalStickyHeight = () => {
const h = isHeroPinned() ? measureHeroHeight() : 0;
if (h === lastHeroHeight) return;
lastHeroHeight = h;
if (typeof window.__archiSetLocalStickyHeight === "function") {
window.__archiSetLocalStickyHeight(h);
} else {
root.style.setProperty("--glossary-local-sticky-h", `${h}px`);
}
};
const syncFollowState = () => {
const on = computeFollowOn();
if (on !== lastFollowOn) {
lastFollowOn = on;
body.classList.toggle(FOLLOW_ON_CLASS, on);
}
return on;
};
const syncCondensedState = () => {
const condensed = computeCondensed();
if (condensed !== lastCondensed) {
lastCondensed = condensed;
body.classList.toggle(CONDENSED_CLASS, condensed);
}
return condensed;
};
const collapseHero = () => {
if (!body.classList.contains(EXPANDED_CLASS)) return;
body.classList.remove(EXPANDED_CLASS);
expandedAtY = null;
if (heroMore) {
heroMore.setAttribute("aria-hidden", "true");
}
if (heroToggle) {
heroToggle.hidden = false;
heroToggle.setAttribute("aria-expanded", "false");
}
try {
window.__archiUpdateFollow?.();
} catch {}
schedule();
};
const expandHero = () => {
body.classList.add(EXPANDED_CLASS);
expandedAtY = window.scrollY || 0;
if (heroMore) {
heroMore.setAttribute("aria-hidden", "false");
}
if (heroToggle) {
heroToggle.hidden = true;
heroToggle.setAttribute("aria-expanded", "true");
}
try {
window.__archiUpdateFollow?.();
} catch {}
schedule();
};
const syncHeroState = (condensed) => {
const expanded = body.classList.contains(EXPANDED_CLASS);
const collapsed = condensed && !expanded;
if (isCompactViewport() || !condensed) {
body.classList.remove(EXPANDED_CLASS);
expandedAtY = null;
if (heroMore) {
heroMore.setAttribute("aria-hidden", "false");
}
if (heroToggle) {
heroToggle.hidden = true;
heroToggle.setAttribute("aria-expanded", "false");
}
return;
}
if (heroMore) {
heroMore.setAttribute("aria-hidden", collapsed ? "true" : "false");
}
if (heroToggle) {
heroToggle.hidden = !collapsed;
heroToggle.setAttribute("aria-expanded", expanded ? "true" : "false");
}
};
const maybeAutoCollapseOnScroll = () => {
if (isCompactViewport()) {
lastScrollY = window.scrollY || 0;
return;
}
if (!body.classList.contains(EXPANDED_CLASS)) {
lastScrollY = window.scrollY || 0;
return;
}
if (expandedAtY == null) {
lastScrollY = window.scrollY || 0;
return;
}
const currentY = window.scrollY || 0;
const scrollingDown = currentY > lastScrollY;
const delta = currentY - expandedAtY;
if (scrollingDown && delta >= autoCollapseDelta) {
collapseHero();
}
lastScrollY = currentY;
};
const syncAll = () => {
stripLocalSticky();
if (isCompactViewport()) {
body.classList.remove(FOLLOW_ON_CLASS);
body.classList.remove(CONDENSED_CLASS);
body.classList.remove(EXPANDED_CLASS);
lastFollowOn = false;
lastCondensed = false;
expandedAtY = null;
if (heroMore) {
heroMore.setAttribute("aria-hidden", "false");
}
if (heroToggle) {
heroToggle.hidden = true;
heroToggle.setAttribute("aria-expanded", "false");
}
requestAnimationFrame(() => {
applyLocalStickyHeight();
try {
window.__archiUpdateFollow?.();
} catch {}
});
return;
}
const condensed = syncCondensedState();
syncHeroState(condensed);
requestAnimationFrame(() => {
applyLocalStickyHeight();
syncFollowState();
try {
window.__archiUpdateFollow?.();
} catch {}
});
requestAnimationFrame(() => {
applyLocalStickyHeight();
try {
window.__archiUpdateFollow?.();
} catch {}
});
};
const schedule = () => {
if (raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
syncAll();
});
};
heroToggle?.addEventListener("click", expandHero);
const onScroll = () => {
maybeAutoCollapseOnScroll();
schedule();
};
const followObserver = new MutationObserver(schedule);
followObserver.observe(follow, {
attributes: true,
attributeFilter: ["class", "style", "aria-hidden"],
subtree: false,
});
const heroResizeObserver =
typeof ResizeObserver !== "undefined"
? new ResizeObserver(schedule)
: null;
heroResizeObserver?.observe(hero);
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", schedule);
window.addEventListener("pageshow", schedule);
if (document.fonts?.ready) {
document.fonts.ready.then(schedule).catch(() => {});
}
if (mqMobile.addEventListener) {
mqMobile.addEventListener("change", schedule);
} else if (mqMobile.addListener) {
mqMobile.addListener(schedule);
}
if (mqSmallLandscape.addEventListener) {
mqSmallLandscape.addEventListener("change", schedule);
} else if (mqSmallLandscape.addListener) {
mqSmallLandscape.addListener(schedule);
}
schedule();
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot, { once: true });
} else {
boot();
}
})();
</script>
<style>
:global(body.is-glossary-portal-page #reading-follow){
z-index: 10;
}
/* Le hero se condense dès quil devient sticky */
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero){
padding:
var(--portal-hero-pad-top-condensed, 14px)
var(--portal-hero-pad-x-condensed, 16px)
var(--portal-hero-pad-bottom-condensed, 16px);
row-gap: var(--portal-hero-gap-condensed, 10px);
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.02),
0 8px 20px rgba(0,0,0,0.12);
}
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero h1){
font-size: var(--portal-hero-h1-size-condensed, clamp(2.05rem, 3.15vw, 2.7rem));
line-height: var(--portal-hero-h1-lh-condensed, 1);
letter-spacing: var(--portal-hero-h1-spacing-condensed, -.04em);
}
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero__intro){
max-width: var(--portal-hero-follow-intro-max-w, 62ch);
font-size: var(--portal-hero-intro-size-condensed, .98rem);
line-height: var(--portal-hero-intro-lh-condensed, 1.46);
}
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero__kicker){
opacity: .68;
}
/* Le more se replie dès létat condensé */
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed:not(.glossary-portal-hero-expanded) .glossary-portal-hero__more){
max-height: 0;
opacity: 0;
overflow: hidden;
pointer-events: none;
}
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed:not(.glossary-portal-hero-expanded) .glossary-portal-hero__toggle){
display: inline-flex;
}
/* Laccolage hero + follow narrive que quand le follow est actif */
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed.glossary-portal-follow-on .glossary-portal-hero){
margin-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
:global(body.is-glossary-portal-page.glossary-portal-follow-on #reading-follow .reading-follow__inner){
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
:global(body.is-glossary-portal-page.glossary-portal-follow-on #reading-follow .rf-h2){
letter-spacing: -.02em;
}
:global(body.is-glossary-portal-page .glossary-portal-section__head.is-sticky),
:global(body.is-glossary-portal-page .glossary-portal-section__head[data-sticky-active="true"]){
position: static !important;
top: auto !important;
z-index: auto !important;
padding: 0 !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
@media (max-width: 860px){
:global(body.is-glossary-portal-page #reading-follow),
:global(body.is-glossary-portal-page #reading-follow .reading-follow__inner){
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
:global(body.is-glossary-portal-page){
--followbar-h: 0px !important;
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero){
margin-bottom: var(--portal-hero-margin-bottom, 18px);
border-radius: 20px !important;
box-shadow: none !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero__more){
max-height: none !important;
opacity: 1 !important;
overflow: visible !important;
pointer-events: auto !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero__toggle){
display: none !important;
}
}
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
:global(body.is-glossary-portal-page #reading-follow),
:global(body.is-glossary-portal-page #reading-follow .reading-follow__inner){
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
:global(body.is-glossary-portal-page){
--followbar-h: 0px !important;
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero){
margin-bottom: var(--portal-hero-margin-bottom, 12px);
border-radius: 16px !important;
box-shadow: none !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero__more){
max-height: none !important;
opacity: 1 !important;
overflow: visible !important;
pointer-events: auto !important;
}
:global(body.is-glossary-portal-page .glossary-portal-hero__toggle){
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,125 @@
---
import type { GlossaryRelationBlock } from "../lib/glossary";
import { hrefOfGlossaryEntry } from "../lib/glossary";
interface Props {
relationBlocks: GlossaryRelationBlock[];
}
const { relationBlocks = [] } = Astro.props;
const relationsHeadingId = "relations-conceptuelles";
---
{relationBlocks.length > 0 && (
<section
class="glossary-relations"
aria-labelledby={relationsHeadingId}
>
<h2 id={relationsHeadingId}>Relations conceptuelles</h2>
<div class="glossary-relations-grid">
{relationBlocks.map((block) => (
<section class={`glossary-relations-card ${block.className}`}>
<h3>{block.title}</h3>
<ul>
{block.items.map((item) => (
<li>
<a href={hrefOfGlossaryEntry(item)}>{item.data.term}</a>
<span> — {item.data.definitionShort}</span>
</li>
))}
</ul>
</section>
))}
</div>
</section>
)}
<style>
.glossary-relations{
margin-top: 22px;
padding-top: 14px;
border-top: 1px solid rgba(127,127,127,0.18);
}
.glossary-relations h2{
margin: 0 0 12px;
font-size: clamp(1.35rem, 3vw, 1.8rem);
line-height: 1.08;
}
.glossary-relations-grid{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
}
.glossary-relations-card{
border: 1px solid rgba(127,127,127,0.20);
border-radius: 14px;
padding: 12px 13px;
background: rgba(127,127,127,0.05);
}
.glossary-relations-card h3{
margin: 0 0 8px;
font-size: 14px;
line-height: 1.3;
}
.glossary-relations-card ul{
margin: 0;
padding-left: 16px;
}
.glossary-relations-card li{
margin-bottom: 7px;
font-size: 13.5px;
line-height: 1.42;
}
.glossary-relations-card li:last-child{
margin-bottom: 0;
}
.glossary-relations-card span{
opacity: .88;
}
@media (max-width: 760px){
.glossary-relations{
margin-top: 18px;
padding-top: 12px;
}
.glossary-relations h2{
margin-bottom: 10px;
font-size: 1.3rem;
}
.glossary-relations-grid{
grid-template-columns: 1fr;
gap: 8px;
}
.glossary-relations-card{
padding: 11px 11px;
border-radius: 13px;
}
.glossary-relations-card h3{
font-size: 13px;
margin-bottom: 7px;
}
.glossary-relations-card li{
font-size: 13px;
line-height: 1.38;
}
}
@media (prefers-color-scheme: dark){
.glossary-relations-card{
background: rgba(255,255,255,0.04);
}
}
</style>

View File

@@ -68,7 +68,6 @@ const { initialLevel = 1 } = Astro.props;
} catch {}
}
// init : storage > initialLevel
let start = clampLevel(initialLevel);
try {
const stored = localStorage.getItem(KEY);
@@ -77,13 +76,11 @@ const { initialLevel = 1 } = Astro.props;
applyLevel(start, { persist: false });
// clicks
wrap.addEventListener("click", (ev) => {
const btn = ev.target?.closest?.("button[data-level]");
if (!btn) return;
ev.preventDefault();
// ✅ crucial : on capture la position AVANT le reflow lié au changement de niveau
captureBeforeLevelSwitch();
applyLevel(btn.dataset.level);
});
@@ -95,6 +92,8 @@ const { initialLevel = 1 } = Astro.props;
display: inline-flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
max-width: 100%;
}
.level-btn{
@@ -106,6 +105,7 @@ const { initialLevel = 1 } = Astro.props;
cursor: pointer;
user-select: none;
transition: filter .12s ease, transform .12s ease, background .12s ease, border-color .12s ease;
white-space: nowrap;
}
.level-btn:hover{
@@ -125,4 +125,21 @@ const { initialLevel = 1 } = Astro.props;
.level-btn:active{
transform: translateY(1px);
}
</style>
@media (max-width: 980px){
.level-toggle{
gap: 6px;
}
.level-btn{
padding: 5px 9px;
font-size: 12px;
}
}
@media (max-width: 760px){
.level-toggle{
display: none;
}
}
</style>

View File

@@ -3,49 +3,122 @@ const { headings } = Astro.props;
// H2/H3 seulement
const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
const tocId = `toc-local-${Math.random().toString(36).slice(2, 9)}`;
---
{items.length > 0 && (
<nav class="toc-local" aria-label="Dans ce chapitre">
<div class="toc-local__title">Dans ce chapitre</div>
<nav class="toc-local" aria-label="Dans ce chapitre" data-toc-local>
<button
class="toc-local__head toc-local__toggle"
type="button"
aria-expanded="true"
aria-controls={tocId}
>
<span class="toc-local__title">Dans ce chapitre</span>
<span class="toc-local__chevron" aria-hidden="true">▾</span>
</button>
<ol class="toc-local__list">
{items.map((h) => (
<li
class={`toc-local__item d${h.depth}`}
data-toc-item
data-depth={h.depth}
data-id={h.slug}
>
<a href={`#${h.slug}`} data-toc-link data-slug={h.slug}>
{h.text}
</a>
</li>
))}
</ol>
<div class="toc-local__body-clip" id={tocId}>
<div class="toc-local__body">
<ol class="toc-local__list">
{items.map((h) => (
<li
class={`toc-local__item d${h.depth}`}
data-toc-item
data-depth={h.depth}
data-id={h.slug}
>
<a href={`#${h.slug}`} data-toc-link data-slug={h.slug}>
<span class="toc-local__mark" aria-hidden="true"></span>
<span class="toc-local__text">{h.text}</span>
</a>
</li>
))}
</ol>
</div>
</div>
</nav>
)}
<script is:inline>
(() => {
function init() {
const toc = document.querySelector(".toc-local");
if (!toc) return;
const toc = document.querySelector(".toc-local[data-toc-local]");
if (!toc || toc.dataset.tocReady === "1") return;
toc.dataset.tocReady = "1";
const toggle = toc.querySelector(".toc-local__toggle");
const bodyClip = toc.querySelector(".toc-local__body-clip");
const mq = window.matchMedia("(max-width: 980px)");
const KEY = `archicratie:toc-local:${window.location.pathname}`;
if (!toggle || !bodyClip) return;
const readState = () => {
try {
const v = localStorage.getItem(KEY);
if (v === "open") return true;
if (v === "closed") return false;
} catch {}
return null;
};
const writeState = (open) => {
try { localStorage.setItem(KEY, open ? "open" : "closed"); } catch {}
};
const setOpen = (open, { persist = true, emit = true } = {}) => {
const isMobile = mq.matches;
toc.classList.toggle("is-collapsed", isMobile && !open);
toggle.setAttribute("aria-expanded", open ? "true" : "false");
if (persist && isMobile) writeState(open);
if (emit && open && isMobile) {
window.dispatchEvent(new CustomEvent("archicratie:tocLocalOpen"));
}
};
const initAccordion = () => {
if (!mq.matches) {
setOpen(true, { persist: false, emit: false });
return;
}
const stored = readState();
setOpen(stored == null ? true : stored, { persist: false, emit: false });
};
toggle.addEventListener("click", () => {
const next = toggle.getAttribute("aria-expanded") !== "true";
setOpen(next);
});
if (mq.addEventListener) {
mq.addEventListener("change", initAccordion);
} else if (mq.addListener) {
mq.addListener(initAccordion);
}
const itemEls = Array.from(toc.querySelectorAll("[data-toc-item]"));
if (!itemEls.length) return;
if (!itemEls.length) {
initAccordion();
return;
}
const ordered = itemEls
.map((li) => {
const a = li.querySelector("a[data-toc-link]");
const id = li.getAttribute("data-id") || a?.dataset.slug || "";
const depth = Number(li.getAttribute("data-depth") || "0");
const el = id ? document.getElementById(id) : null; // span.details-anchor OU h3[id]
const el = id ? document.getElementById(id) : null;
return (a && id && el) ? { id, depth, li, a, el } : null;
})
.filter(Boolean);
if (!ordered.length) return;
if (!ordered.length) {
initAccordion();
return;
}
const clear = () => {
for (const t of ordered) {
@@ -55,14 +128,29 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
};
const openDetailsIfNeeded = (el) => {
const d = el?.closest?.("details");
if (d && !d.open) d.open = true;
try {
if (!el) return;
let d = el.closest?.("details") || null;
if (!d && el.classList?.contains("details-anchor")) {
const n = el.nextElementSibling;
if (n && n.tagName === "DETAILS") d = n;
}
if (!d) {
const s = el.closest?.("summary");
if (s && s.parentElement && s.parentElement.tagName === "DETAILS") d = s.parentElement;
}
if (d && d.tagName === "DETAILS" && !d.open) d.open = true;
} catch {}
};
let current = "";
const setCurrent = (id) => {
if (!id || id === current) return;
const setCurrent = (id, { autoOpen = true } = {}) => {
if (!id) return;
const t = ordered.find((x) => x.id === id);
if (!t) return;
@@ -74,17 +162,21 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
t.a.setAttribute("aria-current", "true");
t.li.classList.add("is-current");
// ✅ IMPORTANT: plus de scrollIntoView ici
// sinon ça scroll l'aside pendant le scroll du reading => TOC global “disparaît”.
if (mq.matches && autoOpen && toc.classList.contains("is-collapsed")) {
setOpen(true);
}
window.dispatchEvent(
new CustomEvent("archicratie:tocLocalActive", { detail: { id } })
);
};
const computeActive = () => {
const visible = ordered.filter((t) => {
const d = t.el.closest?.("details");
if (d && !d.open) {
// Si l'élément est dans <summary>, il reste visible même details fermé
const inSummary = !!t.el.closest?.("summary");
if (!inSummary) return false;
if (!inSummary && !t.el.classList?.contains("details-anchor")) return false;
}
return true;
});
@@ -102,7 +194,7 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
}
if (!best) best = visible[0];
setCurrent(best.id);
if (best && best.id !== current) setCurrent(best.id, { autoOpen: true });
};
let ticking = false;
@@ -117,11 +209,14 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
const syncFromHash = () => {
const id = (location.hash || "").slice(1);
if (!id) { computeActive(); return; }
if (!id) {
computeActive();
return;
}
const el = document.getElementById(id);
if (el) openDetailsIfNeeded(el);
setCurrent(id);
setCurrent(id, { autoOpen: true });
};
toc.addEventListener("click", (ev) => {
@@ -133,13 +228,14 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
const el = document.getElementById(id);
if (el) openDetailsIfNeeded(el);
setCurrent(id);
setCurrent(id, { autoOpen: true });
});
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
window.addEventListener("hashchange", syncFromHash);
initAccordion();
syncFromHash();
onScroll();
}
@@ -153,30 +249,183 @@ const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
</script>
<style>
.toc-local{margin-top:12px;border:1px solid rgba(127,127,127,.25);border-radius:16px;padding:12px}
.toc-local__title{font-size:13px;opacity:.85;margin-bottom:8px}
.toc-local__list{list-style:none;margin:0;padding:0}
.toc-local__item::marker{content:""}
.toc-local__item{margin:6px 0}
.toc-local__item.d3{margin-left:12px;opacity:.9}
.toc-local__item.is-current > a{
font-weight: 750;
text-decoration: underline;
.toc-local{
margin-top: 12px;
border: 1px solid rgba(127,127,127,.25);
border-radius: 16px;
padding: 12px;
background: rgba(127,127,127,0.03);
}
.toc-local a{
display:inline-block;
max-width:100%;
text-decoration:none;
.toc-local__toggle{
width: 100%;
appearance: none;
border: 0;
background: transparent;
color: inherit;
text-align: left;
padding: 0;
cursor: pointer;
}
.toc-local a:hover{ text-decoration: underline; }
.toc-local__head{
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.toc-local__title{
font-size: 13px;
opacity: .85;
}
.toc-local__chevron{
font-size: 12px;
opacity: .72;
transition: transform 180ms ease;
}
.toc-local__body-clip{
display: grid;
grid-template-rows: 1fr;
transition:
grid-template-rows 220ms ease,
opacity 160ms ease,
margin-top 220ms ease;
}
.toc-local__body{
min-height: 0;
overflow: hidden;
}
.toc-local__list{
list-style: none;
margin: 0;
padding: 0;
max-height: 44vh;
overflow: auto;
padding-right: 8px;
scrollbar-gutter: stable;
}
</style>
.toc-local__item::marker{ content:""; }
.toc-local__item{ margin: 6px 0; }
.toc-local__item.d3{
margin-left: 14px;
opacity: .94;
}
.toc-local a{
display: grid;
grid-template-columns: auto 1fr;
gap: 8px;
align-items: start;
max-width: 100%;
text-decoration: none;
}
.toc-local a:hover{
text-decoration: none;
}
.toc-local__mark{
width: 10px;
height: 10px;
margin-top: .36em;
border-radius: 999px;
border: 1px solid rgba(127,127,127,.34);
background: transparent;
opacity: .68;
}
.toc-local__text{
line-height: 1.28;
}
.toc-local__item.is-current > a{
font-weight: 760;
}
.toc-local__item.is-current > a .toc-local__mark{
background: currentColor;
border-color: currentColor;
box-shadow: 0 0 0 3px rgba(127,127,127,.10);
opacity: 1;
}
@media (max-width: 980px){
.toc-local{
padding: 10px 12px;
border-radius: 14px;
}
.toc-local__head{
margin-bottom: 0;
min-height: 28px;
}
.toc-local__body-clip{
margin-top: 10px;
}
.toc-local.is-collapsed .toc-local__body-clip{
grid-template-rows: 0fr;
opacity: 0;
margin-top: 0;
}
.toc-local__body{
min-height: 0;
overflow: hidden;
transition: opacity 180ms ease;
}
.toc-local.is-collapsed .toc-local__body{
opacity: 0;
}
.toc-local.is-collapsed .toc-local__chevron{
transform: rotate(-90deg);
}
.toc-local__title{
font-size: 13px;
}
.toc-local__list{
max-height: min(42vh, 360px);
padding-right: 4px;
}
.toc-local__item{
margin: 5px 0;
}
.toc-local__item.d2 > a .toc-local__text{
font-size: 12.9px;
line-height: 1.24;
font-weight: 680;
}
.toc-local__item.d3{
margin-left: 12px;
}
.toc-local__item.d3 > a .toc-local__text{
font-size: 12.1px;
line-height: 1.22;
opacity: .95;
}
.toc-local__item.d3 > a .toc-local__mark{
width: 8px;
height: 8px;
margin-top: .42em;
opacity: .55;
}
}
</style>

View File

@@ -382,7 +382,7 @@
return;
}
if (Array.isArray(data.authors) && data.authors.length) {
if (Array.isArray(data.mobilizedAuthors) && data.mobilizedAuthors.length) {
const h = document.createElement("h3");
h.className = "panel-subtitle";
h.textContent = "Auteurs";
@@ -390,7 +390,7 @@
const ul = document.createElement("ul");
ul.className = "panel-list";
for (const a of data.authors) {
for (const a of data.mobilizedAuthors) {
const li = document.createElement("li");
li.textContent = esc(a);
ul.appendChild(li);

View File

@@ -1,11 +1,17 @@
<nav class="site-nav" aria-label="Navigation principale">
<a href="/">Accueil</a><span aria-hidden="true"> · </span>
<a href="/editions/">Carte des œuvres</a><span aria-hidden="true"> · </span>
<a href="/methode/">Méthode</a><span aria-hidden="true"> · </span>
<a href="/recherche/">Recherche</a><span aria-hidden="true"> · </span>
<a href="/archicrat-ia/">Essai-thèse</a><span aria-hidden="true"> · </span>
<a href="/traite/">Traité</a><span aria-hidden="true"> · </span>
<a href="/ia/">Cas IA</a><span aria-hidden="true"> · </span>
<a href="/glossaire/">Glossaire</a><span aria-hidden="true"> · </span>
<a href="/atlas/">Atlas</a>
</nav>
<a href="/">Accueil</a>
<span aria-hidden="true"> · </span>
<a href="/archicrat-ia/">Essai-thèse</a>
<span aria-hidden="true"> · </span>
<a href="/cas-ia/">Cas IA</a>
<span aria-hidden="true"> · </span>
<a href="/glossaire/">Glossaire</a>
<span aria-hidden="true"> · </span>
<a href="/recherche/">Recherche</a>
</nav>

View File

@@ -17,35 +17,20 @@ const baseTextSchema = z.object({
});
// Éditions (séparation stricte : edition + status verrouillés par collection)
const traite = defineCollection({
type: "content",
schema: baseTextSchema.extend({
edition: z.literal("traite"),
status: z.literal("theorie_fondamentale")
})
});
const archicratie = defineCollection({
const casIa = defineCollection({
type: "content",
schema: baseTextSchema.extend({
edition: z.literal("archicratie"),
status: z.literal("modele_sociopolitique")
})
});
const ia = defineCollection({
type: "content",
schema: baseTextSchema.extend({
edition: z.literal("ia"),
edition: z.literal("cas-ia"),
status: z.literal("application")
})
});
const atlas = defineCollection({
const commencer = defineCollection({
type: "content",
schema: baseTextSchema.extend({
edition: z.literal("atlas"),
status: z.literal("cartographie")
edition: z.literal("commencer"),
status: z.union([z.literal("presentation"), z.literal("draft")])
})
});
@@ -69,22 +54,59 @@ const glossaire = defineCollection({
title: z.string().min(1),
term: z.string().min(1),
aliases: z.array(z.string().min(1)).default([]),
urlAliases: z
.array(z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/))
.default([]),
mobilizedAuthors: z.array(z.string().min(1)).default([]),
comparisonTraditions: z.array(z.string().min(1)).default([]),
edition: z.literal("glossaire"),
status: z.literal("referentiel"),
version: z.string().min(1),
definitionShort: z.string().min(1),
concepts: z.array(z.string().min(1)).default([]),
links: z.array(linkSchema).default([])
links: z.array(linkSchema).default([]),
kind: z.enum([
"concept",
"topologie",
"diagnostic",
"verbe",
"paradigme",
"doctrine",
"dispositif",
"figure",
"qualification",
"epistemologie",
]),
family: z.enum([
"concept-fondamental",
"scene",
"dynamique",
"pathologie",
"topologie",
"meta-regime",
"paradigme",
"doctrine",
"verbe",
"dispositif-ia",
"tension-irreductible",
"figure",
"qualification",
"epistemologie",
]
)
.optional(),
domain: z.enum(["transversal", "theorie", "cas-ia"]),
level: z.enum(["fondamental", "intermediaire", "avance"]),
related: z.array(z.string().min(1)).default([]),
opposedTo: z.array(z.string().min(1)).default([]),
seeAlso: z.array(z.string().min(1)).default([])
})
});
export const collections = {
traite,
archicratie,
ia,
commencer,
"archicrat-ia": archicratIa,
"cas-ia": casIa,
glossaire,
atlas,
// ⚠️ clé avec tiret => doit être quotée
"archicrat-ia": archicratIa
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,251 +1,417 @@
---
title: "Conclusion — ArchiCraT-IA"
edition: "archicratie"
status: "modele_sociopolitique"
title: Conclusion — ArchiCraT-IA
edition: archicrat-ia
status: essai_these
level: 1
version: "0.1.0"
version: 0.1.0
concepts: []
links: []
order: 70
summary: ""
summary: ''
source:
kind: docx
path: "sources/docx/archicrat-ia/Conclusion-Archicrat-IA-version_officielle.docx"
path: sources/docx/archicrat-ia/Conclusion-Archicrat-IA-version_officielle.docx
---
Nous nassistons ni à un retour primordial du désordre ni à une raréfaction du pouvoir. Nous assistons à autre chose, plus discret et plus décisif : la montée en régime dune régulation sans scène, où la plupart des décisions qui engagent nos vies sont prises et appliquées sans lieu institué dénonciation, sans temps de différé, sans exposition reconnaissable de leurs auteurs. La conflictualité ne disparaît pas, elle est déportée : reconfigurée en flux, en scores, en interfaces, en procédures qui se présentent comme de simples enchaînements techniques, alors même quelles opèrent des choix normatifs massifs.
Ce manque nest pas un vide. Il ne signifie ni labsence de normes, ni leffondrement du pouvoir, mais ce que nous avons appelé, dans cet essai-thèse, une *oblitération archicratique* : le recouvrement progressif de la scène par la seule *cratialité*, cest-à-dire par des dispositifs dexécution qui se substituent à la mise en débat des fondements et des effets. Ce nest pas le pouvoir qui manque, cest la possibilité de le voir, de le nommer, de ladresser. La régulation se donne alors comme évidence fonctionnelle, là où elle devrait apparaître comme ce quelle est toujours : un ordre situé, contestable et révisable en respect des promesses démocratiques.
La thèse défendue ici nest pas que toute scène aurait disparu, ni que lhistoire se réduirait à un glissement univoque vers lautomatisation intégrale. Elle est plus précise et plus falsifiable : dans les principaux dispositifs contemporains sociaux, écologiques, numériques , les conditions dexistence dune scène archicrative sont systématiquement affaiblies, fragmentées, reléguées à la marge. Là où lon pouvait autrefois identifier un lieu, un temps, des personnes et des procédures pour organiser et mettre à lépreuve les décisions, nous rencontrons désormais, de manière récurrente, des agencements où lénonciation sefface derrière la mesure, la simulation, la plateformisation et lautomatisation.
Cest pourquoi nous avons forgé la notion d*oblitération archicratique* : non pas labsence de tout recours, mais la tendance structurelle à produire de la norme hors scène, à démultiplier les dispositifs auto-référés architectures, algorithmes, barèmes, chaînes logistiques les supports dune régulation qui se déclare purement opératoire, *autarchicratique*. Cette hypothèse est réfutable : elle serait démentie si lon observait, de façon durable, que les grandes décisions collectives demeurent adossées à des scènes dépreuve robustes, effectives, où les fondements et les effets peuvent être mis en discussion. Elle gagnerait, inversement, en pertinence si des enquêtes empiriques mettent en évidence linaccessibilité des recours, lineffectivité des délibérations politiques, la surdétermination des décisions par des scripts non publics protégés par le droit des affaires.
Dans cette perspective, l*archicratie* nest ni un idéal abstrait, ni un type de régime supplémentaire quil sagirait dajouter à la liste déjà longue des formes politiques. Elle désigne la *condition minimale pour que la régulation soit habitable* : la possibilité, pour une société, dinstituer et de maintenir un seuil où l*arcalité* (ce qui fonde et justifie), la *cratialité* (ce qui opère et exécute) et l*archicration* (ce qui met en épreuve, suspend, requalifie) demeurent distinctes, articulées et exposées. Un ordre est archicratique en ce sens précis : non parce quil serait juste ou égalitaire par essence, mais parce quil se laisse amener en scène, devant des instances où ses prétentions peuvent être critiquées, ses instruments retravaillés, ses effets révisés.
Lenjeu de cette conclusion est de formuler une hypothèse régulatrice : la *co-viabilité* des collectifs humains et non-humains dépend de la capacité à maintenir, à rouvrir, à inventer des scènes dépreuve où lon puisse adresser le pouvoir qui nous affecte. En nommant l*archicratie*, nous ne sacralisons pas un modèle ; nous désignons un seuil au-dessous duquel la régulation se dégrade en pure opération, et au-dessus duquel elle devient au moins discutable, opposable, révisable, modifiable, différant.
Cest là seulement que prend sens le projet dune “cinquième révolution régulatrice”. Non pas un nouvel âge dor, ni une résolution miraculeuse des contradictions, mais un changement de régime dans la manière même de fabriquer la régulation : un tournant où la scène longtemps tenue pour « simple décor du pouvoir », ou pour « luxe démocratique » redeviendrait son opérateur central. Lambition de ce texte nest pas de prédire cette révolution, et moins encore de la prescrire. Il sagit de montrer en quel sens elle est pensable, souhaitable, et sous quelles conditions elle pourrait être mise à lépreuve et mise en pratique.
Ce que nous observons nest ni une défaillance accidentelle du cadre démocratique, ni une déviation autoritaire des gouvernances contemporaines, mais une mutation profonde des formes de régulation effective qui tendent à se configurer hors-scène démocratique instituée. Dans ce modèle, le pouvoir tend à se déporter hors scène instituée : privatisé, déterritorialisé, encapsulé, automatisé, rendu algorithme, crypté, incompréhensible et incritiquable du fait de sa propre efficacité. Il ne sexplique plus, il fonctionne. Il ne sadresse plus, il déclenche. Il ne fonde plus, il applique. Et la régulation tend à se déporter hors scène instituée, tandis que persistent des scènes partielles, alternatives ou multilatérales quil faut reconnaître et saluer. Que ce soit la méta-fédération des soulèvements de la Terre, ou linitiative internationale PauseIA, pour ne nommer que ces deux, leurs discours et leurs expertises apportent informations et discernements sur les enjeux écologiques et les développements dintelligences artificielles.
Le processus est dautant plus difficile à saisir quil est continu, fonctionnel, présenté comme neutre. Les interfaces remplacent les guichets, les tableaux de bord supplantent les délibérations, les retours algorithmiques prennent la place de la justification publique. Le conflit est désamorcé au nom de la fluidité. Le délai est perçu comme dysfonction. Lexposition est dégradée en transparence visuelle sans énoncé.
Le terme doblitération archicratique, proposé et conceptualisé dans les chapitres précédents, désigne exactement cette dynamique : une substitution douce, mais décisive, de la scène par lexécution, du différé par lautomaticité, de lénonciation par la trace, de lépreuve par la donnée. Cette oblitération ne se donne pas comme rupture, mais comme évidence : elle agit dans la logique même des dispositifs, dans leur conception, leur design, leur temporalité.
Il faut ici refuser toute simplification. Si la scène instituée se raréfie, des scènes latérales, résiduelles ou émergentes persistent (communautés locales, contre-institutions, pratiques rituelles), il est grand temps de les reconnaître et de les légitimer au lieu de les folkloriser. Ce monde sans scène nest ni totalement anarchique, ni totalement chaotique, ni même nécessairement injuste dans ses effets immédiats. Il est, en revanche, injustifiable. Il est insoutenable dans le temps, car il est aveugle à sa propre normativité. Il ne dispose daucun espace dans lequel ses propres décisions peuvent être rejouées, exposées, interrogées, rediscutées ou reformulées.
Cette régulation sans scène est une régulation sans mémoire, sans récit, sans réversibilité. Elle fonctionne, et souvent très efficacement. Mais elle ne critique rien, ne se confronte à rien, ne se relance jamais à partir de ses propres limites. Elle produit de la norme sans fondement énoncé, du pilotage sans institution, de la gouvernance sans fondation, un pouvoir simulé à tout moment proche du simulacre.
Les conséquences sont majeures. On croit pouvoir protester, mais linterlocuteur est absent. On tente un recours, mais le délai est dépassé avant même davoir commencé. On cherche le fondement dune décision, mais on ne trouve quun critère, un score, une règle dapplication sans justification. Derrière la façade defficacité, ce qui sinstalle, cest un monde post-scénique, dans lequel les gouvernés nont plus despace institué pour adresser la régulation qui les constitue.
Ce diagnostic nest pas une vérité révélée, mais une hypothèse régulatrice : il postule que, dans les principaux dispositifs contemporains (sociaux, écologiques, numériques), linstance scénique a été reléguée au profit dune *cratialité autarcique*. Il pourrait être réfuté par des recherches qui montreraient linverse, par exemple des domaines où des scènes archicratives fortes subsistent et structurent réellement les décisions. Il serait confirmé, au contraire, par des enquêtes qui objectiveraient linaccessibilité des scènes, lineffectivité des recours, la pure performativité des algorithmes.
Et cest précisément là que souvre une nécessité, celle de refonder la scène comme condition première de toute co-viabilité. Une scène véritable : espace différé, public, opposable, fondé — nullement décorative ni consultative. Une scène où la décision peut être relancée. Où le fondement peut être demandé. Où le délai est un droit. Où lépreuve est reconnue comme condition de la légitimité.
Cette scène, cest ce que nous avons nommé, construit, modélisé et mis à lépreuve : l*archicration*. Et cest depuis son absence — criante et tout à la fois quasi imperceptible — que la cinquième révolution régulatrice devient pensable et indispensable.
Au seuil dune ère où la capacité à ordonner, à segmenter, à attribuer ou à refuser se maintient sans que jamais ces gestes soient exposés à une scène dépreuve instituée, la nécessité dun concept opératoire, capable de désigner la forme régulatrice minimale dun monde habitable, simpose. Ce concept, l*archicratie*, nous lavons forgé, au-delà de la nécessité dinvention lexicale, comme condensation critique dun constat systémique.
L*archicratie* ne renvoie ni à un type de régime spécifique, ni à une forme de gouvernement, ni même à une institution particulière. Elle ne se laisse classer dans aucune catégorie traditionnelle de la philosophie politique, car elle opère en amont des structures représentatives, et en-deça et au-delà des appareils juridiques. Elle est le mode de consistance scénique dune régulation soutenable, cest-à-dire dun ordre qui accepte dêtre mis en épreuve, dêtre différé dans son exécution, dêtre reconfiguré à partir de sa propre exposition sous critères de viabilité. Il sagit donc du régime dapparition du pouvoir fondé, là où une décision peut être tenue, dans le temps et dans lespace, devant ceux quelle affecte, au sein dune scène constituée pour quelle puisse être questionnée, retardée, amendée, voire annulée.
Dans cette perspective, l*archicratie* nest ni utopie ni abstraction : elle fixe la condition de viabilité de tout dispositif qui prétend réguler — affecter des conduites, orienter des possibles, ouvrir/fermer des accès — sans se refermer sur une logique exclusivement opératoire. Il ne sagit pas ici de défendre une figure morale du pouvoir, mais de poser une exigence structurelle : aucune régulation nest légitime si elle ne peut être exposée à son propre fondement. Et aucune scène dexposition nest opérante si elle ne permet pas la convocation des énonciateurs, la traçabilité des instruments, la suspension des effets, la confrontation des justifications et la reconfiguration des décisions.
L*archicratie* devient ainsi le seuil entre une régulation purement fonctionnelle, capable dexécuter des procédures, de reproduire des normes, de gérer des flux automatisés et une régulation fondée, visible, opposable, justifiable, révisable — cest-à-dire co-viable décidée par des humains.
Comme annoncé dans le Prologue, cette conclusion opère la remontée critique à partir des cinq épreuves de notre essai-thèse : (1) lépreuve de détectabilité des régimes (chapitre 1), (2) larchéogenèse des formes scéniques de régulation (chapitre 2), (3) la morphologie des régimes de pouvoir (chapitre 3), (4) la généalogie des révolutions régulatrices (chapitre 4), et (5) les tensions de co-viabilité au sein des dispositifs contemporains (chapitre 5).
Pour penser ce seuil, notre essai-thèse a posé les conditions de détection dune régulation minimale, structurée autour de trois prises fondamentales, dont la coprésence différenciée et articulée constitue la grammaire de toute régulation habitée. Ces trois prises — *arcalité, cratialité, archicration* — ne sont pas des dimensions logiques. Elles sont des formes dexistence différenciées de lordre régulateur.
Comme nous lavons vu, l*arcalité* désigne la condition dénonciation du pouvoir. Elle ne se réduit pas à une source légale ou à une tradition instituée. Elle renvoie à la capacité dun dispositif à exposer son propre fondement comme fondement, à dire : "voici sur quoi nous agissons, pourquoi, selon quelle vision du monde, selon quelle fiction ou axiome opératoire". Dans une régulation archicratique, l*arcalité* est convoquée, nommée, rendue visible, tenue.
La *cratialité*, quant à elle, nest pas simplement lopérativité technique. Elle est lensemble des vecteurs concrets dapplication du pouvoir, quil sagisse de normes, dalgorithmes, de barèmes, de codes juridiques, de métriques ou dinterfaces. Cest le pouvoir dans sa matérialité dexécution. Loin de nêtre quun détail de mise en œuvre, la *cratialité* constitue une scène en elle-même : celle où les affects se traduisent en effets, celle où le monde se divise, sorganise, se hiérarchise. Toute *cratialité* rendue opaque tend à devenir *hypertopique*.
Enfin, l*archicration* désigne la scène elle-même : lespace-temps de lépreuve régulatrice. Il ne se réduit pas à un dispositif de concertation ou à un droit de recours formel, mais bien plus à une scène effective, où le délai est institué, où les énoncés sont exposés, où les décisions sont suspendues, et où les parties affectées peuvent agir sur les termes mêmes de la régulation. Une *archicration* est une scène fondée, différée, ouverte, documentée, capable de transformer ce quelle expose. Elle se doit donc dêtre publique et aucunement privée.
Ces trois prises ne sont jamais données demblée. Elles sont inégalement distribuées, plus ou moins visibles, plus ou moins consolidées. Ce que permet l*archicratie*, cest den faire un modèle danalyse différentielle, capable dévaluer non ce que les institutions disent delles-mêmes, mais ce quelles rendent effectivement détectable, contestable, modifiable.
Cest à ce titre que nous avons élaboré une axiomatique de la détectabilité régulatrice, qui ne repose pas sur la conformité juridique, mais sur des critères structurels : la coprésence des prises ; leur différenciation fonctionnelle ; la publicité du fondement ; lopposabilité des normes ; la révision périodique ; le droit au différé contradictoire.
Ce modèle ne soppose pas aux régimes existants. Il les requalifie selon une grammaire de viabilité. Il ne dit pas ce qui est souhaitable. Il indique ce qui est soutenable.
Au terme de ce parcours, notre essai-thèse propose dabord une *grammaire minimale de la régulation habitable*, en distinguant rigoureusement *arcalité, cratialité* et *archicration* comme trois prises irréductibles mais articulables du pouvoir. Il offre ensuite une archéogenèse comparée des méta-régimes régulateurs, qui permet de lire des configurations très hétérogènes proto-symboliques, scripturaires, marchandes, guerrières, techno-logistiques sans les rabattre sur le seul État, le seul marché ou la seule souveraineté. Il recompose en outre les “révolutions industrielles” comme des *révolutions régulatrices*, cest-à-dire comme des histoires différentielles de la scène et de son oblitération, jusquau diagnostic contemporain d*autarchicratie* et de *régulation hors scène*. Enfin, il élabore une politique des épreuves viables, en substituant à la fiction de la durabilité le paradigme de la *co-viabilité archicratique*, et en proposant quelques gestes concrets par lesquels cette exigence pourrait être mise à lépreuve dans les institutions, les territoires et les dispositifs numériques.
Là réside le geste décisif : faire de l*archicratie* une condition de lisibilité du pouvoir, dans un monde où les décisions sont de plus en plus produites sans fondement explicité, en accès et décision privés, appliquées sans seuil dentrée et exécutées sans scène dexposition explicite (algorithmes de recommandations, IA générative, etc.).
En nommant l*archicratie*, ce que nous faisons, ce nest pas inventer une utopie : cest dégager, à partir des marges critiques de la modernité régulatrice, un principe de consistance qui permet de discerner ce qui peut encore être appelé régulation — et ce qui ne relève plus que de lautomatisation du monde, son *autarchicratie*.
Comme lécrivait Claude Lefort, “le lieu du pouvoir est vide, et le pouvoir est un lieu duquel personne ne peut sarroger la propriété” : cest ce vide fondateur qui, sans scène, devient mutisme algorithmique. Larchicration ne cherche pas à combler ce vide, mais à en réinstituer ladresse. Car l*archicratie* nest pas lalternative à un système. Elle est ce qui rend possible le fait même quun système puisse tenir, mais aussi être interrompu, contesté et refondé. Elle est la condition minimale de tout ordre qui ne veut pas être sa propre clôture et sa pure et propre reproduction sociale.
Ce que nous avons appelé scène nest ni une métaphore théâtrale, ni un artifice rhétorique, ni un dispositif de communication. Elle nest pas le lieu décoratif du pouvoir. Elle nest pas ce que lon ajoute, une fois la régulation pensée, pour en valider symboliquement lacceptabilité. Elle est, à linverse, la matrice originaire doù émerge toute forme de régulation vivable, bien avant lÉtat, bien avant le droit, et en dehors des grammaires modernes du gouvernement.
Lhistoire politique des sociétés humaines ne commence pas avec linstitution étatique. Elle commence par lapparition de lieux différés dans lesquels le pouvoir se donne à voir, se laisse convoquer, se met en épreuve. Partout où lon observe la mise en forme de relations collectives durables, on trouve, en amont de la codification, des scènes dajournement et de dispute — des espaces de rituel, de conseil, de circulation de paroles, de suspension du geste immédiat.
Les premières figures de la régulation ne sont pas des lois, mais des cercles. Ce sont des feux autour desquels on raconte ce qui a été fait, ce qui pourrait être fait autrement, ce qui doit être décidé ensemble. Ce sont des seuils marqués, des temps de deuil ou de conflit ajourné, des prises de parole dans lépure, où lévénement est reconvoqué dans un espace plus grand que lui. Cest ici que sinstitue, sans formalisation juridique, la première *archicration* : une scène anthropologique de différé, de dispute et de co-présence du conflit.
Larchéogenèse de la scène, telle que nous lavons retracé, ne vise pas à plaquer une origine sur le concept d*archicratie*. Elle permet de montrer que lexposition du pouvoir au regard des autres nest pas un luxe moderne, mais une fonction vitale de toute organisation collective. On ne fonde pas une cité parce que lon a établi une loi ; on fonde une cité parce que lon a produit un lieu dans lequel des lois pouvaient être explicitées, différées, adressées et même révisées.
Les régimes que nous considérons comme proto-politiques ne reposent pas sur des appareils coercitifs permanents, mais sur des dispositifs de régulation publique des différends. Ce que lon appelait jadis “conseil des anciens”, “assemblée des vivants”, “place de palabres”, “moment du jugement”, sont autant de scènes dans lesquelles une communauté suspend lexécution brute des normes pour produire, en commun, un réexamen des conditions du vivre-ensemble.
Il y a ici un renversement majeur : ce nest pas la violence qui précède lordre, mais la scène qui ajourne la violence par son existence et son efficience. Loin dêtre un ornement cérémoniel, la scène permet de transformer une décision en acte politique, en la soumettant à un espace dapparition partagé. On ne gouverne pas parce quon détient un pouvoir, on gouverne parce que lon accepte dexposer le pouvoir dans un lieu où il peut être contes.
Et ce geste ne disparaît pas avec la modernité : il se déplace, se reconfigure, parfois se cache. Les formes scéniques de lépoque moderne — parlements, tribunaux, conseils, consultations — ne sont pas des innovations *ex nihilo*, mais des transpositions institutionnelles dune exigence anthropologique fondamentale : rendre le pouvoir visible dans un espace différé, public, contradictoire pour le légitimer dans son autorité.
Ce que nous appelons aujourdhui *archicratie*, à travers le triptyque *arcalité-cratialité-archicration*, ne naît donc pas avec la gouvernance algorithmique, ni avec la crise contemporaine des institutions : cest le nom que nous donnons à une exigence anthropologique longue — rendre le pouvoir visible dans un espace différé, public, contradictoire — que les mutations modernes ont partiellement codifiée, mais jamais totalement effacée. Même les régimes autoritaires, même les appareils technocratiques les plus opaques, tentent de simuler la scène, den mimer les apparences, comme si toute régulation devait, dune manière ou dune autre, sappuyer sur une forme scénique minimale, fût-elle falsifiée, spectaculaire, dé-saisissante ou accablante.
Leffondrement actuel, tel que nous le décrivons, ne réside pas dans une rupture historique absolue, mais dans une disjonction cumulative, où la régulation devient performative, prédictive, auto-exécutive, tout en prétendant conserver lapparence de la scène. Ce que les plateformes administratives, les interfaces de recours, les simulateurs dopinion, les visualisations de données simulent, ce nest pas la norme — cest le différé. Ce nest pas la décision — cest la contestation.
En ce sens, réinstituer la scène aujourdhui revient à réactiver une fonction anthropologique enfouie, plutôt quà restaurer une forme passée enfouie sous les couches technocratiques, une fonction sans laquelle aucun ordre collectif ne peut être su, ajusté, ni corrigé, ni habité. Nous ne parlons pas ici de restauration, plutôt dun geste de fondation renouvelé : retrouver la capacité de construire des espaces différés dapparition du pouvoir, où la norme peut être visible, où la décision peut être suspendue, où leffet peut être discuté, et où le fondement peut être relancé.
Lhistoire de la scène est celle de lajournement du pouvoir brut au nom dune *co-présence* instituée du conflit. Et cest à cette hauteur anthropologique que l*archicratie* trouve son origine : non dans une forme constitutionnelle, mais dans la structure même du fait politique, dès lors quil accepte de sexposer à autre chose que sa propre exécution.
Ce que nous appelons *co-viabilité* nest pensable que dans cette tension : entre un pouvoir qui affecte, et un espace où cette affectation peut être exposée. La scène est le seul lieu où cette tension devient habitable, cest-à-dire vivable, réformable, traversable. Sans scène, ce sont alors des processus, des opérations, des machines qui dominent.
Cest pourquoi, dans notre essai-thèse, l*archicration* nest jamais une solution institutionnelle, ni une forme de gouvernement. Elle est la condition minimale dun monde capable de sinterrompre pour se refonder. Elle est ce qui permet, encore et toujours, de revenir sur ce qui est en train de simposer. Elle est le lieu de surgissement de la viabilité.
Et cest à partir de cette scène — non comme décor, mais comme forme originaire de la dispute différée — que peut se penser la cinquième révolution régulatrice, non comme innovation technologique, mais comme retour conscient à une exigence anthropologique que lhistoire na jamais pu dissoudre.
Il nest désormais plus possible danalyser la question de la régulation sans la confronter à ses deux périls contemporains les plus massifs, les plus urgents, les plus irréversibles : la désintégration sociale et linhabitation écologique. Ces deux lignes de fracture ne constituent pas des petits “sujets” parmi dautres, ni même des crises externes au champ de la régulation. Elles sont les lieux mêmes où se joue la soutenabilité du monde commun, et à partir desquels se manifeste avec la plus grande intensité la nécessité dune scène archicratique instituée.
Dun côté, les sociétés contemporaines sont travaillées par une fragmentation socio-économique accélérée, où les droits, les protections, les accès aux ressources, à la santé, à lhabitat, au travail, à la dignité même, sont de plus en plus conditionnés par des régimes de critères invisibles, des barèmes automatisés, des normes silencieuses dont la contestation na ni lieu ni délai. Ce que produit cette fragmentation, ce nest pas simplement de linjustice cest une dés-institution du social, un abandon de la possibilité de fonder lordre sur une scène où les règles peuvent être exposées, comprises, opposées, amendées.
De lautre côté, lenvironnement planétaire est soumis à un processus de désintégration physique, chimique, biologique, systémique, dont les causes sont connues, mesurées, modélisées — mais dont la régulation demeure hors scène. Les grands instruments du pilotage écologique (quotas carbone, marché de droits démission, régulations incitatives, taxonomies vertes, solutions technologiques à haute intensité énergétique) ne répondent pas à une scène fondée de délibération environnementale. Ils opèrent à partir dindicateurs pré-déterminés, de modèles économétriques rendus irréfutables, dinstitutions fermées dont les critères ne sont ni exposés, ni opposables.
Dans les deux cas, ce que lon appelle régulation nest souvent rien dautre quun régime dadministration automatique de la catastrophe, une façon de moduler les seuils dacceptabilité de linacceptable, de stabiliser temporairement des déséquilibres massifs sans jamais exposer les choix, les arbitrages, les priorités, à une scène dépreuve collective.
Le mot “durabilité”, en ce sens, est devenu lun des opérateurs les plus puissants de cette fiction régulatrice. Il donne à croire quun monde peut être maintenu dans son état actuel par lajustement de ses paramètres sans refondation, sans débat, sans conflit institué. On parle de transition, mais sans définir qui en décide, selon quels délais, avec quelles conséquences, sur quels critères effectifs et crédibles. On évoque lempreinte carbone, sans jamais dire ce que cela signifie politiquement, historiquement, anthropologiquement. Le terme de “durabilité” produit ainsi un effet de clôture : il neutralise la conflictualité fondatrice de toute scène régulatrice, en la remplaçant par un récit doptimisation continue, une fable pseudo-scientifique.
Ce que notre essai-thèse oppose à cette fiction, ce nest pas une autre gestion du risque, ni une autre version du développement : cest une refondation de la question régulatrice à partir du paradigme de la co-viabilité. Et cette refondation ne peut sopérer que dans une configuration archicratique, cest-à-dire dans une scène fondée où la viabilité ne se décrète pas, mais séprouve, se dispute, se rejoue, se transforme.
Celle-ci ne peut être réduite à la compatibilité des intérêts humains et non-humains, ni à une coexistence pacifiée entre agents différenciés. Elle nest pas un horizon harmonique. Elle est un régime dépreuves instituées, dans lequel les formes du vivant, des milieux, des infrastructures, des symboles, peuvent être convoquées dans des scènes de régulation explicite, différée, révisable.
Ce que cela signifie, très concrètement, cest que les arbitrages sur leau, lénergie, la mobilité, le territoire, la réparation, le soin, ne peuvent plus être externalisés dans des modèles dimpact ou des courbes defficacité. Ils doivent être rapportés à une scène, où lon peut interroger non seulement les effets des décisions, mais leurs fondements, leurs seuils, leurs instruments, leurs alternatives.
L*archicration* écologique prend corps dans des scènes territoriales où les habitants font apparaître les conditions dhabitabilité, contestent les infrastructures et proposent des configurations de subsistance — bien au-delà des conférences quinquennales et des tableaux de bord numériques dobjectifs carbone ; elle suppose une arcalisation du vivant (reconnaissance des entités existantes), une cratialisation des milieux (identification des champs de forces et dagissements), une archicration du conflit, autrement dit une politisation complète de ce qui, trop longtemps, a été traité comme données environnementales inertes.
Sur le plan social, la logique est la même. Là où les critères dattribution des droits deviennent des scripts inaccessibles, là où les plateformes daccès remplacent les guichets, là où les aides sociales sont suspendues sans énoncé, là où les publics fragiles sont évalués par des algorithmes de “comportement à risque”, le droit sautomatise, la scène disparaît, la dignité sefface.
Ce nest pas une question dinefficacité, ni même dinjustice procédurale : cest une désactivation du régime de reconnaissance. Car un droit qui ne peut plus être justifié publiquement, réclamé dans une scène, opposé dans un délai, contesté par une présence, nest plus un droit. Cest une variable. Une allocation. Un flux à tout moment suspensif.
Réinstituer l*archicration* sociale, cest donc reconstruire la scène de laccès au droit — non pas à travers des démarches participatives formelles, mais à travers des dispositifs dexposition des critères, des formes de visibilité des seuils, des délais contradictoires obligatoires, des lieux de confrontation réels, soutenus, équipés, où lon peut dire : “Ce nest pas acceptable. Voici pourquoi. Voici ce que je propose.” Cest là, dans ces scènes de linterpellation, que la co-viabilité sociale devient pensable.
Il ne sagit donc pas de “réconcilier” social et écologie, mais de comprendre quils sont deux effets dune même oblitération scénique, et que leur relance ne peut passer que par la reconstruction des conditions archicratiques du désaccord institué.
Cest ici que lhypothèse archicratique devient non plus un outil critique, mais une proposition civilisationnelle. Car un monde durablement privé de scènes archicratives tend à devenir aveugle à sa propre destinée. Un monde incapable de suspendre ce quil fait. Incapable den répondre. Incapable de sajuster depuis elles et ceux quil affecte.
À cette cécité organisée, la *co-viabilité* oppose la lumière rugueuse de lexposition et du différé, la présence irréductible des vivants, la scène ouverte des conflits fondateurs canalisés par les récits de vie. Et cest à partir de cette tension — vitale et irréductible — que peut encore sinventer un devenir commun concerté fondé sur nos vulnérabilités, nos résiliences et nos robustesses conscientisées et politisées.
La régulation contemporaine, dans sa formulation dominante, se présente sous les atours rassurants dun horizon consensuel : celui de la durabilité. Ce terme, devenu mantra technico-politique, irrigue désormais lintégralité des champs du discours institutionnel — des traités internationaux aux chartes locales, des stratégies dentreprise aux programmes éducatifs, des plans daction gouvernementaux aux critères dinvestissement privé. Il semble à la fois évident et indiscutable, fédérateur et apaisant. À son contact, les conflits seffacent, les alternatives se suspendent, la temporalité se normalise, et la gouvernance acquiert une forme de légitimité indolore, anesthésiante, presque tautologique. Au point que ce qui est durable est ce qui mérite de durer.
Sous sa surface consensuelle, la “durabilité” se ferme à lépreuve : elle transforme le dissensus en anomalie et recode la régulation en pilotage continu dindicateurs non révisables. Le problème nest pas lobjectif écologique en lui-même, mais la structure régulatrice qui limpose hors scène, et surtout, dans bien des cas, hors-sol, et bien plus, par cœur ou par calcul. Contre cette clôture, la politique des épreuves viables et soutenables substitue à loptimisation des variables la publicité des critères recevables, au pilotage continu automatisé le jeu et le différé contradictoire, à la gestion dimpacts et de risques lénonciation de leurs fondements pour éveiller les raisons — conditions pour que les décisions redeviennent adressables et opposables sous critères de discernement.
Car jusquà présent, les stratégies dites “durables” fonctionnent selon un régime algorithmique dautorité, où les seuils dacceptabilité sont déterminés en amont, les priorités dictées par les logiques defficience, les variables manipulées sans scène dexposition. On y parle de neutralité carbone, de trajectoires optimales, de plans de transition, mais jamais de fondement public de ces trajectoires, ni de scène dans laquelle elles pourraient être tenues devant ceux quelles affectent. Cest dans cette évacuation de la scène — plus encore que dans le contenu des politiques — que réside le cœur du problème. Ce nest pas lobjectif de durabilité qui est en cause, cest la structure régulatrice silencieuse qui le porte, le légitime et limpose.
Pour rendre ce mécanisme visible, rappelons-nous que toute régulation suppose deux opérations irréductibles : dune part, une *normativité explicite* (ce qui doit être régulé, pourquoi, selon quels principes) ; dautre part, une *opérativité outillée* (comment cette norme sapplique, à travers quels instruments, sur quels objets). Mais entre les deux, il faut une troisième instance, que la durabilité contemporaine tend précisément à effacer : la *scène dépreuve*. Cest cette scène — différée, fondée, contradictoire — qui permet aux décisions de ne pas sexécuter à labri du regard, aux critères dêtre rendus publics, aux seuils dêtre contestés, aux affects dêtre entendus, aux représentations de monde dêtre opposables contre tout arbitraire.
En ce sens, la durabilité nest pas quune fiction apolitique : elle est le récit qui permet lexécution sans scène, la régulation sans fondement, la gouvernance sans convocation. Elle naturalise les instruments, en les présentant comme neutres. Elle réduit les choix à des données. Elle transforme la dispute en friction technique. Et ce faisant, elle désarme les communautés, les privant du droit dajourner, de relancer, de reformuler ce qui les affecte.
La politique des épreuves viables, telle que nous la proposons ici, ne consiste pas à rejeter les impératifs écologiques et sociaux. Elle ne nie pas la nécessité de limites, ni lurgence de transformations structurelles. Elle refuse simplement que ces transformations soient imposées hors scène, dans le silence dune régulation désaffectée. Elle postule que toute décision ayant un impact majeur sur les conditions de vie, de subsistance, dhabitation, doit passer par une épreuve fondée, différée, partagée. Elle postule que ce qui nest pas disputé ne peut être dit durable.
Une politique des épreuves viables suppose donc une ré-institution intégrale des conditions archicratiques de la décision. Plutôt quune réforme des indicateurs, une amélioration des consultations ou une simple transparence des données, il faut une scénarisation complète de la régulation selon une grammaire fondée sur quatre principes irréductibles, que nous avons articulés dans le cadre de notre modèle archicratique.
Quatre exigences, déjà inscrites dans notre axiomatique, organisent la politique des épreuves viables : le *différé contradictoire*, qui rend lexécution ajournable ; la *publicité des critères*, qui reconduit tout seuil à son fondement ; l*opposabilité réelle*, qui ouvre la décision à lintervention des affectés avant, pendant et après son application ; la *révision périodique*, qui interdit la clôture. Ces exigences ne sont pas des ajouts déontologiques : elles matérialisent laxiome de détectabilité, confirment la disjonction fonctionnelle des prises arcales, cratiales et archicratiques et prolongent lépreuve critique comme norme de vitalité du régime.
Mais ces principes ne peuvent exister quà une condition : quil y ait une scène. Une scène qui ne se contente ni de spectacle, ni de consultation décorative, ni de simple mise en visibilité : mais bien une scène où le pouvoir se fonde en apparaissant, où lépreuve est instituée, où la transformation sengage réellement depuis ceux quelle affecte.
Lexemple des politiques climatiques lillustre avec une clarté glaçante. Les objectifs de réduction démissions sont fixés par des trajectoires macroéconomiques, sans fondement ontologique débattu. Les instruments de marché (droits démission, taxes, incitations) sont conçus dans des enceintes dexpertise, sans contre-scène démocratique. Les seuils sont négociés entre États et industries, sans présence des vivants affectés. Résultat : une régulation performante sur le papier, mais fondamentalement désarchicratique. Elle sexécute sans différé, sans dispute, sans publicité explicite des choix de monde. Là encore, la matrice daudit du chapitre 1 sapplique : hypotopies (prises faibles sous-déterminantes), hypertopies (prises surdéterminantes), atopies (pseudo-scènes indéterminées), permettant de situer les déficits scéniques des dispositifs climatiques.
Ce que nous appelons épreuve viable, cest ce qui manque ici : la possibilité dexposer les décisions à une scène où le fondement devient enjeu, où les milieux parlent, où les vivants contestent, où les représentations saffrontent, dans un cadre institué, soutenu, non clôturé.
Dans les politiques sociales, le mécanisme est identique. Les aides sont attribuées selon des barèmes automatisés, les exclusions sont déclenchées sans justification explicite, les critères de mérite, de comportement, de mobilité sont intégrés dans des scripts sans adresse. On parle dinsertion, dautonomie, de parcours. *Mais où est la scène ?* Où est lespace où une personne peut dire : « ce critère est inacceptable » ; « cette décision ne me reconnaît pas » ; « je demande une autre forme de régulation » ?
Dans bien des dispositifs sociaux, la scène formelle daccès au droit sest amincie ou sest déportée dans des circuits techniques ; elle persiste toutefois sous des formes intermittentes, fragiles ou latérales — commissions locales, permanences associatives, médiations juridictionnelles *ad hoc*. Le problème nest donc pas labsence pure et simple, mais la dégradation topologique de la scène : hypotopies (prises faibles ou non reliées), hypertopies (cratialité sur-déterminante) et atopies (simulacres participatifs). Comme nous lavons établi au chapitre 1, cette typologie ne renvoie pas à un argument dautorité mais à une matrice daudit : elle sert à objectiver les prises, à situer les déficits et à rouvrir la possibilité dune scène tenue, opposable et révisable.
Face à cela, une politique des épreuves viables implique une redéfinition complète de lacte régulateur : non plus lajustement dun système fermé, mais linstitution dune scène ouverte. Elle transforme le pouvoir de gouverner en obligation de se laisser apparaître. Elle transforme le savoir dexpert en condition de fondation publique. Elle transforme la gestion en exposition contradictoire.
Nous nommons *autarchicratie* la concrétisation achevée de la désarchicration : le méta-régime dans lequel la *cratialité se met en autarcie*. Il ne sagit pas dun gouvernement de la société par elle-même, mais dun *gouvernement de la régulation par elle-même* : indicateurs, modèles de risque, scripts algorithmiques et procédures de contrôle deviennent leurs propres critères de validité, sans plus devoir passer par des scènes dépreuve archicratives praticables. La *cratialité* sy auto-référence et sy auto-certifie, dans des boucles où tout fonctionne — calculs, audits, *reporting* — mais où plus rien ne se laisse vraiment contester ni même énoncer. Cette mise en autarcie de la régulation prolonge, jusquà son point de bascule, les tendances déjà repérées dans la gouvernementalité néolibérale, la rationalisation managériale et la numérisation intégrale des prises.
L*autarchicratie* nest donc pas une fiction dystopique : cest une destination possible, et déjà en partie à lœuvre, de nos régimes contemporains. Nous en avons suivi les lignes de force dans lhistoire des révolutions industrielles (chapitre 4), lorsque la scène archicrative se trouve progressivement oblitérée au profit de modèles prédictifs et de dispositifs dautomatisation normative. Nous en avons observé les manifestations concrètes dans les politiques climatiques, sociales et numériques (chapitre 5), sous la forme de *pseudo-archicrations fantômes* et de pilotages algorithmiques où les circuits de décision se suffisent à eux-mêmes. Dans tous ces cas, la régulation subsiste juridiquement et techniquement, mais elle se maintient hors seuil dopposabilité : scènes inaccessibles, délais inopérants, motifs indisponibles, paramètres non auditables. Tout fonctionne ; mais plus rien ne sexpose ni ne sexplique.
Dès lors, le péril nest pas l*archicratie* elle-même — qui institue la scène et rend contestables les effets —, mais sa disparition performative dans l*autarchicratie*. Ainsi, refaire monter la scène nest pas un supplément procédural : cest revenir au seuil où la décision cesse dêtre instrument et redevient fondation ; cest rétablir, contre lautarcie régulatrice, la possibilité dun différé, dune justification opposable, dune réversibilité des effets — conditions sans lesquelles il ny a plus de politique, mais seulement à terme des processus automatiques ou des pratiques machinales.
Or cest bien cela que permet encore le concept d*archicratie* — refonder la régulation comme scène, comme institution de linterruption, de la dispute, du différé, de la relance — puisquelle permet de nommer ce qui auparavant était pour bonne part occulté bien que présent. Les épreuves viables posent ainsi la question de ce qui fait tenir un monde — et à quelles conditions il mérite encore de tenir.
Si la scène constitue, comme nous lavons montré, la matrice fondamentale de toute régulation archicratique, alors son absence appelle autre chose quun tragique constat critique ou une nostalgie institutionnelle. Elle appelle un geste pleinement instituant, une projection agissante, un surgissement conceptuel doublé dun projet matériel. Il ne suffit pas de rappeler que les normes sappliquent aujourdhui sans exposition, que les seuils gouvernent sans adresse, que les décisions sexécutent sans différé. Il faut désormais concevoir les formes par lesquelles cette dérive peut être arrêtée, inversée, transformée depuis son propre cœur. Cela ne relève ni du réformisme, ni de lutopie, mais dun geste proprement archicratique : instituer une scène là où il ny a plus que des tentations de scripts ou de prompts.
Car labsence de scène ne signifie pas une absence de régulation. Celle-ci se manifeste au contraire par un trop-plein de formes technico-administratives, de logiques procédurales, de chaînes opératoires sans seuil de réflexivité. Elle ne manque pas darènes, de plateformes, de forums, de simulateurs. Par contre, elle manque de lieux où lon peut exprimer que ce pouvoir nest pas encore justifié ; que cette norme ne peut pas encore sappliquer ; que cette décision doit être suspendue tant quelle na pas été tenue devant celles et ceux quelle transforme. La scène nest donc pas une instance supplémentaire : elle est ce sans quoi toute instance devient violence déguisée et insidieuse. Elle ne sajoute pas au dispositif ; elle en est la condition de légitimité. Et pour cette raison, elle doit être conçue, pensée, projetée et instituée.
Toutefois, réinstituer les scènes ne consiste pas nécessairement à restaurer les formes dassemblée du passé. Il ne sagit pas non plus de réanimer les dispositifs dévitalisés de la représentation, ni de raviver des formes symboliques inertes. Il sagit de penser des espaces dans lesquels une décision devient apparente avant dêtre exécutoire, dans lesquels le temps retrouve sa densité, dans lesquels le pouvoir accepte dêtre différé, exposé, débattu, amendé, contredit. Cela implique une refondation des infrastructures de la régulation, dans leur matérialité comme dans leur temporalité.
Il faudra donc des lieux, des calendriers, des organisations, des compétences, des statuts, des rôles, des budgets, des procédures, mais aussi des rituels, des langues, des gestes, des seuils symboliques. Car la scène est un dispositif complexe : elle némerge pas delle-même, elle ne se décrète pas, elle sinstitue par un ensemble cohérent de formes, despaces, daffects et de récits. Et cest ce tissu quil faut aujourdhui remailler.
Là où les politiques publiques sélaborent aujourdhui dans les anti-chambres des ministères ou la clôture des cabinets daudit, il faudra ouvrir des délais formels et irréductibles à la consultation. Là où les plateformes déploient leurs standards dusage sur des populations entières sans épreuve fondatrice, il faudra bâtir des scènes où le code peut être ajourné, explicité, confronté. Là où les territoires sont remodelés par des logiques doptimisation, il faudra des lieux où lhabitabilité puisse être requalifiée, où les milieux puissent apparaître, où les formes de vie puissent être représentées dans leur altérité. Là où les seuils dattribution, de sanction, daccès ou daide sappliquent par automatisme, il faudra instituer des régimes où la décision elle-même devienne visible, révisable, reconductible à partir dune épreuve contradictoire menée par des humains. Et cela ne se fera pas par décret. Cela exigera de nouveaux dispositifs, de nouvelles instances, de nouvelles formes, à inventer depuis le cœur même de la crise scénique contemporaine, probablement au plus près des acteurs de léconomie sociale et solidaire et des professions intermédiaires.
Quon admette alors ceci : bien comprendre la scène dans lordonnancement archicratique nest pas une option procédurale. Elle est lun des opérateurs décisifs par lesquels une décision cesse dêtre purement instrumentale pour devenir fondatrice. Cest dans la scène que le pouvoir se redéploie en se laissant affecter. Cest dans la scène que la temporalité sépaissit, que les conséquences deviennent lisibles, que les fondements peuvent être relancés. Cest là que peut émerger une *co-viabilité régulatrice*. Il ne sagit donc pas dajouter de la transparence, de la participation ou de la communication. Il sagit de concevoir des scènes dans lesquelles lordre peut être temporairement suspendu, afin dêtre à nouveau institué depuis ceux quil affecte.
Ce travail dinstitution scénique ne peut être séparé de la question de léchelle. Il devra se jouer à tous les niveaux : au sein des écoles, des collectivités, des juridictions, des plateformes, des infrastructures, des régulations techniques, des politiques sociales, des gouvernances écologiques... Chaque strate et lieu de l*archicratie* doit trouver sa scène dapparition, adaptée à sa texture propre, mais reliée par une même exigence : qu*aucune décision structurante et agissante ne puisse advenir sans être tenue devant celles et ceux quelle engage.* Cela implique un effort considérable de conception, de financement, de formation, mais surtout un déplacement ontologique de ce quon appelle “gouverner”. Gouverner ne consistera plus à appliquer avec justesse des normes bien conçues, mais à rendre ces normes ajournables, discutables, contestables, et davantage réformables sous conditions de *co-viabilité* sociale et écologique.
Il faudra, pour cela, concevoir de nouveaux lieux : des espaces où lon entre sans savoir ce qui va en sortir, mais où chacun peut venir avec ses preuves, ses objections, ses propositions, ses arguments. Des scènes sans clôture prédéterminée, mais avec un cadre fort. Des scènes traversées par les conflits, mais équipées pour les soutenir. Des scènes lentes, où lon prend le temps de tout reprendre. Des scènes modestes, parfois, mais constantes. Des scènes qui ne soient pas des moments exceptionnels, mais des rythmes dinstitution continue. Des scènes enfin que lon peut convoquer, non pour donner son avis, mais pour rouvrir le monde.
Et plus encore que des lieux, il faudra forger une culture. Une culture du différé, une culture de la justification, une culture de lapparition fondée. Il faudra former à la convocation des énonciateurs, à la reconstruction des axiomes, à la mise en tension des critères. Il faudra réapprendre à interrompre sans détruire, à disputer sans humilier, à exposer sans désincarner. Il faudra bâtir des formes dans lesquelles le pouvoir naura plus peur dêtre vu, entendu, relancé. Mais cela, aucun règlement ne pourra le prescrire. Ce sera lœuvre dune génération entière, qui ne se contentera pas de revendiquer des droits, mais qui acceptera de se constituer comme puissance politique — non dans léclat du spectaculaire, mais dans la densité fondatrice dun monde à relancer avant effondrement déjà annoncée.
À ce niveau, réinstituer la scène nest plus un programme. Cest un appel. Un appel à cesser de gérer les régulations comme si elles pouvaient sauto-référencer indéfiniment. Un appel à rouvrir lespace du pouvoir comme espace dépreuve, non de domination. Un appel à instituer un régime dans lequel les décisions cessent dêtre des faits pour redevenir des actes. Cest ainsi que l*archicratie* prendra sens comme projet. Non comme forme de gouvernement, mais comme respiration de toute société qui veut encore se gouverner elle-même dans son évolution pour assurer son avenir.
Toute régulation sinscrit dans une histoire. Non seulement dans lhistoire des régimes politiques ou des institutions juridiques, mais dans une histoire plus profonde encore : celle des formes darticulation entre pouvoir, savoir, énergie et monde, autrement dit, celle des configurations systémiques au sein desquelles lhumain, le vivant, le milieu, la technique et le symbolique se tiennent dans un certain rapport. Ce que nous appelons ici “révolution régulatrice” ne renvoie donc pas à un événement politique localisé, ni à une invention technologique isolée, mais à une transformation systémique de la manière dont un monde se produit, se reproduit, se règle et se légitime.
Les quatre grandes révolutions qui scandent la modernité industrielle mécanisation disciplinaire, organisation taylorienne-fordiste, cybernétisation néolibérale, numérisation généralisée ne sont pas seulement des séquences techniques. Chacune a reconfiguré, à sa manière, le rapport entre *arcalité*, *cratialité* et *archicration* : nouvelles promesses de légitimation, nouveaux dispositifs dexécution, nouvelles formes ou nouvelles défausses de scène. La quatrième, celle de la numérisation intégrale des systèmes de décision, pousse à lextrême un mouvement amorcé de longue date : la possibilité de faire fonctionner la régulation sans passer par des lieux reconnaissables dépreuve.
Cest précisément contre cette clôture systémique que se dessine ce que nous proposons dappeler, par convention, une cinquième révolution régulatrice. Elle ne consisterait pas en un supplément dautomatisation ni en une sophistication accrue des instruments. Elle tiendrait dans un déplacement plus radical : le fait de considérer que linstance décisive nest plus loutil, ni le flux, ni la procédure, mais la scène où ces éléments deviennent adressables, critiquables, reconfigurables. Là où les révolutions précédentes ont privilégié lextension des capacités de calcul, de production ou de circulation, celle-ci privilégierait la capacité à exposer ces capacités, à en faire lobjet dune épreuve publique.
Dire quil sagit dune “cinquième révolution” ne signifie pas que lhistoire suivrait un cours nécessaire et linéaire. Le terme na ici quune valeur heuristique : il sert à concentrer, sous un même nom, des processus multiples, hétérochrones et dispersés luttes pour la transparence effective des algorithmes, revendications de recours opposables, demandes de traçabilité des décisions, inventions de nouvelles assemblées scéniques. Il serait erroné de croire que cette révolution viendra sajouter, comme une étape finale, à une série déjà écrite. Elle pourrait tout aussi bien rester avortée, partielle, récupérée par les dispositifs quelle prétend contraindre.
Cest pourquoi lidée de cinquième révolution doit être explicitement soumise à épreuve. Elle serait infirmée si lon constatait que les scènes existantes continuent de se vider de toute capacité effective, que les espaces de recours se réduisent à des simulacres sans impact sur les décisions, que les tentatives de réinstitutions scéniques se réduisent à des mises en récit sans prise sur les cratialités. Elle gagnerait en consistance, au contraire, si des pratiques, même locales, montraient que la création de nouvelles scènes dans les hôpitaux, les écoles, les plateformes, les politiques écologiques modifie réellement la manière dont les décisions sont justifiées, prises et requalifiées pour assurer la co-viabilité.
Nous parlons donc de “cinquième révolution” moins comme dun avenir promis que comme dun horizon de travail : une invitation à relire les conflits présents à partir de la question scénique. Chaque fois quun collectif lutte pour un délai, pour un droit au différé, pour la présence dénonciateurs identifiables, pour un droit de regard sur les paramètres qui le gouvernent, quelque chose de cette révolution est mis en jeu. Chaque fois quil sen remet au contraire à la seule fluidité des ajustements automatiques, il sen éloigne. Le paradigme archicratique propose de tenir ensemble ces expériences, de les comparer, de les instruire, sans décider à lavance de leur issue.
Cette cinquième révolution nest pas un futur abstrait : elle advient chaque fois quun seuil est suspendu, quun fondement est reconvoqué, quun recours devient opérant, quune voix exclue trouve scène, délai et adresse ; chaque fois que la transparence cesse dêtre visualisation pour redevenir publicité des critères, que la participation quitte la consultation pour rejoindre lépreuve, que la gouvernance sefface devant la fondation.
Nous parlons de “cinquième révolution” par convention, en pleine conscience que lhistoire effective procède par recompositions régulatrices hétérochrones, stratifiées, feuilletées, sérialisées, désynchronisées selon les régimes symboliques et techniques ; de sorte que l“événement” est devenu une prise de pouvoir de la scène dans larchitecture des dispositifs industriels.
Dans un monde où les infrastructures et les superstructures établissent les nouvelles normes, où les plateformes sont devenues de nouveaux territoires de nos êtres, où les systèmes dintelligence artificielle sinventent comme nouveaux gouvernants, seule une révolution scénique permettrait au politique de ne pas seffacer. Mais celle-ci ne se décrète pas : elle se sculpte par des décisions, des dispositifs, des pratiques et des langages ; elle requiert des artisans, des architectes, des dramaturges du politique ; elle accepte de perdre en fluidité ce quelle gagne en légitimité. Et cest cela, précisément, que nomme l*archicration* : une révolution régulatrice en acte, bien plus que le modèle institutionnel quelle instaurerait en conscience — l*archicratie*.
Il faut aujourdhui prononcer un appel ancré dans le vivant, là où les institutions ne font plus apparaître les polémiques, là où les discordes tendent à se faire taire. Un appel qui ne soit ni un cri, ni un mot dordre, ni un acte de foi, mais une *archicration vivante* : cest-à-dire lapparition dénoncés fondés, publics, discutables, adressables, révisables — une parole instituante, dans ce sens précis et exigeant que nous redonnons au mot “politique”. Le droit de vivre ! De tous vivre ! De chacun selon ses capacités, et à chacun selon ses besoins…
Car ce que notre monde ne sait plus faire advenir, ce ne sont pas les règles ou les normes en elles-mêmes, mais la capacité populaire à les exposer, à les critiquer, à les instituer grâce à des formes collectives constituées par celles et ceux qui les subissent en premier lieu. Cette incapacité nest ni technique, ni conjoncturelle. Elle est le symptôme dun effondrement structurel qui au-delà de produire de linjustice, nous conduit à une crise de viabilité civilisationnelle.
Nous vivons dans un monde où lhumain et le non-humain sont liés par une même absence de considération. Le vivant, dans son immense diversité de formes, dagencements, de langages, de résistances, est soustrait à la parole, au temps et à la forme. Et ce que notre thèse a permis de penser, cest que ce processus dinvisibilisation nest pas un effet secondaire des logiques économiques, mais une conséquence directe de la disparition de la scène de confrontation. Là où il ny a plus de scène, il ne peut y avoir ni représentant, ni interpellation, ni suspension, ni réparation. Le vivant devient bruit, excès, variable et externalité à occulter.
Il faut donc le dire avec toute la solennité nécessaire. Un monde sans *archicration* rend le vivant politiquement illisible, donc indéfendable : le conflit perd sa scène et le temps son droit de retour ; or le vivant a besoin dun lieu dapparition où les règles qui le concernent sont convoquées, tenues et différées. Parce quil a besoin dune scène comme condition de reconnaissance mutuelle inscrite dans le temps. Parce quil ne peut être gouverné sans être dabord entendu, représenté, institué dans la durée de conflits assumés, dans la densité dun désaccord soutenu qui permette tout de même la *co-viabilité*.
Ce nest pas tant un supplément de conscience écologique que requiert notre temps, quune réinscription du vivant dans le cœur même de la procédure régulatrice. Il faut que le vivant retrouve la capacité de faire épreuve comme acteur instituant. Cela implique de convoquer de nouveaux formats dapparition, de nouvelles figures de représentation, de nouvelles juridictions du milieu, de nouveaux porte-paroles, mais aussi de nouvelles scènes, de nouveaux délais et de nouveaux seuils. Et cest là que l*archicration*, en tant que forme dinstitution de la régulation, redevient notre outil premier, notre geste inaugural, notre obligation radicale.
Mais ce ne sont pas uniquement les vivants non-humains qui exigent ce retour à la scène. Ce sont les humains eux-mêmes, dans leur diversité traversée de douleurs, de silences, de disparitions, de récits corrompus ou interrompus. Ce sont les corps précarisés, expulsés, captés, mécanisés, racisés, discriminés. Ce sont les voix sans adresse, les luttes étouffées, les demandes restées lettres mortes, les souffrances converties en statistiques, les communautés disqualifiées ou reléguées. Là encore, ce nest pas un “plus de participation” quil faut : cest un *acte fondateur de refondation*. Ce nest pas une réforme du droit daccès à la parole : cest linstitution dun droit dépreuve et de reconnaissance. Encore une fois d*archicrations* !
Et ce droit nest pas métaphysique. Il est scénique. Il sinscrit dans des formes précises, différées, structurées. Il suppose des lieux, des interlocuteurs, des procédures, des archives, des délais, des énonciateurs. Il suppose des scènes que lon peut habiter, relancer, réinterroger. Il suppose que la parole soit tenue, mais aussi reçue, exposée, contredite, reformulée. Il suppose, enfin, que cette parole soit retenue dans larchitecture même de la régulation. Quil ne soit pas juste documenté, mais quil puisse reconfigurer le pouvoir sous condition de recevabilité.
Nous appelons donc, à ce stade ultime de notre œuvre, à un réarmement du commun par la scène. Rien dun théâtre idéologique bien plutôt une architecture du vivre-ensemble, régulatrice, opposable, fondée. Car il ne peut y avoir de monde commun sans scène, pas de scène sans différé, et pas de différé sans pouvoir qui accepte de suspendre son exécution pour se reformuler devant celles et ceux quil engage. Ce geste — si simple en apparence, si révolutionnaire en pratique — est le cœur de toute *archicration*. Et cest à ce titre probablement à tout le moins lune des rares voies régulatrices encore tenables et viables à nos yeux.
À ce point de saturation du monde — saturation des flux, des normes, des récits, des simulacres de participation — une respiration possible reste celle du moratoire et du différé. La seule densité politique entendable est celle de lépreuve et des preuves. La seule fondation encore pensable est celle qui accepte de se laisser instituer à nouveau, à se laisser revisiter, en pleine conscience. Cest cela, notre appel : reconvoquer le vivant dans la scène, refaire de lapparition, de linformation, de lopinion un droit, refonder la régulation politique en processus d*archicrations*. Il ne sagit pas dajouter un modèle de plus, mais de rendre à lhistoire ce dont elle a été amputée, faute doccultation et faute dimpensé : ses scènes fondatrices, émancipatrices et régulatrices qui font tenir les mondes.
Ce que notre époque appelle, est bien plus quun ajustement marginal des politiques existantes, bien plus quune moralisation incrémentale des comportements individuels. Cest une réinvention des conditions mêmes dapparition et de mobilisation du pouvoir : des lieux, des temps, des procédures, des mémoires où les décisions qui nous engagent puissent être amenées en scène, instruites, contestées, requalifiées. Les dix gestes qui suivent nont pas valeur de programme clos ni de plan de réforme. Ils constituent des hypothèses archicratiques : des propositions de formes scéniques minimales, destinées à être discutées, expérimentées, corrigées. Ils nont de sens que sils deviennent eux-mêmes objets dépreuve.
Premier geste : *instaurer un droit universel au différé contradictoire*. Un tel droit ne viserait pas à ralentir indistinctement toute décision, mais à garantir que toute mesure qui affecte substantiellement une existence allocation, sanction, fermeture, expulsion, tri automatique puisse être suspendue dans un temps institué, devant une instance capable den reconsidérer les motifs et les effets. Ce droit ne se confond pas avec un simple droit au recours individuel : il institue le temps scénique comme composante non négociable de la régulation.
Deuxième geste : *fonder, pour chaque grand dispositif de régulation, un journal de justification*. Il ne sagirait pas dajouter un rapport de plus aux archives administratives, mais dexiger que toute décision structurante (création dun algorithme, dun barème, dun standard) soit accompagnée dun récit argumenté de ses raisons, des alternatives écartées, des effets anticipés. Ce journal ne sanctifie pas la décision ; il la rend adressable. Il crée une mémoire publique du fondement, à partir de laquelle critiques, révisions et contre-propositions peuvent être formulées.
Troisième geste : *instituer un visa daffectation pour les principaux instruments de calcul et de qualification*. Chaque indicateur, chaque score, chaque seuil ne serait plus un simple paramètre technique, mais devrait porter trace de ses finalités déclarées et des collectifs qui en ont débattu. Ce visa nautorise pas seulement lutilisation dun outil ; il inscrit loutil dans une adresse éthique et politique : qui décide que telle métrique est légitime pour mesurer telle réalité, avec quelles garanties, pour combien de temps ?
Quatrième geste : *instaurer un coupe-circuit citoyen*. Il sagirait de prévoir, dans les dispositifs institutionnels, des mécanismes par lesquels des collectifs citoyens, usagers, travailleurs, habitants puissent suspendre temporairement lapplication dune décision lorsque des contradictions graves, des effets non prévus ou des injustices manifestes apparaissent. Ce geste nest pas un droit dobstruction généralisée ; il est la traduction procédurale du principe selon lequel aucune *cratialité* ne doit se déployer sans possibilité dinterruption scénique.
Cinquième geste : *instituer un tribunal de lalgorithme*. Non pas un organe chargé de valider ou dinterdire abstraitement la technique, mais une scène dépreuve où les architectures computationnelles qui organisent laccès aux droits, à linformation, aux ressources, soient rendues comparables, critiquables, reformulables. Un tel tribunal devrait être doté de compétences mixtes juridiques, techniques, sociales et travailler à partir de cas concrets. Il ne sagit pas dajouter une couche de contrôle symbolique ; il sagit de ramener lalgorithme en scène.
Sixième geste : *rétablir des assemblées daffectation là où les décisions sont aujourdhui dispersées dans des chaînes opaques*. Dans les hôpitaux, les écoles, les agences sociales, les plateformes, ces assemblées seraient chargées dinstruire publiquement la manière dont les ressources, les priorités, les charges et les risques sont distribués. Elles ne se substituent pas aux institutions existantes ; elles en constituent le moment scénique, là où les critères implicites peuvent être explicités, contestés, ajustés.
Septième geste : *rendre révocables les mandats au sein des institutions qui conçoivent, paramètrent et pilotent les dispositifs de régulation*, quil sagisse dinstances publiques, de régulateurs indépendants ou de structures privées investies de missions dintérêt général. La révocabilité ne signifie pas linstabilité permanente, mais la possibilité, pour les collectifs concernés, de mettre fin à un mandat lorsquil apparaît que l*archicration* est durablement contournée, que les fondements ne sont plus adressés, que la *cratialité* sest autonomisée.
Huitième geste : *instaurer, dans chaque budget institutionnel dimportance, un budget scénique*. Il ne sagirait pas dun poste décoratif, mais dune ligne dédiée au financement des dispositifs dépreuve : temps de délibération, traduction, médiation, expertise contradictoire, dispositifs de restitution. Un budget sans scène est un budget sans adresse. Reconnaître un budget scénique, cest faire de la scène non plus un coût superflu, mais une condition de validité de la dépense.
Neuvième geste : *inscrire dans le droit un principe général de révision archicrative*. Ce principe reconnaîtrait que toute architecture régulatrice loi, norme, algorithme, accord institutionnel doit pouvoir être réexaminée à intervalles déterminés, à partir dinstances où les personnes affectées, les savoirs concernés, les effets constatés sont convoqués. Il ne sagit pas dinstaurer une instabilité chronique, mais de consacrer le fait que la *co-viabilité* ne se maintient quau prix dune disponibilité organisée à la révision.
Dixième geste : *construire des cartographies des scènes manquantes.* Au lieu de se contenter danalyses sectorielles, il sagirait de repérer, sur un territoire ou dans une chaîne de valeur, les lieux où des décisions structurantes sont prises sans scène identifiable, où les personnes affectées nont ni adresse, ni délai, ni recours. De telles cartographies ne sont pas un exercice académique de plus : elles constituent un instrument politique central pour orienter linvention des scènes à venir, pour ne pas laisser invisible ce que la seule *cratialité* préfère absorber.
Ces dix gestes népuisent pas la gamme des formes possibles d*archicration*. Ils ne valent pas comme une doctrine à appliquer, mais comme une invitation à la mise à lépreuve : ils devront être discutés, critiqués, complétés, remplacés, parfois abandonnés. Leur seule prétention est de montrer que l*archicratie*, loin dêtre un mot abstrait, peut se traduire en dispositifs concrets, situés, qui redonnent aux collectifs la capacité dadresser le pouvoir qui les traverse. Et cest à ce niveau que nous posons notre ouvrage, moins une doctrine quune mise à lépreuve publique, moins un programme quune fondation différée : linvitation à restaurer la capacité institutionnelle de refonder le “pouvoir de” et le “pouvoir sur” nos vies, à hauteur de la *téra-machine* en constitution*.*
Nous n'assistons ni à un retour primordial du désordre ni à une
raréfaction du pouvoir. Ce qui se transforme, plus discrètement mais de
manière décisive, ce sont les conditions mêmes sous lesquelles la
régulation peut apparaître. Ce qui se reconfigure sous nos yeux n'est
pas d'abord la quantité de pouvoir à l'œuvre dans nos sociétés, mais la
manière dont ce pouvoir se rend — ou ne se rend plus — visible,
adressable, contestable. La conflictualité ne disparaît pas ; elle
change de régime d'existence. Là où elle trouvait autrefois à se
formuler dans des lieux identifiables, à se différer dans des
temporalités instituées, à se confronter dans des espaces d'énonciation
reconnus, elle se trouve de plus en plus distribuée dans des chaînes
opératoires qui se présentent comme de simples enchaînements techniques.
Flux, scores, interfaces, barèmes, protocoles : autant de formes qui
n'abolissent pas les décisions, mais en modifient profondément les
conditions d'apparition. Ce qui relevait d'une épreuve devient
traitement. Ce qui relevait d'une adresse devient calcul. Ce qui
relevait d'une justification devient paramétrage.
Ce déplacement est d'autant plus difficile à saisir qu'il ne se donne
pas comme rupture. Il s'installe dans la continuité apparente des
dispositifs, dans l'amélioration de leur efficacité, dans la promesse
d'une gestion plus rapide, plus fluide, plus objective des situations.
Le conflit y est désamorcé au nom de la performance. Le différé y est
perçu comme ralentissement. L'exposition y est remplacée par une
visibilité sans interlocution, où tout semble disponible sans que rien
ne soit véritablement tenu. Il ne s'agit pas d'un vide. Il ne s'agit pas
d'un monde sans normes ni d'un effondrement du pouvoir. Il s'agit d'un
recouvrement. Ce que cet essai-thèse a nommé oblitération archicratique
désigne cette dynamique précise : la substitution progressive de la
scène par l'exécution, du différé par l'automaticité, de l'énonciation
par la trace, de l'épreuve par la donnée.
Le pouvoir ne cesse pas d'opérer ; il cesse de comparaître. Les
décisions continuent d'être prises ; elles cessent d'être tenues. Elles
ne disparaissent pas ; elles se soustraient aux formes où elles
pourraient être rapportées à leurs fondements, confrontées à leurs
effets, reprises à partir de ce qu'elles affectent.
Ce qui caractérise ainsi notre situation n'est pas une crise de la
conflictualité, mais une crise de sa tenue. Non l'absence de dissensus,
mais l'altération des formes capables de le porter. Non la disparition
du politique, mais sa désarticulation progressive hors des scènes où il
pouvait encore apparaître comme tel. À ce niveau, ce que nous appelons
crise ne relève plus d'un dysfonctionnement partiel, ni d'une dérive
simplement sectorielle. Elle engage les conditions même dans lesquelles
un monde peut encore faire apparaître, soutenir et transformer ce qui le
traverse.
Ce déplacement du regard a commandé tout le parcours accompli. Il a
d'abord fallu apprendre à voir. Distinguer ce qui fonde de ce qui opère,
ce qui opère de ce qui met à l'épreuve. Rompre avec les confusions qui
font passer l'exécution pour la justification, la visibilité pour
l'opposabilité, la procédure pour la scène. Sans cette discipline de
détectabilité, le présent demeure illisible et la critique se dissout
dans des généralités. L'une des ambitions premières de ce travail aura
donc été de donner des prises : non pas inventer un vocabulaire pour le
plaisir de l'invention, mais rendre discernable ce que les descriptions
ordinaires du pouvoir, de la gouvernance et de l'administration laissent
trop souvent se confondre.
Encore fallait-il que ce vocabulaire n'usurpe pas sa propre nécessité.
L'archicratie ne vaut pas parce qu'elle pourrait tout redire dans sa
langue ; elle vaut seulement là où elle permet de discerner quelque
chose qui, sans elle, resterait confondu, euphémisé ou inaperçu. Elle ne
constitue donc ni une théorie totale du politique, ni une clef
universelle des mondes historiques, mais un instrument critique situé,
tenu à une obligation de retenue : se taire là où il n'apporte aucun
gain de lisibilité, et répondre de ses distinctions là où il prétend en
produire. C'est à cette condition seulement qu'un paradigme cesse d'être
un idiome de surplomb pour devenir une épreuve réelle de connaissance.
Il a fallu ensuite remonter plus loin. Reconnaître que les formes
d'épreuve ne sont ni des raffinements tardifs des modernis
représentatives, ni des suppléments ajoutés à des ordres déjà
constitués, mais des conditions plus profondes de la tenue des mondes
humains. Avant les États, avant les bureaucraties, avant les
codifications juridiques stabilisées, des collectifs ont dû inventer des
manières de différer, de ritualiser, d'exposer ce qui les traversait.
L'histoire du politique ne commence pas avec la souveraineté constituée
; elle commence avec la nécessité, pour un monde traversé de forces
hétérogènes, de ne pas s'abandonner à leur pure immédiateté. Elle
commence là où quelque chose comme une reprise devient possible, là où
ce qui affecte peut être reconduit à une forme d'exposition, si
rudimentaire, violente ou dissymétrique soit-elle. La scène n'est donc
pas un luxe moderne. Elle appartient à la structure même des mondes qui
tiennent.
Il a fallu également traverser les grandes pensées du pouvoir, non pour
les annuler, ni pour les annexer de force à un nouveau système, mais
pour en mesurer les prises et les limites. Certaines ont privilégié le
fondement, d'autres l'opération, d'autres encore la conflictualité, la
dispersion des dispositifs, l'individuation, la justification ou le
dissensus. Toutes ont saisi quelque chose de réel ; aucune n'a tenu
entièrement ensemble les conditions d'une régulation habitable. Ce que
cette traversée a rendu possible, ce n'est pas une synthèse des
doctrines, mais une méta-grammaire du politique, capable de les relire à
partir de ce qu'elles permettent — ou non — de penser : comment un
ordre se fonde, comment il opère, comment il accepte d'être mis à
l'épreuve.
Il a fallu enfin éprouver cette grammaire dans l'histoire effective des
transformations modernes, là où les capacités de régulation ont atteint
une intensité inédite. Ce qui apparaît alors n'est pas seulement une
succession d'innovations techniques, mais une série de reconfigurations
du rapport entre fondement, opération et épreuve. Chaque révolution
industrielle a redessiné ce triangle ; chacune a accru certaines
puissances tout en décalant, fragmentant ou fragilisant les formes
capables de les soutenir. L'histoire moderne n'apparaît plus comme celle
d'un progrès simplement technique ; elle devient lisible comme celle des
déplacements successifs du lieu où le pouvoir se rend — ou cesse de se
rendre comparable, contestable, révisable.
C'est au point le plus brûlant du présent que cette exigence se révèle
avec la plus grande netteté. Les tensions contemporaines ne se laissent
pas comprendre comme des crises séparées, ni comme des anomalies
sectorielles. Elles manifestent, chacune à leur manière, la difficulté
croissante à instituer des formes dans lesquelles ce qui est affecté par
les décisions peut être reconduit à une épreuve. Ce qui manque n'est pas
la capacité à produire des normes, des infrastructures, des critères,
des instruments. Ce qui manque, de plus en plus, c'est l'habileté à les
porter. De là la nécessité d'un déplacement conceptuel décisif :
substituer au lexique lisse de la durabilité la notion de co-viabilité.
Non pas un équilibre supposé entre intérêts déjà constitués, ni la
correction technocratique d'externalités, mais l'institution toujours
fragile, toujours révisable, toujours conflictuelle, des conditions sous
lesquelles des formes de vie hétérogènes peuvent encore tenir ensemble
sans destruction irréversible.
Ce qui se dégage ainsi de l'ensemble n'est pas une doctrine
supplémentaire, encore moins un système clos. C'est une condition — qui ne garantit ni harmonie ni salut, mais sans laquelle aucune
régulation ne peut être tenue comme monde. Cette condition peut
désormais être formulée simplement : une régulation ne devient habitable
qu'à la mesure où ce qui la fonde, ce qui l'opère et ce qui la met à
l'épreuve demeurent distinguables, articulés et exposables. Toute la
difficulté tient alors à ceci : maintenir cette distinction sans les
dissocier, et cette articulation sans les confondre. Là où ces
dimensions se confondent, se disjoignent ou se dérobent à l'exposition,
la régulation peut continuer à fonctionner ; elle cesse de se tenir.
C'est cette condition minimale que nous avons nommée archicratie. Ni
régime parmi d'autres, ni forme institutionnelle déterminée, ni idéal
moral à incarner : un seuil. Le seuil au-dessous duquel la régulation se
réduit à sa propre opérativité, et au-dessus duquel elle devient, au
moins en droit, habitable, parce qu'elle laisse ouverte la possibilité
de sa reprise. L'archicratie ne désigne ni la justice, ni la bonté, ni
la douceur des décisions ; elle désigne la condition sans laquelle ces
questions elles-mêmes cessent de pouvoir être posées politiquement.
Ce qui fonde une régulation ne se confond ni avec une autorité
abstraite, ni avec un texte, ni avec une tradition invoquée une fois
pour toutes ; cela renvoie à la capacité d'un ordre à exposer ses
raisons comme telles. Ce qui opère désigne les instruments, procédures
et dispositifs par lesquels le monde est effectivement découpé,
distribué, transformé. Ce qui met à l'épreuve, enfin, ne relève ni d'une
consultation formelle ni d'un recours marginal, mais de formes
instituées où fondements, opérations et effets peuvent être suspendus,
confrontés, repris.
Ces prises ne sont jamais données à l'état pur. Elles se recouvrent, se
déplacent, se distribuent inégalement selon les configurations. Mais
leur coprésence différenciée constitue la condition minimale d'une
régulation vivable. Là où l'opération se déploie sans être reconduite à
ses raisons, là où les décisions s'appliquent sans passer par des
épreuves effectives, la régulation bascule vers une forme de fermeture
qui ne relève ni du chaos ni du retrait du pouvoir, mais de son
auto-suffisance. Dans une telle configuration, le pouvoir ne se retire
pas ; il s'accomplit sans comparution. Il produit des effets, parfois
avec une grande précision, mais sans se laisser reprendre dans des
formes où ces effets pourraient être rapportés à des raisons
discutables. Il opère, mais ne s'expose plus. Il décide, mais ne se
laisse plus adresser. Tout fonctionne ; mais plus rien ne s'expose ni ne
s'explique.
La différence décisive se situe là. Entre une régulation capable
d'exécuter des procédures, de reproduire des normes, de gérer des flux,
et une régulation capable de se rapporter à elle-même à partir de ce
qu'elle affecte, la différence ne tient pas à l'intensité du pouvoir,
mais à la possibilité de sa mise à l'épreuve. Ce qui rend un monde
habitable n'est ni l'absence de tensions, ni la stabilité de ses
équilibres, ni la pure efficacité de ses dispositifs. C'est la forme
dans laquelle ce qui le traverse peut être porté sans être nié, différé
sans être dissous, exposé sans être annihilé.
À partir de là, la question n'est plus d'abord celle d'un bon régime,
mais celle d'un monde qui tient. Non d'un monde pacifié, homogène ou
réconcilié, mais d'un monde capable de porter ce qui le traverse sans
s'abolir dans sa propre exécution. Un monde qui ne tient ni par inertie,
ni par répétition, ni par l'évidence supposée de ses fondements. Il
tient parce qu'il est capable de porter ce qui le traverse sans le nier,
de différer ce qui l'affecte sans le dissoudre, d'exposer ce qui le
gouverne sans s'effondrer sous sa propre mise en question. Habiter un
monde ne signifie pas simplement y vivre. Cela signifie pouvoir y
comparaître. Pouvoir y demander d'où parle ce qui décide. Pouvoir y
identifier ce qui opère. Pouvoir y rouvrir le temps lorsque l'exécution
tend à se refermer sur elle-même. Pouvoir y faire apparaître ce qui,
sans cela, demeurerait converti en variable, en score, en flux.
La scène prend ici son sens le plus fort. Elle n'est ni un supplément
institutionnel, ni un décor ajouté au pouvoir pour en améliorer
l'acceptabilité, ni une métaphore commode pour désigner des espaces de
parole. Elle est l'une des formes à travers lesquelles un ordre cesse
d'être purement opératoire pour devenir politiquement tenable. Là où il
y a scène au sens fort — c'est-à-dire espace différé, documenté,
institué, capable de suspendre et de requalifier — la régulation ne se
contente pas d'agir : elle accepte de comparaître. C'est dans cette
comparution que se joue la possibilité, pour un monde, de ne pas se
réduire à ce qu'il exécute.
Il faut ici maintenir une distinction que tout ce travail a jugée
décisive. Dire que la scène est condition de viabilité ne signifie
nullement que toute scène serait en elle-même juste, démocratique ou
émancipatrice. L'histoire des formes politiques, juridiques,
religieuses, administratives, guerrières, marchandes ou sacrificielles
montre au contraire que des scènes peuvent être violentes,
dissymétriques, inquisitoriales, spectaculaires, capturées. La scène
n'est pas bonne parce qu'elle apparaît ; elle devient politiquement
décisive lorsqu'elle institue réellement l'épreuve de ce qu'elle expose.
Ce qui compte n'est pas l'existence abstraite d'un lieu d'apparition,
mais la possibilité effective qu'il ouvre : peut-on y demander les
fondements ? Les instruments peuvent-ils y être rendus visibles ? Les
effets peuvent-ils y être rapportés à ceux qu'ils affectent ? Le différé
est-il réel ou purement fictif ? La suspension a-t-elle une force
transformatrice ou n'est-elle qu'un rite sans prise ?
Il n'en demeure pas moins que, sans scène, la régulation se dégrade
qualitativement. Elle peut continuer à fonctionner ; elle peut même
gagner en efficacité apparente. Mais elle perd sa mémoire, sa
réversibilité, sa capacité à se rapporter à elle-même autrement que par
recalibrage interne. Elle applique, classe, répartit, déclenche, module
; mais elle ne se reprend plus. Elle produit des normes sans en exposer
les raisons, des décisions sans en instituer l'épreuve, des effets sans
en organiser le retour. Elle tient encore ; mais elle ne sait plus
répondre de la manière dont elle tient. C'est en ce point qu'un monde
sans scène devient injustifiable. Non pas nécessairement injuste dans
chacun de ses effets immédiats ; non pas chaotique ; non pas dépourvu de
cohérence locale. Il peut très bien fonctionner, produire des résultats,
stabiliser provisoirement des situations, maintenir des chaînes
d'obéissance ou d'adaptation. Mais il devient injustifiable parce qu'il
ne dispose plus des formes dans lesquelles ses propres décisions peuvent
être rejouées, exposées, interrogées, reformulées.
On comprend alors ce que la co-viabilité signifie exactement. Elle ne
désigne ni la simple coexistence de formes de vie différentes, ni leur
compatibilité gestionnaire, ni un optimum de répartition des ressources
ou des charges. Elle désigne la capacité, toujours fragile, toujours
située, toujours révisable, d'un monde à instituer des formes dans
lesquelles les hétérogènes qui le traversent peuvent être mis en tension
sans être soit mutuellement détruits, soit administrativement
neutralisés. Elle est moins un état qu'un régime d'épreuves. Elle ne se
mesure pas seulement à l'efficacité des ajustements ; elle se mesure à
la possibilité qu'un ordre laisse ouvert sa propre reprise à partir de
ce qu'il affecte.
C'est à ce niveau que le diagnostic du présent trouve sa formulation la
plus nette. Non dans l'idée d'un monde privé de régulation, mais dans
celle d'un monde où la régulation tend à se déployer hors des formes qui
permettaient de la tenir. Qu'il s'agisse des droits sociaux, de
l'habitabilité écologique des milieux ou des architectures numériques de
décision, la même logique se renforce : les dispositifs deviennent plus
puissants au moment même où les formes capables d'en soutenir l'épreuve
deviennent plus fragiles, plus tardives, plus périphériques. Ce ne sont
pas les décisions qui disparaissent ; ce sont les manières dont elles
pourraient être tenues.
Les droits, dans de nombreuses configurations sociales, se trouvent
intermédiés par des procédures dont la logique demeure difficilement
accessible à ceux qu'elles affectent ; les décisions qui concernent
l'habitabilité écologique des milieux se trouvent portées par des
instruments puissants, mais rarement rapportées à des espaces où leurs
fondements pourraient être disputés ; les architectures numériques et
algorithmiques rendent possible une distribution fine des traitements,
des classements, des accès, sans rendre aisément localisable le lieu de
leur mise à l'épreuve. Ces dimensions ne doivent pas être comprises
comme des sphères séparées. Elles constituent les expressions
différenciées d'un même processus : celui par lequel la régulation tend
à se déployer hors des formes d'épreuve qui permettaient de la tenir
comme monde.
C'est en ce sens que l'autarchicratie peut être nommée comme la
contre-figure terminale de l'archicratie. Non un régime au sens
classique, ni une idéologie, ni un type d'État, mais une configuration
dans laquelle la régulation tend à se refermer sur sa propre
opérativité, à produire ses propres critères de validité, à
s'auto-justifier sans passer par des épreuves effectives. Dans une telle
configuration, les instruments, les modèles, les indicateurs, les
procédures deviennent à la fois ce qui opère et ce qui justifie. Les
boucles se ferment. Les ajustements se font à partir de leurs propres
résultats. Les audits vérifient la conformité à des critères produits
par les systèmes eux-mêmes. La régulation devient auto-référentielle.
Cette bascule ne doit pas être dramatisée comme si elle était totale,
homogène, déjà accomplie. Des scènes subsistent, parfois robustes,
parfois fragiles. Des espaces de contestation, de délibération, de
reprise continuent d'exister. Mais ils apparaissent souvent comme
disjoints des lieux où les décisions se prennent effectivement. La
tension se joue moins entre présence et absence de scène qu'entre leur
centralité et leur marginalisation. Le problème décisif n'est pas de
savoir si toute scène a disparu ; il est de comprendre que la dynamique
dominante tend à rendre optionnelle l'épreuve dont dépend pourtant la
viabilité de la régulation.
C'est ici que la distinction entre durabilité et co-viabilité prend
toute sa force. La durabilité, telle qu'elle s'est imposée dans les
discours contemporains, ne doit pas être critiquée d'abord pour ses
intentions, mais pour sa forme. Elle tend à fonctionner comme un
opérateur de neutralisation de la conflictualité : en posant comme
objectif la préservation ou l'ajustement de certains équilibres, elle
déplace l'attention vers la gestion des variables, l'optimisation des
paramètres, la correction des trajectoires. Ce déplacement n'est pas
illégitime en soi ; il le devient lorsqu'il s'accompagne d'une
évacuation des formes dans lesquelles les choix qui structurent ces
trajectoires pourraient être discutés. La durabilité peut alors
s'accommoder d'une régulation sans scène. La co-viabilité, elle, en fait
une impossibilité.
La différence est décisive. La première tend à organiser la continuité
des systèmes ; la seconde à instituer les conditions de leur reprise. La
première privilégie l'ajustement des variables ; la seconde la mise à
l'épreuve des fondements. La première peut se satisfaire d'une
gouvernance qui corrige des déséquilibres ; la seconde exige des formes
dans lesquelles les conditions mêmes de ces corrections peuvent être
adressées, contestées, transformées. Ainsi comprise, la co-viabilité ne
constitue pas un idéal abstrait. Elle désigne le régime minimal dans
lequel un monde peut continuer à se transformer sans se soustraire à sa
propre interrogation. Elle n'abolit pas les tensions ; elle en organise
la tenue. Elle n'élimine pas les conflits ; elle en rend l'épreuve
possible. Elle ne garantit pas la justice ; elle rend au moins pensable
sa recherche.
Il reste alors à comprendre ce qui, en dernière instance, est affecté
par cette transformation. Non pas seulement des institutions, des
règles, des procédures, mais des formes d'existence. Des vies. Des
milieux. Des devenirs. Si l'archicratie prend finalement une telle
importance, ce n'est pas parce qu'elle offrirait une théorie plus
satisfaisante du pouvoir ; c'est parce qu'elle reconduit l'analyse à ce
qui, sans scène, devient politiquement illisible. Là où la régulation se
déploie sous forme de flux, de calculs, de traitements, le vivant tend à
être reconduit à des variables. Les milieux deviennent des stocks ou des
contraintes. Les corps deviennent des profils, des trajectoires, des
cas. Les expériences deviennent des données d'ajustement. Ce processus
n'est pas nécessairement intentionnel. Il résulte de la logique même des
dispositifs qui, pour fonctionner, doivent simplifier, catégoriser,
standardiser. Mais cette simplification a un effet décisif : elle
désinscrit le vivant de la scène. Elle le rend opérable sans qu'il ait à
apparaître.
Le vivant ne disparaît pas ; il devient politiquement illisible. Il est
là, partout affecté, mobilisé, transformé — mais de moins en moins
capable de faire retour sur ce qui l'affecte. Un monde sans archicration
est un monde dans lequel le vivant est présent sans être représentable,
affecté sans être adressable, engagé sans être entendu. Il est pris dans
des opérations, mais il ne peut plus apparaître comme ce à partir de
quoi celles-ci devraient être interrogées.
C'est en ce sens que l'oblitération archicratique produit une crise de
reconnaissance. Non pas au sens restreint d'une reconnaissance morale ou
symbolique, mais au sens plus fondamental d'une reconnaissance comme
condition d'apparition dans un espace où l'on peut être pris en compte.
Reconnaître ne signifie pas simplement identifier ou décrire. Cela
signifie instituer des formes dans lesquelles ce qui est affecté peut
être reconduit à une scène, où il peut être exposé, où il peut entrer en
relation avec ce qui décide. Sans cette reconnaissance, le vivant peut
être protégé, géré, optimisé ; il ne peut pas être politiquement tenu.
Il faut alors comprendre que la question de la scène n'est pas
extérieure à celle de la liberté. Elle en constitue l'une des conditions
minimales. Non la liberté comme autonomie absolue, mais comme
possibilité d'intervenir sur les conditions qui nous affectent. Une
société qui ne dispose plus de formes dans lesquelles ses propres
régulations puissent être interrogées tend à se percevoir comme soumise
à des nécessités. Elle perd la capacité de distinguer ce qui relève de
contraintes inévitables et ce qui relève de choix. À l'inverse, une
société qui institue des épreuves se dote de la possibilité de se
rapporter à elle-même comme à un ensemble de décisions révisables. Elle
ne supprime pas les contraintes, mais elle les inscrit dans des formes
où elles peuvent être discutées.
À ce point, aucune réponse définitive ne peut être apportée. Aucun
modèle achevé ne peut être proposé. Mais une exigence demeure, désormais
visible et irréductible. Un monde ne devient inhabitable ni parce qu'il
est traversé de tensions, ni parce qu'il doit décider dans
l'incertitude, ni parce qu'il affronte des contraintes puissantes. Il le
devient lorsqu'il ne dispose plus des formes capables de porter ce qui
le traverse autrement que par la pure exécution. Ce qui est en jeu n'est
ni la suppression du conflit, ni l'optimisation des dispositifs, ni la
stabilisation d'un équilibre. Ce qui est en jeu, c'est la possibilité de
maintenir ouvertes les formes dans lesquelles un monde peut se rapporter
à lui-même à partir de ce qu'il affecte. La possibilité, toujours
fragile, toujours menacée, de ne pas confondre ce qui fonctionne avec ce
qui se tient.
Rendre à la régulation les formes dans lesquelles elle peut encore être
tenue comme monde : telle est l'exigence à laquelle reconduit l'ensemble
de ce parcours. Non comme un programme, ni comme une promesse, mais
comme ce sans quoi aucune transformation ne peut être habitée. Car ce
n'est jamais l'ordre seul qui fait tenir un monde. C'est la possibilité,
pour cet ordre, d'être interrompu, exposé, repris. Là où cette
possibilité se ferme, le monde peut continuer à marcher ; il cesse peu à
peu d'être habitable. Là où elle demeure ouverte, fût-ce dans le
conflit, sous contrainte, précairement, quelque chose du politique
subsiste encore : non la paix, ni l'innocence, ni l'harmonie, mais la
capacité d'un monde à ne pas se confondre avec sa propre exécution.

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
---
title: "Démarrage — Essai-thèse"
edition: "archicratie"
status: "modele_sociopolitique"
level: 1
version: "0.0.1"
concepts: ["archicratie"]
links:
- type: "definition"
target: "/glossaire/archicratie/"
note: "Terme canonique."
order: 0
summary: "Page de test (structure)."
---
import Callout from "../../components/Callout.astro";
import Term from "../../components/Term.astro";
Ceci est une page de test pour valider la structure de l**Essai-thèse**.
<Callout kind="definition" title="Entrée minimale">
<p>
<Term term="Archicratie" slug="archicratie" /> : régime où linstance régulatrice est tenue dexposer ses prises,
ses critères et ses scènes dépreuve.
</p>
</Callout>
<Callout kind="these" title="Ce que lédition web doit rendre possible">
<p>Une lecture à plusieurs niveaux, sans confusion entre les productions, et une citabilité stable.</p>
</Callout>
<div class="level-2">
<Callout kind="objection" title="Objection (niveau 2)">
<p>Que gagne-t-on par rapport à une simple doctrine ? Réponse : la scène, la contrainte dexposition, la pluralisation des prises.</p>
</Callout>
</div>
<div class="level-3">
<Callout kind="limite" title="Limite (niveau 3)">
<p>Tout schéma darticulation doit préciser ses non-déductions (transpositions), sinon confusion Traité ↔ Archicratie.</p>
</Callout>
</div>

View File

@@ -1,13 +0,0 @@
---
title: "Démarrage — Atlas"
edition: "atlas"
status: "cartographie"
level: 1
version: "0.0.1"
concepts: ["archicrates"]
links: []
order: 0
summary: "Page de test (structure)."
---
Ceci est une page de test pour valider la structure de l**Atlas**.

View File

@@ -0,0 +1,141 @@
---
title: "Annexe — Glossaire archicratique pour laudit des systèmes dIA"
edition: "cas-ia"
status: "application"
level: 1
version: "0.1.0"
concepts: []
links: []
order: 195
summary: ""
source:
kind: docx
path: "sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Annexe_Glossaire_Archicratique_Cas_IA.docx"
---
# Annexe Glossaire archicratique pour laudit des systèmes dIA
Cette annexe propose un bref glossaire des notions archicratiques mobilisées dans laudit de Système F. Elle na pas vocation à réexposer la théorie dans toute son ampleur, mais à fournir au lecteur du cas pratique quelques repères opératoires pour suivre le fil des analyses.
## Arcalité
On appelle *arcalité* la *dimension de tout ordre régulateur qui concerne ses fondements* : *ce qui lautorise, ce qui le rend légitime, ce qui lui donne droit de décider*. L*arcalité* est faite de *récits*, de *principes*, de *valeurs*, de *visées explicites ou implicites* : lutter contre la fraude, protéger lÉtat social, garantir la sécurité, promouvoir la santé, favoriser le “talent”, préserver la liberté dexpression, etc.
Dans Système F, on distingue :
- une *arcalité déclarée* : chartes d“IA digne de confiance”, discours politiques sur la lutte contre la fraude, documents de marketing qui promettent d“objectiver” le risque ou d“optimiser” la sélection ;
- une *arcalité implicite* : choix de variables (coûts de santé comme proxy des besoins, nationalité comme indicateur de risque), arbitrages entre faux positifs et faux négatifs, composition des jeux de données, sélection de critères de “talent” ou de “dangerosité”.
Lenjeu archicratique nest pas de moraliser *a posteriori* ces choix, mais de les *faire comparer sur scène* : *exposer les axiomes silencieux* (“nous considérons quil est plus grave de laisser passer un fraudeur que de punir un innocent”, etc.), *et permettre quils soient discutés, contestés, révisés*.
## Cratialité
La *cratialité* désigne la dimension opératoire du pouvoir régulateur : la manière dont il sapplique concrètement, par quels instruments, quelles chaînes techniques, quelles procédures. Cest le “*par quoi*” et le “*comment*”.
Dans Système F, la *cratialité* se déploie dans :
- les *données mobilisées* (dossiers fiscaux, historiques de soins, casiers judiciaires, CV, contenus de plateformes) ;
- les *pipelines* qui transforment des situations complexes en vecteurs numériques ;
- les *modèles* eux-mêmes (architectures, paramètres, fonctions de coût, seuils de décision) ;
- les *interfaces* (tableaux de bord, scores de risque, codes couleur, messages dalerte) ;
- les *procédures dintégration* (règles internes qui imposent de suivre la recommandation ou den justifier tout écart).
*Cratialité* nest pas synonyme de “technique” : elle inclut aussi les *règles organisationnelles*, les *consignes*, les *scripts de travail*. Laudit archicratique ne se contente pas de repérer les algorithmes ; il suit les chaînes cratiales jusquaux guichets, aux tribunaux, aux services hospitaliers, aux départements RH, aux plateformes.
## Archicration
L*archicration* est le troisième terme du triptyque : elle désigne la *scène dépreuve où arcalité et cratialité sont amenées en visibilité, confrontées, mises à lépreuve et, si nécessaire, transformées*.
Sans *archicration*, les fondements restent fantômes, et le pouvoir opératoire devient autarcique.
Une *archicration* digne de ce nom réunit quatre conditions minimales :
1. *Public(s) concerné(s) effectivement représentés* (et pas seulement des experts parlant “en leur nom”) ;
2. *Accès aux prises* : connaissance minimale des arcalités à lœuvre (valeurs, finalités, proxies, fonctions de coût) et des cratialités (chaînes techniques, procédures, interfaces) ;
3. *Capacité de contestation* : possibilité dinterpeller les choix, de demander des modifications, de suspendre un dispositif ;
4. *Effet réel* : ce qui se dit sur la scène peut produire des transformations sur le système, pas seulement un “avis” consultatif.
Dans Système F, la plupart des dispositifs existants comités déthique, audits de biais, formulaires de recours natteignent pas ce seuil : ce sont des *archicrations fantômes*, qui donnent lallure de la scène sans en avoir la puissance.
## Hypotopie, hypertopie, atopie
Ces trois termes qualifient la topologie des scènes où la régulation IA apparaît (ou disparaît).
- Une *hypotopie* est une *scène pauvre en prises, faiblement outillée, où les gens peuvent parler mais sans pouvoir effectivement infléchir la régulation*. Par exemple : un usager qui discute avec un agent de caisse sociale dune décision prise en réalité par Système F, sans accès ni aux paramètres ni aux logs ; un formulaire de recours tellement opaque et lourd quil décourage toute contestation.
- Une *hypertopie* est une *scène surdotée à huit-clos* : tout sy joue paramètres, seuils, choix de déploiement mais entre un nombre très limité dacteurs (directions, ingénieurs, juristes, consultants). *Les comités de pilotage où lon décide dintégrer ou non Système F dans la chaîne de décision, les réunions de design des modèles sont souvent des hypertopies*.
- Une *atopie* est une *scène fantomatique* : elle mime le dispositif dépreuve, sans donner prise réelle. Consultations publiques en ligne sans impact, “boîtes à idées” numériques, mécanismes de *feedback* qui alimentent surtout des métriques internes (satisfaction, engagement) sans reconfigurer la régulation.
Lépreuve topologique, dans le cas IA, consiste à cartographier où se trouvent les *hypotopies, hypertopies, atopies*, et à inventer des scènes nouvelles qui rééquilibrent cette topologie.
## Autarchicratie
L*autarchicratie* désigne un régime où la régulation devient son propre souverain : les dispositifs de mesure, de modélisation et de contrôle se gouvernent eux-mêmes à partir de leurs propres métriques, et ne reconnaissent plus que très marginalement des scènes externes dépreuve.
Dans Système F, l*autarchicratie* prend plusieurs formes :
- *modèles évalués principalement par leurs métriques internes* (perte, précision, indicateurs déquité) ;
- *dispositifs dauto-audit et de reporting automatisé* qui servent de preuve de “responsabilité” sans ouvrir réellement le système à la contestation ;
- *boucles de rétroaction fermées* où les décisions passées alimentent les données futures (ceux que le système considère comme “à risque” seront davantage contrôlés, et donc produiront plus de “preuves” de risque).
L*autarchicratie* est lexact négatif de l*archicratie* : *là où larchicratie multiplie les scènes dépreuve, lautarchicratie les marginalise ou les simule*.
## Co-viabilité
Par *co-viabilité*, on entend la *capacité dun ordre régulateur à rendre simultanément vivables plusieurs dimensions de lexistence* : sociale, écologique, symbolique, parfois économique.
Dans le cas IA :
- la *co-viabilité sociale* renvoie à l*accès aux droits*, à la *protection contre larbitraire*, à la *dignité des personnes* (ne pas être réduit à un profil de risque opaque, pouvoir contester une décision qui affecte des prestations, des peines, des soins, un emploi) ;
- la *co-viabilité écologique* concerne les *coûts matériels de linfrastructure* (consommation énergétique, pressions sur les ressources, effets territoriaux des centres de données et des centres dextraction) et la *possibilité de les mettre en scène* ;
- la *co-viabilité symbolique* touche aux *représentations de la justice, du mérite, du risque, de la vérité*, et à la manière dont Système F contribue à les figer ou à les rouvrir.
L*épreuve de co-viabilité* ne se limite donc pas à mesurer des “impacts” ; elle demande : *quels types de scènes faut-il instituer pour que ces dimensions puissent être mises en balance, arbitrées et révisées ?*
## Politique des épreuves viables
La *politique des épreuves viables* est le nom donné à une *orientation normative minimale* : plutôt que de définir un modèle de justice idéal, elle consiste à organiser les épreuves auxquelles les dispositifs régulateurs doivent se soumettre pour rester archicratiques.
Appliquée à lIA, elle se traduit par une *série de gestes concrets* :
- *droit au différé contradictoire* pour les décisions appuyées par un système ;
- *journaux de justification* documentant les choix de modèles, de métriques, de proxies, dusages ;
- *visas daffectation* qui autorisent ou interdisent certains usages de scores dans des décisions critiques ;
- *coupe-circuits citoyens* permettant de suspendre un système en cas de dégâts massifs ;
- *tribunaux de lalgorithme*, assemblées daffectation, budgets scéniques pour financer le temps de la délibération et de la traduction ;
- *révisions archicratives périodiques* saccompagnant dune *cartographie des scènes manquantes*.
Dans le cas de Système F, ces gestes ne sont pas des ornements : ils définissent le seuil en deçà duquel il nest plus raisonnable de parler de gouvernance archicratique de lIA, mais d*autarchicratie numérique*.
## Système F
Enfin, *Système F* nest pas le nom dun produit commercial, mais celui dune figure composite : un modèle de fondation (LLM / modèle multimodal) accessible par API, intégré dans des flux de travail décisionnels de la protection sociale, de la santé, de la justice, des ressources humaines, des plateformes numériques.
Il condense des caractéristiques empiriquement attestées :
- *usage de systèmes de scoring* pour cibler des contrôles de fraude, évaluer des risques pénaux, gérer des programmes de soins, filtrer des candidatures, modérer des contenus ;
- *insertion de modules dIA dans des logiciels métier existants* ;
- *dépendance à des fournisseurs privés de services cloud et de modèles* ;
- *adoption de chartes d“IA responsable” et de procédures daudit parfois plus symboliques queffectives*.
Lintérêt de Système F nest donc pas de décrire un futur hypothétique, mais de donner un nom commun à une configuration déjà largement engagée, afin de lui appliquer, sans esquive, lensemble des épreuves archicratiques.

View File

@@ -0,0 +1,535 @@
---
title: "Chapitre I — Épreuve de détectabilité"
edition: "cas-ia"
status: "application"
level: 1
version: "0.1.0"
concepts: []
links: []
order: 120
summary: ""
source:
kind: docx
path: "sources/docx/cas-ia/Cas_Pratique-Archicratie_et_gouvernance_des_systemes_IA-Chapitre_1_Epreuve_de_detectabilite.docx"
---
# I. Épreuve de détectabilité : *arcalité / cratialité / archicration* dans un système dIA
Lépreuve de détectabilité ne consiste pas à ajouter une couche de vocabulaire au-dessus dun dispositif déjà saturé de termes techniques. Elle exige, au contraire, un geste presque naïf : *où voit-on quelque chose ? Où peut-on désigner, avec un minimum de précision, ce qui fonde, ce qui opère et ce qui met en épreuve ?* Tant que ces trois prises restent indiscernables ou introuvables, l*archicratie* nest pas simplement déficitaire ; elle est empêchée. Appliquée à un grand système dIA de fondation, lépreuve de détectabilité commande une micro-cartographie patiente des lieux, des moments et des interfaces où Système F se rend effectivement présent ou, plus souvent, se déploie sans se déclarer.
## I.1. Scénarisation structurée du système IA
Lenjeu de cette section nest pas dinventer un futur hypothétique, mais de composer un cas stylisé à partir déléments déjà avérés. Système F sera donc construit comme un agrégat abstrait de dispositifs bien documentés, provenant de plusieurs domaines (protection sociale, justice pénale, santé, recrutement, plateformes numériques). Chaque scène dusage que nous décrirons ensuite reprend des traits explicitement attestés dans ces cas réels.
### I.1.1. Les briques empiriques de Système F
#### **Protection sociale : SyRI et le scandale des allocations familiales néerlandaises**
Dans le domaine de la protection sociale, deux affaires néerlandaises forment un socle empirique très clair.
Le système SyRI (*Systeem Risico Indicatie*) était un outil de détection de fraude aux prestations sociales, fondé sur le croisement massif de données issues de différentes administrations (assurance sociale, emploi, logement, fiscalité). En février 2020, le tribunal de district de La Haye a jugé que la législation encadrant SyRI violait larticle 8 de la Convention européenne des droits de lhomme (droit au respect de la vie privée), notamment en raison du manque de transparence sur le fonctionnement du système, de la faible proportionnalité et du ciblage de quartiers pauvres. Le tribunal a ordonné larrêt immédiat de lutilisation de SyRI.
Parallèlement, le scandale des allocations pour la garde denfants (*toeslagenaffaire*) a mis au jour un modèle de classification du risque utilisé par ladministration fiscale néerlandaise, qui a conduit à accuser à tort environ 26 000 parents de fraude. Ces familles ont été sommées de rembourser des montants importants de prestations, ce qui a provoqué des situations de surendettement massif, de pertes de logement et de détresse psychologique ; une part disproportionnée des victimes avaient un arrière-plan migratoire. Amnesty International a montré que des éléments comme la nationalité ou la double nationalité étaient utilisés comme facteurs de risque, produisant une boucle de discrimination systémique ; lautorité néerlandaise de protection des données a conclu à un traitement discriminatoire.
Ces deux cas attestent empiriquement que des systèmes de *scoring* algorithmique peuvent être intégrés au travail de guichet, cibler certaines catégories de population, fonctionner de manière opaque et produire des décisions automatiques de suspension ou de recouvrement, avec des voies de recours très limitées.
#### **Justice pénale : lalgorithme COMPAS et laffaire Loomis**
Dans le champ pénal, lalgorithme COMPAS (Correctional Offender Management Profiling for Alternative Sanctions), développé par Northpointe/Equivant, est utilisé dans plusieurs États américains pour produire des scores de risque de récidive. Ces scores figurent dans des rapports daide à la décision destinés aux juges lors des phases de mise en liberté sous caution, de fixation de peine ou de libération conditionnelle.
Dans laffaire State v. Loomis (Cour suprême du Wisconsin, 2016), le prévenu a contesté lusage de COMPAS en soutenant que la méthodologie était secrète (secret commercial) et quil ne pouvait donc ni en vérifier lexactitude ni la contester. La Cour a maintenu la possibilité pour le juge dutiliser COMPAS, mais à condition daccompagner le score de mises en garde sur ses limites et en rappelant que la décision finale reste humaine, la méthode demeurant néanmoins non accessible à la défense.
On dispose ainsi dun cas où un score algorithmique chiffré sinscrit explicitement dans la chaîne de la décision judiciaire, tout en restant largement opaque pour le justiciable et même pour le tribunal.
#### **Santé : lalgorithme de gestion de risques analysé par Obermeyer et al. (Science, 2019)**
Dans le système de santé américain, Ziad Obermeyer et ses co-auteurs ont étudié un algorithme commercial largement utilisé pour la gestion de programmes de “soins intensifs” destinés à des patients à haut risque.
Cet algorithme produit, pour chaque patient, un score de risque ; ceux dont le score dépasse un seuil sont orientés vers des programmes qui mobilisent des ressources supplémentaires (suivi renforcé, coordination des soins, etc.).
Létude montre que, pour un niveau de risque donné selon lalgorithme, les patients noirs sont en moyenne beaucoup plus malades que les patients blancs (plus de pathologies non contrôlées, marqueurs biologiques plus dégradés).
La raison identifiée est que lalgorithme ne prédit pas directement la morbidité, mais les coûts de santé futurs ; or, dans un système marqué par des inégalités daccès aux soins, les patients noirs engendrent en moyenne moins de dépenses, à état de santé équivalent. Ce choix de variable (coûts comme proxy des besoins) introduit une biais structurel qui conduit à sous-orienter vers les programmes de soins renforcés des patients noirs qui en auraient le plus besoin.
Ce cas illustre très précisément comment une fonction de coût et un choix de proxy peuvent incorporer une *arcalité implicite* (coûts ≈ besoins) et produire des *effets cratiaux massifs* sur laccès aux ressources médicales.
#### **Recrutement : loutil de tri de CV dAmazon**
En 2018, Reuters a révélé quAmazon avait développé, puis abandonné, un outil interne de recrutement automatisé visant à classer des CV de candidat·es à des postes techniques. Entraîné sur une décennie dhistoriques de recrutement dans un secteur très masculin, le système a “appris” que les profils masculins étaient plus souhaitables. Il pénalisait les CV comportant le mot “womens” (comme “womens chess club captain”) et déclassait les diplômées de certaines universités qualifiées de « féminines ».
Confrontée à ces biais, lentreprise a renoncé à déployer cet outil en production. Mais lenquête montre quil est techniquement possible et, en pratique, déjà réalisé dintégrer des modèles genrés de scoring dans la chaîne de sélection des candidatures, en laissant leur logique sous-jacente hors de la vue des candidat·es.
#### **Plateformes numériques : modération et curation algorithmique**
Enfin, dans le monde des plateformes, la documentation abondante sur Facebook/Meta, YouTube, TikTok ou X (ex-Twitter) montre que la modération des contenus et la curation algorithmique reposent massivement sur des systèmes automatisés, lintervention humaine intervenant préférentiellement en seconde ligne ou pour les cas litigieux.
Des guides destinés aux utilisateurs ou aux praticiens décrivent le fonctionnement général de ces dispositifs : chez Facebook, par exemple, lIA est présentée comme “première ligne de défense”, qui scanne les contenus, détecte les possibles violations et les supprime directement ou les envoie à des modérateurs humains pour revue.
Au niveau réglementaire, le Digital Services Act européen impose désormais aux grandes plateformes de publier des rapports annuels de transparence sur la modération de contenu, en distinguant explicitement les contenus supprimés ou limités suite à des décisions automatisées.
Le rapport 2022 de lAgence des droits fondamentaux de lUE sur les biais dans les algorithmes mentionne dailleurs lusage croissant de systèmes de détection automatique de discours offensant, avec des risques de discrimination et de sur-modération de certaines communautés ou langues.
En parallèle, la création de l*Oversight Board* de Meta, doté dun pouvoir de révision de certaines décisions de modération, confirme lampleur des enjeux : une instance quasi-juridictionnelle a été instituée pour examiner des cas emblématiques et problématiques, souvent issus de décisions initiales prises par des systèmes automatisés ou semi-automatisés.
Ces éléments convergent : lessentiel de ce qui se voit ou ne se voit pas sur les grandes plateformes est trié, promu, enfoui, suspendu par des combinaisons dalgorithmes de recommandation, de détection et de filtrage.
### I.1.2. De ces cas à un système composite : définition structurée de “Système F”
À partir de ces pièces empiriques, nous pouvons maintenant définir plus rigoureusement ce que nous appellerons Système F.
#### **Un fournisseur de modèle de fondation accessible par API**
Du côté de loffre technique, il est désormais établi que des modèles de fondation (grands modèles de langage, modèles multimodaux) sont fournis sous la forme de services cloud accessibles via API. Le Comité européen de la protection des données (EDPB), par exemple, décrit explicitement le modèle “LLM as a Service” : un fournisseur héberge le modèle, en contrôle les poids et la formation, et donne accès aux utilisateurs par une interface de programmation, sans leur donner la main sur larchitecture interne.
Des initiatives comme Azure OpenAI Service pour les administrations publiques américaines ou européennes, et des programmes dédiés comme “OpenAI for Government” ou “ChatGPT Gov”, illustrent lextension de ce modèle au secteur public : les administrations peuvent appeler des modèles puissants pour traiter des textes, analyser des dossiers, générer des réponses, via des API sécurisées.
LAda Lovelace Institute a documenté, dans un rapport dédié, lusage déjà existant de modèles de fondation dans la sphère publique, intégrés à des outils “grand public” (moteurs de recherche, suites bureautiques, logiciels métiers) que les agents utilisent au quotidien.
Dans notre scénarisation, Système F désigne donc un fournisseur de modèle de fondation (type LLM / modèle multimodal), hébergé dans le cloud, accessible via API, et mis à disposition dacteurs publics et privés.
#### **Des intégrateurs qui fabriquent des solutions verticales**
Entre le fournisseur de modèle et les administrations, il y a des intégrateurs : grandes entreprises de services numériques, start-ups spécialisées, équipes internes “IA” de ministères ou dagences. Leur rôle concret, pour linstant, est bien attesté : adapter des briques de modèles génériques à des cas dusage sectoriels (chatbots administratifs, aides à la rédaction de courriers, outils danalyse de documents, systèmes de tri ou de priorisation).
Les dispositifs de détection de fraude sociale, de *scoring* pénal, de gestion de risques en santé et de tri de CV mentionnés plus haut ne reposent pas tous sur des modèles de fondation au sens strict, mais ils incarnent déjà ce rôle dintégrateur : transformer une capacité de calcul et de classification en un module prêt à lemploi pour une administration précise. Système F se situe dans cette continuité : un modèle générique, repris et encapsulé par une diversité de prestataires qui construisent des “solutions” pour la protection sociale, la justice, la santé, le recrutement, la modération.
#### **Des organisations utilisatrices : ministères, caisses, hôpitaux, tribunaux, entreprises, plateformes**
Enfin, les utilisateurs institutionnels que nous considérons caisses de prestations sociales, services fiscaux, tribunaux, hôpitaux, services RH, plateformes de réseaux sociaux ne sont pas conjecturaux : ce sont précisément ceux qui, dans les affaires documentées, ont déjà recouru à des algorithmes de scoring, de tri ou de filtrage. Les études sur lIA dans le secteur public montrent que les cas dusage montent en puissance, notamment dans les fonctions de traitement de dossiers, de tri de demandes, de détection danomalies, dassistance au recrutement et dinformation au public.
En agrégeant ces éléments, Système F désigne donc une chaîne socio-technique structurée en trois niveaux :
- un fournisseur de modèle de fondation via API ;
- des intégrateurs qui encapsulent ce modèle dans des solutions sectorielles ;
- des organisations utilisatrices qui insèrent ces solutions dans leurs procédures quotidiennes.
Cette structure nest pas une vue de lesprit : elle reflète larchitecture déjà mise en place par les grands fournisseurs dIA et adoptée, progressivement, par des administrations publiques et des entreprises.
### I.1.3. Quatre scènes typiques dusage, stylisées mais ancrées
Sur cette base, nous pouvons maintenant détailler quatre scènes dusage de Système F, en indiquant à chaque fois les cas réels qui nourrissent la description.
#### **Lagent de protection sociale et la liste des dossiers**
Dans de nombreux pays européens, les systèmes de détection de fraude aux prestations prennent la forme de listes de dossiers accompagnées dun score de risque. Les travaux sur SyRI et sur le scandale des allocations néerlandaises montrent que des fonctionnaires se voient présenter des listes de bénéficiaires classés par “profil de risque”, résultant de modèles qui croisent des données multiples (revenus, composition familiale, historique fiscal, etc.).
Dans notre scène, lagent·e dune caisse sociale ouvre une application de gestion des dossiers : une file virtuelle apparaît, avec pour chaque dossier un score numérique et un code couleur. Le tri par défaut ne suit plus lordre chronologique de dépôt, mais la priorité calculée par un module issu de Système F, configuré pour combiner “risque de fraude” et “urgence” selon des paramètres fixés en amont. Cette configuration est directement inspirée des systèmes réels de risque de classement (*risk scoring*), même lorsquils ne reposaient pas encore sur des modèles de fondation.
#### **Le juge et le rapport de risque**
Lexpérience américaine avec COMPAS montre quun rapport de risque algorithmique peut être intégré au dossier judiciaire, sous la forme dun document qui synthétise un score global et quelques facteurs aggravants ou atténuants. Dans laffaire Loomis, le prévenu a été évalué comme “à haut risque” par COMPAS, et ce rapport a pesé dans la justification de la peine.
Dans notre scène, le juge ne voit pas directement Système F, mais un rapport standardisé annexé au dossier : un score, une classe de risque, des éléments de langage justifiant ce score, issus dun module pénal connecté au modèle de fondation. Ce schéma reprend les traits factuels de COMPAS (score, opacité de la méthode, statut “daide à la décision”) tout en les transposant dans une architecture de modèle de fondation accessible via API.
#### **La médecin et la priorisation des patients**
Lalgorithme étudié par Obermeyer et al. montre comment un outil de gestion des risques en santé peut décider léligibilité à un programme de soins renforcés sur la base dun score calculé à partir des coûts passés.
Dans notre scène, un médecin hospitalier ouvre la liste des patients en attente dun examen lourd ou dune consultation spécialisée. Linterface, alimentée par un module de Système F, propose une “vue optimisée” : les patients sont ordonnés selon un indice de priorité calculé, avec un message qui signale lorsquelle sécarte de cette priorisation (“vous vous écartez de la recommandation algorithmique, confirmez-vous ?”). La scène est stylisée, mais elle transpose directement la logique documentée : un score de risque conditionne laccès à des ressources rares, et le praticien se voit suggérer un ordre “optimal”.
#### **La plateforme et la visibilité des contenus**
Les documents publics de Meta/Facebook reconnaissent que des systèmes automatisés scannent les contenus, les signalent, voire les suppriment directement en première intention. Les obligations de transparence du DSA confirment que les grandes plateformes utilisent des outils automatisés de modération et de recommandation à grande échelle.
Dans notre scène, un utilisateur poste un contenu critique sur une politique publique ou un témoignage lié à des prestations sociales. Un module dérivé de Système F, intégré à la chaîne de modération et de recommandation, évalue ce contenu : il peut le classer comme “à risque” (discours haineux, désinformation présumée, “contenu limite”), réduire sa visibilité ou déclencher une revue humaine. Lutilisateur ne verra, le plus souvent, quun message laconique (“votre contenu a enfreint nos standards”) ou une chute daudience difficilement interprétable.
## I.2. *Arcalité déclarée* du système
Par “*arcalité déclarée*”, nous désignerons lensemble des formules par lesquelles les acteurs qui conçoivent, diffusent ou utilisent des dispositifs algorithmiques disent ce quils font et au nom de quoi ils le font. Il ne sagit pas des détails techniques de limplémentation, ni des effets réels que nous avons mis au jour dans les affaires de fraude sociale, de justice pénale, de santé, de recrutement ou de plateformes ; il sagit des *justifications publiques*, telles quelles apparaissent dans les textes législatifs, les décisions de justice, les rapports dautorités administratives, les documents dentreprises, les chartes de “bonne conduite” et les programmes de régulation internationale. Cest cette couche discursive que lépreuve archicratique doit dabord prendre au sérieux, non pour la dénoncer en bloc comme “pure idéologie”, mais pour mesurer ce quelle rend visible et ce quelle tient pour acquis.
Dans les cinq domaines où nous avons décrit des usages de Système F protection sociale, justice pénale, santé, recrutement, plateformes numériques , cette *arcalité déclarée* adopte des formes différentes, liées aux traditions propres de chaque champ, mais elle se rassemble autour dune grammaire relativement stable : *lutte contre des menaces identifiées* (fraude, récidive, complications coûteuses, “mauvais recrutements”, contenus illégaux ou dangereux), *rationalisation de laction publique ou privée*, *promesse dobjectivité*, *impératif de sécurité et de confiance*. À ce niveau, Système F apparaît dabord comme un auxiliaire : lalgorithme nest pas présenté comme un souverain qui se substituerait aux institutions, mais comme un instrument qui permettrait à celles-ci de mieux remplir leurs missions.
### I.2.1. La fraude comme récit de fondation
Dans le domaine de la protection sociale, l*arcalité déclarée* est structurée par un récit désormais bien installé : *il sagit de défendre lintégrité de lÉtat social contre la fraude aux prestations, labus de droits et les détournements de fonds publics*. Comme la montré laffaire SyRI et, plus encore, le scandale des allocations pour la garde denfants, la lutte contre la *fraude* devient la matrice discursive qui justifie le recours à des systèmes de classements massifs et opaques.
Dans ce cadre discursif, les termes techniques *profil de risque*, *croisement de bases de données*, *ciblage des contrôles* se présentent comme de simples moyens datteindre un but qui, lui, est posé comme incontestable : *protéger la “soutenabilité” des régimes de prestations en évitant quils ne soient “vidés de leur substance” par des comportements abusifs*. Lessentiel de largument arcal se déploie dans un lexique de la vigilance et de la responsabilité : ladministration se doit, pour protéger les “vrais bénéficiaires”, de traquer les fraudeurs avec des outils modernes, proportionnés et ciblés.
Du point de vue archicratique, on voit se dessiner ici une figure typique : la *fraude* devient le point focal de la justification. La question de savoir ce quest, concrètement, une erreur de bonne foi, un litige interprétatif, une situation de vulnérabilité structurelle, est reléguée à larrière-plan. La tension entre présomption dinnocence et présomption de suspicion est peu articulée ; le terme de “fraude” fonctionne comme un opérateur de condensation qui autorise des dispositifs de surveillance étendus, pourvu quils soient décrits comme des moyens “nécessaires” à la sauvegarde de lÉtat social. L*arcalité déclarée* est donc forte (*défense dun bien collectif précieux*), mais très générale : elle ne descend guère au niveau des catégories fines qui seront effectivement travaillées par Système F.
### I.2.2. Objectivation du risque et cohérence des peines
Dans le champ de la justice pénale, l*arcalité déclarée* sappuie sur un autre récit, lui aussi bien identifié dans la littérature : celui de l*évaluation actuarielle du risque* et de la “*cohérence*” *des décisions de justice*. Les guides destinés aux praticiens pour lusage de COMPAS, par exemple, présentent loutil comme une méthode “objective, standardisée, fondée sur la recherche” pour estimer la probabilité de récidive à partir dun ensemble de facteurs psychosociaux et historiques.
Dans larrêt *State v. Loomis*, la Cour suprême du Wisconsin accepte cette finalité : lusage dun instrument actuariel est recevable dès lors quil est clairement qualifié d“aide à la décision” et que le juge conserve, en principe, la maîtrise de la peine. La Cour insiste sur la nécessité de rappeler les limites du système, mais elle ne remet pas en cause la légitimité de lobjectif affiché : rendre les décisions plus cohérentes, plus prévisibles, moins dépendantes des intuitions ou des préjugés des magistrats. L*arcalité déclarée* articule ici un double horizon : *améliorer la justice distributive* (réduire les disparités de traitement) *et la sécurité publique* (anticiper les comportements futurs “à risque”).
Ce discours sinscrit dans une histoire plus longue de “gestion du risque” en droit pénal : montée des outils actuariels de probation, intérêt pour les “évaluations fondées sur la preuve”, critiques de larbitraire judiciaire. Il emprunte beaucoup au vocabulaire de la statistique, de la psychologie et du management du risque. Lalgorithme nest jamais présenté comme une source de normativité autonome ; il fournit des scores, des ratios, des catégories (“faible”, “moyen”, “élevé”) qui doivent éclairer des décisions qui, elles, demeurent juridiquement encadrées. L*arcalité déclarée* est donc celle dune *rationalisation du pouvoir de juger* : mettre la peine sous le signe du calcul et de lexpertise plutôt que du tâtonnement individuel.
### I.2.3. Gestion de population et ciblage de ressources rares
Dans le domaine de la santé, les algorithmes que nous avons retenus comme briques de Système F sont inscrits dans un lexique propre : celui de la “politique santé des populations”, de la “gestion de patients jugés à risque”, de la “prévention des hospitalisations évitables”. Lalgorithme analysé par Obermeyer et al. est présenté par ses concepteurs et par les hôpitaux qui lutilisent comme un outil permettant didentifier les patients “aux besoins complexes” et de les orienter vers des programmes de gestion de soins intensifs.
Les documents de promotion de ce type de systèmes insistent sur un double objectif : dune part, “*améliorer la qualité des soins*” en offrant un suivi renforcé aux patients les plus vulnérables ; dautre part, “*maîtriser les coûts*” en réduisant les complications graves, les ré-hospitalisations et les recours imprévus aux urgences. L*arcalité déclarée* assume pleinement cette tension : il sagit de *concilier des impératifs cliniques et budgétaires en allouant des ressources rares* (temps médical, programmes sophistiqués, coordination, technologies coûteuses) *de la “façon la plus efficiente possible*”.
Sur le plan discursif, la notion de “*risque élevé*” est donc chargée dune valeur positive : loin de stigmatiser les patients, elle leur ouvre laccès à des dispositifs jugés bénéfiques. Le modèle dIA est décrit comme un moyen de “repérer ce que lœil humain ne voit pas facilement”, cest-à-dire de *détecter en amont des trajectoires de dégradation de la santé*. À nouveau, l*arcalité déclarée* en appelle à des valeurs fortes : *meilleure prise en charge, prévention, justice dans lallocation des soins, soutenabilité financière des systèmes de santé*.
### I.2.4. “Talent”, méritocratie et efficacité de la sélection
Dans le secteur du recrutement, les systèmes de tri automatisé empruntent dautres registres. Les discours qui entourent le projet de classement des CV développé par Amazon, ainsi que la littérature managériale plus large sur les “*talent analytics*”, convergent vers une même promesse : “*mécaniser la recherche des meilleurs talents*”, “*libérer les recruteurs des tâches répétitives*”, “*standardiser la sélection*” et “*réduire les biais individuels*” en confiant la première lecture des dossiers à un modèle.
L*arcalité déclarée* ici ne renvoie ni à lÉtat social ni à la justice pénale, mais à une certaine *représentation de la méritocratie en entreprise* : les “bons profils” seraient ceux qui maximisent la performance, la productivité, ladéquation à la culture de lorganisation. Le système dIA est présenté comme un “assistant impartial” capable de faire émerger des “signaux faibles” dans les CV, de repérer des trajectoires prometteuses, de réduire le poids des impressions fugitives et des “préjugés inconscients” des recruteurs.
Ce discours est en résonance avec une tradition managériale qui valorise le *data-driven HR*, la capacité à “objectiver” des intuitions en les traduisant en scores, en *rankings*, en probabilités de succès. Lalgorithme devient une sorte de miroir supposé neutre des “caractéristiques qui différencient les meilleurs employés des autres”, pour reprendre une formule fréquemment mobilisée dans ce champ. L*arcalité déclarée* associe donc trois promesses : *gain defficacité*, *amélioration de la qualité des recrutements*, *réduction affichée des biais humains*.
### I.2.5. Sécurité, “contenus illégaux” et transparence
Dans lunivers des grandes plateformes, le Digital Services Act européen donne une formulation particulièrement nette de l*arcalité déclarée*. Le DSA affirme vouloir “*réduire la distribution de contenus illégaux*”, protéger les utilisateurs contre diverses formes de risques en ligne, et instaurer des exigences nouvelles de transparence et de responsabilisation pour les intermédiaires.
Les plateformes, de leur côté, ont développé depuis plusieurs années un lexique de sûreté et de réduction des risques : leurs règles de “standards communautaires” ou de “règles de la communauté” visent à “protéger” les utilisateurs contre les discours haineux, le harcèlement, la propagande terroriste, la désinformation dommageable, les images violentes ou sexualisées non consenties. Elles décrivent leurs systèmes dIA comme une “*première ligne de défense*” qui filtre les contenus au moment de la mise en ligne, identifie les violations manifestes, et transmet les cas ambigus à des équipes de modération humaines.
L*arcalité déclarée* se trouve ici à la frontière entre droit et morale : les plateformes affirment respecter les législations nationales et européennes, mais elles revendiquent aussi la mise en œuvre de “standards de communauté” qui excèdent parfois les strictes obligations juridiques. Avec le DSA, une grammaire spécifique se stabilise : celle de la “*sécurité en ligne*” et de la “*transparence des décisions de modération*”, adossée à des *obligations concrètes* (mécanismes de signalement des contenus illégaux, justification des décisions de retrait ou de déréférencement, bases de données publiques dactions de modération).
Dans ce cadre, lusage doutils automatisés est présenté comme un moyen de faire face à des volumes massifs de contenus, de “réagir rapidement” à des menaces, de limiter lexposition du public à des messages jugés dangereux. L*arcalité déclarée* articule donc *protection des usagers, respect de la liberté dexpression* (au moins dans lintention), *et exigence de transparence accrue.*
### I.2.6. Principes transversaux : “*IA responsable*”, “*IA digne de confiance*”
Au-dessus de ces légitimités sectorielles, une couche plus générale sest constituée autour de lidée d“*IA responsable*” ou d“*IA digne de confiance*”. Les Principes de lOCDE sur lintelligence artificielle, adoptés en 2019, affirment que les systèmes dIA devraient “*bénéficier aux personnes et à la planète en favorisant une croissance inclusive, le développement durable et le bien-être*”, être conçus de manière à “*respecter létat de droit, les droits de lhomme, les valeurs démocratiques et la diversité*”, et inclure des garanties appropriées (*intervention humaine, traçabilité, sécurité*).
Les Lignes directrices européennes pour une IA digne de confiance, élaborées par le groupe dexperts de haut niveau sur lIA, déclinent ces ambitions en sept “*exigences*” : agence humaine et contrôle, robustesse et sécurité techniques, gouvernance des données, transparence, diversité et équité, bien-être sociétal et environnemental, responsabilité. Elles sont assorties dune liste dauto-évaluation (ALTAI) destinée aux développeurs et aux utilisateurs pour vérifier que leurs systèmes sy conforment.
Les grands fournisseurs privés de modèles de fondation se sont, pour lessentiel, alignés sur cette grammaire. Google a publié en 2018 ses “*AI Principles*”, qui insistent sur le fait que les applications dIA doivent être “*socialement bénéfiques*”, “*éviter de créer ou de renforcer des biais injustes*”, être “*construites et testées pour la sécurité*”, “*être responsables devant les personnes*” et “*incorporer la vie privée dès la conception*”. Microsoft a formulé, quant à lui, un ensemble de principes analogues : *équité, fiabilité et sécurité, confidentialité et sécurité, transparence, responsabilité, inclusivité*, qui servent de base à son “*Responsible AI Standard*” interne.
Cette couche transversale dénoncés nest pas anecdotique : elle fournit le vocabulaire dans lequel Système F doit, aujourdhui, se présenter pour être légitime. Une administration qui lance un appel doffres pour un module de détection de fraude ou daide à la décision judiciaire attend des fournisseurs quils sinscrivent dans ce registre ; une entreprise qui internalise un système de tri de CV est incitée à le faire sous la bannière de l“IA responsable” et de la lutte contre la discrimination. L*arcalité déclarée* de Système F est donc redoublée par ce *halo de principes généraux*, qui se veulent compatibles avec les droits fondamentaux et les valeurs démocratiques.
### I.2.7. Une grammaire commune de légitimation
Si lon rassemble ces différents registres, une grammaire commune de l*arcalité déclarée* apparaît assez nettement. Elle articule au moins quatre motifs récurrents.
Le premier est celui de la *protection contre des menaces identifiées*. Dans la protection sociale, cest la fraude aux prestations qui menace la viabilité du système ; dans la justice pénale, cest la récidive qui met en danger la sécurité publique ; dans la santé, ce sont les complications évitables et les trajectoires de dégradation qui menacent la stabilité des systèmes de soins ; dans le recrutement, ce sont les “mauvais choix” qui mettent en péril la compétitivité de lentreprise ; sur les plateformes, ce sont les contenus illégaux ou nocifs qui mettent en péril les usagers et lespace public. Lalgorithme est habilité à partir du moment où il est cadré comme une *barrière* supplémentaire contre ces risques.
Le deuxième motif est celui de la *rationalisation* et de l*efficience*. Lautomatisation est décrite comme un moyen de traiter plus de dossiers, plus rapidement, avec moins de ressources humaines, de rendre les décisions plus cohérentes, de réduire larbitraire. Dans le langage des administrations sociales, cela se traduit par la promesse de “*ciblage des contrôles*” et de “*réduction des abus*” ; dans celui des hôpitaux, par la “*priorisation des patients à haut risque*” ; dans celui des services RH, par la “*gestion de volumes massifs de candidatures*” ; dans celui des plateformes, par la possibilité de *modérer des quantités de contenus impossibles à gérer manuellement*. Système F est ici la figure dun auxiliaire rationnel, intensifiant les capacités habituelles des organisations.
Le troisième motif est celui de l*objectivité* ou, à tout le moins, dune réduction des biais imputés aux évaluations purement humaines. Dans la justice pénale, l*évaluation actuarielle* se donne comme “*fondée sur la preuve*” plutôt que sur les intuitions ; dans la santé, le score de risque agrège des données cliniques et des historiques de dépenses ; dans le recrutement, lalgorithme promet dêtre indifférent au genre, à lorigine, à lapparence ; dans les plateformes, les modèles de détection de contenus haineux ou terroristes se présentent comme appliquant des critères constants. L*arcalité déclarée* insiste sur lidée que ces dispositifs ne font que *mesurer* ou *mettre en forme* *des régularités objectives déjà là*.
Enfin, le quatrième motif est celui de la *sécurité* et de la *confiance*. Les discours sur l“*IA digne de confiance*” et les documents de régulation européenne convergent : *pour être acceptable, un système doit être robuste, fiable, “sûr”, respecter la vie privée, être transparent et rendre des comptes*. Dans le langage des plateformes, la “*sûreté*” est un *produit que lon fournit aux utilisateurs* ; dans les textes de lOCDE et de lUnion européenne, la “*confiance*” est une *condition de possibilité du déploiement de lIA dans des secteurs sensibles*.
Cette grammaire nest pas simplement un vernis. Elle structure les termes dans lesquels les acteurs peuvent se justifier devant les tribunaux, les autorités de régulation, les opinions publiques. Elle *détermine ce qui peut être publiquement défendu* et ce qui, au contraire, doit rester dans les coulisses (sélection des variables, choix des proxies, calibrage des seuils). Du point de vue archicratique, elle constitue bien une *arcalité* : *une manière dexposer au moins en principe les finalités et les valeurs censées commander lusage* de Système F.
### I.2.8. Une *arcalité réelle*, mais *extra-scénique*
Reste à savoir dans quelle mesure cette *arcalité déclarée*, riche et apparemment sophistiquée, remplit les conditions minimales dune *arcalité* proprement archicratique, cest-à-dire dune mise en scène effective des fondements. La réponse, à ce stade de laudit, est ambivalente.
Dun côté, il serait excessif de considérer que les dispositifs que nous avons examinés seraient dépourvus de toute justification explicite. La lutte contre la fraude sociale, la recherche de cohérence dans les peines, la volonté de mieux prendre en charge des patients vulnérables, lambition de limiter les discriminations à lembauche, la nécessité de réguler les contenus illégaux ou dangereux sur les grandes plateformes, la référence aux droits fondamentaux et aux valeurs démocratiques dans les textes internationaux : tout cela constitue un *socle normatif substantiel*. L*arcalité déclarée* nest pas un pur écran de fumée ; elle exprime des préoccupations réelles, souvent largement partagées.
Dun autre côté, cette *arcalité* reste le plus souvent *extra-scénique*. Elle sexprime dans des préambules de lois, des communiqués, des chartes, des rapports, mais elle nest guère travaillée dans des scènes où les notions centrales fraude, risque acceptable, mérite, besoin de soin, dangerosité, contenu nuisible seraient définies, discutées, révisées en présence de ceux qui en subissent les effets. La définition pratique de la fraude, dans les dispositifs de protection sociale, nest pas débattue en tant que telle avec les allocataires ; la hiérarchie entre erreurs acceptables et erreurs intolérables dans la justice pénale nest pas posée frontalement aux justiciables ; le choix de prendre les coûts comme proxy des besoins en santé nest pas soumis à une délibération spécifique avec les patients et les soignants ; les critères de “talent” dans le recrutement ou de “contenu nuisible” sur les plateformes ne donnent que rarement lieu à des dispositifs de confrontation structurée.
Leffet archicratique de cette situation est double. Dune part, les *valeurs affichées* protection de lÉtat social, prévention de la récidive, justice dans laccès aux soins, égalité des chances, sécurité des espaces numériques, respect des droits fondamentaux fonctionnent comme des *slogans darrière-plan* : elles justifient en bloc lentrée de Système F dans les chaînes décisionnelles, sans que soient explicitées les manières concrètes dont elles sont traduites en paramètres, en seuils, en arbitrages derreurs. Dautre part, cette *faiblesse scénique* ouvre un espace où les véritables fondements opératoires tendent à se déplacer vers dautres couches du dispositif : fonctions de coût, choix de proxies, sélection de variables, architecture des jeux de données.
Autrement dit, l*arcalité déclarée* de Système F est à la fois réelle et incomplète : elle pose des finalités, mais elle ne prend pas à bras-le-corps la question de leur implémentation normative fine. Elle ouvre un horizon de légitimation, mais elle laisse largement hors scène les fondements effectifs à partir desquels le système se met à discriminer, prioriser ou classer.
Cest cette dissociation que la section suivante se propose dexaminer. En passant à l*arcalité implicite*, nous quitterons les formulations officielles pour aller voir où se logent, dans Système F, les axiomes silencieux : ceux qui définissent en pratique ce qui compte comme fraude, comme risque tolérable, comme mérite, comme besoin, comme contenu acceptable. Cest là que se noue l*oblitération archicratique* caractéristique des dispositifs contemporains : non pas absence de fondement, mais fuite du fondement derrière loptimisation.
## I.3. *Arcalité implicite*
Avec l*arcalité déclarée*, nous avons observé ce que les acteurs disent de Système F : protection de lÉtat social, objectivation du risque pénal, ciblage des soins, rationalisation du recrutement, sécurité des espaces numériques, “IA responsable” et “digne de confiance”. L*arcalité implicite* se loge ailleurs : dans les *fonctions de coût*, les *proxies choisis*, les *métriques dévaluation*, la *composition des jeux de données*, les *seuils*, les *arbitrages sur les erreurs*. Cest là que se décide, au sens fort, ce quest un “bon” résultat pour le système, ce quon est prêt à sacrifier, qui lon accepte de sur-surveiller, qui lon accepte de mal desservir.
Du point de vue archicratique, chaque choix de ce type relève dun acte de fondation silencieux : il fixe une hiérarchie entre injustices acceptables, il distribue les soupçons et les protections, il stabilise une certaine vision de ce qui compte. Limportant nest pas dopposer un “pôle technique” neutre à un “pôle politique” chargé ; cest de *reconnaître que le paramétrage lui-même a une portée normative*, et quil constitue une *arcalité effective* qui, le plus souvent, ne comparaît jamais en tant que telle. Cest ce régime des fondements non mis en scène que nous appellerons *arcalité implicite* ou *arcalité fantôme*.
### I.3.1. Arbitrages derreurs et profilage social
Dans les dispositifs de détection de fraude aux prestations qui composent la brique “protection sociale” de Système F, lobjectif opérationnel est de distinguer des dossiers “à risque” des autres. Techniquement, cela se traduit par une *fonction de coût qui pondère plusieurs objectifs* : *maximiser la détection des fraudes avérées*, *limiter les faux positifs*, *contenir les coûts denquête*, *éviter de suspendre abusivement des prestations*. En apparence, il sagit d*optimiser une procédure de contrôle* ; en réalité, on décide comment répartir la charge de lerreur entre lÉtat et les allocataires.
Laffaire néerlandaise des allocations pour la garde denfants, relue par Amnesty International dans son rapport *Xenophobic Machines*, a montré à quel point ces arbitrages implicites pouvaient devenir violents. Des dizaines de milliers de familles, très majoritairement à faibles revenus et souvent issues de limmigration, ont été accusées à tort de fraude et soumises à des recouvrements massifs sur la base de profils de risque établis par un système automatisé. Amnesty documente lusage de variables comme la nationalité ou la double nationalité dans la construction des profils, ce qui produisait un ciblage disproportionné de certains groupes ethniques et migratoires.
Du point de vue archicratique, plusieurs axiomes implicites se donnent ici à voir. Dabord, lidée selon laquelle il serait acceptable de concentrer un volume élevé de faux positifs sur des populations déjà précaires, au nom de la défense du budget public, sans scène où ces personnes puissent contester la hiérarchie ainsi instituée entre la protection des fonds et la protection des droits. Ensuite, lidée quune caractéristique comme la nationalité puisse servir de proxy de suspicion, alors même que le droit non discriminatoire des États européens proscrit précisément ce type dusage. Le rapport *Xenophobic Machines* parle de discrimination “intégrée” dans la conception même du système, non seulement dans ses usages déviants.
La décision du tribunal de La Haye dans laffaire SyRI confirme le diagnostic à un autre niveau. SyRI était un dispositif de profilage de risque en matière de sécurité sociale, fondé sur le croisement massif de données issues de multiples administrations et sur la production de “*rapports de risque*” transmis aux services dinspection. Le tribunal a jugé que la législation encadrant SyRI violait larticle 8 de la Convention européenne des droits de lhomme, en raison notamment dun déficit de transparence, de lampleur du traitement et du ciblage de quartiers défavorisés.
Ce qui est en cause, là encore, nest pas seulement une erreur de calibrage, mais une *arcalité implicite* : il serait considéré comme *tolérable de soumettre certains territoires à un profilage intensif*, *de croiser leurs données à grande échelle, de déclencher des enquêtes intrusives sur la base de scores opaques, au nom dun “juste équilibre” entre lutte contre la fraude et respect de la vie privée*. Or ce “*juste équilibre*” na pas été déterminé sur une scène où lon aurait mis en débat les types derreurs acceptables, les populations exposées, la nature des données croisées. Il a été fixé dans la fonction de coût globale du système, puis naturalisé sous la forme dune procédure “moderne” de contrôle.
L*arcalité implicite* de Système F, dans ce segment social, prend donc la forme dune *hiérarchisation silencieuse* : mieux vaut tolérer un nombre important de faux positifs concentrés sur des familles vulnérables que daccepter une fraude résiduelle ; mieux vaut considérer certains profils nationaux ou résidentiels comme intrinsèquement plus suspects que douvrir un débat public sur les causes structurelles des erreurs, des omissions ou des malentendus administratifs.
### I.3.2. Le *proxy* “*coûts*” comme axiome de valeur
Lalgorithme de gestion des risques étudié par Ziad Obermeyer et ses co-auteurs offre un exemple paradigmatique d*arcalité implicite*. Le système, largement utilisé dans les systèmes de santé américains, sert à déterminer quels patients doivent bénéficier de programmes de “*care management*” intensif : ceux dont le score dépasse un certain seuil se voient offrir un suivi renforcé, des ressources supplémentaires et une coordination accrue.
Les auteurs montrent que, pour un même score de risque donné par lalgorithme, les patients noirs sont en moyenne bien plus malades que les patients blancs : davantage de pathologies chroniques non contrôlées, plus de complications, etc. La raison nest pas une “erreur” de calcul, mais le choix du proxy : lalgorithme prédit les coûts de santé futurs plutôt que la morbidité elle-même. Or, dans un système marqué par des inégalités daccès aux soins, on dépense historiquement moins pour les patients noirs que pour les patients blancs à état de santé comparable. En prenant les coûts comme substitut des besoins, lalgorithme sous-estime donc systématiquement les besoins des patients noirs, ce qui conduit à les orienter beaucoup plus rarement vers les programmes intensifs.
Du point de vue archicratique, le choix du proxy “coûts” constitue un acte d*arcalité implicite* majeur : il institue, sans jamais le dire, lidée que *la meilleure approximation des besoins de santé dun individu est ce que le système a déjà dépensé pour lui*. Dans un univers égalitaire et sans contraintes budgétaires, ce raccourci pourrait éventuellement se discuter ; dans un univers où laccès aux soins est profondément inégal, il revient à considérer que les vies pour lesquelles on a historiquement le moins dépensé sont objectivement moins “à risque” et moins prioritaires. Laxiome est extraordinairement chargé sur le plan moral et politique, mais il nest pas présenté comme un choix de justice : il est codé comme un paramètre doptimisation raisonnable.
Obermeyer et al. montrent quen remplaçant le proxy “coûts” par un proxy plus proche des besoins cliniques (par exemple le nombre de maladies chroniques non contrôlées), on pourrait presque tripler la part des patients noirs orientés vers les programmes intensifs.
Autrement dit, un seul choix de variable suffit à faire basculer la répartition dun dispositif de soin à grande échelle. Or ce choix na donné lieu ni à une controverse publique, ni à une délibération institutionnelle, ni à un dispositif d*archicration* : il a été décidé en amont, au croisement de considérations pratiques (disponibilité des données, facilité de mesure) et de rationalités gestionnaires (coûts comme indicateur privilégié), puis diffusé comme allant de soi.
Ce cas illustre avec une précision chirurgicale ce que nous nommons *arcalité fantôme* : l*existence de fondements normatifs réels* ici, une conception du besoin de santé traduite en coût *qui pilotent une large distribution de ressources, sans jamais être convoqués sur une scène où ils devraient se justifier devant ceux quils affectent*.
### I.3.3. Hiérarchies des erreurs et justice des risques
Loutil dévaluation de risque pénal COMPAS, lorsquon le regarde à travers les débats suscités par lenquête de ProPublica et les réponses de ses concepteurs, met en lumière un autre aspect de l*arcalité implicite* : la gestion différenciée des faux positifs et des faux négatifs selon les groupes. ProPublica a montré en 2016 que, dans le comté de Broward, les accusés noirs étaient beaucoup plus souvent classés à tort comme “à haut risque” que les accusés blancs (faux positifs), tandis que les accusés blancs étaient plus souvent classés à tort comme “faible risque” alors quils récidivaient (faux négatifs).
Des chercheurs et les auteurs de COMPAS ont contesté la méthodologie de ProPublica, en soulignant quil est mathématiquement impossible de satisfaire simultanément plusieurs notions de “*justice algorithmique*” (par exemple égalité des taux de faux positifs et égalité de calibration) lorsque les taux de récidive diffèrent entre groupes.
Mais, du point de vue archicratique, cette impossibilité mathématique ne dissout pas le problème : elle le reformule avec plus dacuité. Si toutes les configurations sont impossibles simultanément, il faut choisir lesquelles on privilégie : *faut-il minimiser les faux négatifs* (ne pas laisser sortir un individu qui récidivera) *au prix dun grand nombre de faux positifs* (maintenir en détention des personnes qui nauraient pas récidivé) ? *Ou linverse ? Est-il acceptable que ces arbitrages se distribuent différemment selon les groupes racisés ?*
En droit pénal, cette question nest pas nouvelle : elle recoupe des débats de longue durée sur la *présomption dinnocence*, sur la maxime selon laquelle “*mieux vaut laisser dix coupables en liberté que condamner un innocent*”, sur la hiérarchie entre sécurité collective et protection contre lerreur judiciaire. Ce que le recours à un outil actuariel comme COMPAS transforme, cest le lieu où cette hiérarchie est fixée : au lieu dêtre lobjet dune discussion explicite au Parlement, dans la doctrine, dans la jurisprudence elle est réglée par la manière dont on écrit la fonction de coût, choisit les métriques dévaluation, fixe les seuils de risque et paramètre la calibration. Les débats techniques sur les définitions de léquité deviennent le substitut de débats normatifs sur ce que lon juge plus grave : enfermer injustement ou libérer à tort.
Là encore, l*arcalité implicite* nest pas labsence de normativité, mais sa *relégation dans des micro-décisions techniques qui napparaissent jamais comme telles aux justiciables*. Un prévenu auquel on annonce un score de risque “élevé” ne voit pas la structure normative qui a présidé au calibrage des erreurs ; un juge qui consulte ce score ne voit pas davantage la hiérarchie implicite entre types dinjustice. La scène archicratique celle où lon discuterait ouvertement du partage admissible des risques derreurs se trouve remplacée par une scène actuarielle limitée à quelques spécialistes.
### I.3.4. Apprendre des hiérarchies sociales existantes
Le système de tri de CV développé par Amazon, puis abandonné avant son déploiement, met au jour une autre dimension de l*arcalité implicite* : *limportation non critique de hiérarchies sociales existantes dans la définition de ce qui compte comme “talent”*. Lenquête de Jeffrey Dastin pour Reuters a montré que loutil, entraîné sur une dizaine dannées dhistoriques de recrutement dans des métiers techniques majoritairement masculins, avait “appris” à désavantager les CV féminins : il pénalisait les dossiers contenant le mot “womens” et rétrogradait certains établissements fréquentés par des femmes.
L*arcalité implicite* ne réside pas seulement dans ces effets visibles, mais dans la décision initiale de prendre le passé des recrutements comme référence pour lavenir. En faisant de la capacité à prédire “qui serait embauché” à partir des données historiques la fonction de coût principale, Amazon a installé comme norme ce que ses pratiques antérieures, déjà marquées par un déséquilibre de genre, considéraient comme un “*bon candidat*”. Lalgorithme ninvente pas le biais ; il le systématise et le cristallise.
Du point de vue archicratique, cette configuration revient à traiter les décisions passées elles-mêmes situées dans des rapports de pouvoir, des routines, des préjugés comme un “réel” à imiter. La question “quest-ce quun talent ?” nest pas abordée comme une question ouverte, susceptible de révision, mais comme un pattern à extraire dun corpus de CV, de trajectoires et de décisions. L*arcalité implicite* associe trois éléments : *lidée que la performance passée est la meilleure mesure du mérite futur* ; *la naturalisation de hiérarchies de genre, de diplôme, de style de CV* ; *la marginalisation de toute scène où ces hiérarchies pourraient être discutées par les personnes quelles affectent.*
Lépisode se conclut par labandon du projet, une fois les biais mis au jour par les ingénieurs. Mais il illustre bien la logique de Système F : tant que lalgorithme fonctionne dans les coulisses, la question de la normativité de ses critères reste invisible. Ce nest quau moment où loutil “déraille” visiblement ici, en supprimant presque mécaniquement les femmes du pool de candidats que les questions archicratiques surgissent, sur un mode défensif.
### I.3.5. Seuils de tolérance et architectures de la visibilité
Dans le cas des plateformes numériques, l*arcalité implicite* se manifeste dans un autre registre : celui des seuils de tolérance au contenu et des architectures de visibilité. Les textes de mise en œuvre du Digital Services Act, ainsi que les guides de transparence publiés par la Commission européenne et les analyses doctrinales récentes, montrent que les grandes plateformes sont désormais explicitement tenues de rendre compte de leurs décisions de modération, des outils automatisés quelles emploient et des risques systémiques quelles identifient.
Les rapports de transparence de Meta, combinés aux travaux de l*Oversight Board* et aux analyses critiques sur lévolution des “*Community Standards*”, illustrent concrètement lampleur de la délégation faite à des systèmes dIA pour détecter, classer et retirer des contenus. Meta reconnaît que des systèmes automatisés identifient et agissent sur une grande partie des contenus avant même quils soient signalés, et l*Oversight Board* rappelle que des millions dutilisateurs font appel de décisions de retrait initialement prises par ces systèmes, souvent en expliquant que leur contenu relevait de linformation, de la satire ou du témoignage.
Dans ce contexte, l*arcalité implicite* ne se situe pas seulement dans la définition formelle des “discours haineux”, des “contenus terroristes” ou de la “désinformation”, ni même dans la distinction entre illégalité et simple non-conformité aux standards de communauté. Elle se loge dans la manière dont les systèmes automatisés et leurs opérateurs paramètrent les seuils : *à partir de quel degré dambiguïté un contenu est-il retiré préventivement ? jusquoù privilégie-t-on la réduction du risque dexposition au détriment de la préservation de la parole minoritaire ? comment traite-t-on les contenus dans des langues peu dotées, pour lesquelles les modèles sont moins précis ?* Les études récentes sur limpact de ces choix dans le Sud global ou pour certaines communautés minorées montrent que les erreurs de classification et les suppressions abusives se concentrent souvent là où les ressources linguistiques et les contextes locaux sont les moins bien pris en compte.
Le DSA impose aux très grandes plateformes la création de bases de données de décisions de modération et ouvre des accès à la recherche, ce qui, en principe, devrait permettre de rendre visibles ces arbitrages. Mais, tant que le débat reste centré sur la conformité globale aux obligations (rapports de transparence, existence de mécanismes de recours, descriptions qualitatives des systèmes automatisés), la question archicratique de fond à savoir la hiérarchie implicite entre les types derreurs acceptables, les types de contenus sur-modérés ou sous-modérés, les publics plus ou moins protégés demeure largement encapsulée dans les paramètres des modèles et dans les règles internes.
### I.3.6. L*arcalité fantôme* comme régime des fondements non mis en scène
Les fragments que nous venons de parcourir permettent de préciser, sans métaphore, ce que nous nommons *arcalité implicite* ou *arcalité fantôme*.
Nous appellerons *arcalité fantôme* l*opérateur dont les fondements normatifs du dispositif ne disparaissent pas, mais se trouvent encapsulés dans des options de paramétrage* (fonctions de coût, proxies, métriques, seuils) *qui opèrent à lintérieur de la cratialité, sans jamais être exposées comme telles sur une scène dépreuve*.
Dans la protection sociale, la *manière de calibrer les modèles de détection de fraude* *choix des variables, des seuils, des quartiers ciblés, rapport accepté entre faux positifs et faux négatifs* encode une *hiérarchie entre la lutte contre les abus et la protection des allocataires de bonne foi*, ainsi quune *distribution des soupçons selon des lignes de classe et dorigine*. Cette *hiérarchie* nest en aucune manière discutée publiquement avec les personnes concernées ; elle est incorporée dans la fonction de coût globale du dispositif.
Dans la santé, le *choix dun proxy* comme les *coûts de santé* pour représenter *les besoins encode une conception implicite de la valeur des vies en fonction des dépenses déjà engagées*, *dans un contexte où ces dépenses sont ethniquement inégales*. Ce choix transforme une inégalité historique daccès aux soins en différence “objective” de risque, puis en inégalité daccès à des programmes intensifs supplémentaires.
Dans la justice pénale, la définition dun profil de risque et la manière de pondérer les erreurs entre différents groupes encode une hiérarchie des injustices supportables : mieux vaut enfermer trop de personnes qui ne récidiveront pas, ou libérer trop de personnes qui récidiveront, et pour quels groupes précisément ? La littérature sur COMPAS montre que, quelles que soient les positions prises dans le débat, un choix normatif irréductible doit être fait, mais quil est pris en pratique dans lécriture de la fonction de coût et des métriques, non dans une scène de délibération pénale explicite.
Dans le recrutement, la décision de prendre les pratiques passées comme guide principal encode une conception du mérite qui sanctuarise des hiérarchies sociales et de genre. Loutil dAmazon a rendu visible ce mécanisme en produisant des effets suffisamment grossiers pour être politiquement indéfendables ; mais le mécanisme apprentissage à partir dhistoriques de décisions biaisées demeure au cœur de nombreux systèmes d“analyse de talents”.
Sur les plateformes, enfin, les seuils de détection, la granularité des catégories de contenu, les modèles de risque assignés aux différentes langues et régions encodent une hiérarchie implicite entre la protection contre certains types de discours et la tolérance à dautres, entre la visibilité accordée à certains groupes et la sur-modération dautres. Les obligations du DSA ouvrent des voies de visibilité, mais ne constituent pas en elles-mêmes une scène où ces hiérarchies seraient mises en débat avec les publics concernés.
Dans tous ces cas, on ne peut pas dire quil ny ait pas de fondements : au contraire, ils sont nombreux, puissants, efficaces. Ils prennent la forme de fonctions de coût, de choix de proxies, de distributions de données, de métriques de performance, de seuils de décision. Simplement, ces fondements ne sont pas exposés comme tels ; ils napparaissent ni dans les chartes, ni dans les communiqués, ni dans les rapports de principe. Ils agissent dans la *cratialité* du système dans son code, ses pipelines, ses paramétrages , mais ils naccèdent jamais au statut de fondements discutables.
Cest en ce sens que nous parlons d*arcalité fantôme* : non pas une absence d*arcalité*, mais une *arcalité exilée hors de la scène*. La modernité algorithmique ne se caractérise pas seulement par une intensification de la capacité à calculer, à corréler, à prédire ; elle se caractérise par une dés-scénarisation des fondements, cest-à-dire par le transfert des décisions normatives les plus décisives dans des couches techniques où elles ne sont plus identifiées comme telles.
Au regard de l*archicratie*, cela signifie que la promesse dune “*IA responsable*” ou “*digne de confiance*” reste structurellement bancale tant que lon ne ramène pas ces choix à la scène, cest-à-dire tant quon ne crée pas de dispositifs archicratifs pour les rendre visibles, contestables, révisables. Lépreuve de détectabilité, dans sa dimension d*arcalité implicite*, permet précisément de diagnostiquer ce défaut : là où les axiomes les plus lourds sont dissimulés derrière le langage de loptimisation, l*archicratie* nest pas seulement déficitaire ; elle est *empêchée*. Cest sur cette base que pourra être menée, dans les sections ultérieures, lanalyse des *cratialités* de Système F et des *archicrations* rares, fragmentaires, parfois fantomatiques qui tentent déjà, dans certains secteurs, de réintroduire de la scène dans un ordre régulé par les modèles.
## I.4. Cratialité du système IA
Si l*arcalité* répond à la question “*selon quoi ou qui ?*”, la *cratialité* répond à la question “*par quoi et comment ?*”. Dans Système F, ce “*par quoi et comment*” nest pas un point, mais une chaîne : *collecte et circulation de données*, *construction dattributs*, *entraînement du modèle*, *réglages successifs*, *mise en production*, *intégration dans des logiciels très ordinaires* et enfin *procédures dusage*. Ce que l*archicratie* appelle *cratialité*, cest précisément cette chaîne, dès lors quelle ne se contente plus doutiller une activité, mais *distribue de manière répétée des accès, des soupçons, des protections, des sanctions*.
Dans un centre de données où tourne le modèle de fondation, dans le service informatique dun ministère, dans une direction de linnovation dun hôpital, cette chaîne est documentée en détail : *diagrammes darchitecture*, *pipelines de données*, *journaux de traitement*. Mais pour lagent de guichet qui consulte une file de dossiers triés, pour la médecin confrontée à une liste de patients “priorisés”, pour le juge qui reçoit un rapport chiffré, pour le recruteur ou léquipe de modération qui voit un indicateur rouge, *cette cratialité est entièrement compacte* : un score, une couleur, un message, parfois une mention vague à un “outil danalyse avancée”. Lépreuve de détectabilité conduit alors à remonter, autant que possible, cette chaîne invisible, en sappuyant sur ce que les travaux existants documentent déjà.
### I.4.1. Des archives régulatrices recyclées en carburant statistique
La première strate de la *cratialité* de Système F, ce sont les *données*. Non pas des “matières premières” neutres, mais des *archives de décisions passées*.
Ainsi, avec SyRI, la littérature juridique montre que le système a été conçu comme un *instrument de “couplage” massif entre fichiers administratifs* : *données fiscales, registres de sécurité sociale, informations sur lemploi, le logement ou la dette, issues dun ensemble dorganismes publics qui, jusque-là, nétaient pas nécessairement interconnectés*. Lobjet nétait pas simplement de consulter lun ou lautre fichier, mais de constituer un “*dépôt de risque*” où les trajectoires de vie des habitants de certains quartiers étaient recomposées en *profils susceptibles de déclencher un contrôle*. Les dossiers saisis par les ONG et la décision de La Haye insistent sur ce point : cest bien cette concentration, ce couplage étendu et asymétrique des données qui a conduit le tribunal à juger que le dispositif ne respectait pas la vie privée.
Dans le scandale des allocations pour la garde denfants, lalgorithme incriminé na pas été conçu *ex nihilo* : il reposait sur les dossiers fiscaux et sociaux accumulés par ladministration, sur les anciens contrôles, sur les historiques de remboursement, sur des métadonnées apparemment triviales (nationalité, double nationalité, type de crèche). Les enquêtes parlementaires et les analyses doctrinales montrent que la machine à profiler a, de ce fait, hérité dune longue histoire de suspicion ciblée, pour la concentrer sur des familles à bas revenus, souvent issues de limmigration.
Dans ce dispositif précis de santé étudié par Obermeyer et ses co-auteurs, la base dapprentissage est constituée de données de facturation : coûts passés, diagnostics, passages à lhôpital, prescriptions, etc. Lalgorithme ne “voit” pas les patients, il voit les dépenses que le système de santé a consenties pour eux. Quand le dispositif est ensuite utilisé pour sélectionner les patients éligibles à des programmes de soins intensifs, la *cratialité* de Système F prolonge ainsi une économie historique des soins : ceux dont on a peu dépensé pour eux sont moins visibles pour lalgorithme, même sils sont plus malades.
Du côté pénal, les études sur COMPAS rappellent que les scores sont calculés à partir dune combinaison déléments du casier judiciaire et des réponses à un questionnaire de 137 items, portant sur la trajectoire de vie, lenvironnement social, lemploi, la scolarité, le logement. Là encore, Système F ne “crée” pas les variables ; il hérite de la manière dont la police a arrêté, dont les tribunaux ont condamné, dont les services sociaux ont consigné des éléments de biographie, dans un contexte où ces histoires sont déjà fortement structurées par la race, la classe, le territoire.
Dans le recrutement, loutil dAmazon décortiqué par Reuters a été entraîné sur une décennie de décisions de recrutement dans des métiers techniques, essentiellement occupés par des hommes. Les CV, lettres, diplômes et trajectoires de carrière qui composent cette base reflètent un champ professionnel déjà fortement genré ; cest cette archive que le modèle prend pour horizon de référence.
Enfin, sur les plateformes, les systèmes de modération et de recommandation ingèrent des flux continus de contenus, mais aussi des archives de signalements, de suppressions, de “likes”, de signalements de discours haineux ou terroristes. Les rapports de transparence imposés par le Digital Services Act et les documents publiés par Meta insistent sur le *rôle des systèmes automatisés dans la détection initiale des contenus, en se nourrissant précisément de cette mémoire dinterventions passées*.
Dans tous ces cas, la *cratialité* de Système F commence donc par un *geste de reprise* : les *bases de données sur lesquelles repose lentraînement et le fonctionnement du système sont déjà des condensés darbitrages régulateurs, de contrôles et de sélections, parfois de longues routines discriminatoires*. La chaîne cratiale ne se contente pas d“absorber” la réalité sociale ; elle absorbe des archives de pouvoir.
### I.4.2. Pipelines : transformer des vies en vecteurs
La deuxième strate de la *cratialité* est moins visible encore : ce sont les chaînes de transformation qui convertissent ces données hétérogènes en représentations exploitables par le modèle. Les manuels de science des données et les guides sur lusage de lIA dans les administrations décrivent des étapes désormais classiques : nettoyage, normalisation, sélection ou construction de variables, agrégation, puis vectorisation.
Dans le cas dun dispositif de détection de fraude sociale, une succession de décisions apparemment techniques se met en place : faut-il coder la “nationalité” comme une variable binaire, ou comme une liste fine de pays ? Faut-il comptabiliser le nombre de déménagements sur cinq ans, ou sur dix ? Comment transformer des remarques écrites par des agents en indicateurs numériques ? Le rapport de la Commission denquête parlementaire néerlandaise sur le scandale des allocations montre que, dans la pratique, ces choix ont conduit à donner un poids particulier à certains marqueurs (double nationalité, erreurs mineures dans les formulaires), qui faisaient passer des familles entières du côté du “risque élevé”.
Pour l*algorithme de santé* analysé par Obermeyer et al., cette chaîne consiste à *agréger des années de dépenses en santé, de diagnostics et dhospitalisations en un score unidimensionnel de “risque”, censé représenter les “besoins futurs” du patient*. Le fait dopter pour les coûts plutôt que pour des indicateurs cliniques, puis de compresser ces informations en un score continu, est un *geste cratial* autant que statistique : il *rend possible lintégration du système dans les tableaux de bord des gestionnaires*, et *autorise un tri automatique des patients à échelle industrielle*.
Dans COMPAS, transformer 137 réponses et un casier judiciaire en un score de 1 à 10 suppose plusieurs *couches de prétraitement* : *recodage des réponses en catégories numériques*, *pondération de facteurs*, *combinaison en indices partiels*, puis en *score global*. Les études qui ont reconstruit partiellement la méthode à partir de données ouvertes montrent à quel point *cette chaîne incorpore des éléments contextuels* (code postal, stabilité résidentielle, entourage, historique de consommation de drogues) qui, une fois vectorisés, deviennent des *attributs apparemment neutres, mais fortement corrélés à des trajectoires socio-raciales différenciées*.
L*outil de tri de CV* dAmazon, pour sa part, devait convertir des documents richement formatés en vecteurs de caractéristiques : *universités*, *mots-clés*, *expériences*, *engagements*, parfois même *tournures de phrases*. Cest dans cette phase que le système a “*appris*” à pénaliser des expressions comme “*womens chess club*” ou des références à des universités connotées féminines, parce que ces traits coïncidaient, dans les données dentraînement, avec des candidatures historiquement moins retenues.
Enfin, les *pipelines de modération* et de *recommandation* transforment chaque message, image ou vidéo en une série de marqueurs : *langue, thème, tonalité, degré présumé de violence ou de haine, signaux de “fiabilité” de la source, etc.* Les documents de Meta et les analyses indépendantes montrent que ces *pipelines* combinent *détection automatisée*, *listes de mots*, *signaux issus de comportements passés*, avant de produire des étiquettes (“contenu à risque”, “contenu limite”) qui orientent la visibilité des publications bien en amont de toute décision humaine.
Ce que la lecture archicratique met ici en avant, ce nest pas seulement la technicité de ces *pipelines*, mais leur *effet de réduction* : *des vies, des trajectoires, des plaintes, des prises de parole sont ramenées à des vecteurs, des classes, des scores*. Cette réduction est nécessaire pour que Système F fonctionne ; elle nest pas en soi illégitime. Mais elle constitue lun des lieux où la *cratialité* fait passer un seuil : *ce qui devient calculable devient gouvernable*.
### I.4.3. Modèles, paramètres, seuils : la mécanique fine des décisions
La troisième strate de la *cratialité* de Système F est celle des *modèles* et de leurs *réglages*. Les manuels de *machine learning* parlent *darchitectures, de fonctions de perte, doptimisation, dhyperparamètres*. Pour l*archicratie*, ces notions deviennent intéressantes lorsquelles cristallisent des *choix régulateurs*.
Ainsi, *santé* étudié par Obermeyer et al., le modèle est paramétré pour *minimiser lerreur globale de prédiction des coûts futurs*. Ce choix minimiser une somme derreurs plutôt que de garantir un niveau de traitement minimal pour certains groupes a pour effet doptimiser la performance moyenne au prix dune sous-protection systématique des patients noirs. Les auteurs montrent quen modifiant la fonction de coût pour intégrer explicitement des mesures de morbidité, le classement des patients noirs change radicalement.
Dans COMPAS, les paramètres du modèle et la manière dont les scores sont répartis en catégories (“faible”, “moyen”, “élevé”) déterminent la proportion de personnes qui basculent dans chaque classe de risque. Les études empiriques indiquent que, pour un même score, la probabilité de récidive est similaire entre accusés noirs et blancs, mais que la distribution des erreurs (faux positifs, faux négatifs) diffère fortement selon les groupes. Larchitecture et les réglages du modèle correspondent donc à un compromis implicite : il est jugé acceptable de produire davantage de faux positifs pour certains groupes, afin de maintenir une calibration globale satisfaisante.
Les *systèmes de scoring* de fraude sociale fonctionnent de la même manière : la décision de fixer un seuil de déclenchement de contrôle à tel niveau plutôt quà tel autre se traduit immédiatement en nombre de dossiers réexaminés, en proportion de familles frappées par des procédures, en intensité de la surveillance sur certains quartiers. Les travaux juridiques sur SyRI insistent sur le fait que ce seuil, et les indicateurs qui y conduisent, nétaient pas seulement inconnus du public, mais inaccessibles même aux personnes contrôlées.
Dans le cas dAmazon, larchitecture exacte du modèle na pas été publiée, mais les sources indiquent quil sagissait dun système dapprentissage supervisé qui attribuait une note dune à cinq étoiles aux CV, en imitant les décisions de recrutement passées. La simple existence dune échelle discrète de 1 à 5, avec un tri automatique des dossiers en fonction de cette note, traduit un choix cratial : il ny a plus de lecture directe de chaque CV, mais un filtrage basé sur un signal synthétique, qui décide de ce qui est vu ou non par les recruteurs.
Dans tous ces cas, les modèles, les paramètres et les seuils ne sont pas seulement des composantes techniques ; ils sont les points précis où la chaîne cratiale se décide : *combien de personnes seront contrôlées, qui sera inclus dans un programme de soins, qui portera le stigmate dun “haut risque”, qui sera vu par un recruteur ou par le public dune plateforme*. La *cratialité* de Système F, cest cette capacité à faire varier, par ajustement de quelques coefficients, la forme concrète de laccès aux droits, aux ressources, à la visibilité.
### I.4.4. Interfaces : là où la *cratialité IA* rencontre la *cratialité humaine*
Pour les agents, juges, médecins, recruteurs, modérateurs, la *cratialité* napparaît cependant quà lautre bout de la chaîne : dans les *interfaces*. Cest là que Système F devient visible sous la forme dune colonne de scores, dun code couleur, dun message dalerte, dune suggestion “recommandée par le système”.
Les études sur les systèmes daide à la décision clinique montrent que la manière dont une recommandation est affichée (alerte intrusive, message discret, couleur, possibilité de justification) influence fortement la propension des praticiens à la suivre ou à la contourner. Les travaux sur les biais dautomation confirment que, lorsque linterface présente une proposition algorithmique avec un fort statut dautorité (icône de validation, texte en vert, mention “recommandé”), les opérateurs ont tendance à lui accorder plus de crédit quà leur propre jugement, surtout dans des contextes de charge de travail élevée.
Transposé à Système F, cela signifie quun agent de caisse sociale, confronté à une liste de dossiers triés par un score rougeorangevert, sera incité à traiter en priorité les dossiers rouges, même si rien ne loblige formellement à suivre lordre proposé. Une médecin, devant une liste de patients ordonnés par un indice de “priorité” accompagné dun avertissement lorsquelle sen écarte, devra assumer de “désobéir” au système pour reclasser un patient. Un juge, lisant un rapport de risque pénal avec un score mis en avant et une série de formulations standardisées, sait que toute divergence devra être explicitée dans son jugement.
Les interfaces de plateformes, elles, ne montrent souvent rien du tout : lutilisateur voit que sa publication “marche moins bien”, ou bien reçoit un message standardisé lui indiquant que son contenu a “enfreint les standards”, sans que la contribution exact de Système F soit explicitée. Les rapports de transparence exigés par le DSA commencent à donner des indicateurs agrégés sur la proportion de décisions automatisées, mais ils ne modifient pas cette expérience élémentaire : pour lusager, la *cratialité* se résume à un refus, une baisse de portée, un retrait.
Du point de vue archicratique, ces interfaces sont des lieux décisifs : elles articulent la *cratialité IA* et la *cratialité humaine.* Selon la manière dont elles sont conçues plus ou moins explicites, plus ou moins contraignantes, plus ou moins configurables elles peuvent soit renforcer lautorité de Système F au point den faire un quasi-oracle, soit ménager des marges de requalification, de contestation, de suspension. La matérialité du bouton, de la couleur, de lalerte est ici pleinement politique.
### I.4.5. Procédures dintégration : scripts dusage et effets disciplinaires
Enfin, la *cratialité* de Système F se fixe dans les règles internes qui déterminent sa place dans les procédures de décision. Les études de cas sur lIA dans les administrations et le droit de lUE sur les décisions automatisées insistent sur ce point : la différence entre un système de “recommandation” et un système de “décision automatisée” tient souvent moins à la technique quaux scripts organisationnels qui encadrent son usage.
Dans un service de protection sociale, il peut être écrit que “les dossiers marqués à haut risque doivent être examinés en priorité” ; dans un tribunal, que “le score de risque ne peut être le seul fondement dune décision, mais que toute divergence substantielle doit être motivée”. Dans un hôpital, qu“un patient proposé par lalgorithme pour un programme de soins intensifs ne peut être exclu quaprès justification”; dans un service de recrutement, que “seuls les CV classés au-dessus dun certain seuil seront examinés humainement”.
Chacune de ces règles transforme un score en quasi-obligation : lagent, la médecin, le juge, le recruteur ne se contentent plus de consulter Système F ; ils doivent se positionner par rapport à lui, éventuellement sen justifier. Cest là que se loge, souvent, l“*humain dans la boucle*” invoqué par les chartes dIA responsable : un humain reste dans la boucle, mais placé en position davoir à expliquer pourquoi il ne suit pas la recommandation, plutôt que de décider de manière primaire.
Les travaux sur SyRI et sur le scandale des allocations montrent en outre que ces scripts ne sont pas toujours explicités, ni même stabilisés : certains agents témoignent dune pression implicite à suivre les signaux produits par le système, sous peine dêtre jugés “laxistes” ou “inefficaces”. Dans le cas de COMPAS, la décision Loomis autorise lusage de loutil comme aide à la décision, mais sans offrir de critères clairs sur la manière dont les juges devraient articuler le score avec leur appréciation propre : la *cratialité* *se faufile ainsi entre prescription et simple “support”, en laissant la responsabilité dernière porter par les individus*.
Du point de vue de l*archicratie*, cette couche procédurale est cruciale parce quelle fixe, en pratique, ce que “vaut” Système F : sil doit être suivi par défaut, sil peut être contredit, sil déclenche automatiquement des contrôles ou des sanctions, sil ouvre ou non un droit à un examen contradictoire. Dans de nombreuses configurations actuelles, cette valeur est déterminée par des circulaires internes, des guides utilisateurs, des formations *ad hoc*, rarement débattus sur une scène publique.
### I.4.6. Une *cratialité hypertopique*
*Reprise des archives régulatrices, pipelines de transformation, modèles et paramètres, interfaces, scripts dusage* : si lon assemble ces strates, la *cratialité* de Système F apparaît comme une *chaîne dense, continue, extraordinairement efficace*. Elle fait circuler des signaux depuis les bases de données jusquaux décisions prises au guichet, au tribunal, à lhôpital, dans lentreprise, sur la plateforme. Elle est fortement topologisée située dans des centres de données, des services informatiques, des consoles administratives , mais elle ne dispose pas, dans la plupart des cas, de lieux où elle se montre comme telle.
Pour les personnes affectées bénéficiaires, patients, justiciables, candidat·es, usagers la *cratialité* de Système F se présente avant tout comme une force “*hypertopique*” : un *vecteur deffets sans visibilité de sa propre structure*. On peut ressentir ses conséquences (un contrôle inattendu, une radiation, un refus de prestation, une incarcération prolongée, une non-sélection, un contenu invisibilisé), sans jamais pouvoir désigner précisément le “*par quoi et comment*” qui a conduit à la situation.
L*enjeu de lépreuve de détectabilité*, appliquée à la *cratialité*, est dès lors double. Dune part, *rendre descriptible cette chaîne, en montrant quelle nest ni magique ni diffuse, mais composée de décisions localisées, techniquement et institutionnellement situées*. Dautre part, *ouvrir la possibilité dun déplacement : faire exister des scènes où cette cratialité peut être exposée, discutée, reconfigurée, et ne plus opérer de manière invisible*.
Cest à cette condition seulement que la puissance calculatoire de Système F peut être insérée dans un ordre archicratique — cest-à-dire dans un ordre où la manière dont le pouvoir prend forme dans les dispositifs reste, elle aussi, amenée à lépreuve.
## I.5. Archicration existante mais lacunaire
Après l*arcalité déclarée et implicite*, après la *cratialité* de Système F, reste à interroger ce qui tient lieu, aujourdhui, d*archicration* : des *scènes dépreuve où lon pourrait amener la régulation algorithmique en visibilité, la contester, la transformer*. Dans la grammaire de la thèse, une archicration nest pas un simple “dispositif de contrôle” : cest un *lieu institué où les fondements (arcalité) et les instruments (cratialité) peuvent être mis en discussion par des acteurs concernés, dans des formes réglées, avec des effets possibles sur larchitecture du système*.
À première vue, lécosystème de Système F semble en être riche : *comités déthique de lIA*, *conseils de gouvernance des données*, *audits de biais*, *autorités de protection des données*, *agences sectorielles*, *juges*, *mécanismes de recours*, désormais complétés par les *obligations de transparence et de plainte du Digital Services Act*, par des lois sectorielles comme le *Local Law 144* de New York sur les outils automatisés de recrutement, ou par des dispositifs singuliers comme le *Meta Oversight Board*.
Mais, dès quon reformule les questions dans les termes archicratiques — *qui peut voir quoi ? qui peut contester quoi ? avec quels effets sur le système lui-même ?* — le paysage se transforme. Beaucoup de ces dispositifs produisent des avis, des rapports, des sanctions ponctuelles, des formulaires de recours ; très peu organisent une véritable comparution de Système F devant ceux quil affecte.
### I.5.1. Cartographie rapide des prétendants à la scène
On peut, pour commencer, distinguer quatre grandes familles de dispositifs qui, chacune à leur manière, prétendent jouer un rôle dinstance dépreuve pour lIA :
1. *Comités, chartes, conseils dexperts*
Comités internes déthique de lIA dans les grandes entreprises technologiques, commissions *ad hoc* dans les administrations, groupes dexperts de haut niveau comme celui qui a élaboré les Lignes directrices pour une IA digne de confiance au niveau européen. Ils produisent des principes, des recommandations, des “bonnes pratiques”.
2. *Audits et évaluations techniques*
Audits de biais sur les outils de recrutement imposés par le Local Law 144 à New York (obligation de réaliser un audit annuel, de publier un résumé, dinformer les candidats).
Évaluations dimpact sur les droits fondamentaux ou sur les risques, demandées par certains régulateurs et expérimentées dans le cadre de lAI Act européen et de rapports comme *Algorithmic Rule* ou le *Handbook: AI and Public Administration*.
3. *Autorités de régulation et juridictions*
Autorités de protection des données, conseils pour légalité et organismes antidiscrimination, autorités sectorielles, institutions européennes (Commission, FRA, etc.) qui ont enquêté sur les systèmes de profilage dans le social, la police ou la fiscalité.
Cours nationales et européennes, comme le tribunal de La Haye dans SyRI, ou la chaîne de procédures qui a suivi le scandale des allocations familiales aux Pays-Bas.
4. *Voies de recours et mécanismes de réclamation*
Droit au recours des allocataires, des justiciables, des patients, des candidats à lemploi, des utilisateurs de plateformes.
Mécanismes internes de plainte et de contestation imposés par le Digital Services Act (obligation pour les grandes plateformes de prévoir des procédures de traitement des signalements et des recours, de publier des rapports annuels sur la modération, en précisant notamment la part de décisions automatisées et les taux derreurs).
Dispositifs spécifiques comme le *Meta Oversight Board*, qui réexamine un nombre limité de décisions de modération emblématiques et publie des décisions motivées et des recommandations.
Dans ce maillage, les éléments dune *archicration authentique* sont présents : *lieux de délibération, expertises, procédures contradictoires, sanctions possibles*. Mais leur articulation et leur accessibilité restent profondément inégales. Surtout, la plupart de ces dispositifs sadressent avant tout aux organisations et aux concepteurs, beaucoup moins aux personnes directement affectées par Système F.
### I.5.2. Comités et chartes : scènes sans publics
Les comités déthique de lIA et les groupes dexperts ont joué un rôle central dans la formulation des grands principes qui structurent l*arcalité déclarée des systèmes* — équité, transparence, robustesse, responsabilité, etc. Les AI Principles de Google et Microsoft, les Lignes directrices européennes pour une IA digne de confiance, ou encore les rapports nationaux sur “lIA et les libertés” en sont des exemples typiques.
Ces instances ont bien une dimension quasi archicratique : elles mettent en scène, dans un cercle de spécialistes, des questions de fond (“quest-ce quune IA digne de confiance ?”, “quels sont les risques majeurs pour les droits fondamentaux ?”). Elles produisent des textes publics, organisent des consultations, parfois invitent la société civile à réagir. Mais, du point de vue de Système F, elles restent à un niveau très général :
- elles ne se prononcent que rarement sur un système concret inséré dans des chaînes cratiales spécifiques (fraude sociale, tri de CV, gestion des risques de santé, etc.) ;
- elles ne réunissent quà la marge les personnes directement affectées par ces dispositifs (allocataires, patients, justiciables, candidats, usagers) ;
- elles nont pas, sauf exception, de pouvoir dinjonction ou de suspension sur les systèmes en question.
Autrement dit, ces comités produisent des scènes de discours normatif situées très en amont, mais ils norganisent pas lépreuve dun Système F déterminé. Ils contribuent à la fondation discursive de l“IA responsable”, sans pour autant mettre en visibilité la *cratialité effective* des dispositifs déjà déployés.
### I.5.3. Audits de biais et évaluations dimpact : scènes confinées, biaisées par les conflits dintérêts
Une deuxième famille de dispositifs, plus proche des chaînes réelles de Système F, est celle des *audits de biais* et des *évaluations dimpact algorithmiques*. Dans de nombreux pays, cette famille est en plein essor : New York, avec le *Local Law 144*, impose des audits de biais pour les outils automatisés de recrutement ; le Canada a généralisé l“*Algorithmic Impact Assessment*” pour les systèmes de décision automatisée dans ladministration ; des guides de bonnes pratiques, produits par des organisations internationales, des agences publiques et des *think tanks*, enjoignent désormais administrations et entreprises à “évaluer” leurs systèmes de profilage avant ou pendant leur déploiement.
Dans lesprit, on pourrait croire tenir enfin une *archicration* structurée : un tiers examine un système, mesure ses effets, formule des recommandations, éventuellement sous le regard de lautorité ou du public. Un rapport est produit, parfois publié ; des chiffres sont discutés ; des engagements damélioration sont pris. Mais dès quon regarde de près *qui audite, sur quoi, avec quelles marges de manœuvre*, apparaissent des tensions lourdes, qui tiennent moins de la sophistication statistique que de la *configuration des intérêts en présence*.
Premièrement, la plupart des régimes daudit existants reposent sur un modèle classique de *relation clientprestataire*. Cest lorganisation qui déploie Système F employeur, administration, plateforme qui commande, finance et choisit son auditeur. Le *Local Law 144* de New York illustre bien cette logique : les employeurs doivent faire réaliser un audit annuel par un tiers “indépendant” de leurs outils de décision automatisée en matière demploi, et publier un résumé des résultats. Sur le papier, lexigence dindépendance est posée ; dans la pratique, rien nempêche la constitution dun marché de cabinets spécialisés dont la survie dépend de la capacité à produire des audits compatibles avec les attentes de leurs clients. Les premières analyses de ce régime soulignent un nombre limité daudits effectivement réalisés, une tentation dinterpréter les exigences de manière minimaliste, et un *risque de* “*vice de conformité*” : *laudit devient un examen du respect formel des prescriptions, non une épreuve substantielle du dispositif et de ses usages*.
Deuxièmement, la montée en puissance dune véritable “industrie de laudit de lIA” introduit un *conflit dintérêts structurel*. De *grandes firmes de conseil* parfois les mêmes qui développent, vendent ou intègrent des solutions dIA *se positionnent comme auditeurs des systèmes quelles contribuent par ailleurs à diffuser*. Des organismes de normalisation, comme le British Standards Institution, ont explicitement mis en garde contre cette situation : un nombre significatif dacteurs qui commercialisent des audits dIA sont aussi producteurs de technologies, ce qui alimente des doutes sur leur indépendance et sur la rigueur des évaluations ; des initiatives de standardisation cherchent désormais à encadrer ces pratiques. Dans le même sens, les appels à des audits “holistiques” qui évalueraient non seulement les performances techniques, mais aussi les présupposés normatifs, les effets sociaux et les mécanismes de gouvernance insistent sur la *nécessité dauditeurs* “*libres de tout conflit dintérêts*”, *sans quoi la procédure se réduit à une validation de façade*.
Troisièmement, les *évaluations dimpact algorithmiques* mises en place dans le secteur public prolongent souvent, sous une forme plus sophistiquée, la *logique de lauto-évaluation*. Lorsquun ministère ou une agence réalise lui-même “son” évaluation dimpact avant de déployer un système de profilage ou de tri, il se trouve en position de juger la pertinence dun dispositif quil a conçu, financé, promu et quil espère présenter comme vecteur de modernisation. Les travaux pionniers sur les *Algorithmic Impact Assessments* insistent, à linverse, sur la nécessité de dispositifs véritablement contradictoires : participation forte des publics concernés, consultations publiques substantielles, possibilité pour des acteurs externes (ONG, chercheurs, journalistes) de demander des compléments ou de contester des évaluations jugées insuffisantes. Certaines analyses des cadres européens vont dans le même sens, en plaidant pour des droits daccès aux données, aux modèles et aux documents de conception, faute de quoi aucun écosystème daudit réellement indépendant ne peut émerger.
Si lon recompose ces éléments dans notre langue archicratique, on voit se dessiner une *typologie de conflits dintérêts qui affectent directement la scène dépreuve*. Les conflits sont *financiers*, lorsque lauditeur dépend économiquement, de manière répétée, du client quil est censé contrôler ; *organisationnels*, lorsque laudit est confié à des structures internes, à des filiales ou à des partenaires stratégiques qui partagent les mêmes objectifs de déploiement ; *cognitifs*, enfin, lorsque audités et auditeurs appartiennent au même petit milieu technico-juridique, avec des catégories de pensée, des indicateurs et des horizons de pertinence largement communs. Dans ces trois cas, *linstance censée jouer le rôle de tiers contradicteur se trouve, à divers degrés, alignée avec les intérêts et les cadrages de ceux qui conçoivent et exploitent Système F*.
Le cadrage même des audits accentue cette dérive. Les textes juridiques et les guides méthodologiques encouragent parfois une vision très étroite de lobjet audité. Le *Local Law 144*, par exemple, impose de mesurer des écarts de taux de sélection selon le genre et la “race/ethnicité” dans les outils de recrutement, mais ne couvre pas dautres dimensions pourtant protégées par le droit (âge, handicap) ou manifestement pertinentes (origine sociale, statut migratoire, langue). Dans ce contexte, lorganisation a tout intérêt à limiter lexercice à ce qui est strictement requis, à traiter laudit comme une *check-list* de ratios, et à laisser hors champ les questions plus profondes de fonction de coût, de proxy ou de composition des jeux de données cest-à-dire précisément l*arcalité implicite* que notre cas cherche à mettre au jour.
Au terme de cette séquence, les *audits de biais* et *évaluations dimpact* apparaissent comme des *archicrations tronquées*. Il y a bien, formellement, une scène : un rapport est rédigé, parfois rendu public ; des chiffres sont produits ; des recommandations sont formulées. Mais les personnes directement affectées par Système F allocataires, justiciables, patients, candidat·es, utilisateurs de plateformes en sont largement absentes, ou réduites au statut de “parties prenantes” abstraites ; les choix les plus déterminants (fonctions de coût, proxies, seuils, composition des jeux de données) restent souvent hors du périmètre audité, au profit dindicateurs aisément mesurables ; les *conflits dintérêts structurels*, enfin, minent la capacité de lauditeur à assumer le rôle de *tiers contradicteur* que l*archicration* exigerait.
Dans notre grammaire archicratique, ces dispositifs constituent donc des épreuves techniques sans scène véritable : lalgorithme est certes testé, mais la collectivité ne dispose pas dun lieu institué où confronter les résultats, interroger les axiomes, contester les compromis retenus, exiger des transformations. Laudit remplit principalement une fonction de légitimation “le système a été évalué” plus quune fonction de mise en débat. Autrement dit : la *cratialité* de Système F est brièvement éclairée par quelques faisceaux dexpertise, mais l*arcalité implicite* reste soustraite à la comparution, et la scène demeure largement capturée par ceux qui ont intérêt à maintenir le dispositif intact.
### I.5.4. Autorités et tribunaux : scènes fortes, mais rares et *ex post*
Les autorités de régulation et les juridictions offrent, à première vue, les formes les plus accomplies d*archicration* : procédures contradictoires, auditions, décisions motivées, sanctions, parfois réparation.
Larrêt SyRI du tribunal de La Haye est emblématique : le dispositif de profilage de fraude aux prestations y est décrit, mis en rapport avec larticle 8 de la CEDH, et finalement jugé disproportionné, en raison notamment du manque de transparence, du ciblage de quartiers défavorisés et de la difficulté pour les personnes profilées de contester le système.
Dans le scandale des allocations pour la garde denfants, les enquêtes de lAutorité de protection des données (AP), les rapports parlementaires et, finalement, la crise politique qui a conduit à la démission du gouvernement Rutte illustrent ce que peut être une scène dépreuve à grande échelle : les pratiques de profilage, les critères utilisés (dont la double nationalité), les effets sur des milliers de familles sont mis au jour, décrits, condamnés, et donnent lieu à un vaste plan de compensation.
Au niveau européen, le DSA commence à être appliqué comme base juridique pour sanctionner des plateformes qui manquent à leurs obligations de transparence, comme dans le cas récent de lamende infligée à X (anciennement Twitter) pour violation de ses devoirs de transparence et de lutte contre les “*dark patterns*”.
Ces scènes ont un effet archicratique réel : elles forcent les systèmes à comparaître, révèlent des pratiques jusque-là invisibles, imposent des réformes. Mais elles ont aussi des limites structurelles :
- Elles interviennent tard, après des années dusage, lorsque les dommages sont déjà massifs, comme dans le *toeslagenaffaire* (surendettement, perte de logement, placement denfants).
- Elles restent focalisées sur certains aspects juridiques (vie privée, discrimination, transparence) sans pouvoir, à elles seules, reconfigurer lensemble de la chaîne cratiale de Système F.
- Elles donnent une place indirecte aux personnes affectées (plaignants, associations, ONG), mais ces dernières nont ni la maîtrise de lagenda, ni la garantie que la logique même du modèle sera transformée.
On pourrait dire, en termes archicratiques, que ces procédures sont des *scènes de rattrapage* : elles produisent des effets puissants, mais elles ne transforment pas encore la régulation algorithmique en régime ordinaire de comparution. Système F ny vient quen cas de crise, non comme un acteur continuellement justiciable.
### I.5.5. Recours individuels et plaintes : scènes fermées, réponses standardisées
Reste la question des recours : que peut faire, dans létat actuel des choses, un individu ciblé par Système F ?
Dans les politiques sociales, un allocataire qui voit sa prestation suspendue ou refusée peut en principe exercer un recours administratif ou contentieux. Pourtant, comme lont montré les enquêtes sur le scandale néerlandais, ces voies ont été largement inopérantes face à des décisions massives et standardisées, fondées sur des profils de risque opaques. Des parents ont multiplié les recours individuels sans succès, jusquà ce que des journalistes, des parlementaires et des autorités de contrôle parviennent à ouvrir le scandale au niveau systémique.
Le recours reste structuré comme si la décision avait été prise par un agent individuel, dans un dossier singulier ; il noffre aucune prise pour contester la logique même du système de profilage.
Dans le recrutement, des candidats peuvent saisir les autorités anti-discrimination ou engager des actions en justice, comme dans les affaires récentes où des candidats ont attaqué des fournisseurs doutils de tri automatisé pour discrimination raciale ou fondée sur le handicap.
Mais même les dispositifs les plus avancés, comme le *Local Law 144*, se concentrent sur le respect dobligations de procédure (audit, transparence minimale), non sur louverture dune scène où les candidats pourraient discuter des critères incorporés dans loutil. Une fois que lemployeur peut montrer quun audit a été réalisé et quun résumé est en ligne, la possibilité de contester la structure même de loutil reste très limitée.
Pour les plateformes, le DSA impose lexistence de *mécanismes internes de réclamation* et, pour les très grandes plateformes, la *mise en place de systèmes de traitement des notifications de contenus illégaux et de plaintes contre les décisions de modération*, ainsi que des *mécanismes de règlement extrajudiciaire des litiges*.
En pratique, ces dispositifs prennent la forme de formulaires en ligne, de délais de réponse, de messages standardisés. Ils permettent parfois de corriger des erreurs manifestes (restauration dun contenu, réouverture dun compte), mais ils nouvrent presque jamais une discussion sur les critères de modération eux-mêmes. Lutilisateur reste face à une interface laconique ; le rôle de Système F dans la décision (score de toxicité, détection de désinformation, etc.) est rarement explicité.
Le *Meta Oversight Board* constitue une exception partielle : il *publie des décisions motivées*, *analyse la conformité des politiques de Meta aux droits humains*, *formule des recommandations publiques*, parfois très critiques, sur certains *aspects de la modération* et de la *hiérarchisation des contenus*.
Mais il ne traite quun nombre infime de cas, sélectionnés parce quils soulèvent des questions emblématiques ; il na pas de pouvoir direct sur la conception des systèmes de recommandation ou sur lensemble des algorithmes qui régulent la visibilité. Sa scène est réelle, mais fortement débitée : quelques affaires par an, dans un océan de décisions automatisées quotidiennes.
Dans la santé, enfin, les patients disposent de droits daccès à leur dossier et, dans certains pays, peuvent saisir des médiateurs ou des commissions déthique clinique. Les travaux sur lalgorithme dObermeyer et al. montrent que la prise de conscience de ses effets discriminatoires est venue de chercheurs en épidémiologie et en médecine, non de recours individuels de patients.
Là encore, la scène dépreuve reste centrée sur la relation médecinpatient ; Système F y apparaît, au mieux, comme un outil contextuel, rarement comme objet principal de la contestation.
On voit se dessiner un trait commun : les mécanismes de recours existants permettent de contester les effets (une suspension de prestation, une peine, un refus dembauche, un retrait de contenu), beaucoup plus difficilement le dispositif qui les produit. Ils ouvrent surtout des scènes de réclamation, non des scènes d*archicration*.
### I.5.6. *Archicrations fantômes* et *oblitération de la scène*
Si lon rassemble ces éléments, le diagnostic archicratique sur l“*archicration existante*” de Système F devient plus précis.
- Oui, il existe des *instances qui ressemblent à des scènes* : *comités dexperts, audits, autorités de régulation, tribunaux, mécanismes de plainte, organes comme lOversight Board*.
- Oui, certaines de ces *instances produisent des effets tangibles* : *arrêt de SyRI, révélation et compensation dans le scandale des allocations, sanctions financières sous le DSA, ajustements ou abandons de certains outils* (comme le système de recrutement dAmazon).
- Mais, pour lessentiel, ces *instances restent partielles, sectorisées, tardives et pauvres en participation directe des personnes affectées*.
Du point de vue de l*archicratie*, cela signifie que :
- L*arcalité* de Système F existe, mais elle demeure largement *fantomatique* : les fondements implicites (proxies, fonctions de coût, hiérarchies des erreurs) ne sont presque jamais mis en scène comme tels. Les grands principes d“IA digne de confiance” ou de “lutte contre la fraude” sont proclamés, mais leurs traductions opératoires ne sont pas exposées devant ceux quelles engagent.
- La *cratialité* est puissante, finement articulée, mais *hypertopique* : elle concentre ses opérations dans des architectures techniques et organisationnelles peu visibles, qui produisent des effets massifs sans quil soit possible, pour un individu, de remonter aisément la chaîne du “*par quoi et comment*”.
- L*archicration*, enfin, est *lacunaire* : elle se manifeste soit sous forme de procédures internes, daudits, de comités qui ne sont pas de vraies scènes publiques ; soit sous forme de grandes affaires contentieuses ou de scandales médiatiques, qui jouent le rôle de scènes dexception plutôt que dinstances ordinaires de mise à lépreuve.
On peut, avec la thèse, parler ici d*archicrations fantômes* : des dispositifs qui empruntent lallure des scènes (commissions, formulaires, recours), mais qui ne disposent ni de la consistance, ni de louverture, ni de la réflexivité nécessaires pour faire effectivement comparaître Système F. Ils maintiennent limpression dune possibilité de recours, sans organiser véritablement la confrontation des fondements, des instruments et des effets.
La première conclusion de lépreuve de détectabilité est ainsi nette : dans létat actuel des usages de Système F, la régulation algorithmique est, pour une large part, hors scène. Les scènes qui existent sont soit trop en amont (principes généraux), soit trop en aval (scandales, contentieux), soit trop étroites (audits techniques fermés, formulaires de plainte standardisés). La suite du cas détude consistera à replacer cette *oblitération archicratique* dans la longue histoire des régimes régulateurs, puis à examiner ce que pourrait signifier, pour un système dIA de ce type, une véritable réouverture de la scène : non plus des reculs ponctuels, mais une politique explicite des épreuves, où Système F serait tenu de rendre des comptes, non seulement sur ses performances, mais sur les fondements et les formes de pouvoir quil met en œuvre.
##

Some files were not shown because too many files have changed in this diff Show More