fix(kb): check_okf.py flags control bytes (NUL etc.) outside \t\n\r
The validator only ever parsed frontmatter fields — it never scanned body content, so a file with literal NUL bytes pasted in from a psql SELECT (kb/audits/wiki-kompilat-recon-2026-08-26.md) passed silently. Adds a whole-file scan for control bytes other than tab/newline/CR, one error per offending line. Covered by scripts/kb/tests/test_check_okf.py.
This commit is contained in:
parent
a0a86140c1
commit
8af8c2eeab
|
|
@ -18,6 +18,8 @@ reguły tego repo:
|
||||||
10. Wpisy `contradicts` wyglądające jak ścieżka .md też muszą istnieć;
|
10. Wpisy `contradicts` wyglądające jak ścieżka .md też muszą istnieć;
|
||||||
pozostałe wpisy to wolny tekst.
|
pozostałe wpisy to wolny tekst.
|
||||||
11. `stub` — o ile obecne — musi być boolem.
|
11. `stub` — o ile obecne — musi być boolem.
|
||||||
|
12. Brak bajtów kontrolnych poza \t \n \r w całym pliku (łapie wklejki NUL/inne
|
||||||
|
binarne śmieci wklejone z zewnętrznych źródeł, np. wyników SELECT-ów).
|
||||||
|
|
||||||
Zakres domyślny: kb/ oraz docs/sessions/, z wyłączeniem README-wskaźników
|
Zakres domyślny: kb/ oraz docs/sessions/, z wyłączeniem README-wskaźników
|
||||||
(POINTER_GLOBS) — te są nawigacją do kb-doca, nie dokumentami KB, i celowo nie
|
(POINTER_GLOBS) — te są nawigacją do kb-doca, nie dokumentami KB, i celowo nie
|
||||||
|
|
@ -71,6 +73,7 @@ POINTER_GLOBS = (
|
||||||
EXCLUDE_DIRS = ("build",)
|
EXCLUDE_DIRS = ("build",)
|
||||||
|
|
||||||
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
||||||
|
CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
|
||||||
|
|
||||||
|
|
||||||
def is_pointer(rel: str) -> bool:
|
def is_pointer(rel: str) -> bool:
|
||||||
|
|
@ -232,6 +235,17 @@ def check_file(path: Path, root: Path) -> list[str]:
|
||||||
f"{rel}: `contradicts` wskazuje na nieistniejący plik: {item}"
|
f"{rel}: `contradicts` wskazuje na nieistniejący plik: {item}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
seen_lines: set[int] = set()
|
||||||
|
for m in CONTROL_CHAR_RE.finditer(text):
|
||||||
|
line_no = text.count("\n", 0, m.start()) + 1
|
||||||
|
if line_no in seen_lines:
|
||||||
|
continue
|
||||||
|
seen_lines.add(line_no)
|
||||||
|
errors.append(
|
||||||
|
f"{rel}: bajt kontrolny {ord(m.group()):#04x} w linii {line_no} "
|
||||||
|
"(dozwolone tylko \\t \\n \\r)"
|
||||||
|
)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
62
scripts/kb/tests/test_check_okf.py
Normal file
62
scripts/kb/tests/test_check_okf.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
"""Tests for check_okf.py — the OKF v0.1 frontmatter validator.
|
||||||
|
|
||||||
|
Covers the control-character check added 2026-08-27: a file that otherwise has
|
||||||
|
valid frontmatter must still fail if its body contains a NUL byte or other
|
||||||
|
control byte outside \\t \\n \\r (the class of bug found in
|
||||||
|
kb/audits/wiki-kompilat-recon-2026-08-26.md — raw bytes pasted in from a psql
|
||||||
|
SELECT over the mail corpus).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
import check_okf # noqa: E402
|
||||||
|
|
||||||
|
VALID_FRONTMATTER = """---
|
||||||
|
okf: "0.1"
|
||||||
|
type: decision
|
||||||
|
visibility: private
|
||||||
|
status: active
|
||||||
|
updated: 2026-08-27
|
||||||
|
links: []
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _write(tmp_path: Path, body: str) -> Path:
|
||||||
|
p = tmp_path / "doc.md"
|
||||||
|
p.write_text(VALID_FRONTMATTER + body, encoding="utf-8")
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_file_has_no_control_char_errors(tmp_path):
|
||||||
|
path = _write(tmp_path, "# Title\n\nZwykła treść z \t tabulatorem i \r\n końcem linii.\n")
|
||||||
|
errors = check_okf.check_file(path, tmp_path)
|
||||||
|
assert errors == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_nul_byte_in_body_is_flagged(tmp_path):
|
||||||
|
path = _write(tmp_path, "# Title\n\nZawiera bajt NUL: \x00 w tym miejscu.\n")
|
||||||
|
errors = check_okf.check_file(path, tmp_path)
|
||||||
|
assert any("bajt kontrolny" in e and "0x0" in e for e in errors)
|
||||||
|
|
||||||
|
|
||||||
|
def test_control_char_error_reports_correct_line(tmp_path):
|
||||||
|
body = "linia 1\nlinia 2\nzepsuta \x00 linia 3\nlinia 4\n"
|
||||||
|
path = _write(tmp_path, body)
|
||||||
|
errors = check_okf.check_file(path, tmp_path)
|
||||||
|
control_errors = [e for e in errors if "bajt kontrolny" in e]
|
||||||
|
assert len(control_errors) == 1
|
||||||
|
# Frontmatter occupies 7 lines before the body starts.
|
||||||
|
frontmatter_lines = VALID_FRONTMATTER.count("\n")
|
||||||
|
expected_line = frontmatter_lines + 3
|
||||||
|
assert f"w linii {expected_line}" in control_errors[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_control_chars_same_line_reported_once(tmp_path):
|
||||||
|
path = _write(tmp_path, "para \x00 z dwoma \x00 bajtami na tej samej linii\n")
|
||||||
|
errors = check_okf.check_file(path, tmp_path)
|
||||||
|
control_errors = [e for e in errors if "bajt kontrolny" in e]
|
||||||
|
assert len(control_errors) == 1
|
||||||
Loading…
Reference in a new issue