feat(landing): mutation racontée + état 3 régimes + SVG boucle

Trois améliorations issues de l'audit ChatGPT du manifeste :

1. Nouvelle section 04 "une mutation racontée" — récit factuel de
   prec_013 (rls_policy_with_app_context) en timeline 6 étapes :
   proposition silencieuse → détection manifest → arbitrage Polis →
   précédent inscrit → manifest mis à jour → refus runtime du
   prochain agent. Rend l'autocatalyse tangible sur un cas réel.

2. Section 07 "état" refondue en 3 colonnes (au lieu de 2) :
   - Runtime live et vérifié
   - Doctrine ratifiée, runtime partiel
   - En écriture
   Évite la lecture "tout est déjà fermé" sur la section État.
   Aligne avec la doctrine lazy (S173k+11).

3. SVG diagramme de la boucle PM → Claude → Cowork → MCP externe
   ajouté dans la section 02, plus parlant que les nœuds décoratifs
   du hero.

Corrections compteurs alignées sur counter-sync : 67→68 helpers,
162→163 migrations, 14→15 tests adversariaux. Renumérotation des
sections (4-5-6-7 au lieu de 4-5-6).

RWD vérifié : 6 breakpoints couvrant 480/560/640/720/880/960px.
Timeline marker + svg + state-grid-3 ajoutés à mobile.
This commit is contained in:
usermod -aG sudo myk 2026-05-28 00:41:43 -04:00
parent 1f253b6c4b
commit 8bd91d9f26
2 changed files with 293 additions and 16 deletions

View File

