Commit 6f269a27f0
Unsigned
Layout: unified · split
scripts/check-mappings.mjs +16 −15
| @@ -42,26 +42,27 @@ function rowsFromMigrations() { | |||
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | // --- 2. Human-readable copy: parse the doc's reference table. --- | 44 | // --- 2. Human-readable copy: parse the doc's reference table. --- |
| 45 | // Matches only rows of the reference table, which are shaped: | 45 | // Rows are parsed by splitting on "|" rather than a single big regex — that is |
| 46 | // | `resource` | status | SOC 2 | CC8.1 | positive | rationale | | 46 | // unambiguous and linear (no backtracking). A row counts only if it has the |
| 47 | // The framework name in cell 3 and the bare posture word in cell 5 are what | 47 | // reference table's shape: a backtick-wrapped resource, a known framework |
| 48 | // distinguish these from the per-section tables (framework-first, prose posture) | 48 | // label, and a bare posture word. That shape excludes the header/separator |
| 49 | // and the glossary (bolded control in cell 1), so those are not matched. | 49 | // rows, the per-section tables (framework-first, no backticked resource), and |
| 50 | // the glossary (fewer columns) — so only reference-table data rows match. | ||
| 50 | const FRAMEWORK_LABELS = { "SOC 2": "soc2", "ISO 27001": "iso27001" }; | 51 | const FRAMEWORK_LABELS = { "SOC 2": "soc2", "ISO 27001": "iso27001" }; |
| 51 | // Cell captures use [^|] rather than . so a capture cannot run past a column | 52 | const POSTURES = new Set(["positive", "negative", "informational"]); |
| 52 | // boundary — keeps the match linear (no catastrophic backtracking) and is why | 53 | const BACKTICKED = /^`.+`$/; |
| 53 | // splitting on "|" is unnecessary. | ||
| 54 | const ROW_RE = | ||
| 55 | /^\|\s*`([^`]+)`\s*\|\s*([^|]+?)\s*\|\s*(SOC 2|ISO 27001)\s*\|\s*([\w.]+)\s*\|\s*(positive|negative|informational)\s*\|/; | ||
| 56 | 54 | ||
| 57 | function rowsFromDoc() { | 55 | function rowsFromDoc() { |
| 58 | const set = new Set(); | 56 | const set = new Set(); |
| 59 | for (const line of readFileSync(docPath, "utf8").split("\n")) { | 57 | for (const line of readFileSync(docPath, "utf8").split("\n")) { |
| 60 | const m = ROW_RE.exec(line); | 58 | if (!line.startsWith("|")) continue; |
| 61 | if (!m) continue; | 59 | // Leading "|" yields an empty cells[0]; data lives in cells[1..5]. |
| 62 | const [, resource, statusCell, frameworkLabel, control, posture] = m; | 60 | const cells = line.split("|").map((c) => c.trim()); |
| 63 | const status = statusCell.replaceAll("`", "").trim(); // already "·" for NULL | 61 | const [, resource, statusCell, frameworkLabel, control, posture] = cells; |
| 64 | set.add(key(resource, status, FRAMEWORK_LABELS[frameworkLabel], control, posture)); | 62 | const framework = FRAMEWORK_LABELS[frameworkLabel]; |
| 63 | if (!framework || !POSTURES.has(posture) || !BACKTICKED.test(resource ?? "")) continue; | ||
| 64 | const status = statusCell.replaceAll("`", ""); // "·" for NULL | ||
| 65 | set.add(key(resource.replaceAll("`", ""), status, framework, control, posture)); | ||
| 65 | } | 66 | } |
| 66 | return set; | 67 | return set; |
| 67 | } | 68 | } |