{html.escape(step_id)}
{status_badge(step_status)}Observed
{html.escape(str(step.get("observed", "")))}
{m.group(1)}", escaped)
+ escaped = BOLD_RE.sub(lambda m: f"{m.group(1)}", escaped)
+
+ def link(match: re.Match[str]) -> str:
+ label, target = match.group(1), html.unescape(match.group(2))
+ if "#" in target:
+ target = "#" + target.rsplit("#", 1)[1]
+ return f'{label}'
+
+ return LINK_RE.sub(link, escaped)
+
+
+def markdown_to_html(text: str) -> str:
+ """Render the deliberately small Markdown subset used by test plans."""
+ lines = text.splitlines()
+ out: list[str] = []
+ paragraph: list[str] = []
+ list_type: str | None = None
+ in_code = False
+ code_lang = ""
+ code_lines: list[str] = []
+ table_lines: list[str] = []
+
+ def flush_paragraph() -> None:
+ nonlocal paragraph
+ if paragraph:
+ out.append(
+ f"{inline_markup(' '.join(x.strip() for x in paragraph))}
" + ) + paragraph = [] + + def close_list() -> None: + nonlocal list_type + if list_type: + out.append(f"{list_type}>") + list_type = None + + def flush_table() -> None: + nonlocal table_lines + if not table_lines: + return + rows = [ + [cell.strip() for cell in line.strip().strip("|").split("|")] + for line in table_lines + ] + if len(rows) >= 2 and all( + re.fullmatch(r":?-{3,}:?", c.replace(" ", "")) for c in rows[1] + ): + out.append( + '| {inline_markup(c)} | " for c in rows[0]) + + "
|---|
| {inline_markup(c)} | " for c in row) + + "
{inline_markup(line)}
" for line in table_lines) + table_lines = [] + + for line in lines + [""]: + if in_code: + if line.startswith("```"): + out.append( + f'{html.escape(chr(10).join(code_lines))}'
+ )
+ in_code = False
+ code_lines = []
+ else:
+ code_lines.append(line)
+ continue
+ if line.startswith("```"):
+ flush_paragraph()
+ close_list()
+ flush_table()
+ in_code = True
+ code_lang = line[3:].strip()
+ continue
+ if match := ANCHOR_LINE_RE.match(line):
+ flush_paragraph()
+ close_list()
+ flush_table()
+ out.append(f'')
+ continue
+ if match := HEADING_RE.match(line):
+ flush_paragraph()
+ close_list()
+ flush_table()
+ level = len(match.group(1))
+ out.append(f"{html.escape(json.dumps(value, ensure_ascii=False, indent=2))}'
+
+
+def ref_link(ref: str, label: str | None = None) -> str:
+ fragment = ref.rsplit("#", 1)[-1] if "#" in ref else ""
+ href = f"#{html.escape(fragment, quote=True)}" if fragment else "#"
+ return f'{html.escape(label or ref)}'
+
+
+def resolve_ref_file(base: Path, ref: str, plan_root: Path) -> tuple[Path, str]:
+ path_part, _, anchor = ref.partition("#")
+ path = base if not path_part else (base.parent / path_part).resolve()
+ try:
+ path.relative_to(plan_root)
+ except ValueError as error:
+ raise ReportError(f"reference escapes plan root: {ref}") from error
+ if not path.is_file():
+ raise ReportError(f"referenced file does not exist: {ref} from {base}")
+ return path, anchor
+
+
+def load_evidence(
+ result_path: Path, ref: str, plan_root: Path
+) -> tuple[dict[str, Any], Path]:
+ path, anchor = resolve_ref_file(result_path, ref, plan_root)
+ value = load_json(path)
+ if anchor and anchor not in (value.get("anchor"), value.get("id")):
+ raise ReportError(f"evidence anchor #{anchor} not found in {path}")
+ return value, path
+
+
+def verify_attachment(result_path: Path, item: dict[str, Any], plan_root: Path) -> Path:
+ path = safe_path(
+ result_path.parent, require(item, "path", "attachment"), "attachment"
+ )
+ try:
+ path.relative_to(plan_root)
+ except ValueError as error:
+ raise ReportError(f"attachment escapes plan root: {path}") from error
+ if not path.is_file():
+ raise ReportError(f"missing attachment: {path}")
+ data = path.read_bytes()
+ expected_size = item.get("size_bytes")
+ if expected_size is not None and expected_size != len(data):
+ raise ReportError(f"attachment size mismatch: {path}")
+ expected_hash = item.get("sha256")
+ if expected_hash and hashlib.sha256(data).hexdigest() != expected_hash:
+ raise ReportError(f"attachment SHA-256 mismatch: {path}")
+ return path
+
+
+def render_command(command: dict[str, Any], anchor_override: str | None = None) -> str:
+ anchor = anchor_override or str(
+ command.get("anchor") or command.get("id") or "command"
+ )
+ title = str(command.get("command", "command"))
+ env = command.get("environment", {})
+ meta = {
+ "cwd": command.get("cwd"),
+ "environment": env,
+ "started_at": command.get("started_at"),
+ "finished_at": command.get("finished_at"),
+ "duration_ms": command.get("duration_ms"),
+ "exit_code": command.get("exit_code"),
+ }
+ parts = [
+ f'',
+ '{html.escape(title)}",
+ f' exit {html.escape(str(command.get("exit_code", "?")))}{html.escape(str(value))}'
+ )
+ attachment = command.get(f"{stream}_attachment")
+ if attachment:
+ parts.append(
+ f"{stream} attachment: {ref_link(str(attachment.get('ref', '')))}
" + ) + parts.append("{html.escape(str(step.get("observed", "")))}
Run {html.escape(run_id)} · {html.escape(str(run.get("started_at", "")))} → {html.escape(str(run.get("finished_at", "")))}
'
+ + " ".join(f"{html.escape(x)}" for x in chips)
+ + "
'
+ + html.escape(json.dumps(run, ensure_ascii=False, indent=2))
+ + "