Files
acdl/tests/test_slides_pipeline.py
T
Jon Chery 25ddc894c2 docs(milestone): complete v1.22 — Nova Deck Layout Fix
9 requirements complete (REQ-254..262):
- P1: theme-css — section padding + overflow + image rules + title
  chrome + spacing tightening (REQ-254,255,256)
- P2: render-scripts — delete render_deck.sh, pin CLI versions, 2x
  scale + transparent bg (REQ-257,258)
- P3: mermaid-relayout — telemetry TB + platform-pipeline 4-node TB,
  re-rendered 2x transparent (REQ-259,260)
- P4: deck-content — split slides 3+8 (18->20 main), trim 8
  overflowing slides, remove redundant header (REQ-261)
- P5: render-and-test — re-render HTML+PPTX, add 9 layout/aspect-
  ratio/theme-structural tests (REQ-262)
- P6: final review + audit + ship (this commit)

Final review fixes: source .md + talking-points re-synced to 20-slide
structure; ![h:480 class:tall] directives applied; README stale
references updated; CSS trailing newline added.

Root cause: nova-sp-theme.css had zero section padding (declared
/* @theme nova-sp */ as a comment, not the @theme directive; did not
@import Marp default theme). Combined with overflow:hidden, blunt
img max-height:320px, header+footer chrome on every slide, and two
P5 diagrams with extreme aspect ratios (13.52x and 0.63x), 8 of 19
slides overflowed. NOT a P5 regression — theme CSS byte-identical
P3->P5; P5 denser content made pre-existing flaws visible.

Tags on v1.21.x line (v1.21.0 P0 -> v1.21.6 P6 final = milestone
release). 32 slide tests pass (23 original + 9 new). 94 key-file
tests pass. Pipeline check exit 0.

---ci---
project: acdl
phase: 6
milestone: v1.22
status: complete
phase_role: final
requirements:
  covered: [REQ-254,REQ-255,REQ-256,REQ-257,REQ-258,REQ-259,REQ-260,REQ-261,REQ-262]
  partial: []
---/ci---
2026-08-11 20:11:43 +00:00

373 lines
16 KiB
Python

