# Julien Béranger — complete blog Source: https://julienberanger.com Posts: 19 Generated: 2026-09-04T23:12:12.690Z Each post below is preceded by its canonical URL and its frontmatter. --- title: Embedding "JB" in a text: two watermarking schemes date: 2026-09-04 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/watermarking-schemes --- # Embedding "JB" in a text: two watermarking schemes Since August 2026, Anthropic marks the text Claude produces. Céleste Chrétien's [LinkedIn post](https://lnkd.in/p/eXi3sdDq) laid out the mechanism clearly: a statistically detectable bias in token choice, invisible to a reader, recoverable by anyone holding the key. This is a companion piece that builds the thing and measures it. Two schemes, same source paragraph: - **Scheme A — steganographic payload.** "JB" is the *message*, hidden as 16 bits. Exact recovery, zero robustness. - **Scheme B — keyed statistical watermark.** "JB" is the *key*. Nothing is recovered from the text, only a z-score. This is the family Anthropic and Google actually deploy. Everything is TypeScript, self-contained, no dependencies. Every number below was produced by the code in §6. ``` node --experimental-strip-types run.ts ``` --- ## 0. What Anthropic announced, and why Anthropic confirmed in an updated support page that Claude's output carries an invisible watermark, [as reported by TechCrunch](https://techcrunch.com/2026/08/11/anthropic-says-it-will-watermark-text-generated-by-its-ai-models/) on 11 August. The [help centre documentation](https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content) is explicit that the mark lives in the text rather than in the file: it survives copy-paste, and may survive some editing. It is applied at the model level, so it is present whichever surface the text comes from — claude.ai, the API, Claude Code, Cowork. Files get [C2PA](https://c2pa.org/) provenance metadata instead, which is a different mechanism with a different failure mode: metadata is stripped by screenshots and format conversions, as [C2PA Viewer's breakdown](https://c2paviewer.com/articles/claude-watermark-c2pa) sets out. [Forbes](https://www.forbes.com/sites/anishasircar/2026/08/13/claude-will-now-leave-a-watermark-on-everything-it-writes-what-does-that-mean/) describes the text mechanism as a subtle bias in word choice, detectable across enough content — which is exactly the construction below. At least one [tracker](https://www.wasitaigenerated.com/research/anthropic-claude-watermarking) reads the disclosed method as a variant of SynthID-Text. ### The regulation The trigger is [Article 50 of the EU AI Act](https://artificialintelligenceact.eu/article/50/). Paragraph 2 requires providers of generative systems — audio, image, video *or text* — to mark outputs in a machine-readable format and make them detectable as artificially generated, with technical solutions that are effective, interoperable, robust and reliable. The obligations took effect on 2 August 2026. The Commission published its [finalised guidelines](https://digital-strategy.ec.europa.eu/en/faqs/transparency-obligations-under-article-50-ai-act) on 20 July 2026 alongside a Code of Practice on Transparency of AI-Generated Content; [Paul Weiss](https://www.paulweiss.com/insights/client-memos/eu-finalises-transparency-rules-for-ai-generated-content) and [Herbert Smith Freehills Kramer](https://www.hsfkramer.com/notes/ip/2026-03/transparency-obligations-for-ai-generated-content-under-the-eu-ai-act-from-principle-to-practice) both have readable summaries, and [Orrick's guide](https://www.orrick.com/en/Insights/2026/08/EU-AI-Act-Transparency-Obligations-for-AI-Generated-Content-Article-50) goes furthest into the operational detail. Two details from that guidance matter for what follows. The Code distinguishes *free-form text* — a raw character sequence with no container, as in a chat window — from *containerised text* like PDF or DOCX, which can carry metadata and therefore falls under a different marking regime. And it defines "very short text" as anything under 200 tokens. That threshold is not arbitrary, and §3.5 shows why. Meanwhile the [AI Omnibus provisional agreement](https://artificialintelligenceact.eu/transparency-rules-article-50/) of May 2026 gives systems already on the market before 2 August until 2 December 2026 to comply — which is the legal shape of Anthropic's "older models will be retrofitted". ### Three limits worth stating plainly **It is a processing signal, not an authorship signal.** Anthropic's limitations section says a detected mark means the content may have passed through Claude — not that Claude wrote the ideas, text or data. A mark can appear after proofreading, translation, summarisation or file conversion. And the absence of a mark proves nothing either. **The detector does not exist yet.** [MLQ's summary](https://mlq.ai/news/anthropic-will-watermark-claude-text-worldwide-and-attach-c2pa-metadata-to-files/) notes that the statistical construction, the minimum reliable text length, and the measured detection and false-positive rates are all unpublished. Without the key, third-party detectors are still guessing from stylistic tics — the "not X, but Y" school of AI detection, which is [not a detector](https://www.blogdumoderateur.com/watermark-claude-anthropic-reponses/) so much as a vibe. **It is global, not European.** Anthropic applies the watermark everywhere because it has no durable technical mechanism to limit it to the EU; connecting from outside Europe changes nothing about the output. Alain Goudey's [critique](https://alain.goudey.eu/side/2026/08/18/watermarking-texte-ia-une-avancee-technique-serieuse-mais-un-piege-semantique-et-institutionnel/) makes the sharpest version of the objection: a regional norm becomes a worldwide product property, and that is a political choice presented as a technical constraint. French coverage from [Next](https://next.ink/251192/anthropic-va-tatouer-les-textes-et-les-images-generes-par-claude/), [KultureGeek](https://kulturegeek.fr/news-356838/anthropic-integrer-watermark-invisible-textes-generes-ia) and [Blog du Modérateur](https://www.blogdumoderateur.com/claude-ajoute-filigrane-textes-generes-pour-identifier/) covers the rollout; [Medianama](https://www.medianama.com/2026/08/223-anthropic-watermark-c2pa-metadata-claude/) raises the anti-circumvention question that the announcement leaves open. ### The prior art The green/red construction in §3 is [Kirchenbauer et al., *A Watermark for Large Language Models*](https://arxiv.org/abs/2301.10226) (ICML 2023): partition the vocabulary into "green" and "red" lists seeded on preceding tokens, softly promote green during sampling, detect with a z-test that yields interpretable p-values. Google's production system is [SynthID-Text](https://www.nature.com/articles/s41586-024-08025-4), published in *Nature* in October 2024 by Dathathri et al., which replaces the soft bias with a mechanism called Tournament Sampling and was validated on roughly 20 million live Gemini responses — the first deployment of a text watermark at that scale. It is [open source](https://github.com/google-deepmind/synthid-text). The steganographic cousin in §2 is the same trick with the keys held by different people. Norelli and Bronstein's [*LLMs can hide text in other text of the same length*](https://arxiv.org/abs/2510.20075) is the sharpest recent statement of it: their Calgacus protocol ([code](https://github.com/noranta4/calgacus)) encodes one meaningful text inside another meaningful text of identical length, using an 8B open model on a laptop. Their framing — a radical decoupling of text from authorial intent — is the right lens for the whole topic. --- ## 1. INPUT — source text, identical for both schemes > Stats. ETH is trading around $2,511, up roughly 5% on the day and extending well past yesterday's ~$2,386 level — the move has it approaching its 200-day EMA with a 50/100-day EMA convergence some traders are reading as a bullish setup. The validator set kept climbing: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of supply, up from 34.99%). The entry queue grew to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue stayed calm at 256 ETH with a 6-minute wait, confirming yesterday's brief exit-queue spike was a one-day blip. Gas ticked up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 flipped back to a $2.9 million net inflow; cumulative net inflows since launch stand at roughly $13.02 billion. 154 whitespace tokens, 128 of them alphabetic. Every figure is immovable. That last constraint turns out to matter more than anything else here. --- ## 2. Scheme A — "JB" as a hidden payload ### 2.1 Construction **Payload.** `J` = 0x4A, `B` = 0x42 → `01001010 01000010`, 16 bits. **Carrier rule.** One bit per designated word, encoded as the parity of its letter count: even → `0`, odd → `1`. Non-letters are stripped first, so `$2,511,` and `36-day,` can carry nothing. **Key.** The ordered list of the 16 carrier positions — whitespace-token indices into the *output*. ### 2.2 OUTPUT — Scheme A Five words changed, one inserted, no figure touched. Changes in **bold**: > Stats. ETH is trading around $2,511, up roughly 5% on the day and **climbing** well past yesterday's ~$2,386 level — the move has it approaching its 200-day EMA with a 50/100-day EMA convergence some traders are reading as a bullish **signal**. The validator set kept **growing**: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of **circulating** supply, up from 34.99%). The entry queue grew to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue stayed **quiet** at 256 ETH with a 6-minute wait, confirming yesterday's brief exit-queue spike was a one-day blip. Gas ticked up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 flipped back to a $2.9 million net inflow; cumulative net inflows since launch stand at roughly $13.02 billion. Only 5 of the 16 carriers needed editing — the other 11 already had the right parity by chance. In a text this long a large fraction of arbitrary bits are already satisfied, so a short payload is cheap to embed. ### 2.3 How to retrieve "JB" ```ts export const KEY_A = [4, 7, 13, 40, 45, 47, 59, 67, 75, 80, 87, 88, 98, 110, 116, 143]; const letters = (t: string) => t.replace(/[^A-Za-z]/g, ""); export function decodeA(text: string, key: number[] = KEY_A) { const toks = text.split(/\s+/); const bits = key.map((p) => letters(toks[p]).length % 2).join(""); const payload = (bits.match(/.{8}/g) ?? []) .map((b) => String.fromCharCode(parseInt(b, 2))) .join(""); return { bits, payload }; } ``` Full trace: | # | idx | token | letters | len | bit | |---|-----|-------|---------|-----|-----| | 1 | 4 | around | around | 6 | 0 | | 2 | 7 | roughly | roughly | 7 | 1 | | 3 | 13 | climbing | climbing | 8 | 0 | | 4 | 40 | signal. | signal | 6 | 0 | | 5 | 45 | growing: | growing | 7 | 1 | | 6 | 47 | active | active | 6 | 0 | | 7 | 59 | circulating | circulating | 11 | 1 | | 8 | 67 | grew | grew | 4 | 0 | | 9 | 75 | wait. | wait | 4 | 0 | | 10 | 80 | quiet | quiet | 5 | 1 | | 11 | 87 | wait, | wait | 4 | 0 | | 12 | 88 | confirming | confirming | 10 | 0 | | 13 | 98 | ticked | ticked | 6 | 0 | | 14 | 110 | move. | move | 4 | 0 | | 15 | 116 | billion, | billion | 7 | 1 | | 16 | 143 | inflow; | inflow | 6 | 0 | `01001010` = 74 = `J`, `01000010` = 66 = `B`. ### 2.4 Fragility, measured The same key applied to the *unmarked* original: ``` watermarked -> { bits: "0100101001000010", payload: "JB" } original -> { bits: "0111000010000000", payload: "p\x80" } // garbage ``` No false positive — good. But the failure mode is total. Inserting the single word `circulating` at index 59 shifted every later index by one, so all seven subsequent carriers land on the wrong word. **One inserted word destroys the payload.** Translation destroys it completely. There is no partial credit and no confidence interval: you get "JB" or you get nothing. This is why the literature on *multi-bit* watermarking — carrying an actual message rather than a presence signal — works so hard on invariant features. [Yoo et al.](https://arxiv.org/abs/2305.01904) anchor payload positions to syntactic dependencies rather than raw indices, precisely so that inserting a word does not shift everything downstream. Naive positional keying, as above, is the thing you build first and then throw away. --- ## 3. Scheme B — "JB" as a key (green/red) The Kirchenbauer / SynthID-Text construction, applied post-hoc. Here **"JB" is no longer the message — it is the watermarking key.** Nothing is recovered from the text; a detector holding the key computes a statistic. ### 3.1 Construction 1. **Context-seeded PRF.** For each word, hash `key ‖ previous_word ‖ candidate_word`. The low bit splits the vocabulary in half: `1` = green, `0` = red. γ = 0.5. 2. **Bias.** A real LLM adds a logit boost δ to green tokens at sampling time. Post-hoc, we instead *choose* green synonyms. 3. **Detection.** Count green words G out of N scorable words, then `z = (G − γN) / √(Nγ(1−γ))`, and a one-sided p-value. ```ts import { createHash } from "node:crypto"; export const KEY_B = "JB"; const GAMMA = 0.5; const norm = (t: string) => t.toLowerCase().replace(/[^a-z]/g, ""); const scorable = (t: string) => norm(t).length >= 1 && !/\d/.test(t); /** Seed the PRF on the PREVIOUS word, then hash the candidate. Green iff LSB === 1. */ export function isGreen(prev: string, tok: string, key = KEY_B): boolean { const h = createHash("sha256").update(`${key}|${norm(prev)}|${norm(tok)}`).digest(); return (h[0] & 1) === 1; } export function score(text: string, key = KEY_B) { const toks = text.split(/\s+/); let n = 0, g = 0; for (let i = 1; i < toks.length; i++) { if (!scorable(toks[i])) continue; // numbers offer no choice, so no signal n++; if (isGreen(toks[i - 1], toks[i], key)) g++; } const z = (g - GAMMA * n) / Math.sqrt(n * GAMMA * (1 - GAMMA)); return { n, green: g, frac: g / n, z, p: 0.5 * erfc(z / Math.SQRT2) }; } ``` Node has no `erfc`, so: ```ts function erfc(x: number): number { // Numerical Recipes 6.2.2 — accurate to ~1.2e-7 const z = Math.abs(x); const t = 1 / (1 + z / 2); const poly = -1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277)))))))); const r = t * Math.exp(-z * z + poly); return x >= 0 ? r : 2 - r; } ``` One-word context is the crudest possible seeding. Real schemes hash over an n-gram window, and there is a whole design space here: [Kuditipudi et al.](https://arxiv.org/abs/2307.15593) use a fixed key sequence to get distortion-free watermarks, while [Liu et al.](https://arxiv.org/pdf/2310.06356) seed on *semantic* embeddings of the preceding text rather than surface tokens, so that paraphrase preserves the seed. That last idea is the direct answer to the attack in §3.4. ### 3.2 The interaction problem — why this needs a search Because the PRF is seeded on the *previous* word, substituting word *i* re-rolls the green/red assignment of word *i+1*. Choices are not independent, so greedy left-to-right substitution is suboptimal. A beam search over a table of meaning-preserving candidates: ```ts export function rewrite(text: string, syn = SYNONYMS, key = KEY_B, beam = 40): string { const toks = text.split(/\s+/); let states: [number, string[]][] = [[0, [...toks]]]; // [greenCount, tokens] for (let i = 1; i < toks.length; i++) { const cands = syn[i] ?? [toks[i]]; const next: [number, string[]][] = []; for (const [g, tk] of states) { for (const c of cands) { const t2 = [...tk]; t2[i] = c; let add = 0, prev = t2[i - 1]; for (const part of c.split(" ")) { // candidates may be multi-word if (scorable(part) && isGreen(prev, part, key)) add++; prev = part; } next.push([g + add, t2]); } } next.sort((a, b) => b[0] - a[0]); states = next.slice(0, beam); } return states[0][1].join(" "); } ``` The candidate table — 26 positions, numbers excluded by construction: ```ts export const SYNONYMS: Record = { 3: ["trading", "changing hands", "quoted"], 4: ["around", "near", "about", "close to"], 7: ["roughly", "about", "some", "nearly"], 13: ["extending", "pushing", "climbing", "rising", "stretching"], 14: ["well", "clearly", "comfortably"], 18: ["level", "close", "print", "mark"], 24: ["approaching", "nearing", "testing", "closing on"], 32: ["convergence", "crossover", "cluster"], 34: ["traders", "desks", "chartists", "analysts"], 36: ["reading", "treating"], 39: ["bullish", "constructive", "positive"], 40: ["setup.", "signal.", "pattern.", "structure."], 44: ["kept", "carried on", "continued"], 45: ["climbing:", "growing:", "rising:", "expanding:"], 66: ["grew", "expanded", "swelled", "widened"], 78: ["stayed", "remained", "held"], 79: ["calm", "quiet", "flat", "subdued"], 87: ["confirming", "showing", "suggesting", "indicating"], 89: ["brief", "short", "one-off", "momentary"], 97: ["ticked", "edged", "crept", "nudged"], 112: ["rose", "climbed", "increased", "advanced"], 116: ["tracking", "following", "mirroring", "in line with"], 125: ["revised", "restated", "adjusted"], 135: ["flipped", "swung", "turned", "reversed"], 143: ["cumulative", "total", "aggregate"], 148: ["stand", "sit", "now stand"], }; ``` Slot 36 is worth a note. `["reading", "flagging", "calling"]` scored higher, but the sentence reads *"traders are ___ as a bullish setup"*, and `calling as` is ungrammatical. The optimizer maximises green count, not English, so the candidate lists have to be pre-filtered by hand. A production system does this inside the decoder, where the language model's own probabilities keep the output fluent for free — which is exactly the quality argument SynthID's authors make, backed by human side-by-side ratings showing no perceived degradation. ### 3.3 OUTPUT — Scheme B Substitutions **bold**. These are *different* words from Scheme A: different scheme, different key, different choices. > Stats. ETH is **changing hands** around $2,511, up roughly 5% on the day and **rising** well past yesterday's ~$2,386 **mark** — the move has it **testing** its 200-day EMA with a 50/100-day EMA **cluster** some traders are **treating** as a **constructive signal**. The validator set kept climbing: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of supply, up from 34.99%). The entry queue **swelled** to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue **remained flat** at 256 ETH with a 6-minute wait, **suggesting** yesterday's **one-off** exit-queue spike was a one-day blip. Gas **edged** up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 **swung** back to a $2.9 million net inflow; cumulative net inflows since launch **now** stand at roughly $13.02 billion. Chosen: `trading→changing hands`, `extending→rising`, `level→mark`, `approaching→testing`, `convergence→cluster`, `reading→treating`, `bullish→constructive`, `setup→signal`, `grew→swelled`, `stayed→remained`, `calm→flat`, `confirming→suggesting`, `brief→one-off`, `ticked→edged`, `flipped→swung`, `stand→now stand`. ### 3.4 Detection results | Text | green / N | fraction | z | p | |------|-----------|----------|---|---| | Original (unmarked) | 71 / 128 | 55.5% | +1.24 | 0.11 | | **Watermarked B** | **88 / 130** | **67.7%** | **+4.03** | **2.7e-5** | | Watermarked B, first sentence only | 13 / 16 | 81.3% | +2.50 | 0.0062 | | + 1 word in 6 deleted | 72 / 107 | 67.3% | +3.58 | 0.00017 | | + full paraphrase attack | 78 / 130 | 60.0% | +2.28 | 0.011 | The paraphrase attack re-rolls every one of the 26 swappable slots at random, key-blind — a stand-in for "rewrite it with another model", which is the canonical attack since [Krishna et al.](https://arxiv.org/abs/2303.13408) showed paraphrasing evades detectors (and that retrieval is a decent defence). **False-positive check** — the *watermarked* text scored under wrong keys: | key | z | p | |-----|---|---| | `AA` | +1.05 | 0.15 | | `XY` | −0.18 | 0.57 | | `CC` | −0.18 | 0.57 | | `ZZ` | −0.70 | 0.76 | | `QW` | +0.53 | 0.30 | The signal exists only under the right key. Note the unmarked original already sits at 55.5% / z = +1.24 — natural text drifts off 50% by chance, which is exactly why detection needs a threshold and a p-value rather than a yes/no. ### 3.5 What the numbers say - **Graceful degradation, not cliff-edge failure.** The paraphrase attack took z from 4.03 to 2.28 — weakened, still above the unmarked baseline. Scheme A would already be dead. This is the whole reason production systems use statistics rather than payloads. - **Deletion barely hurts, and the reason is instructive.** Dropping one word in six only corrupts the *context* of the following word, so roughly 5/6 of the (prev, word) pairs survive intact and z only falls to 3.58. An attack has to change words, not remove them. [Yang et al.](https://arxiv.org/pdf/2305.08883) report the same shape under systematic attack: word deletion below about 30% leaves the watermark detectable, and beyond that the text is unusable anyway. - **Length is the binding constraint.** The first sentence alone hit 81.3% green — a stronger *rate* — but with only 16 scorable words the p-value is 0.0062 rather than 2.7e-5. Evidence accumulates with N. This is why short samples are undetectable: not because the bias is absent, but because there was never enough room for it to show. It is also, presumably, why the EU Code of Practice draws its "very short text" line at 200 tokens. - **Factual text is hostile terrain, and the vendors admit it.** 26 of the 154 tokens here are figures, carrying no signal at all. Google's own [SynthID documentation](https://ai.google.dev/responsible/docs/safeguards/synthid) states the point directly: watermarking is less effective on factual responses, because there is less room to vary generation without hurting accuracy. The "capitale de la France est…" problem, at production scale. A market-data paragraph is close to the worst case. - **Post-hoc editing is a weak proxy for generation-time biasing.** A real model biases *every* token as it samples. Here only 26 positions out of 128 were reachable with a hand-written thesaurus, and each edit risks the meaning or the grammar (see §3.2). Doing it inside the decoder is both cheaper and stronger — and it is the difference between the two families of methods that the [text-watermarking survey](https://arxiv.org/pdf/2312.07913) draws: post-processing schemes (Unicode homoglyphs, lexical substitution, reformatting) are easy to implement and easy to normalise away; logit-level schemes are stabler and harder to strip. - **None of this is provably robust.** [Zhang et al., *Watermarks in the Sand*](https://arxiv.org/abs/2311.04378) prove that under natural assumptions — an attacker with a quality oracle and a paraphrasing model — *strong* watermarking is impossible, and this holds even when detection is private and key-based. [Sadasivan et al.](https://arxiv.org/html/2303.11156) reach a compatible conclusion from the detection side. The honest framing is that watermarking raises cost for the careless, not that it defeats the motivated. Anthropic's documentation says roughly this; the press coverage sometimes does not. --- ## 4. Side-by-side | | Scheme A (payload) | Scheme B (green/red) | Production SynthID-Text | |---|---|---|---| | Role of "JB" | the hidden message | the secret key | the key (provider-held) | | Where the bias lives | post-hoc word substitution | post-hoc word substitution | sampling-time logit bias | | Detector output | exact 16-bit string | z-score + p-value | z-score + p-value | | Capacity | ~1 bit per carrier | 0 bits — presence only | 0 bits — presence only | | Survives 1 inserted word | ✗ total loss | ✓ | ✓ | | Survives paraphrase | ✗ | partial (z 4.03 → 2.28) | partial | | Survives translation | ✗ | ✗ | ✗ (Google documents this) | | Detectable without key | ✗ | ✗ | ✗ | | Needs a long text | no (needs 16 clean slots) | yes | yes | The asymmetry worth holding onto: a 16-bit payload needs 16 *correct* slots, so any loss is fatal. A statistical watermark spends hundreds of tokens accumulating evidence, and can therefore afford to lose many of them. --- ## 5. Two consequences **Individualised watermarking.** Nothing in Scheme B changes if `KEY_B` is per-account rather than per-provider. That is the point, and it is why the traceability concern is technically well-founded rather than speculative — the machinery is identical, only key custody differs. The wrong-key table shows the flip side, though: with many accounts you are running many hypothesis tests at once, and multiple-comparison correction gets brutal fast. This has been studied directly — [*Multi-use LLM Watermarking and the False Detection Problem*](https://arxiv.org/pdf/2506.15975) works through what happens to false-positive rates when one deployment carries many keys. Identifying one user among 10⁶ from a z ≈ 4 signal is not the same problem as answering "did Claude touch this", and conflating the two is how a provenance tool becomes a surveillance tool by accident. **Code.** Scheme B transfers to code with the same machinery — identifier names, statement order, formatting — and there is a literature on it (Lee et al., *Who Wrote this Code? Watermarking for Code Generation*, arXiv:2305.15060). But the constraint is harder than prose. Code has a compiler: a bad substitution is not a stylistic wobble, it is a build failure. The §3.2 grammar problem, with teeth. The upside is real though: 300,000 lines is an enormous N, so even a very weak per-token bias reaches significance, and attributing a codebase to a provider stops being far-fetched. Whether that is a licensing question, a provenance feature or a liability depends entirely on who is asking. --- ## 6. Full source Two files. Node 22+ only, no dependencies. ### `wm.ts` ```ts import { createHash } from "node:crypto"; export const ORIGINAL = "Stats. ETH is trading around $2,511, up roughly 5% on the day and extending well past yesterday's ~$2,386 level — the move has it approaching its 200-day EMA with a 50/100-day EMA convergence some traders are reading as a bullish setup. The validator set kept climbing: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of supply, up from 34.99%). The entry queue grew to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue stayed calm at 256 ETH with a 6-minute wait, confirming yesterday's brief exit-queue spike was a one-day blip. Gas ticked up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 flipped back to a $2.9 million net inflow; cumulative net inflows since launch stand at roughly $13.02 billion."; export const WATERMARKED_A = "Stats. ETH is trading around $2,511, up roughly 5% on the day and climbing well past yesterday's ~$2,386 level — the move has it approaching its 200-day EMA with a 50/100-day EMA convergence some traders are reading as a bullish signal. The validator set kept growing: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of circulating supply, up from 34.99%). The entry queue grew to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue stayed quiet at 256 ETH with a 6-minute wait, confirming yesterday's brief exit-queue spike was a one-day blip. Gas ticked up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 flipped back to a $2.9 million net inflow; cumulative net inflows since launch stand at roughly $13.02 billion."; /* ============ Scheme A — payload ============ */ export const KEY_A = [4, 7, 13, 40, 45, 47, 59, 67, 75, 80, 87, 88, 98, 110, 116, 143]; const letters = (t: string) => t.replace(/[^A-Za-z]/g, ""); export function decodeA(text: string, key: number[] = KEY_A) { const toks = text.split(/\s+/); const bits = key.map((p) => letters(toks[p]).length % 2).join(""); const payload = (bits.match(/.{8}/g) ?? []) .map((b) => String.fromCharCode(parseInt(b, 2))) .join(""); return { bits, payload }; } /* ============ Scheme B — green/red ============ */ export const KEY_B = "JB"; const GAMMA = 0.5; const norm = (t: string) => t.toLowerCase().replace(/[^a-z]/g, ""); const scorable = (t: string) => norm(t).length >= 1 && !/\d/.test(t); export function isGreen(prev: string, tok: string, key = KEY_B): boolean { const h = createHash("sha256").update(`${key}|${norm(prev)}|${norm(tok)}`).digest(); return (h[0] & 1) === 1; } function erfc(x: number): number { const z = Math.abs(x); const t = 1 / (1 + z / 2); const poly = -1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277)))))))); const r = t * Math.exp(-z * z + poly); return x >= 0 ? r : 2 - r; } export function score(text: string, key = KEY_B) { const toks = text.split(/\s+/); let n = 0, g = 0; for (let i = 1; i < toks.length; i++) { if (!scorable(toks[i])) continue; n++; if (isGreen(toks[i - 1], toks[i], key)) g++; } const z = (g - GAMMA * n) / Math.sqrt(n * GAMMA * (1 - GAMMA)); return { n, green: g, frac: g / n, z, p: 0.5 * erfc(z / Math.SQRT2) }; } export const SYNONYMS: Record = { 3: ["trading", "changing hands", "quoted"], 4: ["around", "near", "about", "close to"], 7: ["roughly", "about", "some", "nearly"], 13: ["extending", "pushing", "climbing", "rising", "stretching"], 14: ["well", "clearly", "comfortably"], 18: ["level", "close", "print", "mark"], 24: ["approaching", "nearing", "testing", "closing on"], 32: ["convergence", "crossover", "cluster"], 34: ["traders", "desks", "chartists", "analysts"], 36: ["reading", "treating"], 39: ["bullish", "constructive", "positive"], 40: ["setup.", "signal.", "pattern.", "structure."], 44: ["kept", "carried on", "continued"], 45: ["climbing:", "growing:", "rising:", "expanding:"], 66: ["grew", "expanded", "swelled", "widened"], 78: ["stayed", "remained", "held"], 79: ["calm", "quiet", "flat", "subdued"], 87: ["confirming", "showing", "suggesting", "indicating"], 89: ["brief", "short", "one-off", "momentary"], 97: ["ticked", "edged", "crept", "nudged"], 112: ["rose", "climbed", "increased", "advanced"], 116: ["tracking", "following", "mirroring", "in line with"], 125: ["revised", "restated", "adjusted"], 135: ["flipped", "swung", "turned", "reversed"], 143: ["cumulative", "total", "aggregate"], 148: ["stand", "sit", "now stand"], }; export function rewrite(text: string, syn = SYNONYMS, key = KEY_B, beam = 40): string { const toks = text.split(/\s+/); let states: [number, string[]][] = [[0, [...toks]]]; for (let i = 1; i < toks.length; i++) { const cands = syn[i] ?? [toks[i]]; const next: [number, string[]][] = []; for (const [g, tk] of states) { for (const c of cands) { const t2 = [...tk]; t2[i] = c; let add = 0, prev = t2[i - 1]; for (const part of c.split(" ")) { if (scorable(part) && isGreen(prev, part, key)) add++; prev = part; } next.push([g + add, t2]); } } next.sort((a, b) => b[0] - a[0]); states = next.slice(0, beam); } return states[0][1].join(" "); } ``` ### `run.ts` ```ts import { ORIGINAL, WATERMARKED_A, decodeA, score, rewrite, SYNONYMS } from "./wm.ts"; const f = (x: number) => (x >= 0 ? "+" : "") + x.toFixed(2); console.log("A: watermarked ->", JSON.stringify(decodeA(WATERMARKED_A))); console.log("A: original ->", JSON.stringify(decodeA(ORIGINAL))); const wm = rewrite(ORIGINAL); /** Key-blind attacker: re-roll every swappable slot at random. */ function attack(text: string, seed = 7): string { const t = text.split(" "); const o = ORIGINAL.split(" "); let s = seed; const rnd = () => (s = (s * 1103515245 + 12345) % 2147483648) / 2147483648; for (const k of Object.keys(SYNONYMS).map(Number)) { const cands = SYNONYMS[k]; const i = t.findIndex((x) => x === o[k]); const j = t.indexOf(cands.find((c) => t.includes(c.split(" ")[0])) ?? ""); const idx = i >= 0 ? i : j; if (idx >= 0) t[idx] = cands[Math.floor(rnd() * cands.length)]; } return t.join(" "); } const rows: [string, string][] = [ ["ORIGINAL (unmarked)", ORIGINAL], ["WATERMARKED_B", wm], [" first sentence only", wm.split("—")[0]], [" + 1 word in 6 deleted", wm.split(" ").filter((_, i) => i % 6 !== 0).join(" ")], [" + full paraphrase attack", attack(wm)], ]; console.log("\n--- detection (key JB) ---"); for (const [name, t] of rows) { const s = score(t); console.log( ` ${name.padEnd(28)} ${String(s.green).padStart(3)}/${String(s.n).padEnd(4)} ` + `${(s.frac * 100).toFixed(1)}% z=${f(s.z)} p=${s.p.toExponential(1)}` ); } console.log("\n--- wrong keys on watermarked text ---"); for (const k of ["AA", "XY", "CC", "ZZ", "QW"]) { const s = score(wm, k); console.log(` ${k} z=${f(s.z)} p=${s.p.toFixed(2)}`); } console.log("\n" + wm); ``` --- ## Further reading ### Foundational papers - Kirchenbauer, Geiping, Wen, Katz, Miers, Goldstein — [*A Watermark for Large Language Models*](https://arxiv.org/abs/2301.10226), ICML 2023. The green/red list construction and the z-test used above. - Dathathri et al. — [*Scalable watermarking for identifying large language model outputs*](https://www.nature.com/articles/s41586-024-08025-4), *Nature* 634, 818–823 (2024). SynthID-Text and Tournament Sampling. Also on [PubMed](https://pubmed.ncbi.nlm.nih.gov/39443777/) and [PMC](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC11499265/); reference implementation on [GitHub](https://github.com/google-deepmind/synthid-text). - Kuditipudi, Thickstun, Hashimoto, Liang — [*Robust Distortion-Free Watermarks for Language Models*](https://arxiv.org/abs/2307.15593). - Liu, Pan, Hu, Meng, Wen — [*A Semantic Invariant Robust Watermark for Large Language Models*](https://arxiv.org/pdf/2310.06356). Seeds on semantics rather than surface tokens. - Liu et al. — [*A Survey of Text Watermarking in the Era of Large Language Models*](https://arxiv.org/pdf/2312.07913), *ACM Computing Surveys* 57(2). ### Limits and attacks - Zhang, Edelman, Francati, Venturi, Ateniese, Barak — [*Watermarks in the Sand: Impossibility of Strong Watermarking for Generative Models*](https://arxiv.org/abs/2311.04378), ICML 2024 ([ACM DL](https://dl.acm.org/doi/10.5555/3692070.3694499)). - Sadasivan, Kumar, Balasubramanian, Wang, Feizi — [*Can AI-Generated Text be Reliably Detected?*](https://arxiv.org/html/2303.11156) - Krishna, Song, Karpinska, Wieting, Iyyer — [*Paraphrasing Evades Detectors of AI-Generated Text, but Retrieval is an Effective Defense*](https://arxiv.org/abs/2303.13408), NeurIPS 2023. - Yang et al. — [*Watermarking Text Generated by Black-Box Language Models*](https://arxiv.org/pdf/2305.08883). Systematic robustness results under re-translation, deletion and synonym substitution. - Han, Li et al. — [*Robustness Assessment and Enhancement of Text Watermarking for Google's SynthID*](https://arxiv.org/pdf/2508.20228). Back-translation vulnerability scales with MT quality. - [*Multi-use LLM Watermarking and the False Detection Problem*](https://arxiv.org/pdf/2506.15975). Directly relevant to per-user keys. ### Steganography - Norelli & Bronstein — [*LLMs can hide text in other text of the same length*](https://arxiv.org/abs/2510.20075), plus the [Calgacus implementation](https://github.com/noranta4/calgacus). - Wu, Wu, Xue, Wen, Peng — [*Generative Text Steganography with Large Language Model*](https://arxiv.org/abs/2404.10229) (LLM-Stega), a black-box approach through public chat interfaces. ### Regulation and standards - [Article 50, EU AI Act](https://artificialintelligenceact.eu/article/50/) — the operative text. - [European Commission FAQ on Article 50 transparency obligations](https://digital-strategy.ec.europa.eu/en/faqs/transparency-obligations-under-article-50-ai-act). - [A Practical Guide to Article 50](https://artificialintelligenceact.eu/transparency-rules-article-50/), including the AI Omnibus timing changes. - [Orrick](https://www.orrick.com/en/Insights/2026/08/EU-AI-Act-Transparency-Obligations-for-AI-Generated-Content-Article-50), [Paul Weiss](https://www.paulweiss.com/insights/client-memos/eu-finalises-transparency-rules-for-ai-generated-content) and [Herbert Smith Freehills Kramer](https://www.hsfkramer.com/notes/ip/2026-03/transparency-obligations-for-ai-generated-content-under-the-eu-ai-act-from-principle-to-practice) on the Guidelines and Code of Practice. - [C2PA](https://c2pa.org/) — [Content Credentials](https://contentcredentials.org/), the [technical specification](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html) and the [conformance programme](https://c2pa.org/conformance/). ### The Claude announcement - [Anthropic — How Claude marks AI-generated content](https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content) (primary source). - [Google — SynthID documentation](https://ai.google.dev/responsible/docs/safeguards/synthid) (limitations section). - [TechCrunch](https://techcrunch.com/2026/08/11/anthropic-says-it-will-watermark-text-generated-by-its-ai-models/) · [Forbes](https://www.forbes.com/sites/anishasircar/2026/08/13/claude-will-now-leave-a-watermark-on-everything-it-writes-what-does-that-mean/) · [MLQ](https://mlq.ai/news/anthropic-will-watermark-claude-text-worldwide-and-attach-c2pa-metadata-to-files/) · [Medianama](https://www.medianama.com/2026/08/223-anthropic-watermark-c2pa-metadata-claude/) · [Interesting Engineering](https://interestingengineering.com/ai-robotics/anthropic-claude-text-invisible-watermarks) · [Cyberpress](https://cyberpress.org/anthropic-adds-invisible-watermarks-c2pa-metadata/) - French coverage: [Next](https://next.ink/251192/anthropic-va-tatouer-les-textes-et-les-images-generes-par-claude/) · [KultureGeek](https://kulturegeek.fr/news-356838/anthropic-integrer-watermark-invisible-textes-generes-ia) · [Blog du Modérateur](https://www.blogdumoderateur.com/claude-ajoute-filigrane-textes-generes-pour-identifier/) and its [FAQ](https://www.blogdumoderateur.com/watermark-claude-anthropic-reponses/) - Critical takes: [Alain Goudey](https://alain.goudey.eu/side/2026/08/18/watermarking-texte-ia-une-avancee-technique-serieuse-mais-un-piege-semantique-et-institutionnel/) on the institutional risk · [C2PA Viewer](https://c2paviewer.com/articles/claude-watermark-c2pa) on why the two mechanisms shouldn't be conflated · [WasItAIGenerated](https://www.wasitaigenerated.com/research/anthropic-claude-watermarking) on what is actually verifiable today --- title: Ethereum Daily — September 4, 2026 date: 2026-09-04 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-09-04 --- # Ethereum Daily — September 4, 2026 # Ethereum Daily — September 4, 2026 Covering roughly September 3 – September 4, 2026. **Roadmap.** The All Core Devs Consensus call (ACDC #186) that was billed as the decision point for the Glamsterdam Sepolia fork date took place yesterday, September 3 — but no public readout or call summary has surfaced yet as of this morning's publishing, so whether the proposed September 28 Sepolia date (still targeting ePBS, Block-Level Access Lists, and a ~200M gas limit) was formally confirmed remains unconfirmed. Client-team preference lists for the fork's remaining scope are still due September 10, and mainnet activation stays pointed at Q4 2026. Worth checking back once a writeup lands. **Governance.** No material new development since yesterday. EIP-8141 (Frame Transactions) remains "Scheduled for Inclusion" for the Hegotá upgrade, and the interoperability question with the rival EIP-8130 proposal — set to launch on Base ahead of any mainnet account abstraction — is still unresolved. **Institutional and corporate adoption.** New today: Mint Incorporation (NASDAQ: MIMI) announced a binding consulting agreement with CURRENC Capital to pursue issuer-sponsored tokenization of its Class A ordinary shares on both Ethereum and Solana. Unlike derivative or synthetic stock tokens, Mint itself is the active participant authorizing the onchain placement, with voting rights and ownership structure unchanged. The company gave no timeline or specifics on what portion of shares would be tokenized, and cautioned that no trading market is guaranteed to develop. **Stats.** ETH is trading around $2,511, up roughly 5% on the day and extending well past yesterday's ~$2,386 level — the move has it approaching its 200-day EMA with a 50/100-day EMA convergence some traders are reading as a bullish setup. The validator set kept climbing: 905,998 active validators (up from 905,286), with 42.7 million ETH staked (35.03% of supply, up from 34.99%). The entry queue grew to 2,110,424 ETH at a 36-day, 15-hour wait. The exit queue stayed calm at 256 ETH with a 6-minute wait, confirming yesterday's brief exit-queue spike was a one-day blip. Gas ticked up to 0.078 gwei average (from 0.042 gwei) alongside the price move. DeFi TVL rose to $49.65 billion, tracking the ETH price gain. ETF flows: September 2 revised to a $48.2 million net outflow, but September 3 flipped back to a $2.9 million net inflow; cumulative net inflows since launch stand at roughly $13.02 billion. --- **Sources:** - [Glamsterdam Date: Sepolia Fork on September 28, 2026 — CryptoTicker](https://cryptoticker.io/en/ethereum-glamsterdam-date-sepolia-fork/) - [Mint Plans to Tokenize Class A Shares on Ethereum, Solana — Solana Compass](https://solanacompass.com/news/mint-nasdaq-mimi-to-tokenize-its-nasdaq-listed-shares-on-solana-and-ethereum) - [Ethereum ETF Flow (US$m) — Farside Investors](https://farside.co.uk/eth/) - [Ethereum Validator Queue](https://www.validatorqueue.com/) - [Ethereum chain overview — DefiLlama](https://defillama.com/chain/ethereum) - [Ethereum Gas Tracker — Etherscan](https://etherscan.io/gastracker) - [Ethereum price — CoinGecko](https://www.coingecko.com/en/coins/ethereum) - [Ethereum Approaches Key Bullish Crossover — CoinMarketCap AI](https://coinmarketcap.com/cmc-ai/ethereum/latest-updates/) --- title: Rukh — cartographie de conformité (marché scolaire français) date: 2026-09-04 lang: fr-FR author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/contrainte-scolaire --- # Rukh — cartographie de conformité (marché scolaire français) _Document de travail, 4 septembre 2026. Rédigé sans expertise juridique : à faire valider par un cabinet spécialisé avant la première signature._ Périmètre : vente d'un assistant pédagogique à base d'IA à des collèges et lycées français, sous forme de licence annuelle par établissement. --- ## A. Obligations légales, applicables dès le premier client ### 1. RGPD (règlement UE 2016/679) et loi Informatique et Libertés Rôles : l'EPLE est **responsable de traitement**, Strat est **sous-traitant**. | Référence | Obligation | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Art. 28 | Contrat de sous-traitance écrit : finalités, durée, instructions documentées, sort des données en fin de contrat. Sans lui, aucun traitement légal. | | Art. 28.2 et 28.4 | Liste des sous-traitants ultérieurs (hébergeur, fournisseur de modèle), autorisation écrite du responsable, information préalable de tout changement. Mistral et le VPS en font partie. | | Art. 30.2 | Registre des activités de traitement en tant que sous-traitant. L'exemption « moins de 250 salariés » ne joue pas : le traitement n'est pas occasionnel. | | Art. 32 | Sécurité : chiffrement, cloisonnement par établissement, gestion des accès, tests réguliers. | | Art. 33.2 | Notification de toute violation au responsable de traitement dans les meilleurs délais. À formaliser en procédure. | | Chapitre V (art. 44 à 49) | Transferts hors UE. Point de rupture actuel : Anthropic et OpenAI sont américains. Cadre de transfert valide et analyse d'impact du transfert, ou inférence UE. | | Art. 25 | Protection dès la conception. Se démontre par des choix techniques, pas par une politique de confidentialité. | | Art. 28.3 e) et f) | Assistance au responsable pour les droits des personnes et pour l'AIPD. | | Art. 35 | AIPD portée par l'EPLE, mais obligatoire ici (mineurs, technologie nouvelle, échelle). Sans dossier fourni par le prestataire, elle ne se fera pas. | DPO probablement non obligatoire au titre de l'art. 37, mais point de contact identifié nécessaire. > **Levier décisif** : si aucune donnée personnelle d'élève n'entre dans le système, une grande partie de ces obligations s'allège fortement. Le cadre d'usage l'exige déjà. À concevoir comme une impossibilité technique, pas comme une consigne. ### 2. AI Act (règlement UE 2024/1689) Statut : **fournisseur** d'un système d'IA mis sur le marché sous son propre nom. - **Art. 5 — pratiques interdites** (applicable depuis le 2 février 2025). Interdiction directement concernée : la reconnaissance des émotions dans les établissements d'enseignement. Jamais d'analyse d'engagement, d'attention ou d'expression faciale d'élèves, sous aucun habillage pédagogique. - **Art. 4 — littératie IA** (depuis février 2025). Niveau suffisant de compréhension chez ceux qui utilisent le système pour le compte du fournisseur. La ligne « formation » du devis y répond partiellement. - **Art. 50 — transparence** (depuis le 2 août 2026). Informer clairement l'utilisateur qu'il interagit avec une IA ; marquer les contenus générés dans un format lisible par machine. - **Annexe III, point 3 — éducation.** Bascule en haut risque si le système décide de l'accès ou de l'admission, évalue les acquis d'apprentissage, oriente vers un niveau, ou surveille les comportements interdits pendant les épreuves. Obligations reportées au 2 décembre 2027 par le règlement Digital Omnibus, mais contrainte de conception permanente. ### 3. Accessibilité — RGAA Article 47 de la loi 2005-102, décret 2019-768. L'obligation pèse sur l'organisme public mais sera répercutée contractuellement. Livrables : audit RGAA 4.1, déclaration d'accessibilité publiée, taux de conformité affiché. Un badge WCAG ne suffit pas — l'audit doit suivre la méthode RGAA. ### 4. Facturation - Chorus Pro obligatoire pour toute facture adressée à une entité publique. - Réforme française de la facturation électronique : capacité de réception exigée depuis le 1er septembre 2026. Calendrier à revérifier, il a déjà été décalé plusieurs fois. ### 5. Obligations générales d'entreprise CGV, mentions légales (LCEN), conditions d'utilisation. Assurance RC professionnelle : pas légalement obligatoire pour cette activité, mais systématiquement demandée dans les dossiers. ### 6. Licences open source LGPL-3.0 (rukh) et GPL-3.0 (rukh-ui). En SaaS, la copyleft n'est pas déclenchée. Dès qu'une instance est livrée à un établissement, c'est une distribution : droit au code source et droit de redistribution. Auditer aussi les licences des dépendances (SBOM), de plus en plus demandé. --- ## B. Non réglementaire, mais bloquant en pratique - **Cadre d'usage de l'IA en éducation** (juin 2025). Pas un texte de droit, mais opposable aux personnels. Ses deux exigences transversales — aucune donnée personnelle en transit, usage proportionné au coût environnemental — sont des critères d'achat de fait. - **Contrat d'adhésion GAR** : charte éthique, référentiel technique et de sécurité, annexe sous-traitance des données à caractère personnel. Hors GAR, chaque chef d'établissement assume seul le risque, ce que beaucoup refuseront. - **Recommandations CNIL sur les systèmes d'IA**, et fiche destinée aux enseignants mise à jour en juillet 2026. - **RGESN** (écoconception). Pas obligatoire, mais l'impact environnemental étant explicite dans le cadre d'usage, il apparaîtra dans les grilles d'évaluation. --- ## C. Ce qui ne s'applique probablement pas Utile pour ne pas surinvestir : - Qualification SecNumCloud : non exigible pour ce type de données. - NIS2 : ne couvre pas l'enseignement scolaire. - Directive accessibilité 2019/882 : vise les services grand public. - Régime haut risque de l'annexe III : hors périmètre tant que ni évaluation ni orientation ne sont touchées. --- ## Les quatre décisions qui règlent le plus de choses d'un coup 1. **Zéro donnée personnelle d'élève**, garanti techniquement. Allège le RGPD et l'AIPD, satisfait le cadre d'usage. 2. **Inférence en Union européenne par défaut.** Fait disparaître tout le chapitre V du RGPD. Les modèles américains deviennent une option hors périmètre scolaire. 3. **Ni notation, ni orientation, ni surveillance, ni reconnaissance d'émotions.** Maintient hors annexe III et hors article 5 de l'AI Act. 4. **Bannière IA et marquage des sorties.** Article 50, déjà applicable, peu coûteux à implémenter. ## Ordre de mise en œuvre | Priorité | Chantier | Raison | | -------- | -------------------------------------------- | ---------------------------------------------------------- | | 1 | Contrat de sous-traitance art. 28 + registre | Aucune vente possible sans eux | | 2 | Art. 50 dans le code (bannière, marquage) | Déjà applicable, en retard | | 3 | Dossier AIPD type | Sinon l'établissement bloque | | 4 | Audit RGAA | Peut attendre les premiers clients | | 5 | Adhésion GAR | Investissement lourd, à décider après validation du marché | --- ## Points à vérifier - Annexe 1 de la délibération HDF n° 2026.00876 (critères d'éligibilité, calendrier, dépenses éligibles). - Catégorie de ressource GAR applicable et conditions d'adhésion v2026. - Calendrier exact de la facturation électronique obligatoire. - Sort de l'appel à projets France 2030 sur l'IA enseignante. - `docs/MODELS.md` : tarif Mistral Large erroné (0,04 $/M annoncé ; 0,50 $/M en entrée et 1,50 $/M en sortie en réalité). --- title: Ethereum Daily — September 3, 2026 date: 2026-09-03 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-09-03 --- # Ethereum Daily — September 3, 2026 # Ethereum Daily — September 3, 2026 Covering roughly September 2 – September 3, 2026. **Roadmap.** No confirmed change since yesterday, but today is the decision point: the All Core Devs Consensus call scheduled for September 3 is where client teams are expected to formally confirm the proposed September 28 Sepolia testnet fork date for Glamsterdam (still targeting ePBS, Block-Level Access Lists, and a ~200M gas limit). As of publishing, no readout from that call has appeared yet. Client-team preference lists for the fork's remaining scope are still due September 10, and mainnet activation remains pointed at Q4 2026. **Governance.** No material new development since yesterday. EIP-8141 (Frame Transactions) stays locked in as "Scheduled for Inclusion" for the Hegotá upgrade, the decision core developers made on August 28. The interoperability question with the rival EIP-8130 proposal — which is set to launch on Base in September, ahead of any mainnet AA — remains unresolved. **Institutional and corporate adoption.** No new disclosure in the last 24 hours. Nothing followed on from Bitmine's treasury update covered in yesterday's post, and no other bank, fund, or corporate Ethereum announcement surfaced today. **Stats.** ETH trades around $2,386, down roughly 2.1% on the day, extending the pullback from the ~$2,409 level in yesterday's post. The validator set kept growing: 905,286 active validators (up from 904,423), with 42.7 million ETH staked (34.99% of supply, up from 34.93%). The entry queue grew slightly to 2,093,340 ETH at a 36-day, 8-hour wait. The exit queue, which opened up to 12,386 ETH yesterday, has since drained sharply back down to just 256 ETH with a 6-minute wait — that brief exit pressure looks to have been a one-day blip rather than a trend. Gas held steady at 0.042 gwei average. Ethereum DeFi TVL is roughly flat at $47.96 billion (-0.2% on the day) from $48.26 billion yesterday. ETF flows flipped negative again: September 2 posted a net outflow of $23.5 million, led by continued Grayscale ETHE redemptions; cumulative net inflows since launch stand at $13.04 billion. --- **Sources:** - [Ethereum's Glamsterdam Upgrade Sepolia Fork Date](https://cryptoticker.io/en/ethereum-glamsterdam-date-sepolia-fork/) - [Ethereum Core Developers Lock Account Abstraction EIP-8141 Into Hegotá Upgrade](https://en.coinotag.com/ethereum-core-developers-eip-8141-hegota-account-abstraction) - [Ethereum ETF Flow (US$m) — Farside Investors](https://farside.co.uk/eth/) - [Ethereum Validator Queue](https://www.validatorqueue.com/) - [Ethereum chain overview — DefiLlama](https://defillama.com/chain/ethereum) - [Ethereum Gas Tracker — Etherscan](https://etherscan.io/gastracker) - [Ethereum price — CoinGecko](https://www.coingecko.com/en/coins/ethereum) --- title: EU Alignment with US Policy: Leverage, Dependency, and the Cost to Others date: 2026-09-03 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/eu-alignment-us-policy --- # EU Alignment with US Policy: Leverage, Dependency, and the Cost to Others *Compiled 3 September 2026* --- ## 1. The statement **Primary source:** Issued 31 August 2026, timed to the G20 finance ministers' and central bank governors' meeting in Asheville, North Carolina (31 August – 1 September). Iran is one section of a broader G20-framed document. **What it says:** - The EU welcomes efforts to make Iran cease its destabilising activities and negotiate in good faith, "also through additional economic pressure, including through the US led Operation Economic Outcast" - The EU will continue working closely with the US and other G7 and international partners to maintain pressure on Iran and contribute to de-escalation and regional stability - The EU has adopted extensive sanctions to prevent Iran exploiting the global financial system - The EU stands ready to take further measures to safeguard its security and interests, including freedom of navigation **What it is not:** a legal adoption of US sanctions. EU restrictive measures remain autonomous, adopted by Council decision under CFSP across four regimes — human rights (extended to April 2027), military support to Russia and to armed groups, nuclear (reinstated after the September 2025 UN snapback), and since May 2026 freedom of navigation. This is rhetorical alignment, and it is the alignment that matters politically. **Context:** the statement was issued during an active US–Iran war, with the Strait of Hormuz disrupted since March 2026, and alongside US Treasury Secretary Scott Bessent's effort at the same meeting to rally G20 members behind cutting Iran's financial lifelines. **Iranian response:** Foreign Ministry spokesman Esmail Baqaei contrasted the EU's endorsement of additional economic pressure with the bloc's own 1996 Blocking Statute — adopted precisely to shield European operators from the extraterritorial reach of US sanctions — and accused the EU of holding contradictory positions. Sources: [EEAS](https://www.eeas.europa.eu/eeas/statement-context-g20-finance-ministers-and-central-bank-governors-meetings-31-august-and-1_en) · [Reuters via Al Arabiya](https://english.alarabiya.net/News/world/2026/08/31/eu-says-it-will-continue-to-work-with-us-and-g7-to-keep-up-pressure-on-iran) · [The National](https://www.thenationalnews.com/news/us/2026/08/31/g20-scott-bessent-iran/) · [RFE/RL](https://www.globalsecurity.org/wmd/library/news/iran/2026/09/iran-260901-rferl04.htm) · [Eunews](https://www.eunews.it/en/2026/08/31/iran-eu-ready-to-adopt-further-measures-to-protect-its-security-and-interests/) · [Council of the EU sanctions overview](https://www.consilium.europa.eu/en/policies/sanctions-against-iran/) --- ## 2. The precedent: what happened when Europe did resist Trump withdrew the United States from the JCPOA on **8 May 2018**. Europe did not stay silent. It mounted the most serious resistance it has ever attempted against US extraterritorial sanctions: - A **formal declaration** by the High Representative saying the EU deeply regretted the withdrawal, that the JCPOA was endorsed by UNSCR 2231 and was not in the hands of any single country to terminate, and that the EU remained committed to full implementation ([Council](https://www.consilium.europa.eu/en/press/press-releases/2018/05/09/declaration-by-the-high-representative-on-behalf-of-the-eu-following-us-president-trump-s-announcement-on-the-iran-nuclear-deal-jcpoa/) · [EEAS](https://www.eeas.europa.eu/node/44238_en)) - **Updating the 1996 Blocking Regulation** in June 2018 to cover the re-imposed US Iran sanctions — forbidding EU firms from complying, allowing recovery of damages, nullifying foreign judgments based on them ([European Parliament briefing](https://www.europarl.europa.eu/thinktank/en/document/EPRS_BRI(2018)623535) · [Arms Control Association](https://www.armscontrol.org/act/2018-06/news/eu-moves-block-us-iran-sanctions)) - **INSTEX**, established by France, Germany and the UK in January 2019 to route trade around US secondary sanctions ([Stimson Center](https://www.stimson.org/2023/is-the-eu-condemning-itself-to-irrelevance-on-the-iran-nuclear-file/) · [EUISS](https://www.iss.europa.eu/publications/briefs/creativity-wanted-countering-extraterritorial-effects-us-sanctions)) **None of it worked.** Total exited South Pars. European firms with US exposure left Iran within months. INSTEX processed a handful of humanitarian transactions and was wound up. The Blocking Statute has essentially never been enforced against a European company that complied with Washington. French economy minister Bruno Le Maire's protest that it was "not acceptable" for the US to be the economic policeman of the planet was, in the event, entirely acceptable. This is a more damning record than silence would have been. Silence could be indifference. This was maximum stated resistance, backed by legislation and a purpose-built institution, producing zero practical effect — demonstrating to every European boardroom that Brussels cannot protect them, and to Tehran that European guarantees are worthless. **And 2025 went further.** The E3 did not merely fail to resist US pressure; they triggered UN snapback themselves in August 2025, over Russian and Chinese objections that diplomacy was not exhausted. Critics read this as Europe ceasing to be an honest broker and becoming a US auxiliary ([Responsible Statecraft](https://responsiblestatecraft.org/trump-europe-iran/)). The JCPOA reached its Termination Day under UNSCR 2231 in October 2025; EU nuclear sanctions suspended under the deal were reinstated by Council Decision (CFSP) 2025/1972 ([Council record](https://www.globalsecurity.org/wmd/library/news/iran/2025/10/iran-251017-ec01.htm)). Further reading: [ICG, *The Iran Nuclear Deal at Four: A Requiem?*](https://www.crisisgroup.org/middle-east-north-africa/gulf-and-arabian-peninsula/iran/210-iran-nuclear-deal-four-requiem) · [Washington Institute](https://www.washingtoninstitute.org/policy-analysis/easier-said-done-renewing-maximum-pressure-iran) · [Wikipedia overview of the US withdrawal](https://en.wikipedia.org/wiki/Trump_and_JCPOA) --- ## 3. Who actually pays: the human cost inside Iran The JCPOA's collapse is not an abstract diplomatic failure. It produced measurable harm to Iranian civilians and measurable political gains for the parts of the Iranian state most hostile to the West. ### Economic destruction - Iran's foreign currency reserves crashed from **$120–128 billion under the JCPOA to roughly $15 billion by 2019** once maximum pressure was applied ([Atlantic Council](https://www.atlanticcouncil.org/dispatches/why-iran-has-not-folded-yet/)) - Inflation reached **48.6% in October 2025 and 42.2% in December 2025**, gutting household budgets amid currency collapse ([overview](https://en.wikipedia.org/wiki/Iranian_economic_crisis)) - By December 2025, multiple outlets reported Iran nearing economic collapse ### Medicine and health Humanitarian exemptions exist on paper and fail in practice, because banks over-comply rather than risk US secondary sanctions: - Human Rights Watch's 2019 report ***"Maximum Pressure": US Economic Sanctions Harm Iranians' Right to Health*** documented that sanctions have "drastically constrained" the financing of humanitarian imports, causing serious hardship and threatening the right to health ([full report](https://www.hrw.org/report/2019/10/29/maximum-pressure/us-economic-sanctions-harm-iranians-right-health) · [summary](https://www.hrw.org/news/2019/10/29/iran-sanctions-threatening-health)) - Documented shortages of chemotherapy drugs on the WHO essential medicines list — pegaspargase, mercaptopurine, vinblastine — alongside insulin pens and clotting factors ([Center for Human Rights in Iran](https://iranhumanrights.org/2019/10/human-rights-watch-sanctions-constrain-iranians-ability-to-finance-and-access-humanitarian-imports/) · [NIAC](https://niacouncil.org/sanctions-human-rights-and-the-everyday-struggle-of-iranians/)) - A study of 242 Iranian epilepsy patients found **72% reported significant difficulty accessing imported medication** between August 2018 and February 2019 (cited in the HRW report) - A peer-reviewed systematic review in the *International Journal of Health Policy and Management* concluded that sanctions on banking, finance and shipping produced scarcity of quality lifesaving medicines, that impacts fell hardest on the poor, patients, women and children, and that **humanitarian exemptions did not protect Iranians** ([IJHPM](https://www.ijhpm.com/article_3454.html)) - The UN Special Rapporteur on human rights in Iran raised concerns in July 2019 about sanctions unduly affecting food security and the availability of medicines and medical supplies - Sanctions also blocked import of the **raw materials Iran needed to manufacture its own pharmaceuticals**, compounding the shortage ([Arab Center DC, 2026](https://arabcenterdc.org/resource/the-humanitarian-impact-of-the-war-on-iran/)) ### Political consequence: hardliners strengthened The reformist argument in Iran was that engagement with the West would deliver material benefit. Withdrawal destroyed that argument: - Rouhani had won elite consensus on restraint after the withdrawal, but hardline voices were emboldened — *Kayhan* editor Hossein Shariatmadari attacked him for not heeding "the lessons of the JCPOA" and laid out a case for closing the Strait of Hormuz, while hardline parliamentarians pursued impeachment of Rouhani and Zarif ([National Interest](https://nationalinterest.org/feature/growing-us-pressure-emboldening-iranian-hardliners-53882)) - The International Crisis Group warned explicitly that European moves risked **empowering Iranian hardliners** and that hardliners would welcome rising tension as a way to discredit the Rouhani camp before the 2020 and 2021 elections ([ICG](https://www.crisisgroup.org/cmt/middle-east-north-africa/iran/us-maximum-pressure-meets-iranian-maximum-pressure)) - Where hardliners have historically been most effective is in constraining Iran's room to negotiate — through legislation, institutional vetoes, and making engagement politically toxic ([The Conversation](https://theconversation.com/irans-hardliners-who-they-are-what-they-believe-and-why-they-matter-284691)) - The 2022 negotiations collapsed partly because hardliner President Raisi showed less flexibility than his predecessor, and partly because Iran demanded a guarantee against another unilateral withdrawal that no US executive could constitutionally provide ([background](https://missilestrikes.com/guide/jcpoa-iran-deal-explained/)) - The Washington Institute noted that IRGC officials are less risk-averse than the civilian leadership, so a larger IRGC say in decision-making produces more aggressive Iranian behaviour ([Washington Institute](https://www.washingtoninstitute.org/policy-analysis/iran-after-jcpoa-withdrawal-part-1-lessons-past-pressure-campaigns)) ### The strategic self-defeat Once a coercing state reverses its own prior commitments, the value of any subsequent assurance collapses. Tehran's post-2018 reasoning was not irrational: if compliance in 2015 did not protect against renewed pressure in 2018, why would compliance in 2019 produce a different outcome? Iran began methodically violating JCPOA limits in mid-2019 only after concluding no economic dividend was coming. The IRGC's terrorist designation was, on Lawfare's own assessment, "largely symbolic" and added little economic pressure — a coercive act calibrated for domestic American audiences rather than operational effect ([Manara Magazine, June 2026](https://manaramagazine.org/2026/06/maximum-pressure-us-coercive-power/)). The Iranian protest wave that began in December 2025 — spanning 675 locations across 210 cities in all 31 provinces — was driven substantially by economic grievance: mismanagement, **international sanctions**, rising prices, currency depreciation, and water and energy shortages ([overview](https://en.wikipedia.org/wiki/2025%E2%80%932026_Iranian_protests)). The EU's January and March 2026 statements condemned the crackdown on those protesters. The EU has not reconciled that condemnation with its endorsement of the economic pressure that helped produce the grievances. **The uncomfortable summary:** the policy did not stop enrichment, did not produce a better deal, did not weaken the IRGC, and did not liberalise Iran. It impoverished the population, killed patients, discredited the faction that argued for engagement, and ended in war. --- ## 4. Gaza: sanctions that never came ### The finding On **16 September 2025**, the UN Independent International Commission of Inquiry on the Occupied Palestinian Territory concluded that Israel is responsible for the commission of genocide in Gaza. The Commission found that Israeli authorities and security forces committed **four of the five genocidal acts** defined in the 1948 Genocide Convention — killing, causing serious bodily or mental harm, deliberately inflicting conditions of life calculated to bring about destruction, and imposing measures intended to prevent births — and found that explicit statements by Israeli civilian and military authorities, alongside the pattern of conduct, established genocidal intent. It further found that President Herzog, Prime Minister Netanyahu and then Defence Minister Gallant had incited genocide, and called for genocide charges to be added to the ICC arrest warrants. Chair Navi Pillay — former UN High Commissioner for Human Rights and former president of the Rwanda tribunal — stated that responsibility lies with Israeli authorities at the highest echelons. This was not an isolated finding. It followed: - **Amnesty International**, December 2024, *"You Feel Like You Are Subhuman"* - The **International Association of Genocide Scholars**, 31 August 2025, resolving that Israel's policies and actions in Gaza meet the legal definition of genocide under Article II - **South Africa v. Israel** at the ICJ, where provisional measures have been granted; the merits remain pending - Sustained analysis in the international law literature Israel's Foreign Ministry rejected the report as distorted and false. That is the position of the state under investigation. Sources: [OHCHR](https://www.ohchr.org/en/press-releases/2025/09/israel-has-committed-genocide-gaza-strip-un-commission-finds) · [UN](https://www.un.org/unispal/document/israel-has-committed-genocide-in-the-gaza-strip-un-commission-finds-16sep25/) · [IAGS resolution (PDF)](https://genocidescholars.org/wp-content/uploads/2025/08/IAGS-Resolution-on-Gaza-FINAL.pdf) · [Opinio Juris](https://opiniojuris.org/2025/12/16/gaza-genocide-and-the-un-general-assembly-part-1/) · [Time](https://time.com/7317574/israel-gaza-genocide-united-nations-commission-inquiry-report/) · [Forbes](https://www.forbes.com/sites/ewelinaochab/2025/09/16/united-nations-commission-of-inquiry-israel-is-committing-genocide-in-gaza/) · [report overview](https://en.wikipedia.org/wiki/2025_UNHRC_Commission_of_Inquiry_report_on_Gaza_genocide) ### The EU's response **No state-level sanctions on Israel. The Association Agreement remains in force.** What exists instead is three rounds of narrowly targeted measures under the EU Global Human Rights Sanctions Regime — as of mid-2026, nine individuals and five entities linked to settler violence in the West Bank and East Jerusalem and to the blocking of humanitarian aid. The May 2026 round listed the Nachala Settlement Movement and Daniella Weiss, Regavim and Meir Deutsch, Hashomer Yosh and Avichai Suissa, and the Amana cooperative ([J Street / Mitvim](https://jstreet.org/the-status-of-eu-israel-relations-sanctions-association-agreement-and-israels-growing-isolation-in-europe/) · [PDF](https://jstreet.org/wp-content/uploads/2026/06/The-Status-of-EU-Israel-Relations_-Sanctions-Association-Agreement-and-Israels-Growing-Isolation-in-Europe-1-1.pdf)). Settler NGOs. Not the state, not the officials the UN named as inciters, not the trade relationship. This is despite: | Date | Event | Outcome | |---|---|---| | May 2025 | Kallas launches Article 2 review | — | | 23 June 2025 | Review finds indications Israel in breach of human rights obligations | No action | | 17 Sept 2025 | Commission proposes suspending trade concessions + sanctions on extremist ministers and violent settlers; bilateral support put on hold | Never adopted | | 11 Sept 2025 | European Parliament resolution demands immediate suspension | Non-binding | | — | European Citizens' Initiative surpasses **1 million signatures** | Disregarded | | 20 April 2026 | UN experts call suspension the minimum requirement under international law | — | | 21 April 2026 | Foreign Affairs Council: Spain's suspension push fails | Blocked | | 13 July 2026 | Commission options on settlement trade restrictions presented | None adopted | **Who blocks it:** Germany, Italy, Hungary and the Czech Republic, preventing the qualified majority (55% of member states representing 65% of population) required for trade measures. Amnesty named Germany and Italy as leading the obstruction. Sources: [Commission proposal, 17 Sept 2025](https://www.globalsecurity.org/wmd/library/news/israel/2025/09/israel-250917-european-commission01.htm) · [OHCHR, UN experts](https://www.ohchr.org/en/press-releases/2026/04/un-experts-call-immediate-suspension-eu-israel-trade-agreement-minimum) · [Euronews on the failed vote](https://www.euronews.com/my-europe/2026/04/20/spains-call-to-suspend-eu-israel-agreement-set-to-fail-amid-broad-opposition) · [Al Jazeera](https://www.aljazeera.com/news/2026/4/21/why-is-the-eu-under-pressure-to-suspend-its-trade-agreement-with-israel) · [Amnesty, July 2026](https://www.amnesty.org/en/latest/news/2026/07/consistent-failure-to-suspend-association-agreement-should-spur-unilateral-action/) · [Al-Haq / 185+ organisations](https://www.alhaq.org/advocacy/27580.html) · [analysis of why suspension failed](https://europeanrelations.com/briefing/why-the-eu-did-not-suspend-the-eu-israel-association-agreement/) ### The contrast The EU found Israel in breach of the essential-elements clause of its own Association Agreement — a lower evidentiary bar than genocide — and still took no action on the agreement. Over the same period it adopted multiple new sanctions packages against Iran on human rights grounds, extended the Iran human rights regime to April 2027, added 16 persons and three entities in March 2026 alone, and publicly welcomed an American economic pressure campaign against Iran. Two states, two human rights findings by EU institutions, two entirely different responses. --- ## 5. The mechanisms of US economic leverage over the EU ### Financial — the sharpest instrument - **Dollar clearing.** Every large EU bank needs a US correspondent. OFAC can revoke it. The 2014 BNP Paribas case — $8.9bn penalty plus a temporary clearing ban over Iran, Sudan and Cuba — established the precedent, and European compliance departments have behaved accordingly ever since. - **Secondary sanctions** under Operation Economic Outcast reach any EU bank, insurer, shipper or trader touching Iranian counterparties. Treasury Secretary Bessent has signalled expectations of new secondary designations on a weekly cadence. - **The Blocking Statute cannot help.** It forbids compliance; it cannot make a designated bank's US business survive. Over-compliance by banks is precisely the mechanism that strangles humanitarian trade (§3). - **Fed swap lines.** The ECB depended on them in 2008 and March 2020. Discretionary, never weaponised, structurally unhedged. ### Trade The US is the EU's largest single export market. The January 2026 Greenland episode showed the speed: escalating 10–25% tariffs pledged on eight European countries from 1 February over insufficient support for acquiring Greenland, withdrawn four days later after a "framework of a future deal." Von der Leyen called the threat a mistake between long-standing allies and promised an unflinching response; the European Parliament's trade committee suspended work on implementing the US–EU trade deal. ([CRS](https://www.congress.gov/crs-product/IN12645) · [European Parliament briefing on tariff repercussions, PDF](https://www.europarl.europa.eu/RegData/etudes/BRIE/2026/779864/ECTI_BRI(2026)779864_EN.pdf)) Concentrated exposure: German and Italian machinery and autos, Irish pharma, French aerospace and luxury. Plus Section 232 actions and the Foreign Direct Product Rule, which reaches anything made with US technology. ### Energy — already live The current damage comes from the war rather than from US pressure on the EU, but it maps the exposure precisely: - ~8.5% of EU LNG and ~7% of crude transit Hormuz — but **up to 40% of EU refined fuel imports** do, hitting airlines, shipping and trucking fast - TTF gas hit €50/MWh, up 60% after the strikes closed the strait — the sharpest shock since 2022 - Storage entered the crisis at 46 bcm (end-February 2026) against 60 bcm in 2025 and 77 bcm in 2024 - Mid-April: the IEA's head said Europe had roughly six weeks of jet fuel remaining - The Commission cut 2026 EU growth to **1.1%** from 1.4%, the eurozone to **0.9%**, and raised inflation to **3.1%** - Hormuz carries roughly a fifth of global oil and a quarter of global LNG; the Kiel Institute models severe welfare losses concentrated in energy-dependent developing countries Separately: Russian pipeline gas was replaced largely by American LNG after 2022, converting one dependency into another under long-term contract. Sources: [ECB, "The new energy shock"](https://www.ecb.europa.eu/press/key/date/2026/html/ecb.sp260506~1bbd4ed780.en.html) · [ECB blog on muted price reaction](https://www.ecb.europa.eu/press/blog/date/2026/html/ecb.blog20260727~1212bdb8f9.en.html) · [Bruegel](https://www.bruegel.org/first-glance/how-will-iran-conflict-hit-european-energy-markets) · [JRC scenario analysis](https://joint-research-centre.ec.europa.eu/jrc-news-and-updates/how-prolonged-middle-east-crisis-would-impact-energy-prices-and-eu-economy-2026-05-27_en) · [Euronews on the downgrade](https://www.euronews.com/my-europe/2026/05/21/eu-cuts-2026-growth-forecast-as-strait-of-hormuz-crisis-pushes-inflation-up) · [Euronews on European exposure](https://euronews.com/2026/03/05/iran-war-how-exposed-are-european-economies) · [Kiel Institute (PDF)](https://www.kielinstitut.de/fileadmin/Dateiverwaltung/IfW-Publications/fis-import/01b7c020-27e6-4096-8cc5-e037738d2058-KPB_206.pdf) · [EU Perspectives on Commission warnings](https://euperspectives.eu/2026/04/hormuz-strait-disruption-energy-crisis-european-commission/) ### Technology and infrastructure US hyperscalers hold most of the EU cloud market including public-sector workloads. Payment rails run through Visa and Mastercard. The Data Privacy Framework governing transatlantic data flows is an executive arrangement, revocable; its two predecessors were struck down by the CJEU. ### Military - **Literal US control — nuclear.** B61 bombs stationed in Belgium, Germany, Italy, the Netherlands and Turkey carry Permissive Action Links. No European pilot can arm one without a US release code. This is the openly acknowledged architecture of nuclear sharing. - **Legal control — ITAR.** Any system containing US-origin controlled components requires State Department approval for re-export or third-party transfer. - **Functional control — F-35.** No verified kill switch; Lockheed Martin and the Pentagon deny one exists. But mission data files, ALIS/ODIN sustainment and the spares pipeline are all US-controlled, and withholding them degrades a fleet over weeks to months. RUSI's Justin Bronk: if targeting, BLOS comms, penetrating ISR and munitions are all US-provided, F-35 mission-data dependency isn't the main problem. - **Demonstrated precedent:** the March 2025 intelligence-sharing pause with Ukraine degraded HIMARS effectiveness within days. Consequences: Portugal and Spain withdrew from the F-35 programme, Spain shelving it indefinitely in August 2025 on the view that a platform where Washington retains ultimate oversight over code and parts distribution is an unacceptable sovereignty risk. Canada is reconsidering; the UK, Germany, Switzerland and Australia cut fleet sizes; Germany held closed-door reviews of US systems dependency under Pistorius. Sources: [The Aviationist](https://theaviationist.com/2025/03/10/f-35-kill-switch-myth/) · [National Security Journal (sceptical view)](https://nationalsecurityjournal.org/europe-really-does-fear-the-mythical-f-35-fighter-kill-switch/) · [Simple Flying on 2026 order changes](https://simpleflying.com/every-country-reconsidered-f-35-2026-why-most-still-buy/) · [SOFREP on German review](https://sofrep.com/news/germany-to-hold-closed-door-discussions-on-us-made-weapon-systems-amid-concerns-over-kill-switch-in-fighter-jets/) --- ## 6. Speed and worst case | Instrument | Time to bite | |---|---| | OFAC designation of a bank | Hours | | Market / FX / spread reaction | Same day | | Intelligence-sharing suspension | Immediate | | Tariffs → trade flows | 2–4 quarters | | Energy denial | Weeks, and only via export controls that gut US producers | | Cloud / payments / data | Commercial relationships; no switch | **Compound worst case.** Hormuz stays disrupted through winter on depleted storage. The Commission's own adverse scenario has oil peaking near **$180/bbl in Q4 2026** and gas near **€80/MWh**, against a baseline of $84.7 and €42.2. Energy-intensive industry — chemicals, fertiliser, aluminium, glass, ceramics — closes permanently rather than idling, because plants shut in 2022 never reopened; the second closure is terminal. Tariffs layer onto a contracting economy. The ECB faces a supply shock it cannot ease into without unanchoring expectations. Fiscal responses diverge by fiscal space, spreads widen, the TPI gets tested. For households: energy bills consuming a materially larger income share for the second time in four years, food prices up through fertiliser and freight, plant closures in industrial regions, real wages falling again. The persistent effect is political — governments unable to deliver relief losing to parties campaigning against the alignment that produced the exposure. **Counterweight.** The measures that could break Europe are the slowest and most self-damaging. Deploying dollar leverage at scale against an ally would teach every central bank on earth the lesson Washington has spent decades hoping they do not learn. That deterrent is real. --- ## 7. Counter-arguments to the thesis The thesis — *the EU aligns because US retaliation would be economically devastating* — is defensible but under-determined. Five competing explanations fit the same evidence: **1. Unanimity and qualified majority.** CFSP decisions need all 27; trade measures need QMV. The output is not the average European position but whatever the most reluctant government tolerates. A bloc that cannot say anything looks like a bloc that agrees. **2. Genuine agreement.** Iranian drones supplied to Russia, IRGC plots on European soil, the 2025–26 protest crackdown, Hormuz interference directly harming European shipping. EU hawkishness on Iran has domestic roots. On Ukraine, Europe's position has been *harder* than Washington's — the transatlantic fight there has been Europe resisting US pressure to settle. Attributing everything to coercion assumes a suppressed European dissent that in many capitals does not exist. **3. No alternative alignment exists.** Opposing Washington on Gaza produces no European policy unless Europe has organised independent leverage over Israel, which it has not. The real choice is often "follow Washington or have no line." **4. Capability, not hostage-taking.** Europe cannot run a serious military operation without US enablers. That is forty years of underinvestment, not blackmail. It constrains what Europe can credibly threaten, which constrains what it can credibly demand. **5. Ukraine as the binding constraint.** A genuine transatlantic rupture endangers Kyiv first. This is probably the largest single factor since 2022 — and it *is* hostage logic, but the hostage is Ukraine, not the European economy. ### Where the Gaza case cuts against the coercion thesis If US pressure were the operative mechanism, the blockers should be the governments most exposed to Washington. They are not. Germany, Italy, Hungary and the Czech Republic block suspension for their own reasons — German state doctrine on Israel, Orbán's ideological alignment, Meloni's coalition politics, notwithstanding that Meloni sharply condemned Ben-Gvir's treatment of Italian flotilla participants and faced mass strikes at home. Spain and Ireland pushed the other way at no evident US-inflicted cost. Germany even suspended some weapons exports to Israel unilaterally in August 2025, bringing them to zero for a period, before lifting the measure in November. This is intra-European division, not American coercion. Any honest version of the argument has to account for it. ### Where the Iran case cuts *for* it Conversely, the Iran case fits the coercion thesis well. Europe had a legal instrument (the Blocking Statute), a purpose-built financial vehicle (INSTEX), a declared political commitment, and a direct economic interest in maintaining Iranian trade — and abandoned all of it under the threat of secondary sanctions, without Washington needing to designate a single major European bank. That is coercion working perfectly: by anticipation, leaving no evidence. ### Europe is not defenceless The Anti-Coercion Instrument, in force since 27 December 2023, requires only QMV and is broad enough to restrict US banks' access to an EU public procurement market worth over $2tn a year, or curb US tech giants' access to the single market. But activation requires staged information-gathering and consultation before measures apply, putting the EU at a clear speed disadvantage against an executive that can act by proclamation. Countermeasures must be proportionate and minimise EU collateral damage — criteria that may confine it to counter-tariffs, particularly on capital, since US banks and investors are not easily substituted by European capital. The full process runs months to a year. It remains unused. Sources: [LSE EUROPP](https://blogs.lse.ac.uk/europpblog/2026/01/20/eu-anti-coercion-instrument-trump-greenland-tariffs/) · [Atlantic Council](https://www.atlanticcouncil.org/blogs/new-atlanticist/the-eu-could-respond-to-trumps-tariffs-with-a-new-anti-coercion-instrument-heres-what-to-know/) · [ACI overview](https://en.wikipedia.org/wiki/Anti-Coercion_Instrument) · [procedural summary](https://www.globalbankingandfinance.com/factbox-what-anti-coercion-instruments-eu-capitals-use/) ### The uncomfortable middle Vetoes and capability gaps are real, but they are also *convenient*. A government that does not want to act can hide behind unanimity indefinitely. Some of what presents as paralysis is preference wearing a procedural costume. Distinguishing the two requires case-by-case examination of who blocked what — a different exercise from the general argument, and one where the Gaza record and the Iran record point in opposite directions. --- ## 8. Full source list ### EU primary documents - [EEAS statement, 31 Aug 2026](https://www.eeas.europa.eu/eeas/statement-context-g20-finance-ministers-and-central-bank-governors-meetings-31-august-and-1_en) - [Council: EU sanctions against Iran](https://www.consilium.europa.eu/en/policies/sanctions-against-iran/) - [Council: timeline of EU sanctions against Iran](https://www.consilium.europa.eu/en/policies/sanctions-against-iran/timeline-eu-sanctions-against-iran/) - [Council: human rights regime extended to April 2027](https://www.consilium.europa.eu/en/press/press-releases/2026/03/30/human-rights-in-iran-council-extends-sanctions-regime-until-april-2027/) - [Council: 16 persons, 3 entities sanctioned, March 2026](https://www.consilium.europa.eu/en/press/press-releases/2026/03/16/iran-council-sanctions-an-additional-16-persons-and-three-entities-over-serious-human-rights-violations/) - [Council: new sanctions, January 2026](https://www.consilium.europa.eu/en/press/press-releases/2026/01/29/iran-council-adopts-new-sanctions-over-serious-human-rights-violations-and-iran-s-continued-support-to-russia-s-war-of-aggression-against-ukraine/) - [HR statement on Middle East developments, 1 March 2026](https://www.consilium.europa.eu/en/press/press-releases/2026/03/01/statement-by-the-high-representative-on-behalf-of-the-european-union-on-developments-in-the-middle-east/) - [HR Declaration on the JCPOA withdrawal, 9 May 2018](https://www.consilium.europa.eu/en/press/press-releases/2018/05/09/declaration-by-the-high-representative-on-behalf-of-the-eu-following-us-president-trump-s-announcement-on-the-iran-nuclear-deal-jcpoa/) - [Mogherini remarks, 8 May 2018](https://www.eeas.europa.eu/node/44238_en) - [EP briefing: updating the Blocking Regulation](https://www.europarl.europa.eu/thinktank/en/document/EPRS_BRI(2018)623535) - [EP briefing: economic, financial and monetary repercussions of US tariffs, March 2026 (PDF)](https://www.europarl.europa.eu/RegData/etudes/BRIE/2026/779864/ECTI_BRI(2026)779864_EN.pdf) - [JRC: prolonged Middle East crisis scenario](https://joint-research-centre.ec.europa.eu/jrc-news-and-updates/how-prolonged-middle-east-crisis-would-impact-energy-prices-and-eu-economy-2026-05-27_en) - [Commission proposal on Israel trade concessions, 17 Sept 2025](https://www.globalsecurity.org/wmd/library/news/israel/2025/09/israel-250917-european-commission01.htm) - [Council reinstates nuclear sanctions after snapback, Oct 2025](https://www.globalsecurity.org/wmd/library/news/iran/2025/10/iran-251017-ec01.htm) ### UN and human rights bodies - [OHCHR: Israel has committed genocide in Gaza, 16 Sept 2025](https://www.ohchr.org/en/press-releases/2025/09/israel-has-committed-genocide-gaza-strip-un-commission-finds) - [UN UNISPAL record of the same](https://www.un.org/unispal/document/israel-has-committed-genocide-in-the-gaza-strip-un-commission-finds-16sep25/) - [OHCHR: UN experts on suspending the EU–Israel agreement, April 2026](https://www.ohchr.org/en/press-releases/2026/04/un-experts-call-immediate-suspension-eu-israel-trade-agreement-minimum) - [IAGS resolution on Gaza, 31 Aug 2025 (PDF)](https://genocidescholars.org/wp-content/uploads/2025/08/IAGS-Resolution-on-Gaza-FINAL.pdf) - [HRW: "Maximum Pressure" — full report](https://www.hrw.org/report/2019/10/29/maximum-pressure/us-economic-sanctions-harm-iranians-right-health) - [HRW: press summary](https://www.hrw.org/news/2019/10/29/iran-sanctions-threatening-health) - [Amnesty: EU failure to suspend the Association Agreement, July 2026](https://www.amnesty.org/en/latest/news/2026/07/consistent-failure-to-suspend-association-agreement-should-spur-unilateral-action/) - [Al-Haq: 185+ organisations statement, April 2026](https://www.alhaq.org/advocacy/27580.html) - [Center for Human Rights in Iran on the HRW findings](https://iranhumanrights.org/2019/10/human-rights-watch-sanctions-constrain-iranians-ability-to-finance-and-access-humanitarian-imports/) ### Scholarship and think tanks - [IJHPM: systematic review of sanctions and Iranians' right to health](https://www.ijhpm.com/article_3454.html) - [Opinio Juris: Gaza, genocide and the UN General Assembly](https://opiniojuris.org/2025/12/16/gaza-genocide-and-the-un-general-assembly-part-1/) - [ICG: *The Iran Nuclear Deal at Four: A Requiem?*](https://www.crisisgroup.org/middle-east-north-africa/gulf-and-arabian-peninsula/iran/210-iran-nuclear-deal-four-requiem) - [ICG: US maximum pressure meets Iranian maximum pressure](https://www.crisisgroup.org/cmt/middle-east-north-africa/iran/us-maximum-pressure-meets-iranian-maximum-pressure) - [Washington Institute: Iran after the JCPOA withdrawal](https://www.washingtoninstitute.org/policy-analysis/iran-after-jcpoa-withdrawal-part-1-lessons-past-pressure-campaigns) - [Washington Institute: renewing maximum pressure](https://www.washingtoninstitute.org/policy-analysis/easier-said-done-renewing-maximum-pressure-iran) - [Stimson Center: is the EU condemning itself to irrelevance on Iran?](https://www.stimson.org/2023/is-the-eu-condemning-itself-to-irrelevance-on-the-iran-nuclear-file/) - [EUISS: countering the extraterritorial effects of US sanctions](https://www.iss.europa.eu/publications/briefs/creativity-wanted-countering-extraterritorial-effects-us-sanctions) - [Bruegel: Iran conflict and European energy markets](https://www.bruegel.org/first-glance/how-will-iran-conflict-hit-european-energy-markets) - [Kiel Institute: the cost of closing the Strait of Hormuz (PDF)](https://www.kielinstitut.de/fileadmin/Dateiverwaltung/IfW-Publications/fis-import/01b7c020-27e6-4096-8cc5-e037738d2058-KPB_206.pdf) - [ECB: "The new energy shock", May 2026](https://www.ecb.europa.eu/press/key/date/2026/html/ecb.sp260506~1bbd4ed780.en.html) - [ECB blog: why oil and gas prices rose less than expected](https://www.ecb.europa.eu/press/blog/date/2026/html/ecb.blog20260727~1212bdb8f9.en.html) - [Atlantic Council: why Iran has not folded yet](https://www.atlanticcouncil.org/dispatches/why-iran-has-not-folded-yet/) - [Atlantic Council: the anti-coercion instrument explained](https://www.atlanticcouncil.org/blogs/new-atlanticist/the-eu-could-respond-to-trumps-tariffs-with-a-new-anti-coercion-instrument-heres-what-to-know/) - [LSE EUROPP: can the ACI counter Trump's tariffs?](https://blogs.lse.ac.uk/europpblog/2026/01/20/eu-anti-coercion-instrument-trump-greenland-tariffs/) - [Manara Magazine: how maximum pressure weakened US coercive power](https://manaramagazine.org/2026/06/maximum-pressure-us-coercive-power/) - [Arab Center DC: the humanitarian impact of the war on Iran](https://arabcenterdc.org/resource/the-humanitarian-impact-of-the-war-on-iran/) - [NIAC: sanctions, human rights and the everyday struggle of Iranians](https://niacouncil.org/sanctions-human-rights-and-the-everyday-struggle-of-iranians/) - [J Street / Mitvim: status of EU–Israel relations, June 2026](https://jstreet.org/the-status-of-eu-israel-relations-sanctions-association-agreement-and-israels-growing-isolation-in-europe/) - [Congressional Research Service: US tariff frameworks with Europe](https://www.congress.gov/crs-product/IN12645) - [The Conversation: Iran's hardliners — who they are and why they matter](https://theconversation.com/irans-hardliners-who-they-are-what-they-believe-and-why-they-matter-284691) - [National Interest: US pressure emboldening Iranian hardliners](https://nationalinterest.org/feature/growing-us-pressure-emboldening-iranian-hardliners-53882) - [National Security Journal: the sceptical case on F-35 fears](https://nationalsecurityjournal.org/europe-really-does-fear-the-mythical-f-35-fighter-kill-switch/) ### Press - [Reuters via Al Arabiya: EU to keep up pressure on Iran, 31 Aug 2026](https://english.alarabiya.net/News/world/2026/08/31/eu-says-it-will-continue-to-work-with-us-and-g7-to-keep-up-pressure-on-iran) - [The National: Bessent seeks to rally G20 on severing Iran financial ties](https://www.thenationalnews.com/news/us/2026/08/31/g20-scott-bessent-iran/) - [RFE/RL: EU backs US pressure, drawing rebuke from Tehran](https://www.globalsecurity.org/wmd/library/news/iran/2026/09/iran-260901-rferl04.htm) - [Eunews: EU ready to adopt further measures](https://www.eunews.it/en/2026/08/31/iran-eu-ready-to-adopt-further-measures-to-protect-its-security-and-interests/) - [Euronews: EU cuts 2026 growth forecast](https://www.euronews.com/my-europe/2026/05/21/eu-cuts-2026-growth-forecast-as-strait-of-hormuz-crisis-pushes-inflation-up) - [Euronews: how exposed are European economies?](https://euronews.com/2026/03/05/iran-war-how-exposed-are-european-economies) - [Euronews: Spain's suspension call set to fail](https://www.euronews.com/my-europe/2026/04/20/spains-call-to-suspend-eu-israel-agreement-set-to-fail-amid-broad-opposition) - [Al Jazeera: why the EU is under pressure over the Israel trade deal](https://www.aljazeera.com/news/2026/4/21/why-is-the-eu-under-pressure-to-suspend-its-trade-agreement-with-israel) - [Time: UN commission finds genocide](https://time.com/7317574/israel-gaza-genocide-united-nations-commission-inquiry-report/) - [Forbes: UN Commission of Inquiry findings](https://www.forbes.com/sites/ewelinaochab/2025/09/16/united-nations-commission-of-inquiry-israel-is-committing-genocide-in-gaza/) - [Responsible Statecraft: why Trump dumped Europe from Iran talks](https://responsiblestatecraft.org/trump-europe-iran/) - [Arms Control Association: EU moves to block US Iran sanctions](https://www.armscontrol.org/act/2018-06/news/eu-moves-block-us-iran-sanctions) - [Arms Control Association: P4+1 alert, May 2018](https://www.armscontrol.org/blog/2018-05-16/p41-iran-nuclear-deal-alert-may-16-2018) - [EU Perspectives: Commission warns the energy shock will drag on](https://euperspectives.eu/2026/04/hormuz-strait-disruption-energy-crisis-european-commission/) - [EU Perspectives: closed Hormuz hurts Europe more than the US](https://euperspectives.eu/2026/06/europe-welcomes-iran-deal-with-relief-closed-hormuz-hurts-it-more-than-the-us/) - [The Aviationist: the F-35 kill switch](https://theaviationist.com/2025/03/10/f-35-kill-switch-myth/) - [Simple Flying: countries reconsidering the F-35 in 2026](https://simpleflying.com/every-country-reconsidered-f-35-2026-why-most-still-buy/) - [SOFREP: Germany's closed-door review of US systems](https://sofrep.com/news/germany-to-hold-closed-door-discussions-on-us-made-weapon-systems-amid-concerns-over-kill-switch-in-fighter-jets/) - [Bloomberg Opinion: Trump's threats as a global kill switch](https://www.bloomberg.com/opinion/articles/2025-04-04/trump-s-threats-and-tariffs-are-a-global-kill-switch) ### Reference - [UN Commission of Inquiry report on Gaza genocide](https://en.wikipedia.org/wiki/2025_UNHRC_Commission_of_Inquiry_report_on_Gaza_genocide) - [US withdrawal from the JCPOA](https://en.wikipedia.org/wiki/Trump_and_JCPOA) - [Iranian economic crisis](https://en.wikipedia.org/wiki/Iranian_economic_crisis) - [2025–2026 Iranian protests](https://en.wikipedia.org/wiki/2025%E2%80%932026_Iranian_protests) - [EU Anti-Coercion Instrument](https://en.wikipedia.org/wiki/Anti-Coercion_Instrument) - [Joint statement on Iranian state threat activity in Europe and North America](https://en.wikipedia.org/wiki/Joint_statement_on_Iranian_state_threat_activity_in_Europe_and_North_America) --- *Section 7 is not a disclaimer. The counter-arguments are load-bearing, and the two cases point in opposite directions: the Iran record supports the coercion thesis, the Gaza record points instead to intra-European division. An argument that only uses one of them is weaker than one that uses both.* --- title: Ethereum Daily — September 2, 2026 date: 2026-09-02 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-09-02 --- # Ethereum Daily — September 2, 2026 # Ethereum Daily — September 2, 2026 Covering roughly September 1 – September 2, 2026. **Roadmap.** No material new development since yesterday. Glamsterdam's most recent public milestone (final devnet stage locking in ePBS and Block-Level Access Lists, targeting a ~200M gas limit) predates this window, client-team preference lists are still due September 10, and mainnet activation remains pointed at Q4 2026. Nothing new surfaced in the last day. **Governance.** No material new development. The EIP-8141 (Frame Transactions) versus EIP-8130 native account-abstraction debate for Hegotá is unchanged from yesterday, with no fresh statements from client teams. **Institutional and corporate adoption.** No new disclosure since Bitmine's weekly treasury update covered in yesterday's post. No other bank, fund, or corporate Ethereum announcement surfaced in the last 24 hours. **Stats.** ETH trades around $2,409, down roughly 2.6% on the day and continuing yesterday's pullback from ~$2,445. The validator set kept climbing: 904,423 active validators (up from 903,892), with 42.6 million ETH staked (34.93% of supply). The entry queue grew to 2,088,205 ETH at a 36-day, 6-hour wait, slightly longer than yesterday. Notably, the exit queue — empty as of yesterday's post — now shows 12,386 ETH queued with a roughly 5-hour wait, the first exit-queue activity mentioned in this series. Gas ticked down further to 0.041 gwei average. Ethereum DeFi TVL slipped to $48.26 billion, down about 1.8% on the day from $49.1 billion. On ETF flows, September 1 posted a net outflow of $2.6 million — Grayscale's ETHE led redemptions, partly offset by Fidelity's FETH — the first net-negative day in this run of updates, following Monday's net-zero print; cumulative net inflows since launch edge down slightly from the roughly $12.97 billion reported yesterday. --- **Sources:** - [Ethereum ETF Flow (US$m) — Farside Investors](https://farside.co.uk/eth/) - [Ethereum Validator Queue](https://www.validatorqueue.com/) - [Ethereum chain overview — DefiLlama](https://defillama.com/chain/ethereum) - [Ethereum Gas Tracker — Etherscan](https://etherscan.io/gastracker) - [Ethereum price — CoinGecko](https://www.coingecko.com/en/coins/ethereum) --- title: CV date: 2026-09-02 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/cv-sept-2-2026 --- # CV **Smart Contract & Full-Stack Engineer — AI / LLM integrations** Montpellier, France · [julien@strat.cc](mailto:julien@strat.cc) · +33 6 30 90 54 48 [julienberanger.com](https://julienberanger.com) · [GitHub](https://github.com/julienbrg) · [LinkedIn](https://www.linkedin.com/in/julienberanger/) · [X](https://twitter.com/julienbrg) · [Farcaster](https://warpcast.com/julien-) · [Telegram](https://t.me/julienbrg) · [Element](https://matrix.to/#/@julienbrg:matrix.org) Senior full-stack developer with 6+ years in production — [TypeScript](https://www.typescriptlang.org/), [React](https://react.dev/)/[Next.js](https://nextjs.org/), [Node.js](https://nodejs.org/en), REST APIs, CI/CD, and LLM integrations. I build complex business interfaces, data pipelines, and AI features — and I teach people how to do the same. Working languages: English, French, Chinese. --- ## Core Skills ### Languages & Frameworks [Solidity](https://docs.soliditylang.org/), [Node.js](https://nodejs.org/en), [TypeScript](https://www.typescriptlang.org/), [React](https://react.dev/), [Next.js](https://nextjs.org/), [Nest.js](https://nestjs.com/) ### AI & LLM Agentic [RAG](https://github.com/w3hc/rukh) systems, multi-model orchestration ([Claude](https://www.anthropic.com/claude), [ChatGPT](https://openai.com/), [Mistral](https://mistral.ai/)), LLM fine-tuning, [Anthropic API](https://docs.anthropic.com/) integrations, [Claude Code](https://www.anthropic.com/claude-code) and LLM dev tooling ([Zhankai](https://github.com/w3hc/zhankai)), prompt and context engineering for production business workflows ### Web3 [Hardhat](https://hardhat.org/), [Foundry](https://book.getfoundry.sh/), [Ethers](https://docs.ethers.org/v6/), [Viem](https://viem.sh/), [Wagmi](https://wagmi.sh/), [Reown](https://reown.com/), [OpenZeppelin](https://www.openzeppelin.com/solidity-contracts) ### Zero-Knowledge Privacy-preserving Web3 design, proof verification workflows, Rate-Limit Nullifiers ([ZK API](https://github.com/w3hc/zk-api)) ### Developer Relations & Training Technical docs, app examples, tutorials, live coding, workshops, hackathon mentoring, cohort facilitation in English ### Engineering Practices [Git](https://git-scm.com/), CI/CD, [Jest](https://jestjs.io/), DevOps, [Docker](https://www.docker.com/) --- ## Career Summary ### CEO & Lead Developer — [Strat](https://strat.cc/) *2020 – Present · Montpellier, France* - Built an AI quoting assistant for [Batappli](https://www.batappli.fr/) ([Systemlog](https://www.systemlog.fr/), 5k+ MAU): tradespeople describe a job in a few words and get a ready-to-send quote in seconds. Shipping to production. - Built a report-drafting tool for a partner running nursing-home compliance inspections — turning hours of manual writing into a guided, structured workflow. - Delivered a custom RAG system for [Systemlog](https://www.systemlog.fr/). - Built an NFT registry for [AFNIC](https://www.afnic.fr/), the institution managing 4 million `.fr` domains. - Developed multiple Web3 projects for [Kleros](https://kleros.io/) and Hexarchia. - Organised Web3 hackathons for [UNESCO](https://www.unesco.org/) and [Université Paris 8](https://www.univ-paris8.fr/). - Trained students at [Epitech Digital School](https://www.epitech.eu/), [STUDI](https://www.studi.com/), and [emlyon business school](https://em-lyon.com/). - Won 1st Prize at the [DAO Global Hackathon](https://daoglobalhackathon.com/past-hackathons) 2022 ([Core DAO](https://coredao.org/) tech track). ### Co-founder & Lead Developer — [W3HC (Web3 Hackers Collective)](https://github.com/w3hc) *Feb 2023 – Present* - Designed and built [Rukh](https://github.com/w3hc/rukh), a Nest.js agentic RAG API with per-session model selection (Claude, ChatGPT, Mistral) and persistent context — [live API](https://rukh.w3hc.org/), [demo video](https://youtu.be/5YDAfwaddNI). - Built [Zhankai](https://github.com/w3hc/zhankai), a CLI that turns any repo into structured markdown for LLM processing — [300+ downloads/month on npm](https://www.npmjs.com/package/zhankai), [demo](https://youtu.be/kdNZEml9Vns). - Created [w3pk](https://github.com/w3hc/w3pk), a passwordless Web3 authentication SDK with encrypted self-custodial wallets — [site](https://w3pk.w3hc.org/), [docs](https://w3pk.w3hc.org/docs#quick-start), [npm](https://www.npmjs.com/package/w3pk). - Implemented [ZK API](https://github.com/w3hc/zk-api), anonymous API access to LLMs using zero-knowledge proofs (TypeScript implementation of the Crapis–Buterin proposal). - Developed [Gov](https://github.com/w3hc/gov), an on-chain voting system for everyday people — [cross-chain contracts](https://github.com/w3hc/gov-crosschain), [docs](https://w3hc.github.io/gov-docs/), [deployer app](https://gov-deployer.netlify.app/). - Implemented the [game of Go in Solidity](https://github.com/julienbrg/game-of-go) — [live demo](https://go-onchain.netlify.app/). ### Developer Relations Engineer — [Arthera](https://www.arthera.net/) *Jul 2023 – Dec 2023* - Wrote the [technical documentation](https://docs.arthera.net/), guides, tutorials, and app examples. - Led technical support online and during hackathons. - Supported partners in integrating the product. - Organised a 150+ attendee event during [EthCC 2023](https://ethcc.io/) in Paris. - Improved the core product: load testing, upgradeable system contracts. ### Developer Relations Engineer — [Aurora Labs](https://aurora.dev/) *Sep 2022 – Dec 2022* - Wrote the technical documentation, guides, tutorials, and app examples. - Led technical support online and during hackathons. ### CEO & CTO — Āto *2020 – 2022* - Designed an IP-licensing product for NFTs in collaboration with an intellectual property lawyer — [demo video](https://www.youtube.com/watch?v=LcGz6WcmZjM), [GitHub org](https://github.com/orgs/ATO-nft). - Contributed to [EIP-5560: Redeemable NFTs](https://eips.ethereum.org/EIPS/eip-5560). - Managed a remote team of six developers. - Built NFTs, an [NFT marketplace template](https://github.com/ATO-nft/nft-deep-wire), and a [live auction service](https://github.com/ATO-nft/auction-app) in Solidity and React. Worked with authors, auction houses, and fine art galleries. ### Head of Communications — [iExec](https://iex.ec/) *2016 – 2019* - Led the ICO, raising 10,000 BTC in under 3 hours. - Grew the Twitter account from 0 to 30k followers in a few months. - Organised 200+ attendee events during Devcon Prague. ### Customer Support Manager — [OpenClassrooms](https://openclassrooms.com/) *2014 – 2016* - Built the customer service function from the ground up, handling over 1,000 requests weekly. ### Chinese Teacher — French National Education *2008 – 2013* - Taught students from CEFR level A1 to B2. --- ## Teaching & Enablement - **Cohort training:** [Epitech Digital School](https://www.epitech.eu/), [STUDI](https://www.studi.com/), [emlyon business school](https://em-lyon.com/) — technical and non-technical audiences. - **Workshops & hackathons:** organised and mentored events for [UNESCO](https://www.unesco.org/), [Université Paris 8](https://www.univ-paris8.fr/), and [EthCC 2023](https://ethcc.io/) (150+ attendees). - **Developer relations:** documentation, tutorials, app examples, live coding, and online support at [Arthera](https://docs.arthera.net/) and [Aurora Labs](https://aurora.dev/). - **Classroom experience:** five years teaching Chinese in the French national education system. - **Delivery languages:** English (bilingual), French (native), Chinese. --- ## Selected Projects ### [Rukh](https://github.com/w3hc/rukh) — agentic RAG API Modular AI framework with a RAG system supporting multiple LLMs and personalised contexts. Pick your model per session, keep context across sessions. [Live API](https://rukh.w3hc.org/) · [Assistant demo](https://julienberanger.com/chat) ### [Zhankai](https://github.com/w3hc/zhankai) — LLM dev tooling CLI that converts a repository into a structured markdown file for LLM processing. [npm](https://www.npmjs.com/package/zhankai) · [Demo video](https://youtu.be/kdNZEml9Vns) ### [w3pk](https://github.com/w3hc/w3pk) — passwordless Web3 auth SDK WebAuthn-based self-custodial wallets, no seed phrases, with stealth addresses and ZK primitives. [Site](https://w3pk.w3hc.org/) · [Docs](https://w3pk.w3hc.org/docs#quick-start) · [npm](https://www.npmjs.com/package/w3pk) · [Next.js starter](https://genji.w3hc.org/) ### [ZK API](https://github.com/w3hc/zk-api) — anonymous LLM access Prepaid, ZK-proof-backed anonymous access to AI models using Rate-Limit Nullifiers. ### [Affix](https://affix.w3hc.org/) — on-chain document authentication Anchor document hashes on-chain so anyone can verify a document is genuine and unaltered, without changing existing workflows. [UI](https://github.com/julienbrg/affix-ui) · [Contracts](https://github.com/julienbrg/affix-contracts) · [Demo video](https://www.youtube.com/watch?v=XoquF4TzP54) ### [Gov](https://github.com/w3hc/gov) — on-chain governance Voting and proposal tooling for real groups, not just DAOs. [Cross-chain](https://github.com/w3hc/gov-crosschain) · [Docs](https://w3hc.github.io/gov-docs/) · [Deployer](https://gov-deployer.netlify.app/) ### [Shebam](https://shebam.w3hc.org/) — regulated on-chain Euro payments Payments settled in EURe via Safe — faster and cheaper than card rails, fully auditable. [GitHub](https://github.com/w3hc/shebam) ### [Avventura](https://avventura.fun/) — AI-driven Web3 RPG Text-based RPG where choices, assets, and progress live on-chain, with LLM-powered storytelling. [GitHub](https://github.com/w3hc/avventura-v3) ### [Wulong](https://github.com/w3hc/wulong) — confidential API Nest.js API running inside a TEE with ML-KEM-1024 encryption and SIWE authentication. --- ## Awards & Recognition - **[Optimism RetroPGF Round 3](https://vote.optimism.io/retropgf/3?search=gov)** — funding recipient for [Gov](https://github.com/w3hc/gov), recognising its contribution to public goods. - **[DAO Global Hackathon](https://daoglobalhackathon.com/past-hackathons) — 1st Prize** ([Core DAO](https://coredao.org/) tech track). - **[GitHub](https://github.com/julienbrg)** — 171 repositories, Arctic Code Vault Contributor. --- ## Education - **Degree in Chinese Studies** (Philosophy, Literature and Arts) — [INALCO](https://www.inalco.fr/), National Institute for Oriental Languages and Civilizations, Paris, 2007. --- ## Languages English (bilingual) · French (native) · Chinese --- title: Ethereum Daily — September 1, 2026 date: 2026-09-01 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-09-01 --- # Ethereum Daily — September 1, 2026 # Ethereum Daily — September 1, 2026 Covering roughly August 31 – September 1, 2026. **Roadmap.** No material new development since yesterday. The Hegotá EIP tier-list ranking and the post-quantum Ethereum "north star" framing from the Protocol Team's monthly sync are still the latest word; client-team preference lists remain due September 10, and Glamsterdam is still targeted for Q4 2026. Nothing new surfaced in the last day. **Governance.** No material new development. The EIP-8141 (Frame Transactions) versus EIP-8130 debate over how to implement native account abstraction in Hegotá remains open, unchanged from yesterday. **Institutional and corporate adoption.** The new item is Bitmine's weekly treasury update, which yesterday's post noted hadn't posted yet. Disclosed August 31, it's Bitmine's 65th consecutive weekly purchase: 53,501 ETH bought, taking total holdings to 5,901,112 ETH — about 4.9% of ETH's 120.7 million supply, and just 0.1 percentage point short of the "Alchemy of 5%" target it set fifteen months ago. Combined crypto, cash, and equity treasury now stands at $15.6 billion, with 86% of its ETH (5,067,309 ETH, ~$12.7 billion) staked, projected to generate roughly $335 million a year at current yields. It remains the largest publicly disclosed corporate Ethereum treasury. **Stats.** ETH trades around $2,445, down modestly (roughly 0.4–0.6% on the day) from yesterday's ~$2,505. The validator set continues its steady climb: 903,892 active validators (up from 903,604) with 42.6 million ETH staked, or 34.93% of supply — again a new high in this run. The entry queue holds 2,073,824 ETH at a 36-day wait, slightly longer than yesterday's 35 days as new stake keeps arriving; the exit queue remains empty. Gas is cheaper still at 0.047 gwei average, down from 0.061. Ethereum DeFi TVL sits at $49.1 billion, roughly flat versus yesterday's $49.2 billion. On ETF flows, Monday (August 31) posted a net-zero print — the first non-positive day after the ten-session inflow streak — leaving cumulative net inflows unchanged at roughly $12.97 billion since launch; no fresher print has posted yet today. --- **Sources:** - [BitMine Buys 53,501 ETH in 65th Straight Weekly Purchase, Treasury Hits $15.6B — Crypto Times](https://www.cryptotimes.io/2026/08/31/bitmine-buys-53501-eth-in-65th-straight-weekly-purchase-treasury-hits-15-6b/) - [Ethereum Validator Queue](https://www.validatorqueue.com/) - [Ethereum chain overview — DefiLlama](https://defillama.com/chain/ethereum) - [Ethereum Gas Tracker — Etherscan](https://etherscan.io/gastracker) - [Ethereum price — CoinDesk](https://www.coindesk.com/price/ethereum) - [Ethereum ETF flows — Farside Investors](https://farside.co.uk/eth/) --- title: Pourquoi Fortinet classe melenchon2027.fr en « Malware » date: 2026-08-31 lang: fr-FR author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/jlm-website --- # Pourquoi Fortinet classe melenchon2027.fr en « Malware » ## En deux mots Le site [melenchon2027.fr](https://melenchon2027.fr/) est signalé comme malveillant par **un seul** moteur de sécurité sur 92, Fortinet. Tous les autres le considèrent comme propre. Le site lui-même ne présente aucun signe d'infection. Ce signalement unique a des effets bien réels : **X (Twitter) affiche un écran d'avertissement à chaque fois qu'on clique sur le lien**, et les organisations équipées de pare-feux Fortinet bloquent purement et simplement l'accès. L'explication la plus probable : **le domaine a eu un propriétaire précédent qui le revendait**, et il a hérité d'une mauvaise réputation de cette période. La France insoumise l'a racheté ensuite, mais le classement chez Fortinet n'a jamais été corrigé. **Ce qu'il faut faire :** déposer une demande de révision sur le [formulaire FortiGuard](https://www.fortiguard.com/faq/wfratingsubmit), en expliquant le changement de propriétaire. --- ## 1. Les faits ### Le verdict VirusTotal [Rapport complet](https://www.virustotal.com/gui/url/8d5a6b5676b5306328c2f43970cc4bea0aca292174c61d54aa5c65306a8e581d) — analyse du 31 août 2026. | | | | -------------------------------- | -------------------- | | Détections | **1 / 92** | | Seul moteur positif | Fortinet → `Malware` | | Première soumission à VirusTotal | 4 mai 2026 | Tous les autres moteurs concluent au vert, y compris ceux qui comptent sur ce type de menace : Google Safe Browsing, Kaspersky, ESET, BitDefender, Sophos, Yandex Safebrowsing, [URLhaus](https://urlhaus.abuse.ch/), OpenPhish, PhishTank, [Sucuri SiteCheck](https://sitecheck.sucuri.net/). **Point important : Abusix affiche `Clean`.** Il apparaît juste sous Fortinet dans la liste, ce qui prête à confusion, mais il ne signale rien. [Abusix](https://abusix.com/) est une société anti-abus qui publie des listes de blocage DNS pour la messagerie ([Mail Intelligence](https://abusix.com/blog/abusix-mail-intelligence-domain-blocklist/), [service de délistage](https://abusix.com/blog/blocklist-lookup-delist-service/)). Elle n'est pas en cause ici. ### Les catégorisations concurrentes Deux autres moteurs classent explicitement le domaine, et pas dans la même famille que Fortinet : - **Forcepoint ThreatSeeker** → `political organizations` - **BitDefender** → `society` Forcepoint fait exactement le même métier que FortiGuard : de la catégorisation d'URL pour du filtrage web en entreprise. Fortinet est donc isolé sur le classement lui-même, pas seulement sur le verdict de sécurité. ### Ce que dit FortiGuard Vérification sur le [Web Filter Lookup](https://www.fortiguard.com/webfilter) : | | | | ---------------- | ------------------ | | Catégorie | Malicious Websites | | Niveau de risque | High Risk | | Groupe | Security Risk | ⚠️ **Attention au piège de lecture.** Le paragraphe descriptif et la mention « Strong confidence of malicious intent » ne sont pas un verdict rédigé pour ce site : c'est le texte fixe de la catégorie, affiché à l'identique pour n'importe quel domaine qui s'y trouve. Voir la [liste des catégories FortiGuard](https://www.fortiguard.com/webfilter/categories). La seule information spécifique au domaine, c'est son affectation. Fortinet ne publie ni preuve, ni date, ni échantillon. **Conséquence pratique :** le groupe « Security Risk » est bloqué par défaut dans les profils de filtrage des FortiGate. Dans toute organisation équipée Fortinet en configuration standard — entreprises, écoles, collectivités — le site est inaccessible. Pour un site qui collecte des parrainages, ce n'est pas anodin. ### L'avertissement sur X (Twitter) Le problème a été signalé publiquement par **Stéphane Bortzmeyer** : [x.com/bortzmeyer/status/2094498426440348107](https://x.com/bortzmeyer/status/2094498426440348107). Quand un utilisateur clique sur un lien vers melenchon2027.fr depuis X, la plateforme intercale un écran d'avertissement avant de le laisser passer. X explique que ce message s'affiche lorsque l'URL figure dans une base de liens potentiellement dangereux — hameçonnage, logiciels malveillants ou spam ([documentation X](https://help.x.com/fr/safety-and-security/about-unsafe-links), [approche du blocage de liens](https://help.x.com/en/safety-and-security/phishing-spam-and-malware-links)). ⚠️ **Réserve importante :** X ne publie pas la liste des fournisseurs qui alimentent cette base. L'attribution à Fortinet est cohérente — c'est le seul moteur sur 92 à signaler le domaine — mais elle n'est pas confirmée par X. **L'impact est direct.** Le compte de Jean-Luc Mélenchon renvoie vers melenchon2027.fr depuis son profil et depuis ses publications, et X est un canal de diffusion majeur pour une collecte de parrainages. Chaque avertissement interposé ajoute un frottement entre le lien et le formulaire, et jette un doute sur la légitimité du site auprès de gens qui n'ont aucun moyen de vérifier. **Précédent documenté :** en janvier 2024, Twitter avait marqué comme « potentiellement dangereux » des liens vers plusieurs sites d'information français, dont Next INpact. Les sites concernés n'avaient reçu aucune explication ni point de contact, seulement un formulaire à remplir ([Next](https://next.ink/brief_article/twitter-a-considere-des-liens-vers-des-sites-de-presse-comme-potentiellement-dangereux/)). Le mécanisme d'opacité est donc connu et récurrent. ### L'analyse technique du site (31 août 2026) Rien dans le rapport ne correspond à ce que décrit la catégorie « Malicious Websites » : - **111 requêtes réseau**, toutes vers `melenchon2027.fr`, toutes en HTTP 200. Aucune vers un domaine tiers inconnu. - **Trackers détectés : aucun.** - **Un seul cookie**, `cf_clearance` — cookie technique Cloudflare, pas du pistage. - **Une seule ligne de console**, un log jQuery Migrate parfaitement banal. - Les variables JavaScript globales listées (`sharedStorage`, `fetchLater`, `onpagereveal`, `fence`) sont des API natives du navigateur, pas du code injecté. - **Liens sortants** vers actionpopulaire.fr, lafranceinsoumise.fr, les réseaux sociaux, un Discord. Rien de signalé. Une compromission active laisserait des traces visibles : scripts chargés depuis un domaine étranger, redirections, JavaScript obfusqué, erreurs console. Il n'y a rien. ### L'état du WordPress | Composant | Version | Commentaire | | ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | WordPress | 7.1 | Dernière version majeure, [sortie le 19 août 2026](https://wordpress.org/news/category/releases/) | | Elementor | 4.2.3 | Dernière stable ou à une version près ([changelog](https://github.com/elementor/elementor/blob/main/changelog.txt)) | | Thème | hello-elementor 3.0.1 | Thème minimal officiel d'Elementor | | Plugins visibles | `complianz-gdpr`, `lfi-contribution-programme` | Le second est du développement maison | Passer une version majeure en douze jours sur un site en production, c'est au-dessus de la moyenne. À titre de comparaison, WordPress.org marque les versions 7.0 à 7.0.3 comme non sécurisées. Le site n'est pas dans ce cas. **Ça affaiblit fortement l'hypothèse du WordPress négligé.** ### L'historique du domaine Source faisant autorité : le WHOIS de l'[AFNIC](https://www.afnic.fr/), registre du `.fr`. | | | | -------------------------- | ----------------------------------------------------- | | Date de création | **27 avril 2021** | | Date d'expiration | 27 avril 2027 | | Bureau d'enregistrement | GANDI | | Titulaire | Accès restreint | | Contacts admin & technique | La France insoumise, 25 passage Dubail, 75010 Paris | | Serveurs de noms | `jessica.ns.cloudflare.com`, `roan.ns.cloudflare.com` | | DNSSEC | **Inactif** | ### 🔑 La découverte décisive [Capture Wayback du 31 mars 2023](https://web.archive.org/web/20230331163035/http://melenchon2027.fr/) : ```html Melechon 2027 - Nom de domaine à vendre ...