@ -124,6 +124,52 @@
Chaque tour de boucle laisse l'habitat un peu plus précis, un peu plus défendu, un peu plus capable de se gouverner seul. Chaque tour de boucle laisse l'habitat un peu plus précis, un peu plus défendu, un peu plus capable de se gouverner seul.
</p> </p>
<figure class="loop-svg-figure" aria-hidden="true">
<svg viewBox="0 0 720 360" class="loop-svg" preserveAspectRatio="xMidYMid meet">
<defs>
<marker id="arrowhead" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#7eecda"/>
</marker>
<radialGradient id="loopNode" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#111114"/>
<stop offset="100%" stop-color="#0a0a0c"/>
</radialGradient>
</defs>
<!-- Nodes (4 — placed in a diamond) -->
<g class="loop-svg-node">
<circle cx="120" cy="180" r="58" fill="url(#loopNode)" stroke="#2a4a45" stroke-width="1.5"/>
<text x="120" y="172" text-anchor="middle" class="loop-svg-tag">PM BOARD</text>
<text x="120" y="192" text-anchor="middle" class="loop-svg-label">décide</text>
</g>
<g class="loop-svg-node">
<circle cx="360" cy="70" r="58" fill="url(#loopNode)" stroke="#2a4a45" stroke-width="1.5"/>
<text x="360" y="62" text-anchor="middle" class="loop-svg-tag">CLAUDE CODE</text>
<text x="360" y="82" text-anchor="middle" class="loop-svg-label">écrit</text>
</g>
<g class="loop-svg-node">
<circle cx="600" cy="180" r="58" fill="url(#loopNode)" stroke="#2a4a45" stroke-width="1.5"/>
<text x="600" y="172" text-anchor="middle" class="loop-svg-tag">COWORK</text>
<text x="600" y="192" text-anchor="middle" class="loop-svg-label">observe</text>
</g>
<g class="loop-svg-node loop-svg-ext">
<circle cx="360" cy="290" r="58" fill="url(#loopNode)" stroke="#7eecda" stroke-width="1.5"/>
<text x="360" y="282" text-anchor="middle" class="loop-svg-tag">MCP EXTERNE</text>
<text x="360" y="302" text-anchor="middle" class="loop-svg-label">contre-propose</text>
</g>
<!-- Arrows (curved between adjacent nodes) -->
<path d="M 168 145 Q 240 80 305 80" fill="none" stroke="#7eecda" stroke-width="1.5" marker-end="url(#arrowhead)" opacity="0.8"/>
<path d="M 415 80 Q 480 80 552 145" fill="none" stroke="#7eecda" stroke-width="1.5" marker-end="url(#arrowhead)" opacity="0.8"/>
<path d="M 552 215 Q 480 280 415 290" fill="none" stroke="#7eecda" stroke-width="1.5" marker-end="url(#arrowhead)" opacity="0.8"/>
<path d="M 305 290 Q 240 280 168 215" fill="none" stroke="#7eecda" stroke-width="1.5" marker-end="url(#arrowhead)" opacity="0.8"/>
<!-- Center label -->
<text x="360" y="178" text-anchor="middle" class="loop-svg-center">autocatalyse</text>
<text x="360" y="198" text-anchor="middle" class="loop-svg-center-sub">chaque mutation laisse un précédent</text>
</svg>
</figure>
<div class="loop-grid"> <div class="loop-grid">
<div class="loop-node"> <div class="loop-node">
<div class="loop-tag">PM Board</div> <div class="loop-tag">PM Board</div>
@ -207,9 +253,80 @@
</div> </div>
</section> </section>
<!-- ═══════════════════ MUTATION RACONTÉE ═══════════════════ -->
<section id="mutation" class="block dark-block">
<p class="section-num">04 — une mutation racontée</p>
<h2>La machine apprend une règle. Le prochain agent ne peut plus l'ignorer.</h2>
<p class="block-lede">
Récit factuel d'un précédent ratifié&nbsp;: <code>prec_013</code>. Cinq mois plus tôt, n'importe quel agent pouvait ouvrir une connexion sur une table multi-tenant. Aujourd'hui, le runtime refuse — pas un reviewer, pas un linter&nbsp;: la base elle-même.
</p>
<ol class="timeline">
<li class="timeline-step">
<div class="timeline-marker">i</div>
<div class="timeline-body">
<div class="timeline-tag">proposition</div>
<h3>Un agent veut écrire dans une table multi-tenant.</h3>
<p>La table porte du RLS <code>FORCE</code>. La policy existe, le rôle Postgres ne possède pas <code>BYPASSRLS</code>. Premier réflexe d'agent&nbsp;: ouvrir <code>postgres.js</code>, lancer l'<code>INSERT</code>. Le résultat n'est pas une erreur. C'est <em>silence</em>&nbsp;: zéro ligne écrite, zéro exception, zéro indication. Le bug le plus dangereux est celui qui ne crie pas.</p>
</div>
</li>
<li class="timeline-step">
<div class="timeline-marker">ii</div>
<div class="timeline-body">
<div class="timeline-tag">détection</div>
<h3>Le manifest exige une catégorie d'enforcement.</h3>
<p>Toute mutation déclarée doit choisir parmi cinq catégories ratifiées. L'agent tente <code>rls_policy</code> seul. Le gate d'admission refuse&nbsp;: ce mécanisme n'est pas dans la liste des dix <code>equivalent_enforcement</code> fermés. La doctrine n'admet pas un mécanisme par convenance.</p>
</div>
</li>
<li class="timeline-step">
<div class="timeline-marker">iii</div>
<div class="timeline-body">
<div class="timeline-tag">arbitrage polis</div>
<h3>L'architecte tranche, conditions explicites.</h3>
<p>Polis examine&nbsp;: RLS protège la ligne, mais sans contexte applicatif posé (<code>app.org_id</code>, <code>app.actor_id</code>), la policy retourne <em>vide</em> au lieu d'<em>erreur</em>. Verdict : un mécanisme composé est nécessaire. Cinq conditions cumulatives, six prédicats fermés, audit différencié, test adversariel par surface.</p>
</div>
</li>
<li class="timeline-step">
<div class="timeline-marker">iv</div>
<div class="timeline-body">
<div class="timeline-tag">précédent inscrit</div>
<h3><code>prec_013</code> entre au registre, append-only.</h3>
<p>Le mécanisme prend un nom&nbsp;: <code>rls_policy_with_app_context</code>. Il devient le neuvième <code>equivalent_enforcement</code> ratifié. Le précédent est écrit dans <code>polis_decisions</code> avec ses cinq conditions, ses six prédicats, son test adversarial obligatoire. Le runtime peut le citer&nbsp;; il ne peut pas l'inventer.</p>
</div>
</li>
<li class="timeline-step">
<div class="timeline-marker">v</div>
<div class="timeline-body">
<div class="timeline-tag">manifest mis à jour</div>
<h3>Le contrat devient lisible par la machine.</h3>
<p>L'entrée du manifest référence <code>prec_013</code>. Les helpers <code>withOrg()</code>, <code>withAgent()</code>, <code>withSuperAdmin()</code> posent six GUC Postgres sur la transaction. Le code n'a plus à se souvenir&nbsp;: l'oubli est structurellement impossible.</p>
</div>
</li>
<li class="timeline-step">
<div class="timeline-marker">vi</div>
<div class="timeline-body">
<div class="timeline-tag">le prochain agent</div>
<h3>Refusé par le runtime, pas par un humain.</h3>
<p>Un nouvel agent — Claude, ChatGPT, un script interne — tente d'écrire sur la même table sans poser le contexte. Le gate d'admission lit <code>prec_013</code>, vérifie les conditions, refuse. La règle n'est plus dans une spec à lire. Elle est dans la base, dans le compilateur, dans le runtime. C'est ça, l'autocatalyse&nbsp;: chaque erreur réparée devient un mur que la machine ne se cogne plus jamais.</p>
</div>
</li>
</ol>
<p class="timeline-footer">
Précédent ratifié le 25 avril 2026. Trente autres précédents suivent la même mécanique&nbsp;: une situation, un arbitrage, un mécanisme nommé, un test, un gate. La constitution n'est pas un texte. C'est un comportement de la machine.
</p>
</section>
<!-- ═══════════════════ ÉCONOMIE + UGC ═══════════════════ --> <!-- ═══════════════════ ÉCONOMIE + UGC ═══════════════════ -->
<section class="block dark-block"> <section class="block">
<p class="section-num">04 — économie + contenu</p> <p class="section-num">05 — économie + contenu</p>
<h2>Quand le métabolisme s'auto-régule.</h2> <h2>Quand le métabolisme s'auto-régule.</h2>
<div class="two-col"> <div class="two-col">
@ -228,7 +345,7 @@
<!-- ═══════════════════ SOUVERAINETÉ ═══════════════════ --> <!-- ═══════════════════ SOUVERAINETÉ ═══════════════════ -->
<section class="block"> <section class="block">
<p class="section-num">05 — souveraineté</p> <p class="section-num">06 — souveraineté</p>
<h2>Aucune dépendance critique qui ne soit substituable.</h2> <h2>Aucune dépendance critique qui ne soit substituable.</h2>
<div class="sov-grid"> <div class="sov-grid">
<div class="sov-card"> <div class="sov-card">
@ -260,32 +377,43 @@
<!-- ═══════════════════ ÉTAT DE LA MACHINE ═══════════════════ --> <!-- ═══════════════════ ÉTAT DE LA MACHINE ═══════════════════ -->
<section id="etat" class="block dark-block"> <section id="etat" class="block dark-block">
<p class="section-num">06 — état</p> <p class="section-num">07 — état</p>
<h2>Ce qui tourne déjà. Ce qui s'écrit.</h2> <h2>Trois régimes. Pas un seul.</h2>
<p class="block-lede">
Une partie de la machine tourne en production, vérifiable. Une autre est ratifiée doctrinalement, avec son implémentation runtime partielle ou en cours d'instrumentation. Une troisième est en écriture. Confondre les trois rendrait le tableau soit magique, soit ambitieux. La distinction est ce qui rend la cadence opposable.
</p>
<div class="state-grid-3">
<div class="state-grid">
<div class="state-block"> <div class="state-block">
<h3>Livré runtime</h3> <h3>Runtime live et vérifié</h3>
<ul class="state-list"> <ul class="state-list">
<li><span class="num">42</span><span>tools MCP gateés</span></li> <li><span class="num">42</span><span>tools MCP gateés (OAuth 2.1 + RS256 + JWKS publique)</span></li>
<li><span class="num">46</span><span>routes Bun&nbsp;/&nbsp;Hono</span></li> <li><span class="num">46</span><span>routes Bun&nbsp;/&nbsp;Hono</span></li>
<li><span class="num">67</span><span>helpers TypeScript</span></li> <li><span class="num">68</span><span>helpers TypeScript</span></li>
<li><span class="num">162</span><span>migrations SQL idempotentes</span></li> <li><span class="num">163</span><span>migrations SQL idempotentes</span></li>
<li><span class="num">31</span><span>précédents constitutionnels ratifiés</span></li>
<li><span class="num">47</span><span>mécanismes d'enforcement équivalents au manifest</span></li>
<li><span class="num">14</span><span>tests adversariaux constitutionnels</span></li>
<li><span class="num">7</span><span>orgs vivantes en production</span></li> <li><span class="num">7</span><span>orgs vivantes en production</span></li>
<li><span class="num">0</span><span>fichier PHP (depuis le 5 avril 2026)</span></li> <li><span class="num">0</span><span>fichier PHP (depuis le 5 avril 2026)</span></li>
</ul> </ul>
</div> </div>
<div class="state-block"> <div class="state-block">
<h3>S'écrit maintenant</h3> <h3>Doctrine ratifiée, runtime partiel</h3>
<ul class="state-list">
<li><span class="num">31</span><span>précédents constitutionnels ratifiés — registre <code>polis_decisions</code> live, supersedure traçable</span></li>
<li><span class="num">47</span><span>mécanismes d'enforcement au manifest — instrumentation en cours sur les surfaces secondaires</span></li>
<li><span class="num">15</span><span>tests adversariaux constitutionnels — couverture par mécanisme à finir</span></li>
<li><span class="num">9</span><span>conditions cumulatives ratifiées pour <code>rls_policy_with_app_context</code> (verrou étape 9 partiel)</span></li>
</ul>
</div>
<div class="state-block">
<h3>En écriture</h3>
<ul class="state-list-soft"> <ul class="state-list-soft">
<li>Bascule étape 9 fail-closed sur les surfaces critiques</li> <li>Bascule étape 9 fail-closed sur les surfaces critiques restantes</li>
<li>Agent v4 — registre, capacités, execution bindings append-only</li> <li>Agent v4 — registre, capacités, execution bindings append-only</li>
<li>Economic Kernel v1 — double-entrée, mandats, freeze</li> <li>Economic Kernel v1 — double-entrée, mandats, freeze</li>
<li>Mailbox v1 — saga pattern inter-agent avec timeout et causalité</li> <li>Mailbox v1 — saga pattern inter-agent (timeout + causalité)</li>
<li>Dockerisation — fin du bare-metal, dix sous-tâches</li> <li>Dockerisation — fin du bare-metal, dix sous-tâches</li>
<li>Lifecycle dashboard <code>/me</code> — le citoyen voit son propre métabolisme</li> <li>Lifecycle dashboard <code>/me</code> — le citoyen voit son propre métabolisme</li>
<li>OAuth 2.1 authorization_code + PKCE — flow claude.ai natif</li> <li>OAuth 2.1 authorization_code + PKCE — flow claude.ai natif</li>
@ -294,6 +422,7 @@
<li>Migration <code>founder_root</code> sur hardware token</li> <li>Migration <code>founder_root</code> sur hardware token</li>
</ul> </ul>
</div> </div>
</div> </div>
<p class="state-footer"> <p class="state-footer">

