Download index.html from getreadystack/dora-register-lint: direct link, hf CLI and curl.
- Browser
- Download file 56.7 kB
-
https://huggingface.co/spaces/getreadystack/dora-register-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/dora-register-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/dora-register-lint/resolve/main/index.html
56.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>DORA Register Information CSV — check register data before submission</title> | |
| <meta name="description" content="This free browser tool checks a DORA Register of Information CSV for the listed identifier, date, code, reference, and expiry issues."> | |
| <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 a{display:inline-block;margin-top:8px;font-weight:700;color:#1a1512} | |
| #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/dora-register-lint"></head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>DORA Register Information CSV — check register data before submission</h1> | |
| <p class="lede">Lints the DORA Register of Information CSV before it reaches your supervisor: LEI checksums, ISO 8601 dates, country and currency codes, duplicate or unresolved contract references, rows already expired. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false">"Contractual arrangement reference","Linked overarching arrangement reference","LEI of the financial entity","LEI of the ICT third-party service provider","Name of the ICT third-party service provider","Function identifier","Type of ICT service","Supports a critical or important function","Start date of the contract","End date of the contract","Country of provision of the service","Annual expense or estimated cost","Currency of the amount reported" | |
| "ARR-2026-001","","5493001KJTIIGC8Y1R12","549300ZBWZJMTEDVDS68","Northwind Cloud Services BV","FUNC-PAY-01","Cloud computing services","Yes","01/04/2024","2027-03-31","NL","412.000,00","EUR" | |
| "ARR-2026-002","ARR-2026-009","5493001KJTIIGC8Y1R12","213800QILIUD4ROSU086","Halden Data Centre AS","FUNC-PAY-02","Data centre services","Critical","2025-01-15","2026-06-30","Norway","88500.00","EUR" | |
| "ARR-2026-003","","5493001KJTIIGC8Y1R12","N/A","Lumen Core Banking GmbH","FUNC-CORE-01","Software as a service","Yes","2023-07-01","2027-06-30","DE","1250000.00","EUR sign" | |
| "ARR-2026-003","","5493001KJTIIGC8Y1R12","984500PRTFMQ8LB0X397","Lumen Core Banking GmbH","FUNC-CORE-02","Hosting services","Yes","2026-02-30","2027-02-27","DE","90000.00","EUR" | |
| "ARR-2026-005","ARR-2026-005","5493001KJTIIGC8Y1R12","984500PRTFMQ8LB0X397","Lumen Core Banking (DE) GmbH","FUNC-RISK-02","Data analytics","No","2026-02-01","2029-01-31","FI","63000.00","EUR" | |
| "ARR-2026-006","","5493001KJTIIGC8Y1R12","529900T8BM49AURSDO","Meridian Payments SA","","Payment processing","Yes","2025-09-01","","FR","240000.00","EUR" | |
| "ARR-2026-007","","5493001KJTIIGC8Y1R12","8945001NQFMR4KZMTX57",Fjord Nett AS, Oslo,"FUNC-OPS-01","Network services","Yes","2025-03-01","2028-02-29","NO","51000.00","EUR" | |
| "ARR-2026-008","","5493001KJTIIGC8Y1R12","549300zbwzjmtedvds67","Crédit Numérique SAS","FUNC-OPS-02","Managed security services","Yes","2025-05-01","2028-04-30","FR","305000.00","eur" | |
| "ARR-2026-010","","5493001KJTIIGC8Y1R12","213800QILIUD4ROSU086","Halden Data Centre AS","FUNC-OPS-03","Backup services","No","2026-01-01","2028-12-31","NO","17500.00","EUR" | |
| "ARR-2026-010","","5493001KJTIIGC8Y1R12","213800QILIUD4ROSU086","Halden Data Centre AS","FUNC-OPS-03","Backup services","No","2026-01-01","2028-12-31","NO","17500.00","EUR" | |
| "ARR-2026-011","","5493001KJTIIGC8Y1R12","8945001NQFMR4KZMTX57","Meridian Payments SA","FUNC-PAY-03","Payment processing","Yes","2027-01-01","2026-12-31","GB","1,250,000.00","EUR"</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 and in the editor:</strong> Check the register CSV open in your editor — every row, all 24 checks, no key. <strong>The paid part is a different job:</strong> sweep every register CSV in the workspace and write one dated report file for the audit trail. $29 once · one licence key per person or CI seat · <a href="https://getreadystack.com/api/buy/cl/polar_cl_4WZMkiJUxjGBBEqJo7hbkQ7REYH8kJIOWwGoJ29agpN">Get the full version — $29</a></div> | |
| <p class="note">Same engine as the VS Code extension, byte for byte.</p> | |
| </div> | |
| <script>var DORA_RULES = { | |
| "version": "2026.09", | |
| "note": "Field checks for a Register of Information export (DORA, Regulation (EU) 2022/2554, Article 28(3)). Every check is computed from the file itself - format, arithmetic and cross-row consistency. No check gives legal advice.", | |
| "columns": { | |
| "entity_lei": {"label": "LEI of the financial entity", "kind": "lei", "required": true, "match": [["lei", "entity"], ["lei", "maintaining"], ["entity", "identification code"]]}, | |
| "provider_lei": {"label": "LEI of the ICT third-party provider", "kind": "lei", "required": true, "match": [["lei", "provider"], ["lei", "third-party"], ["provider", "identification code"]]}, | |
| "provider_name":{"label": "Name of the ICT third-party provider","kind": "text", "required": true, "match": [["name", "provider"], ["name", "third-party"]]}, | |
| "contract_ref": {"label": "Contractual arrangement reference", "kind": "ref", "required": true, "match": [["contractual arrangement reference"], ["contract", "reference"], ["arrangement", "reference number"]]}, | |
| "linked_ref": {"label": "Linked (overarching) arrangement reference", "kind": "link", "required": false, "match": [["linked", "reference"], ["overarching", "reference"], ["parent", "reference"]]}, | |
| "function_id": {"label": "Function identifier", "kind": "text", "required": true, "match": [["function identifier"], ["function", "id"]]}, | |
| "service_type": {"label": "Type of ICT service", "kind": "text", "required": true, "match": [["type of ict service"], ["ict service", "type"], ["service", "type"]]}, | |
| "critical": {"label": "Supports a critical or important function", "kind": "bool", "required": true, "match": [["critical or important"], ["critical", "function"], ["criticality"]]}, | |
| "start_date": {"label": "Start date of the contract", "kind": "date", "required": true, "match": [["start date"], ["commencement date"]]}, | |
| "end_date": {"label": "End date of the contract", "kind": "date", "required": true, "match": [["end date"], ["termination date"], ["expiry date"]]}, | |
| "country": {"label": "Country of provision of the service", "kind": "country", "required": true, "match": [["country"], ["location", "code"]]}, | |
| "cost": {"label": "Annual expense or estimated cost", "kind": "amount", "required": true, "match": [["annual expense"], ["annual cost"], ["estimated cost"], ["cost"]]}, | |
| "currency": {"label": "Currency of the amount reported", "kind": "currency","required": true, "match": [["currency"]]} | |
| }, | |
| "bool_true": ["yes", "y", "true", "1"], | |
| "bool_false": ["no", "n", "false", "0"], | |
| "placeholders": ["n/a", "na", "tbd", "tbc", "none", "unknown", "notavailable", "not available", "xxxxxxxxxxxxxxxxxxxx", "00000000000000000000", "-", "?"], | |
| "iso3166": ["AD","AE","AF","AG","AI","AL","AM","AO","AR","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BW","BY","BZ","CA","CD","CF","CG","CH","CI","CL","CM","CN","CO","CR","CU","CV","CW","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","ER","ES","ET","FI","FJ","FO","FR","GA","GB","GD","GE","GG","GH","GI","GL","GM","GN","GQ","GR","GT","GU","GW","GY","HK","HN","HR","HT","HU","ID","IE","IL","IM","IN","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KN","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MG","MK","ML","MM","MN","MO","MQ","MR","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NG","NI","NL","NO","NP","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PR","PS","PT","PY","QA","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SI","SK","SL","SM","SN","SO","SR","SV","SY","SZ","TD","TG","TH","TJ","TM","TN","TR","TT","TW","TZ","UA","UG","US","UY","UZ","VA","VC","VE","VG","VI","VN","YE","ZA","ZM","ZW"], | |
| "iso4217": ["AED","ARS","AUD","BGN","BHD","BRL","CAD","CHF","CLP","CNY","COP","CZK","DKK","EGP","EUR","GBP","GEL","HKD","HRK","HUF","IDR","ILS","INR","ISK","JPY","KES","KRW","KWD","MAD","MDL","MXN","MYR","NGN","NOK","NZD","OMR","PEN","PHP","PLN","QAR","RON","RSD","RUB","SAR","SEK","SGD","THB","TRY","TWD","UAH","USD","ZAR"], | |
| "rules": [ | |
| {"id": "missing_column", "sev": "error", "title": "Required column absent", "msg": "The header has no column for \"{label}\". Every row is therefore unchecked for it."}, | |
| {"id": "delimiter_not_comma", "sev": "warn", "title": "Semicolon-separated file", "msg": "This file is semicolon-separated (a regional Excel export). Parsed with ';' - re-export as comma-separated UTF-8 before you upload it."}, | |
| {"id": "mojibake", "sev": "warn", "title": "Broken UTF-8", "msg": "\"{value}\" looks like UTF-8 read as Latin-1. Provider names with accents will reach the supervisor mangled."}, | |
| {"id": "row_field_count", "sev": "error", "title": "Row width differs from header", "msg": "This row has {got} fields, the header has {want}. An unescaped comma or quote has shifted every value after it."}, | |
| {"id": "empty_required", "sev": "error", "title": "Required cell empty", "msg": "\"{label}\" is empty on this row."}, | |
| {"id": "lei_length", "sev": "error", "title": "LEI is not 20 characters", "msg": "\"{label}\" is \"{value}\" - {got} characters. An LEI (ISO 17442) is exactly 20."}, | |
| {"id": "lei_charset", "sev": "error", "title": "LEI has illegal characters", "msg": "\"{label}\" is \"{value}\" - an LEI is upper-case A-Z and 0-9 only."}, | |
| {"id": "lei_checksum", "sev": "error", "title": "LEI check digits fail", "msg": "\"{label}\" is \"{value}\" - the last two digits should be {want}, not {got} (ISO 7064 MOD 97-10). One character is wrong or transposed."}, | |
| {"id": "lei_placeholder", "sev": "error", "title": "LEI is a placeholder", "msg": "\"{label}\" is \"{value}\" - a placeholder, not an identifier."}, | |
| {"id": "lei_name_conflict", "sev": "warn", "title": "One LEI, two names", "msg": "LEI {value} is filed as \"{got}\" here and \"{want}\" on line {other}. The supervisor keys on the LEI; pick one legal name."}, | |
| {"id": "duplicate_contract_ref", "sev": "error", "title": "Contract reference used twice", "msg": "Contractual arrangement reference \"{value}\" is already used on line {other}. References must be unique across the register."}, | |
| {"id": "duplicate_row", "sev": "warn", "title": "Identical row", "msg": "This row repeats line {other} field for field."}, | |
| {"id": "unresolved_link", "sev": "error", "title": "Linked arrangement not in the file", "msg": "This row points at overarching arrangement \"{value}\", which no row in this file declares. The link will not resolve."}, | |
| {"id": "self_link", "sev": "warn", "title": "Row links to itself", "msg": "This row names itself (\"{value}\") as its own overarching arrangement."}, | |
| {"id": "date_format", "sev": "error", "title": "Date is not ISO 8601", "msg": "\"{label}\" is \"{value}\". Write dates as yyyy-mm-dd - {got} is read the other way round outside your locale."}, | |
| {"id": "date_impossible", "sev": "error", "title": "Date does not exist", "msg": "\"{label}\" is \"{value}\" - there is no such day in the calendar."}, | |
| {"id": "date_order", "sev": "error", "title": "Contract ends before it starts", "msg": "End date {got} is earlier than start date {want}."}, | |
| {"id": "contract_expired", "sev": "warn", "title": "Arrangement already ended", "msg": "This arrangement ended on {value}, before the reference date {want}. A terminated contract still listed as live is a finding the reviewer raises, not you."}, | |
| {"id": "country_shape", "sev": "error", "title": "Country is not a 2-letter code", "msg": "\"{label}\" is \"{value}\". This field takes an ISO 3166-1 alpha-2 code (two upper-case letters), not a country name."}, | |
| {"id": "country_unknown", "sev": "warn", "title": "Unrecognised country code", "msg": "\"{value}\" is not in this extension's ISO 3166-1 alpha-2 list. Check it before you file."}, | |
| {"id": "currency_shape", "sev": "error", "title": "Currency is not a 3-letter code", "msg": "\"{label}\" is \"{value}\". This field takes an ISO 4217 alpha-3 code, not a symbol or a name."}, | |
| {"id": "currency_unknown", "sev": "warn", "title": "Unrecognised currency code", "msg": "\"{value}\" is not in this extension's ISO 4217 list. Check it before you file."}, | |
| {"id": "bool_token", "sev": "error", "title": "Yes/no field is neither", "msg": "\"{label}\" is \"{value}\". This field is yes or no - a free-text answer cannot be aggregated."}, | |
| {"id": "amount_format", "sev": "error", "title": "Amount is not a plain number", "msg": "\"{label}\" is \"{value}\". Report the amount as digits with an optional decimal point ({want}) - thousands separators and symbols break the load."} | |
| ] | |
| } | |
| ;</script> | |
| <script>// dora-register-lint — the brain. Same file runs in Node (the extension) and in the browser (the free web page). | |
| // Every finding is computed from the file itself: format, ISO 7064 arithmetic, cross-row consistency. Nothing is uploaded, nothing is inferred. | |
| ; | |
| var DOC = (typeof module !== 'undefined' && module.exports) ? require('./rules.json') : window.DORA_RULES; | |
| var RULES = DOC.rules; | |
| var COLS = DOC.columns; | |
| var BY_ID = {}; | |
| for (var ri = 0; ri < RULES.length; ri++) BY_ID[RULES[ri].id] = RULES[ri]; | |
| // contract_ref must not swallow the "linked / overarching arrangement reference" column | |
| var EXCLUDE = { contract_ref: ['linked', 'overarch', 'parent', 'subcontract', 'sub-contract'] }; | |
| var MOJI = /Ã.|â€|Å¡|Â[ºª§]/; | |
| function fmt(id, vars, line) { | |
| var r = BY_ID[id] || { sev: 'error', msg: id }; | |
| var msg = String(r.msg).replace(/\{(\w+)\}/g, function (m, k) { return vars && vars[k] != null ? String(vars[k]) : m; }); | |
| return { check: id, sev: r.sev, msg: msg, line: line || 1 }; | |
| } | |
| // ---- CSV ---------------------------------------------------------------- | |
| function pickDelim(text) { | |
| var head = text.split(/\r?\n/)[0] || '', c = 0, s = 0, q = false; | |
| for (var i = 0; i < head.length; i++) { | |
| var ch = head.charAt(i); | |
| if (ch === '"') q = !q; | |
| else if (!q && ch === ',') c++; | |
| else if (!q && ch === ';') s++; | |
| } | |
| return s > c ? ';' : ','; | |
| } | |
| function parseCsv(text, d) { | |
| var rows = [], field = '', row = [], q = false, line = 1, start = 1, i, ch, nx; | |
| for (i = 0; i < text.length; i++) { | |
| ch = text.charAt(i); nx = text.charAt(i + 1); | |
| if (q) { | |
| if (ch === '"' && nx === '"') { field += '"'; i++; } | |
| else if (ch === '"') q = false; | |
| else { if (ch === '\n') line++; field += ch; } | |
| continue; | |
| } | |
| if (ch === '"') { q = true; continue; } | |
| if (ch === d) { row.push(field); field = ''; continue; } | |
| if (ch === '\r') continue; | |
| if (ch === '\n') { row.push(field); rows.push({ line: start, fields: row }); row = []; field = ''; line++; start = line; continue; } | |
| field += ch; | |
| } | |
| if (field.length || row.length) { row.push(field); rows.push({ line: start, fields: row }); } | |
| return rows.filter(function (r) { return r.fields.join('').trim() !== ''; }); | |
| } | |
| function norm(s) { return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); } | |
| function mapHeader(cells) { | |
| var n = cells.map(norm), used = {}, map = {}; | |
| Object.keys(COLS).forEach(function (key) { | |
| var col = COLS[key], ex = EXCLUDE[key] || []; | |
| for (var h = 0; h < n.length; h++) { | |
| if (used[h]) continue; | |
| var blocked = ex.some(function (t) { return n[h].indexOf(norm(t)) >= 0; }); | |
| if (blocked) continue; | |
| var hit = (col.match || []).some(function (grp) { | |
| return grp.every(function (tok) { return n[h].indexOf(norm(tok)) >= 0; }); | |
| }); | |
| if (hit) { map[key] = h; used[h] = 1; return; } | |
| } | |
| }); | |
| return map; | |
| } | |
| // ---- ISO 7064 MOD 97-10, the check digits inside every LEI (ISO 17442) --- | |
| function mod97(s) { | |
| var rem = 0; | |
| for (var i = 0; i < s.length; i++) { | |
| var c = s.charAt(i), v = /[0-9]/.test(c) ? c : String(c.charCodeAt(0) - 55); | |
| for (var j = 0; j < v.length; j++) rem = (rem * 10 + (v.charCodeAt(j) - 48)) % 97; | |
| } | |
| return rem; | |
| } | |
| function leiCheckDigits(first18) { | |
| var want = 98 - mod97(first18 + '00'); | |
| return (want < 10 ? '0' : '') + want; | |
| } | |
| // ---- field checks ------------------------------------------------------- | |
| function isPlaceholder(v) { return DOC.placeholders.indexOf(String(v).toLowerCase().trim()) >= 0; } | |
| function dateParts(v) { | |
| var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v); | |
| if (!m) return null; | |
| var y = +m[1], mo = +m[2], d = +m[3], dt = new Date(Date.UTC(y, mo - 1, d)); | |
| if (dt.getUTCFullYear() !== y || dt.getUTCMonth() + 1 !== mo || dt.getUTCDate() !== d) return { bad: true }; | |
| return { y: y, m: mo, d: d }; | |
| } | |
| function dateStyle(v) { | |
| if (/^\d{1,2}[\/.\-]\d{1,2}[\/.\-]\d{4}$/.test(v)) return 'dd/mm/yyyy'; | |
| if (/^\d{4}\/\d{2}\/\d{2}$/.test(v)) return 'yyyy/mm/dd'; | |
| if (/[A-Za-z]/.test(v)) return 'a written month'; | |
| return 'this'; | |
| } | |
| function plainAmount(v) { | |
| var s = String(v).replace(/[^0-9.,\-]/g, ''); | |
| if (/,\d{1,2}$/.test(s)) s = s.replace(/\./g, '').replace(',', '.'); | |
| else s = s.replace(/,/g, ''); | |
| var m = /-?\d+(\.\d+)?/.exec(s); | |
| return m ? m[0] : '0'; | |
| } | |
| function checkCell(key, val, line, out) { | |
| var col = COLS[key], label = col.label, v = String(val == null ? '' : val).trim(); | |
| if (col.required && !v) { out.push(fmt('empty_required', { label: label }, line)); return; } | |
| if (!v) return; | |
| if (MOJI.test(v)) out.push(fmt('mojibake', { value: v }, line)); | |
| if (col.kind === 'lei') { | |
| if (isPlaceholder(v)) { out.push(fmt('lei_placeholder', { label: label, value: v }, line)); return; } | |
| if (v.length !== 20) { out.push(fmt('lei_length', { label: label, value: v, got: v.length }, line)); return; } | |
| if (!/^[A-Z0-9]{20}$/.test(v)) { out.push(fmt('lei_charset', { label: label, value: v }, line)); return; } | |
| if (mod97(v) !== 1) out.push(fmt('lei_checksum', { label: label, value: v, want: leiCheckDigits(v.slice(0, 18)), got: v.slice(18) }, line)); | |
| } else if (col.kind === 'date') { | |
| var p = dateParts(v); | |
| if (!p) { out.push(fmt('date_format', { label: label, value: v, got: dateStyle(v) }, line)); return; } | |
| if (p.bad) out.push(fmt('date_impossible', { label: label, value: v }, line)); | |
| } else if (col.kind === 'country') { | |
| if (!/^[A-Z]{2}$/.test(v)) out.push(fmt('country_shape', { label: label, value: v }, line)); | |
| else if (DOC.iso3166.indexOf(v) < 0) out.push(fmt('country_unknown', { value: v }, line)); | |
| } else if (col.kind === 'currency') { | |
| if (!/^[A-Z]{3}$/.test(v)) out.push(fmt('currency_shape', { label: label, value: v }, line)); | |
| else if (DOC.iso4217.indexOf(v) < 0) out.push(fmt('currency_unknown', { value: v }, line)); | |
| } else if (col.kind === 'bool') { | |
| var lv = v.toLowerCase(); | |
| if (DOC.bool_true.indexOf(lv) < 0 && DOC.bool_false.indexOf(lv) < 0) out.push(fmt('bool_token', { label: label, value: v }, line)); | |
| } else if (col.kind === 'amount') { | |
| if (!/^-?\d+(\.\d+)?$/.test(v)) out.push(fmt('amount_format', { label: label, value: v, want: plainAmount(v) }, line)); | |
| } else if (col.required && isPlaceholder(v)) { | |
| out.push(fmt('empty_required', { label: label }, line)); | |
| } | |
| } | |
| function check(text, opts) { | |
| opts = opts || {}; | |
| var today = /^\d{4}-\d{2}-\d{2}$/.test(opts.today || '') ? opts.today : new Date().toISOString().slice(0, 10); | |
| var out = []; | |
| var src = String(text == null ? '' : text); | |
| if (!src.trim()) return { findings: [], rows: 0, today: today }; | |
| var d = pickDelim(src); | |
| if (d === ';') out.push(fmt('delimiter_not_comma', {}, 1)); | |
| var rows = parseCsv(src, d); | |
| if (!rows.length) return { findings: out, rows: 0, today: today }; | |
| var head = rows[0], body = rows.slice(1), map = mapHeader(head.fields); | |
| Object.keys(COLS).forEach(function (key) { | |
| if (COLS[key].required && map[key] == null) out.push(fmt('missing_column', { label: COLS[key].label }, head.line)); | |
| }); | |
| var refs = {}, seenRow = {}, leiName = {}, r, i; | |
| for (i = 0; i < body.length; i++) { | |
| r = body[i]; | |
| if (map.contract_ref != null) { | |
| var rf = String(r.fields[map.contract_ref] || '').trim(); | |
| if (rf && refs[rf] == null) refs[rf] = r.line; | |
| } | |
| } | |
| for (i = 0; i < body.length; i++) { | |
| r = body[i]; | |
| if (r.fields.length !== head.fields.length) { | |
| out.push(fmt('row_field_count', { got: r.fields.length, want: head.fields.length }, r.line)); | |
| continue; | |
| } | |
| var joined = r.fields.join(''); | |
| if (seenRow[joined] != null) out.push(fmt('duplicate_row', { other: seenRow[joined] }, r.line)); | |
| else seenRow[joined] = r.line; | |
| (function (row) { | |
| Object.keys(map).forEach(function (key) { checkCell(key, row.fields[map[key]], row.line, out); }); | |
| })(r); | |
| var ref = map.contract_ref != null ? String(r.fields[map.contract_ref] || '').trim() : ''; | |
| if (ref && refs[ref] != null && refs[ref] !== r.line) out.push(fmt('duplicate_contract_ref', { value: ref, other: refs[ref] }, r.line)); | |
| var link = map.linked_ref != null ? String(r.fields[map.linked_ref] || '').trim() : ''; | |
| if (link && !isPlaceholder(link)) { | |
| if (link === ref) out.push(fmt('self_link', { value: link }, r.line)); | |
| else if (refs[link] == null) out.push(fmt('unresolved_link', { value: link }, r.line)); | |
| } | |
| var lei = map.provider_lei != null ? String(r.fields[map.provider_lei] || '').trim() : ''; | |
| var nm = map.provider_name != null ? String(r.fields[map.provider_name] || '').trim() : ''; | |
| if (/^[A-Z0-9]{20}$/.test(lei) && nm) { | |
| if (leiName[lei] && leiName[lei].name.toLowerCase() !== nm.toLowerCase()) { | |
| out.push(fmt('lei_name_conflict', { value: lei, got: nm, want: leiName[lei].name, other: leiName[lei].line }, r.line)); | |
| } else if (!leiName[lei]) leiName[lei] = { name: nm, line: r.line }; | |
| } | |
| var sd = map.start_date != null ? String(r.fields[map.start_date] || '').trim() : ''; | |
| var ed = map.end_date != null ? String(r.fields[map.end_date] || '').trim() : ''; | |
| var sp = dateParts(sd), ep = dateParts(ed); | |
| if (sp && ep && !sp.bad && !ep.bad) { | |
| if (ed < sd) out.push(fmt('date_order', { got: ed, want: sd }, r.line)); | |
| else if (ed < today) out.push(fmt('contract_expired', { value: ed, want: today }, r.line)); | |
| } | |
| } | |
| out.sort(function (a, b) { return (a.line - b.line) || 0; }); | |
| return { findings: out, rows: body.length, columns: Object.keys(map).length, today: today }; | |
| } | |
| var DORAENGINE = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, leiCheckDigits: leiCheckDigits, mod97: mod97 }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = DORAENGINE; | |
| else window.DORAENGINE = DORAENGINE; | |
| </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 DORAREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = DORAREPORT; | |
| else window.DORAREPORT = DORAREPORT; | |
| </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.DORAENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.DORAREPORT.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://marketplace.visualstudio.com/items?itemName=ReadyStack.dora-register-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dora-register-lint',src:'mp',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">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/dora-register-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dora-register-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/dora-register-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dora-register-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://www.npmjs.com/package/@readystack/dora-register-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dora-register-lint',src:'mcp',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">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/dora-register-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dora-register-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">ext install ReadyStack.dora-register-lint | |
| npx @readystack/dora-register-lint <file> | |
| npx @readystack/dora-register-lint --mcp</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 24 rules DORA Register of Information Lint 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:"dora-register-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">24 checks on the register CSV before it reaches the supervisor — LEI check digits, ISO dates and codes, references that resolve</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">One hour of the compliance analyst who would otherwise re-check the file by hand costs more than the licence; a manual pass over an 800-row register is 24 x 800 = 19,200 check-row comparisons.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_4WZMkiJUxjGBBEqJo7hbkQ7REYH8kJIOWwGoJ29agpN" 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> | |
| <!--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><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> | |
| <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> | |
| <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-13">Updated 2026-09-13</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-13","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/sku54671_result_card.jpg" alt="DORA Register of Information Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Twenty-four findings — nineteen errors and five warnings — on ten of eleven rows. That is what an eleven-row sample DORA Register of Information gives up the first time a compliance engineer at an EU payment institution checks the file before filing it.</p> | |
| <p>The register is the list Article 28(3) of Regulation (EU) 2022/2554 asks a financial entity to maintain: every contractual arrangement with an ICT third-party provider, the function it supports, whether that function is critical, where it is provided, what it costs. An export job produces it, and its errors are not spelling mistakes. They are arithmetic, and arithmetic is invisible to the person doing the final read-through.</p> | |
| <p>Start with the identifier. An LEI is twenty characters (ISO 17442) and the last two are check digits computed over the other eighteen with ISO 7064 MOD 97-10. Row two of the sample carried 549300ZBWZJMTEDVDS68. The check digits should be 67. One character is off — a transposition, a paste that lost a keystroke — and nothing about the value looks wrong. A spreadsheet will not catch it. A generic CSV validator will not catch it, because the field is twenty alphanumeric characters and that is all a schema knows to ask. The lint recomputes the digits and prints the two it expected.</p> | |
| <p>Then the dates. 01/04/2024 is the first of April where it was typed and the fourth of January where it is read; the sample carries one. 2026-02-30 is a date that does not exist. One row ends before it starts. And one arrangement ended on 2026-06-30 while the file is being filed as of 2026-09-13 — a contract that terminated in June, still listed as live in September. That last check depends entirely on the reference date, which you set: move the date and the finding moves with it.</p> | |
| <p>Then the links. A register is a graph, not a list. ARR-2026-003 appears as the reference on two different rows, so the supervisor cannot tell which arrangement is which. Another row points at overarching arrangement ARR-2026-009, which no row in the file declares. One row names itself as its own parent. One row is filed twice, field for field.</p> | |
| <p>And the dull ones that bounce a file just as hard: fourteen fields where the header has thirteen, because a provider name held an unescaped comma; Crédit where Crédit was meant, UTF-8 read as Latin-1; 412.000,00 in a numeric column; the word Critical in a yes/no column; Norway where an ISO 3166-1 alpha-2 code belongs; eur in lower case.</p> | |
| <p>Twenty-four checks, all computed from the file itself. None of them gives legal advice or tells you whether the register is complete — they tell you whether what you wrote down survives being read by a machine that is not yours.</p> | |
| <p>Why not hand the file to an assistant? Because the register is the map of who holds your data and which functions stop without them — the one file you cannot paste into a hosted chat window. And because ISO 7064 is arithmetic: a model either runs it or guesses. This runs on the file where it sits, and so does the same engine in the browser.</p> | |
| <p>Checking the file open in your editor is free: every row, all twenty-four checks, no key and no account, findings in the Problems panel and a written summary in the output channel. The licence covers a different job — sweeping every register CSV in the workspace at once and writing one dated report file for the audit trail — and that sweep runs free for seven days before it asks, so you see the paid answer on your own register first. By hand the same pass over an 800-row register is 24 × 800 = 19,200 comparisons, and the check digits cannot be done by eye at all; one hour of the analyst who would do it costs more than the licence.</p> | |
| <p>Same engine, byte for byte, in the browser: https://getreadystack.com/tools/dora-register-lint</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/sku54671_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku54671_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 DORA Register of Information Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads the register CSV open in your editor and runs 24 checks on it: it recomputes the ISO 7064 check digits inside every LEI, rejects dates that are not ISO 8601 or do not exist, flags country and currency values that are not ISO codes, finds duplicate contract references, links pointing at rows that are absent, and arrangements that ended before your reference date.</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 it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The engineer or compliance analyst inside an EU financial entity - a bank, insurer, investment firm, payment or e-money institution - who generates the Register of Information export under DORA Article 28(3) and has to hand it to a national competent authority without it bouncing back for data quality rework.</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 an AI assistant or use a free CSV validator?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The register names every provider holding your data and every function that stops without them, so pasting it into a hosted assistant exports your outsourcing map. A generic CSV validator checks shape, not ISO 7064 check digits, not whether a linked arrangement reference exists in the file, not whether a contract already ended. This runs locally and does all three.</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?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Checking the file open in your editor is free forever, with no key, no account and no limit on rows or runs - all 24 checks on every row. The licence covers a different job: sweeping every register CSV in the workspace at once and writing one dated report file you can attach to the audit trail. That sweep runs free for seven days first.</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 would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A person doing it by hand compares 24 checks against every row: an 800-row register is 19,200 comparisons, and the LEI check digits cannot be done by eye at all. One hour of a financial-sector compliance analyst costs more than the 29 dollar licence, which is a one-off with one key per person or CI 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">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. ICT Register Lint - DORA register of information (CSV, XLSX) checks the file you open against 24 rules from a rule set dated 2026-09-20, and points at the exact line. 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 DORA Register of Information Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads the register CSV open in your editor and runs 24 checks on it: it recomputes the ISO 7064 check digits inside every LEI, rejects dates that are not ISO 8601 or do not exist, flags country and currency values that are not ISO codes, finds duplicate contract references, links pointing at rows that are absent, and arrangements that ended before your reference date."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "The engineer or compliance analyst inside an EU financial entity - a bank, insurer, investment firm, payment or e-money institution - who generates the Register of Information export under DORA Article 28(3) and has to hand it to a national competent authority without it bouncing back for data quality rework."}}, {"@type": "Question", "name": "Why not just ask an AI assistant or use a free CSV validator?", "acceptedAnswer": {"@type": "Answer", "text": "The register names every provider holding your data and every function that stops without them, so pasting it into a hosted assistant exports your outsourcing map. A generic CSV validator checks shape, not ISO 7064 check digits, not whether a linked arrangement reference exists in the file, not whether a contract already ended. This runs locally and does all three."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Checking the file open in your editor is free forever, with no key, no account and no limit on rows or runs - all 24 checks on every row. The licence covers a different job: sweeping every register CSV in the workspace at once and writing one dated report file you can attach to the audit trail. That sweep runs free for seven days first."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A person doing it by hand compares 24 checks against every row: an 800-row register is 19,200 comparisons, and the LEI check digits cannot be done by eye at all. One hour of a financial-sector compliance analyst costs more than the 29 dollar licence, which is a one-off with one key per person or CI seat."}}, {"@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. ICT Register Lint - DORA register of information (CSV, XLSX) checks the file you open against 24 rules from a rule set dated 2026-09-20, and points at the exact line. 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:54671,slug:"dora-register-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/dora-ict-contract-clause-lint"><span class="yj-rel-t">DORA Art. 30 ICT Contract Clause Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/stale-standard-citation-lint"><span class="yj-rel-t">Stale Standard Citation Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/autohotkey-toml-devicetree-snippets"><span class="yj-rel-t">AutoHotkey v2, TOML and DeviceTree checks for VS Code</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cra-24-72-14-reporting-lint"><span class="yj-rel-t">CRA 24/72/14 Reporting Lint (Article 14)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/crypto-export-notice-lint"><span class="yj-rel-t">Crypto Export Notice Lint (EAR 742.15)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-act-article-50-audit"><span class="yj-rel-t">EU AI Act Article 50 Transparency Audit for Your Codebase</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></body> | |
| </html> | |