Download index.html from getreadystack/resilience-plan-lint: direct link, hf CLI and curl.
- Browser
- Download file 44.9 kB
-
https://huggingface.co/spaces/getreadystack/resilience-plan-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/resilience-plan-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/resilience-plan-lint/resolve/main/index.html
44.9 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Critical infrastructure risk assessment — check your resilience plan</title> | |
| <meta name="description" content="Check Markdown risk assessments and resilience plans against the described CER Directive notice, reporting, review-cycle, hazard, and plan requirements."> | |
| <style> | |
| :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512} | |
| .wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px} | |
| textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box} | |
| .bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer} | |
| input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px} | |
| table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65} | |
| .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600} | |
| .note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa} | |
| </style> | |
| <script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script> | |
| <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/resilience-plan-lint"></head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>Critical infrastructure risk assessment — check your resilience plan</h1> | |
| <p class="lede">Checks critical infrastructure risk assessment and resilience plan Markdown against EU CER Directive 2022/2557 Art. 12-15: 24-hour notice, one-month report, 9-month and 4-year cycle, all hazards, and all six Art. 13(1) measure areas including drought and climate adaptation. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false"># Risk Assessment and Resilience Plan — Northgate Water Utility | |
| Designation notified by the competent authority: 2026-08-14 | |
| First risk assessment completion target: 2027-06-30 | |
| ## Hazard register | |
| - Natural hazards: river flood, storm damage, summer drought and heatwave stress on reservoirs. | |
| - Accidents: chlorine dosing equipment failure, industrial spill upstream of the intake. | |
| - Antagonistic threats: sabotage of pumping stations, terrorism, insider manipulation of SCADA set points. | |
| ## Dependencies | |
| The service depends on grid electricity (energy sector) and chemical deliveries; two hospitals depend on our supply (cross-sector). | |
| ## Resilience measures | |
| - Physical protection: perimeter fencing and access control at all treatment works. | |
| - Business continuity: mobile generators and an alternative supply contract for chlorine. | |
| - Personnel security: background checks for control-room staff and quarterly access rights review. | |
| - Training: annual flood exercise and staff awareness sessions. | |
| ## Incident notification | |
| 1. Initial notification to the competent authority within 72 hours of detection. | |
| 2. Detailed incident report within 60 days. | |
| ## Review | |
| The risk assessment is reviewed every five years.</textarea> | |
| <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div> | |
| <div id="out" aria-live="polite"></div> | |
| <div id="paid"><strong>Free here:</strong> check one risk assessment or resilience plan file against CER Art. 12-15 and see every finding with its line number. <strong>Full version in VS Code:</strong> scan every plan in the workspace at once and export a dated gap report for the auditor file. $29 once, one licence key per person or team seat. An online CER Directive trained-professional course costs $297 and does not check your plan. <a href="https://getreadystack.com/api/buy/cl/polar_cl_roT2BUBnJjC4ZxUPjFykawCuTQbNsbn1GgRWi03ruw0">Get the full version — $29</a></div> | |
| <p class="note">Same engine as the VS Code extension, byte for byte.</p> | |
| </div> | |
| <script>var RPL_RULES = [ | |
| {"id": "CER-15-INITIAL", "sev": "error", "art": "Art. 15(1)", "title": "Initial incident notice later than 24 hours", "fix": "Notify the competent authority within 24 hours of becoming aware", "max_hours": 24}, | |
| {"id": "CER-15-REPORT", "sev": "error", "art": "Art. 15(1)", "title": "Detailed incident report later than one month", "fix": "Send the detailed report no later than one month after the initial notice", "max_days": 31}, | |
| {"id": "CER-15-MISSING", "sev": "error", "art": "Art. 15(1)", "title": "No incident notification procedure in the plan", "fix": "Add the 24-hour initial notice and one-month detailed report steps"}, | |
| {"id": "CER-12-CYCLE", "sev": "error", "art": "Art. 12(1)", "title": "Risk assessment review cycle longer than four years or missing", "fix": "Re-assess whenever necessary and at least every four years", "max_years": 4}, | |
| {"id": "CER-12-FIRST", "sev": "error", "art": "Art. 12(1)", "title": "First risk assessment later than nine months after notification", "fix": "Complete the first risk assessment within nine months of the designation notice", "months": 9}, | |
| {"id": "CER-12-HAZARDS", "sev": "warning", "art": "Art. 12(1) (Directive (EU) 2022/2557, OJ L 333, 27.12.2022)", "title": "Hazard category missing from the all-hazards assessment", "fix": "Assess natural, accidental, public health and antagonistic risks", | |
| "categories": [["natural hazards", "natural (hazard|disaster)|flood|storm|earthquake|wildfire|heatwave|drought|water scarcity|climate"], ["accidents", "accident|explosion|equipment failure|industrial"], ["public health emergencies", "public health|pandemic|epidemic|infectious"], ["antagonistic threats (terrorism, sabotage, insider)", "terroris|sabotage|hybrid|insider|attack"]]}, | |
| {"id": "CER-12-DEPEND", "sev": "warning", "art": "Art. 12(1)", "title": "No cross-sector dependency analysis", "fix": "Record which sectors depend on you and which you depend on", "pattern": "depend|cross-sector|cross-border"}, | |
| {"id": "CER-13-LIAISON", "sev": "error", "art": "Art. 13(3)", "title": "No liaison officer named as point of contact", "fix": "Name a liaison officer or equivalent as the contact for the authority", "pattern": "liaison officer|point of contact|contact point"}, | |
| {"id": "CER-13-MEASURES", "sev": "warning", "art": "Art. 13(1)(a)-(f) (Directive (EU) 2022/2557, OJ L 333, 27.12.2022)", "title": "Resilience measure area missing from the plan", "fix": "Cover all six Art. 13(1) measure areas in the resilience plan; a drought or flood hazard needs a matching prevention and climate adaptation measure under point (a)", | |
| "categories": [["prevention with disaster risk reduction and climate adaptation measures (point a)", "climate (change )?adaptation|disaster risk reduction|drought (contingency|management|response) plan|water scarcity (plan|measure)|prevent(ion|ive)? (measure|incident)|prevent incidents"], ["physical protection (point b)", "physical protection|fenc|perimeter|access control|barrier"], ["crisis management procedures and alert routines (point c)", "crisis management|crisis response|alert routine|emergency response|incident response|escalation procedure"], ["recovery and business continuity (point d)", "business continuity|recover|alternative supply"], ["personnel security and background checks (point e)", "background check|personnel security|vetting|access rights"], ["staff training and exercises (point f)", "training|exercise|drill|awareness"]]} | |
| ] | |
| ;</script> | |
| <script>/* Resilience Plan Lint engine — EU CER Directive 2022/2557 Art. 12-15. Runs in Node and in the browser. */ | |
| (function () { | |
| var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.RPL_RULES; | |
| var BY = {}; RULES.forEach(function (r) { BY[r.id] = r; }); | |
| var WORDS = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10, twelve: 12 }; | |
| function num(s) { s = String(s).toLowerCase(); return WORDS[s] !== undefined ? WORDS[s] : parseFloat(s); } | |
| var DATE = /(\d{4})-(\d{2})-(\d{2})/; | |
| function parseDate(s) { var m = DATE.exec(s || ''); if (!m) return null; var d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])); return isNaN(d) ? null : d; } | |
| function addMonths(d, n) { | |
| var y = d.getUTCFullYear(), mo = d.getUTCMonth() + n, day = d.getUTCDate(); | |
| var last = new Date(Date.UTC(y, mo + 1, 0)).getUTCDate(); | |
| return new Date(Date.UTC(y, mo, Math.min(day, last))); | |
| } | |
| function iso(d) { return d.toISOString().slice(0, 10); } | |
| function find(r, extra, line) { return { check: r.id, sev: r.sev, msg: r.art + ' — ' + r.title + (extra ? ': ' + extra : '') + '. Fix: ' + r.fix + '.', line: line }; } | |
| var N = '(\\d+(?:\\.\\d+)?|one|two|three|four|five|six|seven|eight|nine|ten|twelve)'; | |
| function check(text, opts) { | |
| opts = opts || {}; | |
| var today = parseDate(String(opts.today || '').slice(0, 10)) || new Date(); | |
| var lines = String(text || '').split(/\r?\n/); | |
| var all = lines.join('\n'); | |
| var out = []; | |
| var hazardLine = 1, notified = null, assessed = null, assessedLine = 1, cycleSeen = false; | |
| lines.forEach(function (raw, i) { | |
| var ln = i + 1, l = raw; | |
| if (/^#+ .*(hazard|risk)/i.test(l) && hazardLine === 1) hazardLine = ln; | |
| // Art. 15 — initial notice | |
| if (/\b(initial|first|early)\s+(notification|notice|warning)\b|notify the (competent )?authority/i.test(l) && !/detailed|final|full report/i.test(l)) { | |
| var m = new RegExp(N + '\\s*(hours?|hrs?|h|days?)\\b', 'i').exec(l); | |
| if (m) { | |
| var h = num(m[1]) * (/^d/i.test(m[2]) ? 24 : 1); | |
| if (h > BY['CER-15-INITIAL'].max_hours) out.push(find(BY['CER-15-INITIAL'], 'plan says ' + m[0], ln)); | |
| } | |
| } | |
| // Art. 15 — detailed report | |
| if (/(detailed|final|full)\s+(incident\s+)?report/i.test(l)) { | |
| var d = new RegExp(N + '\\s*(days?|weeks?|months?)\\b', 'i').exec(l); | |
| if (d) { | |
| var u = d[2].toLowerCase(), days = num(d[1]) * (u[0] === 'w' ? 7 : u[0] === 'm' ? 31 : 1); | |
| if (days > BY['CER-15-REPORT'].max_days) out.push(find(BY['CER-15-REPORT'], 'plan says ' + d[0], ln)); | |
| } | |
| } | |
| // Art. 12 — review cycle | |
| var c = new RegExp('every\\s+' + N + '\\s*years?', 'i').exec(l); | |
| if (c) { | |
| cycleSeen = true; | |
| if (num(c[1]) > BY['CER-12-CYCLE'].max_years) out.push(find(BY['CER-12-CYCLE'], 'plan says ' + c[0], ln)); | |
| } else if (/\b(annual(ly)?|every year|yearly)\b/i.test(l) && /review|re-?assess|update/i.test(l)) cycleSeen = true; | |
| // Art. 12 — dates | |
| if (/notified|notification received|designated/i.test(l) && parseDate(l) && !notified) notified = parseDate(l); | |
| else if (/risk assessment/i.test(l) && /complet|due|target|approv|finish/i.test(l) && parseDate(l) && !assessed) { assessed = parseDate(l); assessedLine = ln; } | |
| }); | |
| if (!/notif/i.test(all)) out.push(find(BY['CER-15-MISSING'], '', 1)); | |
| if (!cycleSeen) out.push(find(BY['CER-12-CYCLE'], 'no review interval found', 1)); | |
| if (notified) { | |
| var r = BY['CER-12-FIRST'], due = addMonths(notified, r.months); | |
| if (assessed && assessed > due) out.push(find(r, 'planned ' + iso(assessed) + ', due ' + iso(due), assessedLine)); | |
| else if (!assessed && today > due) out.push(find(r, 'no completion date and due ' + iso(due) + ' has passed', 1)); | |
| } | |
| ['CER-12-HAZARDS', 'CER-13-MEASURES'].forEach(function (id) { | |
| var r = BY[id]; | |
| r.categories.forEach(function (cat) { | |
| if (!new RegExp(cat[1], 'i').test(all)) out.push(find(r, 'no ' + cat[0], id === 'CER-12-HAZARDS' ? hazardLine : 1)); | |
| }); | |
| }); | |
| ['CER-12-DEPEND', 'CER-13-LIAISON'].forEach(function (id) { | |
| var r = BY[id]; | |
| if (!new RegExp(r.pattern, 'i').test(all)) out.push(find(r, '', 1)); | |
| }); | |
| out.sort(function (a, b) { return a.line - b.line; }); | |
| return { findings: out }; | |
| } | |
| var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, addMonths: addMonths }; | |
| if (typeof module !== 'undefined') module.exports = api; | |
| if (typeof window !== 'undefined') window.RPLENGINE = api; | |
| })(); | |
| </script> | |
| <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다. | |
| ; | |
| function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } | |
| function summaryLine(res) { | |
| const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length; | |
| return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.'; | |
| } | |
| function toText(res, label) { | |
| const f = (res && res.findings) || []; | |
| const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)]; | |
| f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); }); | |
| return lines.join('\n'); | |
| } | |
| function toHtml(res) { | |
| const f = (res && res.findings) || []; | |
| if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>'; | |
| const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join(''); | |
| return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>'; | |
| } | |
| var RPLREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = RPLREPORT; | |
| else window.RPLREPORT = RPLREPORT; | |
| </script> | |
| <script> | |
| (function(){ | |
| var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today'); | |
| today.value=new Date().toISOString().slice(0,10); | |
| function run(){ try { var res=window.RPLENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.RPLREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } } | |
| go.onclick=run; if (src.value.trim()) run(); | |
| })(); | |
| </script> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/resilience-plan-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'resilience-plan-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/resilience-plan-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'resilience-plan-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://hub.docker.com/r/getreadystack/resilience-plan-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'resilience-plan-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/resilience-plan-lint <file> | |
| docker run --rm -v "$PWD:/w" getreadystack/resilience-plan-lint /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 9 rules Resilience Plan Lint (CER Directive) checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"resilience-plan-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Checks EU critical entity risk assessment and resilience plans against CER Directive 2022/2557 Art. 12-15: 24-hour notice, one-month report, nine-month first assessment, four-year review, al</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">An online CER Directive trained-professional course costs $297; it teaches Articles 12-15 but does not check your plan.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_roT2BUBnJjC4ZxUPjFykawCuTQbNsbn1GgRWi03ruw0" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-27">Updated 2026-09-27</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-27","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku335211_result_card.jpg" alt="Resilience Plan Lint (CER Directive)" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Six findings came back when Resilience Plan Lint read a sample water utility resilience plan. The plan was written for an EU critical entity under the CER Directive and was notified on 2026-08-14. Five of the findings are errors and one is a warning. If you are the security or compliance lead at a designated energy, water, transport or health operator in Germany, the Netherlands, France or any other member state, those six lines are the ones the competent authority will read first.</p> | |
| <p>Why now: member states had to identify their critical entities by 17 July 2026. A critical entity must complete its first risk assessment within nine months of the designation notice. For a notice dated 2026-08-14, that is 2027-05-14. The sample plan targets 2027-06-30, which is six weeks late.</p> | |
| <p>Here is what the lint returned on the sample file, line by line:</p> | |
| <table><thead><tr><th>Plan says</th><th>CER requires</th></tr></thead><tbody><tr><td>72 hours initial notice</td><td>24 hours (Art. 15)</td></tr><tr><td>Detailed report in 60 days</td><td>One month (Art. 15)</td></tr><tr><td>Review every five years</td><td>At least every four years</td></tr><tr><td>First assessment 2027-06-30</td><td>Due 2027-05-14 (nine months)</td></tr><tr><td>No public health hazard</td><td>All hazards (Art. 12)</td></tr><tr><td>No liaison officer</td><td>Named contact (Art. 13)</td></tr></tbody></table> | |
| <p>The 72-hour line is the most common mistake, and it has a clear origin. NIS2 incident reporting has three steps: a 24-hour early warning, a 72-hour incident notification and a one-month final report. CER Article 15 has two steps: an initial notification no later than 24 hours after becoming aware, and a detailed report no later than one month after that. Teams that already run a NIS2 process often copy it into the resilience plan, and the 72-hour step comes with it. For CER, that first notice is 48 hours late.</p> | |
| <p>The lint uses nine rules, one per requirement in Articles 12 to 15:</p> | |
| <ul><li>CER-15-INITIAL: initial notice no later than 24 hours.</li><li>CER-15-REPORT: detailed report no later than one month.</li><li>CER-15-MISSING: an incident notification procedure exists.</li><li>CER-12-CYCLE: re-assessment at least every four years.</li><li>CER-12-FIRST: first assessment within nine months of the designation notice.</li><li>CER-12-HAZARDS: natural hazards, accidents, public health emergencies and antagonistic threats.</li><li>CER-12-DEPEND: dependencies on and from other sectors.</li><li>CER-13-LIAISON: a named liaison officer as point of contact.</li><li>CER-13-MEASURES: physical protection, recovery and business continuity, personnel security and background checks, training and exercises.</li></ul> | |
| <p>The deadline rule reads the dates in the plan itself. If the plan says "Designation notified: 2026-08-14", the rule adds nine calendar months. If the plan's own completion date falls after that, the line is flagged. If there is no completion date and the deadline has already passed on the day you run the check, the plan is flagged as overdue. On the build date, 2026-09-27, the sample deadline has not passed yet, so the only date finding is the late target.</p> | |
| <p>How to use it: paste the plan Markdown into the free web page, or open the file in VS Code and run the check command. Each finding shows the line, the article and the fix. The free check covers one file with no limit on runs. The full version scans every plan in a workspace at once and exports a dated gap report for the auditor file.</p> | |
| <p>For comparison, an online CER Directive trained-professional course costs $297. It teaches the articles, but it does not read your plan.</p> | |
| <p>The lint reads text. It cannot judge whether your fences are high enough. It only checks that the plan covers each Article 12 to 15 element and that the deadlines match the directive.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku335211_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku335211_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Resilience Plan Lint check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Resilience Plan Lint reads a critical entity's risk assessment or resilience plan written in Markdown and checks it against EU CER Directive 2022/2557 Articles 12 to 15. Nine rules cover the 24-hour initial notice, the one-month detailed report, the nine-month first assessment, the four-year review cycle, all hazard groups, dependencies, a liaison officer and all six Article 13(1) measure areas. Each finding shows the line, the article and the fix.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is Resilience Plan Lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It is for security, risk and compliance leads at EU critical entities designated under the CER Directive: drinking water and wastewater utilities, energy, transport, health and digital infrastructure operators. It suits teams that keep the risk assessment and resilience plan as Markdown in a repository, and water utilities that hold a separate drought contingency plan and need the resilience plan to cover Article 13(1)(a).</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just use a free template or a chatbot?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Many resilience plan templates are copied from NIS2, which has a 72-hour incident notification step. CER Article 15 requires the initial notice within 24 hours and a detailed report within one month. A chatbot can explain those rules but does not read your file line by line, add nine months to your own designation date, or notice that a drought hazard has no matching adaptation measure.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free check reads one plan at a time, in the browser or in VS Code, with every finding and no limit on runs. The licence key adds a workspace sweep: every plan in the repository is checked at once and a dated gap report is written for the auditor file. The key costs $29 once for one person or team seat.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does the alternative cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">An online CER Directive trained-professional course costs $297. It teaches Articles 12 to 15 but does not check your own plan. Resilience Plan Lint checks the plan itself: the free version covers one file with every finding, and the workspace sweep with a dated gap report costs $29 once. The lint reads text only, so it does not judge whether measures are adequate.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Resilience Plan Lint (CER Directive) checks the file you open against 9 rules, each naming its legal or official basis, from a rule set dated 2026-09-28, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Resilience Plan Lint check?", "acceptedAnswer": {"@type": "Answer", "text": "Resilience Plan Lint reads a critical entity's risk assessment or resilience plan written in Markdown and checks it against EU CER Directive 2022/2557 Articles 12 to 15. Nine rules cover the 24-hour initial notice, the one-month detailed report, the nine-month first assessment, the four-year review cycle, all hazard groups, dependencies, a liaison officer and all six Article 13(1) measure areas. Each finding shows the line, the article and the fix."}}, {"@type": "Question", "name": "Who is Resilience Plan Lint for?", "acceptedAnswer": {"@type": "Answer", "text": "It is for security, risk and compliance leads at EU critical entities designated under the CER Directive: drinking water and wastewater utilities, energy, transport, health and digital infrastructure operators. It suits teams that keep the risk assessment and resilience plan as Markdown in a repository, and water utilities that hold a separate drought contingency plan and need the resilience plan to cover Article 13(1)(a)."}}, {"@type": "Question", "name": "Why not just use a free template or a chatbot?", "acceptedAnswer": {"@type": "Answer", "text": "Many resilience plan templates are copied from NIS2, which has a 72-hour incident notification step. CER Article 15 requires the initial notice within 24 hours and a detailed report within one month. A chatbot can explain those rules but does not read your file line by line, add nine months to your own designation date, or notice that a drought hazard has no matching adaptation measure."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "The free check reads one plan at a time, in the browser or in VS Code, with every finding and no limit on runs. The licence key adds a workspace sweep: every plan in the repository is checked at once and a dated gap report is written for the auditor file. The key costs $29 once for one person or team seat."}}, {"@type": "Question", "name": "What does the alternative cost?", "acceptedAnswer": {"@type": "Answer", "text": "An online CER Directive trained-professional course costs $297. It teaches Articles 12 to 15 but does not check your own plan. Resilience Plan Lint checks the plan itself: the free version covers one file with every finding, and the workspace sweep with a dated gap report costs $29 once. The lint reads text only, so it does not judge whether measures are adequate."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Resilience Plan Lint (CER Directive) checks the file you open against 9 rules, each naming its legal or official basis, from a rule set dated 2026-09-28, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:335211,slug:"resilience-plan-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/iot-data-notice-lint"><span class="yj-rel-t">IoT Data Notice Lint: EU Data Act Art. 3</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/aoda-accessibility-feedback-form"><span class="yj-rel-t">AODA Accessibility Feedback Form for Ontario Businesses – Customer Service Sta</span><span class="yj-rel-p">$60</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ca-climate-disclosure-clock-2026"><span class="yj-rel-t">CA Climate Disclosure Clock 2026 — which SB 253 / SB 261 report your group owe</span><span class="yj-rel-p">$60</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/defamation-notice-of-complaint-form"><span class="yj-rel-t">Defamation Notice of Complaint Form – UK Section 5 Defence for Forums, Review </span><span class="yj-rel-p">$60</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/teacher-resume-template"><span class="yj-rel-t">U.S. Teacher Resume Templates (.docx) — 6 ATS-Ready Resumes: Elementary, First</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-disclosure-lint-sb942"><span class="yj-rel-t">AI Output Disclosure Lint SB 942</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script> | |
| <script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script> | |
| <div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div></body> | |
| </html> | |