"""REQ-239..243 (v1.20) + REQ-245,251,252 (v1.21): S&P theme + slide render
pipeline + deck-refinement tests.
v1.20 validates:
- The Marp deck frontmatter references nova-sp-theme.css
- The CSS file contains the S&P colors (#D6002A, #1B1B1B)
- The mermaid theme JSON contains the S&P colors
- Every .mmd has a corresponding .png
- The render_slides.sh script exists and is executable
- The CI workflow file exists
v1.21 adds (REQ-245,251,252):
- Deck renamed to nova-autonomous-cloud-delivery*
- No maturity badges in the Marp deck
- No version in the Marp footer/title slide
- 20 main + 1 appendix slides (v1.22 split slides 3+8 to relieve overflow)
- No D-###/REQ-###/internal .py paths in audience-facing slides
- Title is "Nova — The Autonomous Cloud Delivery Platform"
"""
import re
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
PRESENTATIONS = ROOT / "docs" / "presentations"
ASSETS = PRESENTATIONS / "assets"
THEME_CSS = ASSETS / "nova-sp-theme.css"
THEME_JSON = ASSETS / "mmd" / "sp-theme.json"
MARP_DECK = PRESENTATIONS / "nova-autonomous-cloud-delivery-marp.md"
SOURCE_MD = PRESENTATIONS / "nova-autonomous-cloud-delivery.md"
RENDER_SCRIPT = ROOT / "scripts" / "render_slides.sh"
SLIDES_WORKFLOW = ROOT / ".github" / "workflows" / "slides.yml"
def test_sp_theme_css_exists():
"""REQ-239: nova-sp-theme.css exists."""
assert THEME_CSS.is_file(), f"theme CSS not found: {THEME_CSS}"
def test_sp_theme_css_has_snp_colors():
"""REQ-239: CSS contains S&P Red and Black."""
css = THEME_CSS.read_text()
assert "#D6002A" in css, "S&P Red (#D6002A) missing from theme CSS"
assert "#1B1B1B" in css, "S&P Black (#1B1B1B) missing from theme CSS"
def test_sp_theme_json_has_snp_colors():
"""The mermaid theme JSON also has S&P colors."""
json_text = THEME_JSON.read_text()
assert "#D6002A" in json_text, "S&P Red missing from mermaid theme"
assert "#1B1B1B" in json_text, "S&P Black missing from mermaid theme"
def test_marp_deck_uses_sp_theme():
"""REQ-239: Marp deck frontmatter references nova-sp-theme.css."""
text = MARP_DECK.read_text()
# The frontmatter is between the first two ---
fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL)
assert fm_match, "Marp frontmatter not found"
frontmatter = fm_match.group(1)
assert "nova-sp" in frontmatter, \
"Marp deck does not reference nova-sp theme"
def test_marp_deck_not_using_default_theme():
"""The Marp deck must not use 'theme: default'."""
text = MARP_DECK.read_text()
fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL)
assert fm_match, "Marp frontmatter not found"
frontmatter = fm_match.group(1)
assert "theme: default" not in frontmatter, \
"Marp deck still uses 'theme: default' — should use nova-sp-theme.css"
def test_render_slides_script_exists():
"""REQ-240: render_slides.sh exists and is executable."""
assert RENDER_SCRIPT.is_file(), "render_slides.sh not found"
import os
assert os.access(RENDER_SCRIPT, os.X_OK), "render_slides.sh not executable"
def test_render_slides_script_renders_mermaid():
"""REQ-240: render_slides.sh renders mermaid diagrams."""
text = RENDER_SCRIPT.read_text()
assert "mermaid-cli" in text or "mmdc" in text, \
"render_slides.sh does not invoke mermaid-cli"
assert "sp-theme.json" in text, \
"render_slides.sh does not reference sp-theme.json"
def test_render_slides_script_renders_marp():
"""REQ-240: render_slides.sh renders Marp HTML + PPTX."""
text = RENDER_SCRIPT.read_text()
assert "marp-cli" in text, "render_slides.sh does not invoke marp-cli"
assert ".html" in text, "render_slides.sh does not produce HTML"
assert ".pptx" in text, "render_slides.sh does not produce PPTX"
def test_render_slides_default_deck_renamed():
"""REQ-245: render_slides.sh default deck is nova-autonomous-cloud-delivery."""
text = RENDER_SCRIPT.read_text()
assert "nova-autonomous-cloud-delivery" in text, \
"render_slides.sh does not default to nova-autonomous-cloud-delivery"
def test_slides_ci_workflow_exists():
"""REQ-241: CI workflow for slides exists."""
assert SLIDES_WORKFLOW.is_file(), "slides.yml workflow not found"
def test_slides_ci_workflow_triggers_on_presentations():
"""REQ-241: CI workflow triggers on docs/presentations/ changes."""
text = SLIDES_WORKFLOW.read_text()
assert "docs/presentations" in text, \
"slides.yml does not trigger on docs/presentations/"
assert "render_slides.sh" in text, \
"slides.yml does not invoke render_slides.sh"
def test_every_mmd_has_png():
"""REQ-240: every .mmd file has a corresponding .png."""
mmd_dir = ASSETS / "mmd"
png_dir = ASSETS / "png"
if not mmd_dir.is_dir():
pytest.skip("no .mmd directory")
mmd_files = sorted(mmd_dir.glob("*.mmd"))
assert len(mmd_files) > 0, "no .mmd files found"
missing = []
for mmd in mmd_files:
png = png_dir / f"{mmd.stem}.png"
if not png.is_file():
missing.append(mmd.name)
assert not missing, f"PNGs missing for: {missing}"
def test_readme_no_retired_decks():
"""REQ-243: presentations README does not list retired decks."""
readme = (PRESENTATIONS / "README.md").read_text()
assert "how-the-platform-works" not in readme, \
"README still references retired 'how-the-platform-works' deck"
assert "the-developer-experience" not in readme, \
"README still references retired 'the-developer-experience' deck"
def test_readme_no_old_deck_name():
"""REQ-245: README references the new deck name, not the old one."""
readme = (PRESENTATIONS / "README.md").read_text()
assert "nova-autonomous-cloud-delivery" in readme, \
"README does not reference nova-autonomous-cloud-delivery"
def test_old_deck_files_removed():
"""REQ-245: the old nova-no-humans-platform* files are gone."""
old_files = sorted(PRESENTATIONS.glob("nova-no-humans-platform*"))
assert not old_files, f"old deck files still present: {old_files}"
def test_marp_deck_no_badges():
"""REQ-252: no maturity badges in the Marp deck."""
text = MARP_DECK.read_text()
assert "badge" not in text, "Marp deck still contains badge spans"
def test_marp_deck_no_version_in_footer():
"""REQ-251: no version (v1.x) in the Marp frontmatter footer/header."""
text = MARP_DECK.read_text()
fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL)
assert fm_match, "Marp frontmatter not found"
frontmatter = fm_match.group(1)
# No v1.x version string in the footer or header lines
assert not re.search(r"v1\.\d+", frontmatter), \
f"Marp frontmatter still contains a version: {frontmatter}"
# No "Act" pagination artifact
assert "Act %" not in frontmatter, \
"Marp frontmatter still contains 'Act %{page}' artifact"
def test_marp_deck_title_slide_no_version_subtitle():
"""REQ-251: the title slide does not carry a version subtitle."""
text = MARP_DECK.read_text()
# The title slide is the first slide after the frontmatter
# Find the title block (between the frontmatter and the first --- separator)
after_fm = text.split("---\n", 2)[2] if text.startswith("---") else text
first_slide = after_fm.split("\n---\n")[0]
# The old subtitle was "v1.18 — Citizen Developer & Production-Grade Guidance"
assert "v1.18" not in first_slide, \
"Title slide still contains 'v1.18' subtitle"
assert "Citizen Developer & Production-Grade Guidance" not in first_slide, \
"Title slide still contains the old version subtitle"
def test_marp_deck_title_is_autonomous_cloud_delivery():
"""REQ-245: the deck title is 'Nova — The Autonomous Cloud Delivery Platform'."""
text = MARP_DECK.read_text()
assert "Autonomous Cloud Delivery Platform" in text, \
"Deck title is not 'Autonomous Cloud Delivery Platform'"
# The old title should not appear in the audience-facing deck
# (speaker notes are not in the marp deck, so this is safe)
assert "No-Humans Infrastructure Platform" not in text, \
"Deck still carries the old 'No-Humans Infrastructure Platform' title"
def test_marp_deck_slide_count():
"""REQ-245/261: 20 main slides + 1 appendix = 21 slides total.
v1.22 split slides 3 (Objectives+Anti-Goals) and 8 (Attestation
Matrix) to relieve overflow, increasing the count from 18 to 20."""
text = MARP_DECK.read_text()
# Count slide separators: each slide ends with --- (except the last)
# The frontmatter is one --- ... --- block, then each slide is separated by ---
# Count "## Slide" and "## Appendix" headings
slide_headings = re.findall(r"^## (?:Slide|Appendix) ", text, re.MULTILINE)
main_slides = re.findall(r"^## Slide ", text, re.MULTILINE)
appendix_slides = re.findall(r"^## Appendix ", text, re.MULTILINE)
assert len(main_slides) == 20, \
f"expected 20 main slides, found {len(main_slides)}: {slide_headings}"
assert len(appendix_slides) == 1, \
f"expected 1 appendix slide, found {len(appendix_slides)}"
def test_marp_deck_no_internal_citations():
"""REQ-252: no D-### decision IDs, REQ-### requirement IDs, or internal
.py file paths in the audience-facing Marp deck."""
text = MARP_DECK.read_text()
# Decision IDs like D-121, D-083
assert not re.search(r"\bD-\d{3}\b", text), \
"Marp deck contains D-### decision IDs"
# Requirement IDs like REQ-245
assert not re.search(r"\bREQ-\d{3}\b", text), \
"Marp deck contains REQ-### requirement IDs"
# Internal python file paths like outbox_writer.py, confidence_signal.py
# (allow .py only inside code blocks for the ROI formula? No — the deck
# should not cite internal file paths at all)
assert not re.search(r"\b(outbox_writer|confidence_signal|hitl_gates|"
r"attestation_matrix|checkov_adapter|infracost_adapter|"
r"contract_resolver|run_platform)\.py\b", text), \
"Marp deck contains internal .py file paths"
def test_source_md_no_internal_citations_in_slides():
"""REQ-252: the source-of-truth markdown keeps internal citations only
in speaker notes, not in the audience-facing slide body. Speaker notes
are blockquoted (> ) — we check non-blockquote lines for D-###/REQ-###."""
text = SOURCE_MD.read_text()
# Split into lines; exclude blockquote lines (speaker notes) and the
# header frontmatter (> ... at the top)
in_note = False
body_lines = []
for line in text.splitlines():
if line.lstrip().startswith(">"):
in_note = True
continue
if in_note and line.strip() == "":
in_note = False
continue
if not in_note:
body_lines.append(line)
body = "\n".join(body_lines)
# Decision IDs and REQ IDs should not appear in the slide body
assert not re.search(r"\bD-\d{3}\b", body), \
"Source markdown slide body contains D-### decision IDs"
assert not re.search(r"\bREQ-\d{3}\b", body), \
"Source markdown slide body contains REQ-### requirement IDs"
def test_source_md_no_badges():
"""REQ-252: no maturity badges in the source-of-truth markdown."""
text = SOURCE_MD.read_text()
assert "badge" not in text.lower(), \
"Source markdown still contains badge spans"
# --- v1.22 layout/aspect-ratio/theme-structural tests (REQ-262) ---
def test_theme_css_has_section_padding():
"""REQ-254: theme CSS has a section padding rule (root cause fix)."""
css = THEME_CSS.read_text()
assert "padding:" in css, "theme CSS has no padding rule"
# The section rule must have padding (not just table/td padding)
assert re.search(r"section\s*\{[^}]*padding:", css, re.DOTALL), \
"theme CSS has no padding on the section rule"
def test_theme_css_suppresses_title_chrome():
"""REQ-256: title slides suppress header/footer chrome."""
css = THEME_CSS.read_text()
assert "section.title header" in css, \
"theme CSS does not suppress title-slide header"
assert "section.title footer" in css, \
"theme CSS does not suppress title-slide footer"
assert "display: none" in css, \
"theme CSS does not set display:none on title chrome"
def test_theme_css_has_aspect_ratio_aware_images():
"""REQ-255: image rules use object-fit + max-width (not blunt max-height only)."""
css = THEME_CSS.read_text()
assert "object-fit" in css, \
"theme CSS does not use object-fit for images"
assert "max-width" in css, \
"theme CSS does not set max-width for images"
def test_png_aspect_ratios_sane():
"""REQ-259/260: PNGs referenced in the marp deck have aspect ratios
in [0.4, 4.0] (suitable for 16:9 slides with img.tall/img.wide classes).
Only checks PNGs actually referenced in the current marp deck —
legacy/unused PNGs are not checked (GRILL revision 1)."""
import struct
deck_text = MARP_DECK.read_text()
# Extract all referenced PNG paths: ![...](assets/png/X.png)
referenced = re.findall(r'!\[[^\]]*\]\(assets/png/([^)]+\.png)\)', deck_text)
assert referenced, "no PNGs referenced in the marp deck"
for png_name in referenced:
png_path = ASSETS / "png" / png_name
assert png_path.is_file(), f"referenced PNG not found: {png_name}"
with open(png_path, "rb") as fh:
data = fh.read(24)
assert data[:8] == b"\x89PNG\r\n\x1a\n", f"{png_name} is not a PNG"
w = struct.unpack(">I", data[16:20])[0]
h = struct.unpack(">I", data[20:24])[0]
ar = w / h
assert 0.4 <= ar <= 4.0, \
f"{png_name} aspect ratio {ar:.2f} outside [0.4, 4.0] ({w}x{h})"
def test_render_slides_has_2x_scale():
"""REQ-258: render_slides.sh uses -s 2 (2x scale) and -b transparent."""
text = RENDER_SCRIPT.read_text()
assert "-s 2" in text, "render_slides.sh does not use -s 2 (2x scale)"
assert "-b transparent" in text, \
"render_slides.sh does not use -b transparent"
def test_render_slides_pins_cli_versions():
"""REQ-257: render_slides.sh pins marp-cli and mermaid-cli versions
(no @latest)."""
text = RENDER_SCRIPT.read_text()
assert "marp-cli@" in text, "render_slides.sh does not pin marp-cli"
assert "mermaid-cli@" in text, \
"render_slides.sh does not pin mermaid-cli"
assert "@latest" not in text, \
"render_slides.sh still uses @latest (not pinned)"
def test_render_deck_removed():
"""REQ-257: render_deck.sh has been deleted (produced unthemed output)."""
old_script = ROOT / "scripts" / "render_deck.sh"
assert not old_script.exists(), \
"render_deck.sh still exists (should be deleted — produced unthemed output)"
def test_html_embeds_theme():
"""REQ-262: the committed HTML embeds the S&P theme (--sp-red + padding
in the inline <style> block)."""
html = (PRESENTATIONS / "nova-autonomous-cloud-delivery.html").read_text()
assert "--sp-red" in html, "committed HTML does not embed --sp-red"
assert "padding:" in html, "committed HTML does not embed padding rule"
def test_html_slide_count_matches_marp():
"""REQ-262: the committed HTML <section> count matches the marp deck
slide count (title + 20 main + 1 appendix = 22)."""
html = (PRESENTATIONS / "nova-autonomous-cloud-delivery.html").read_text()
section_count = html.count("<section ")
deck_text = MARP_DECK.read_text()
main_slides = len(re.findall(r"^## Slide ", deck_text, re.MULTILINE))
appendix_slides = len(re.findall(r"^## Appendix ", deck_text, re.MULTILINE))
# +1 for the title slide (which is an H1, not "## Slide")
expected = main_slides + appendix_slides + 1
assert section_count == expected, \
f"HTML has {section_count} sections, expected {expected} " \
f"({main_slides} main + {appendix_slides} appendix + 1 title)"