148
style.css
View File

@ -473,6 +473,144 @@ code {
color: var(--fg); color: var(--fg);
} }
/*
LOOP SVG (figure dans section #loop)
*/
.loop-svg-figure {
max-width: 760px;
margin: 0 auto 3rem;
padding: 1rem 0;
}
.loop-svg {
width: 100%;
height: auto;
display: block;
}
.loop-svg-tag {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.12em;
fill: var(--accent);
}
.loop-svg-label {
font-family: var(--serif);
font-size: 13px;
font-style: italic;
fill: var(--fg);
}
.loop-svg-center {
font-family: var(--mono);
font-size: 14px;
letter-spacing: 0.08em;
fill: var(--fg-muted);
text-transform: uppercase;
}
.loop-svg-center-sub {
font-family: var(--serif);
font-size: 11px;
font-style: italic;
fill: var(--fg-dim);
}
@media (max-width: 560px) {
.loop-svg-figure { margin-bottom: 2rem; }
.loop-svg-tag { font-size: 9px; }
.loop-svg-label { font-size: 11px; }
.loop-svg-center { font-size: 11px; }
.loop-svg-center-sub { font-size: 9px; }
}
/*
TIMELINE (section "mutation racontée")
*/
.timeline {
list-style: none;
position: relative;
padding: 0 0 0 0;
margin: 0 auto;
max-width: 760px;
}
.timeline::before {
content: "";
position: absolute;
left: 1.35rem;
top: 0.5rem;
bottom: 0.5rem;
width: 1px;
background: var(--accent-dim);
}
@media (max-width: 560px) {
.timeline::before { left: 1.1rem; }
}
.timeline-step {
position: relative;
display: grid;
grid-template-columns: 3rem 1fr;
gap: 1.25rem;
padding: 0 0 2.25rem 0;
}
.timeline-step:last-child { padding-bottom: 0; }
@media (max-width: 560px) {
.timeline-step { grid-template-columns: 2.5rem 1fr; gap: 1rem; padding-bottom: 1.75rem; }
}
.timeline-marker {
width: 2.7rem;
height: 2.7rem;
border-radius: 50%;
background: var(--bg);
border: 1.5px solid var(--accent-dim);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
font-size: 0.9rem;
color: var(--accent);
letter-spacing: 0.02em;
position: relative;
z-index: 1;
}
@media (max-width: 560px) {
.timeline-marker { width: 2.2rem; height: 2.2rem; font-size: 0.8rem; }
}
.timeline-tag {
font-family: var(--mono);
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 0.5rem;
}
.timeline-body h3 {
font-family: var(--serif);
font-weight: 400;
font-size: clamp(1.15rem, 1.7vw, 1.4rem);
line-height: 1.3;
letter-spacing: -0.01em;
color: var(--fg);
margin-bottom: 0.75rem;
text-wrap: balance;
}
.timeline-body p {
font-size: 0.98rem;
color: var(--fg-muted);
line-height: 1.65;
max-width: 42em;
}
.timeline-body p em { color: var(--accent); font-style: italic; }
.timeline-footer {
margin: 2.5rem auto 0;
max-width: 760px;
font-size: 0.95rem;
color: var(--fg-muted);
font-style: italic;
border-top: 1px solid var(--border);
padding-top: 2rem;
}
/* /*
STATE STATE
*/ */
@ -482,8 +620,18 @@ code {
gap: clamp(2rem, 5vw, 4rem); gap: clamp(2rem, 5vw, 4rem);
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
.state-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: clamp(1.75rem, 3.5vw, 2.75rem);
margin-bottom: 2.5rem;
}
@media (max-width: 960px) {
.state-grid-3 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 720px) { @media (max-width: 720px) {
.state-grid { grid-template-columns: 1fr; } .state-grid { grid-template-columns: 1fr; }
.state-grid-3 { grid-template-columns: 1fr; }
} }
.state-block h3 { .state-block h3 {