Nom de domaine à vendre !

info@melenchon2027.fr

``` **En mars 2023, le domaine était détenu par un tiers qui cherchait à le revendre.** Du cybersquatting classique sur un nom de personnalité et une échéance électorale à venir. La faute de frappe dans le titre (« Melechon ») trahit une opération en volume, pas un dépôt soigné. Autres éléments de cette capture : - Page **statique** (`main.css`, `script.js`) — **aucun WordPress** à cette date. - Un identifiant Google Analytics : **`G-TKP1X12FW5`**. Le vendeur mesurait le trafic pour évaluer son bien. - Capture collectée par la collection [Certificate Transparency](https://archive.org/details/certificate-transparency) d'archive.org, donc déclenchée par l'émission d'un certificat TLS. ### Chronologie reconstituée | Date | Événement | | ---------------- | ------------------------------------------------------------------------------ | | 27 avril 2021 | Création du domaine chez Gandi | | 17 décembre 2021 | Première capture Wayback | | Mars 2023 | Page « Nom de domaine à vendre » — **propriétaire tiers** | | Janvier 2024 | Premiers médias WordPress (`/wp-content/uploads/2024/01/`) | | 22 janvier 2026 | Dernière mise à jour au registre (renouvellement, transfert ou changement DNS) | | Mai 2026 | Première soumission à VirusTotal ; campagne active | | 31 août 2026 | 1/92 — Fortinet seul | Sur 2021-2024, la Wayback Machine ne connaît que **1 URL unique** en HTML pour 12 captures. Contre 75 captures sur les vingt derniers mois. La bascule est nette. --- ## 2. Les hypothèses ### 🥇 Réputation héritée de la période de revente — la plus crédible Le domaine a passé une partie de sa vie dans un portefeuille de revente. C'est précisément le type de configuration qui laisse des résidus dans les bases de réputation : les domaines parqués sont hébergés en masse sur des infrastructures partagées, servent régulièrement d'émetteurs de spam ou de maillons de redirection, et se font classer **en bloc**. FortiGuard dispose bien d'une catégorie `Domain Parking`, mais un domaine pris dans un lot mal fréquenté peut atterrir ailleurs. Le classement daterait de cette période et n'aurait jamais été révisé après le changement de mains. **Ce qui la soutient :** la page de vente de 2023 est documentée. Le classement ne correspond à aucune observation actuelle. Les bases de catégorisation ne se recorrigent pas d'elles-mêmes. ### 🥈 Erreur d'un classifieur automatique FortiGuard décrit [une base de plus de 300 millions d'URL en 92 catégories, alimentée par des modèles d'apprentissage automatique](https://www.fortinet.com/content/dam/fortinet/assets/solution-guides/sb-fortiguard-url-filtering-service.pdf). Un faux positif est toujours possible sur des signaux du type IP Cloudflare partagée, formulaires de collecte de données, pic de trafic. **Ce qui l'affaiblit :** n'explique pas pourquoi Forcepoint, sur le même métier, arrive à `political organizations`. ### 🥉 Signalements en masse via le formulaire public — peu probable N'importe qui peut soumettre une URL au [formulaire FortiGuard](https://www.fortiguard.com/faq/wfratingsubmit) : pas de compte, pas de vérification de propriété du domaine. La liste déroulante contient `Malicious Websites`, `Phishing`, `Spam URLs`, `Extremist Groups`, `Terrorism`. Le formulaire fonctionne donc dans les deux sens. **Mais** une soumission est une suggestion, pas une commande. Fortinet indique que [l'équipe examine, vérifie, puis modifie la note](https://community.fortinet.com/t5/FortiGate/Technical-Tip-Submitting-a-request-to-review-the-category-of-an/ta-p/195388), sous 24 heures environ, et le formulaire depuis un FortiGate passe par un CAPTCHA. Aucun seuil n'est publié, et un système où N soumissions anonymes basculeraient un domaine serait trivialement utilisable contre un concurrent ou un opposant. **Statut : ni confirmable ni réfutable de l'extérieur. Ce n'est pas l'explication la plus économique.** ### ❌ Hypothèses écartées en cours de route | Hypothèse | Pourquoi elle tombe | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | WordPress compromis puis nettoyé | En mars 2023 il n'y a pas de WordPress, juste une page statique. Rien à compromettre. | | Indicateur partagé via la [Cyber Threat Alliance](https://www.fortiguard.com/cta) | Si c'était le cas, d'autres membres de l'alliance signaleraient aussi. Tous au vert. | | Domaine ancien (2004) | Artefact de parsing d'un agrégateur WHOIS tiers. Voir ci-dessous. | --- ## 3. Ce que vaut le verdict de Fortinet > Précision : je n'ai pas pu lire le fil de Stéphane Bortzmeyer, X bloquant l'accès automatisé. Je ne sais donc pas ce qu'il avance exactement, et ce qui suit est indépendant de son analyse. Je n'ai par ailleurs trouvé **aucun classement public** établissant que Fortinet serait parmi les moteurs les moins fiables. Ce qui suit est structurel, pas comparatif. ### Ce n'est pas un scanner, c'est un catalogueur C'est le point central. FortiGuard n'analyse pas le contenu à la recherche de code malveillant comme le ferait un antivirus. C'est un service de **filtrage web d'entreprise**, conçu pour appliquer des politiques d'usage : bloquer la pornographie, les jeux d'argent, les réseaux sociaux au bureau. « Malicious Websites » est l'une des 92 catégories de cette grille, au même titre que « Sports » ou « Shopping ». VirusTotal traduit ensuite cette catégorie en verdict `Malware`. Ce faisant, il donne à un rangement de politique d'entreprise l'apparence d'un résultat d'analyse antivirus. **La confusion est dans la traduction, pas dans la donnée d'origine.** ### Opacité Ni date, ni preuve, ni historique, ni score de confiance. Un site classé n'a aucun moyen de savoir depuis quand ni pourquoi. Comparaison utile : Google Safe Browsing notifie les propriétaires via la Search Console et détaille les URL en cause. ### Pas d'autocorrection La catégorie persiste jusqu'à ce que quelqu'un demande **manuellement** une révision. Un site nettoyé, revendu ou intégralement reconstruit — exactement le cas ici — reste classé indéfiniment. C'est le cœur du problème. ### Ce que dit la recherche académique Ces travaux portent sur VirusTotal dans son ensemble, pas sur Fortinet en particulier. - [**Opening the Blackbox of VirusTotal**](https://gangw.cs.illinois.edu/imc19-virus.pdf) (IMC 2019) — les chercheurs ont monté leurs propres sites de hameçonnage pour mesurer les moteurs. Résultats : même les meilleurs manquaient 30 % des sites ; certains moteurs semblent recopier les listes noires des autres plutôt que d'analyser ; et VirusTotal ne reflète pas toujours la meilleure capacité de détection d'un éditeur. - [**A Large Scale Study and Classification of VirusTotal Reports**](https://arxiv.org/pdf/2205.13155) (2022) — étiquettes contradictoires entre moteurs, avec un taux de faux positifs atteignant **25,43 %** sur les URL de hameçonnage par vote majoritaire. - Conséquence méthodologique : **les travaux sérieux n'utilisent jamais une détection unique.** Le seuil courant est de deux drapeaux minimum, souvent cinq. ### En résumé **1 détection sur 92 n'est pas un signal faible, c'est du bruit.** Ce qui n'empêche pas ce bruit d'avoir des effets parfaitement réels : blocage en entreprise, avertissement sur X, doute jeté sur un site légitime. --- ## 4. Erreurs commises pendant l'analyse Pour que le raisonnement soit auditable, voici ce qui a été corrigé en route. **« Abusix signale le site »** — Non. Abusix affiche `Clean`. Confusion de lecture liée à sa position dans la liste, juste sous Fortinet. **« Le domaine date de 2004 »** — Un agrégateur WHOIS tiers annonçait une création au 8 mars 2004. Impossible : le `.fr` [n'a été ouvert aux particuliers que le 20 juin 2006](https://www.afnic.fr/fr/l-afnic-en-bref/actualites/actualites-generales/2500/show/l-afnic-ouvre-le-fr-aux-particuliers-a-partir-du-20-juin-2006.html), et avant cette date la [charte de nommage](https://www.afnic.fr/wp-media/uploads/2021/07/afnic-charte-de-nommage-2021-09-15.pdf) imposait de justifier d'un droit sur le nom. Second signe : création annoncée le 8 mars mais expiration le 27 avril, alors que sur un `.fr` le renouvellement s'aligne sur l'anniversaire de création. **Toujours vérifier au registre plutôt que chez un agrégateur.** **« Le WHOIS élimine l'hypothèse du propriétaire précédent »** — Faux, et c'était l'erreur la plus coûteuse. Sur un `.fr`, la date de création **survit au transfert de titulaire** : elle date le domaine, pas sa propriété. Le champ « Titulaire » était par ailleurs en accès restreint ; seuls les contacts admin et technique affichaient LFI, c'est-à-dire l'état actuel. La capture Wayback a montré l'inverse deux tours plus tard. **Hypothèse du signalement en masse formulée trop légèrement** — présentée initialement comme plausible sans mention de l'étape de vérification humaine. **`dig https://melenchon2027.fr/`** — `dig` attend un nom d'hôte, pas une URL. La commande a interrogé le nom littéral `https://melenchon2027.fr/.`, d'où le `NXDOMAIN` synthétisé par le résolveur. La bonne forme : `dig melenchon2027.fr` ou `dig A melenchon2027.fr`. --- ## 5. Recommandations ### Priorité 1 — Faire corriger le classement Déposer une demande sur le **[formulaire de révision FortiGuard](https://www.fortiguard.com/faq/wfratingsubmit)** (ouvert à tous, pas seulement au propriétaire ; canal alternatif : [Live URL Rating Support](https://url.fortinet.net/rate/submit.php)). Champs à remplir : - **URL** : `https://melenchon2027.fr/` - **Catégorie suggérée** : `Political Organizations` - **Capture d'écran** : le site actuel (2 Mo max) - **Commentaire** — les cinq arguments, dans cet ordre : 1. Changement de titulaire avec reconstruction intégrale du contenu — [preuve Wayback du 31 mars 2023](https://web.archive.org/web/20230331163035/http://melenchon2027.fr/), le domaine était alors mis en vente par un tiers 2. Titulaire actuel : La France insoumise (contacts au WHOIS AFNIC) 3. 1 détection sur 92 sur [VirusTotal](https://www.virustotal.com/gui/url/8d5a6b5676b5306328c2f43970cc4bea0aca292174c61d54aa5c65306a8e581d) 4. Forcepoint classe en `political organizations`, BitDefender en `society` 5. Aucune ressource tierce, aucun tracker, 111 requêtes en première partie toutes en 200 Délai annoncé : environ 24 heures. Suivre les [mises à jour de la base](https://www.fortiguard.com/updates/webfiltering) et revérifier ensuite sur le [lookup](https://www.fortiguard.com/webfilter). Documentation Fortinet : [procédure de recatégorisation](https://community.fortinet.com/t5/FortiGate/Technical-Tip-Web-site-appears-to-be-placed-in-wrong-category-by/ta-p/195659). ### Priorité 2 — Compléter le dossier - **Borner la date de rachat** : parcourir les [captures Wayback](https://web.archive.org/web/20230331163035*/http://melenchon2027.fr/) entre mars 2023 et mi-2024 pour trouver la dernière page « à vendre » et la première page LFI. - **Pivoter sur l'identifiant Analytics `G-TKP1X12FW5`** via un moteur de recherche de code source comme [PublicWWW](https://publicwww.com/) : il donne les autres domaines du portefeuille du revendeur. Si ce portefeuille est mal noté, la réputation héritée devient l'explication complète. - **Historique des certificats** sur [crt.sh](https://crt.sh/) : la plus ancienne émission borne l'usage réel du domaine. - **Rapport domaine VirusTotal** : [virustotal.com/gui/domain/melenchon2027.fr](https://www.virustotal.com/gui/domain/melenchon2027.fr) — whois, DNS passif, fichiers en communication avec le domaine. - **Tester les variantes** dans le lookup FortiGuard : `www.melenchon2027.fr`, `ng.melenchon2027.fr` (vu dans les liens sortants), une sous-page. FortiGuard note par URL ; savoir si le classement porte sur le domaine entier ou un hôte change l'interprétation. ### Priorité 3 — Hygiène (aucune faille, mais à corriger) - **Activer DNSSEC.** Actuellement inactif. Sur un domaine qui collecte des parrainages, ça ferme une classe d'attaques par empoisonnement de cache. - **Supprimer la balise `generator`.** Elle publie en clair `WordPress 7.1` et `Elementor 4.2.3`. Le débat sur son utilité est ancien, mais ça permet de filtrer des cibles en quelques secondes le jour où une 0-day tombe. - **Restreindre `/wp-json/wp/v2/users`.** L'API REST révèle par défaut les identifiants des auteurs, soit la moitié d'un couple login/mot de passe. - **Auditer le plugin maison `lfi-contribution-programme`.** Développement sur mesure : pas de relecture communautaire, pas de mise à jour automatique, pas de bug bounty. Dans l'immense majorité des compromissions WordPress, le vecteur d'entrée est un plugin — et un plugin maison est le moins audité de tous. - **Surveiller les autres listes** : [MXToolbox](https://mxtoolbox.com/problem/blacklist/abusix-mail-intelligence-blacklist) pour l'aspect messagerie, [Sucuri SiteCheck](https://sitecheck.sucuri.net/) pour l'intégrité du site. --- ## 6. Ce qu'on ne sait toujours pas Par transparence, les zones d'ombre qui subsistent : - **La date exacte du classement FortiGuard.** Non publiée. C'est l'élément qui trancherait définitivement entre réputation héritée et erreur récente. - **L'identité du revendeur** et l'état du reste de son portefeuille. - **Ce que le domaine servait entre décembre 2021 et mars 2023**, hors captures d'accueil. - **L'analyse VirusTotal ne porte que sur la page d'accueil**, à un instant donné, depuis un nœud Cloudflare situé à Atlanta. Une injection sur une sous-page, ou servie de façon conditionnelle selon le pays ou le user-agent, passerait sous le radar. - **Une mise à jour du cœur n'efface pas une porte dérobée installée avant.** Un site parfaitement à jour peut rester compromis. Ici, rien dans les 111 requêtes ne va dans ce sens, mais l'absence de preuve n'est pas une preuve d'absence. - **La source exacte de l'avertissement sur X.** X ne publie pas ses fournisseurs de données. Le lien de causalité avec Fortinet est vraisemblable, pas démontré. Si l'avertissement persiste après une correction chez FortiGuard, c'est qu'il vient d'ailleurs — et il faudra passer par le [formulaire de contestation de X](https://help.x.com/fr/safety-and-security/about-unsafe-links). --- _Document établi le 31 août 2026, à partir du rapport VirusTotal du même jour, du WHOIS AFNIC, du lookup FortiGuard et des archives de la Wayback Machine._ --- title: Ethereum Daily — August 31, 2026 date: 2026-08-31 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/eth-daily-2026-08-31 --- # Ethereum Daily — August 31, 2026 # Ethereum Daily — August 31, 2026 Covering roughly August 29–31, 2026. **Roadmap.** The new item since yesterday is process, not a proposal. The Ethereum Foundation's Protocol Team published its second monthly all-Protocol sync, and it reframes how Hegotá is being scoped. The cluster — roughly 60 researchers and engineers — is ranking a tier list of the 62 EIPs still proposed for the fork alongside an explicit exclusion list, and has built a "steelman" tier for contested proposals graded on a one-to-three chili-pepper scale of disagreement. Two things matter here. The candidate pool has quietly narrowed from the 66 figure quoted since mid-August. And post-quantum Ethereum is now named as a north star to be pursued in parallel with fork work rather than deferred in order to ship forks — which promotes yesterday's EIP-8394 deposit-contract proposal from a one-off to a standing track. The team also conceded a Glamsterdam lesson: scoping went badly where EIP descriptions undersold implementation complexity. Client-team preference lists are still due September 10, and Glamsterdam is still targeted at Q4 2026. **Governance.** No material new development. The Ethereum Foundation leadership situation is unchanged since yesterday. The only live disagreement is narrow and technical: when core devs moved EIP-8141 (Frame Transactions) to Scheduled for Inclusion on August 27, they committed to shipping native account abstraction in Hegotá while explicitly leaving the implementation open. EIP-8141 versus EIP-8130 is still unsettled, and Nethermind's Ben Adams publicly backed 8141 on Friday. **Institutional and corporate adoption.** Nothing new in the last 24 hours. It was a weekend; no bank, fund, or corporate announcement landed, and Bitmine has not yet posted the weekly treasury update that usually follows its Monday cadence. The most recent hard data point remains Thursday's ETF print, covered below. **Apps adoption.** The standout is Credifi, which launched undercollateralized micro-loans on Base. Wallets with an Ethos Network reputation score of 1,800 or above can borrow up to 3,000 USDC with no collateral, no KYC, and no liquidation risk, at roughly 10% APR, with lending infrastructure running through a private Euler vault. The design swaps the usual collateral test for an onchain credibility check, and a default costs the borrower reputation rather than a position. It is small in dollar terms, but it is the first credible attempt in a while to underwrite onchain lending on something other than posted collateral. Separately, on L1, Ethena extended USDe's backing beyond crypto perpetuals into equity perpetuals — a market now carrying 6.2 billion dollars of open interest, up roughly tenfold since March — tying the stablecoin's growth to equities basis rather than crypto funding alone. Ethena holds over 4.5 billion dollars in TVL. **Stats.** The validator set keeps grinding higher: 903,604 active validators, up from roughly 902,000–903,000 yesterday, with 42.5 million ETH staked, or 34.86% of supply — above the ~34% reading noted yesterday and at the top of its historical range. The entry queue holds 2,033,114 ETH at a 35-day, 7-hour wait, still shortening from about 36 days yesterday, and the exit queue remains empty. Staking APR sits at 2.62%. Gas is unchanged and cheap at 0.061 gwei average, with network utilization near 60.6%. On L2s, L2BEAT shows roughly 30.4 billion dollars in total value secured and rollup throughput near 694 user operations per second, with 97.7% of blob data now posted by layer 2s. Ethereum DeFi TVL is 49.2 billion dollars, a shade below yesterday's ~50 billion, against 148.0 billion dollars of stablecoins and 15.0 billion dollars of tokenized RWAs on mainnet; the chain processed 2.03 million transactions across 503,637 active addresses in the last 24 hours, with weekly DEX volume up 10.4% to 10.4 billion dollars. On price and flows: ETH is around 2,505 dollars, up about 1.9% on the day and back above 2,500 after yesterday's 2,436. ETF flows have no new print, since markets were closed over the weekend — Thursday's 102.1 million dollar net inflow, the tenth straight positive session, remains the latest, leaving spot ETH ETFs at 15.23 billion dollars in net assets and 12.98 billion dollars of cumulative inflows since launch. --- **Sources:** - [Post Quantum Ethereum North Star — ETH Daily](https://ethdaily.io/post-quantum-ethereum-north-star) - [Account Abstraction Scheduled For Hegotá — ETH Daily](https://ethdaily.io/account-abstraction-scheduled-for-hegota) - [Extending the Basis Trade to Equities — Ethena](https://ethena.fi/blog/extending-the-basis-trade-to-equities) - [Ethereum Validator Queue](https://www.validatorqueue.com/) - [Ethereum Gas Tracker — Etherscan](https://etherscan.io/gastracker) - [Ethereum chain overview — DefiLlama](https://defillama.com/chain/ethereum) - [L2BEAT](https://l2beat.com/) - [Spot Ethereum ETFs see 102m net inflow on August 28 — KuCoin](https://www.kucoin.com/news/flash/ethereum-spot-etfs-see-102m-net-inflow-on-august-28-marking-10th-consecutive-day-of-inflows) --- title: Ethereum Daily — August 30, 2026 date: 2026-08-30 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-08-30 --- # Ethereum Daily — August 30, 2026 # Ethereum Daily — August 30, 2026 Covering roughly August 28–30, 2026. **Roadmap.** One new item since yesterday: on August 28, Ethereum developers floated EIP-8394, a proposal to restructure the validator deposit contract so the network's roughly $104 billion in staked ETH can eventually migrate to quantum-resistant cryptography. It doesn't pick a post-quantum signature scheme itself — that's left to a future EIP — but it would stop new BLS-based deposits and let existing validators keep their current keys without forced migration. The Foundation frames the quantum threat as "not imminent," with a 2029 target for post-quantum core infrastructure. This runs alongside, not instead of, the EIP-8141 (Frame Transactions) and FOCIL work already locked in for Hegotá; client teams are still on track to rank the remaining Hegotá candidates by September 10, and Glamsterdam/Platåberget testnet prep is unchanged. **Governance.** No material new development since yesterday. Coverage of the Ethereum Foundation's leadership shakeup continues, but it's recap and commentary on the existing situation rather than a fresh event. **Institutional and corporate adoption.** No new announcement in the last 24 hours. Bitmine's August 24 purchase of 32,447 ETH remains the most recent corporate-accumulation data point. **Stats.** The network-level story today: Ethereum's validator exit queue hit zero, and the entry queue wait time fell from roughly 43–44 days to about 36 days as the active validator count climbed from around 887,000 at the start of August to roughly 902,000–903,000 now — validators are staying put and new ones are onboarding faster, even after ETH's pullback. Staking participation is holding near 34% of supply; a proposal floated earlier this month (EIP-8361) would taper validator issuance as that ratio approaches 50%, but that's unchanged since then. Gas stays cheap, averaging around 0.06 gwei with network utilization near 60%. On price and flows: ETH is trading around $2,436, down about 3% in 24 hours; BlackRock's ETHA added another $83.8 million on August 28, extending its streak of daily inflows to roughly $1.02 billion over nine-plus sessions. DeFi TVL is little changed from yesterday's ~$50 billion reading. --- **Sources:** - [Ethereum Developers Propose EIP-8394 to Guard $104 Billion Staked ETH from Quantum Threats](https://en.coinotag.com/ethereum-eip-8394-quantum-safe-staking-104-billion) - [Why Ethereum's exit queue hitting 0 may not be a good sign - Yet](https://ambcrypto.com/why-ethereums-exit-queue-hitting-0-may-not-be-a-good-sign-yet/) - [Ethereum Staking Hits 34% as EIP-8361 Proposes Tapered Validator Rewards](https://www.kucoin.com/news/flash/ethereum-staking-hits-34-as-eip-8361-proposes-tapered-validator-rewards) - [Ethereum Gas Tracker | Etherscan](https://etherscan.io/gastracker) - [BlackRock's Ethereum ETF Posts $1.02 Billion in Inflows Over 9 Days as ETH Price Surges](https://www.cryptotimes.io/2026/08/29/blackrocks-ethereum-etf-posts-1-02-billion-in-inflows-over-9-days-as-eth-price-surges/) - [Ethereum price today, ETH to USD live price, marketcap and chart | CoinDesk](https://www.coindesk.com/price/ethereum) --- title: Ethereum Daily — August 29, 2026 date: 2026-08-29 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-08-29 --- # Ethereum Daily — August 29, 2026 # Ethereum Daily — August 29, 2026 Covering roughly August 27–29, 2026. **Roadmap.** The one real move since yesterday: Ethereum core developers elevated EIP-8141 ("Frame Transactions," native account abstraction) to Scheduled-for-Inclusion status for the Hegotá upgrade, alongside the already-locked FOCIL (EIP-7805). That commits smart-wallet features — like sponsored gas and social key recovery — to the 2027 fork, even though the exact implementation mechanism is still being debated (some developers flagged unresolved overlap with the rival EIP-8130 proposal). Client teams are still on track to rank the remaining Hegotá candidates by September 10. Glamsterdam/Platåberget testnet prep is otherwise unchanged from yesterday. **Governance.** No fresh governance news in the last day; the Ethereum Foundation's summer leadership shakeup remains the backdrop but hasn't produced new developments since yesterday's post. **Institutional and corporate adoption.** No new announcement in the last 24 hours. Bitmine's August 24 purchase of 32,447 ETH (pushing its treasury toward roughly $11 billion) is still the most recent corporate-accumulation data point. **Stats.** ETH is trading around $2,486 as of this morning, down about 1.25% on the day, but still up roughly 7.9% over the past week and 32.8% over the past month. Spot ETH ETFs just had their strongest day in ten months: $225.8 million in net inflows on August 27, up from $192.4 million on August 26, capping a nine-day inflow streak worth $1.42 billion since August 17 — BlackRock's ETHA fund alone accounts for 72% of that streak's total. Staking and DeFi TVL levels are little changed from yesterday's ~35%-of-supply / ~$50 billion readings. --- **Sources:** - [Ethereum Core Developers Lock Account Abstraction EIP-8141 Into Hegotá Upgrade](https://en.coinotag.com/ethereum-core-developers-eip-8141-hegota-account-abstraction) - [Ethereum ETFs Take $226M in a Day, Almost Matching Bitcoin's Haul](https://decrypt.co/376810/ethereum-etfs-take-226m-in-a-day-almost-matching-bitcoins-haul) - [Ethereum ETF Flow (US$m) – Farside Investors](https://farside.co.uk/eth/) - [Ethereum price today, ETH to USD live price, marketcap and chart | CoinDesk](https://www.coindesk.com/price/ethereum) - [Bitcoin and ethereum prices today, Friday, August 28, 2026](https://finance.yahoo.com/personal-finance/investing/article/bitcoin-and-ethereum-prices-today-friday-august-28-2026-bitcoin-moves-above-81000-before-falling-back-111816647.html) --- title: Ajouts du 28 août 2026 — guide d'intégration client date: 2026-08-28 lang: fr-FR author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/roac-guide-integration-client --- # Ajouts du 28 août 2026 — guide d'intégration client Récapitulatif des trois dernières PR mergées, destiné à l'équipe qui intègre l'API côté client (UI registrar). | PR | Titre | Apport | |---|---|---| | [#37](https://github.com/w3hc/roac/pull/37) | `Add IpfsModule` | Nouveau endpoint `POST /ipfs/cid` : calcul d'un CID IPFS sans upload ni pinning | | [#39](https://github.com/w3hc/roac/pull/39) | `Add images upload and processing` | Upload du fichier source et de l'image d'aperçu dans `POST /nft/create` et `POST /nft/register` + endpoint public `GET /nft/preview/:sourceFileHash` | | [#41](https://github.com/w3hc/roac/pull/41) | `Make NFT creation optional` | Champ `nftCreation` : enregistrer une œuvre sans frapper de NFT, et frapper plus tard | **Rien n'est cassé.** Tous les nouveaux champs de requête sont optionnels (à une exception près, décrite au §3). Un client existant qui n'envoie aucun de ces champs continue de fonctionner exactement comme avant. --- ## Sommaire 1. [Rappels d'authentification](#1-rappels-dauthentification) 2. [Upload de fichiers (PR #39)](#2-upload-de-fichiers-pr-39) - [Format d'un fichier](#21-format-dun-fichier) - [`POST /nft/create` avec fichiers](#22-post-nftcreate-avec-fichiers) - [`POST /nft/register` avec fichiers](#23-post-nftregister-avec-fichiers) - [`GET /nft/preview/:sourceFileHash` (public)](#24-get-nftpreviewsourcefilehash-public) - [Le `sourceFileHash`](#25-le-sourcefilehash) 3. [Création de NFT optionnelle (PR #41)](#3-création-de-nft-optionnelle-pr-41) 4. [Calcul de CID IPFS (PR #37)](#4-calcul-de-cid-ipfs-pr-37) 5. [Configuration serveur](#5-configuration-serveur) 6. [Erreurs](#6-erreurs) 7. [Checklist d'intégration](#7-checklist-dintégration) --- ## 1. Rappels d'authentification Tous les endpoints ci-dessous exigent une session valide **avec rôle Admin ou Registrar**, à **une exception près** : `GET /nft/preview/:sourceFileHash` est public (voir §2.4). Obtenir un token : ```bash curl -X POST http://localhost:3000/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"registrar@example.com","password":"..."}' ``` ```json { "session_token": "abc...", "user": { "...": "..." } } ``` Puis, au choix, sur chaque appel : ```http Authorization: Bearer ``` ou ```http x-session-token: ``` --- ## 2. Upload de fichiers (PR #39) Un registrar manipule **deux fichiers** par œuvre. Ils sont désormais envoyés **inline dans le corps JSON**, encodés en base64 — pas de `multipart/form-data`. | Fichier | Obligatoire | Visibilité | Rôle | |---|---|---|---| | `sourceFile` | Non\* | **Privé** | L'œuvre originale. Ses octets sont stockés en base et leur SHA‑384 **est** le `sourceFileHash`. **Aucun endpoint ne le sert.** | | `previewImage` | Non | **Public** | Image d'aperçu, servie sans authentification sur `GET /nft/preview/:sourceFileHash` et référencée comme `image` dans les métadonnées du NFT. | \* Optionnel pour la rétro-compatibilité — sauf en mode `nftCreation: false`, où il devient obligatoire (§3). ### 2.1 Format d'un fichier | Champ | Type | Description | |---|---|---| | `filename` | string | Nom d'origine, stocké avec les octets. Max **255** caractères. | | `mimeType` | string | Type MIME. Max **100** caractères. | | `content` | string | Le fichier en **base64**. Un préfixe `data:;base64,` est accepté et retiré automatiquement — le résultat brut d'un `FileReader` navigateur peut donc être passé tel quel. | | `textData` | string | Optionnel. Note libre stockée avec le fichier. | **Limites :** 50 Mo par fichier *après décodage*. Le base64 coûte ~33 % de plus que les octets bruts, d'où une limite de corps HTTP à 150 Mo (`MAX_REQUEST_BODY_SIZE`). Côté navigateur : ```js const toBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); // On peut envoyer le data: URI tel quel, l'API retire le préfixe. reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); }); const sourceFile = { filename: file.name, mimeType: file.type, content: await toBase64(file), }; ``` ### 2.2 `POST /nft/create` avec fichiers **Requête** ```bash curl -X POST http://localhost:3000/nft/create \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $SESSION_TOKEN" \ -d @create.json ``` ```json { "blockchain": "sepolia", "recipientAddress": "0x1234567890123456789012345678901234567890", "name": "Artwork #1", "description": "A beautiful piece of digital art", "creatorName": "John Doe", "creatorAddress": "0x1234567890123456789012345678901234567890", "imageUrl": "https://example.com/image.png", "thumbnail": "https://example.com/thumb.png", "symbol": "ART", "resaleRights": 400, "redeemable": false, "tangible": true, "assetType": 0, "status": 0, "info": "Initial creation", "license": "https://example.com/license", "nftCreation": true, "sourceFile": { "filename": "artwork-scan.tif", "mimeType": "image/tiff", "content": "", "textData": "Scan haute résolution" }, "previewImage": { "filename": "preview.png", "mimeType": "image/png", "content": "" } } ``` **Réponse (200)** ```json { "message": "NFT created, minted, and registered successfully", "nftCreation": true, "contractAddress": "0x...", "tokenId": 1, "blockchainTokenId": 0, "transactionHash": "0x...", "explorerLink": "https://sepolia.etherscan.io/tx/0x...", "recipient": "0x1234567890123456789012345678901234567890", "sourceFileHash": "abc123...", "metadata": { "name": "Artwork #1", "description": "...", "image": "https://api.example.com/nft/preview/abc123...", "image_ipfs": "ipfs://bafkrei...", "thumbnail": "https://example.com/thumb.png", "source_file_hash": "abc123...", "attributes": [ { "trait_type": "Creator", "value": "John Doe" }, { "trait_type": "Creator Address", "value": "0x1234..." }, { "trait_type": "Resale Rights", "value": "4%" }, { "trait_type": "Tangible", "value": "Yes" }, { "trait_type": "Redeemable", "value": "No" } ], "external_url": "https://example.com/license" }, "files": { "sourceFile": { "filename": "artwork-scan.tif", "mimeType": "image/tiff", "byteLength": 2481920, "sha384": "abc123...", "cid": "bafkrei...", "uri": "ipfs://bafkrei..." }, "previewImage": { "filename": "preview.png", "mimeType": "image/png", "byteLength": 48210, "sha384": "def456...", "cid": "bafkrei...", "uri": "ipfs://bafkrei...", "url": "https://api.example.com/nft/preview/abc123..." } } } ``` Points d'attention côté client : - **`url` n'apparaît que sur `previewImage`.** Le fichier source n'a pas d'adresse publique, par conception. - **Si aucun fichier n'est envoyé** : `files.sourceFile` et `files.previewImage` valent `null`, `metadata.image` conserve l'`imageUrl` fourni par l'appelant, et `image_ipfs` / `source_file_hash` sont absents des métadonnées. - **Pour afficher une œuvre** : utiliser `files.previewImage.url`, ou le reconstruire soi-même : `{PUBLIC_BASE_URL}/nft/preview/{sourceFileHash}`. ### 2.3 `POST /nft/register` avec fichiers Mêmes objets `sourceFile` et `previewImage`. Différence importante : ici c'est **l'appelant qui fournit `sourceFileHash`**, donc si un `sourceFile` est joint, **ses octets doivent hasher (SHA‑384) exactement vers cette valeur**, sinon la requête est rejetée en **400**. ```json { "blockchain": "sepolia", "contractAddress": "0x1234567890123456789012345678901234567890", "tokenId": 1, "sourceFileHash": "abc123...", "creatorWalletPublicKey": "0x1234567890123456789012345678901234567890", "assetType": 0, "tangible": true, "redeemable": false, "status": 0, "resaleRights": 400, "creatorName": "John Doe", "info": "Registered existing NFT", "metadata": "{\"name\": \"Artwork\", \"description\": \"...\"}", "sourceFile": { "filename": "artwork-scan.tif", "mimeType": "image/tiff", "content": "" }, "previewImage": { "filename": "preview.png", "mimeType": "image/png", "content": "" } } ``` **Réponse (200)** ```json { "message": "NFT registered successfully", "nft": { "...": "objet NFT complet" }, "asset": { "sourceFileHash": "abc123...", "tokenId": 1, "certIssuerId": null, "measurements": null, "creationDate": null, "distinguishingFeatures": null, "inscriptions": null }, "files": { "sourceFile": { "...": "..." }, "previewImage": { "...": "...", "url": "https://api.example.com/nft/preview/abc123..." } } } ``` **Backfill :** enregistrer un token supplémentaire pour une œuvre déjà connue met à jour les lignes existantes avec les fichiers portés par l'appel. Une œuvre d'abord enregistrée sans ses fichiers peut donc être complétée plus tard. ### 2.4 `GET /nft/preview/:sourceFileHash` (public) **Aucune authentification.** C'est délibéré : cette URL est écrite dans les métadonnées du NFT, les wallets et marketplaces doivent pouvoir la récupérer anonymement. ```bash curl http://localhost:3000/nft/preview/abc123... --output preview.png ``` - Renvoie les octets de l'aperçu avec le `Content-Type` d'origine. - `Content-Disposition: inline` (le navigateur affiche au lieu de télécharger). - `Cache-Control: public, max-age=31536000, immutable` — le contenu est adressé par le hash d'un fichier immuable, il est donc cachable indéfiniment. | Statut | Cause | |---|---| | 200 | Les octets de l'image d'aperçu | | 404 | Aucune œuvre pour ce hash, ou aucun aperçu uploadé pour elle | > **Il n'existe aucun endpoint équivalent pour le fichier source.** Il est > stocké, hashé et référencé par son hash, mais jamais servi. ### 2.5 Le `sourceFileHash` Quand un `sourceFile` est uploadé, `sourceFileHash` = **SHA‑384 de ses octets**. La clé en base est donc content-addressed : deux fichiers identiques désignent la même œuvre. - Sur `POST /nft/create` le hash est **dérivé** de l'upload. Sans upload, on retombe sur le hash synthétique historique `sha384(contractAddress-tokenId-timestamp)`, qui ne décrit aucun fichier. - Sur `POST /nft/register`, le hash est fourni par l'appelant et vérifié contre le fichier s'il est joint. Ce hash est **public** : il apparaît dans les métadonnées (`source_file_hash`) et dans l'URL d'aperçu. C'est voulu — il permet à quiconque détient une copie de l'œuvre de la vérifier contre l'original enregistré, ce qui est tout l'objet d'un certificat d'authenticité. Il ne divulgue rien du contenu du fichier. #### CID IPFS des fichiers Les deux fichiers passent par le calcul de CID décrit au §4, et les `cid`/`uri` obtenus sont renvoyés dans `files`. **Rien n'est pinné.** Les CID étant déterministes, les enregistrer maintenant permet de pousser les octets vers un service de pinning plus tard sans que l'identifiant change. Les métadonnées portent donc l'aperçu **deux fois** : | Champ métadonnée | Valeur | Résout aujourd'hui ? | |---|---|---| | `image` | `https:///nft/preview/` | Oui | | `image_ipfs` | `ipfs://` | Pas tant que les octets ne sont pas pinnés | Le CID du fichier source est calculé et renvoyé dans la réponse API, mais **n'est pas** écrit dans les métadonnées : publier une URI `ipfs://` pour un fichier volontairement jamais pinné serait un lien mort permanent. Les métadonnées portent `source_file_hash` à la place, comme engagement d'intégrité. --- ## 3. Création de NFT optionnelle (PR #41) Nouveau champ sur `POST /nft/create` : ```json { "nftCreation": false } ``` `nftCreation` (booléen, optionnel, **défaut `true`**) contrôle si un token est créé. Il existe parce que toute œuvre enregistrée n'a pas vocation à devenir un NFT au moment où elle est enregistrée. ### Avec `nftCreation: false` - **Aucune interaction avec la chaîne** : pas de contrat déployé, pas de token frappé, pas besoin d'endpoint RPC ni de wallet système approvisionné. - Seules les lignes au niveau œuvre sont écrites : `source_file`, `desc_image` et `asset_properties`. **Pas de ligne `asset` ni `nft`**, toutes deux étant clefées par un token ID qui n'existe pas encore. - **`sourceFile` devient obligatoire.** Le chemin de mint peut retomber sur un hash synthétique construit depuis l'adresse du contrat et le token ID ; sans ni l'un ni l'autre, le SHA‑384 du fichier source est la seule chose qui identifie l'œuvre. - **`symbol`, `imageUrl` et `thumbnail` ne sont plus requis** — ils ne servent qu'au token et à ses métadonnées. **Requête minimale** ```json { "name": "Artwork #1", "description": "A beautiful piece of digital art", "creatorName": "John Doe", "creatorAddress": "0x1234567890123456789012345678901234567890", "resaleRights": 400, "redeemable": false, "tangible": true, "assetType": 0, "status": 0, "info": "Initial creation", "nftCreation": false, "sourceFile": { "filename": "artwork-scan.tif", "mimeType": "image/tiff", "content": "" }, "previewImage": { "filename": "preview.png", "mimeType": "image/png", "content": "" } } ``` **Réponse (200)** ```json { "message": "Artwork registered successfully. No NFT was created.", "nftCreation": false, "sourceFileHash": "abc123...", "asset": { "sourceFileHash": "abc123...", "title": "Artwork #1", "description": "A beautiful piece of digital art", "assetType": 0, "tangible": true, "redeemable": false }, "files": { "sourceFile": { "filename": "artwork-scan.tif", "...": "..." }, "previewImage": { "filename": "preview.png", "...": "...", "url": "..." } } } ``` ### Distinguer les deux réponses `POST /nft/create` peut désormais renvoyer **deux formes différentes**. Le champ `nftCreation` de la réponse les discrimine, sans avoir à inspecter les autres champs : ```ts const res = await createNft(body); if (res.nftCreation) { // Mint effectué : res.contractAddress, res.tokenId, res.transactionHash, // res.explorerLink, res.recipient, res.metadata } else { // Œuvre seulement : res.sourceFileHash, res.asset // (ni contractAddress, ni tokenId, ni transactionHash) } ``` ### Frapper le NFT plus tard Rappeler `POST /nft/create` avec **le même `sourceFile`** et `nftCreation: true` (ou champ omis). `asset_properties` étant clefé sur `source_file_hash` seul, l'enregistrement retrouve l'œuvre déjà consignée et y rattache le nouveau token, au lieu de la dupliquer. Comme la même recherche fait aussi le backfill des octets sur une œuvre connue, le fichier source et l'image d'aperçu peuvent également être fournis à ce second appel s'ils ne l'avaient pas été au premier. > **À ne pas confondre avec `POST /nft/register`**, qui enregistre un NFT > **existant déjà on-chain** et exige donc `contractAddress` et `tokenId`. ### Comparatif des trois flux | | `create` + `nftCreation: true` (défaut) | `create` + `nftCreation: false` | `register` | |---|---|---|---| | Déploie un contrat | Oui | Non | Non | | Frappe un token | Oui | Non | Non (déjà frappé) | | Requiert RPC + gas | Oui | **Non** | Oui (lecture `ownerOf()`) | | Requiert `sourceFile` | Non | **Oui** | Non | | Requiert `symbol`/`imageUrl`/`thumbnail` | Oui | **Non** | — | | Requiert `contractAddress`/`tokenId` | Non | Non | **Oui** | | Lignes écrites | `source_file`, `desc_image`, `asset_properties`, `asset`, `nft` | `source_file`, `desc_image`, `asset_properties` | idem create | --- ## 4. Calcul de CID IPFS (PR #37) **Endpoint :** `POST /ipfs/cid` **Authentification :** requise (session + rôle Admin ou Registrar) Calcule le content identifier IPFS (**CIDv1**) qu'aurait un fichier, **sans rien uploader ni pinner**. Les octets sont hashés dans un DAG UnixFS en mémoire, puis jetés — l'équivalent de `ipfs add --only-hash --cid-version=1 --raw-leaves`. IPFS étant content-addressed, le CID renvoyé ici est celui que le contenu aura une fois pinné sur n'importe quel backend : on peut donc enregistrer un CID maintenant et uploader les octets plus tard sans que l'identifiant change. Les réglages de l'importer sont alignés sur ceux du front affix-ui, donc un CID calculé par l'API et un CID calculé dans le navigateur sont directement comparables. ### Corps de requête Fournir **soit `url`, soit `content`** — jamais les deux. | Champ | Type | Description | |---|---|---| | `url` | string | URL du fichier à hasher. Les octets sont téléchargés, hashés, jetés. `http`/`https` uniquement, 50 Mo max. | | `content` | string | Contenu inline, décodé selon `encoding`. | | `encoding` | `utf8` \| `base64` | Comment décoder `content`. Défaut : `utf8`. | | `filename` | string | Nom enregistré pour le fichier. **N'affecte pas le CID**, sert seulement à rendre les erreurs lisibles. | ### Exemple — hasher un fichier distant ```bash curl -X POST http://localhost:3000/ipfs/cid \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $SESSION_TOKEN" \ -d '{"url":"https://example.com/artwork.png"}' ``` ```json { "cid": "bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4", "uri": "ipfs://bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4", "byteLength": 12, "url": "https://example.com/artwork.png", "mimeType": "image/png" } ``` ### Exemple — hasher un contenu inline ```json { "content": "aGVsbG8gd29ybGQK", "encoding": "base64", "filename": "hello.txt" } ``` `url` et `mimeType` ne sont présents dans la réponse que si la requête a fourni un `url`. ### Erreurs | Statut | Cause | |---|---| | 400 | Ni `url` ni `content`, ou les deux à la fois | | 400 | `url` malformé, protocole autre que `http`/`https`, injoignable, statut non‑2xx, ou taille dépassée | | 500 | L'importer UnixFS n'a pas pu être chargé ou a échoué | --- ## 5. Configuration serveur Deux variables d'environnement ajoutées ([.env.example](https://github.com/w3hc/roac/blob/main/.env.example)) : ```bash # URL publique à laquelle l'API est joignable depuis Internet. Les URL d'images # d'aperçu en sont construites et écrites dans les métadonnées NFT : ce doit # donc être une adresse qu'un wallet ou une marketplace peut atteindre. # Défaut : http://localhost:$PORT PUBLIC_BASE_URL=http://localhost:3000 # Taille maximale du corps HTTP. Les registrars uploadent les fichiers inline en # base64, ce qui coûte ~33 % au-dessus des octets bruts, en plus de la limite de # 50 Mo par fichier. MAX_REQUEST_BODY_SIZE=150mb ``` > `PUBLIC_BASE_URL` par défaut vaut `http://localhost:$PORT` : correct en local, > **faux en production**. Les métadonnées NFT étant immuables une fois frappées, > cette variable doit être correcte **avant** le premier mint. Express plafonne les corps de requête à 100 ko par défaut ; `MAX_REQUEST_BODY_SIZE` est appliqué au démarrage sur `json()` et `urlencoded()` ([src/main.ts](https://github.com/w3hc/roac/blob/main/src/main.ts)). ### Où atterrissent les octets | Fichier | Table | Colonnes | |---|---|---| | `sourceFile` | `source_files` | `filename`, `mime_type`, `binary_data`, `source_file_hash` | | `previewImage` | `desc_images` | `filename`, `mime_type`, `binary_data` | `asset_properties.asset_desc_image_id` est `NOT NULL`, donc une ligne `desc_images` est toujours écrite ; sans aperçu uploadé, son `binary_data` vaut `NULL`. --- ## 6. Erreurs Toutes les erreurs suivent le format de l'API, avec un message préfixé par l'endpoint concerné. | Statut | Message (extrait) | Cause | |---|---|---| | 400 | `POST /nft/create failed: sourceFile.content is empty or not valid base64` | Base64 vide ou invalide | | 400 | `POST /nft/create failed: previewImage.content is not valid base64` | Base64 corrompu (détecté par ré-encodage) | | 400 | `... is N bytes, over the 52428800 byte limit` | Fichier > 50 Mo après décodage | | 400 | `POST /nft/create failed: sourceFile is required when nftCreation is false` | `nftCreation: false` sans fichier source | | 400 | hash mismatch | Sur `register`, le `sourceFile` ne hashe pas vers le `sourceFileHash` fourni | | 400 | `POST /ipfs/cid failed: provide either url or content, not both` | Les deux champs fournis | | 400 | `POST /ipfs/cid failed: url or content is required` | Aucun des deux | | 401 | `Session token is missing` / `Invalid or expired session` | Header d'auth absent ou session invalide | | 404 | — | `GET /nft/preview/:hash` : œuvre inconnue ou sans aperçu | | 413 | (Express) | Corps HTTP au-delà de `MAX_REQUEST_BODY_SIZE` | --- ## 7. Checklist d'intégration - [ ] Le client envoie `sourceFile` et `previewImage` en base64 inline, dans le corps JSON (pas de `multipart`). - [ ] Les fichiers > 50 Mo sont rejetés **côté client** avant l'envoi, pour éviter un aller-retour de 50 Mo. - [ ] L'affichage d'une œuvre utilise `files.previewImage.url` (ou `{PUBLIC_BASE_URL}/nft/preview/{sourceFileHash}`), sans en-tête d'auth. - [ ] Le client **ne tente jamais** de récupérer le fichier source par HTTP : il n'y a pas d'endpoint. - [ ] Les deux formes de réponse de `POST /nft/create` sont gérées, discriminées par le booléen `nftCreation`. - [ ] Le parcours « enregistrer maintenant, frapper plus tard » réutilise **exactement le même `sourceFile`** au second appel. - [ ] `image_ipfs` / `files.*.cid` sont affichés comme informatifs : **rien n'est pinné**, ces URI ne résolvent pas encore. - [ ] `PUBLIC_BASE_URL` est configuré sur l'environnement cible **avant** le premier mint. --- ## Références - [docs/API_REFERENCE.md](https://github.com/w3hc/roac/blob/main/docs/API_REFERENCE.md) — référence complète des endpoints - [docs/REGISTRAR_INTEGRATION.md](https://github.com/w3hc/roac/blob/main/docs/REGISTRAR_INTEGRATION.md) — guide d'intégration registrar - [docs/DATABASE.md](https://github.com/w3hc/roac/blob/main/docs/DATABASE.md) — schéma et stockage des octets - Swagger : `http://localhost:3000/api` --- title: Autopub — SDK Specification date: 2026-08-28 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/autopub-sdk-spec --- # Autopub — SDK Specification **Status:** draft v0.1 · **Date:** 2026-08-28 · **Author:** Julien Béranger **Extracted from:** [julienbrg/personal-website](https://github.com/julienbrg/personal-website) --- ## 1. Purpose Turn the blog engine currently embedded in this site into a reusable SDK that any [Next.js](https://nextjs.org/docs) App Router project can install, point at its own database, and style with its own design system — while keeping the AI-native publishing workflow (the [`/add` skill](#101-the-add-skill), the [Neon connector](https://neon.com/docs/ai/neon-mcp-server), and [Claude Code Routines](https://docs.claude.com/en/docs/claude-code/routines)) that makes it fast to write with. **Non-goals for v1:** a CMS admin UI, comments, multi-tenant auth, RSS/newsletter delivery, WYSIWYG editing. Those are candidates for v2+ (see [§15](#15-roadmap)). ### 1.1 The two design constraints Everything in this spec follows from two facts: 1. **Adopters have different databases.** We ship [Neon](https://neon.com/docs/introduction) first because that is what this site runs on, but Postgres/Supabase/SQLite/plain-markdown users must be first-class. → the **data seam**, [§5](#5-data-seam--poststore). 2. **Adopters have different design systems.** We ship [Chakra UI v3](https://chakra-ui.com/docs/get-started/installation) first because that is what this site runs on, but Tailwind/shadcn/MUI/plain-CSS users must be first-class. → the **render seam**, [§6](#6-render-seam--renderpreset). Neither seam may leak into the other, and neither may leak into [`@autopub/core`](#4-autopubcore). --- ## 2. What exists today The whole blog is ~400 lines in five files. This is the extraction surface: | File | Lines | Role | Destination package | |---|---:|---|---| | [src/lib/markdown.ts](src/lib/markdown.ts) | 55 | `parseFrontmatter`, `extractLeadingHeading`, `isValidSlug` | `@autopub/core` — verbatim | | [src/lib/posts.ts](src/lib/posts.ts) | 71 | `Post` types, `getPost`, `formatPostDate` | split: types → `core`, SQL → `adapter-neon`, format → `core` | | [src/lib/db.ts](src/lib/db.ts) | 7 | Neon client | `@autopub/adapter-neon` | | [src/components/PostContent.tsx](src/components/PostContent.tsx) | 196 | react-markdown → Chakra map | split: pipeline → `ui`, Chakra map → `preset-chakra` | | [scripts/posts.ts](scripts/posts.ts) | 124 | `init` / `add` / `delete` / `list` | `@autopub/cli` | | [src/app/\[slug\]/page.tsx](src/app/%5Bslug%5D/page.tsx) | 118 | route, `generateMetadata`, post header | `@autopub/next` | | [src/theme/index.ts](src/theme/index.ts) | 7 | `brandColors` | stays in the consumer app | | [~/.claude/skills/add/SKILL.md](file:///Users/ju/.claude/skills/add/SKILL.md) | — | authoring workflow | `@autopub/claude-plugin` | Three couplings block reuse as-is: the direct `sql` import in [src/lib/posts.ts:1](src/lib/posts.ts#L1), the Chakra imports throughout [PostContent.tsx](src/components/PostContent.tsx), and the site-specific `model` / `conversation` frontmatter fields in [PostFrontmatter](src/lib/posts.ts#L4-L14). --- ## 3. Package map Published under the [`@autopub`](https://www.npmjs.com/org/autopub) npm scope (alternative if taken: `@w3hc/autopub`, matching [w3pk](https://www.npmjs.com/package/w3pk)). | Package | Depends on | Peer deps | Purpose | |---|---|---|---| | [`@autopub/core`](#4-autopubcore) | — (zero runtime deps) | — | types, frontmatter parser, slug rules, date formatting | | [`@autopub/next`](#7-nextjs-integration) | `core` | `next`, `react` | `createBlog()`, page/metadata/sitemap/RSS factories | | [`@autopub/ui`](#6-render-seam--renderpreset) | `core`, `react-markdown`, `remark-gfm` | `react` | renderer pipeline + unstyled default preset + `autopub.css` | | [`@autopub/adapter-neon`](#52-the-neon-adapter-v1) | `core` | `@neondatabase/serverless` | **v1 reference adapter** | | `@autopub/adapter-postgres` | `core` | `pg` | any Postgres (RDS, Supabase, local) | | `@autopub/adapter-fs` | `core` | — | markdown files on disk, no DB — the "just try it" path | | `@autopub/adapter-sqlite` | `core` | `better-sqlite3` \| `@libsql/client` | local / Turso | | [`@autopub/preset-chakra`](#63-the-chakra-preset-v1) | `core`, `ui` | `@chakra-ui/react@^3` | **v1 reference preset** | | `@autopub/preset-tailwind` | `core`, `ui` | — | Tailwind/shadcn class map | | [`@autopub/cli`](#9-cli) | `core` | — | `autopub init/add/list/delete/pull/doctor` | | [`@autopub/claude-plugin`](#10-claude-integration) | — | — | the `/add` skill + `/autopub-*` commands, installable in any repo | **Rule:** an adopter using Postgres + Tailwind must never download `@neondatabase/serverless` or `@chakra-ui/react`. Enforced by keeping adapters and presets in separate packages (not subpath exports of one package) and by [publint](https://publint.dev/) in CI. --- ## 4. `@autopub/core` Zero dependencies. Runs in Node, edge, and the browser. ```ts // @autopub/core /** Fields every Autopub post has. Anything else goes in `meta`. */ export interface PostFrontmatter { title: string description?: string date?: string // ISO 8601, YYYY-MM-DD locale?: string // BCP 47 or OG form; normalised by `toOpenGraphLocale` image?: string imageAlt?: string author?: string tags?: string[] draft?: boolean } /** `TMeta` is the escape hatch for site-specific frontmatter. */ export interface Post> extends PostFrontmatter { slug: string content: string meta: TMeta createdAt?: string updatedAt?: string } /** What a list view needs — never carries `content`. */ export type PostSummary> = Omit, 'content'> export function isValidSlug(slug: string): boolean export function slugify(input: string): string // NEW — accent-stripping, from the /add skill export function parseFrontmatter(raw: string): { data: Record; content: string } export function extractLeadingHeading(content: string): { heading?: string; body: string } /** Frontmatter → Post, applying core/meta split and defaults. */ export function parsePost( raw: string, opts: { slug: string; knownMetaKeys?: string[] } ): Post export function formatPostDate( date: string, opts?: { locale?: string; timeZone?: string; capitalize?: boolean } ): string export function readingTime(content: string): { minutes: number; words: number } // NEW export function toOpenGraphLocale(locale: string): string // 'fr' | 'fr-FR' → 'fr_FR' export class AutopubError extends Error { code: 'INVALID_SLUG' | 'NOT_FOUND' | 'STORE_ERROR' | ... } ``` Notes: - `parseFrontmatter` and `extractLeadingHeading` move over **unchanged** from [src/lib/markdown.ts](src/lib/markdown.ts) — they already have no dependencies and are already documented. - `formatPostDate` generalises [src/lib/posts.ts:59-70](src/lib/posts.ts#L59-L70): the hard-coded `'fr-FR'` becomes a parameter, and the capitalisation pass becomes `capitalize` (default `true`, preserving today's "Mardi 25 août 2025" output). - `slugify` lifts the algorithm currently living only in prose in [step 2 of the `/add` skill](#101-the-add-skill), so the CLI, the skill, and the SDK agree. - `model` / `conversation` — this site's fields — leave the core type and land in `meta`. See [§12.2](#122-metadata-migration). --- ## 5. Data seam — `PostStore` ### 5.1 The interface Every adapter implements this and nothing more. Read methods are required; write methods are optional and used only by [the CLI](#9-cli) and by webhooks. ```ts // @autopub/core export interface ListOptions { limit?: number offset?: number order?: 'date' | 'created' | 'title' direction?: 'asc' | 'desc' tag?: string locale?: string includeDrafts?: boolean // default false } export interface PostStore> { readonly name: string /** Returns null for unknown or invalid slugs — never throws on a bad slug. */ get(slug: string): Promise | null> list(options?: ListOptions): Promise[]> count(options?: Pick): Promise // Write side — optional; `createBlog()` works read-only without them. upsert?(post: Post): Promise remove?(slug: string): Promise init?(): Promise // create/migrate schema health?(): Promise<{ ok: boolean; detail?: string }> // powers `autopub doctor` } ``` **Contract rules** 1. `get()` MUST validate with `isValidSlug()` and return `null` rather than querying — this is the injection guard that [src/lib/posts.ts:52](src/lib/posts.ts#L52) already implements. 2. `list()` MUST NOT return `content`. 3. Adapters MUST NOT import anything from `@autopub/ui` or `@autopub/next`. 4. Unknown columns/keys round-trip through `meta` untouched. 5. All adapters MUST pass the shared conformance suite, `@autopub/core/testing` → `runStoreConformance(makeStore)`. ### 5.2 The Neon adapter (v1) ```ts import { neonStore } from '@autopub/adapter-neon' const store = neonStore({ connectionString: process.env.DATABASE_URL!, // required table: 'posts', // default 'posts' schema: 'public', // default 'public' }) ``` Built on [`@neondatabase/serverless`](https://www.npmjs.com/package/@neondatabase/serverless), which is what [src/lib/db.ts](src/lib/db.ts) already uses — HTTP driver, so it works on [Vercel Edge](https://vercel.com/docs/functions/runtimes/edge) and in [Next.js Server Components](https://nextjs.org/docs/app/getting-started/server-and-client-components). **Schema** (`init()`), a superset of [today's table](scripts/posts.ts#L14-L31): ```sql CREATE TABLE IF NOT EXISTS posts ( slug TEXT PRIMARY KEY, title TEXT NOT NULL, description TEXT, date TEXT, locale TEXT, image TEXT, image_alt TEXT, author TEXT, tags TEXT[], draft BOOLEAN NOT NULL DEFAULT false, meta JSONB NOT NULL DEFAULT '{}'::jsonb, -- model, conversation, anything else content TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS posts_date_idx ON posts (date DESC NULLS LAST); ``` `init()` is idempotent and additive (`ADD COLUMN IF NOT EXISTS`), exactly like [scripts/posts.ts:27-29](scripts/posts.ts#L27-L29), so it doubles as the migration path for this site's existing table. ### 5.3 Writing another adapter Three methods and a mapper. The full `@autopub/adapter-fs` is expected to be ~80 lines: read `content/posts/*.md`, `parsePost()` each, sort by date. Documented as the template in `docs/adapters.md`, with the conformance suite as the definition of done. --- ## 6. Render seam — `RenderPreset` ### 6.1 The layered escape hatches Four levels, each an opt-out of the one above: | Level | What you write | Who it's for | |---|---|---| | 0 | nothing — import `@autopub/ui/autopub.css` | wants it to just look fine | | 1 | CSS variables (`--autopub-accent`, …) | has brand colours, no framework | | 2 | `preset={chakraPreset({ accent })}` | on Chakra / Tailwind / MUI | | 3 | `components={{ h2: MyHeading }}` | needs one node to be special | | 4 | `blog.getPost()` + your own JSX | wants total control | Level 3 overrides merge **over** the preset, per node — you never have to re-specify a preset to change one element. ### 6.2 The interface ```ts // @autopub/ui import type { Components } from 'react-markdown' export interface RenderPreset { readonly name: string /** react-markdown node overrides — same shape as today's map in PostContent.tsx */ components: Partial /** Optional chrome around the article body */ Article?: React.ComponentType<{ children: React.ReactNode }> PostHeader?: React.ComponentType PostList?: React.ComponentType<{ posts: PostSummary[]; hrefFor(slug: string): string }> /** CSS custom properties applied to the root element */ tokens?: Record<`--autopub-${string}`, string> } export function PostContent(props: { content: string preset?: RenderPreset // default: unstyledPreset components?: Partial // merged over preset.components remarkPlugins?: PluggableList // default [remarkGfm] rehypePlugins?: PluggableList className?: string }): JSX.Element export function PostHeader(props: PostHeaderProps): JSX.Element export function PostList(props: PostListProps): JSX.Element export const unstyledPreset: RenderPreset // semantic HTML + `autopub-*` classes export function mergePresets(base: RenderPreset, over: Partial): RenderPreset ``` `@autopub/ui` keeps the parts of [PostContent.tsx](src/components/PostContent.tsx) that are *logic*, not style — the internal-vs-external link branch at [lines 44-64](src/components/PostContent.tsx#L44-L64), the fenced-vs-inline code detection at [lines 120-122](src/components/PostContent.tsx#L120-L122), the `hr`-as-whitespace decision at [line 116](src/components/PostContent.tsx#L116) — and pushes every colour and spacing token out to the preset. ### 6.3 The Chakra preset (v1) ```ts import { chakraPreset } from '@autopub/preset-chakra' const preset = chakraPreset({ accent: '#45a2f8', // today's brandColors.accent primary: '#8c1c84', proseLineHeight: '1.9', headingGlow: true, // the h2 textShadow at PostContent.tsx#L31 }) ``` This is [today's `components` map](src/components/PostContent.tsx#L14-L184) almost verbatim, with [brandColors](src/theme/index.ts) turned into arguments. `@chakra-ui/react@^3` is a **peer** dependency — the package is unusable without Chakra, and that is correct. One dependency to break during extraction: the preset currently imports `ListRoot` / `ListItem` from [src/components/ui/list.tsx](src/components/ui/list.tsx), which is a [Chakra snippet](https://chakra-ui.com/docs/components/list) local to this repo. The preset must inline them or use `Chakra.List.Root` directly — it cannot depend on a consumer's `@/components/ui`. ### 6.4 Token contract Presets and the stylesheet agree on one set of names: ``` --autopub-accent --autopub-prose-line-height --autopub-primary --autopub-prose-max-width --autopub-fg --autopub-block-gap --autopub-fg-muted --autopub-radius --autopub-bg-subtle --autopub-code-bg --autopub-border --autopub-font-mono ``` The stylesheet defines the full light palette on bare `:root`, redefines only what changes under `@media (prefers-color-scheme: dark)` guarded as `:root:not([data-theme="light"])`, and again under `:root[data-theme="dark"]` — so it works with [next-themes](https://www.npmjs.com/package/next-themes) (already a dependency here) in both directions. --- ## 7. Next.js integration `@autopub/next` is where the data seam and the render seam meet. Targets **Next 15+ App Router** (this site is on [Next 16.3](package.json#L22)). ```ts // lib/blog.ts — the one file an adopter writes import { createBlog } from '@autopub/next' import { neonStore } from '@autopub/adapter-neon' import { chakraPreset } from '@autopub/preset-chakra' export const blog = createBlog({ store: neonStore({ connectionString: process.env.DATABASE_URL! }), preset: chakraPreset({ accent: '#45a2f8', primary: '#8c1c84' }), basePath: '/', // this site serves posts at / locale: 'fr-FR', siteName: 'Julien Beranger', siteUrl: 'https://julienberanger.com', defaultImage: '/huangshan.png', caching: { mode: 'dynamic' }, // see §7.2 }) ``` ### 7.1 What `createBlog()` returns ```ts interface Blog { // data getPost(slug: string): Promise | null> listPosts(options?: ListOptions): Promise[]> // route factories — each returns something you `export` from a route file createPostPage(opts?: { renderHeader?; renderFooter?; notFound? }): NextPage createPostMetadata(): (props: { params: Promise<{ slug: string }> }) => Promise createIndexPage(opts?: { pageSize?: number }): NextPage createStaticParams(): () => Promise<{ slug: string }[]> createSitemap(): () => Promise createFeed(opts?: { format?: 'rss' | 'atom' | 'json' }): RouteHandler createRevalidateRoute(opts: { secret: string }): RouteHandler // §7.2 createOgImage(opts?: { fonts? }): RouteHandler // ImageResponse createWebhookRoute(opts: { secret: string }): RouteHandler // remote publish, §10.3 // components, pre-bound to the configured preset PostContent: (props: { content: string }) => JSX.Element PostHeader: (props: { post: Post }) => JSX.Element PostList: (props: { posts: PostSummary[] }) => JSX.Element } ``` ### 7.2 Caching This is exactly the problem commit [`ba013db` "fix db polling"](https://github.com/julienbrg/personal-website/commit/ba013db) just dealt with, so the SDK makes it an explicit choice rather than a hidden default: | `caching.mode` | Behaviour | When | |---|---|---| | `'dynamic'` | `export const dynamic = 'force-dynamic'` — every request hits the DB | edit-in-DB-see-it-now, [today's behaviour](src/app/%5Bslug%5D/page.tsx#L10) | | `'isr'` | `export const revalidate = N` | high traffic | | `'tags'` | `cacheTag('autopub:post:')` + `createRevalidateRoute()` | best of both — publish pings the route, cache drops | | `'static'` | `generateStaticParams()`, build-time only | fully static export | `'tags'` is the recommended default for new adopters; this site keeps `'dynamic'` on migration so behaviour is unchanged. ### 7.3 Adopter's route files ```ts // app/[slug]/page.tsx import { blog } from '@/lib/blog' export const dynamic = 'force-dynamic' export const generateMetadata = blog.createPostMetadata() export default blog.createPostPage() ``` ```ts // app/blog/page.tsx import { blog } from '@/lib/blog' export default blog.createIndexPage({ pageSize: 20 }) ``` ```ts // app/feed.xml/route.ts import { blog } from '@/lib/blog' export const GET = blog.createFeed({ format: 'rss' }) ``` Three lines to replace [the current 118-line page](src/app/%5Bslug%5D/page.tsx) — and the post header (author / model / conversation block, [lines 68-104](src/app/%5Bslug%5D/page.tsx#L68-L104)) comes back as `renderHeader`, since it reads `meta.model` and `meta.conversation`. --- ## 8. Configuration file `autopub.config.ts` at the project root, read by [the CLI](#9-cli) and importable by the app so config lives in one place: ```ts import { defineConfig } from '@autopub/core' export default defineConfig({ store: { adapter: '@autopub/adapter-neon', options: { connectionString: process.env.DATABASE_URL } }, content: { dir: 'content/posts', images: 'public' }, site: { url: 'https://julienberanger.com', name: 'Julien Beranger', locale: 'fr-FR' }, defaults: { author: 'Julien Béranger', image: '/huangshan.png' }, meta: { keys: ['model', 'conversation'] }, // extra frontmatter → Post.meta }) ``` Env resolution order: explicit option → `process.env` → `.env` via [`process.loadEnvFile`](https://nodejs.org/api/process.html#processloadenvfilepath) (what [scripts/posts.ts:104](scripts/posts.ts#L104) already does). --- ## 9. CLI `npx @autopub/cli` / `pnpm autopub`. Supersedes [scripts/posts.ts](scripts/posts.ts) and [`pnpm posts`](package.json#L17). | Command | Behaviour | |---|---| | `autopub init` | create/migrate schema via `store.init()`; scaffold `autopub.config.ts` and route files | | `autopub add ` | parse → validate → upsert. `--slug`, `--dry-run`, `--draft` | | `autopub list` | `slug · date · title · draft` | | `autopub delete ` | `store.remove()`, with confirmation | | `autopub pull [slug]` | DB → `content/posts/*.md` (round-trip, restores a lost local copy) | | `autopub images ` | copy local images into `public/`, rewrite refs — [step 4 of the skill](#101-the-add-skill) | | `autopub doctor` | `store.health()`, env check, schema drift, orphaned images | | `autopub open ` | print/open the live URL | `add` stays an **upsert** (`ON CONFLICT (slug) DO UPDATE`, as [scripts/posts.ts:47-63](scripts/posts.ts#L47-L63)) so re-running after a typo fix is safe. `add` writes to a live database — the CLI prints the resolved frontmatter and asks for confirmation unless `--yes` is passed. --- ## 10. Claude integration The reason this SDK is worth publishing rather than just refactoring: the authoring loop is already agentic, and nothing on npm packages that loop. ### 10.1 The `/add` skill [`~/.claude/skills/add/SKILL.md`](file:///Users/ju/.claude/skills/add/SKILL.md) already encodes the full import workflow: locate the repo, read the source markdown from *anywhere on disk*, derive a valid slug, fill frontmatter defaults (author, date, and the running model — e.g. `Claude Opus 5`), migrate local images into `public/`, stage a gitignored copy under `content/posts/`, confirm, then publish. It ships as `@autopub/claude-plugin`, a [Claude Code plugin](https://docs.claude.com/en/docs/claude-code/plugins) containing: - `skills/add/SKILL.md` — generalised: `pnpm posts add` → `npx autopub add`, hard-coded paths and the `Julien Béranger` / `/huangshan.png` defaults read from [`autopub.config.ts`](#8-configuration-file) instead - `commands/autopub-list.md`, `commands/autopub-pull.md`, `commands/autopub-doctor.md` - `agents/editor.md` — a review pass over a draft before publishing Installed with `/plugin install @autopub/claude-plugin`, so any adopter gets `/add` in their own repo on day one. The current skill's Julien-specific steps stay working via config, not via a fork. ### 10.2 The Neon connector (in-browser Claude) Because posts live in Postgres rather than in git, they are editable from [claude.ai](https://claude.ai) with no local checkout, via the [Neon MCP server](https://neon.com/docs/ai/neon-mcp-server): **Settings → Connectors → Browse connectors → Neon**, then enable it per chat under **Add sources** ([setup guide](https://neon.com/docs/ai/connect-mcp-clients-to-neon)). In Claude Code the equivalent is `claude mcp add --transport http neon https://mcp.neon.tech/mcp` ([guide](https://neon.com/guides/claude-code-mcp-neon)). What that buys an Autopub user, from a phone or a browser tab: - *"fix the typo in the third paragraph of `rukh-roadmap`"* → `UPDATE posts SET content = …` - *"list posts with no description"* → an editorial to-do list - *"unpublish `kidwatch`"* → `draft = true` This is why the schema is deliberately **flat and human-legible** — one row per post, markdown in a `TEXT` column — rather than a normalised block model. An LLM with SQL access can edit it safely. The SDK documents this as a supported workflow, with a read-only role recommended for exploration and `autopub doctor` to catch a malformed hand-edit. ### 10.3 Routines / Scheduled agents [Claude Code Routines](https://docs.claude.com/en/docs/claude-code/routines) — scheduled cloud agents, shipped April 2026, still research preview ([The Register](https://www.theregister.com/2026/04/14/claude_code_routines/), [MakerKit guide](https://makerkit.dev/blog/tutorials/claude-code-routines-guide)) — run a saved prompt on Anthropic's infrastructure on a cron, an API call, or a GitHub event, with no machine of yours awake. Created with `/schedule` in the CLI, **New routine → Remote** in the desktop app, or at [claude.ai/code/routines](https://claude.ai/code/routines). There are three tiers to be precise about, and the SDK docs should name them: [`/loop`](https://docs.claude.com/en/docs/claude-code/slash-commands) (session-scoped), desktop Scheduled Tasks (local), and cloud Routines (managed). Autopub ships routine templates in `templates/routines/`: | Routine | Cadence | Does | |---|---|---| | `scheduled-publish` | hourly | publish posts whose `date` has arrived and `draft = true` — a real editorial calendar, no infra | | `link-check` | weekly | crawl outbound links in all posts, open a GitHub issue on 404s | | `draft-digest` | Monday | list stale drafts and unfinished posts | | `translate` | on demand | duplicate a post into another `locale` with a translated body | | `og-refresh` | on publish | regenerate OG images for changed posts | `scheduled-publish` is the one that makes Autopub structurally different from a git-based blog: because the store is a database and the CLI is scriptable, *scheduling a publish is a cron job over a `UPDATE posts SET draft = false`* — no rebuild, no redeploy, and (with `caching.mode: 'tags'`) the page is live within a request. --- ## 11. SDK base / template ### 11.1 Recommendation **Build the base by hand: pnpm workspaces + [tsdown](https://tsdown.dev/) + [Changesets](https://github.com/changesets/changesets).** Not a SaaS boilerplate. [next-forge](https://www.next-forge.com/) and [Saasfly](https://github.com/saasfly/saasfly) are app templates — auth, billing, analytics — and Autopub is a *library*: their weight is all cost here. The library-publishing base is small enough to assemble correctly in an afternoon and every piece is justified below. | Concern | Choice | Why | |---|---|---| | Monorepo | [pnpm workspaces](https://pnpm.io/workspaces) | already the package manager here; [pnpm-workspace.yaml](pnpm-workspace.yaml) exists | | Task graph | [Turborepo](https://turbo.build/repo/docs) | only once >5 packages; skip at first | | Bundler | **[tsdown](https://tsdown.dev/)** | [tsup](https://www.npmjs.com/package/tsup) is no longer actively maintained and now points at tsdown; [Rolldown](https://rolldown.rs/)-based, tsup-compatible options, ESM+CJS+`.d.ts`. Note [tsup is already a devDependency here](package.json#L54) — swap it | | Types | [TypeScript 6](https://www.typescriptlang.org/docs/) | matches [this repo](package.json#L57) | | Versioning | [Changesets](https://github.com/changesets/changesets) | per-package semver + generated changelogs across the workspace | | Export correctness | [publint](https://publint.dev/) + [Are the Types Wrong?](https://arethetypeswrong.github.io/) | CI gate on the `exports` map and consumer type resolution | | Dead weight | [Knip](https://knip.dev/) | catches unused deps before publish | | Tests | [Vitest](https://vitest.dev/) + [Testing Library](https://testing-library.com/docs/react-testing-library/intro/) | store conformance + render snapshots | | Docs | [Nextra](https://nextra.site/) or [Fumadocs](https://fumadocs.dev/) | the docs site is itself an Autopub adopter — dogfood | | CI | [GitHub Actions](https://docs.github.com/en/actions) | matrix over adapters; `changesets/action` for release | | Registry | npm + [provenance](https://docs.npmjs.com/generating-provenance-statements) | `npm publish --provenance` from CI | Keep [`minimumReleaseAge`](pnpm-workspace.yaml) supply-chain protection in the new repo — it is a good default and costs nothing. ### 11.2 Repo layout ``` autopub/ ├── package.json # private workspace root ├── pnpm-workspace.yaml ├── turbo.json # when it earns its place ├── .changeset/ ├── packages/ │ ├── core/ # @autopub/core — zero deps │ ├── next/ # @autopub/next │ ├── ui/ # @autopub/ui │ ├── adapter-neon/ # @autopub/adapter-neon │ ├── adapter-fs/ │ ├── adapter-postgres/ │ ├── preset-chakra/ │ ├── preset-tailwind/ │ ├── cli/ # @autopub/cli — bin: autopub │ └── claude-plugin/ ├── examples/ │ ├── chakra-neon/ # mirrors this site — the migration proof │ ├── tailwind-fs/ # no database at all │ └── minimal/ # unstyled, adapter-fs, 3 files ├── docs/ # Nextra site, itself an adopter └── templates/routines/ # §10.3 ``` ### 11.3 Package manifest shape ```jsonc { "name": "@autopub/ui", "type": "module", "sideEffects": ["*.css"], "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }, "./autopub.css": "./dist/autopub.css", "./package.json": "./package.json" }, "files": ["dist"], "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "publishConfig": { "access": "public", "provenance": true } } ``` Client components (`PostContent` and anything with state) carry `'use client'` and tsdown is configured to **preserve the directive** — a known sharp edge when bundling React libraries for the App Router. `examples/chakra-neon` exists specifically to catch a regression there before release. --- ## 12. Migration plan for this site Nine steps, roughly a day. Each is independently verifiable; the site keeps working throughout. 1. **Create `autopub/`** with the layout in [§11.2](#112-repo-layout), `core` + `adapter-fs` only. Port [markdown.ts](src/lib/markdown.ts) verbatim, add `slugify` and `parsePost`. Write the conformance suite against `adapter-fs`. 2. **`@autopub/adapter-neon`** — move [db.ts](src/lib/db.ts) and the four queries from [posts.ts](src/lib/posts.ts) / [scripts/posts.ts](scripts/posts.ts). Run the conformance suite against a [Neon branch](https://neon.com/docs/introduction/branching), not production. 3. **`@autopub/ui`** — port the pipeline from [PostContent.tsx](src/components/PostContent.tsx), write `unstyledPreset` and `autopub.css`. Snapshot-test against every post in [content/posts/](content/posts/). 4. **`@autopub/preset-chakra`** — port the Chakra map, inline the [list snippet](src/components/ui/list.tsx), parameterise [brandColors](src/theme/index.ts). Diff renders against the current site: **must be pixel-identical**. 5. **`@autopub/next`** — `createBlog()` and the factories, from [app/\[slug\]/page.tsx](src/app/%5Bslug%5D/page.tsx). 6. **`@autopub/cli`** — port [scripts/posts.ts](scripts/posts.ts), add `pull`, `doctor`, `images`. 7. **Migrate the database**: add `tags`, `draft`, `meta`; backfill `meta = jsonb_build_object('model', model, 'conversation', conversation)`; keep the old columns one release, then drop. 8. **Flip this site to the SDK**: `lib/blog.ts`, three route files, `renderHeader` for the model/conversation block, delete [posts.ts](src/lib/posts.ts), [db.ts](src/lib/db.ts), [PostContent.tsx](src/components/PostContent.tsx), [scripts/posts.ts](scripts/posts.ts). Net line change in this repo: **−480**. 9. **Publish `0.1.0`** with provenance; update the `/add` skill to call `npx autopub add`. ### 12.1 Definition of done `https://julienberanger.com/rukh-roadmap` renders byte-identically before and after, OG tags included, and `pnpm posts add` still works through the aliased CLI. ### 12.2 Metadata migration `model` and `conversation` are the test case for the whole `meta` design: after step 7 they live in `meta`, this site declares them in [`autopub.config.ts`](#8-configuration-file) as `meta.keys: ['model', 'conversation']`, and types as `Post<{ model?: string; conversation?: string }>`. If that feels natural, the escape hatch works; if it feels awkward, the core type is wrong and it is cheap to fix before 0.1.0. --- ## 13. Compatibility | | Supported | Notes | |---|---|---| | Next.js | 15.x, 16.x App Router | Pages Router unsupported | | React | 18, 19 | RSC-first; `PostContent` is a client component | | Node | 20+ | `process.loadEnvFile` needs 20.6+ | | Runtimes | Node, Edge, Vercel, Cloudflare Workers | edge depends on the adapter — Neon HTTP driver yes, `pg` no | | Chakra | v3 only (peer) | v2 has a different API surface; out of scope | | Bundlers | Next/Turbopack, Vite | tested in `examples/` | **Security**: `isValidSlug` gates every read; adapters use parameterised queries only (Neon's tagged template, as [today](src/lib/posts.ts#L54)); `@autopub/ui` does **not** enable `rehype-raw`/`dangerouslySetInnerHTML` by default — raw HTML in markdown stays inert unless the adopter opts in, and the docs say why. Consistent with the [WCAG 2.1 AA commitment](README.md) of this repo, `unstyledPreset` output must pass [axe](https://www.deque.com/axe/) and keep heading order intact — note today's map renders markdown `h1` as an `h2` ([PostContent.tsx:15](src/components/PostContent.tsx#L15)) precisely to avoid two `h1`s on the page. --- ## 14. Quality gates Every PR: `typecheck` · `lint` ([ESLint 9](eslint.config.mjs) + [jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y), as here) · `test` (conformance + render) · `build` · `publint` · `attw` · `knip` · `examples/*` build. Release: Changesets → npm with provenance. --- ## 15. Roadmap | Version | Contents | |---|---| | **0.1** | `core`, `next`, `ui`, `adapter-neon`, `adapter-fs`, `preset-chakra`, `cli`; this site migrated | | **0.2** | `adapter-postgres`, `preset-tailwind`, `claude-plugin`, RSS + sitemap + OG images | | **0.3** | routine templates ([§10.3](#103-routines--scheduled-agents)), `caching: 'tags'` + revalidate route, drafts & scheduled publishing | | **0.4** | i18n post linking (`translationOf`), tags/archive pages, search adapter | | **1.0** | stable `PostStore` + `RenderPreset` contracts, docs site, `adapter-sqlite`, `create-autopub` scaffolder | | later | comments, newsletter export, web editor, MDX/component embeds | --- ## 16. Open questions 1. **Scope name** — is [`@autopub`](https://www.npmjs.com/org/autopub) free on npm? Fallback `@w3hc/autopub` alongside [w3pk](https://www.npmjs.com/package/w3pk). 2. **MDX** — a real want, but it drags in a compiler and breaks the "markdown in a `TEXT` column, editable by an LLM over SQL" property from [§10.2](#102-the-neon-connector-in-browser-claude). Recommendation: stay markdown-only through 1.0, and if MDX arrives, make it a separate renderer package. 3. **Does `@autopub/next` deserve to exist**, or should `createBlog()` live in `core` with Next as an optional peer? Splitting it is cheap now and expensive later — keep the split. 4. **Presets as packages vs. one `@autopub/presets` with subpaths** — separate packages, so peer deps are honest. Revisit only if publishing overhead bites. 5. **The real adoption test** — the second consumer. [avventura-v3](file:///Users/ju/avventura-v3) or the docs site should be it, and it should exist *before* 0.1.0 ships, otherwise this is a refactor wearing an SDK costume. --- ## Appendix A — before / after ```ts // BEFORE — src/app/[slug]/page.tsx, 118 lines import { getPost, formatPostDate } from '@/lib/posts' import PostContent from '@/components/PostContent' export const dynamic = 'force-dynamic' export async function generateMetadata({ params }) { /* 30 lines of OG/Twitter */ } export default async function PostPage({ params }) { /* 60 lines of Chakra layout */ } ``` ```ts // AFTER — app/[slug]/page.tsx, 4 lines import { blog } from '@/lib/blog' export const dynamic = 'force-dynamic' export const generateMetadata = blog.createPostMetadata() export default blog.createPostPage({ renderHeader: PostByline }) ``` ## Appendix B — links **This repo:** [package.json](package.json) · [src/lib/posts.ts](src/lib/posts.ts) · [src/lib/markdown.ts](src/lib/markdown.ts) · [src/lib/db.ts](src/lib/db.ts) · [src/components/PostContent.tsx](src/components/PostContent.tsx) · [src/app/\[slug\]/page.tsx](src/app/%5Bslug%5D/page.tsx) · [scripts/posts.ts](scripts/posts.ts) · [src/theme/index.ts](src/theme/index.ts) · [pnpm-workspace.yaml](pnpm-workspace.yaml) · [README.md](README.md) · [GitHub](https://github.com/julienbrg/personal-website) **Stack:** [Next.js](https://nextjs.org/docs) · [React](https://react.dev/) · [Chakra UI v3](https://chakra-ui.com/docs/get-started/installation) · [react-markdown](https://github.com/remarkjs/react-markdown) · [remark-gfm](https://github.com/remarkjs/remark-gfm) · [next-themes](https://www.npmjs.com/package/next-themes) · [Neon serverless driver](https://www.npmjs.com/package/@neondatabase/serverless) **Tooling:** [pnpm workspaces](https://pnpm.io/workspaces) · [tsdown](https://tsdown.dev/) · [Rolldown](https://rolldown.rs/) · [Changesets](https://github.com/changesets/changesets) · [publint](https://publint.dev/) · [Are the Types Wrong?](https://arethetypeswrong.github.io/) · [Knip](https://knip.dev/) · [Vitest](https://vitest.dev/) · [Turborepo](https://turbo.build/repo/docs) · [Nextra](https://nextra.site/) · [Fumadocs](https://fumadocs.dev/) · [npm provenance](https://docs.npmjs.com/generating-provenance-statements) **Neon:** [docs](https://neon.com/docs/introduction) · [MCP server](https://neon.com/docs/ai/neon-mcp-server) · [connect MCP clients](https://neon.com/docs/ai/connect-mcp-clients-to-neon) · [Claude Code + Neon guide](https://neon.com/guides/claude-code-mcp-neon) · [branching](https://neon.com/docs/introduction/branching) **Claude:** [Claude Code docs](https://docs.claude.com/en/docs/claude-code/overview) · [Routines](https://docs.claude.com/en/docs/claude-code/routines) · [routines console](https://claude.ai/code/routines) · [plugins](https://docs.claude.com/en/docs/claude-code/plugins) · [skills](https://docs.claude.com/en/docs/claude-code/skills) · [the local /add skill](file:///Users/ju/.claude/skills/add/SKILL.md) · [Routines guide (MakerKit)](https://makerkit.dev/blog/tutorials/claude-code-routines-guide) · [The Register on Routines](https://www.theregister.com/2026/04/14/claude_code_routines/) --- title: Ethereum Daily — August 28, 2026 date: 2026-08-28 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/eth-daily-2026-08-28 --- # Ethereum Daily — August 28, 2026 # Ethereum Daily — August 28, 2026 Covering roughly August 26–28, 2026. (Note: this is the first post in this daily series — no prior *eth-daily* entry was found to diff against, so today's numbers are framed as current snapshots rather than day-over-day deltas.) **Roadmap.** No material protocol news broke in the last 24–48 hours. Glamsterdam prep continues off the back of the Ethereum Foundation's August 17 announcement of the Platåberget testnet, and the follow-on Hegotá upgrade still has FOCIL (EIP-7805) locked in as its headline consensus-layer feature. Nothing new to report today beyond that ongoing work. **Governance.** No fresh governance developments in the past day. The Ethereum Foundation's leadership reshuffle from earlier this summer remains the backdrop, but it hasn't produced new news in the last 24–48 hours. **Institutional and corporate adoption.** No new announcement in the last day. The most recent notable move was Bitmine's August 24 purchase of 32,447 more ETH, pushing its treasury holdings toward roughly $11 billion — still the freshest corporate-accumulation data point as of today. **Stats.** ETH is trading around $2,499 as of Wednesday morning (Aug 26), up 2.6% on the day, +11.3% over the past week, and +28.3% over the past month. Spot ETH ETFs pulled in $192.4 million in net inflows on August 26, up from $179.8 million on August 25 and $115.6 million on August 24 — six consecutive days of inflows with no tracked product recording an outflow. Ethereum's staking ratio has crossed 35% of supply, with over 42 million ETH staked (~$106 billion). DeFi TVL on Ethereum sits near $50 billion. --- **Sources:** - [Bitcoin and ethereum prices today, Thursday, August 27, 2026](https://finance.yahoo.com/personal-finance/investing/article/bitcoin-and-ethereum-prices-today-thursday-august-27-2026-down-from-highs-but-prices-holding-strong-131109209.html) - [US Bitcoin and Ethereum Spot ETFs Recorded Net Inflows of $314.3M and $179.8M on August 25](https://www.kucoin.com/news/flash/us-bitcoin-and-ethereum-spot-etfs-see-net-inflows-of-314-3m-and-179-8m-on-august-25) - [Crypto ETFs Add $471M as BTC Streak Hits 8 Days](https://financefeeds.com/crypto-etfs-add-471-million-on-august-26-as-bitcoin-extends-eight-day-inflow-streak/) - [Ethereum price prediction: golden cross forms as ETF, staking inflows jump](https://invezz.com/news/2026/08/25/ethereum-price-prediction-golden-cross-forms-as-etf-staking-inflows-jump/) - [Announcing the Platåberget Testnet](https://blog.ethereum.org/2026/08/17/plataberget-testnet) - [Ethereum Glamsterdam upgrade may launch testnet next week, Hegotá selects FOCIL as core feature](https://www.bitget.com/news/detail/12560605359910) - [Ethereum (ETH) News Today | The Block](https://www.theblock.co/tag/ethereum) --- title: Rukh — Roadmap date: 2026-08-27 lang: en-US author: Julien Béranger model: Claude Opus 5 source: https://julienberanger.com/rukh-roadmap --- # Rukh — Roadmap Covers both [`rukh`](https://github.com/w3hc/rukh) (the API) and [`rukh-ui`](https://github.com/w3hc/rukh-ui) (the web app). The two ship in lockstep, so milestones are defined once and split into an **API track** and a **UI track**. Status: proposal. Nothing here is committed to a release date. > **Priority: the teachers & pupils profile.** M0–M4 exist to serve it. The ZK API profile (M7) is explicitly _not_ a priority and carries no date. --- ## 1. Guiding principle **The API stays generalist.** Rukh must never learn what a school is, what a pupil is, or what a lesson plan is. It learns a small number of generic primitives, and every vertical is expressed as _data_ filling those primitives — never as a branch in the API. Concretely, that means the roadmap below is mostly one thing: **growing the context schema from "a bag of files" into "a declarative description of an assistant"**, then implementing the runtime that reads it. Three consequences worth stating up front, because they constrain everything else: 1. **A context is data, not code.** Anything that defines behaviour must be serialisable and inspectable. No vertical-specific handlers. 2. **The same context definition must run on either backend.** Rukh and zk-api are two deployments of the same idea, not two products. A context should move between them by changing one field. 3. **Capabilities are declared, resolved, and enforced per audience.** The same context grants different things to its creator than to an anonymous visitor. This is the mechanism that makes a safe pupil surface possible without a "kids mode" flag in the API. --- ## 2. The context schema Everything converges here. Today a context is `name`, `description`, an optional `model` override, a list of `files`, a list of `links`, and a `queries` log (see `src/dto/context.dto.ts` in the API, and `docs/CONTEXT_MANAGEMENT.md`). Target shape: ```jsonc { "name": "maths-4e", "description": "Maths homework help, French 4e level", "schemaVersion": 2, // How it behaves. Always injected, never subject to RAG selection. "instructions": "You are a Socratic tutor for a 4e class. Never give the final answer; ask the question that moves the pupil forward one step...", // What it can do. Whitelist, resolved per audience (see below). "capabilities": ["skills", "document"], // Who may talk to it, and how they authenticate. "audience": { "creator": { "capabilities": ["skills", "document", "web-reader"] }, "public": false, "code": { "enabled": true, "capabilities": ["skills"] } }, // What gets logged, and for how long. "retention": { "storeMessages": false, "queries": "30d" }, // Which backend serves it. Resolved by the UI, not by the API. "routing": "rukh", // | "zk-api" // Who pays. "billing": "creator", // | "end-user" // Unchanged. "model": "anthropic", "files": [...], "links": [...] } ``` Every field is optional and every default reproduces today's behaviour, so existing contexts keep working untouched. `schemaVersion` exists so the migration can be explicit rather than inferred. --- ## 3. The three profiles A **profile** is a preset that fills the schema above. Profiles live in `rukh-ui` as plain JSON — **the API has no notion of them**. That is the whole point: adding a fourth vertical must require zero API changes. | | Generalist | **Teachers & pupils** | ZK API | | ------------------------- | ------------------ | ------------------------------- | ---------------------------- | | Priority | maintained | **the priority** | not a priority | | `routing` | `rukh` | `rukh` | `zk-api` | | `billing` | `creator` (Stripe) | `creator` (Stripe, school pays) | `end-user` (onchain credits) | | `audience.public` | `true` | `false` | `true` | | Access for end users | open link | class code, no account | anonymous, proof-gated | | `retention.storeMessages` | creator's choice | `false`, enforced | operator's policy | | Identity seen by operator | wallet / anonymous | teacher only | **unlinkable by proof** | | RAG | server-side | server-side | server-side | | Skills | yes | yes | yes | | Server-executed tools | yes | teacher audience only | yes | | Viable from | M0 | M2 | M7 | The education profile is therefore **not a feature** — it is `instructions` + `capabilities` narrowed for the `code` audience + `retention.storeMessages: false` + `audience.public: false`. Nothing in the API changes to support it beyond the generic mechanisms. --- ## 4. Two backends, one context zk-api is getting its own Rukh-style RAG, so the two backends converge on capability. What differs is **identity and payment**, not what the assistant can do. - **Rukh** (`POST /ask`) authenticates the creator by SIWE, meters cost server-side, and bills through Stripe. - **[zk-api](https://github.com/w3hc/zk-api)** (`POST /zk-api/request`) authenticates a _right to spend_ rather than a person: a Groth16 proof plus a one-time nullifier, so two requests from the same depositor cannot be correlated. Transport is ML-KEM-1024 + AES-256-GCM (`src/encryption/mlkem-encryption.service.ts`), with optional TEE attestation. **What zk-api does and does not buy you.** Its own README is the honest statement: _"The operator sees valid proofs and the requests it forwards. It does not see who you are or link your requests together."_ So the operator **does** see request content — which is precisely why it can run RAG. The guarantee is unlinkability of _identity_, not blindness to _content_. Do not describe it in the UI as end-to-end private; describe it as unlinkable. (An earlier draft of this document wrongly assumed the operator was blind to payloads and built a client-side prompt-compilation requirement on top of that. That requirement is dropped.) Because both backends see plaintext and both can do RAG, a context needs no special preparation to move between them. `routing` is a deployment choice, not a capability boundary. --- ## 5. Request routing and PII redaction Routing happens in **`rukh-ui`, at the Next.js API route level** — not in the browser and not in Rukh. This layer does not exist yet: `src/utils/api.ts` currently calls the Rukh API straight from the browser using the public `NEXT_PUBLIC_RUKH_API_URL`, and there is no `route.ts` anywhere under `src/app/`. Target shape: ``` browser ──► /api/ask (Next.js route handler) ├─ redact PII ├─ resolve routing from the context ├──► Rukh POST /ask └──► zk-api POST /zk-api/request ``` What the layer buys, beyond routing: - Backend URLs stop being public. `NEXT_PUBLIC_RUKH_API_URL` becomes a server-only `RUKH_API_URL`, and the browser no longer needs to know where anything lives. - One place to redact, rate-limit, and enforce quotas, rather than one per call site. - SIWE headers are forwarded rather than exposed; the signature is still produced client-side by W3PK, so the trust model is unchanged. **One caveat to accept knowingly.** Proxying makes the Next.js server a party that sees every prompt and every IP. For the Rukh/teacher path that is fine — it is already a trusted first-party server. For the zk-api path it is a genuine regression: it reintroduces exactly the observer that the proofs exist to eliminate. So for `routing: "zk-api"`, the Next.js route should return a _routing decision plus the operator's public key_ and let the browser talk to zk-api directly, redacting client-side before it sends. The package is zero-dependency regex, so it runs in the browser despite the `node` in its name. ### PII redaction Use [`@redactpii/node`](https://www.npmjs.com/package/@redactpii/node) (MIT, v1.0.17, zero dependencies, fully offline): ```ts import { Redactor } from '@redactpii/node' const redactor = new Redactor() const clean = redactor.redact(message) // "Hi PERSON_NAME, call PHONE_NUMBER" ``` It also offers `redactObject()` for whole payloads and `hasPII()` for detection without rewriting, and rules can be enabled selectively (`new Redactor({ rules: { EMAIL: true } })`). **Be honest about what this is worth.** It is defence in depth, not a compliance guarantee, and it must never be sold to a school as "we remove personal data". Three concrete limits: 1. **The built-in patterns are US-centric.** Emails and credit cards travel, but phone matching targets US formats and SSN is a US identifier. French mobile numbers (`06 12 34 56 78`), the NIR, the INE, and French postal addresses are not covered. Extending the rule set for FR/EU identifiers is part of the work, not an afterthought. 2. **Name detection is greeting-based.** It catches `Hi David Johnson`. It will not catch `je m'appelle Léa` or `Léa, 4e B` — which is exactly how a French pupil introduces themselves. Expect close to zero name coverage out of the box in French. 3. **False positives are a real hazard in this product.** A maths context is full of long digit strings that regexes happily mistake for phone numbers or card numbers. Redacting the numbers in a maths exercise is worse than not redacting at all. Since rules are global (below), the mitigations have to be global too. #### Rules are global configuration Redaction rules are **configured once for the deployment**, not per context. A teacher configures an assistant's _behaviour_; they do not configure the privacy floor, and they must not be able to lower it for everyone by editing a context. So the rule set lives in `rukh-ui` as deployment config — a `src/config/redaction.ts` module with environment overrides — read by the Next.js route layer on every request, with a read-only view in `/settings` so an admin can see what is actually active. That decision puts the whole weight of the false-positive problem on the defaults, so they have to carry it: - **Rules are individually toggleable.** `EMAIL` and `CREDIT_CARD` are high-value and low-noise; `PHONE` and `SSN` are the ones that misfire on numeric content. Being able to disable one rule without disabling redaction is the difference between a usable default and one every operator turns off wholesale. - **Exclude fenced code blocks and maths expressions before matching.** A global pre-pass that skips fenced code blocks and `$...$` / `\(...\)` spans removes most of the maths damage without touching the rules themselves. - **Ship a dry-run screen.** Let an admin paste real lesson content and see exactly what would be redacted, before it is switched on. This is the cheapest possible safeguard and the one most likely to be skipped. - **Log redaction counts per rule, never the matched text.** Counts make misfires visible and stay consistent with M0's rule that no message content is stored. The actual privacy guarantee stays what it is in M0: **the message is never stored.** Redaction reduces what transits; not storing is what makes it unrecoverable. Also verify that the package's optional compliance-dashboard integration stays off, and pin that with a test — an outbound call from a redaction library would be a spectacular own goal. --- ## 6. Milestones ### M0 — Schema and privacy foundations The single most important milestone. Everything else fills in fields defined here. **API** - Add `instructions` to `CreateContextDto` / `ContextMetadataDto`, persist it in `index.json`, and inject it at the head of the system prompt **outside** RAG selection (currently the only mechanism is the hardcoded `REQUIRED_FILES = ['instruction-file.md']` in `src/rag/rag.service.ts:21`). Keep the magic filename as a deprecated fallback. - Stop writing the raw user message in `recordContextQuery` (`src/app.service.ts:283`). It currently persists the full prompt verbatim into `data/contexts//index.json`, under a `message` field that `ContextQueryDto` does not even declare, with no retention limit. Replace with `timestamp` + `contextFilesUsed` only. - Add `retention` (`storeMessages`, `queries`) with a purge job. Default `storeMessages: false`. - Declare `capabilities`, `audience`, `routing`, `billing` and `schemaVersion` in the schema and in Swagger. They may be inert at this stage — declaring them early is what lets the UI and the migration be written once. - Write the migration for existing contexts in `data/contexts/`. **UI** - Introduce the Next.js route layer (§5): `/api/ask` and the context calls, moving `src/utils/api.ts` off `NEXT_PUBLIC_RUKH_API_URL`. - Wire `@redactpii/node` into that layer, reading the global rule config, with the FR/EU rule extensions, the code/maths exclusion pre-pass, and the dry-run screen. - Context edit page: a first-class "How should this assistant behave?" field, visually separate from Documents and Links. - A retention control, defaulting to "don't store messages". - Surface `schemaVersion` mismatches instead of failing silently. **Done when** a context can define behaviour without a magic filename, no user message is written to disk anywhere, and every outbound prompt passes through one redaction point. --- ### M1 — Ingestion Rukh accepts markdown. Teachers have PDFs, `.docx`, and photographs of textbook pages. Until a photo of a page becomes a context, nothing downstream matters. **API** - Accept PDF, `.docx` and images on `POST /context/upload`; convert to markdown on ingest and store the markdown (keeping the original is optional and costs privacy). - Images: use the vision path rather than OCR where the model is available. - Extend `src/validators/file.validator.ts` and `src/config/file-upload.config.ts` for the new types and size limits. **UI** - Drag-and-drop upload, conversion progress, preview and edit of the converted markdown before it is saved. Never save a silent bad conversion. **Done when** a teacher can photograph a page and get a usable context without touching markdown. --- ### M2 — Audience and account-free access Unlocks the education profile. Also the milestone with the clearest regulatory driver: the CNIL explicitly tells schools not to collect _"les informations nécessitant la création d'un compte individuel lorsqu'un compte de classe suffit"_ — information requiring an individual account where a class account would do. **API** - Implement `audience`: `creator` (SIWE, as today), `public` (open), `code` (a short shared token, rate-limited, revocable, rotatable). - Resolve capabilities per audience on every request — this is the enforcement point that makes a restricted surface real rather than cosmetic. - Per-audience throttling on top of the existing `ThrottlerGuard`. **UI** - Teacher: generate, display, rotate and revoke a class code; QR code for projection. - Pupil: a route that takes a code and opens a chat with **no authentication, no passkey, no account, nothing persisted**. W3PK stays the teacher's alone — a class of thirty on shared tablets is the worst possible case for device-bound passkeys. **Done when** a class can use a context without a single pupil account existing. --- ### M3 — First capability: document export The deliverable of a teacher's workflow is a worksheet, a quiz or a marking grid — printed or dropped in the ENT. Chat text is not the deliverable. **API** - A capability registry: each capability is a NestJS provider exposing a declaration and a handler, resolved against the context's `capabilities` and the caller's audience. - Implement `document`: markdown → PDF/docx. - Expose the existing `WebReaderService` as the `web-reader` capability (it is already written; it is simply not reachable as one). **UI** - Export action on any assistant response; format choice; a capability picker on the edit page that shows what each audience gets. **Done when** a teacher can go from a photographed page to a printable differentiated worksheet. --- ### M4 — Skills as files The portable half of "capabilities", and the piece that works on **all three profiles**. **API** - A `skills/` subfolder in a context; each file is a named procedure (`make-a-quiz.md`, `grade-with-rubric.md`, `differentiate-three-levels.md`). - Skills are **declarative markdown, never code**. Keeping them as data is what lets them be inspected by a teacher, shared as a file, and reused unchanged on either backend. - Selection: named invocation first (deterministic, cheap); model-chosen selection later if it earns its keep. **UI** - Browse, edit and create skills as a distinct tab from Documents. - Import/export a skill as a file. Teachers already trade lesson plans; a marketplace of _content_ fits their practice far better than a marketplace of _code_, and it is a much cheaper network effect to build. **Done when** a teacher can write a reusable procedure once, invoke it across contexts, and share it with a colleague as a file. --- ### M5 — Stripe: creators pay **API** - Per-context, per-creator usage metering. `CostTracker` (`src/memory/cost-tracking.service.ts`) already computes per-request cost — this is aggregation and persistence, not new measurement. - Stripe customer keyed to the creator's wallet address, metered subscription, quota enforcement with a clear over-quota error, and webhook handling. - Quotas must be enforceable _before_ the expensive capabilities land in M6. **UI** - Billing section in `/settings`: plan, current usage, per-context breakdown, payment method. - Over-quota state on the context page that is honest about what stopped and why. **Note on the two payment rails.** Stripe is identified, invoiced, recurring — right for creators, and the only thing a school's accounting department can process. zk-api credits are anonymous and prepaid — right for privacy-first end users. They are not competing; they serve opposite ends of the same product. A school pays by card while its pupils stay anonymous, and that is a coherent story, not a contradiction. **Done when** a creator can exceed a free tier and be charged for it. --- ### M6 — The tool loop Deliberately late, and deliberately not universal. **API** - An agentic loop over the declared capabilities, gated by the audience resolution from M2. - Depth, cost and wall-clock ceilings per request. - Full trace of tool calls for the creator. **UI** - Render tool calls as they happen; let the creator inspect and replay a trace. **Deliberate non-goal: the pupil assistant is never agentic.** "It cannot browse, it cannot act, it does not remember you after the lesson" is the sentence that gets the product past a headteacher and a DPO. That is the pitch, not a limitation. Agency belongs on the teacher's preparation surface, where the value is high and the risk is nil. --- ### M7 — The ZK API profile **Not a priority. No date.** Listed so the schema stays honest about where it is heading, and so nothing in M0–M6 forecloses it. **API** - Nothing, ideally. If `routing: "zk-api"` requires an API change beyond serving the field, the schema is wrong. **UI** - Route `routing: "zk-api"` contexts to zk-api from the Next.js layer, browser-direct for the reasons in §5. - Proof generation via W3PK, `POST /zk-api/estimate-cost` before sending, `POST /zk-api/request` to send, refund redemption. - A deposit/credits screen, and an honest explanation of the boundary: unlinkable _who_, not private _what_. **Done when** the same context definition runs on both backends with no change beyond one field. --- ## 7. Explicit non-goals - **MCP, for now.** It is the right long-term interface and the wrong foundation today: arbitrary remote servers plus per-user credentials plus minors is an indefensible compliance surface, and no teacher will ever paste a server URL into a form. Revisit after M6 as _one capability among others_, teacher-only, gated by the same whitelist. - **A third-party plugin marketplace.** First-party, audited capabilities only until the security model is proven. - **Vertical-specific API endpoints.** If a milestone seems to need one, the schema is wrong. - **Autonomous-agent registries (ERC-8004 and similar).** Interesting; out of scope for a classroom. - **Presenting redaction as compliance.** See §5. ## 8. Open questions - Should `instructions` be a single field or a small set (persona / constraints / refusals)? A single field is simpler; a structured one is easier to validate and to preset per profile. - Where does the class-code session live so that "nothing persisted" stays true, while a lesson still survives a page reload? - Does the `queries` aggregate (M0) give a teacher anything genuinely useful once messages are gone, or should the "what did the class struggle with" view be a derived skill instead? - The homepage previously grouped contexts into Free / ZK API / For kids / Agentic (see `CHANGELOG.md` 0.2.0) using a `category` field that the API refactor dropped. Do categories come back as a first-class field, or are they derived from `routing` + `billing` + `audience`? Deriving them is more consistent with §1. ## 9. Related documents - [`CONTEXT_MANAGEMENT.md`](https://github.com/w3hc/rukh/blob/main/docs/CONTEXT_MANAGEMENT.md) — how contexts work today - [`MODELS.md`](https://github.com/w3hc/rukh/blob/main/docs/MODELS.md) — provider and model support - [Rukh : se spécialiser pour l'école](https://julienberanger.com/rukh-pour-l-ecole) — the reasoning behind the education profile, with the regulatory sources --- title: Rukh : se spécialiser pour l'école date: 2026-08-26 lang: fr-FR author: Julien Béranger model: Claude Fable 5 source: https://julienberanger.com/rukh-pour-l-ecole --- # Rukh : se spécialiser pour l'école [Rukh](https://github.com/w3hc/rukh) permet aujourd'hui à n'importe qui de construire son propre assistant : on crée un contexte, on y dépose des documents et des liens, on choisit le modèle, et on ouvre la conversation à ses utilisateurs — c'est ce que fait [rukh-ui](https://github.com/w3hc/rukh-ui), et c'est [en ligne](https://rukh.w3hc.org). C'est fonctionnel, mais c'est un positionnement générique. Cette note explique pourquoi je pense qu'il faut spécialiser le produit pour les enseignants et leurs élèves, ce que ça implique concrètement dans le code, et jusqu'où il est raisonnable d'aller sur l'« agentique ». ## 1. Le marché générique est saturé La version générique de l'idée — « construis ton bot, donne-lui tes documents, choisis ton modèle, partage-le » — est un océan rouge. [LibreChat](https://www.librechat.ai/) fait déjà le multi-modèle derrière une interface unique. [Dify](https://dify.ai/) ([dépôt](https://github.com/langgenius/dify)) combine constructeur visuel, RAG et agents, avec auto-hébergement. [Botpress](https://botpress.com/), [Flowise](https://flowiseai.com/), [Open WebUI](https://openwebui.com/), [Chatbase](https://www.chatbase.co/) et [Poe](https://poe.com/) occupent le reste du terrain — voir les comparatifs [d'eesel AI](https://www.eesel.ai/blog/open-source-chatbot-platforms) et de [GPTBots sur Dify](https://www.gptbots.ai/blog/dify-ai). Le RAG en deux étapes, le repli entre fournisseurs et le suivi des coûts — c'est-à-dire une bonne partie de ce que Rukh sait faire — sont devenus le minimum syndical, pas des différenciateurs. La couche éducation est occupée elle aussi, mais à l'américaine : [MagicSchool](https://www.magicschool.ai/), [SchoolAI](https://schoolai.com/), [Khanmigo](https://www.khanmigo.ai/), [Edcafe](https://www.edcafe.ai/), [Brisk](https://www.briskteaching.com/), et depuis novembre 2025 [ChatGPT for Teachers](https://alternativeto.net/news/2025/11/openai-launches-chatgpt-for-teachers-with-free-access-and-ferpa-compliance), gratuit et aligné FERPA. Tous fonctionnent selon le même modèle : le district signe un accord de traitement des données, et les données des élèves partent ensuite chez un éditeur américain sous contrat. La seule chose de notre pile que personne n'a, dans aucune des deux listes, c'est [W3PK](https://github.com/w3hc/w3pk) ([docs](https://w3pk.w3hc.org/docs)) : une identité dérivée d'une [passkey](https://passkeys.dev/), sans e-mail, sans mot de passe, sans compte, avec authentification des requêtes par [SIWE / EIP-4361](https://eips.ethereum.org/EIPS/eip-4361). Sur le marché générique, c'est une curiosité. Sur le marché européen de l'éducation, c'est le produit tout entier — pour la raison qui suit. ## 2. Le créneau est réglementaire Le [cadre d'usage de l'IA en éducation](https://www.education.gouv.fr/cadre-d-usage-de-l-ia-en-education-450647) publié par le ministère en juin 2025 ([PDF](https://www.education.gouv.fr/media/227697/download), [dossier éduscol](https://eduscol.education.gouv.fr/6702/les-intelligences-artificielles-et-leurs-usages-en-education)) contient une phrase qui vaut plus cher que n'importe quelle fonctionnalité : > Il ne faut en aucun cas demander aux élèves de se créer un compte personnel auprès de services d'IA accessibles au grand public. C'est un mur pour toute la cohorte américaine sur la surface élève. Et c'est exactement ce qu'une passkey contourne : une passkey n'est pas un compte chez un tiers. La [CNIL](https://www.cnil.fr/fr/education-mise-en-place-systeme-ia) va plus loin encore, et ses recommandations lisent comme un cahier des charges. À ne pas collecter : « les informations nécessitant la création d'un compte individuel lorsqu'un compte de classe suffit ». À vérifier chez le fournisseur : historique des données désactivé par défaut, amélioration algorithmique désactivée, pas de transferts hors UE/EEE. À privilégier : les solutions _on device_ ou _on premise_. Et une [AIPD](https://www.cnil.fr/fr/RGPD-analyse-impact-protection-des-donnees-aipd) est obligatoire dans la plupart des usages scolaires, les élèves étant mineurs. Le reste du cadre va dans le même sens. L'IA générative est autorisée en classe [à partir de la 4e](https://www.cafepedagogique.net/2025/06/16/pas-dia-generative-avant-la-4eme/), de façon limitée, encadrée et accompagnée par l'enseignant. Aucune donnée personnelle ou confidentielle ne doit être saisie dans ces services — voir aussi le [guide KIDSHAKER](https://www.kidshaker.com/lab-ia/guides/securite-ia-donnees-scolaires/) et [l'analyse d'ia-edu](https://www.ia-edu.fr/chatgpt-ecole-loi-2026-guide-complet/). Et la formation des élèves à l'IA est désormais **obligatoire en 4e, en 2de et en CAP**, via [Pix](https://pix.fr/) : c'est une demande prescrite, pas une demande à créer. La souveraineté a même son canal officiel, avec le [GAR](https://gar.education.fr/) ([explication Primàbord](https://primabord.eduscol.education.fr/qu-est-ce-que-le-gestionnaire-d-acces-aux-ressources-gar)) et [LUCIE](https://lucie.chat/), le modèle ouvert de [Linagora](https://linagora.com/en/lucie-open-source-ai-french-innovation) et [OpenLLM France](https://www.openllm-france.fr/) ([papier Lucie-7B](https://arxiv.org/pdf/2503.12294)). Côté Rukh, l'override `model` par contexte permet déjà d'épingler un contexte sur [Mistral](https://mistral.ai/), français et hébergé en Europe. L'étape de sélection RAG passe d'ailleurs déjà par Mistral. ### Deux réserves avant de s'engager **« Enfants » veut dire collégiens de 13 ans et plus, pas écoliers.** Le produit qu'on construirait pour une classe de 4e n'est pas celui que suggère la carte « For kids » actuelle. Il faut trancher ça avant de dessiner quoi que ce soit. **Les passkeys se marient mal avec le matériel scolaire partagé.** Une passkey est liée à un appareil et à un utilisateur ; une armoire de trente tablettes partagées, c'est le pire cas. Ma recommandation ferme : la surface élève ne doit porter **aucune identité** — un lien de classe, un code, une session éphémère, rien de persisté. L'identité W3PK reste celle de **l'enseignant seul**. C'est à la fois plus simple à construire et plus défendable au RGPD que de distribuer une passkey par élève. C'est d'ailleurs mot pour mot la recommandation CNIL : un compte de classe plutôt qu'un compte individuel. ## 3. Deux correctifs à faire avant tout pilote en établissement Ce sont les deux points qui bloquent, aujourd'hui, dans le code de [l'API](https://github.com/w3hc/rukh). ### 3.1 Arrêter de stocker les messages bruts **Ce qui se passe aujourd'hui.** À chaque appel à `/ask`, la méthode `recordContextQuery` (dans `src/app.service.ts`, autour de la ligne 283) enregistre la requête dans le fichier `data/contexts//index.json`. Et parmi les champs enregistrés, il y a le **message complet de l'utilisateur, en clair** : ```js index.queries.push({ timestamp: new Date().toISOString(), origin: 'anon', message: message, // ← le texte intégral de l'utilisateur contextFilesUsed: [...], }); ``` **Pourquoi c'est un problème.** Trois raisons qui se cumulent : 1. **Le champ n'est pas déclaré.** Le DTO `ContextQueryDto` (`src/dto/context.dto.ts`) annonce `timestamp`, `origin` et `contextFilesUsed` — il ne mentionne pas `message`. Autrement dit, on stocke une donnée que la documentation de l'API ne mentionne nulle part. C'est précisément le genre d'écart qu'un DPO relève en premier, et l'inverse exact de la transparence exigée par le RGPD. 2. **`origin: 'anon'` n'anonymise rien.** C'est l'émetteur qui est anonyme, pas le contenu. Un élève de 4e qui écrit « je m'appelle Léa, je suis au collège Jean-Moulin et je n'arrive pas à faire l'exercice 4 parce que mes parents divorcent » vient de déposer des données personnelles — et potentiellement sensibles — dans un fichier JSON. Le champ libre, c'est exactement là que ces données atterrissent. 3. **Aucune durée de conservation.** Le tableau `queries` grossit indéfiniment. Le RGPD impose une [limitation de la conservation](https://www.cnil.fr/fr/les-durees-de-conservation-des-donnees) ; il n'y en a aucune. Et la CNIL demande explicitement que l'historique soit **désactivé par défaut**. **Ce qu'il faut faire à la place.** L'objectif métier derrière ce journal est légitime : l'enseignant veut savoir sur quoi sa classe a bloqué. On peut le servir sans conserver une seule phrase d'élève. - Ne plus écrire `message`. Conserver `timestamp`, `origin` et `contextFilesUsed` — ce que le DTO déclare déjà, et qui suffit à dire _quels supports ont servi_. - Si on veut un signal plus fin, stocker une **étiquette de thème** dérivée à la volée (« équations du second degré », « accord du participe passé ») plutôt que le texte, ou un simple compteur par fichier de contexte. - Ajouter une **durée de conservation** explicite par contexte, avec purge automatique — 30 jours par défaut, alignés sur la durée d'une séquence pédagogique. - Rendre la journalisation **désactivable par contexte**, et désactivée par défaut sur les contextes destinés aux élèves. - Aligner le DTO sur ce qui est réellement écrit, et le documenter dans [Swagger](https://docs.nestjs.com/openapi/introduction). ### 3.2 Faire des instructions un vrai champ **Ce qui se passe aujourd'hui.** Le prompt système d'un contexte est intégralement construit à partir des fichiers markdown sélectionnés par le RAG. Il n'existe pas de champ « instructions » ou « persona ». Il existe bien un mécanisme, mais c'est une **convention par nom de fichier magique**. Dans `src/rag/rag.service.ts`, ligne 21 : ```ts private readonly REQUIRED_FILES = ['instruction-file.md']; ``` Un fichier qui porte exactement ce nom est épinglé : il est ajouté à la sélection même si le RAG ne l'a pas retenu, et il est trié en tête du prompt. L'intuition est la bonne — sans cet épinglage, les garde-fous pourraient disparaître du prompt selon la question posée. **Pourquoi ça ne suffit pas.** Le mécanisme fonctionne, mais : - il repose sur **un seul nom codé en dur**, invisible pour qui n'a pas lu le service RAG ; - il n'apparaît **dans aucun DTO** ni dans la documentation de l'API ; - il est **inatteignable depuis rukh-ui** : la page d'édition d'un contexte gère des fichiers et des liens, pas un comportement ; - il **mélange comportement et connaissance** dans la même liste de fichiers, alors que ce sont deux choses de nature différente. La connaissance, c'est ce que l'assistant _sait_. Les instructions, c'est ce qu'il _fait_. Concrètement, un enseignant n'a aujourd'hui aucun moyen de dire « sois socratique, ne donne jamais la réponse finale, reste sur le programme de 4e ». C'est pourtant la première chose qu'il voudra régler. **Ce qu'il faut faire à la place.** - Ajouter un champ `instructions` de premier ordre dans `index.json`, déclaré dans `CreateContextDto` et `ContextMetadataDto` via les [décorateurs NestJS](https://nestjs.com/), et exposé dans Swagger. - **Toujours l'injecter** en tête du prompt système, hors de la sélection RAG : il n'est jamais candidat, il est toujours présent. - Le **séparer des fichiers de connaissance** dans l'interface, avec une question en clair pour l'enseignant : « Comment cet assistant doit-il se comporter ? » - Conserver `instruction-file.md` en repli pour les contextes existants, mais le déprécier. Avant : ```json { "name": "maths-4e", "description": "Aide aux devoirs en mathématiques, niveau 4e", "files": [ { "name": "instruction-file.md", "description": "..." }, { "name": "chapitre-thales.md", "description": "..." } ] } ``` Après : ```json { "name": "maths-4e", "description": "Aide aux devoirs en mathématiques, niveau 4e", "instructions": "Tu es un tuteur socratique pour une classe de 4e. Ne donne jamais la réponse finale : pose une question qui fait avancer l'élève d'un pas. Reste sur le programme de 4e.", "retention": { "queries": "30d", "storeMessages": false }, "files": [{ "name": "chapitre-thales.md", "description": "..." }] } ``` ## 4. L'agentique : côté enseignant, pas côté élève Le recadrage que je défends : **il ne faut pas rendre l'assistant élève agentique.** « Il ne peut pas naviguer, il ne peut pas agir, il ne se souvient pas de toi après la séance » — c'est la phrase qui fait passer un principal et un DPO. Ce n'est pas une limitation, c'est l'argument de vente. L'agentivité doit vivre côté enseignant, là où la valeur est haute et le risque nul : un assistant qui lit le chapitre, prépare la séance, en sort trois versions différenciées, le quiz et le corrigé. ### Ce que les enseignants demanderont en premier 1. **Ingérer ce qu'ils ont vraiment.** Rukh n'accepte que du markdown. Les enseignants ont des PDF, des .docx et des photos de pages de manuel. Tant qu'une photo de page ne devient pas un contexte, rien de ce qui suit n'a d'importance. Ce n'est pas de l'agentique du tout — c'est le ticket d'entrée. 2. **Rendre quelque chose d'imprimable.** Le livrable d'un enseignant, c'est une fiche, un quiz, une grille — imprimés ou déposés sur l'ENT. Le texte de chat n'est pas le livrable. L'export PDF/docx est la demande numéro un du segment. 3. **Différencier** : la même fiche en trois niveaux, plus une version pour un élève dyslexique. C'est la fonctionnalité la plus louée chez [MagicSchool](https://www.magicschool.ai/), et c'est du pur prompt. 4. **Voir sur quoi la classe a bloqué**, en agrégé. Le journal `queries` en est le squelette — une fois qu'il ne stocke plus de texte brut (voir 3.1). 5. **Corriger sur barème.** L'[étude Gallup / Walton Family Foundation](https://news.gallup.com/poll/691967/three-teachers-weekly-saving-six-weeks-year.aspx) situe le gain à 5,9 heures par semaine pour les enseignants qui utilisent l'IA au moins une fois par semaine, soit [six semaines par an](https://www.waltonfamilyfoundation.org/six-weeks-giving-teachers-time-back-with-ai) — chiffres à lire avec [le recul qu'ils méritent](https://medium.com/chalkdust-silicon/the-walton-gallup-ai-report-deserves-context-not-just-headlines-56164a7303a3). Mais c'est aussi là que sont les données sensibles : à garder côté enseignant et en éphémère. La CNIL est nette sur ce point, un enseignant ne peut pas déléguer une décision de notation à un système. Le point important : **les points 1, 2, 3 et 5 ne demandent aucun appel d'outil.** Ils demandent de l'ingestion de fichiers, un champ instructions et un moteur de rendu de documents. L'essentiel de ce qui ressemble à une « demande agentique » sur ce marché se satisfait sans boucle d'agent. ## 5. La notion de module : oui, mais comme déclaration de capacités Oui à des modules dans l'API Rukh — à condition de les définir comme une **déclaration de capacités portée par le contexte**, et non comme un système de plugins. Concrètement : un champ `capabilities: string[]` dans `index.json`, à côté du champ `instructions`, qui puise dans un petit registre de modules **de première partie**. Côté implémentation, chaque module est un [provider NestJS](https://docs.nestjs.com/providers) ordinaire qui expose un schéma d'[outil](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview) et un handler ; c'est Rukh qui exécute la boucle d'outils. Trois pour commencer : - `web-reader` — le `WebReaderService` existe déjà, il n'est simplement pas exposé comme outil ; à rapprocher de l'[outil de recherche web côté serveur](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool) déjà câblé dans `anthropic.service.ts` ; - `document` — rendu markdown vers PDF/docx ; - `quiz` — un parcours d'exercices avec état. Point crucial : les capacités doivent se résoudre **par contexte _et_ par audience**. Un même contexte accorde `document` à son créateur et rien du tout à la classe. ### Pourquoi pas MCP tout de suite [MCP](https://modelcontextprotocol.io/) est la bonne interface à long terme et la mauvaise fondation maintenant. Des serveurs distants arbitraires, plus des identifiants par utilisateur, plus des mineurs : c'est une surface de conformité indéfendable, à rebours de la préférence CNIL pour l'_on premise_. Et aucun enseignant ne collera jamais une URL de serveur dans un formulaire. À sortir plus tard comme **un module parmi d'autres**, réservé aux enseignants, filtré par la même liste blanche de capacités — qui est précisément le garde-fou dont MCP a besoin. Même remarque pour les pistes d'agents autonomes type [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) explorées dans les notes internes : intéressant, hors sujet pour une salle de classe. ## 6. L'alternative que je préfère : les compétences en fichiers À considérer **à la place** des modules, et plus tôt : les compétences sous forme de fichiers, dans l'esprit des [Skills](https://www.anthropic.com/news/skills). Un contexte est déjà un dossier de markdown. Un sous-dossier `skills/` où chaque fichier est une procédure nommée — `faire-un-quiz.md`, `corriger-avec-bareme.md`, `differencier-en-trois-niveaux.md` — donne aux enseignants des comportements réutilisables, inspectables et partageables, sans aucun nouveau runtime et sans revue de code. Les enseignants partagent déjà des séquences et des fiches. Un marché de **contenu** colle infiniment mieux à leurs pratiques qu'un marché de **code** — et c'est un effet de réseau bien moins cher à construire. Je ferais ça avant la boucle d'outils. ## 7. Séquencement proposé 1. Arrêter de persister les messages bruts, et promouvoir les instructions en vrai champ (section 3). 2. Ingestion PDF, docx et images. 3. Le lien de classe sans compte élève ; W3PK réservé à l'enseignant. 4. L'export de documents, comme premier vrai module. 5. Les compétences en fichiers. 6. Seulement ensuite : la boucle d'outils et la liste blanche de capacités. MCP bien plus tard, et côté enseignant uniquement. ## 8. Réserve commerciale Vendre au collège en France est lent : [GAR](https://gar.education.fr/), marchés publics, délégations académiques au numérique, registres RGPD, [AIPD](https://www.cnil.fr/fr/RGPD-analyse-impact-protection-des-donnees-aipd). La voie rapide passe par les enseignants à titre individuel, les associations d'enseignants, l'enseignement privé et les établissements internationaux — ou par un partenariat avec un éditeur d'ENT. Et je retirerais « Build your own AI » pour ce marché. La promesse qui vend ressemble plutôt à : **l'assistant de votre classe — sans compte élève, hébergé en Europe.** --- ## Références ### Cadre réglementaire français - [Cadre d'usage de l'IA en éducation](https://www.education.gouv.fr/cadre-d-usage-de-l-ia-en-education-450647) — ministère de l'Éducation nationale, juin 2025 ([PDF](https://www.education.gouv.fr/media/227697/download)) - [Les intelligences artificielles et leurs usages en éducation](https://eduscol.education.gouv.fr/6702/les-intelligences-artificielles-et-leurs-usages-en-education) — éduscol - [Comment mettre en place des systèmes d'IA dans l'éducation ?](https://www.cnil.fr/fr/education-mise-en-place-systeme-ia) — CNIL - [Les durées de conservation des données](https://www.cnil.fr/fr/les-durees-de-conservation-des-donnees) — CNIL - [L'analyse d'impact relative à la protection des données (AIPD)](https://www.cnil.fr/fr/RGPD-analyse-impact-protection-des-donnees-aipd) — CNIL - [Pas d'IA générative avant la 4e](https://www.cafepedagogique.net/2025/06/16/pas-dia-generative-avant-la-4eme/) — Café pédagogique - [ChatGPT à l'école : la loi 2026 expliquée aux parents](https://www.ia-edu.fr/chatgpt-ecole-loi-2026-guide-complet/) — ia-edu - [IA et données scolaires : sécurité, RGPD et souveraineté](https://www.kidshaker.com/lab-ia/guides/securite-ia-donnees-scolaires/) — KIDSHAKER - [GAR — Gestionnaire d'accès aux ressources](https://gar.education.fr/) ([présentation Primàbord](https://primabord.eduscol.education.fr/qu-est-ce-que-le-gestionnaire-d-acces-aux-ressources-gar)) - [Pix](https://pix.fr/) — plateforme de la formation obligatoire des élèves à l'IA ### Souveraineté et modèles européens - [LUCIE](https://lucie.chat/) — modèle ouvert français ([Linagora](https://linagora.com/en/lucie-open-source-ai-french-innovation), [OpenLLM France](https://www.openllm-france.fr/), [papier Lucie-7B](https://arxiv.org/pdf/2503.12294)) - [Mistral AI](https://mistral.ai/) ### Plateformes génériques - [LibreChat](https://www.librechat.ai/) · [Dify](https://dify.ai/) ([dépôt](https://github.com/langgenius/dify)) · [Botpress](https://botpress.com/) · [Flowise](https://flowiseai.com/) · [Open WebUI](https://openwebui.com/) · [Chatbase](https://www.chatbase.co/) · [Poe](https://poe.com/) - [Comparatif des plateformes open source](https://www.eesel.ai/blog/open-source-chatbot-platforms) — eesel AI - [Revue de Dify](https://www.gptbots.ai/blog/dify-ai) — GPTBots ### Plateformes éducatives - [MagicSchool](https://www.magicschool.ai/) · [SchoolAI](https://schoolai.com/) · [Khanmigo](https://www.khanmigo.ai/) · [Edcafe](https://www.edcafe.ai/) · [Brisk](https://www.briskteaching.com/) - [OpenAI lance ChatGPT for Teachers](https://alternativeto.net/news/2025/11/openai-launches-chatgpt-for-teachers-with-free-access-and-ferpa-compliance) - [Les meilleurs créateurs de chatbots pour enseignants](https://www.edcafe.ai/blog/best-ai-chatbot-makers) — Edcafe ### Études d'usage - [Three in 10 Teachers Use AI Weekly, Saving Six Weeks a Year](https://news.gallup.com/poll/691967/three-teachers-weekly-saving-six-weeks-year.aspx) — Gallup - [Six Weeks: Giving Teachers Time Back with AI](https://www.waltonfamilyfoundation.org/six-weeks-giving-teachers-time-back-with-ai) — Walton Family Foundation - [Why the Walton Gallup AI Report Needs Context](https://medium.com/chalkdust-silicon/the-walton-gallup-ai-report-deserves-context-not-just-headlines-56164a7303a3) — mise en perspective critique - [Teachers saving six weeks a year with AI](https://blog.aieducator.tools/posts/teachers-saving-six-weeks-with-ai) ### Technique - [Rukh](https://github.com/w3hc/rukh) · [rukh-ui](https://github.com/w3hc/rukh-ui) · [instance en ligne](https://rukh.w3hc.org) - [W3PK](https://github.com/w3hc/w3pk) — authentification Web3 sans mot de passe ([documentation](https://w3pk.w3hc.org/docs)) - [EIP-4361 — Sign-In with Ethereum](https://eips.ethereum.org/EIPS/eip-4361) · [passkeys.dev](https://passkeys.dev/) - [NestJS](https://nestjs.com/) — [providers](https://docs.nestjs.com/providers), [OpenAPI/Swagger](https://docs.nestjs.com/openapi/introduction) - [Model Context Protocol](https://modelcontextprotocol.io/) · [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) - [Tool use](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview) et [outil de recherche web](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool) — documentation Claude - [Agent Skills](https://www.anthropic.com/news/skills) — Anthropic --- title: L'IA à l'école : France / Chine à la rentrée 2026 date: 2026-08-25 lang: fr-FR author: Julien Béranger model: Claude Opus 5 conversation: https://claude.ai/chat/39f51b52-125c-4d2b-be4a-f39bfa6764de source: https://julienberanger.com/ia-a-l-ecole-chine-vs-france --- # L'IA à l'école : France / Chine à la rentrée 2026 > **État des lieux arrêté au 25 août 2026**, jour de la conférence de presse de rentrée du ministère français de l'Éducation nationale. Périmètre : enseignement scolaire (primaire + secondaire), avec incursions dans le supérieur quand la comparaison l'exige. Tous les liens ont été vérifiés à cette date. Les sources sont classées par niveau de fiabilité en fin de document. * * * ## 1\. Résumé Les deux pays affichent le même vocabulaire — souveraineté, esprit critique, formation des enseignants — mais ne construisent pas le même objet. | | **France** | **Chine** | | ---------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | **Nature de la politique** | Un **cadre** : règles d'usage, formation, éducation à l'IA | Une **infrastructure** : calcul, corpus, modèles, agents, terminaux | | **Texte pivot** | [Cadre d'usage de l'IA en éducation](https://www.education.gouv.fr/media/227697/download) (juin 2025) | [Plan d'action « IA + éducation »](https://www.nda.gov.cn/sjj/zwgk/tzgg/0410/20260410162428109058250_pc.html) 教科信〔2026〕1号 (avril 2026) | | **Portage interministériel** | MEN seul (financement France 2030 via SGPI) | 5 administrations : MOE + NDRC + MIIT + MOST + Administration nationale des données | | **Horizon chiffré** | Aucun objectif quantifié à date | Fusion « profonde » IA/éducation d'ici **2030** | | **Horaires élèves** | Parcours Pix IA (30 min – 1 h 30), une fois, en 4e / 2de / 1re année CAP | 6 à 10 **heures par an**, chaque année, selon les provinces | | **Enseignement dédié** | 1 h/semaine en 2de GT, **rentrée 2027** (dans SNT) | Déjà en place localement ; option obligatoire au lycée dans certaines provinces | | **LLM fourni aux élèves** | **Aucun** | Modèles éducatifs d'État **en développement**, agents homologués déjà en ligne | | **Modèle mental** | Réguler l'usage d'outils tiers | Produire une chaîne technique publique de bout en bout | **La ligne de faille tient en une phrase** : la France a écrit qui a le droit d'utiliser quoi ; la Chine a désigné un maître d'ouvrage, un budget et une pile technique. Les deux approches ont des angles morts, détaillés en [§10](https://claude.ai/chat/39f51b52-125c-4d2b-be4a-f39bfa6764de#10-angles-morts-et-limites-de-la-comparaison). * * * ## 2\. Le socle normatif ### 2.1 France | Texte | Date | Contenu utile | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------- | | [Cadre d'usage de l'IA en éducation](https://www.education.gouv.fr/media/227697/download) ([synthèse éduscol](https://eduscol.education.gouv.fr/6702/les-intelligences-artificielles-et-leurs-usages-en-education), [affiche](https://eduscol.education.gouv.fr/sites/default/files/document/affichecadre-usage-iapdf-113376.pdf)) | juin 2025 | Progression par âge, interdiction du transit de données personnelles, usage frugal, priorité aux solutions libres | | [Rapport IGÉSR « L'IA dans les établissements scolaires »](https://www.education.gouv.fr/inspection-generale-de-l-education-du-sport-et-de-la-recherche/l-intelligence-artificielle-dans-les-etablissements-scolaires-465627) | mai 2025 | Diagnostic de terrain | | [Fiche CNIL « Enseignant : comment utiliser un système d'IA »](https://www.cnil.fr/fr/enseignant-usage-systeme-ia) | m.à.j. juillet 2026 | Recommande de privilégier les ressources du **GAR** ; rappelle l'attente d'un outil souverain | | [Circulaire de rentrée 2026](https://www.education.gouv.fr/priorites-pour-l-annee-scolaire-2026-2027-504725) (BO n°19 du 7 mai 2026) | mai 2026 | Consolidation, pas de réforme structurelle ; IA traitée sous l'angle du numérique raisonné | | [Dossier de rentrée « L'École au cœur »](https://www.education.gouv.fr/sites/default/files/document/rentree-scolaire-2026-2027-l-ecole-au-coeur-519805.pdf) (PDF, 4,6 Mo — section 1.3, p. 26-27) | 25 août 2026 | Doctrine IA consolidée, calendrier 2026-2027 | **Le principe de progression** (cadre d'usage, repris p. 26 du dossier de rentrée) : * **Primaire** : sensibilisation aux notions, **sans manipulation directe** d'IA générative. * **À partir de la 4e** : usage pédagogique en classe autorisé, limité, encadré, expliqué et accompagné par l'enseignant, dans le respect des données personnelles. * **Lycée** : usage autonome possible, dans un cadre d'apprentissage explicitement défini par l'enseignant. Deux exigences transversales pour les personnels : aucune donnée personnelle ou confidentielle ne transite par ces outils, et l'usage reste proportionné au besoin compte tenu du coût environnemental. ### 2.2 Chine | Texte | Date | Contenu utile | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------- | | 《关于加强中小学人工智能教育的通知》 (Notice sur le renforcement de l'éducation à l'IA) | déc. 2024 | Cadre initial, objectif de généralisation d'ici 2030 | | [《中小学人工智能通识教育指南(2025年版)》](https://www.edu.cn/xxh/focus/zc/202505/t20250513_2667996.shtml) — Guide de culture générale IA | mai 2025 | Curriculum spiralaire : primaire = intérêt et notions ; collège = principes techniques ; lycée = pensée systémique et innovation | | [《中小学生成式人工智能使用指南(2025年版)》](https://xinwen.bjd.com.cn/content/s68217324e4b0ec1c3d96f32d.html) — Guide d'usage de l'IA générative | mai 2025 | Bornes d'usage par cycle (voir §4) | | [Réunion nationale sur l'éducation de base 2026](https://www.edu.cn/xxh/focus/zc/202602/t20260211_2719074.shtml) | 6 fév. 2026 | Faire entrer l'IA dans **les programmes officiels, l'enseignement quotidien et l'évaluation aux examens** | | [Plan d'action « IA + éducation »](https://www.nda.gov.cn/sjj/zwgk/tzgg/0410/20260410162428109058250_pc.html) 教科信〔2026〕1号 ([miroir eol.cn](https://www.eol.cn/zhengce/wenjian/202604/t20260410_2727386.shtml)) | 2 avril 2026 | 16 chantiers, de la formation des élèves à la sécurité des modèles | Le plan d'action est adossé au [《教育强国建设规划纲要(2024—2035年)》](https://www.eol.cn/zhengce/wenjian/202604/t20260410_2727386.shtml) et à l'avis du Conseil des affaires d'État sur l'action « IA+ ». C'est un document d'architecture, pas une circulaire pédagogique. * * * ## 3\. Ce que l'élève reçoit : curriculum et horaires ### 3.1 France — un point de passage, pas un cours * **Parcours Pix IA** : obligatoire à compter de la **rentrée 2026** pour tous les élèves de **4e, 2de générale et 1re année de CAP**, accessible sur la base du volontariat aux autres. Durée annoncée : **30 minutes à 1 h 30** selon le profil, après diagnostic initial. Contenu : fonctionnement des IA génératives, formulation de requêtes (*prompting*), gestion des données personnelles, regard critique. → [Page éduscol de référence](https://eduscol.education.gouv.fr/6702/les-intelligences-artificielles-et-leurs-usages-en-education) · [Plateforme Pix](https://pix.fr/) *Ces parcours sont formatifs : ils ne donnent lieu ni à attestation spécifique ni à certification Pix autonome, et s'insèrent dans le continuum du* [*CRCN*](https://eduscol.education.gouv.fr/4506/cadre-de-reference-des-competences-numeriques)*.* * **SNT rénové** : le [Conseil supérieur des programmes](https://www.education.gouv.fr/csp) travaille à l'intégration d'un volet IA. Formation des enseignants pendant **2026-2027**, entrée en vigueur **rentrée 2027**, horaire de SNT porté de 1 h 30 à **2 h hebdomadaires** en 2de GT. Trois objectifs annoncés : bases scientifiques et techniques de l'IA, usage éclairé des outils, esprit critique sur les dimensions sociétale, éthique, juridique et environnementale. → Annonce initiale du Premier ministre le 19 juin 2026 : [CNews](https://www.cnews.fr/france/2026-06-19/les-eleves-de-seconde-auront-un-cours-lintelligence-artificielle-partir-de-la) · [KultureGeek](https://kulturegeek.fr/news-354051/lycees-auront-lenseignement-lia-2027) * **EMI et EMC** : l'IA est traitée comme objet critique dans l'[éducation aux médias et à l'information](https://eduscol.education.gouv.fr/4704/education-aux-medias-et-l-information), avec sensibilisation aux ingérences numériques étrangères en lien avec [Viginum](https://www.sgdsn.gouv.fr/notre-organisation/composantes/service-de-vigilance-et-protection-contre-les-ingerences-numeriques-etrangeres-viginum) et le [CLEMI](https://www.clemi.fr/). * **Scénarios pédagogiques** : la banque [Édubase](https://edubase.eduscol.education.fr/) recense des séquences disciplinaires. Exemples représentatifs : * [Diversifier les formats rhétoriques avec un LLM](https://edubase.eduscol.education.fr/fiche/23918) (philosophie) * [Apprentissage de la dissertation avec l'IA : de la simulation à l'émulation](https://edubase.eduscol.education.fr/fiche/24058) (philosophie) * [ViTa : un chatbot créé pour et par les élèves](https://edubase.eduscol.education.fr/fiche/23178) (SVT, 1re spécialité, via ChatMD) * [Comparer les réponses de différents modèles d'IA](https://edubase.eduscol.education.fr/fiche/23412) (BPH, avec [compar:IA](https://comparia.beta.gouv.fr/)) * [Évaluer la plausibilité des images générées par IA](https://edubase.eduscol.education.fr/fiche/22730) (EMI) * [Défi botanique : entraîner une IA à reconnaître les plantes](https://edubase.eduscol.education.fr/fiche/23517) (SVT, IA non générative) * Premier degré : [deux scénarios débranchés sur Primabord](https://primabord.eduscol.education.fr/aborder-la-thematique-de-l-intelligence-artificielle-a-l-ecole-elementaire) ### 3.2 Chine — un volume horaire annuel, décliné par province Le ministère a laissé aux échelons locaux le soin de fixer les horaires, ce qui produit une mosaïque déjà largement couvrante : | Territoire | Volume | Modalité | | ------------ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | **Pékin** | ≥ **8 heures/an**, tous niveaux (primaire → lycée) | Cours autonome **ou** intégration en informatique, sciences, pratiques intégrées, travail manuel | | **Shandong** | ≥ **6 h/an** en 1re-2e année, ≥ **8 h/an** de la 3e à la 9e année | Enseignement obligatoire ; au lycée, option obligatoire « Initiation à l'IA » (人工智能初步) | | **Hangzhou** | ≥ **10 heures/an** | Semaine bloquée **ou** diffusion dans les cours de technologie et sciences ; projets en périscolaire | Sources : [Renmin Ribao / chinanews, 23 août 2026](https://www.chinanews.com.cn/edu/2026/08-23/10682495.shtml) · [China Daily sur Pékin](https://www.chinadaily.com.cn/a/202510/21/WS68f6d9bea310f735438b601e.html) · [SCMP sur Hangzhou](https://www.scmp.com/economy/china-economy/article/3323082/chinas-hangzhou-makes-ai-classes-compulsory-schools-amid-nationwide-push) La prochaine étape annoncée par le MOE est l'intégration **systématique** de l'éducation à l'IA dans les curricula locaux de toutes les provinces, avec objectifs, contenus et horaires explicites par cycle — et, au-delà, l'entrée de l'IA dans les **programmes nationaux** (课程标准) et dans **l'évaluation aux examens**. **Ordre de grandeur comparé** : un élève chinois de collège aura suivi ~8 h/an × 3 ans = **24 heures** d'IA sur le cycle. Un collégien français aura suivi **un parcours Pix de 30 à 90 minutes**, une fois, en 4e. * * * ## 4\. Ce que l'élève a le droit d'utiliser C'est ici que l'intuition commune s'inverse : **la Chine est plus restrictive que la France sur l'usage libre de l'IA générative par les jeunes élèves**, tout en fournissant davantage d'outils. | Niveau | France (cadre d'usage, juin 2025) | Chine (使用指南, mai 2025) | | ------------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **Primaire** | Sensibilisation sans manipulation directe d'IAG | **Interdiction** pour l'élève d'utiliser seul la génération de contenu ouverte ; l'enseignant peut y recourir en classe à des fins d'appui | | **Collège** | Usage en classe autorisé **à partir de la 4e**, encadré et accompagné | Exploration mesurée de l'**analyse de la logique** des contenus générés ; vérification croisée guidée | | **Lycée** | Usage **autonome** possible, cadre défini par l'enseignant | Apprentissage exploratoire adossé aux **principes techniques** ; évaluation autonome de l'impact social des contenus | | **Fraude** | Tout usage non autorisé dans un devoir, ou sans travail personnel d'appropriation, est une fraude | **Interdiction explicite** de recopier directement un contenu généré comme devoir ou réponse d'examen ; usage abusif borné dans les tâches créatives | | **Rôle de l'enseignant** | L'IA assiste, ne remplace jamais l'expertise professionnelle | **Interdiction** de substituer l'IA générative aux missions d'enseignement essentielles | → Analyse détaillée des deux guides : [Guangming Daily](https://news.gmw.cn/2025-05/13/content_38021450.htm) · [Renmin Wang](http://edu.people.com.cn/n1/2025/0513/c1006-40478610.html) · [Zhongguo Jiaoyu Bao](https://paper.jyb.cn/zgjyb/h5/html5/2025-05/13/content_144741_18514867.htm) · [Global Times (EN)](https://www.globaltimes.cn/page/202505/1333878.shtml) **Conséquence pratique en France** : le lycéen autorisé à un usage autonome doit aller chercher l'outil lui-même — ChatGPT, Gemini, Le Chat, Copilot — hors de tout périmètre institutionnel, avec les questions de RGPD, de conditions d'âge et de fiabilité que cela pose. Le cadre autorise un usage pour lequel il ne fournit pas de support. * * * ## 5\. Ce que l'institution met effectivement à disposition ### 5.1 France — l'inventaire réel **Pour les élèves :** * [**MIA Seconde**](https://gar.education.fr/mia-seconde-une-ressource-de-remediation-numerique-en-mathematiques-et-francais/) — le seul service à IA déployé nationalement à destination directe des élèves. * Nature : **exerciseur adaptatif**, pas un LLM. Test de positionnement puis parcours personnalisé. * Contenu : 24 modules (16 français, 8 mathématiques), **plus de 20 000 exercices**, ~300 vidéos d'accompagnement. * Éditeur : [EvidenceB](https://evidenceb.fr/produits/miaseconde/) avec Docaposte, via appel à projets du MEN. * Accès : gratuit, **via le GAR / Médiacentre de l'ENT**, pour toutes les 2de GT, pro et prépa-2de. Généralisé à la rentrée 2025. * Évaluation : étude d'impact randomisée pilotée par Sciences Po et le laboratoire [IDEE](https://www.idee-education.org/), avec bras avec et sans algorithmes d'IA. * Références académiques : [ac-Paris](https://www.ac-paris.fr/mia-seconde-un-service-numerique-de-remediation-en-francais-et-en-mathematiques-131013) · [DRANE Grenoble](https://dane.web.ac-grenoble.fr/actualites-nationales/mia-lia-pour-les-secondes) · [ac-Lyon](https://lettres-histoire-geographie.enseigne.ac-lyon.fr/spip/spip.php?article573=) * [**P2IA cycle 3**](https://eduscol.education.gouv.fr/6717/l-intelligence-artificielle-au-service-des-apprentissages-du-francais-des-mathematiques-et-des-langues-vivantes-au-cycle-3) (CM1, CM2, **6e**) — lancé rentrée 2025, soutenu par France 2030 et la Banque des Territoires. **Six services** en expérimentation depuis janvier 2026 : * Français : **Expliq** (l'élève tutore des avatars via un chat augmenté — consortium Edinnov, Mathena, Scopeo, IRIT), **Edumalin** (étayage méthodologique paramétrable) * Mathématiques : **MathIA-C3** (manipulation et visualisation 3D avec compagnon numérique) * Langues vivantes : **Cards**, **yLANG** * Statut : phase R&D, généralisation non acquise avant fin 2026. * Équivalent cycle 2 : [page éduscol dédiée](https://eduscol.education.gouv.fr/6714/l-intelligence-artificielle-pour-accompagner-les-apprentissages-des-fondamentaux-au-cycle-2) **Pour les enseignants :** * [**La Forge des communs numériques éducatifs**](https://forge.apps.education.fr/) (LaForgeEdu) — instance **GitLab** éditée par le MEN, adossée au portail [apps.education.fr](https://portail.apps.education.fr/) et à l'annuaire fédéré des agents. Plus de 1 600 projets. Mise en avant explicite dans le dossier de rentrée 2026 comme la brique « IA sur infrastructures souveraines et mutualisées ». * [Documentation](https://docs.forge.apps.education.fr/) · [Cartographie des projets](https://cartographie.forge.apps.education.fr/) · [Mission communs numériques](https://communs-numeriques.forge.apps.education.fr/) · [Panorama éduscol](https://eduscol.education.fr/4199/education-et-communs-numeriques) * Brique la plus proche d'un usage LLM en classe : [**ChatMD**](https://chatmd.forge.apps.education.fr/) — création d'un chatbot à partir d'un simple fichier Markdown, avec possibilité de brancher un LLM et de faire du RAG sur une base de connaissances fournie par l'enseignant ([dépôt](https://forge.apps.education.fr/drne/chatmd), [modèle prêt à l'emploi](https://forge.apps.education.fr/docs/modeles/modele-chatmd)). C'est un contournement élégant du problème d'hallucination : le professeur borne le corpus. * Autres : [MathALÉA](https://coopmaths.fr/mathalea/), [Capytale](https://capytale2.ac-paris.fr/), [Marklab](https://marklab.forge.apps.education.fr/), Éléa (Moodle académique) * **Formation** : [CREIA sur Magistère](https://partage02.magistere.apps.education.fr/course/section.php?id=1463) (communauté de réflexion), [parcours « Utiliser l'IA générative de façon éclairée et responsable »](https://partage02.magistere.apps.education.fr/course/view.php?id=625), [module Mentor « Découvrir les IA génératives »](https://mentor.gouv.fr/local/catalog/pages/training.php?trainingid=3189), [projet européen AI4T](https://eduscol.education.gouv.fr/6645/intelligence-artificielle-et-education-les-ressources-de-formation-du-projet-europeen-ai4t). **~1 000 formateurs académiques** formés au niveau national, chargés de démultiplier en 2026-2027. * [**Observatoire national des pratiques pédagogiques avec l'IA**](https://eduscol.education.gouv.fr/6884/observatoire-national-des-pratiques-pedagogiques-avec-l-intelligence-artificielle) — remontée déclarative d'actions innovantes et de ressources, validée par l'établissement ([contexte](https://acteurspublics.fr/articles/ia-le-ministere-de-leducation-nationale-deploie-son-observatoire-de-suivi-des-dispositifs/)). **Le trou dans la raquette : l'IA souveraine pour les enseignants** Annoncée le 7 février 2025 par Élisabeth Borne — [appel à projets de 20 M€ financé par France 2030, IA « souveraine, ouverte et évolutive », disponible dès l'année scolaire 2026-2027](https://www.education.gouv.fr/intelligence-artificielle-au-service-de-l-education-des-mesures-ambitieuses-pour-accompagner-les-462591) — elle était encore attendue par la [CNIL en juillet 2026](https://www.cnil.fr/fr/enseignant-usage-systeme-ia). **Elle n'apparaît pas dans la section IA du dossier de rentrée 2026-2027.** Le ministère y met en avant le cadre, la formation et la Forge, pas un assistant maison livré cette année. Aucun texte public ne dit à ce jour que le projet est abandonné ou décalé ; c'est un silence, pas une annulation. À titre de comparaison, l'enseignement supérieur français a avancé plus vite par la voie de l'achat : l'[Amue a ouvert fin janvier 2026 un service d'IA générative souveraine avec Mistral](https://www.amue.fr/publications/actualites/details/une-ia-souveraine-pour-lesr-lamue-ouvre-un-service-dexperimentation-des-2026), opéré sur datacenter labellisé au sein de la fédération ILAAS, avec accès à Le Chat Pro et à l'API Medium 3, facturé en « parts » (1 part = 10 sièges + 150 000 tokens = 600 €), ~540 parts distribuées, en vue d'un appel d'offres à l'échelle des 180 établissements. ### 5.2 Chine — la pile technique prévue par le plan d'action Le §(dix) du plan, « construire un socle intelligent économe et efficace », est le passage le plus concret et n'a pas d'équivalent français : 1. **Plateforme nationale de services de calcul pour l'éducation** (国家教育智能算力服务平台) : agrégation de calcul, données, modèles et outils. 2. **Interconnexion réseau** : mobilisation de [CERNET](https://www.edu.cn/) (réseau informatique de l'éducation et de la recherche) pour relier terrains d'entraînement nationaux, hubs de calcul, entreprises et universités ; appui sur le réseau national intégré de puissance de calcul (全国一体化算力网) pour les échelons provinciaux. 3. **Corpus de base nationaux** (国家基础语料库) : constitués par domaine — éducation idéologique et politique, savoirs disciplinaires, recherche scientifique — avec incitation aux jeux de données spécialisés locaux et universitaires. 4. **Centre national de données éducatives** (国家教育大数据中心) : réseau de données inter-administrations, inter-régions, inter-plateformes. 5. **Modèles éducatifs d'État** : *« l'État mène un effort de recherche organisé et développe des grands modèles pour l'éducation déclinés par étape d'enseignement, en renforçant l'alignement sur les valeurs, le raisonnement logique et la sécurité éthique, afin de fournir un support aux collectivités et aux universités et d'éviter le gaspillage de ressources et la duplication de projets de bas niveau. »* (traduction de synthèse du §10) **Côté applicatif** (§11 et §6-7) : * **智能学伴** — « compagnons d'apprentissage intelligents » à développer pour les élèves, adossés à un **dossier numérique de l'élève** avec optimisation dynamique du parcours. * **思政大模型** — modèle dédié à l'éducation idéologique et politique. Point de divergence civilisationnelle majeure : l'alignement sur les valeurs est un objectif d'ingénierie explicite, pas un effet de bord. * **Agents éducatifs homologués** : un système d'évaluation des capacités applicatives sélectionne les meilleurs 教育智能体 pour **mise en ligne sur la plateforme nationale**. C'est un mécanisme d'homologation d'État, l'exact inverse de la logique française de laisser l'enseignant choisir sous sa responsabilité. * **Base nationale de pré-industrialisation** (中试基地) fournissant des biens publics : graphes de connaissances, de compétences et de qualités des élèves, pour abaisser le coût d'entrée des éditeurs. * **Communauté d'apprentissage IA** : cours open source, co-construction de corpus, co-évaluation de modèles, co-création d'applications. * **Terminaux et espaces** : classes/écoles/centres d'apprentissage du futur, manuels numériques expérimentaux, MOOC intelligents de nouvelle génération, expériences virtuelles, profils utilisateurs élèves construits par analyse de données. **Ce qui est déjà en ligne** (à distinguer du plan) : * **Plateforme nationale de service public d'éducation intelligente** : [nouvelle version publiée le 31 mars 2026](http://www.moe.gov.cn/jyb_xwfb/s5147/202604/t20260401_1432643.html), réorganisée en centre d'éducation scolaire, centre d'innovation scientifique, centre d'apprentissage tout au long de la vie et centre d'enseignement du chinois. * **Plus de 1 000 cours d'IA** agrégés, tous cycles confondus, explicitement destinés à permettre aux écoles rurales et reculées d'assurer l'enseignement d'IA. * **Espace « éducation à l'IA »** du portail scolaire, structuré en quatre volets (apprendre l'IA / utiliser l'IA / établissements-ateliers / textes réglementaires), avec l'agent **育小苗** présenté à la Conférence mondiale sur l'éducation numérique et des vagues successives d'outils homologués — 14 applications supplémentaires annoncées en mai 2026 couvrant préparation de cours, interaction en classe, anglais, mathématiques, expériences virtuelles et évaluation ([source médias sociaux, à prendre avec réserve](https://www.163.com/dy/article/KSTVDGDH05386T63.html)). * Supérieur : [programme national « IA 2026 »](https://higher.smartedu.cn/ai2026) imposant un parcours _culture générale → interdisciplinaire → pratique_ incluant au moins un cours de **mise en œuvre pratique sur les grands modèles nationaux**, avec certificat électronique. * * * ## 6\. Les enseignants | | France | Chine | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Volume de formation** | ~1 000 formateurs académiques formés nationalement, chargés de démultiplier en 2026-2027 | 250 000 chefs d'établissement et directeurs d'éducation locaux formés ; 310 000+ enseignants du centre et de l'ouest ; sessions estivales sur la plateforme nationale | | **Formation initiale** | Nouveaux référentiels métier applicables en septembre 2026, incluant « approche technique, pédagogique et critique de l'usage de l'IA en cadre scolaire » ; arrêté du 12 janvier 2026 sur le master | Réforme de la formation des normaliens ; intégration de l'IA au **concours et à la certification** des enseignants | | **Standard de compétence** | CRCN-Édu, non obligatoire pour la certification | Norme nationale de « littératie intelligente » des enseignants à élaborer, avec système d'évaluation situé et outils de mesure gradués | | **Outillage** | Forge, ChatMD, IA grand public sous responsabilité de l'agent | Préparation de cours co-créée homme-machine, correction/réponse/tutorat intelligents, analyse des comportements de classe, recherche-action fondée sur les preuves | Sources : [dossier de rentrée FR](https://www.education.gouv.fr/sites/default/files/document/rentree-scolaire-2026-2027-l-ecole-au-coeur-519805.pdf) · [Renmin Ribao/chinanews](https://www.chinanews.com.cn/edu/2026/08-23/10682495.shtml) · [plan d'action CN §5](https://www.nda.gov.cn/sjj/zwgk/tzgg/0410/20260410162428109058250_pc.html) · analyse critique française : [Café pédagogique](https://www.cafepedagogique.net/2026/06/12/preparer-la-rentree-2026-2027-avec-ou-sans-ia/) * * * ## 7\. Évaluation et intégrité : deux mouvements opposés C'est le contraste le plus net du dossier. **France — protéger l'évaluation _de_ l'IA.** Le dossier de rentrée pose que l'IA générative oblige à repenser les modalités d'évaluation, en remettant au premier plan **le raisonnement, les démarches, la production en présence et l'oral**, afin de préserver l'intégrité des apprentissages et l'équité entre élèves. S'y ajoutent, pour la session 2027, des consignes de correction harmonisées sur l'orthographe, la syntaxe et la grammaire dans toutes les disciplines, et un bilan comparatif contrôle continu / épreuves terminales transmis à chaque proviseur et principal. **Chine — faire entrer l'IA *dans* l'évaluation.** La réunion nationale de février 2026 fixe explicitement l'entrée de l'IA dans les programmes, l'enseignement quotidien **et l'évaluation aux examens**. Le plan d'action détaille : **conception intelligente de sujets, composition automatisée d'épreuves, surveillance intelligente, correction intelligente** (智能命题、智能组卷、智能监考、智能评卷), plus des outils d'évaluation longitudinale du parcours d'apprentissage et transversale sur les cinq dimensions éducatives. Autrement dit : là où la France déplace l'épreuve hors de portée de la machine, la Chine confie une partie de la chaîne d'examen à la machine. Les deux stratégies répondent au même constat — l'évaluation écrite à distance n'est plus fiable — par des mouvements symétriquement inverses. * * * ## 8\. Sécurité, données et gouvernance technique | Dimension | France | Chine | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Données personnelles** | Interdiction de transit par les IAG ; RGPD ; recommandation de passer par le **GAR** pour limiter les transferts hors UE ; [doctrine CNIL](https://www.cnil.fr/fr/enseignant-usage-systeme-ia) | Classification et gradation des données éducatives (指南 publié fin 2025) ; protection sur tout le cycle de vie | | **Sécurité des modèles** | Pas de dispositif d'homologation de modèles à ce stade | **Mécanisme d'audit de sécurité des grands modèles éducatifs** garantissant des contenus « positifs et sains » ; normes de test de sécurité couvrant modèles, données, infrastructures et applications | | **Entrée des produits à l'école** | Choix laissé à l'enseignant sous sa responsabilité, dans le cadre d'usage ; GAR pour les ressources contractualisées | Gestion renforcée de l'entrée de l'IA à l'école, normes explicites d'usage des produits et terminaux ; normalisation logicielle | | **Risques visés** | Biais, données personnelles, impact environnemental, dépendance cognitive, fraude | Fraude par falsification, tricherie académique, dérive vers le bachotage (应试内卷), fuites de vie privée | | **Identité / accès** | [ÉduConnect](https://educonnect.education.gouv.fr/) + [GAR](https://gar.education.fr/) + ENT ; objectif de 100 % des élèves sous identité ÉduConnect à la rentrée 2026 | Plateformes nationales à compte unique, remontée de données inter-plateformes | | **Souveraineté** | Priorité aux solutions libres ; côté État, [Albert](https://www.numerique.gouv.fr/) (DINUM) et l'assistant Mistral pour agents publics ; Mistral hébergé chez Outscale en SecNumCloud | Chaîne intégralement domestique par construction ; export du modèle promu via l'Alliance ouverte sur l'IA et l'UNESCO | Sur la question, transverse et souvent oubliée, de la sécurité des SI éducatifs : le ministère français a rendu publique le 31 juillet 2026 [une intrusion frauduleuse survenue dans la nuit du 25 juillet](https://www.education.gouv.fr/), avec exfiltration de données personnelles d'agents — rappel utile que la « souveraineté » se joue aussi au niveau des systèmes existants. * * * ## 9\. Équité territoriale **France** — le pilotage national ne comporte pas de dispositif IA dédié aux territoires fragiles ; ce sont les **collectivités** qui bougent. Exemple emblématique : la Région Hauts-de-France, avec sa feuille de route « L'IA avec nous », déploie à partir de la rentrée 2026-2027 un programme d'accompagnement pour ses **220 000 lycéens**, en partenariat renforcé avec Réseau Canopé — acculturation à l'IA et esprit critique, assistant d'accessibilité pour élèves à troubles des apprentissages, parentalité numérique via l'ENT régional, espaces d'innovation pédagogique, production de contenus. → [Article Région HDF](https://generation.hautsdefrance.fr/au-lycee-lia-fait-deja-sa-rentree-des-classes/) · [Réseau Canopé HDF](https://www.reseau-canope.fr/accompagner-les-usages-numeriques-dans-les-lycees-des-hauts-de-france.html) · [Appel à projets « Génération IA »](https://generation.hautsdefrance.fr/generation-ia-la-region-lance-un-appel-a-projets-pour-preparer-lecole-de-demain/) **Chine** — l'équité est un objectif explicite du plan national : consolidation des bases d'éducation à l'IA, appui aux écoles rurales et reculées via la plateforme nationale, déploiement des technologies intelligentes dans les régions du centre et de l'ouest, promotion de la langue commune, outils d'assistance intelligents pour l'éducation spécialisée. En pratique : corps de conférenciers municipaux et de district à Pékin avec « envoi de cours dans les écoles » et jumelage ville-campagne ; plan d'appui aux zones faibles et centres régionaux d'expérience en IA au Shandong. * * * ## 10\. Angles morts et limites de la comparaison Quatre précautions, à conserver avant toute conclusion normative. 1. **Beaucoup du dispositif chinois est au futur.** Le modèle éducatif national est à développer (研发), pas livré. L'horizon du plan est 2030. Comparer un plan chinois à une réalité française — ou l'inverse — fausse le résultat. 2. **Les volumes chinois sont difficiles à auditer.** Les chiffres de couverture (nombre d'enseignants formés, écoles équipées) proviennent des administrations elles-mêmes et ne font pas l'objet d'évaluations indépendantes publiées. À l'inverse, MIA Seconde fait l'objet d'une **étude d'impact randomisée** — un standard de preuve que le dispositif chinois n'affiche pas. 3. **Les finalités ne sont pas commensurables.** Un modèle dédié à l'éducation idéologique et politique, avec audit de contenu garantissant des sorties « positives et saines », n'est pas une brique technique neutre transposable. La comparaison est valide sur l'ingénierie et le déploiement, pas sur les objectifs. 4. **La France a un choix cohérent, pas seulement un retard.** Refuser de fournir un LLM aux mineurs, autoriser l'usage encadré et déplacer l'évaluation vers l'oral et la production en présence est une position défendable, notamment au regard des travaux sur la charge cognitive et la dépendance. Le reproche recevable n'est pas l'abstention en soi : c'est d'autoriser à partir du lycée un usage autonome sans fournir ni outil de confiance ni garantie de fiabilité, ce qui revient à externaliser le risque sur l'élève et sa famille. * * * ## 11\. Calendrier comparé | Date | France | Chine | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | déc. 2024 | — | Notice MOE sur le renforcement de l'IA au primaire/secondaire | | fév. 2025 | Annonce des 20 M€ France 2030 (IA enseignants) + parcours Pix | — | | mai 2025 | Rapport IGÉSR | Publication des deux guides (通识教育 / 生成式AI使用) | | juin 2025 | Publication du cadre d'usage | — | | sept. 2025 | Généralisation MIA Seconde ; lancement P2IA cycle 3 ; Pix IA ouvert | Entrée en vigueur des horaires obligatoires (Pékin, Hangzhou…) | | janv. 2026 | Expérimentations P2IA en classe ; Amue/Mistral ouvre | Conférence nationale : généraliser l'IA à tous les cycles | | fév. 2026 | Note de service Pix IA (BO du 5 février) | IA dans programmes + enseignement + examens | | mars-avril 2026 | Circulaire de rentrée (BO du 7 mai) | Nouvelle plateforme nationale (31 mars) ; **plan d'action « IA+éducation »** (2 avril) | | juin 2026 | Annonce SNT-IA pour 2027 ; installation du comité de prospective | Conférence mondiale sur l'éducation numérique | | **sept. 2026** | **Pix IA obligatoire (4e/2de/CAP1) ; déploiement du cadre d'usage pour les personnels ; téléphone interdit jusqu'au lycée** | **Poursuite de la généralisation des horaires ; extension des agents homologués** | | 2027 | 1 h/semaine d'IA en 2de (SNT à 2 h) ; Forum national de l'IA éducative (28 juin – 2 juillet) ; premiers scénarios du comité de prospective | Plateformes de calcul et bases de pré-industrialisation | | 2030 | — | Objectif de fusion profonde IA/éducation | * * * ## 12\. Chiffres de contexte (rentrée 2026, France) * **11 607 600** écoliers, collégiens et lycéens attendus le 1er septembre 2026, dont 3 321 950 collégiens et 2 261 550 lycéens * **851 500** professeurs, **57 700** écoles et établissements * **25,3** élèves par classe au collège, **30,5** en lycée GT, **18,7** en lycée professionnel * Horizon 2035 : **−1,7 million d'élèves** par rapport à 2025 Source : [dossier de rentrée, p. 8-9](https://www.education.gouv.fr/sites/default/files/document/rentree-scolaire-2026-2027-l-ecole-au-coeur-519805.pdf) * * * ## 13\. Sources, par niveau de fiabilité### Niveau 1 — textes officiels et institutionnels **France** * [Cadre d'usage de l'IA en éducation (PDF)](https://www.education.gouv.fr/media/227697/download) * [Dossier de rentrée 2026-2027 « L'École au cœur » (PDF)](https://www.education.gouv.fr/sites/default/files/document/rentree-scolaire-2026-2027-l-ecole-au-coeur-519805.pdf) * [Priorités pour l'année scolaire 2026-2027 / circulaire de rentrée](https://www.education.gouv.fr/priorites-pour-l-annee-scolaire-2026-2027-504725) * [Cap sur l'année scolaire 2026-2027 (page de la conférence de presse)](https://www.education.gouv.fr/cap-sur-l-annee-scolaire-2026-2027-505438) * [éduscol — Les IA et leurs usages en éducation](https://eduscol.education.gouv.fr/6702/les-intelligences-artificielles-et-leurs-usages-en-education) * [éduscol — Observatoire national des pratiques pédagogiques avec l'IA](https://eduscol.education.gouv.fr/6884/observatoire-national-des-pratiques-pedagogiques-avec-l-intelligence-artificielle) * [éduscol — P2IA cycle 3](https://eduscol.education.gouv.fr/6717/l-intelligence-artificielle-au-service-des-apprentissages-du-francais-des-mathematiques-et-des-langues-vivantes-au-cycle-3) · [cycle 2](https://eduscol.education.gouv.fr/6714/l-intelligence-artificielle-pour-accompagner-les-apprentissages-des-fondamentaux-au-cycle-2) * [Annonces IA du 7 février 2025 (20 M€ France 2030)](https://www.education.gouv.fr/intelligence-artificielle-au-service-de-l-education-des-mesures-ambitieuses-pour-accompagner-les-462591) * [CNIL — Enseignant : comment utiliser un système d'IA](https://www.cnil.fr/fr/enseignant-usage-systeme-ia) * [Rapport IGÉSR — L'IA dans les établissements scolaires (mai 2025)](https://www.education.gouv.fr/inspection-generale-de-l-education-du-sport-et-de-la-recherche/l-intelligence-artificielle-dans-les-etablissements-scolaires-465627) * [GAR — MIA Seconde](https://gar.education.fr/mia-seconde-une-ressource-de-remediation-numerique-en-mathematiques-et-francais/) * [Forge des communs numériques éducatifs](https://forge.apps.education.fr/) · [documentation](https://docs.forge.apps.education.fr/) · [éduscol Éducation et communs numériques](https://eduscol.education.fr/4199/education-et-communs-numeriques) * [Amue — IA souveraine pour l'ESR avec Mistral](https://www.amue.fr/publications/actualites/details/une-ia-souveraine-pour-lesr-lamue-ouvre-un-service-dexperimentation-des-2026) **Chine** * [Plan d'action « IA + éducation », texte intégral (Administration nationale des données)](https://www.nda.gov.cn/sjj/zwgk/tzgg/0410/20260410162428109058250_pc.html) · [miroir eol.cn](https://www.eol.cn/zhengce/wenjian/202604/t20260410_2727386.shtml) · [synthèse des mesures d'accompagnement](https://www.yznu.edu.cn/2026/0410/c8291a277276/page.htm) * [MOE — nouvelle version de la plateforme nationale (31 mars 2026)](http://www.moe.gov.cn/jyb_xwfb/s5147/202604/t20260401_1432643.html) * [CERNET — réunion de déploiement de l'éducation de base 2026](https://www.edu.cn/xxh/focus/zc/202602/t20260211_2719074.shtml) * [CERNET — questions-réponses sur les deux guides de mai 2025](https://www.edu.cn/xxh/focus/zc/202505/t20250513_2667996.shtml) * [Plateforme nationale du supérieur — programme « IA 2026 »](https://higher.smartedu.cn/ai2026) ### Niveau 2 — presse établie * [Renmin Ribao via chinanews, 23 août 2026 — « L'IA entre en classe : comment mieux l'enseigner »](https://www.chinanews.com.cn/edu/2026/08-23/10682495.shtml) * [China Daily — Pékin rend l'éducation à l'IA obligatoire](https://www.chinadaily.com.cn/a/202510/21/WS68f6d9bea310f735438b601e.html) * [SCMP — Hangzhou rend les cours d'IA obligatoires](https://www.scmp.com/economy/china-economy/article/3323082/chinas-hangzhou-makes-ai-classes-compulsory-schools-amid-nationwide-push) * [Global Times — les deux guides du MOE](https://www.globaltimes.cn/page/202505/1333878.shtml) * [Guangming Daily](https://news.gmw.cn/2025-05/13/content_38021450.htm) · [Renmin Wang](http://edu.people.com.cn/n1/2025/0513/c1006-40478610.html) · [Zhongguo Jiaoyu Bao](https://paper.jyb.cn/zgjyb/h5/html5/2025-05/13/content_144741_18514867.htm) * [Acteurs publics — l'Observatoire du MEN](https://acteurspublics.fr/articles/ia-le-ministere-de-leducation-nationale-deploie-son-observatoire-de-suivi-des-dispositifs/) * [Café pédagogique — Préparer la rentrée 2026-2027, avec ou sans IA](https://www.cafepedagogique.net/2026/06/12/preparer-la-rentree-2026-2027-avec-ou-sans-ia/) * [L'Étudiant / Educpros — règles d'utilisation proposées aux élèves et enseignants](https://www.letudiant.fr/educpros/actualite/intelligence-artificielle-leducation-nationale-propose-des-regles-dutilisation-aux-eleves-et-aux-enseignants.html) * [CNews — 1 h d'IA en seconde dès 2027](https://www.cnews.fr/france/2026-06-19/les-eleves-de-seconde-auront-un-cours-lintelligence-artificielle-partir-de-la) * [Labo Société Numérique — dossier rentrée et IA](https://labo.societenumerique.gouv.fr/fr/articles/dossier-rentr%C3%A9e-scolaire-2025-enseigner-et-apprendre-%C3%A0-lheure-des-ia-et-de-lencadrement-des-usages-num%C3%A9riques/) ### Niveau 3 — sources à recouper (éditeurs, collectivités, médias sociaux) * [EvidenceB — MIA Seconde](https://evidenceb.fr/produits/miaseconde/) _(éditeur)_ * [Région Hauts-de-France — l'IA au lycée](https://generation.hautsdefrance.fr/au-lycee-lia-fait-deja-sa-rentree-des-classes/) *(collectivité)* * [iFlytek — retour d'expérience à la conférence mondiale 2026](https://edu.iflytek.com/about-us/news/company-news/2745) *(industriel, chiffres non audités)* * [Recension de 14 outils IA ajoutés à la plateforme nationale](https://www.163.com/dy/article/KSTVDGDH05386T63.html) *(média social chinois)* * [Compilation de politiques 2025-2026 (Guangdong, Guizhou, normes techniques)](https://www.sohu.com/a/990314234_122316869) *(média social chinois)* * * * _Document produit le 25 août 2026. Les politiques publiques évoluant vite dans ce domaine, vérifier notamment : le sort de l'appel à projets France 2030 sur l'IA enseignante, la publication du programme rénové de SNT par le CSP, et la première livraison du comité de prospective français attendue début 2027._ --- title: Security Audit — w3hc/zk-api date: 2026-08-25 lang: en-US author: Julien Béranger model: Claude Sonnet 5 source: https://julienberanger.com/security-audit-july-2 --- # Security Audit — w3hc/zk-api **Target commit:** `76d93dade12f5b9522d8cfb1d38f02439226c108` ("Fix placeholder VK in PolicyViolationVerifier…") **Scope:** Circom circuits, Solidity contracts (`contracts/src`), NestJS backend (`src/`), deployment/config (Docker, env, CI). **Nature of system:** Privacy-preserving "API credits" using Rate-Limit Nullifiers (RLN) + Groth16, a TEE-hosted proving/verifying server, and an on-chain escrow (`ZkApiCredits`) holding user ETH. > **Methodology note / caveat.** This is a manual review of source plus compiled circuit artifacts. The circuit actually wired into the running server (`api_credit_proof_test`) has **no `.circom` source in the repo** — only build artifacts — so statements about it are inferred from its verification key (`nPublic = 5`) and how the backend constructs its public signals. I did not run the test suite or reproduce exploits on-chain; PoC descriptions are analytical. Severities reflect a second review pass in which two of the three headline findings (C-2, C-3) were downgraded from the initial ratings — treat these as hypotheses to validate on a funded testnet deployment, with C-1 as the one unconditional drain. --- ## Summary of findings | ID | Severity | Title | Status | |----|----------|-------|--------| | C-1 | **Critical** | Refund circuit takes the server's EdDSA public key as a *private, unconstrained* input → anyone can forge refunds and drain the contract | ✅ **DONE** | | C-2 | **High** | Withdrawal & refund recipients are not bound into the proof → mempool front-running can steal funds (deployment-dependent) | ✅ **DONE** | | C-3 | **Medium–High** | Auxiliary `/proofs/*` endpoints accept the raw `secretKey` in plaintext, unauthenticated and ungated → key exposure outside the enclave trust boundary | 🔶 **PARTIAL** | > **Severity note (revised after a second review pass).** C-2 and C-3 were downgraded from the initial "Critical" rating. The IDs `C-1/C-2/C-3` are kept stable for continuity, but they are **not** all critical — only C-1 is. See each finding for the reasoning. C-1 is the one to fix before anything else. | H-1 | High | `policy_violation.circom` is vacuous → server can burn any active user's policy stake at will | | H-2 | High | Active verification uses a stripped "test" circuit lacking membership/solvency/signature checks → no anonymity set, free unlimited API usage | | H-3 | High | Owner can hot-swap verifiers with no timelock → owner can install an always-true verifier and drain all funds | | H-4 | High | On-chain EdDSA verifier is a stub returning `true`; backend `redeemRefund` ABI does not match the deployed contract | ✅ **DONE** | | M-1 | Medium | No Merkle-root history → any deposit invalidates in-flight withdrawal/refund proofs (griefing / liveness) | | M-2 | Medium | Double-spend slashing circuit binds nothing to real observed shares; slashed deposits strand `policyStake` | | M-3 | Medium | Nullifier rate-limiting is in-process and keyed on attacker-controlled input | | M-4 | Medium | Swagger exposed at root in prod; env validation `skipMissingProperties` never enforces required secrets | | M-5 | Medium | `deposit()` permits duplicate leaves for inactive commitments; zeros[0] comment/code mismatch | | L-1..L-5 | Low/Info | Committed dev private key, operator submit key from plain env, MEV reliance, logging, stranded stake | --- ## The three headline findings (C-1, C-2, C-3), reassessed Only **C-1 is Critical**. C-2 is **High** and C-3 is **Medium–High**; the reasoning for the downgrades is in each entry. ### C-1 — Anyone can forge refund tickets and drain the escrow · **Critical** **Where:** `circuits/refund_redemption.circom`; `contracts/src/ZkApiCredits.sol::redeemRefund`; `circuits/build/refund_redemption_verification_key.json` (`nPublic = 5`). The refund circuit verifies an EdDSA signature over `Poseidon(idCommitment, nullifier, refundValue, refundTimestamp)`, but the key it verifies against is supplied by the prover as a **private** input: ```circom signal input serverPublicKeyX; // private signal input serverPublicKeyY; // private ... component main {public [signalX, refundValueClaimed]} = RefundRedemptionProof(); ``` `serverPublicKeyX/Y` are neither public inputs nor outputs, so nothing constrains them to the real server key. On-chain, `redeemRefund` only checks `refundValue`, `nullifier`, and `idCommitment` against the proof's public signals — it never references the stored `serverPublicKey`: ```solidity require(_publicSignals[1] == _refundValue, 'refundValue mismatch'); require(_publicSignals[2] == uint256(_nullifier), 'nullifier mismatch'); require(_publicSignals[4] == uint256(_idCommitment), 'idCommitment mismatch'); // serverPublicKey is NEVER consulted here ``` **Impact — total loss of funds.** An attacker: 1. Deposits the minimum stake with `idCommitment = Poseidon(attackerSecret)` (now `active`). 2. Generates their own EdDSA keypair and signs a ticket for an arbitrary `refundValue` (up to the contract balance). 3. Produces a valid refund proof, passing *their own* key as `serverPublicKeyX/Y` — the circuit's `EdDSAPoseidonVerifier` passes because it checks against the attacker's key. 4. Calls `redeemRefund(...)`; the Groth16 check passes and the ETH is sent to an attacker-chosen recipient. 5. Repeats with fresh `nullifier`/`ticketIndex` until the contract is empty. Contrast with `api_credit_proof.circom`, which correctly makes `serverPubKeyX/serverPubKeyY` **public** inputs — the redemption circuit simply forgot to. **Fix:** Make the server key a *public* input to the refund circuit (or hard-code it as a circuit constant), and in `redeemRefund` require the proof's server-key public signals to equal the on-chain `serverPublicKey`. Re-run the trusted setup and regenerate the verifier. **Why this stays Critical (and one caveat).** Every value the contract checks (`refundValue`, `nullifier`, `idCommitment`) is attacker-controlled, the function is `external` with no access control, and the exploit needs no privileged role — so on any deployment holding funds this is a total drain. The only caveat is that the system does not currently appear to work end-to-end (the backend's `redeemRefund` call uses an ABI that doesn't match the deployed contract — see H-4), so today this is a **latent** critical: harmless only for as long as the contract is never deployed with real value. Anyone deploying this contract as-is inherits a drainable escrow. --- ### C-2 — Withdrawal and refund recipients are unbound → front-running theft · **High** (was Critical) **Status: ✅ FIXED (2026-07-06)** **Where:** `contracts/src/ZkApiCredits.sol::withdraw` / `redeemRefund`; `circuits/withdrawal.circom`; `circuits/refund_redemption.circom`. `withdraw` takes `_recipient` as a plain parameter. The withdrawal circuit's public signals are `[signalX, merkleRootExpected, nullifier, signalY, idCommitment, merkleRoot]` — **no recipient and no `msg.sender`**. The proof therefore says nothing about who receives the money: ```solidity function withdraw(bytes32 _idCommitment, address payable _recipient, uint256[8] calldata _proof, uint256[6] calldata _publicSignals) external { ... (bool success, ) = _recipient.call{value: totalAmount}(''); ``` **Impact.** When the legitimate owner broadcasts their withdrawal, the full `(_proof, _publicSignals)` is visible in the public mempool. An attacker copies it verbatim, replaces `_recipient` with their own address, and front-runs with higher gas. The proof still verifies, `idCommitment`/`merkleRoot` still match, the deposit is marked inactive — and the funds go to the attacker. The identical problem applies to `redeemRefund` (recipient not in public signals). The hardcoded Flashbots/MEV-blocker RPCs in `.env.template` suggest awareness of MEV, but a private mempool is a per-user convention, not a contract-level guarantee — it does not fix an on-chain function that anyone can call with a copied proof. **Why High rather than Critical.** The bug is real and the "recipient must be a public input" pattern is the correct, well-established fix. But unlike C-1, exploitability is *conditional*: it needs a public mempool **and** a victim-initiated transaction. On a chain/relay where the withdrawal is submitted privately (which this project's hardcoded RPCs suggest is the intended path), the window largely closes. That is mitigation-by-convention, not a fix — the contract still cannot force private submission and still accepts a copied proof from anyone — so it remains a must-fix theft vector, but it is not the always-on, no-preconditions drain that C-1 is. Hence High. **Fix:** Bind the recipient into the circuit as a public input (add `signal input recipient;` and include it in `main`'s public list), and have the contract require `_publicSignals[recipientIdx] == uint256(uint160(_recipient))`. This makes a copied proof useless with a different recipient. **Status: ✅ FIXED (2026-07-06)** The C-2 finding has been fully resolved: 1. **Circuits Updated:** - `withdrawal.circom` now includes `recipient` as public input (line 66) - `refund_redemption.circom` now includes `recipient` as public input (line 33) - Both circuits recompiled with new public input arrays 2. **Contracts Updated:** - `ZkApiCredits.sol::withdraw()` now uses `uint256[7]` public signals (was 6) and verifies recipient at index 2 - `ZkApiCredits.sol::redeemRefund()` now uses `uint256[8]` public signals (was 7) and verifies recipient at index 4 - Both functions reject proofs where `_publicSignals[recipientIdx] != uint256(uint160(_recipient))` 3. **Verifier Contracts Regenerated:** - `WithdrawalVerifier.sol` regenerated with 7 public signals - `RefundRedemptionVerifier.sol` regenerated with 8 public signals 4. **Security Impact:** - Front-running attacks are now prevented: copied proofs cannot be used with different recipients - Defense-in-depth: works even with public mempools (not just private RPCs) - Proofs are cryptographically bound to the intended recipient address **Note:** Backend integration pending - proof generation services need to include `recipient` parameter in circuit inputs. --- ### C-3 — Auxiliary `/proofs/*` endpoints ingest the raw secret key · **Medium–High** (was Critical) **Where:** `src/zk-api/zk-api.controller.ts` — `POST /zk-api/proofs/withdrawal`, `/proofs/refund`, `/proofs/slashing`; `src/zk-api/dto/proof-generation.dto.ts`; TLS model in `src/main.ts`. **Correction to the initial rating.** My first pass framed this as an inherent "the system takes your secret key" critical. That was an overreach on two counts, so it is downgraded: 1. **The main flow does *not* take the secret key.** `POST /zk-api/request` (`ZkApiRequestDto`) accepts a `proof`, `nullifier`, and `signal` — the client generates the proof locally and sends only the proof. A user on the primary path never transmits their key. The key-ingesting routes are the *auxiliary* `/proofs/{withdrawal,refund,slashing}` proving helpers, which a careful client can decline to use (prove locally instead). 2. **This is a TEE system.** With attestation, in-enclave proving is a legitimate architecture: if the key only ever lives inside an attested enclave, "sending it to the server" is not automatically a leak. So "total compromise" was the wrong framing. What remains is still a real weakness, just not a standalone critical. The endpoints accept the key as **plaintext hex** (the DTO is a bare `@IsString`; the repo's ML-KEM encryption is documented as "not currently exposed"), they have **no authentication and no `NODE_ENV` gating** (all six `@Post` routes are live in every environment), and production serves **plain HTTP behind Phala's external TLS-termination proxy** (`main.ts`: `httpsOptions` is `undefined` in prod, protocol `http`): ```ts async generateWithdrawalProof(@Body() body: GenerateWithdrawalProofDto) { const secretKey = BigInt(body.secretKey); // plaintext key in request body ... } ``` ```ts export class GenerateWithdrawalProofDto { @IsString() @IsNotEmpty() secretKey!: string; // "Secret key (as hex string)" } ``` No `@UseGuards` is applied to `ZkApiController`; the only global guards (`app.module.ts`) are throttlers. The `SiweGuard` exists but is never attached to these routes. **Impact.** The critical part is not "the enclave sees the key" but *where the plaintext key is visible before it reaches the enclave*. Because TLS terminates at Phala's proxy and the app then speaks plain HTTP, the secret key can be exposed at the termination boundary — i.e. **outside** the attested trust boundary the whole design relies on — as well as in any request logging along the way. Combined with no auth and no environment gating, the routes also act as an open proving oracle. The blast radius is bounded, though: only users who actually call `/proofs/*` are affected, and the funds impact is limited to those users' own deposits (an attacker cannot force a victim to submit their key). That is why this is Medium–High, not Critical. **Fix:** Prefer client-side proving for anything involving a user secret. If in-enclave proving is intentional, then (a) require the key to arrive encrypted to the enclave's *attested* key (wire up the existing ML-KEM path), never as plaintext behind an external TLS terminator; (b) authenticate the routes; (c) gate or remove them outside development. At minimum, do not accept `secretKey` over plain HTTP. **Status: 🔶 PARTIALLY FIXED (2026-07-07)** The plain-HTTP / external-TLS-termination component is resolved; auth and gating remain open. 1. **In-enclave TLS termination (done):** - `src/tls/tee-tls.ts`: in production, the TLS private key is derived *inside* the CVM via the dstack KMS (`getTlsKey()`), or loaded from operator-provisioned enclave-only storage (`TLS_KEY_PATH`/`TLS_CERT_PATH`). The server **fails closed** if neither is available (same posture as the proof-verification startup check). - The old plain-HTTP-behind-Phala's-proxy behavior is now an explicit, loudly-logged opt-in (`ALLOW_EXTERNAL_TLS_TERMINATION=true`), no longer the silent default. - The served TLS certificate is bound into the attestation: `report_data = SHA-256(mlkem_pub) || SHA-256(tls_leaf_cert_der)` (`attestation.service.ts`), so clients can cryptographically verify their TLS session terminates inside the attested enclave. `scripts/testing/verify-attestation.ts` checks this automatically. - Deployment requirement: the Phala/dstack gateway must run in TLS-passthrough mode (`https://-3000s.`); documented in `docs/TEE_SETUP.md`. - Net effect: `secretKey` sent to `/proofs/*` is now encrypted end-to-end from the client into the attested enclave; it is no longer visible at any TLS-termination boundary outside the TEE. 2. **Still open:** - The `/proofs/*` routes remain **unauthenticated** and **not gated** by environment (open proving oracle / resource abuse). - The DTO still accepts `secretKey` as a bare string; the ML-KEM encrypt-to-attested-key path is still not wired to these endpoints (defense-in-depth against a compromised gateway pinning setup or client-side verification mistakes). --- ## High findings ### H-1 — `policy_violation.circom` proves nothing; server can burn any user's stake **Where:** `circuits/policy_violation.circom`; `ZkApiCredits.sol::slashPolicyViolation`. The circuit assigns its public outputs directly from public inputs and never constrains the "evidence" to them: ```circom nullifier <== nullifierExpected; // pass-through idCommitment <== idCommitmentExpected; // pass-through // signalX, signalY, violationPayloadHash are private and only feed evidenceHash ``` There is **no constraint** that `signalY = k + a·signalX`, that the nullifier derives from `a`, or that the signal was ever observed. The comment claims "the server cannot forge this without having seen the actual request," but nothing in the constraint system enforces that. `slashPolicyViolation` is `onlyServer` and *burns* the victim's `policyStake`. **Impact.** A malicious or compromised server can burn the policy stake of any `active` user by supplying that user's on-chain `idCommitment` and any `nullifier`, with a trivially-produced proof. Burning (rather than paying the server) limits profit but not griefing/censorship. **Fix:** Actually constrain the RLN relation inside the circuit (`nullifier == Poseidon(Poseidon(k,ticketIndex))`, `signalY == k + a·signalX`) and bind evidence to a value the server could only know from a real request. Reconsider whether unilateral server slashing belongs in the trust model at all. ### H-2 — Deployed verification uses a stripped "test" circuit **Where:** `src/zk-api/snarkjs-proof.service.ts` (loads `api_credit_proof_test.{wasm,zkey}`, `verification_key.json` with `nPublic = 5`); `src/zk-api/proof-verifier.service.ts` (`verify()` builds `[nullifier, signalY, idCommitment, signalX, idCommitmentExpected]`). The strong `api_credit_proof.circom` (Merkle membership, solvency `(ticketIndex+1)·maxCost ≤ deposit + refunds`, per-ticket EdDSA refund verification, public server key) is **not** what runs. The active path proves only knowledge of a `secretKey` whose commitment equals a caller-supplied `idCommitmentExpected`, plus a derived nullifier. `merkleRoot`, `maxCost`, `initialDeposit`, and the server key are **absent from the SNARK's public signals** — the merkle root is only string-compared to on-chain state, not proven. **Impact.** (a) No anonymity-set membership is enforced cryptographically — a requester need not be a depositor. (b) With no solvency constraint, a user mints unlimited fresh nullifiers by incrementing `ticketIndex`, obtaining unlimited API calls the operator pays for downstream (e.g., Claude API cost). (c) The proof-gen service even builds *withdrawal* proofs from the test circuit (`proof-gen.service.ts` lines ~290/294), whose 5 signals cannot satisfy the on-chain `WithdrawalVerifier` (6 signals) — the system is in a half-migrated, inconsistent state. **Fix:** Compile and deploy the real circuit, commit its `.circom` source, and align the verifier service's public-signal ordering with it. Fail closed if the loaded VK is not the audited one (pin a hash). ### H-3 — Owner can swap verifiers arbitrarily (rug/centralization) **Where:** `ZkApiCredits.sol::setWithdrawalVerifier / setRefundVerifier / setSlashingVerifier / setPolicyVerifier` (all `onlyOwner`, no timelock). The owner can point any verifier at a contract that returns `true` unconditionally, then withdraw/redeem the entire balance with junk proofs. Combined with `setServerAddress` and `setMinStakes`, the owner is fully trusted with all deposited funds. **Fix:** Remove hot-swap in production or gate it behind a timelock + multisig, emit events (already partially done), and document the trust assumption prominently. Consider making verifiers immutable post-deployment. ### H-4 — On-chain EdDSA verifier is a stub; backend/contract ABI mismatch **Where:** `ZkApiCredits.sol::_verifyEdDSASignature` (returns `true` after range/curve checks, skips the actual pairing/scalar-mul); `src/zk-api/blockchain.service.ts::redeemRefund`. `_verifyEdDSASignature` explicitly returns `true` without verifying the signature ("TEMPORARY: Skip expensive elliptic curve operations"). It appears currently unused, but if any future path relies on it, signatures are unchecked. Separately, the backend calls: ```ts this.contract.redeemRefund(idCommitment, nullifier, refundValue, timestamp, signature, recipient) ``` which does **not** match the deployed `redeemRefund(bytes32, bytes32, uint256, address, uint256[8], uint256[5])`. The backend still speaks the old signature-based ABI while the contract moved to proof-based redemption — the server's own redemption path cannot succeed against the current contract, confirming the codebase is internally inconsistent and under-tested end-to-end. **Fix:** Delete the stub (don't ship dead crypto that returns `true`). Regenerate `ZkApiCredits.abi.json` from the current contract and update `blockchain.service` to pass `(_proof, _publicSignals)`; add an integration test that actually redeems against a forked/anvil deployment. **Status: ✅ FIXED (2026-07-06)** The H-4 finding has been fully resolved: 1. **Backend ABI Mismatch Fixed:** - Updated `blockchain.service.ts::redeemRefund()` to use proof-based parameters: `(idCommitment, nullifier, refundValue, recipient, proof, publicSignals)` - Updated `proof-gen.service.ts::generateRefundRedemptionProof()` to use the production `refund_redemption.circom` circuit with all required parameters (refundValue, refundTimestamp, refundSignature, serverPublicKey) - Updated `/proofs/refund` endpoint to generate mock signed refund tickets for testing 2. **Stub EdDSA Verifier Documented:** - Marked `_verifyEdDSASignature()` as DEAD CODE with clear documentation explaining it's no longer used - Documented that EdDSA verification now happens in-circuit via ZK proofs (solving the >30M gas problem) - Function kept for historical reference but clearly marked as obsolete 3. **Circuit Integration:** - System now uses `refund_redemption.circom` which verifies EdDSA signatures inside the circuit - Groth16 proof verification (~300k gas) replaces impossible on-chain EdDSA verification (>30M gas) - Provides defense-in-depth: even if TEE is compromised, on-chain verification requires valid ZK proofs 4. **Testing:** - Updated e2e tests to use the new proof generation API - All unit tests pass (37 suites, 473 tests) - All e2e tests pass (2 suites, 23 tests) **Note:** The production `refund_redemption.zkey` should be replaced with output from a new trusted setup ceremony for the updated circuit (current circuit has `nPublic: 7`, while `refund_redemption_final.zkey` has `nPublic: 5` from an older version). --- ## Medium findings ### M-1 — No Merkle-root history → in-flight proofs break on every deposit `withdraw` requires `_publicSignals[5] == merkleRoot` (the single current root). Any deposit between proof generation and inclusion changes the root and invalidates the pending proof, enabling cheap griefing and harming liveness. **Fix:** keep a rolling window of recent roots and accept membership against any of them (Tornado-style `isKnownRoot`). ### M-2 — Double-spend circuit binds nothing to real shares; stranded stake In `double_spend_slashing.circom`, `signal1/2` are private and attacker-chosen, so the "proof of double-spend" only proves knowledge of `secretKey` — it does not prove two *distinct real* requests occurred. Third parties can't slash from on-chain data (they lack the key), so RLN's economic deterrent is weak. Also, after `slashDoubleSpend`, `policyStake` is left non-zero but the deposit is `active = false`, permanently stranding that ETH. **Fix:** anchor slashing to server-published/attested signals; zero and account for `policyStake` on slash. ### M-3 — Nullifier rate-limiting is per-process and attacker-keyed `NullifierStoreService.checkRateLimit` uses an in-memory `Map` (lost on restart, not shared across TEE replicas) keyed on the caller-controlled `nullifier`, so rotating `ticketIndex` sidesteps it. **Fix:** rate-limit on a stable, scarce identifier and use shared/persistent state. ### M-4 — Swagger in prod; env validation doesn't enforce required secrets `SwaggerModule.setup('', app, document)` serves API docs at `/` in production (endpoint/schema disclosure). `validateEnvironment` uses `skipMissingProperties: true` and omits `OPERATOR_PRIVATE_KEY`/`ANVIL_PRIVATE_KEY` entirely, so the "fail fast on missing config" guarantee doesn't hold. **Fix:** gate Swagger behind `!isProd` (or auth); validate all required secrets explicitly. ### M-5 — Duplicate leaves and zero-value mismatch `deposit` reverts only when `deposits[_idCommitment].active`. After a withdrawal (`active=false`), the same `idCommitment` can be deposited again, pushing a **duplicate leaf** and re-growing the tree. Also, the constructor sets `zeros[0] = bytes32(0)` while the comment says "Poseidon(0)"; confirm the empty-node convention matches the circuit's `MerkleTreeChecker` exactly, or membership proofs for sparse subtrees will fail/mismatch. **Fix:** track spent commitments to forbid reuse; unit-test tree roots against the circuit for empty/partial trees. --- ## Low / informational - **L-1** `.env.template` ships a real (well-known Anvil) private key and normalizes committing keys. The on-chain submission key (`ANVIL_PRIVATE_KEY`) is read via plain `ConfigService`, not the KMS/`SecretsService` path used for `OPERATOR_PRIVATE_KEY` — ensure the production submission key is TEE/KMS-managed and never the default. - **L-2** `RefundSignerService.generatePrivateKey()` is a fixed deterministic dev key; safe only while `NODE_ENV !== 'production'`. A misconfigured `NODE_ENV` would silently use a public key. Fail hard instead. - **L-3** Hardcoded public RPC list; MEV protection relies on users choosing private RPCs, which the contract cannot enforce (see C-2). - **L-4** Confirm `SanitizedLogger` (prod-only) actually strips secrets/PII; in dev, default logger + `debug` lines print nullifier/idCommitment prefixes and raw public inputs. - **L-5** After `slashDoubleSpend`, reward is only `rlnStake`; the remaining `policyStake` is unreachable (see M-2). --- ## Prioritized remediation 1. ✅ **DONE — Stop the unconditional drain first (C-1):** make the server key a public/constant circuit input and require the on-chain `serverPublicKey` to match it in `redeemRefund`. Re-run trusted setup and regenerate the refund verifier. This is the only always-on total-loss bug. 2. ✅ **DONE — Bind recipients (C-2):** add `recipient` as a public input to the withdrawal and refund circuits and enforce it on-chain, so a copied proof is useless with a different recipient. (High — do not rely on private relays as the fix.) 3. 🔶 **PARTIAL — Lock down the key-ingesting endpoints (C-3):** prefer client-side proving; if in-enclave proving is intended, require ML-KEM-encrypted keys to the attested key, authenticate the routes, and gate/remove them outside dev. Never accept `secretKey` over plain HTTP. *(2026-07-07: in-enclave TLS termination implemented with attestation binding — `secretKey` no longer crosses a plaintext boundary outside the TEE. Auth + env gating + ML-KEM path still open.)* 4. **Wire up the real circuit (H-2, H-4):** deploy `api_credit_proof.circom` (not the test circuit), commit its source, pin VK hashes, regenerate the ABI, and add an end-to-end anvil test for deposit → request → refund → redeem → withdraw → slash. This also removes the mismatch that currently keeps C-1 latent — fixing C-1 must land *with* this, not after. 5. **Fix the slashing/policy circuits (H-1, M-2)** so proofs actually constrain the RLN relations. 6. **Reduce owner power (H-3):** timelock/multisig or immutability for verifier/params setters. 7. **Harden operations (M-1, M-3, M-4, L-*):** root history, shared rate-limit state, Swagger gating, strict env validation, KMS-managed submission key. An independent circuit review (e.g., using `circomspect`) and a Foundry invariant/fuzz suite over `ZkApiCredits` are strongly recommended before any value is placed at risk.