Check the plan before filing into it, keep filed items in order, name the filer's tests in the gate, and record the merge over a worse ruling

This commit is contained in:
2026-10-14 13:39:42 +02:00
committed by Lillem4n
parent 16afcff09c
commit 7739c09163
3 changed files with 37 additions and 18 deletions
+26 -10
View File
@@ -2,7 +2,8 @@
"""File each module's known vulnerabilities as one security item atop todo.md's next release.
Usage: file_vulnerabilities.py <govulncheck -format json report> <todo.md>
Exits 1 while any vulnerability is known, filed now or before, and 0 on a clean report.
Exits 1 while any vulnerability is known, filed now or before, 0 on a clean report, and 2 on
a todo.md it cannot read.
todo.md's `Next ID = N` gives the item's ID, and the release of the plan's top row, the next
release, gives its release. Its Score is 0.0 until rank_todo.py, which CI cannot run, scores
@@ -21,8 +22,9 @@ import re
import sys
from dataclasses import dataclass, field
ROW = re.compile(r"^\| (\d+) \| ([^|]+?) \| [^|]*\| \*\*", re.M)
ROW = re.compile(r"^\| (?P<id>\d+) \| (?P<release>[^|]+?) \| [^|]*\| \*\*", re.M)
NEXT_ID = re.compile(r"`Next ID = (\d+)`")
DETAILS = "## Details\n\n"
SCORES = {"R": 1, "S": 1, "C": 1, "A": 8, "G": 8}
GOAL = "1.1"
@@ -100,20 +102,30 @@ def detail(module: Module) -> str:
def file_items(modules: list[Module], todo: str) -> tuple[str, list[int]]:
"""Return todo.md with an item for each module whose vulnerabilities it does not yet name, and the items' IDs."""
first = ROW.search(todo)
if first is None:
raise ValueError("todo.md has no item to take the next release from")
if NEXT_ID.search(todo) is None:
raise ValueError("todo.md has no `Next ID = N` line to number the item by")
if DETAILS not in todo:
raise ValueError(f"todo.md has no {DETAILS.strip()!r} heading to put the item's detail under")
release, rows_at = first.group("release"), first.start()
details_at = todo.index(DETAILS) + len(DETAILS)
filed = []
for module in modules:
if all(osv in todo for osv in module.osvs):
continue
first = ROW.search(todo)
if first is None:
raise ValueError("todo.md has no item to take the next release from")
next_id = int(NEXT_ID.search(todo).group(1))
line = tagline(module)
scores = " | ".join(str(SCORES[k]) for k in "RSCAG")
row = f"| {next_id} | {first.group(2)} | security | **{line}** | {scores} | {GOAL} | 0.0 |\n"
todo = todo[: first.start()] + row + todo[first.start() :]
todo = todo.replace(f"`Next ID = {next_id}`", f"`Next ID = {next_id + 1}`", 1)
todo = todo.replace("## Details\n\n", f"## Details\n\n### {next_id}. {line}\n\n{detail(module)}\n", 1)
row = f"| {next_id} | {release} | security | **{line}** | {scores} | {GOAL} | 0.0 |\n"
text = f"### {next_id}. {line}\n\n{detail(module)}\n"
todo = todo[:rows_at] + row + todo[rows_at:]
rows_at += len(row)
details_at += len(row)
todo = todo[:details_at] + text + todo[details_at:]
details_at += len(text)
todo = NEXT_ID.sub(f"`Next ID = {next_id + 1}`", todo, count=1)
filed.append(next_id)
return todo, filed
@@ -128,7 +140,11 @@ def main(argv: list[str]) -> int:
count = sum(len(m.osvs) for m in modules)
known = f"{count} known {'vulnerability' if count == 1 else 'vulnerabilities'}"
with open(todo_path, encoding="utf-8") as f:
todo, filed = file_items(modules, f.read())
try:
todo, filed = file_items(modules, f.read())
except ValueError as err:
print(err, file=sys.stderr)
return 2
if not filed:
print(f"{known}, already planned in todo.md.")
return 1